use std::sync::Arc;
#[cfg(feature = "rendering")]
use image::RgbaImage;
use wows_replays::analyzer::decoder::DeathCause;
use wows_replays::analyzer::decoder::Recognized;
use wows_replays::types::ElapsedClock;
use wows_replays::types::EntityId;
use wows_replays::types::GameClock;
use wows_replays::types::GameParamId;
use wows_replays::types::PlaneId;
use wowsunpack::game_types::AdvantageLevel;
use wowsunpack::game_types::BattleResult;
use wowsunpack::game_types::FinishType;
pub use wowsunpack::game_types::Ribbon;
use crate::map_data::MinimapPos;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
pub enum BuildingIconType {
Airbase,
AirDefence,
Artillery,
Generator,
Radar,
Station,
Supply,
Tower,
}
impl BuildingIconType {
pub fn icon_name(&self) -> &'static str {
match self {
Self::Airbase => "airbase",
Self::AirDefence => "air_defence",
Self::Artillery => "artillery",
Self::Generator => "generator",
Self::Radar => "radar",
Self::Station => "station",
Self::Supply => "supply",
Self::Tower => "tower",
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
pub enum BuildingRelation {
Ally,
Enemy,
Neutral,
Dead,
SuppressedAlly,
SuppressedEnemy,
SuppressedNeutral,
}
impl BuildingRelation {
pub fn icon_suffix(&self) -> &'static str {
match self {
Self::Ally => "ally",
Self::Enemy => "enemy",
Self::Neutral => "neutral",
Self::Dead => "dead",
Self::SuppressedAlly => "suppressed_ally",
Self::SuppressedEnemy => "suppressed_enemy",
Self::SuppressedNeutral => "suppressed_neutral",
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
pub enum ShipVisibility {
Visible,
MinimapOnly,
Undetected,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
pub enum ShipConfigCircleKind {
Detection,
MainBattery,
SecondaryBattery,
TorpedoRange,
Radar,
Hydro,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[cfg_attr(feature = "rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
pub struct ShipConfigFilter {
pub detection: bool,
pub main_battery: bool,
pub secondary_battery: bool,
pub torpedo: bool,
pub radar: bool,
pub hydro: bool,
}
impl ShipConfigFilter {
pub fn is_enabled(&self, kind: &ShipConfigCircleKind) -> bool {
match kind {
ShipConfigCircleKind::Detection => self.detection,
ShipConfigCircleKind::MainBattery => self.main_battery,
ShipConfigCircleKind::SecondaryBattery => self.secondary_battery,
ShipConfigCircleKind::TorpedoRange => self.torpedo,
ShipConfigCircleKind::Radar => self.radar,
ShipConfigCircleKind::Hydro => self.hydro,
}
}
pub fn all_enabled() -> Self {
Self { detection: true, main_battery: true, secondary_battery: true, torpedo: true, radar: true, hydro: true }
}
pub fn any_enabled(&self) -> bool {
self.detection || self.main_battery || self.secondary_battery || self.torpedo || self.radar || self.hydro
}
}
#[derive(Default)]
pub enum ShipConfigVisibility {
#[default]
SelfOnly,
Filtered(Arc<dyn Fn(EntityId) -> Option<ShipConfigFilter> + Send + Sync>),
}
impl ShipConfigVisibility {
pub fn filter_for(&self, is_self: bool, entity_id: EntityId) -> Option<ShipConfigFilter> {
match self {
ShipConfigVisibility::SelfOnly => {
if is_self {
Some(ShipConfigFilter::all_enabled())
} else {
None
}
}
ShipConfigVisibility::Filtered(cb) => cb(entity_id),
}
}
}
impl Clone for ShipConfigVisibility {
fn clone(&self) -> Self {
match self {
Self::SelfOnly => Self::SelfOnly,
Self::Filtered(cb) => Self::Filtered(Arc::clone(cb)),
}
}
}
impl std::fmt::Debug for ShipConfigVisibility {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::SelfOnly => write!(f, "SelfOnly"),
Self::Filtered(_) => write!(f, "Filtered(<callback>)"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
pub enum FontHint {
#[default]
Primary,
Fallback(usize),
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
pub struct ChatEntry {
pub clan_tag: String,
pub clan_color: Option<[u8; 3]>,
pub player_name: String,
pub team_color: [u8; 3],
pub ship_species: Option<String>,
pub ship_name: Option<String>,
pub message: String,
pub message_color: [u8; 3],
pub opacity: f32,
pub font_hint: FontHint,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
pub struct KillFeedEntry {
pub killer_name: String,
pub killer_species: Option<String>,
pub killer_ship_name: Option<String>,
pub killer_color: [u8; 3],
pub killer_is_friendly: bool,
pub victim_name: String,
pub victim_species: Option<String>,
pub victim_ship_name: Option<String>,
pub victim_color: [u8; 3],
pub victim_is_friendly: bool,
pub cause: Recognized<DeathCause>,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
pub struct RibbonCount {
pub ribbon: Ribbon,
pub count: usize,
pub display_name: String,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
pub struct ActivityFeedEntry {
pub clock: GameClock,
pub kind: ActivityFeedKind,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
pub enum ActivityFeedKind {
Kill(KillFeedEntry),
Chat(ChatEntry),
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
pub struct DamageBreakdownEntry {
pub label: String,
pub damage: f64,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
pub enum DrawCommand {
ShotTracer { from: MinimapPos, to: MinimapPos, color: [u8; 3] },
Torpedo { pos: MinimapPos, color: [u8; 3] },
Smoke { pos: MinimapPos, radius: i32, color: [u8; 3], alpha: f32 },
Ship {
entity_id: EntityId,
pos: MinimapPos,
yaw: f32,
species: Option<String>,
color: Option<[u8; 3]>,
visibility: ShipVisibility,
opacity: f32,
is_self: bool,
player_name: Option<String>,
ship_name: Option<String>,
is_detected_teammate: bool,
name_color: Option<[u8; 3]>,
},
HealthBar {
entity_id: EntityId,
pos: MinimapPos,
fraction: f32,
fill_color: [u8; 3],
background_color: [u8; 3],
background_alpha: f32,
},
DeadShip {
entity_id: EntityId,
pos: MinimapPos,
yaw: f32,
species: Option<String>,
color: Option<[u8; 3]>,
is_self: bool,
player_name: Option<String>,
ship_name: Option<String>,
},
BuffZone {
pos: MinimapPos,
radius: i32,
color: [u8; 3],
alpha: f32,
marker_name: Option<String>,
},
CapturePoint {
pos: MinimapPos,
radius: i32,
color: [u8; 3],
alpha: f32,
label: String,
progress: f32,
invader_color: Option<[u8; 3]>,
#[cfg(feature = "rendering")]
#[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::Skip))]
flag_icon: Option<RgbaImage>,
},
TurretDirection {
entity_id: EntityId,
pos: MinimapPos,
yaw: f32,
color: [u8; 3],
length: i32,
},
Building {
pos: MinimapPos,
color: [u8; 3],
is_alive: bool,
icon_type: Option<BuildingIconType>,
relation: BuildingRelation,
},
WeatherZone {
pos: MinimapPos,
radius: i32,
},
Plane {
plane_id: PlaneId,
owner_entity_id: EntityId,
pos: MinimapPos,
icon_key: String,
player_name: Option<String>,
ship_name: Option<String>,
},
ConsumableRadius {
entity_id: EntityId,
pos: MinimapPos,
radius_px: i32,
color: [u8; 3],
alpha: f32,
},
PatrolRadius {
plane_id: PlaneId,
pos: MinimapPos,
radius_px: i32,
color: [u8; 3],
alpha: f32,
},
ConsumableIcons {
entity_id: EntityId,
pos: MinimapPos,
icon_keys: Vec<String>,
is_friendly: bool,
has_hp_bar: bool,
},
ShipConfigCircle {
entity_id: EntityId,
pos: MinimapPos,
radius_px: f32,
color: [u8; 3],
alpha: f32,
dashed: bool,
label: Option<String>,
kind: ShipConfigCircleKind,
player_name: String,
is_self: bool,
},
PositionTrail {
entity_id: EntityId,
player_name: Option<String>,
points: Vec<(MinimapPos, [u8; 3])>,
},
TeamBuffs {
friendly_buffs: Vec<(String, u32)>,
enemy_buffs: Vec<(String, u32)>,
},
ScoreBar {
team0: i32,
team1: i32,
team0_color: [u8; 3],
team1_color: [u8; 3],
max_score: i32,
team0_timer: Option<String>,
team1_timer: Option<String>,
advantage: Option<(AdvantageLevel, u8)>,
},
TeamAdvantage {
level: Option<AdvantageLevel>,
color: [u8; 3],
breakdown: crate::advantage::AdvantageBreakdown,
},
Timer {
time_remaining: Option<i64>,
elapsed: ElapsedClock,
},
PreBattleCountdown { seconds: i64 },
KillFeed { entries: Vec<KillFeedEntry> },
ChatOverlay { entries: Vec<ChatEntry> },
BattleResultOverlay {
result: BattleResult,
finish_type: Option<Recognized<FinishType>>,
color: [u8; 3],
subtitle_above: bool,
},
StatsPanel { x: i32, width: i32 },
StatsSilhouette {
x: i32,
y: i32,
width: i32,
height: i32,
ship_param_id: Option<GameParamId>,
hp_fraction: f32,
hp_current: f32,
hp_max: f32,
player_name: Option<String>,
clan_tag: Option<String>,
clan_color: Option<[u8; 3]>,
ship_name: Option<String>,
#[cfg(feature = "rendering")]
#[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::Skip))]
silhouette: Option<RgbaImage>,
},
StatsDamage {
x: i32,
y: i32,
width: i32,
breakdowns: Vec<DamageBreakdownEntry>,
damage_spotting: f64,
spotting_breakdowns: Vec<DamageBreakdownEntry>,
damage_potential: f64,
potential_breakdowns: Vec<DamageBreakdownEntry>,
},
StatsRibbons { x: i32, y: i32, width: i32, ribbons: Vec<RibbonCount> },
StatsActivityFeed { x: i32, y: i32, width: i32, height: i32, entries: Vec<ActivityFeedEntry> },
}
impl DrawCommand {
pub fn is_hud(&self) -> bool {
matches!(
self,
Self::ScoreBar { .. }
| Self::Timer { .. }
| Self::PreBattleCountdown { .. }
| Self::KillFeed { .. }
| Self::BattleResultOverlay { .. }
| Self::TeamBuffs { .. }
| Self::TeamAdvantage { .. }
| Self::ChatOverlay { .. }
| Self::StatsPanel { .. }
| Self::StatsSilhouette { .. }
| Self::StatsDamage { .. }
| Self::StatsRibbons { .. }
| Self::StatsActivityFeed { .. }
)
}
pub fn is_stats(&self) -> bool {
matches!(
self,
Self::StatsPanel { .. }
| Self::StatsSilhouette { .. }
| Self::StatsDamage { .. }
| Self::StatsRibbons { .. }
| Self::StatsActivityFeed { .. }
)
}
}
pub trait RenderTarget {
fn begin_frame(&mut self);
fn draw(&mut self, cmd: &DrawCommand);
fn end_frame(&mut self);
}