rFactor2_vk_hud/src/overlay/elements/leaderboard/mod.rs

382 lines
12 KiB
Rust
Raw Normal View History

2023-01-19 09:23:21 +00:00
mod leaderboard_entry;
use leaderboard_entry::*;
2023-01-18 06:09:44 +00:00
use std::{
ffi::{c_char, CStr},
sync::Arc,
};
use anyhow::Result;
use rfactor_sm_reader::{rF2VehicleTelemetry, VehicleScoringInfoV01};
use ui::prelude::*;
use utilities::prelude::Color;
2023-01-18 07:55:43 +00:00
use crate::overlay::{
rfactor_data::{DataReceiver, GamePhase},
UiOverlay,
};
2023-01-18 07:04:47 +00:00
use crate::write_log;
2023-01-18 06:09:44 +00:00
pub struct LeaderBoard {
gui_handler: Arc<GuiHandler>,
2023-01-19 06:40:16 +00:00
leaderboard: Arc<GuiBuilder>,
2023-01-19 09:23:21 +00:00
deltaboard: Arc<GuiBuilder>,
leaderboard_grid: Arc<Grid>,
deltaboard_grid: Arc<Grid>,
2023-01-18 06:09:44 +00:00
2023-01-19 09:23:21 +00:00
leaderboard_entries: Vec<LeaderBoardEntry>,
deltaboard_entries: [LeaderBoardEntry; 5],
leaderboard_redraw: bool,
last_player_id: i32,
2023-01-18 06:09:44 +00:00
entry_backgrounds: [Color; 2],
2023-01-19 12:41:08 +00:00
player_background: Color,
2023-01-18 06:09:44 +00:00
}
impl LeaderBoard {
2023-01-19 09:23:21 +00:00
const LEADERBOARD: &str = include_str!("leaderboard_grid.xml");
const DELTABOARD: &str = include_str!("deltaboard_grid.xml");
2023-01-18 06:09:44 +00:00
pub fn new(gui_handler: &Arc<GuiHandler>) -> Result<Self> {
2023-01-19 09:23:21 +00:00
let leaderboard = GuiBuilder::from_str(gui_handler, Self::LEADERBOARD)?;
let deltaboard = GuiBuilder::from_str(gui_handler, Self::DELTABOARD)?;
2023-01-18 06:09:44 +00:00
2023-01-19 09:23:21 +00:00
let leaderboard_grid = leaderboard.element("main_grid")?;
let deltaboard_grid = deltaboard.element("main_grid")?;
2023-01-18 06:09:44 +00:00
Ok(Self {
gui_handler: gui_handler.clone(),
2023-01-19 09:23:21 +00:00
leaderboard,
deltaboard,
leaderboard_grid,
deltaboard_grid,
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,
2023-01-18 06:09:44 +00:00
entry_backgrounds: [Color::try_from("#838383")?, Color::try_from("#545454")?],
2023-01-19 12:41:08 +00:00
player_background: Color::try_from("#b4bf26")?,
2023-01-18 06:09:44 +00:00
})
}
2023-01-18 07:04:47 +00:00
fn c_char_to_string(c: [c_char; 32usize]) -> String {
unsafe { CStr::from_ptr(&c as *const c_char) }
.to_str()
.unwrap()
.to_string()
}
fn update_leaderboard<F, D>(
2023-01-18 16:02:20 +00:00
&mut self,
vehicle_scorings: &[VehicleScoringInfoV01],
f: F,
d: D,
2023-01-18 16:02:20 +00:00
) -> Result<()>
where
F: Fn(&mut LeaderBoardEntry, &VehicleScoringInfoV01) -> Result<()>,
D: Fn(&mut LeaderBoardEntry) -> Result<()>,
2023-01-18 16:02:20 +00:00
{
2023-01-18 06:09:44 +00:00
for vehicle_scoring in vehicle_scorings {
2023-01-18 07:04:47 +00:00
let driver_name = Self::c_char_to_string(vehicle_scoring.mDriverName);
2023-01-18 06:09:44 +00:00
// check driver list
match self
2023-01-19 09:23:21 +00:00
.leaderboard_entries
2023-01-18 06:09:44 +00:00
.iter_mut()
2023-01-18 07:04:47 +00:00
.find(|entry| vehicle_scoring.mID == entry.id())
2023-01-18 06:09:44 +00:00
{
// update existing entry
2023-01-18 06:09:44 +00:00
Some(entry) => {
2023-01-18 07:04:47 +00:00
if entry.name() != driver_name {
entry.change_name(driver_name)?;
}
2023-01-18 06:09:44 +00:00
entry.update_place(vehicle_scoring.mPlace)?;
2023-01-18 16:02:20 +00:00
f(entry, vehicle_scoring)?;
2023-01-18 06:09:44 +00:00
}
// add new entry if not found
2023-01-18 06:09:44 +00:00
None => {
let mut entry = LeaderBoardEntry::new(
2023-01-18 06:09:44 +00:00
&self.gui_handler,
vehicle_scoring.mID,
2023-01-18 07:04:47 +00:00
driver_name,
2023-01-18 06:09:44 +00:00
vehicle_scoring.mPlace,
2023-01-19 06:40:16 +00:00
{
let laps_behind = vehicle_scoring.mLapsBehindLeader;
if laps_behind != 0 {
BehindLeader::Laps(laps_behind)
} else {
BehindLeader::Time(vehicle_scoring.mTimeBehindLeader)
}
},
2023-01-18 06:09:44 +00:00
vehicle_scoring.mTimeBehindNext,
2023-01-18 16:02:20 +00:00
vehicle_scoring.mBestLapTime,
2023-01-18 06:09:44 +00:00
)?;
d(&mut entry)?;
2023-01-19 09:23:21 +00:00
self.leaderboard_entries.push(entry);
2023-01-18 06:09:44 +00:00
}
}
}
2023-01-18 07:04:47 +00:00
write_log!("create entries");
2023-01-18 06:09:44 +00:00
// check if entry count in grid is the same as the gathered entries
2023-01-19 17:08:13 +00:00
// that means some joined or left the game
2023-01-19 09:23:21 +00:00
let force_update = if !self.leaderboard_entries.is_empty()
2023-01-19 17:08:13 +00:00
&& self.leaderboard_entries.len() <= self.leaderboard_grid.dimensions().1
2023-01-18 07:04:47 +00:00
&& self
2023-01-19 09:23:21 +00:00
.leaderboard_grid
.child_at(0, self.leaderboard_entries.len() - 1)?
2023-01-18 07:04:47 +00:00
.is_none()
2023-01-18 06:09:44 +00:00
{
2023-01-19 09:23:21 +00:00
for (i, entry) in self.leaderboard_entries.iter().enumerate() {
self.leaderboard_grid.detach(0, i)?;
entry.snippet().set_visibility(false)?;
2023-01-18 06:09:44 +00:00
}
true
}
// there are more entries in leaderboard when someone leaves and ID doesn't get reused
else if self.leaderboard_entries.len() > vehicle_scorings.len() {
self.leaderboard_entries.retain(|entry| {
vehicle_scorings
.iter()
.any(|scoring| scoring.mID == entry.id())
});
2023-01-18 06:09:44 +00:00
true
} else {
false
};
// check if any entry needs resorting
2023-01-19 09:23:21 +00:00
if force_update
|| self
.leaderboard_entries
.iter()
.any(|entry| entry.needs_resorting())
{
2023-01-18 07:04:47 +00:00
write_log!("leader board update required");
2023-01-19 09:23:21 +00:00
self.leaderboard_entries
2023-01-18 06:09:44 +00:00
.sort_by(|lhs, rhs| lhs.place().cmp(&rhs.place()));
2023-01-19 09:23:21 +00:00
for (i, entry) in self.leaderboard_entries.iter_mut().enumerate() {
2023-01-18 06:09:44 +00:00
entry.resorting_finished();
2023-01-19 12:41:08 +00:00
// 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)?;
}
2023-01-19 09:23:21 +00:00
}
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())?;
2023-01-19 12:41:08 +00:00
if entry.id() == self.last_player_id {
entry.change_background_color(self.player_background)?;
} else {
entry.change_background_color(self.entry_backgrounds[i % 2])?;
}
2023-01-19 09:23:21 +00:00
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;
}
}
2023-01-18 06:09:44 +00:00
}
}
2023-01-18 07:55:43 +00:00
Ok(())
}
2023-01-18 16:02:20 +00:00
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))
}
},
|entry| entry.force_display_behind_leader(),
)
2023-01-18 16:02:20 +00:00
}
fn quali_leaderboard(&mut self, vehicle_scorings: &[VehicleScoringInfoV01]) -> Result<()> {
self.update_leaderboard(
vehicle_scorings,
|entry, scoring| entry.update_best_lap(scoring.mBestLapTime),
|entry| entry.force_display_best_lap(),
)
2023-01-18 16:02:20 +00:00
}
2023-01-18 07:55:43 +00:00
}
impl UiOverlay for LeaderBoard {}
impl DataReceiver for LeaderBoard {
2023-01-18 16:02:20 +00:00
fn game_phase_change(&mut self, phase: GamePhase) -> Result<()> {
match phase {
GamePhase::Practice | GamePhase::Qualifying | GamePhase::TestDay => {
2023-01-19 09:23:21 +00:00
for entry in self.leaderboard_entries.iter_mut() {
2023-01-20 04:49:57 +00:00
entry.reset_time()?;
2023-01-19 06:40:16 +00:00
}
2023-01-19 09:23:21 +00:00
self.leaderboard.enable()?;
2023-01-19 06:40:16 +00:00
}
GamePhase::Race => {
2023-01-19 09:23:21 +00:00
for entry in self.leaderboard_entries.iter_mut() {
2023-01-20 04:49:57 +00:00
entry.reset_time()?;
}
for entry in self.deltaboard_entries.iter_mut() {
entry.reset_time()?;
2023-01-19 06:40:16 +00:00
}
self.leaderboard.enable()?;
2023-01-19 09:23:21 +00:00
self.deltaboard.enable()?;
}
_ => {
self.last_player_id = -1;
self.leaderboard.disable()?;
self.deltaboard.disable()?;
2023-01-19 06:40:16 +00:00
}
2023-01-18 16:02:20 +00:00
}
2023-01-19 06:40:16 +00:00
Ok(())
2023-01-18 16:02:20 +00:00
}
fn update_for_phase(&self, phase: GamePhase) -> bool {
match phase {
GamePhase::Practice | GamePhase::Qualifying | GamePhase::Race | GamePhase::TestDay => {
true
}
2023-01-18 16:02:20 +00:00
_ => false,
}
}
2023-01-18 07:55:43 +00:00
fn scoring_update(
&mut self,
phase: GamePhase,
vehicle_scorings: &[VehicleScoringInfoV01],
) -> Result<()> {
write_log!("=================== leader board: scoring update ===================");
match phase {
GamePhase::Practice | GamePhase::Qualifying | GamePhase::TestDay => {
2023-01-19 09:23:21 +00:00
self.quali_leaderboard(vehicle_scorings)?;
2023-01-18 07:55:43 +00:00
}
2023-01-18 16:02:20 +00:00
2023-01-19 09:23:21 +00:00
GamePhase::Race => {
self.race_leaderboard(vehicle_scorings)?;
}
2023-01-18 16:02:20 +00:00
_ => (),
2023-01-18 07:55:43 +00:00
}
2023-01-18 07:04:47 +00:00
write_log!("leader board update finished");
2023-01-18 06:09:44 +00:00
Ok(())
}
fn telemetry_update(
&mut self,
2023-01-18 07:04:47 +00:00
player_id: Option<i32>,
2023-01-18 06:09:44 +00:00
_telemetries: &[rF2VehicleTelemetry],
) -> Result<()> {
2023-01-19 09:23:21 +00:00
if self.leaderboard_redraw {
self.leaderboard_redraw = false;
2023-01-19 12:41:08 +00:00
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)?;
}
}
2023-01-18 07:04:47 +00:00
}
2023-01-19 09:23:21 +00:00
if let Some(player_id) = player_id {
self.last_player_id = player_id;
2023-01-18 11:49:04 +00:00
}
2023-01-18 16:02:20 +00:00
Ok(())
2023-01-18 06:09:44 +00:00
}
}