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()
|
|
|
|
}
|
|
|
|
|
2023-01-20 05:56:26 +00:00
|
|
|
fn update_leaderboard<F, D>(
|
2023-01-18 16:02:20 +00:00
|
|
|
&mut self,
|
|
|
|
vehicle_scorings: &[VehicleScoringInfoV01],
|
|
|
|
f: F,
|
2023-01-20 05:56:26 +00:00
|
|
|
d: D,
|
2023-01-18 16:02:20 +00:00
|
|
|
) -> Result<()>
|
|
|
|
where
|
|
|
|
F: Fn(&mut LeaderBoardEntry, &VehicleScoringInfoV01) -> Result<()>,
|
2023-01-20 05:56:26 +00:00
|
|
|
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
|
|
|
{
|
2023-01-20 05:56:26 +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
|
|
|
}
|
2023-01-20 05:56:26 +00:00
|
|
|
// add new entry if not found
|
2023-01-18 06:09:44 +00:00
|
|
|
None => {
|
2023-01-20 05:56:26 +00:00
|
|
|
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
|
|
|
)?;
|
|
|
|
|
2023-01-20 05:56:26 +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
|
|
|
}
|
|
|
|
|
2023-01-19 14:36:56 +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<()> {
|
2023-01-20 05:56:26 +00:00
|
|
|
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<()> {
|
2023-01-20 05:56:26 +00:00
|
|
|
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 {
|
2023-01-19 14:36:56 +00:00
|
|
|
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 {
|
2023-01-19 14:36:56 +00:00
|
|
|
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 {
|
2023-01-19 14:36:56 +00:00
|
|
|
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
|
|
|
}
|
|
|
|
}
|