Add GamePhase handling and DeltaBoard #4
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>
|
||||
<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="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
|
||||
id="time_behind"
|
||||
x_slot="7" y_slot="0" x_size="2" text_color="black" text_alignment="right"></label>
|
||||
id="time"
|
||||
x_slot="8" y_slot="0" x_size="3" text_color="black" text_alignment="right"></label>
|
||||
</grid>
|
||||
</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">
|
||||
<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>
|
||||
</root>
|
|
@ -1,3 +1,7 @@
|
|||
mod leaderboard_entry;
|
||||
|
||||
use leaderboard_entry::*;
|
||||
|
||||
use std::{
|
||||
ffi::{c_char, CStr},
|
||||
sync::Arc,
|
||||
|
@ -16,30 +20,51 @@ use crate::write_log;
|
|||
|
||||
pub struct LeaderBoard {
|
||||
gui_handler: Arc<GuiHandler>,
|
||||
gui: Arc<GuiBuilder>,
|
||||
main_grid: Arc<Grid>,
|
||||
leaderboard: Arc<GuiBuilder>,
|
||||
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],
|
||||
player_background: Color,
|
||||
}
|
||||
|
||||
impl LeaderBoard {
|
||||
const GRID: &str = include_str!("leaderboard_grid.xml");
|
||||
const ENTRY: &str = include_str!("leaderboard_entry.xml");
|
||||
const LEADERBOARD: &str = include_str!("leaderboard_grid.xml");
|
||||
const DELTABOARD: &str = include_str!("deltaboard_grid.xml");
|
||||
|
||||
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 {
|
||||
gui_handler: gui_handler.clone(),
|
||||
gui,
|
||||
main_grid,
|
||||
leaderboard,
|
||||
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")?],
|
||||
player_background: Color::try_from("#b4bf26")?,
|
||||
|
@ -53,13 +78,20 @@ impl LeaderBoard {
|
|||
.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 {
|
||||
let driver_name = Self::c_char_to_string(vehicle_scoring.mDriverName);
|
||||
|
||||
// check driver list
|
||||
match self
|
||||
.entries
|
||||
.leaderboard_entries
|
||||
.iter_mut()
|
||||
.find(|entry| vehicle_scoring.mID == entry.id())
|
||||
{
|
||||
|
@ -69,8 +101,8 @@ impl LeaderBoard {
|
|||
}
|
||||
|
||||
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 => {
|
||||
let entry = LeaderBoardEntry::new(
|
||||
|
@ -78,26 +110,39 @@ impl LeaderBoard {
|
|||
vehicle_scoring.mID,
|
||||
driver_name,
|
||||
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.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");
|
||||
|
||||
// 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
|
||||
.main_grid
|
||||
.child_at(0, self.entries.len() - 1)?
|
||||
.leaderboard_grid
|
||||
.child_at(0, self.leaderboard_entries.len() - 1)?
|
||||
.is_none()
|
||||
{
|
||||
for i in 0..self.entries.len() {
|
||||
self.main_grid.detach(0, i)?;
|
||||
for (i, entry) in self.leaderboard_entries.iter().enumerate() {
|
||||
self.leaderboard_grid.detach(0, i)?;
|
||||
entry.snippet().set_visibility(false)?;
|
||||
}
|
||||
|
||||
true
|
||||
|
@ -106,33 +151,154 @@ impl LeaderBoard {
|
|||
};
|
||||
|
||||
// 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");
|
||||
|
||||
self.entries
|
||||
self.leaderboard_entries
|
||||
.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.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(())
|
||||
}
|
||||
|
||||
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 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(
|
||||
&mut self,
|
||||
phase: GamePhase,
|
||||
|
@ -141,13 +307,15 @@ impl DataReceiver for LeaderBoard {
|
|||
write_log!("=================== leader board: scoring update ===================");
|
||||
|
||||
match phase {
|
||||
GamePhase::TestDay => self.gui.disable()?,
|
||||
GamePhase::Practice => self.gui.disable()?,
|
||||
GamePhase::Qualifying => self.gui.disable()?,
|
||||
GamePhase::Warmup => self.gui.disable()?,
|
||||
GamePhase::Practice | GamePhase::Qualifying => {
|
||||
self.quali_leaderboard(vehicle_scorings)?;
|
||||
}
|
||||
|
||||
GamePhase::Race => {
|
||||
self.race_leaderboard(vehicle_scorings)?;
|
||||
}
|
||||
|
||||
_ => (),
|
||||
}
|
||||
|
||||
write_log!("leader board update finished");
|
||||
|
@ -160,132 +328,28 @@ impl DataReceiver for LeaderBoard {
|
|||
player_id: Option<i32>,
|
||||
_telemetries: &[rF2VehicleTelemetry],
|
||||
) -> Result<()> {
|
||||
if let Some(player_id) = player_id {
|
||||
if let Some(entry) = self.entries.iter().find(|entry| entry.id() == player_id) {
|
||||
entry.change_background_color(self.player_background)?;
|
||||
if self.leaderboard_redraw {
|
||||
self.leaderboard_redraw = false;
|
||||
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
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>,
|
||||
_history: Arc<Icon>,
|
||||
|
||||
enable: bool,
|
||||
|
||||
throttle_samples: HeapRb<f32>,
|
||||
brake_samples: HeapRb<f32>,
|
||||
|
||||
|
@ -138,6 +140,8 @@ impl Pedals {
|
|||
throttle,
|
||||
_history: history,
|
||||
|
||||
enable: false,
|
||||
|
||||
throttle_samples,
|
||||
brake_samples,
|
||||
|
||||
|
@ -202,7 +206,7 @@ impl Pedals {
|
|||
let command_buffer =
|
||||
CommandBuffer::new_primary().build(self.device.clone(), self.queue.clone())?;
|
||||
|
||||
{
|
||||
if self.enable {
|
||||
let mut recorder = command_buffer.begin(VkCommandBufferBeginInfo::new(
|
||||
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT
|
||||
| VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT,
|
||||
|
@ -231,6 +235,30 @@ impl Pedals {
|
|||
impl UiOverlay 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(
|
||||
&mut self,
|
||||
_phase: GamePhase,
|
||||
|
@ -244,25 +272,18 @@ impl DataReceiver for Pedals {
|
|||
player_id: Option<i32>,
|
||||
telemetries: &[rF2VehicleTelemetry],
|
||||
) -> Result<()> {
|
||||
match player_id {
|
||||
Some(id) => {
|
||||
self.gui.enable()?;
|
||||
if let Some(id) = player_id {
|
||||
if let Some(telemetry) = telemetries.iter().find(|telemetry| telemetry.id == id) {
|
||||
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) {
|
||||
let brake = 1.0 - telemetry.unfiltered_brake as f32;
|
||||
let throttle = 1.0 - telemetry.unfiltered_throttle as f32;
|
||||
self.throttle.set_progress(throttle)?;
|
||||
self.brake.set_progress(brake)?;
|
||||
|
||||
self.throttle.set_progress(throttle)?;
|
||||
self.brake.set_progress(brake)?;
|
||||
self.throttle_samples.push_overwrite(throttle);
|
||||
self.brake_samples.push_overwrite(brake);
|
||||
|
||||
self.throttle_samples.push_overwrite(throttle);
|
||||
self.brake_samples.push_overwrite(brake);
|
||||
|
||||
self.update_vertex_buffers()?;
|
||||
}
|
||||
}
|
||||
None => {
|
||||
self.gui.disable()?;
|
||||
self.update_vertex_buffers()?;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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">
|
||||
<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">
|
||||
|
|
|
@ -73,6 +73,8 @@ pub struct Radar {
|
|||
car_width: f32,
|
||||
car_height: f32,
|
||||
|
||||
enable: bool,
|
||||
|
||||
device: Arc<Device>,
|
||||
queue: Arc<Mutex<Queue>>,
|
||||
|
||||
|
@ -171,6 +173,8 @@ impl Radar {
|
|||
car_width,
|
||||
car_height,
|
||||
|
||||
enable: false,
|
||||
|
||||
device,
|
||||
queue,
|
||||
|
||||
|
@ -216,7 +220,7 @@ impl Radar {
|
|||
let command_buffer =
|
||||
CommandBuffer::new_primary().build(self.device.clone(), self.queue.clone())?;
|
||||
|
||||
{
|
||||
if self.enable {
|
||||
let mut recorder = command_buffer.begin(VkCommandBufferBeginInfo::new(
|
||||
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT
|
||||
| VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT,
|
||||
|
@ -268,6 +272,24 @@ impl Radar {
|
|||
impl UiOverlay 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(
|
||||
&mut self,
|
||||
_phase: GamePhase,
|
||||
|
@ -286,68 +308,71 @@ impl DataReceiver for Radar {
|
|||
|
||||
self.cars.clear();
|
||||
|
||||
if let Some(player_id) = player_id {
|
||||
// make sure there are enough cars in buffer
|
||||
if self.car_handles.len() < telemetries.len() {
|
||||
let size_diff = telemetries.len() - self.car_handles.len();
|
||||
if self.enable {
|
||||
if let Some(player_id) = player_id {
|
||||
// make sure there are enough cars in buffer
|
||||
if self.car_handles.len() < telemetries.len() {
|
||||
let size_diff = telemetries.len() - self.car_handles.len();
|
||||
|
||||
for _ in 0..size_diff {
|
||||
self.car_handles
|
||||
.push(self.create_car_object(vec2(0.0, 0.0), [0.0, 0.0, 0.0, 0.0])?);
|
||||
for _ in 0..size_diff {
|
||||
self.car_handles
|
||||
.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();
|
||||
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()));
|
||||
}
|
||||
|
||||
write_log!(format!("other cars: {:?}", self.cars.len()));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,11 +26,7 @@ impl Watermark {
|
|||
impl UiOverlay for Watermark {}
|
||||
|
||||
impl DataReceiver for Watermark {
|
||||
fn scoring_update(
|
||||
&mut self,
|
||||
phase: GamePhase,
|
||||
_vehicle_scoring: &[VehicleScoringInfoV01],
|
||||
) -> Result<()> {
|
||||
fn game_phase_change(&mut self, phase: GamePhase) -> Result<()> {
|
||||
match phase {
|
||||
GamePhase::TestDay => self.gui.enable()?,
|
||||
_ => self.gui.disable()?,
|
||||
|
@ -39,6 +35,20 @@ impl DataReceiver for Watermark {
|
|||
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(
|
||||
&mut self,
|
||||
_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">
|
||||
<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">
|
||||
|
|
|
@ -1,13 +1,21 @@
|
|||
use anyhow::Result;
|
||||
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 super::UiOverlay;
|
||||
|
||||
pub trait DataReceiver {
|
||||
fn game_phase_change(&mut self, phase: GamePhase) -> Result<()>;
|
||||
|
||||
fn update_for_phase(&self, phase: GamePhase) -> bool;
|
||||
|
||||
fn scoring_update(
|
||||
&mut self,
|
||||
phase: GamePhase,
|
||||
|
@ -21,13 +29,16 @@ pub trait DataReceiver {
|
|||
) -> 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 {
|
||||
TestDay,
|
||||
Practice,
|
||||
Qualifying,
|
||||
Warmup,
|
||||
Race,
|
||||
None,
|
||||
}
|
||||
|
||||
impl TryFrom<i32> for GamePhase {
|
||||
|
@ -51,8 +62,10 @@ pub struct RFactorData {
|
|||
telemetry_reader: TelemetryReader,
|
||||
scoring_reader: ScoringReader,
|
||||
|
||||
last_scoring_read: Duration,
|
||||
start_time: Instant,
|
||||
player_id: Option<i32>,
|
||||
previous_game_phase: GamePhase,
|
||||
|
||||
receivers: Vec<Rc<RefCell<dyn UiOverlay>>>,
|
||||
}
|
||||
|
@ -67,8 +80,10 @@ impl RFactorData {
|
|||
telemetry_reader: TelemetryReader::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,
|
||||
player_id: None,
|
||||
previous_game_phase: GamePhase::None,
|
||||
|
||||
receivers: Vec::new(),
|
||||
})
|
||||
|
@ -86,35 +101,66 @@ impl RFactorData {
|
|||
write_log!(" =================== update RFactorData ===================");
|
||||
|
||||
// get scoring info
|
||||
if let Some((scoring_info, vehicle_scorings)) =
|
||||
self.scoring_reader.vehicle_scoring(self.now())
|
||||
{
|
||||
write_log!(format!(
|
||||
"new scoring info: vehicles: {}",
|
||||
scoring_info.mNumVehicles
|
||||
));
|
||||
match self.scoring_reader.vehicle_scoring(self.now()) {
|
||||
Some((scoring_info, vehicle_scorings)) => {
|
||||
self.last_scoring_read = self.start_time.elapsed();
|
||||
|
||||
// check for player id
|
||||
if scoring_info.mNumVehicles == 0 {
|
||||
self.player_id = None;
|
||||
} 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;
|
||||
write_log!(format!(
|
||||
"new scoring info: vehicles: {}",
|
||||
scoring_info.mNumVehicles
|
||||
));
|
||||
|
||||
// check for player id
|
||||
if scoring_info.mNumVehicles == 0 {
|
||||
self.player_id = None;
|
||||
} 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
|
||||
.borrow_mut()
|
||||
.scoring_update(phase, &vehicle_scorings)?;
|
||||
for receiver in self.receivers.iter() {
|
||||
receiver
|
||||
.borrow_mut()
|
||||
.game_phase_change(self.previous_game_phase)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -124,9 +170,11 @@ impl RFactorData {
|
|||
write_log!("new telemetry update");
|
||||
|
||||
for receiver in self.receivers.iter() {
|
||||
receiver
|
||||
.borrow_mut()
|
||||
.telemetry_update(self.player_id, &telemetries)?;
|
||||
let mut rec_mut = receiver.borrow_mut();
|
||||
|
||||
if rec_mut.update_for_phase(self.previous_game_phase) {
|
||||
rec_mut.telemetry_update(self.player_id, &telemetries)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue