use serde::{Deserialize, Serialize}; use std::collections::HashSet; use bevy::prelude::*; use crate::{ actions::Action, jobs::{WorkKind, site_work}, selection::{Cursor, update_cursor}, sim::{SimSystems, SimTick}, ui::PointerOverUi, world::{TILE_SIZE, WorldMap}, worldgen::WorldGenerated, zones::{PREVIEW_ADD_COLOR, PREVIEW_REMOVE_COLOR, removing}, }; pub struct DesignationsPlugin; impl Plugin for DesignationsPlugin { fn build(&self, app: &mut App) { app.init_resource::() .init_resource::() .add_systems( Update, ( clear_designations.run_if(on_message::), draw_designations, ), ) .add_systems( PostUpdate, (designate_tool, draw_designate_preview) .chain() .after(update_cursor), ) .add_systems(FixedUpdate, prune_designations.in_set(SimSystems::Decide)); } } const PRUNE_INTERVAL_TICKS: u64 = 60; const DESIGNATION_COLOR: Color = Color::srgba(1.0, 0.85, 0.2, 0.9); #[derive(Resource, Default, Debug, Serialize, Deserialize)] pub struct Designations(pub HashSet); #[derive(Resource, Default, Debug)] pub struct DesignateTool { pub active: bool, drag_start: Option, } pub fn designatable(map: &WorldMap, tile: UVec2) -> bool { site_work(map, tile).is_some_and(|w| matches!(w.kind, WorkKind::Chop | WorkKind::Mine)) } pub fn designate(map: &WorldMap, designations: &mut Designations, start: UVec2, end: UVec2) { let (lo, hi) = (start.min(end), start.max(end)); for y in lo.y..=hi.y { for x in lo.x..=hi.x { let tile = UVec2::new(x, y); if designatable(map, tile) { designations.0.insert(tile); } } } } fn clear_designations(mut designations: ResMut) { designations.0.clear(); } fn prune_designations( tick: Res, map: Res, mut designations: ResMut, ) { if tick.0.is_multiple_of(PRUNE_INTERVAL_TICKS) && !designations.0.is_empty() { designations.0.retain(|&tile| designatable(&map, tile)); } } #[allow(clippy::too_many_arguments)] fn designate_tool( mut actions: MessageReader, keys: Res>, buttons: Res>, over_ui: Res, cursor: Res, map: Res, mut tool: ResMut, mut designations: ResMut, ) { for action in actions.read() { match action { Action::ToggleDesignateTool => tool.active = !tool.active, Action::Cancel | Action::ToggleStockpileTool | Action::ToggleFarmTool | Action::CycleBuildTool => tool.active = false, _ => continue, } tool.drag_start = None; return; } if !tool.active { return; } if buttons.just_pressed(MouseButton::Left) && !over_ui.0 { tool.drag_start = cursor.tile; } if !buttons.just_released(MouseButton::Left) { return; } let (Some(start), Some(end)) = (tool.drag_start.take(), cursor.tile) else { return; }; if removing(&keys) { let (lo, hi) = (start.min(end), start.max(end)); designations .0 .retain(|t| !(t.cmpge(lo).all() && t.cmple(hi).all())); } else { designate(&map, &mut designations, start, end); } } fn draw_designate_preview( keys: Res>, cursor: Res, tool: Res, mut gizmos: Gizmos, ) { let (Some(start), Some(end)) = (tool.drag_start, cursor.tile) else { return; }; let (min, max) = (start.min(end), start.max(end)); let center = (WorldMap::tile_center(min) + WorldMap::tile_center(max)) / 2.0; let size = (max - min + 1).as_vec2() * TILE_SIZE; let color = if removing(&keys) { PREVIEW_REMOVE_COLOR } else { PREVIEW_ADD_COLOR }; gizmos.rect_2d(center, size, color); } fn draw_designations(designations: Res, mut gizmos: Gizmos) { let half = TILE_SIZE * 0.3; for &tile in &designations.0 { let c = WorldMap::tile_center(tile); gizmos.line_2d( c - Vec2::splat(half), c + Vec2::splat(half), DESIGNATION_COLOR, ); gizmos.line_2d( c + Vec2::new(-half, half), c + Vec2::new(half, -half), DESIGNATION_COLOR, ); } } #[cfg(test)] mod tests { use super::*; use crate::world::{Plant, Terrain}; #[test] fn only_trees_and_rock_can_be_designated() { let mut map = WorldMap::filled(UVec2::splat(6), Terrain::Grass); map.set_plant(UVec2::new(1, 1), Some(Plant::Tree)); map.set(UVec2::new(2, 2), Terrain::Stone); map.set_plant(UVec2::new(3, 3), Some(Plant::BerryBush)); map.set_growth(UVec2::new(3, 3), 1.0); let mut designations = Designations::default(); designate(&map, &mut designations, UVec2::ZERO, UVec2::splat(5)); assert_eq!( designations.0, HashSet::from([UVec2::new(1, 1), UVec2::new(2, 2)]) ); } }