Add a Demolish tool for structures and roofs
- author
- Marijn Besseling <njirambem@gmail.com> · 2026-10-25 14:45 UTC
- commit
- d1f6a4bb12f4a1fd52711b449ce77ba5a5d53f5c
- parent
- 9448289802
- tree
- browse at this commit
10 files changed +107 -31
| CLAUDE.md | +1 -1 | |
| src/actions.rs | +4 -1 | |
| src/building.rs | +27 -12 | |
| src/designations.rs | +2 -1 | |
| src/family.rs | +49 -0 | |
| src/jobs/mod.rs | +5 -2 | |
| src/selection.rs | +1 -1 | |
| src/ui.rs | +13 -10 | |
| src/zones.rs | +4 -3 | |
| tasks.md | +1 -0 |
CLAUDE.md +1 -1
| @@ -30,7 +30,7 @@ DF/RimWorld-style colony sim, Rust + Bevy 0.20 (newer than training data — che | ||
| 30 | 30 | - `src/structures.rs` `Structure{kind: StructureKind::{Campfire, Torch, Bed, Wall, Door}}` + TilePos entities; `Fuel(u32)` only on kinds with `fuel_ticks()` (fires: 12h/8h), `burn_fuel` (SimSystems::Act) despawns at 0; beds are permanent. Kind stats: `cost()` (material, amount): fires/bed/door wood, wall 2 stone; `light()` radius+color (fires only), `name()`. Walls connect: `join_walls` (Update; reruns when the wall/door tile set changes or a wall/door is added) gives each wall `WallJointPiece` children from `wall_joints` (top arm toward each wall/door neighbour, corner fill when both sides and the diagonal are solid), so the light top is continuous and only outer edges show the dark base. Visuals are Mesh2d children (campfire = stone ring + crossed logs + triangle flame, torch = stick + small flame, bed = frame + blanket + pillow, wall = stone square, door = brown square + knob; avoid plain circles, those read as colonists; z=0.8). `spawn_structure` inserts Fuel when needed. Cleared on WorldGenerated. |
| 31 | 31 | - `src/needs.rs` `Rest(f32)` on colonists (1 = rested): `update_rest` (SimSystems::Act) drains 1/24 per game hour awake, recovers 1/7 per hour (1/5 in a bed, ×ROOF_REST_BONUS 1.25 under a roof) while `is_sleeping` (Sleep job, at spot, path empty). `wants_sleep`: rest < NAP_REST (0.3) any time, or night (21–6) and rest < BEDTIME_REST (0.9). `done_sleeping`: rest full and not night (sleep lasts until morning). EXHAUSTED_REST (0.1) makes choose_jobs drop autonomous (not ordered) jobs. `SleepContext::spot` = nearest bed not reserved as `Target::SleepSpot`, else nearest free tile (dry, no plant/stockpile/fire/item, unreserved `Target::SleepSpot`) to the nearest campfire, else home. Sleeping colonists show a `SleepMarker` Text2d "z" child. |
| 32 | 32 | - `src/building.rs` construction planning. `BuildKind::{Structure(StructureKind), Roof, Pave}` with `cost()`/`work()`/`blocks_tile()` (walls). `Blueprints(HashMap<tile, BuildKind>)` resource, drawn as gizmo outlines, cleared on WorldGenerated. `plan_shelter` (SimSystems::Decide, every 300 ticks, only while no non-Pave blueprints; merges its plan into Blueprints): for unroofed beds, if `enclosed_room` (4-conn flood bounded by walls/doors/rock, ≤ MAX_ROOM_TILES, not touching map edge) → Roof blueprints for unroofed room tiles; else `ShelterContext::plan` tries every wall ring with 0–2 tiles margin per side around the beds' bbox, cheapest (ring length + penalty per margin-0 side) where every ring tile is rock/existing wall/door or buildable (dry, no plant/item/structure; empty stockpile tiles allowed and removed from the stockpile when planned), one Door on the non-corner ring tile nearest the campfire/home. |
| 33 | -- Build tool (building.rs): `BuildTool{kind}` `mode: Option<BuildMode::{Place(kind), Deconstruct}>` cycled by B / Build button through `BUILD_TOOL_CYCLE` (Wall, Door, Campfire, Torch, Bed, Workbench, Statue, Floor (drag fills a rect via `place_floors`), Deconstruct, off); Deconstruct drag marks structure tiles as `BuildKind::Deconstruct` blueprints (cost 0 → Build task without pickup; completion despawns the structure, clears wall flag, refunds half its cost); Wall drag = room outline (`placements`), others place at the release tile; `place_blueprints` skips plants/walls/structures/water/rock and clears stockpile under walls/doors; shift-drag removes blueprints in the rect. Zone tool and build tool cancel each other; selection ignores clicks while either is active. Colonists build manual blueprints via `find_blueprint_work`. | |
| 33 | +- Build tool (building.rs): `BuildTool{kind}` `mode: Option<BuildMode::{Place(kind), Deconstruct}>` cycled by B / Build button through `BUILD_TOOL_CYCLE` (Wall, Door, Campfire, Torch, Bed, Workbench, Statue, Hearth, Floor (drag fills a rect via `place_floors`), off). Demolish is its own tool: Action::ToggleDemolishTool (Q, "Demolish" button) toggles `BuildMode::Deconstruct` (B from it goes to the first build mode); drag marks structure and roofed tiles as `BuildKind::Deconstruct` blueprints (cost 0 → Build task without pickup; blocked only if neither structure nor roof is left; completion despawns the structure, refunds half its cost, clears wall and roof flags). plan_homes forgets homes that are `torn_down` (fewer than half of the ring tiles wall/door, no ring blueprints pending); Wall drag = room outline (`placements`), others place at the release tile; `place_blueprints` skips plants/walls/structures/water/rock and clears stockpile under walls/doors; shift-drag removes blueprints in the rect. Zone tool and build tool cancel each other; selection ignores clicks while either is active. Colonists build manual blueprints via `find_blueprint_work`. | |
| 34 | 34 | - `src/designations.rs` `Designations(HashSet<tile>)` of trees/rock (`designatable` = site_work Chop/Mine) marked with the Chop/Mine tool (X; drag adds, shift-drag clears; yellow X gizmos); `prune_designations` drops worked-out tiles. choose_jobs works designated sites (find_work with a site predicate) right after food gathering, regardless of stock goals. Tools (zone, build, designate) cancel each other. |
| 35 | 35 | - Refuel: `BuildKind::Refuel(kind)` (cost = kind.cost(), 60 work) reuses the Build task; `find_refuel` (before find_lighting) picks the lowest-fuel fire within 2×HOME_RADIUS of home below 1/REFUEL_BELOW_SHARE of full; completion resets Fuel to full. |
| 36 | 36 | - Camera zoom buttons: Action::ZoomIn/ZoomOut (=/-) → `button_zoom` using `CameraController::zoomed`. |
src/actions.rs +4 -1
| @@ -26,9 +26,10 @@ pub enum Action { | ||
| 26 | 26 | ZoomOut, |
| 27 | 27 | Save, |
| 28 | 28 | Load, |
| 29 | + ToggleDemolishTool, | |
| 29 | 30 | } |
| 30 | 31 | |
| 31 | -pub const KEYBINDS: [(KeyCode, Action); 16] = [ | |
| 32 | +pub const KEYBINDS: [(KeyCode, Action); 17] = [ | |
| 32 | 33 | (KeyCode::Space, Action::TogglePause), |
| 33 | 34 | (KeyCode::Digit1, Action::SetSpeed(SimSpeed::Normal)), |
| 34 | 35 | (KeyCode::Digit2, Action::SetSpeed(SimSpeed::Fast)), |
| @@ -38,6 +39,7 @@ pub const KEYBINDS: [(KeyCode, Action); 16] = [ | ||
| 38 | 39 | (KeyCode::KeyF, Action::ToggleFarmTool), |
| 39 | 40 | (KeyCode::KeyB, Action::CycleBuildTool), |
| 40 | 41 | (KeyCode::KeyX, Action::ToggleDesignateTool), |
| 42 | + (KeyCode::KeyQ, Action::ToggleDemolishTool), | |
| 41 | 43 | (KeyCode::Escape, Action::Cancel), |
| 42 | 44 | (KeyCode::KeyR, Action::NewWorld), |
| 43 | 45 | (KeyCode::KeyH, Action::ToggleUi), |
| @@ -59,6 +61,7 @@ impl Action { | ||
| 59 | 61 | Action::ToggleFarmTool => "Farm", |
| 60 | 62 | Action::CycleBuildTool => "Build", |
| 61 | 63 | Action::ToggleDesignateTool => "Mark", |
| 64 | + Action::ToggleDemolishTool => "Demolish", | |
| 62 | 65 | Action::Cancel => "Cancel", |
| 63 | 66 | Action::NewWorld => "New map", |
| 64 | 67 | Action::ToggleUi => "Hide", |
src/building.rs +27 -12
| @@ -316,7 +316,7 @@ pub enum BuildMode { | ||
| 316 | 316 | Deconstruct, |
| 317 | 317 | } |
| 318 | 318 | |
| 319 | -pub const BUILD_TOOL_CYCLE: [BuildMode; 10] = [ | |
| 319 | +pub const BUILD_TOOL_CYCLE: [BuildMode; 9] = [ | |
| 320 | 320 | BuildMode::Place(StructureKind::Wall), |
| 321 | 321 | BuildMode::Place(StructureKind::Door), |
| 322 | 322 | BuildMode::Place(StructureKind::Campfire), |
| @@ -326,7 +326,6 @@ pub const BUILD_TOOL_CYCLE: [BuildMode; 10] = [ | ||
| 326 | 326 | BuildMode::Place(StructureKind::Statue), |
| 327 | 327 | BuildMode::Place(StructureKind::Hearth), |
| 328 | 328 | BuildMode::Floor, |
| 329 | - BuildMode::Deconstruct, | |
| 330 | 329 | ]; |
| 331 | 330 | |
| 332 | 331 | #[derive(Resource, Default, Debug)] |
| @@ -343,11 +342,10 @@ impl BuildTool { | ||
| 343 | 342 | fn cycle(&mut self) { |
| 344 | 343 | let next = match self.mode { |
| 345 | 344 | None => Some(0), |
| 346 | - Some(mode) => BUILD_TOOL_CYCLE | |
| 347 | - .iter() | |
| 348 | - .position(|m| *m == mode) | |
| 349 | - .map(|i| i + 1) | |
| 350 | - .filter(|&i| i < BUILD_TOOL_CYCLE.len()), | |
| 345 | + Some(mode) => match BUILD_TOOL_CYCLE.iter().position(|m| *m == mode) { | |
| 346 | + Some(i) => Some(i + 1).filter(|&i| i < BUILD_TOOL_CYCLE.len()), | |
| 347 | + None => Some(0), | |
| 348 | + }, | |
| 351 | 349 | }; |
| 352 | 350 | self.mode = next.map(|i| BUILD_TOOL_CYCLE[i]); |
| 353 | 351 | } |
| @@ -359,12 +357,13 @@ fn rect(start: UVec2, end: UVec2) -> impl Iterator<Item = UVec2> { | ||
| 359 | 357 | } |
| 360 | 358 | |
| 361 | 359 | pub fn mark_deconstruction( |
| 360 | + map: &WorldMap, | |
| 362 | 361 | blueprints: &mut Blueprints, |
| 363 | 362 | occupied: &HashSet<UVec2>, |
| 364 | 363 | start: UVec2, |
| 365 | 364 | end: UVec2, |
| 366 | 365 | ) { |
| 367 | - for tile in rect(start, end).filter(|t| occupied.contains(t)) { | |
| 366 | + for tile in rect(start, end).filter(|t| occupied.contains(t) || map.is_roofed(t.as_ivec2())) { | |
| 368 | 367 | blueprints.0.insert(tile, BuildKind::Deconstruct); |
| 369 | 368 | } |
| 370 | 369 | } |
| @@ -432,6 +431,10 @@ fn build_tool( | ||
| 432 | 431 | for action in actions.read() { |
| 433 | 432 | match action { |
| 434 | 433 | Action::CycleBuildTool => tool.cycle(), |
| 434 | + Action::ToggleDemolishTool => { | |
| 435 | + tool.mode = | |
| 436 | + (tool.mode != Some(BuildMode::Deconstruct)).then_some(BuildMode::Deconstruct); | |
| 437 | + } | |
| 435 | 438 | Action::Cancel |
| 436 | 439 | | Action::ToggleStockpileTool |
| 437 | 440 | | Action::ToggleFarmTool |
| @@ -467,7 +470,7 @@ fn build_tool( | ||
| 467 | 470 | place_blueprints(&map, &mut zones, &mut blueprints, &occupied, kind, &tiles); |
| 468 | 471 | } |
| 469 | 472 | BuildMode::Floor => place_floors(&map, &mut blueprints, start, end), |
| 470 | - BuildMode::Deconstruct => mark_deconstruction(&mut blueprints, &occupied, start, end), | |
| 473 | + BuildMode::Deconstruct => mark_deconstruction(&map, &mut blueprints, &occupied, start, end), | |
| 471 | 474 | } |
| 472 | 475 | } |
| 473 | 476 | |
| @@ -549,12 +552,24 @@ mod tests { | ||
| 549 | 552 | } |
| 550 | 553 | |
| 551 | 554 | #[test] |
| 552 | - fn deconstruction_marks_only_structures() { | |
| 555 | + fn demolition_marks_structures_and_roofs_in_the_area() { | |
| 556 | + let mut map = WorldMap::filled(UVec2::splat(10), Terrain::Grass); | |
| 557 | + map.set_roof(UVec2::new(2, 3), true); | |
| 553 | 558 | let mut blueprints = Blueprints::default(); |
| 554 | 559 | let occupied = HashSet::from([UVec2::new(1, 1), UVec2::new(9, 9)]); |
| 555 | - mark_deconstruction(&mut blueprints, &occupied, UVec2::ZERO, UVec2::splat(3)); | |
| 556 | - assert_eq!(blueprints.0.len(), 1); | |
| 560 | + mark_deconstruction( | |
| 561 | + &map, | |
| 562 | + &mut blueprints, | |
| 563 | + &occupied, | |
| 564 | + UVec2::ZERO, | |
| 565 | + UVec2::splat(3), | |
| 566 | + ); | |
| 567 | + assert_eq!(blueprints.0.len(), 2); | |
| 557 | 568 | assert_eq!(blueprints.get(UVec2::ONE), Some(BuildKind::Deconstruct)); |
| 569 | + assert_eq!( | |
| 570 | + blueprints.get(UVec2::new(2, 3)), | |
| 571 | + Some(BuildKind::Deconstruct) | |
| 572 | + ); | |
| 558 | 573 | } |
| 559 | 574 | |
| 560 | 575 | #[test] |
src/designations.rs +2 -1
| @@ -96,7 +96,8 @@ fn designate_tool( | ||
| 96 | 96 | Action::Cancel |
| 97 | 97 | | Action::ToggleStockpileTool |
| 98 | 98 | | Action::ToggleFarmTool |
| 99 | - | Action::CycleBuildTool => tool.active = false, | |
| 99 | + | Action::CycleBuildTool | |
| 100 | + | Action::ToggleDemolishTool => tool.active = false, | |
| 100 | 101 | _ => continue, |
| 101 | 102 | } |
| 102 | 103 | tool.drag_start = None; |
src/family.rs +49 -0
| @@ -399,6 +399,15 @@ pub fn floored(map: &WorldMap, tile: UVec2) -> bool { | ||
| 399 | 399 | map.get(tile.as_ivec2()) == Some(Terrain::WoodFloor) |
| 400 | 400 | } |
| 401 | 401 | |
| 402 | +pub fn torn_down(map: &WorldMap, doors: &HashSet<UVec2>, home: &Home) -> bool { | |
| 403 | + let ring: Vec<UVec2> = home.ring().collect(); | |
| 404 | + let intact = ring | |
| 405 | + .iter() | |
| 406 | + .filter(|t| map.is_wall(t.as_ivec2()) || doors.contains(t)) | |
| 407 | + .count(); | |
| 408 | + intact * 2 < ring.len() | |
| 409 | +} | |
| 410 | + | |
| 402 | 411 | pub fn abandoned_home(map: &WorldMap, doors: &HashSet<UVec2>, bed: UVec2) -> Option<Home> { |
| 403 | 412 | let room = enclosed_room(map, doors, bed)?; |
| 404 | 413 | let lo = room.iter().fold(UVec2::MAX, |a, t| a.min(*t)); |
| @@ -472,6 +481,12 @@ fn plan_homes( | ||
| 472 | 481 | .collect() |
| 473 | 482 | }; |
| 474 | 483 | let doors = of_kind(StructureKind::Door); |
| 484 | + let standing = |home: &Home| { | |
| 485 | + let planned = home.ring().any(|t| blueprints.get(t).is_some()); | |
| 486 | + planned || !torn_down(&map, &doors, home) | |
| 487 | + }; | |
| 488 | + homes.by_family.retain(|_, home| standing(home)); | |
| 489 | + homes.vacant.retain(standing); | |
| 475 | 490 | let statues = of_kind(StructureKind::Statue); |
| 476 | 491 | let hearths = of_kind(StructureKind::Hearth); |
| 477 | 492 | let beds = of_kind(StructureKind::Bed); |
| @@ -1019,4 +1034,38 @@ mod tests { | ||
| 1019 | 1034 | .any(|k| *k == BuildKind::Structure(StructureKind::Wall)) |
| 1020 | 1035 | ); |
| 1021 | 1036 | } |
| 1037 | + | |
| 1038 | + // When the player demolishes a home, the family stops treating it as theirs (and plans a new | |
| 1039 | + // one) once most of its walls are gone; otherwise colonists kept putting doors in the gaps. | |
| 1040 | + #[test] | |
| 1041 | + fn torn_down_homes_are_forgotten() { | |
| 1042 | + let map = WorldMap::filled(UVec2::splat(30), Terrain::Grass); | |
| 1043 | + let home = Home::at(UVec2::new(5, 5)); | |
| 1044 | + let mut standing = map.clone(); | |
| 1045 | + for tile in home.ring() { | |
| 1046 | + standing.set_wall(tile, true); | |
| 1047 | + } | |
| 1048 | + let mut ruin = map.clone(); | |
| 1049 | + for tile in home.ring().take(4) { | |
| 1050 | + ruin.set_wall(tile, true); | |
| 1051 | + } | |
| 1052 | + let doors = HashSet::new(); | |
| 1053 | + assert!(!torn_down(&standing, &doors, &home)); | |
| 1054 | + assert!(torn_down(&ruin, &doors, &home)); | |
| 1055 | + let mut app = App::new(); | |
| 1056 | + app.insert_resource(ruin) | |
| 1057 | + .init_resource::<SimTick>() | |
| 1058 | + .init_resource::<Zones>() | |
| 1059 | + .init_resource::<Blueprints>() | |
| 1060 | + .init_resource::<ColonyReach>() | |
| 1061 | + .insert_resource(Homes { | |
| 1062 | + by_family: HashMap::from([(1, home)]), | |
| 1063 | + ..default() | |
| 1064 | + }) | |
| 1065 | + .add_systems(Update, plan_homes); | |
| 1066 | + app.world_mut() | |
| 1067 | + .spawn((Colonist { name: "Ada".into() }, Family(1))); | |
| 1068 | + app.update(); | |
| 1069 | + assert!(app.world().resource::<Homes>().of(Some(Family(1))) != Some(&home)); | |
| 1070 | + } | |
| 1022 | 1071 | } |
src/jobs/mod.rs +5 -2
| @@ -2159,7 +2159,9 @@ fn run_jobs( | ||
| 2159 | 2159 | BuildKind::Refuel(fire) => !structures |
| 2160 | 2160 | .iter() |
| 2161 | 2161 | .any(|(_, pos, s, _)| pos.0 == site && s.kind == fire), |
| 2162 | - BuildKind::Deconstruct => !structure_there, | |
| 2162 | + BuildKind::Deconstruct => { | |
| 2163 | + !structure_there && !map.is_roofed(site.as_ivec2()) | |
| 2164 | + } | |
| 2163 | 2165 | _ => structure_there, |
| 2164 | 2166 | }; |
| 2165 | 2167 | let (material, cost) = kind.cost(); |
| @@ -2263,8 +2265,9 @@ fn run_jobs( | ||
| 2263 | 2265 | ); |
| 2264 | 2266 | } |
| 2265 | 2267 | } |
| 2266 | - if map.is_wall(site.as_ivec2()) { | |
| 2268 | + if map.is_wall(site.as_ivec2()) || map.is_roofed(site.as_ivec2()) { | |
| 2267 | 2269 | map.set_wall(site, false); |
| 2270 | + map.set_roof(site, false); | |
| 2268 | 2271 | changed.write(TileChanged(site)); |
| 2269 | 2272 | } |
| 2270 | 2273 | } |
src/selection.rs +1 -1
| @@ -438,7 +438,7 @@ fn update_tile_info( | ||
| 438 | 438 | "\nBUILD FLOOR: drag an area, shift-drag to remove plans, B for next".to_string() |
| 439 | 439 | } |
| 440 | 440 | (None, Some(BuildMode::Deconstruct)) => { |
| 441 | - "\nDECONSTRUCT: drag over structures, shift-drag to remove plans, B for next" | |
| 441 | + "\nDEMOLISH: drag over structures and roofs to take them down, shift-drag to cancel" | |
| 442 | 442 | .to_string() |
| 443 | 443 | } |
| 444 | 444 | (None, Some(BuildMode::Place(kind))) => format!( |
src/ui.rs +13 -10
| @@ -10,7 +10,7 @@ use bevy::{ | ||
| 10 | 10 | |
| 11 | 11 | use crate::{ |
| 12 | 12 | actions::Action, |
| 13 | - building::BuildTool, | |
| 13 | + building::{BuildMode, BuildTool}, | |
| 14 | 14 | designations::DesignateTool, |
| 15 | 15 | sim::SimSpeed, |
| 16 | 16 | zones::{ZoneKind, ZoneTool}, |
| @@ -36,7 +36,7 @@ impl Plugin for HudPlugin { | ||
| 36 | 36 | } |
| 37 | 37 | } |
| 38 | 38 | |
| 39 | -const TOOLBAR: [Action; 16] = [ | |
| 39 | +const TOOLBAR: [Action; 17] = [ | |
| 40 | 40 | Action::TogglePause, |
| 41 | 41 | Action::SetSpeed(SimSpeed::Normal), |
| 42 | 42 | Action::SetSpeed(SimSpeed::Fast), |
| @@ -46,6 +46,7 @@ const TOOLBAR: [Action; 16] = [ | ||
| 46 | 46 | Action::ToggleFarmTool, |
| 47 | 47 | Action::CycleBuildTool, |
| 48 | 48 | Action::ToggleDesignateTool, |
| 49 | + Action::ToggleDemolishTool, | |
| 49 | 50 | Action::Cancel, |
| 50 | 51 | Action::NewWorld, |
| 51 | 52 | Action::Save, |
| @@ -203,11 +204,13 @@ pub fn is_active( | ||
| 203 | 204 | paused: bool, |
| 204 | 205 | speed: SimSpeed, |
| 205 | 206 | tool: Option<ZoneKind>, |
| 206 | - building: bool, | |
| 207 | + build_mode: Option<BuildMode>, | |
| 207 | 208 | designating: bool, |
| 208 | 209 | ) -> bool { |
| 210 | + let demolishing = build_mode == Some(BuildMode::Deconstruct); | |
| 209 | 211 | match action { |
| 210 | - Action::CycleBuildTool => building, | |
| 212 | + Action::CycleBuildTool => build_mode.is_some() && !demolishing, | |
| 213 | + Action::ToggleDemolishTool => demolishing, | |
| 211 | 214 | Action::ToggleDesignateTool => designating, |
| 212 | 215 | Action::TogglePause => paused, |
| 213 | 216 | Action::SetSpeed(s) => !paused && s == speed, |
| @@ -231,7 +234,7 @@ fn style_buttons( | ||
| 231 | 234 | time.is_paused(), |
| 232 | 235 | *speed, |
| 233 | 236 | tool.mode, |
| 234 | - build_tool.active(), | |
| 237 | + build_tool.mode, | |
| 235 | 238 | designate_tool.active, |
| 236 | 239 | ); |
| 237 | 240 | let new = match (pressed && hovered.get(), active, hovered.get()) { |
| @@ -274,19 +277,19 @@ mod tests { | ||
| 274 | 277 | true, |
| 275 | 278 | normal, |
| 276 | 279 | None, |
| 277 | - false, | |
| 280 | + None, | |
| 278 | 281 | false |
| 279 | 282 | )); |
| 280 | 283 | let fast = Action::SetSpeed(SimSpeed::Fast); |
| 281 | - assert!(is_active(fast, false, SimSpeed::Fast, None, false, false)); | |
| 282 | - assert!(!is_active(fast, true, SimSpeed::Fast, None, false, false)); | |
| 284 | + assert!(is_active(fast, false, SimSpeed::Fast, None, None, false)); | |
| 285 | + assert!(!is_active(fast, true, SimSpeed::Fast, None, None, false)); | |
| 283 | 286 | let farm = Some(ZoneKind::Farm); |
| 284 | 287 | assert!(is_active( |
| 285 | 288 | Action::ToggleFarmTool, |
| 286 | 289 | false, |
| 287 | 290 | normal, |
| 288 | 291 | farm, |
| 289 | - false, | |
| 292 | + None, | |
| 290 | 293 | false |
| 291 | 294 | )); |
| 292 | 295 | assert!(!is_active( |
| @@ -294,7 +297,7 @@ mod tests { | ||
| 294 | 297 | false, |
| 295 | 298 | normal, |
| 296 | 299 | farm, |
| 297 | - false, | |
| 300 | + None, | |
| 298 | 301 | false |
| 299 | 302 | )); |
| 300 | 303 | } |
src/zones.rs +4 -3
| @@ -390,9 +390,10 @@ fn zone_tool( | ||
| 390 | 390 | match action { |
| 391 | 391 | Action::ToggleStockpileTool => tool.toggle(ZoneKind::Stockpile), |
| 392 | 392 | Action::ToggleFarmTool => tool.toggle(ZoneKind::Farm), |
| 393 | - Action::Cancel | Action::CycleBuildTool | Action::ToggleDesignateTool => { | |
| 394 | - tool.mode = None | |
| 395 | - } | |
| 393 | + Action::Cancel | |
| 394 | + | Action::CycleBuildTool | |
| 395 | + | Action::ToggleDesignateTool | |
| 396 | + | Action::ToggleDemolishTool => tool.mode = None, | |
| 396 | 397 | _ => continue, |
| 397 | 398 | } |
| 398 | 399 | tool.drag_start = None; |
tasks.md +1 -0
| @@ -50,6 +50,7 @@ | ||
| 50 | 50 | - [x] Optimization pass: single-threaded fixed schedules, A* scratch buffers, capped wander searches, no retry-every-tick for boxed-in animals, cached reach, more search memos; native SIM_BENCH mode |
| 51 | 51 | - [x] Colonist cap removed (numbered names past the list); job search pass for 40+ colonists (reach-based nearest targets, cheaper cooking/blueprint checks) |
| 52 | 52 | - [x] Family hearths: every home gets a hearth where the family cooks (meals stay home) and which heats the home; growth needs 3 days of food per colonist; failed food searches back off |
| 53 | +- [x] Demolish tool (Q / toolbar): drag to have colonists take down structures and roofs; torn-down homes are forgotten | |
| 53 | 54 | - [x] Action/keybind system, toolbar buttons for every keybind, hide-UI toggle (H), clicks on UI don't reach the map |
| 54 | 55 | |
| 55 | 56 | ## Planned |