Files
smart-rgb/crates/borders-core/src/ui/mod.rs
2025-10-16 00:02:34 -05:00

105 lines
4.5 KiB
Rust

//! UI/Frontend module for rendering and user interaction
//!
//! This module contains all frontend-related concerns including:
//! - Protocol definitions for frontend-backend communication
//! - Input handling
//! - Leaderboard management
//! - Platform transport abstraction
pub mod input;
pub mod leaderboard;
pub mod plugin;
pub mod protocol;
pub mod transport;
// Re-export commonly used types
pub use input::{InputEvent, InputState, KeyCode, MouseButton, TileCoord, WorldPos, tile_from_index, tile_to_index};
pub use leaderboard::{LastAttacksDigest, LastLeaderboardDigest, LeaderboardThrottle, build_attacks_update, build_leaderboard_snapshot, emit_attacks_update_system, emit_leaderboard_snapshot_system};
pub use plugin::FrontendPlugin;
pub use protocol::{AttackEntry, AttacksUpdatePayload, BackendMessage, CameraCommand, CameraStateUpdate, FrontendMessage, GameOutcome, LeaderboardEntry, LeaderboardSnapshot, MapQuery, MapQueryResponse, PaletteInit, RenderInit, RenderInputEvent, RgbColor, ShipUpdateVariant, ShipsUpdatePayload, SpawnCountdown, TerrainInit, TerrainPalette, TerrainType, TerritoryDelta, TerritorySnapshot, TileChange};
pub use transport::{FrontendTransport, RenderBridge, handle_camera_update, handle_render_input};
use crate::networking::GameView;
use bevy_ecs::prelude::*;
use std::collections::HashMap;
/// Resource to track currently highlighted nation for visual feedback
#[derive(Resource, Default, Debug)]
pub struct NationHighlightState {
pub highlighted_nation: Option<u16>,
}
/// System that tracks hovered nation and emits highlight events
pub fn emit_nation_highlight_system(input_state: NonSend<std::sync::Arc<std::sync::Mutex<InputState>>>, game_view: Res<GameView>, mut highlight_state: ResMut<NationHighlightState>, mut backend_messages: MessageWriter<BackendMessage>) {
let Ok(input) = input_state.lock() else {
return;
};
let new_highlighted = if let Some(tile_coord) = input.cursor_tile() {
let tile_index = tile_to_index(tile_coord, game_view.width());
let owner_id = game_view.get_owner(tile_index as u32);
// Water (65535) and unclaimed (65534) should clear highlight
if owner_id >= 65534 { None } else { Some(owner_id) }
} else {
None
};
// Only emit if highlight changed
if new_highlighted != highlight_state.highlighted_nation {
highlight_state.highlighted_nation = new_highlighted;
backend_messages.write(BackendMessage::HighlightNation { nation_id: new_highlighted });
}
}
/// Resource to track previous ship states for delta updates
#[derive(Resource, Default)]
pub struct ShipStateTracker {
/// Map of ship ID to current_path_index
ship_indices: HashMap<u32, u32>,
}
/// System that emits ship update variants to the frontend (delta-based)
/// - Create: sent when ship first appears
/// - Move: sent only when current_path_index changes
/// - Destroy: sent when ship disappears
pub fn emit_ships_update_system(game_view: Res<GameView>, mut ship_tracker: ResMut<ShipStateTracker>, mut backend_messages: MessageWriter<BackendMessage>) {
let current_ship_ids: std::collections::HashSet<u32> = game_view.ships.iter().map(|s| s.id).collect();
let mut updates = Vec::new();
// Detect destroyed ships
for &ship_id in ship_tracker.ship_indices.keys() {
if !current_ship_ids.contains(&ship_id) {
updates.push(ShipUpdateVariant::Destroy { id: ship_id });
}
}
// Detect new ships and moved ships
for ship in &game_view.ships {
match ship_tracker.ship_indices.get(&ship.id) {
None => {
// New ship - send Create
updates.push(ShipUpdateVariant::Create { id: ship.id, owner_id: ship.owner_id, path: ship.path.clone(), troops: ship.troops });
ship_tracker.ship_indices.insert(ship.id, ship.path_progress);
}
Some(&prev_index) if prev_index != ship.path_progress => {
// Ship moved to next tile - send Move
updates.push(ShipUpdateVariant::Move { id: ship.id, current_path_index: ship.path_progress });
ship_tracker.ship_indices.insert(ship.id, ship.path_progress);
}
_ => {
// No change, do nothing
}
}
}
// Clean up destroyed ships from tracker
ship_tracker.ship_indices.retain(|id, _| current_ship_ids.contains(id));
// Only send if there are updates
if !updates.is_empty() {
backend_messages.write(BackendMessage::ShipsUpdate(ShipsUpdatePayload { turn: game_view.turn_number, updates }));
}
}