Merge pull request 'Add GamePhase handling and DeltaBoard' (#4) from dev into master
Reviewed-on: #4
This commit is contained in:
commit
adc81b9232
11 changed files with 661 additions and 275 deletions
6
src/overlay/elements/leaderboard/deltaboard_grid.xml
Normal file
6
src/overlay/elements/leaderboard/deltaboard_grid.xml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml-model href="../gui.xsd" type="application/xml" schematypes="http://www.w3.org/2001/XMLSchema"?>
|
||||||
|
<root reference_width="2560" reference_height="1440">
|
||||||
|
<!-- max 5 entries, each entry gets 39 pixel (at 1440p) -->
|
||||||
|
<grid id="main_grid" x_dim="1" y_dim="5" x_offset="-410" y_offset="10" width="400" height="195"
|
||||||
|
vert_align="top" hori_align="right" margin="0" padding="0"> </grid>
|
||||||
|
</root>
|
211
src/overlay/elements/leaderboard/leaderboard_entry.rs
Normal file
211
src/overlay/elements/leaderboard/leaderboard_entry.rs
Normal file
|
@ -0,0 +1,211 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
use ui::prelude::*;
|
||||||
|
use utilities::prelude::Color;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
|
||||||
|
pub enum BehindLeader {
|
||||||
|
Time(f64),
|
||||||
|
Laps(i32),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct LeaderBoardEntry {
|
||||||
|
id: i32,
|
||||||
|
|
||||||
|
name: String,
|
||||||
|
place: u8,
|
||||||
|
behind: BehindLeader,
|
||||||
|
time_behind_next: f64,
|
||||||
|
best_lap: f64,
|
||||||
|
|
||||||
|
snippet: Arc<GuiSnippet>,
|
||||||
|
|
||||||
|
name_label: Arc<Label>,
|
||||||
|
place_label: Arc<Label>,
|
||||||
|
time_label: Arc<Label>,
|
||||||
|
|
||||||
|
place_updated: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LeaderBoardEntry {
|
||||||
|
const ENTRY: &str = include_str!("leaderboard_entry.xml");
|
||||||
|
|
||||||
|
pub fn empty(gui_handler: &Arc<GuiHandler>) -> Result<Self> {
|
||||||
|
Self::new(
|
||||||
|
gui_handler,
|
||||||
|
-1,
|
||||||
|
"".to_string(),
|
||||||
|
0,
|
||||||
|
BehindLeader::Laps(0),
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(
|
||||||
|
gui_handler: &Arc<GuiHandler>,
|
||||||
|
id: i32,
|
||||||
|
name: String,
|
||||||
|
place: u8,
|
||||||
|
behind: BehindLeader,
|
||||||
|
time_behind_next: f64,
|
||||||
|
best_lap: f64,
|
||||||
|
) -> Result<Self> {
|
||||||
|
let snippet = GuiSnippet::from_str(gui_handler, Self::ENTRY)?;
|
||||||
|
|
||||||
|
let name_label: Arc<Label> = snippet.element("name")?;
|
||||||
|
let place_label: Arc<Label> = snippet.element("place")?;
|
||||||
|
let time_label: Arc<Label> = snippet.element("time")?;
|
||||||
|
|
||||||
|
name_label.set_text(&name)?;
|
||||||
|
place_label.set_text(place)?;
|
||||||
|
time_label.set_text("---")?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
id,
|
||||||
|
|
||||||
|
name,
|
||||||
|
place,
|
||||||
|
behind,
|
||||||
|
time_behind_next,
|
||||||
|
best_lap,
|
||||||
|
|
||||||
|
snippet,
|
||||||
|
|
||||||
|
name_label,
|
||||||
|
place_label,
|
||||||
|
time_label,
|
||||||
|
|
||||||
|
place_updated: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn change_id(&mut self, id: i32) {
|
||||||
|
self.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn id(&self) -> i32 {
|
||||||
|
self.id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn place(&self) -> u8 {
|
||||||
|
self.place
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn name(&self) -> &str {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn change_name(&mut self, name: String) -> Result<()> {
|
||||||
|
self.name = name;
|
||||||
|
|
||||||
|
self.name_label.set_text(&self.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn snippet(&self) -> Arc<GuiSnippet> {
|
||||||
|
self.snippet.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn change_background_color(&self, color: Color) -> Result<()> {
|
||||||
|
self.time_label.set_background(color)?;
|
||||||
|
self.name_label.set_background(color)?;
|
||||||
|
self.place_label.set_background(color)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_place(&mut self, place: u8) -> Result<()> {
|
||||||
|
if self.place != place {
|
||||||
|
self.place_updated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.place = place;
|
||||||
|
|
||||||
|
self.place_label.set_text(self.place)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_time_behind_leader(&mut self, behind: BehindLeader) -> Result<()> {
|
||||||
|
if self.behind != behind {
|
||||||
|
self.behind = behind;
|
||||||
|
|
||||||
|
match self.behind {
|
||||||
|
BehindLeader::Time(time_behind) => {
|
||||||
|
// check if we are leader
|
||||||
|
if time_behind == 0.0 {
|
||||||
|
self.time_label.set_text("---")?;
|
||||||
|
} else {
|
||||||
|
let text = if time_behind > 60.0 {
|
||||||
|
let full_minutes = (self.best_lap / 60.0).floor();
|
||||||
|
let remainder = self.best_lap - (full_minutes * 60.0);
|
||||||
|
|
||||||
|
format!("+{:.0}:{:.0}", full_minutes, remainder)
|
||||||
|
} else {
|
||||||
|
format!("+{:.3}", time_behind)
|
||||||
|
};
|
||||||
|
|
||||||
|
self.time_label.set_text(text)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BehindLeader::Laps(laps_behind) => {
|
||||||
|
let text = if laps_behind == 1 {
|
||||||
|
format!("+{} Lap", laps_behind)
|
||||||
|
} else {
|
||||||
|
format!("+{} Laps", laps_behind)
|
||||||
|
};
|
||||||
|
|
||||||
|
self.time_label.set_text(text)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_best_lap(&mut self, time: f64) -> Result<()> {
|
||||||
|
if self.best_lap != time {
|
||||||
|
self.best_lap = time;
|
||||||
|
|
||||||
|
if self.best_lap < 0.0 {
|
||||||
|
self.time_label.set_text("---")?;
|
||||||
|
} else {
|
||||||
|
let text = if self.best_lap > 60.0 {
|
||||||
|
let full_minutes = (self.best_lap / 60.0).floor();
|
||||||
|
let remainder = self.best_lap - (full_minutes * 60.0);
|
||||||
|
|
||||||
|
format!("{:.0}:{:.3}", full_minutes, remainder)
|
||||||
|
} else {
|
||||||
|
format!("{:.3}", self.best_lap)
|
||||||
|
};
|
||||||
|
|
||||||
|
self.time_label.set_text(text)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_time_behind_next(&mut self, time: f64) -> Result<()> {
|
||||||
|
self.time_behind_next = time;
|
||||||
|
|
||||||
|
let text = if self.time_behind_next > 60.0 {
|
||||||
|
let full_minutes = (self.time_behind_next / 60.0).floor();
|
||||||
|
let remainder = self.time_behind_next - (full_minutes * 60.0);
|
||||||
|
|
||||||
|
format!("+{:.0}:{:.3}", full_minutes, remainder)
|
||||||
|
} else {
|
||||||
|
format!("+{:.3}", self.time_behind_next)
|
||||||
|
};
|
||||||
|
|
||||||
|
self.time_label.set_text(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn needs_resorting(&self) -> bool {
|
||||||
|
self.place_updated
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resorting_finished(&mut self) {
|
||||||
|
self.place_updated = false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,12 +1,12 @@
|
||||||
<?xml-model href="../gui.xsd" type="application/xml" schematypens="http://www.w3.org/2001/XMLSchema"?>
|
<?xml-model href="../gui.xsd" type="application/xml" schematypes="http://www.w3.org/2001/XMLSchema"?>
|
||||||
<root>
|
<root>
|
||||||
<grid id="grid" x_dim="9" y_dim="1">
|
<grid x_dim="11" y_dim="1" padding="2" margin="2">
|
||||||
<label id="place" x_slot="0" y_slot="0" text_color="black" text_alignment="right"></label>
|
<label id="place" x_slot="0" y_slot="0" text_color="black" text_alignment="right"></label>
|
||||||
<label
|
<label
|
||||||
id="name"
|
id="name"
|
||||||
x_slot="1" y_slot="0" x_size="6" text_color="black" text_alignment="left"></label>
|
x_slot="1" y_slot="0" x_size="7" text_color="black"></label>
|
||||||
<label
|
<label
|
||||||
id="time_behind"
|
id="time"
|
||||||
x_slot="7" y_slot="0" x_size="2" text_color="black" text_alignment="right"></label>
|
x_slot="8" y_slot="0" x_size="3" text_color="black" text_alignment="right"></label>
|
||||||
</grid>
|
</grid>
|
||||||
</root>
|
</root>
|
|
@ -1,5 +1,6 @@
|
||||||
<?xml-model href="../gui.xsd" type="application/xml" schematypens="http://www.w3.org/2001/XMLSchema"?>
|
<?xml-model href="../gui.xsd" type="application/xml" schematypes="http://www.w3.org/2001/XMLSchema"?>
|
||||||
<root reference_width="2560" reference_height="1440">
|
<root reference_width="2560" reference_height="1440">
|
||||||
<grid id="main_grid" x_dim="1" y_dim="25" x_offset="10" y_offset="10" width="350" height="875"
|
<!-- max 25 entries, each entry gets 39 pixel (at 1440p) -->
|
||||||
|
<grid id="main_grid" x_dim="1" y_dim="25" x_offset="10" y_offset="10" width="400" height="975"
|
||||||
vert_align="top" hori_align="left" margin="0" padding="0"> </grid>
|
vert_align="top" hori_align="left" margin="0" padding="0"> </grid>
|
||||||
</root>
|
</root>
|
|
@ -1,3 +1,7 @@
|
||||||
|
mod leaderboard_entry;
|
||||||
|
|
||||||
|
use leaderboard_entry::*;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
ffi::{c_char, CStr},
|
ffi::{c_char, CStr},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
|
@ -16,30 +20,51 @@ use crate::write_log;
|
||||||
|
|
||||||
pub struct LeaderBoard {
|
pub struct LeaderBoard {
|
||||||
gui_handler: Arc<GuiHandler>,
|
gui_handler: Arc<GuiHandler>,
|
||||||
gui: Arc<GuiBuilder>,
|
leaderboard: Arc<GuiBuilder>,
|
||||||
main_grid: Arc<Grid>,
|
deltaboard: Arc<GuiBuilder>,
|
||||||
|
|
||||||
entries: Vec<LeaderBoardEntry>,
|
leaderboard_grid: Arc<Grid>,
|
||||||
|
deltaboard_grid: Arc<Grid>,
|
||||||
|
|
||||||
|
leaderboard_entries: Vec<LeaderBoardEntry>,
|
||||||
|
deltaboard_entries: [LeaderBoardEntry; 5],
|
||||||
|
|
||||||
|
leaderboard_redraw: bool,
|
||||||
|
last_player_id: i32,
|
||||||
|
|
||||||
entry_backgrounds: [Color; 2],
|
entry_backgrounds: [Color; 2],
|
||||||
player_background: Color,
|
player_background: Color,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LeaderBoard {
|
impl LeaderBoard {
|
||||||
const GRID: &str = include_str!("leaderboard_grid.xml");
|
const LEADERBOARD: &str = include_str!("leaderboard_grid.xml");
|
||||||
const ENTRY: &str = include_str!("leaderboard_entry.xml");
|
const DELTABOARD: &str = include_str!("deltaboard_grid.xml");
|
||||||
|
|
||||||
pub fn new(gui_handler: &Arc<GuiHandler>) -> Result<Self> {
|
pub fn new(gui_handler: &Arc<GuiHandler>) -> Result<Self> {
|
||||||
let gui = GuiBuilder::from_str(gui_handler, Self::GRID)?;
|
let leaderboard = GuiBuilder::from_str(gui_handler, Self::LEADERBOARD)?;
|
||||||
|
let deltaboard = GuiBuilder::from_str(gui_handler, Self::DELTABOARD)?;
|
||||||
|
|
||||||
let main_grid = gui.element("main_grid")?;
|
let leaderboard_grid = leaderboard.element("main_grid")?;
|
||||||
|
let deltaboard_grid = deltaboard.element("main_grid")?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
gui_handler: gui_handler.clone(),
|
gui_handler: gui_handler.clone(),
|
||||||
gui,
|
leaderboard,
|
||||||
main_grid,
|
deltaboard,
|
||||||
|
leaderboard_grid,
|
||||||
|
deltaboard_grid,
|
||||||
|
|
||||||
entries: Vec::new(),
|
leaderboard_entries: Vec::new(),
|
||||||
|
deltaboard_entries: [
|
||||||
|
LeaderBoardEntry::empty(gui_handler)?,
|
||||||
|
LeaderBoardEntry::empty(gui_handler)?,
|
||||||
|
LeaderBoardEntry::empty(gui_handler)?,
|
||||||
|
LeaderBoardEntry::empty(gui_handler)?,
|
||||||
|
LeaderBoardEntry::empty(gui_handler)?,
|
||||||
|
],
|
||||||
|
|
||||||
|
leaderboard_redraw: false,
|
||||||
|
last_player_id: -1,
|
||||||
|
|
||||||
entry_backgrounds: [Color::try_from("#838383")?, Color::try_from("#545454")?],
|
entry_backgrounds: [Color::try_from("#838383")?, Color::try_from("#545454")?],
|
||||||
player_background: Color::try_from("#b4bf26")?,
|
player_background: Color::try_from("#b4bf26")?,
|
||||||
|
@ -53,13 +78,20 @@ impl LeaderBoard {
|
||||||
.to_string()
|
.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn race_leaderboard(&mut self, vehicle_scorings: &[VehicleScoringInfoV01]) -> Result<()> {
|
fn update_leaderboard<F>(
|
||||||
|
&mut self,
|
||||||
|
vehicle_scorings: &[VehicleScoringInfoV01],
|
||||||
|
f: F,
|
||||||
|
) -> Result<()>
|
||||||
|
where
|
||||||
|
F: Fn(&mut LeaderBoardEntry, &VehicleScoringInfoV01) -> Result<()>,
|
||||||
|
{
|
||||||
for vehicle_scoring in vehicle_scorings {
|
for vehicle_scoring in vehicle_scorings {
|
||||||
let driver_name = Self::c_char_to_string(vehicle_scoring.mDriverName);
|
let driver_name = Self::c_char_to_string(vehicle_scoring.mDriverName);
|
||||||
|
|
||||||
// check driver list
|
// check driver list
|
||||||
match self
|
match self
|
||||||
.entries
|
.leaderboard_entries
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.find(|entry| vehicle_scoring.mID == entry.id())
|
.find(|entry| vehicle_scoring.mID == entry.id())
|
||||||
{
|
{
|
||||||
|
@ -69,8 +101,8 @@ impl LeaderBoard {
|
||||||
}
|
}
|
||||||
|
|
||||||
entry.update_place(vehicle_scoring.mPlace)?;
|
entry.update_place(vehicle_scoring.mPlace)?;
|
||||||
entry.update_time_behind_leader(vehicle_scoring.mTimeBehindLeader)?;
|
|
||||||
entry.update_time_behind_next(vehicle_scoring.mTimeBehindNext)?;
|
f(entry, vehicle_scoring)?;
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
let entry = LeaderBoardEntry::new(
|
let entry = LeaderBoardEntry::new(
|
||||||
|
@ -78,26 +110,39 @@ impl LeaderBoard {
|
||||||
vehicle_scoring.mID,
|
vehicle_scoring.mID,
|
||||||
driver_name,
|
driver_name,
|
||||||
vehicle_scoring.mPlace,
|
vehicle_scoring.mPlace,
|
||||||
vehicle_scoring.mTimeBehindLeader,
|
{
|
||||||
|
let laps_behind = vehicle_scoring.mLapsBehindLeader;
|
||||||
|
|
||||||
|
if laps_behind != 0 {
|
||||||
|
BehindLeader::Laps(laps_behind)
|
||||||
|
} else {
|
||||||
|
BehindLeader::Time(vehicle_scoring.mTimeBehindLeader)
|
||||||
|
}
|
||||||
|
},
|
||||||
vehicle_scoring.mTimeBehindNext,
|
vehicle_scoring.mTimeBehindNext,
|
||||||
|
vehicle_scoring.mBestLapTime,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
self.entries.push(entry);
|
self.leaderboard_entries.push(entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: when people disconnect there needs to be a remove function
|
||||||
|
// vehicle_scorings.len() != self.leaderboard_entries.len()
|
||||||
|
|
||||||
write_log!("create entries");
|
write_log!("create entries");
|
||||||
|
|
||||||
// check if entry count in grid is the same as the gathered entries
|
// check if entry count in grid is the same as the gathered entries
|
||||||
let force_update = if !self.entries.is_empty()
|
let force_update = if !self.leaderboard_entries.is_empty()
|
||||||
&& self
|
&& self
|
||||||
.main_grid
|
.leaderboard_grid
|
||||||
.child_at(0, self.entries.len() - 1)?
|
.child_at(0, self.leaderboard_entries.len() - 1)?
|
||||||
.is_none()
|
.is_none()
|
||||||
{
|
{
|
||||||
for i in 0..self.entries.len() {
|
for (i, entry) in self.leaderboard_entries.iter().enumerate() {
|
||||||
self.main_grid.detach(0, i)?;
|
self.leaderboard_grid.detach(0, i)?;
|
||||||
|
entry.snippet().set_visibility(false)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
true
|
true
|
||||||
|
@ -106,33 +151,154 @@ impl LeaderBoard {
|
||||||
};
|
};
|
||||||
|
|
||||||
// check if any entry needs resorting
|
// check if any entry needs resorting
|
||||||
if force_update || self.entries.iter().any(|entry| entry.needs_resorting()) {
|
if force_update
|
||||||
|
|| self
|
||||||
|
.leaderboard_entries
|
||||||
|
.iter()
|
||||||
|
.any(|entry| entry.needs_resorting())
|
||||||
|
{
|
||||||
write_log!("leader board update required");
|
write_log!("leader board update required");
|
||||||
|
|
||||||
self.entries
|
self.leaderboard_entries
|
||||||
.sort_by(|lhs, rhs| lhs.place().cmp(&rhs.place()));
|
.sort_by(|lhs, rhs| lhs.place().cmp(&rhs.place()));
|
||||||
|
|
||||||
for (i, entry) in self.entries.iter_mut().enumerate() {
|
for (i, entry) in self.leaderboard_entries.iter_mut().enumerate() {
|
||||||
entry.resorting_finished();
|
entry.resorting_finished();
|
||||||
entry.change_background_color(self.entry_backgrounds[i % 2])?;
|
|
||||||
|
|
||||||
self.main_grid.attach(entry.snippet(), 0, i, 1, 1)?;
|
// don't break here, just skip adding to grid
|
||||||
|
// because resorting_finished should be called for every entry
|
||||||
|
if i < self.leaderboard_grid.dimensions().1 {
|
||||||
|
entry.change_background_color(self.entry_backgrounds[i % 2])?;
|
||||||
|
|
||||||
|
self.leaderboard_grid.attach(entry.snippet(), 0, i, 1, 1)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.leaderboard_redraw = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update delta board
|
||||||
|
if self.last_player_id != -1 {
|
||||||
|
write_log!("update delta board");
|
||||||
|
|
||||||
|
if let Some((index, _player_entry)) = self
|
||||||
|
.leaderboard_entries
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.find(|(_index, entry)| entry.id() == self.last_player_id)
|
||||||
|
{
|
||||||
|
let mut start_index = if index >= 2 { index - 2 } else { 0 };
|
||||||
|
|
||||||
|
let max = self.leaderboard_entries.len().min(5);
|
||||||
|
|
||||||
|
write_log!(format!(
|
||||||
|
"Delta Board: start {} - count {}",
|
||||||
|
start_index, max
|
||||||
|
));
|
||||||
|
|
||||||
|
// clear old entries
|
||||||
|
for i in 0..5 {
|
||||||
|
if let Some(child) = self.deltaboard_grid.detach(0, i)? {
|
||||||
|
if let Some(visiblity) = child.visibility() {
|
||||||
|
visiblity.set_visibility(false)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add new entries
|
||||||
|
for i in 0..max {
|
||||||
|
if let Some(leaderboard_entry) = self.leaderboard_entries.get(start_index) {
|
||||||
|
if let Some(entry) = self.deltaboard_entries.get_mut(i) {
|
||||||
|
entry.change_id(leaderboard_entry.id());
|
||||||
|
entry.update_place(leaderboard_entry.place())?;
|
||||||
|
|
||||||
|
if entry.id() == self.last_player_id {
|
||||||
|
entry.change_background_color(self.player_background)?;
|
||||||
|
} else {
|
||||||
|
entry.change_background_color(self.entry_backgrounds[i % 2])?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if entry.name() != leaderboard_entry.name() {
|
||||||
|
entry.change_name(leaderboard_entry.name().to_string())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(vehicle_scoring) = vehicle_scorings
|
||||||
|
.iter()
|
||||||
|
.find(|scoring| scoring.mID == entry.id())
|
||||||
|
{
|
||||||
|
entry.update_time_behind_next(vehicle_scoring.mTimeBehindNext)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.deltaboard_grid.attach(entry.snippet(), 0, i, 1, 1)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
start_index += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.entries.is_empty() {
|
|
||||||
self.gui.disable()?;
|
|
||||||
} else {
|
|
||||||
self.gui.enable()?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn race_leaderboard(&mut self, vehicle_scorings: &[VehicleScoringInfoV01]) -> Result<()> {
|
||||||
|
self.update_leaderboard(vehicle_scorings, |entry, scoring| {
|
||||||
|
let laps_behind = scoring.mLapsBehindLeader;
|
||||||
|
|
||||||
|
if laps_behind != 0 {
|
||||||
|
entry.update_time_behind_leader(BehindLeader::Laps(laps_behind))
|
||||||
|
} else {
|
||||||
|
entry.update_time_behind_leader(BehindLeader::Time(scoring.mTimeBehindLeader))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn quali_leaderboard(&mut self, vehicle_scorings: &[VehicleScoringInfoV01]) -> Result<()> {
|
||||||
|
self.update_leaderboard(vehicle_scorings, |entry, scoring| {
|
||||||
|
entry.update_best_lap(scoring.mBestLapTime)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UiOverlay for LeaderBoard {}
|
impl UiOverlay for LeaderBoard {}
|
||||||
|
|
||||||
impl DataReceiver for LeaderBoard {
|
impl DataReceiver for LeaderBoard {
|
||||||
|
fn game_phase_change(&mut self, phase: GamePhase) -> Result<()> {
|
||||||
|
match phase {
|
||||||
|
GamePhase::Practice | GamePhase::Qualifying => {
|
||||||
|
for entry in self.leaderboard_entries.iter_mut() {
|
||||||
|
entry.update_best_lap(-1.0)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.leaderboard.enable()?;
|
||||||
|
self.deltaboard.enable()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
GamePhase::Race => {
|
||||||
|
for entry in self.leaderboard_entries.iter_mut() {
|
||||||
|
entry.update_time_behind_leader(BehindLeader::Time(0.0))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.leaderboard.enable()?;
|
||||||
|
self.deltaboard.enable()?;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
self.last_player_id = -1;
|
||||||
|
self.leaderboard.disable()?;
|
||||||
|
self.deltaboard.disable()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_for_phase(&self, phase: GamePhase) -> bool {
|
||||||
|
match phase {
|
||||||
|
GamePhase::Practice | GamePhase::Qualifying | GamePhase::Race => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn scoring_update(
|
fn scoring_update(
|
||||||
&mut self,
|
&mut self,
|
||||||
phase: GamePhase,
|
phase: GamePhase,
|
||||||
|
@ -141,13 +307,15 @@ impl DataReceiver for LeaderBoard {
|
||||||
write_log!("=================== leader board: scoring update ===================");
|
write_log!("=================== leader board: scoring update ===================");
|
||||||
|
|
||||||
match phase {
|
match phase {
|
||||||
GamePhase::TestDay => self.gui.disable()?,
|
GamePhase::Practice | GamePhase::Qualifying => {
|
||||||
GamePhase::Practice => self.gui.disable()?,
|
self.quali_leaderboard(vehicle_scorings)?;
|
||||||
GamePhase::Qualifying => self.gui.disable()?,
|
}
|
||||||
GamePhase::Warmup => self.gui.disable()?,
|
|
||||||
GamePhase::Race => {
|
GamePhase::Race => {
|
||||||
self.race_leaderboard(vehicle_scorings)?;
|
self.race_leaderboard(vehicle_scorings)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
write_log!("leader board update finished");
|
write_log!("leader board update finished");
|
||||||
|
@ -160,132 +328,28 @@ impl DataReceiver for LeaderBoard {
|
||||||
player_id: Option<i32>,
|
player_id: Option<i32>,
|
||||||
_telemetries: &[rF2VehicleTelemetry],
|
_telemetries: &[rF2VehicleTelemetry],
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
if let Some(player_id) = player_id {
|
if self.leaderboard_redraw {
|
||||||
if let Some(entry) = self.entries.iter().find(|entry| entry.id() == player_id) {
|
self.leaderboard_redraw = false;
|
||||||
entry.change_background_color(self.player_background)?;
|
|
||||||
|
if let Some(player_id) = player_id {
|
||||||
|
if let Some(entry) = self
|
||||||
|
.leaderboard_entries
|
||||||
|
.iter()
|
||||||
|
.find(|entry| entry.id() == player_id)
|
||||||
|
{
|
||||||
|
write_log!(format!(
|
||||||
|
"Update player entry background color: {:?}",
|
||||||
|
self.player_background
|
||||||
|
));
|
||||||
|
entry.change_background_color(self.player_background)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(player_id) = player_id {
|
||||||
|
self.last_player_id = player_id;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LeaderBoardEntry {
|
|
||||||
id: i32,
|
|
||||||
|
|
||||||
name: String,
|
|
||||||
place: u8,
|
|
||||||
time_behind_leader: f64,
|
|
||||||
time_behind_next: f64,
|
|
||||||
|
|
||||||
snippet: Arc<GuiSnippet>,
|
|
||||||
|
|
||||||
grid: Arc<Grid>,
|
|
||||||
name_label: Arc<Label>,
|
|
||||||
place_label: Arc<Label>,
|
|
||||||
time_behind_label: Arc<Label>,
|
|
||||||
|
|
||||||
place_updated: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LeaderBoardEntry {
|
|
||||||
pub fn new(
|
|
||||||
gui_handler: &Arc<GuiHandler>,
|
|
||||||
id: i32,
|
|
||||||
name: String,
|
|
||||||
place: u8,
|
|
||||||
time_behind_leader: f64,
|
|
||||||
time_behind_next: f64,
|
|
||||||
) -> Result<Self> {
|
|
||||||
let snippet = GuiSnippet::from_str(gui_handler, LeaderBoard::ENTRY)?;
|
|
||||||
|
|
||||||
let background = snippet.element("grid")?;
|
|
||||||
let name_label: Arc<Label> = snippet.element("name")?;
|
|
||||||
let place_label: Arc<Label> = snippet.element("place")?;
|
|
||||||
let time_behind_label: Arc<Label> = snippet.element("time_behind")?;
|
|
||||||
|
|
||||||
name_label.set_text(&name)?;
|
|
||||||
place_label.set_text(place)?;
|
|
||||||
time_behind_label.set_text(format!("{:.3}", time_behind_leader))?;
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
id,
|
|
||||||
|
|
||||||
name,
|
|
||||||
place,
|
|
||||||
time_behind_leader,
|
|
||||||
time_behind_next,
|
|
||||||
|
|
||||||
snippet,
|
|
||||||
|
|
||||||
grid: background,
|
|
||||||
name_label,
|
|
||||||
place_label,
|
|
||||||
time_behind_label,
|
|
||||||
|
|
||||||
place_updated: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn id(&self) -> i32 {
|
|
||||||
self.id
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn place(&self) -> u8 {
|
|
||||||
self.place
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn name(&self) -> &str {
|
|
||||||
&self.name
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn change_name(&mut self, name: String) -> Result<()> {
|
|
||||||
self.name = name;
|
|
||||||
|
|
||||||
self.name_label.set_text(&self.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn snippet(&self) -> Arc<GuiSnippet> {
|
|
||||||
self.snippet.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn change_background_color(&self, color: Color) -> Result<()> {
|
|
||||||
self.grid.set_background(color)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_place(&mut self, place: u8) -> Result<()> {
|
|
||||||
if self.place != place {
|
|
||||||
self.place_updated = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.place = place;
|
|
||||||
|
|
||||||
self.place_label.set_text(self.place)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_time_behind_leader(&mut self, time: f64) -> Result<()> {
|
|
||||||
self.time_behind_leader = time;
|
|
||||||
|
|
||||||
// check if we are leader
|
|
||||||
if self.time_behind_leader == 0.0 {
|
|
||||||
self.time_behind_label.set_text("---")
|
|
||||||
} else {
|
|
||||||
self.time_behind_label
|
|
||||||
.set_text(format!("+{:.3}", self.time_behind_leader))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_time_behind_next(&mut self, time: f64) -> Result<()> {
|
|
||||||
self.time_behind_next = time;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn needs_resorting(&self) -> bool {
|
|
||||||
self.place_updated
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn resorting_finished(&mut self) {
|
|
||||||
self.place_updated = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -26,6 +26,8 @@ pub struct Pedals {
|
||||||
throttle: Arc<ProgressBar>,
|
throttle: Arc<ProgressBar>,
|
||||||
_history: Arc<Icon>,
|
_history: Arc<Icon>,
|
||||||
|
|
||||||
|
enable: bool,
|
||||||
|
|
||||||
throttle_samples: HeapRb<f32>,
|
throttle_samples: HeapRb<f32>,
|
||||||
brake_samples: HeapRb<f32>,
|
brake_samples: HeapRb<f32>,
|
||||||
|
|
||||||
|
@ -138,6 +140,8 @@ impl Pedals {
|
||||||
throttle,
|
throttle,
|
||||||
_history: history,
|
_history: history,
|
||||||
|
|
||||||
|
enable: false,
|
||||||
|
|
||||||
throttle_samples,
|
throttle_samples,
|
||||||
brake_samples,
|
brake_samples,
|
||||||
|
|
||||||
|
@ -202,7 +206,7 @@ impl Pedals {
|
||||||
let command_buffer =
|
let command_buffer =
|
||||||
CommandBuffer::new_primary().build(self.device.clone(), self.queue.clone())?;
|
CommandBuffer::new_primary().build(self.device.clone(), self.queue.clone())?;
|
||||||
|
|
||||||
{
|
if self.enable {
|
||||||
let mut recorder = command_buffer.begin(VkCommandBufferBeginInfo::new(
|
let mut recorder = command_buffer.begin(VkCommandBufferBeginInfo::new(
|
||||||
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT
|
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT
|
||||||
| VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT,
|
| VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT,
|
||||||
|
@ -231,6 +235,30 @@ impl Pedals {
|
||||||
impl UiOverlay for Pedals {}
|
impl UiOverlay for Pedals {}
|
||||||
|
|
||||||
impl DataReceiver for Pedals {
|
impl DataReceiver for Pedals {
|
||||||
|
fn game_phase_change(&mut self, phase: GamePhase) -> Result<()> {
|
||||||
|
match phase {
|
||||||
|
GamePhase::None | GamePhase::TestDay => {
|
||||||
|
self.enable = false;
|
||||||
|
self.gui.disable()?;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
self.enable = true;
|
||||||
|
self.gui.enable()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_for_phase(&self, phase: GamePhase) -> bool {
|
||||||
|
match phase {
|
||||||
|
GamePhase::Practice | GamePhase::Qualifying | GamePhase::Race | GamePhase::Warmup => {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn scoring_update(
|
fn scoring_update(
|
||||||
&mut self,
|
&mut self,
|
||||||
_phase: GamePhase,
|
_phase: GamePhase,
|
||||||
|
@ -244,25 +272,18 @@ impl DataReceiver for Pedals {
|
||||||
player_id: Option<i32>,
|
player_id: Option<i32>,
|
||||||
telemetries: &[rF2VehicleTelemetry],
|
telemetries: &[rF2VehicleTelemetry],
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
match player_id {
|
if let Some(id) = player_id {
|
||||||
Some(id) => {
|
if let Some(telemetry) = telemetries.iter().find(|telemetry| telemetry.id == id) {
|
||||||
self.gui.enable()?;
|
let brake = 1.0 - telemetry.unfiltered_brake as f32;
|
||||||
|
let throttle = 1.0 - telemetry.unfiltered_throttle as f32;
|
||||||
|
|
||||||
if let Some(telemetry) = telemetries.iter().find(|telemetry| telemetry.id == id) {
|
self.throttle.set_progress(throttle)?;
|
||||||
let brake = 1.0 - telemetry.unfiltered_brake as f32;
|
self.brake.set_progress(brake)?;
|
||||||
let throttle = 1.0 - telemetry.unfiltered_throttle as f32;
|
|
||||||
|
|
||||||
self.throttle.set_progress(throttle)?;
|
self.throttle_samples.push_overwrite(throttle);
|
||||||
self.brake.set_progress(brake)?;
|
self.brake_samples.push_overwrite(brake);
|
||||||
|
|
||||||
self.throttle_samples.push_overwrite(throttle);
|
self.update_vertex_buffers()?;
|
||||||
self.brake_samples.push_overwrite(brake);
|
|
||||||
|
|
||||||
self.update_vertex_buffers()?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
self.gui.disable()?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<?xml-model href="../gui.xsd" type="application/xml" schematypens="http://www.w3.org/2001/XMLSchema"?>
|
<?xml-model href="../gui.xsd" type="application/xml" schematypes="http://www.w3.org/2001/XMLSchema"?>
|
||||||
<root reference_width="2560" reference_height="1440">
|
<root reference_width="2560" reference_height="1440">
|
||||||
<grid x_dim="10" y_dim="2" x_offset="-850" y_offset="-190" width="200" height="160"
|
<grid x_dim="10" y_dim="2" x_offset="-850" y_offset="-190" width="200" height="160"
|
||||||
vert_align="bottom" hori_align="right" margin="3" padding="3" background="#686868">
|
vert_align="bottom" hori_align="right" margin="3" padding="3" background="#686868">
|
||||||
|
|
|
@ -73,6 +73,8 @@ pub struct Radar {
|
||||||
car_width: f32,
|
car_width: f32,
|
||||||
car_height: f32,
|
car_height: f32,
|
||||||
|
|
||||||
|
enable: bool,
|
||||||
|
|
||||||
device: Arc<Device>,
|
device: Arc<Device>,
|
||||||
queue: Arc<Mutex<Queue>>,
|
queue: Arc<Mutex<Queue>>,
|
||||||
|
|
||||||
|
@ -171,6 +173,8 @@ impl Radar {
|
||||||
car_width,
|
car_width,
|
||||||
car_height,
|
car_height,
|
||||||
|
|
||||||
|
enable: false,
|
||||||
|
|
||||||
device,
|
device,
|
||||||
queue,
|
queue,
|
||||||
|
|
||||||
|
@ -216,7 +220,7 @@ impl Radar {
|
||||||
let command_buffer =
|
let command_buffer =
|
||||||
CommandBuffer::new_primary().build(self.device.clone(), self.queue.clone())?;
|
CommandBuffer::new_primary().build(self.device.clone(), self.queue.clone())?;
|
||||||
|
|
||||||
{
|
if self.enable {
|
||||||
let mut recorder = command_buffer.begin(VkCommandBufferBeginInfo::new(
|
let mut recorder = command_buffer.begin(VkCommandBufferBeginInfo::new(
|
||||||
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT
|
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT
|
||||||
| VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT,
|
| VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT,
|
||||||
|
@ -268,6 +272,24 @@ impl Radar {
|
||||||
impl UiOverlay for Radar {}
|
impl UiOverlay for Radar {}
|
||||||
|
|
||||||
impl DataReceiver for Radar {
|
impl DataReceiver for Radar {
|
||||||
|
fn game_phase_change(&mut self, phase: GamePhase) -> Result<()> {
|
||||||
|
match phase {
|
||||||
|
GamePhase::None | GamePhase::TestDay => self.enable = false,
|
||||||
|
_ => self.enable = true,
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_for_phase(&self, phase: GamePhase) -> bool {
|
||||||
|
match phase {
|
||||||
|
GamePhase::Practice | GamePhase::Qualifying | GamePhase::Race | GamePhase::Warmup => {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn scoring_update(
|
fn scoring_update(
|
||||||
&mut self,
|
&mut self,
|
||||||
_phase: GamePhase,
|
_phase: GamePhase,
|
||||||
|
@ -286,68 +308,71 @@ impl DataReceiver for Radar {
|
||||||
|
|
||||||
self.cars.clear();
|
self.cars.clear();
|
||||||
|
|
||||||
if let Some(player_id) = player_id {
|
if self.enable {
|
||||||
// make sure there are enough cars in buffer
|
if let Some(player_id) = player_id {
|
||||||
if self.car_handles.len() < telemetries.len() {
|
// make sure there are enough cars in buffer
|
||||||
let size_diff = telemetries.len() - self.car_handles.len();
|
if self.car_handles.len() < telemetries.len() {
|
||||||
|
let size_diff = telemetries.len() - self.car_handles.len();
|
||||||
|
|
||||||
for _ in 0..size_diff {
|
for _ in 0..size_diff {
|
||||||
self.car_handles
|
self.car_handles
|
||||||
.push(self.create_car_object(vec2(0.0, 0.0), [0.0, 0.0, 0.0, 0.0])?);
|
.push(self.create_car_object(vec2(0.0, 0.0), [0.0, 0.0, 0.0, 0.0])?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut player_position = CarPosition::default();
|
||||||
|
let mut other_positions = Vec::new();
|
||||||
|
|
||||||
|
for telemetry in telemetries {
|
||||||
|
let car = CarPosition::new(
|
||||||
|
convert_vec(telemetry.position),
|
||||||
|
[
|
||||||
|
convert_vec(telemetry.orientation[0]),
|
||||||
|
convert_vec(telemetry.orientation[1]),
|
||||||
|
convert_vec(telemetry.orientation[2]),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
if telemetry.id == player_id {
|
||||||
|
player_position = car
|
||||||
|
} else {
|
||||||
|
other_positions.push(car);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// update radar objects
|
||||||
|
let mut buffer_car_index = 0;
|
||||||
|
|
||||||
|
for other_position in other_positions {
|
||||||
|
let diff = player_position.position - other_position.position;
|
||||||
|
let distance = diff.magnitude();
|
||||||
|
|
||||||
|
// check if car is close enough to the players car
|
||||||
|
if distance < self.config.radar_car_distance {
|
||||||
|
let offset =
|
||||||
|
diff.xz() * (self.radar_extent / self.config.radar_car_distance);
|
||||||
|
|
||||||
|
let buffered_car = self.car_handles[buffer_car_index].clone();
|
||||||
|
buffer_car_index += 1;
|
||||||
|
buffered_car.update(
|
||||||
|
self.ortho,
|
||||||
|
offset,
|
||||||
|
player_position.rotation,
|
||||||
|
other_position.rotation,
|
||||||
|
self.radar_center,
|
||||||
|
self.car_width,
|
||||||
|
self.car_height,
|
||||||
|
[0.9, 0.9, 0.0, 0.9],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
self.cars.push(buffered_car);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut player_position = CarPosition::default();
|
write_log!(format!("other cars: {:?}", self.cars.len()));
|
||||||
let mut other_positions = Vec::new();
|
|
||||||
|
|
||||||
for telemetry in telemetries {
|
|
||||||
let car = CarPosition::new(
|
|
||||||
convert_vec(telemetry.position),
|
|
||||||
[
|
|
||||||
convert_vec(telemetry.orientation[0]),
|
|
||||||
convert_vec(telemetry.orientation[1]),
|
|
||||||
convert_vec(telemetry.orientation[2]),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
if telemetry.id == player_id {
|
|
||||||
player_position = car
|
|
||||||
} else {
|
|
||||||
other_positions.push(car);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// update radar objects
|
|
||||||
let mut buffer_car_index = 0;
|
|
||||||
|
|
||||||
for other_position in other_positions {
|
|
||||||
let diff = player_position.position - other_position.position;
|
|
||||||
let distance = diff.magnitude();
|
|
||||||
|
|
||||||
// check if car is close enough to the players car
|
|
||||||
if distance < self.config.radar_car_distance {
|
|
||||||
let offset = diff.xz() * (self.radar_extent / self.config.radar_car_distance);
|
|
||||||
|
|
||||||
let buffered_car = self.car_handles[buffer_car_index].clone();
|
|
||||||
buffer_car_index += 1;
|
|
||||||
buffered_car.update(
|
|
||||||
self.ortho,
|
|
||||||
offset,
|
|
||||||
player_position.rotation,
|
|
||||||
other_position.rotation,
|
|
||||||
self.radar_center,
|
|
||||||
self.car_width,
|
|
||||||
self.car_height,
|
|
||||||
[0.9, 0.9, 0.0, 0.9],
|
|
||||||
)?;
|
|
||||||
|
|
||||||
self.cars.push(buffered_car);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
write_log!(format!("other cars: {:?}", self.cars.len()));
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,11 +26,7 @@ impl Watermark {
|
||||||
impl UiOverlay for Watermark {}
|
impl UiOverlay for Watermark {}
|
||||||
|
|
||||||
impl DataReceiver for Watermark {
|
impl DataReceiver for Watermark {
|
||||||
fn scoring_update(
|
fn game_phase_change(&mut self, phase: GamePhase) -> Result<()> {
|
||||||
&mut self,
|
|
||||||
phase: GamePhase,
|
|
||||||
_vehicle_scoring: &[VehicleScoringInfoV01],
|
|
||||||
) -> Result<()> {
|
|
||||||
match phase {
|
match phase {
|
||||||
GamePhase::TestDay => self.gui.enable()?,
|
GamePhase::TestDay => self.gui.enable()?,
|
||||||
_ => self.gui.disable()?,
|
_ => self.gui.disable()?,
|
||||||
|
@ -39,6 +35,20 @@ impl DataReceiver for Watermark {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update_for_phase(&self, phase: GamePhase) -> bool {
|
||||||
|
match phase {
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scoring_update(
|
||||||
|
&mut self,
|
||||||
|
_phase: GamePhase,
|
||||||
|
_vehicle_scoring: &[VehicleScoringInfoV01],
|
||||||
|
) -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn telemetry_update(
|
fn telemetry_update(
|
||||||
&mut self,
|
&mut self,
|
||||||
_player_id: Option<i32>,
|
_player_id: Option<i32>,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<?xml-model href="../gui.xsd" type="application/xml" schematypens="http://www.w3.org/2001/XMLSchema"?>
|
<?xml-model href="../gui.xsd" type="application/xml" schematypes="http://www.w3.org/2001/XMLSchema"?>
|
||||||
<root reference_width="2560" reference_height="1440">
|
<root reference_width="2560" reference_height="1440">
|
||||||
<grid x_dim="1" y_dim="1" x_offset="10" y_offset="10" width="300" height="50"
|
<grid x_dim="1" y_dim="1" x_offset="10" y_offset="10" width="300" height="50"
|
||||||
vert_align="top" hori_align="left" margin="2" padding="2" background="#c9c9c9">
|
vert_align="top" hori_align="left" margin="2" padding="2" background="#c9c9c9">
|
||||||
|
|
|
@ -1,13 +1,21 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use rfactor_sm_reader::*;
|
use rfactor_sm_reader::*;
|
||||||
|
|
||||||
use std::{cell::RefCell, rc::Rc, time::Instant};
|
use std::{
|
||||||
|
cell::RefCell,
|
||||||
|
rc::Rc,
|
||||||
|
time::{Duration, Instant},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::write_log;
|
use crate::write_log;
|
||||||
|
|
||||||
use super::UiOverlay;
|
use super::UiOverlay;
|
||||||
|
|
||||||
pub trait DataReceiver {
|
pub trait DataReceiver {
|
||||||
|
fn game_phase_change(&mut self, phase: GamePhase) -> Result<()>;
|
||||||
|
|
||||||
|
fn update_for_phase(&self, phase: GamePhase) -> bool;
|
||||||
|
|
||||||
fn scoring_update(
|
fn scoring_update(
|
||||||
&mut self,
|
&mut self,
|
||||||
phase: GamePhase,
|
phase: GamePhase,
|
||||||
|
@ -21,13 +29,16 @@ pub trait DataReceiver {
|
||||||
) -> Result<()>;
|
) -> Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
const GAME_PHASE_TIME_OUT: Duration = Duration::from_secs(2);
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub enum GamePhase {
|
pub enum GamePhase {
|
||||||
TestDay,
|
TestDay,
|
||||||
Practice,
|
Practice,
|
||||||
Qualifying,
|
Qualifying,
|
||||||
Warmup,
|
Warmup,
|
||||||
Race,
|
Race,
|
||||||
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<i32> for GamePhase {
|
impl TryFrom<i32> for GamePhase {
|
||||||
|
@ -51,8 +62,10 @@ pub struct RFactorData {
|
||||||
telemetry_reader: TelemetryReader,
|
telemetry_reader: TelemetryReader,
|
||||||
scoring_reader: ScoringReader,
|
scoring_reader: ScoringReader,
|
||||||
|
|
||||||
|
last_scoring_read: Duration,
|
||||||
start_time: Instant,
|
start_time: Instant,
|
||||||
player_id: Option<i32>,
|
player_id: Option<i32>,
|
||||||
|
previous_game_phase: GamePhase,
|
||||||
|
|
||||||
receivers: Vec<Rc<RefCell<dyn UiOverlay>>>,
|
receivers: Vec<Rc<RefCell<dyn UiOverlay>>>,
|
||||||
}
|
}
|
||||||
|
@ -67,8 +80,10 @@ impl RFactorData {
|
||||||
telemetry_reader: TelemetryReader::new(start_time.elapsed().as_secs_f32())?,
|
telemetry_reader: TelemetryReader::new(start_time.elapsed().as_secs_f32())?,
|
||||||
scoring_reader: ScoringReader::new(start_time.elapsed().as_secs_f32())?,
|
scoring_reader: ScoringReader::new(start_time.elapsed().as_secs_f32())?,
|
||||||
|
|
||||||
|
last_scoring_read: start_time.elapsed(),
|
||||||
start_time,
|
start_time,
|
||||||
player_id: None,
|
player_id: None,
|
||||||
|
previous_game_phase: GamePhase::None,
|
||||||
|
|
||||||
receivers: Vec::new(),
|
receivers: Vec::new(),
|
||||||
})
|
})
|
||||||
|
@ -86,35 +101,66 @@ impl RFactorData {
|
||||||
write_log!(" =================== update RFactorData ===================");
|
write_log!(" =================== update RFactorData ===================");
|
||||||
|
|
||||||
// get scoring info
|
// get scoring info
|
||||||
if let Some((scoring_info, vehicle_scorings)) =
|
match self.scoring_reader.vehicle_scoring(self.now()) {
|
||||||
self.scoring_reader.vehicle_scoring(self.now())
|
Some((scoring_info, vehicle_scorings)) => {
|
||||||
{
|
self.last_scoring_read = self.start_time.elapsed();
|
||||||
write_log!(format!(
|
|
||||||
"new scoring info: vehicles: {}",
|
|
||||||
scoring_info.mNumVehicles
|
|
||||||
));
|
|
||||||
|
|
||||||
// check for player id
|
write_log!(format!(
|
||||||
if scoring_info.mNumVehicles == 0 {
|
"new scoring info: vehicles: {}",
|
||||||
self.player_id = None;
|
scoring_info.mNumVehicles
|
||||||
} else if self.player_id.is_none() {
|
));
|
||||||
for vehicle_scoring in vehicle_scorings.iter() {
|
|
||||||
if vehicle_scoring.mIsPlayer != 0 {
|
// check for player id
|
||||||
write_log!(format!("player found: {}", vehicle_scoring.mID));
|
if scoring_info.mNumVehicles == 0 {
|
||||||
self.player_id = Some(vehicle_scoring.mID);
|
self.player_id = None;
|
||||||
break;
|
} else if self.player_id.is_none() {
|
||||||
|
for vehicle_scoring in vehicle_scorings.iter() {
|
||||||
|
if vehicle_scoring.mIsPlayer != 0 {
|
||||||
|
write_log!(format!("player found: {}", vehicle_scoring.mID));
|
||||||
|
self.player_id = Some(vehicle_scoring.mID);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let phase = GamePhase::try_from(scoring_info.mSession)?;
|
||||||
|
|
||||||
|
if self.previous_game_phase != phase {
|
||||||
|
self.previous_game_phase = phase;
|
||||||
|
|
||||||
|
for receiver in self.receivers.iter() {
|
||||||
|
receiver
|
||||||
|
.borrow_mut()
|
||||||
|
.game_phase_change(self.previous_game_phase)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
write_log!(format!("GamePhase: {:?}", self.previous_game_phase));
|
||||||
|
}
|
||||||
|
|
||||||
|
for receiver in self.receivers.iter() {
|
||||||
|
let mut rec_mut = receiver.borrow_mut();
|
||||||
|
|
||||||
|
if rec_mut.update_for_phase(self.previous_game_phase) {
|
||||||
|
rec_mut.scoring_update(self.previous_game_phase, &vehicle_scorings)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
None => {
|
||||||
|
let now = self.start_time.elapsed();
|
||||||
|
|
||||||
let phase = GamePhase::try_from(scoring_info.mSession)?;
|
if now > (self.last_scoring_read + GAME_PHASE_TIME_OUT) {
|
||||||
|
if self.previous_game_phase != GamePhase::None {
|
||||||
|
self.previous_game_phase = GamePhase::None;
|
||||||
|
|
||||||
write_log!(format!("GamePhase: {:?}", phase));
|
for receiver in self.receivers.iter() {
|
||||||
|
receiver
|
||||||
for receiver in self.receivers.iter() {
|
.borrow_mut()
|
||||||
receiver
|
.game_phase_change(self.previous_game_phase)?;
|
||||||
.borrow_mut()
|
}
|
||||||
.scoring_update(phase, &vehicle_scorings)?;
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,9 +170,11 @@ impl RFactorData {
|
||||||
write_log!("new telemetry update");
|
write_log!("new telemetry update");
|
||||||
|
|
||||||
for receiver in self.receivers.iter() {
|
for receiver in self.receivers.iter() {
|
||||||
receiver
|
let mut rec_mut = receiver.borrow_mut();
|
||||||
.borrow_mut()
|
|
||||||
.telemetry_update(self.player_id, &telemetries)?;
|
if rec_mut.update_for_phase(self.previous_game_phase) {
|
||||||
|
rec_mut.telemetry_update(self.player_id, &telemetries)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue