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

501 lines
16 KiB
Rust
Raw Normal View History

2023-01-20 15:08:14 +00:00
mod bg_generator;
2023-01-19 09:23:21 +00:00
mod leaderboard_entry;
use leaderboard_entry::*;
2023-01-20 21:31:43 +00:00
use serde::{Deserialize, Serialize};
2023-01-19 09:23:21 +00:00
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::*;
2023-01-20 16:01:21 +00:00
use vulkan_rs::prelude::*;
2023-01-18 06:09:44 +00:00
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
2023-01-20 21:31:43 +00:00
use bg_generator::BackgroundGenerator;
2023-01-23 06:21:52 +00:00
#[derive(Default, Deserialize, Serialize, Clone, Copy, Debug)]
2023-01-20 21:31:43 +00:00
pub struct LeaderBoardConfig {
first_board_color: [f32; 3],
second_board_color: [f32; 3],
player_board_color: [f32; 3],
}
impl LeaderBoardConfig {
pub const fn new() -> Self {
Self {
first_board_color: [0.33; 3],
second_board_color: [0.51; 3],
player_board_color: [0.7, 0.75, 0.15],
}
}
}
2023-01-20 16:01:21 +00:00
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
2023-01-20 16:01:21 +00:00
entry_backgrounds: [(Arc<Image>, Arc<Image>, Arc<Image>); 2],
player_background: (Arc<Image>, Arc<Image>, Arc<Image>),
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
2023-01-20 21:31:43 +00:00
pub fn new(
gui_handler: &Arc<GuiHandler>,
leader_board_config: LeaderBoardConfig,
) -> 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-20 16:01:21 +00:00
let leaderboard_grid: Arc<Grid> = leaderboard.element("main_grid")?;
2023-01-19 09:23:21 +00:00
let deltaboard_grid = deltaboard.element("main_grid")?;
2023-01-18 06:09:44 +00:00
2023-01-20 16:01:21 +00:00
let images = {
// attach snippet to leader board to let it resize its child element correctly
// then use these sizes to create actual UI element background
let dummy_snippet = LeaderBoardEntry::create_snippet(gui_handler)?;
let place: Arc<Icon> = dummy_snippet.element("place")?;
let name: Arc<Icon> = dummy_snippet.element("name")?;
let time: Arc<Icon> = dummy_snippet.element("time")?;
leaderboard_grid.attach(dummy_snippet, 0, 0, 1, 1)?;
let colors = [
{
2023-01-20 21:31:43 +00:00
let a = leader_board_config.first_board_color;
2023-01-20 16:01:21 +00:00
[a[0], a[1], a[2], 1.0]
},
{
2023-01-20 21:31:43 +00:00
let a = leader_board_config.second_board_color;
2023-01-20 16:01:21 +00:00
[a[0], a[1], a[2], 1.0]
},
{
2023-01-20 21:31:43 +00:00
let a = leader_board_config.player_board_color;
2023-01-20 16:01:21 +00:00
[a[0], a[1], a[2], 1.0]
},
];
let images = colors
.iter()
.map(|color| {
let images = BackgroundGenerator::generate(
gui_handler.device(),
gui_handler.queue(),
*color,
[
Self::extent_i_to_u(place.extent()),
Self::extent_i_to_u(name.extent()),
Self::extent_i_to_u(time.extent()),
],
)?;
Ok((images[0].clone(), images[1].clone(), images[2].clone()))
})
.collect::<Result<Vec<(Arc<Image>, Arc<Image>, Arc<Image>)>>>()?;
leaderboard_grid.detach(0, 0)?;
images
};
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,
2023-01-20 16:01:21 +00:00
2023-01-19 09:23:21 +00:00
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
2023-01-20 16:01:21 +00:00
entry_backgrounds: [images[0].clone(), images[1].clone()],
player_background: images[2].clone(),
2023-01-18 06:09:44 +00:00
})
}
2023-01-18 07:04:47 +00:00
2023-01-20 16:01:21 +00:00
fn extent_i_to_u((x, y): (i32, i32)) -> (u32, u32) {
(x as u32, y as u32)
}
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-20 16:21:24 +00:00
Self::query_behind_leader(vehicle_scoring),
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 {
2023-01-20 16:01:21 +00:00
entry.change_background_color(&self.entry_backgrounds[i % 2])?;
2023-01-19 12:41:08 +00:00
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)
{
2023-01-20 07:14:51 +00:00
let mut start_index = if index >= 2 {
if index == self.leaderboard_entries.len() - 2 {
if index >= 3 {
index - 3
} else {
index - 2
}
} else if index == self.leaderboard_entries.len() - 1 {
if index >= 4 {
index - 4
} else if index >= 3 {
index - 3
} else {
index - 2
}
} else {
index - 2
}
} else {
0
};
2023-01-19 09:23:21 +00:00
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 {
2023-01-20 16:01:21 +00:00
entry.change_background_color(&self.player_background)?;
2023-01-19 12:41:08 +00:00
} else {
2023-01-20 16:01:21 +00:00
entry.change_background_color(&self.entry_backgrounds[i % 2])?;
2023-01-19 12:41:08 +00:00
}
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
2023-01-20 16:21:24 +00:00
fn query_behind_leader(scoring: &VehicleScoringInfoV01) -> BehindLeader {
2023-01-20 16:27:09 +00:00
match scoring.mFinishStatus {
0 | 1 => {
if scoring.mInPits != 0 {
BehindLeader::PITS
} else {
let laps_behind = scoring.mLapsBehindLeader;
2023-01-20 16:21:24 +00:00
2023-01-20 16:27:09 +00:00
if laps_behind != 0 {
BehindLeader::Laps(laps_behind)
} else {
BehindLeader::Time(scoring.mTimeBehindLeader)
}
}
}
2 => BehindLeader::DNF,
3 => BehindLeader::DSQ,
_ => {
write_log!(format!(
"not allowed finish state: {}",
scoring.mFinishStatus
));
BehindLeader::Time(scoring.mTimeBehindLeader)
}
2023-01-20 16:21:24 +00:00
}
}
2023-01-18 16:02:20 +00:00
fn race_leaderboard(&mut self, vehicle_scorings: &[VehicleScoringInfoV01]) -> Result<()> {
self.update_leaderboard(
vehicle_scorings,
2023-01-20 16:21:24 +00:00
|entry, scoring| entry.update_time_behind_leader(Self::query_behind_leader(scoring)),
|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<()> {
2023-01-21 15:50:29 +00:00
for i in 0..self.leaderboard_grid.dimensions().1 {
self.leaderboard_grid.detach(0, i)?;
}
for i in 0..self.deltaboard_grid.dimensions().1 {
self.deltaboard_grid.detach(0, i)?;
}
2023-01-18 16:02:20 +00:00
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
));
2023-01-20 16:01:21 +00:00
entry.change_background_color(&self.player_background)?;
2023-01-19 12:41:08 +00:00
}
}
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
}
}