Hunts leave carcasses that colonists butcher into meat
- author
- Marijn Besseling <njirambem@gmail.com> · 2026-10-24 22:11 UTC
- commit
- 137cf1843fb299dab6f19eadc73c61e7a67ec24d
- parent
- 1bacc518e6
- tree
- browse at this commit
6 files changed +77 -5
| src/economy.rs | +3 -1 | |
| src/food.rs | +1 -1 | |
| src/items.rs | +4 -1 | |
| src/jobs/mod.rs | +54 -1 | |
| src/jobs/tests.rs | +11 -0 | |
| src/selection.rs | +4 -1 |
src/economy.rs +3 -1
| @@ -47,7 +47,9 @@ impl Stock { | ||
| 47 | 47 | ItemKind::Stone => Stock::Stone, |
| 48 | 48 | ItemKind::Iron => Stock::Iron, |
| 49 | 49 | ItemKind::Gold => Stock::Gold, |
| 50 | - ItemKind::Berries | ItemKind::Meat | ItemKind::Vegetables => Stock::RawFood, | |
| 50 | + ItemKind::Berries | ItemKind::Meat | ItemKind::Vegetables | ItemKind::Carcass => { | |
| 51 | + Stock::RawFood | |
| 52 | + } | |
| 51 | 53 | ItemKind::Meal => Stock::Meals, |
| 52 | 54 | } |
| 53 | 55 | } |
src/food.rs +1 -1
| @@ -33,7 +33,7 @@ const SPOIL_INTERVAL_TICKS: u64 = 60; | ||
| 33 | 33 | |
| 34 | 34 | pub fn shelf_life_hours(kind: ItemKind) -> Option<f32> { |
| 35 | 35 | match kind { |
| 36 | - ItemKind::Meat => Some(48.0), | |
| 36 | + ItemKind::Meat | ItemKind::Carcass => Some(48.0), | |
| 37 | 37 | ItemKind::Berries => Some(72.0), |
| 38 | 38 | ItemKind::Meal => Some(96.0), |
| 39 | 39 | ItemKind::Vegetables => Some(120.0), |
src/items.rs +4 -1
| @@ -29,10 +29,11 @@ pub enum ItemKind { | ||
| 29 | 29 | Meat, |
| 30 | 30 | Vegetables, |
| 31 | 31 | Meal, |
| 32 | + Carcass, | |
| 32 | 33 | } |
| 33 | 34 | |
| 34 | 35 | impl ItemKind { |
| 35 | - pub const ALL: [ItemKind; 8] = [ | |
| 36 | + pub const ALL: [ItemKind; 9] = [ | |
| 36 | 37 | ItemKind::Wood, |
| 37 | 38 | ItemKind::Stone, |
| 38 | 39 | ItemKind::Iron, |
| @@ -41,6 +42,7 @@ impl ItemKind { | ||
| 41 | 42 | ItemKind::Meat, |
| 42 | 43 | ItemKind::Vegetables, |
| 43 | 44 | ItemKind::Meal, |
| 45 | + ItemKind::Carcass, | |
| 44 | 46 | ]; |
| 45 | 47 | |
| 46 | 48 | pub fn nutrition(self) -> f32 { |
| @@ -69,6 +71,7 @@ impl ItemKind { | ||
| 69 | 71 | ItemKind::Meat => Color::srgb_u8(200, 80, 70), |
| 70 | 72 | ItemKind::Vegetables => Color::srgb_u8(120, 170, 60), |
| 71 | 73 | ItemKind::Meal => Color::srgb_u8(240, 210, 150), |
| 74 | + ItemKind::Carcass => Color::srgb_u8(120, 60, 50), | |
| 72 | 75 | } |
| 73 | 76 | } |
| 74 | 77 | } |
src/jobs/mod.rs +54 -1
| @@ -122,6 +122,10 @@ pub enum Task { | ||
| 122 | 122 | station: UVec2, |
| 123 | 123 | progress: u32, |
| 124 | 124 | }, |
| 125 | + Butcher { | |
| 126 | + site: UVec2, | |
| 127 | + progress: u32, | |
| 128 | + }, | |
| 125 | 129 | } |
| 126 | 130 | |
| 127 | 131 | pub fn work_rate(light: &LightMap, tile: UVec2) -> u32 { |
| @@ -348,6 +352,7 @@ fn reserve_task(reservations: &mut Reservations, task: Task, by: Entity) { | ||
| 348 | 352 | } |
| 349 | 353 | } |
| 350 | 354 | Task::Hunt { animal, .. } => reservations.reserve(Target::Animal(animal), by), |
| 355 | + Task::Butcher { site, .. } => reservations.reserve(Target::Pickup(site), by), | |
| 351 | 356 | Task::Cook { |
| 352 | 357 | pickup, station, .. |
| 353 | 358 | } => { |
| @@ -689,6 +694,25 @@ pub fn raw_food_source(ctx: &BuildContext, from: UVec2) -> Option<UVec2> { | ||
| 689 | 694 | nearest_of(ctx.map, from, SEARCH_TILES, &sources) |
| 690 | 695 | } |
| 691 | 696 | |
| 697 | +pub const BUTCHER_WORK: u32 = 240; | |
| 698 | + | |
| 699 | +pub fn find_butchering( | |
| 700 | + map: &WorldMap, | |
| 701 | + reservations: &Reservations, | |
| 702 | + items: &[(Item, TilePos)], | |
| 703 | + from: UVec2, | |
| 704 | +) -> Option<Task> { | |
| 705 | + let carcasses: HashSet<UVec2> = items | |
| 706 | + .iter() | |
| 707 | + .filter(|(item, pos)| { | |
| 708 | + item.kind == ItemKind::Carcass && reservations.is_free(Target::Pickup(pos.0)) | |
| 709 | + }) | |
| 710 | + .map(|(_, pos)| pos.0) | |
| 711 | + .collect(); | |
| 712 | + let site = nearest_of(map, from, SEARCH_TILES, &carcasses)?; | |
| 713 | + Some(Task::Butcher { site, progress: 0 }) | |
| 714 | +} | |
| 715 | + | |
| 692 | 716 | pub fn find_cooking(ctx: &BuildContext, from: UVec2) -> Option<Task> { |
| 693 | 717 | let station = ctx |
| 694 | 718 | .structures |
| @@ -950,6 +974,7 @@ fn choose_jobs( | ||
| 950 | 974 | }) |
| 951 | 975 | .flatten() |
| 952 | 976 | }) |
| 977 | + .or_else(|| find_butchering(&map, &reservations, &snapshot, tile.0)) | |
| 953 | 978 | .or_else(|| find_work(&map, &reservations, tile.0, |_, k| k.is_food() && wanted(k))) |
| 954 | 979 | .or_else(|| { |
| 955 | 980 | (!designations.0.is_empty()) |
| @@ -1180,7 +1205,7 @@ fn run_jobs( | ||
| 1180 | 1205 | &mut items, |
| 1181 | 1206 | &item_assets, |
| 1182 | 1207 | prey, |
| 1183 | - Item::new(ItemKind::Meat, kind.meat()), | |
| 1208 | + Item::new(ItemKind::Carcass, kind.meat()), | |
| 1184 | 1209 | ); |
| 1185 | 1210 | Step::Done |
| 1186 | 1211 | } |
| @@ -1188,6 +1213,34 @@ fn run_jobs( | ||
| 1188 | 1213 | } |
| 1189 | 1214 | _ => Step::Done, |
| 1190 | 1215 | }, |
| 1216 | + Task::Butcher { site, progress } => { | |
| 1217 | + let site = *site; | |
| 1218 | + if reservations.holder(Target::Pickup(site)) != Some(entity) { | |
| 1219 | + Step::Done | |
| 1220 | + } else if tile.0 != site { | |
| 1221 | + walk_to(&map, tile.0, site, &mut path) | |
| 1222 | + } else { | |
| 1223 | + *progress += work_rate(&light, tile.0); | |
| 1224 | + if *progress < BUTCHER_WORK { | |
| 1225 | + Step::Continue | |
| 1226 | + } else { | |
| 1227 | + let is_carcass = items | |
| 1228 | + .iter() | |
| 1229 | + .any(|(_, item, pos)| pos.0 == site && item.kind == ItemKind::Carcass); | |
| 1230 | + if is_carcass | |
| 1231 | + && let Some(carcass) = | |
| 1232 | + take_items(&mut commands, &mut items, site, u32::MAX) | |
| 1233 | + { | |
| 1234 | + let meat = Item { | |
| 1235 | + kind: ItemKind::Meat, | |
| 1236 | + ..carcass | |
| 1237 | + }; | |
| 1238 | + drop_items(&mut commands, &mut items, &item_assets, site, meat); | |
| 1239 | + } | |
| 1240 | + Step::Done | |
| 1241 | + } | |
| 1242 | + } | |
| 1243 | + } | |
| 1191 | 1244 | Task::Eat { pickup, progress } => { |
| 1192 | 1245 | if let Some(from) = *pickup { |
| 1193 | 1246 | if reservations.holder(Target::Pickup(from)) != Some(entity) { |
src/jobs/tests.rs +11 -0
| @@ -1224,3 +1224,14 @@ fn colonists_deconstruct_marked_structures_and_recover_some_material() { | ||
| 1224 | 1224 | assert!(app.world().resource::<Blueprints>().0.is_empty()); |
| 1225 | 1225 | assert_eq!(items_of(&mut app, ItemKind::Stone), 1); |
| 1226 | 1226 | } |
| 1227 | + | |
| 1228 | +#[test] | |
| 1229 | +fn carcasses_are_butchered_into_meat() { | |
| 1230 | + let mut app = test_app(map_with_trees(&[])); | |
| 1231 | + set_goals(&mut app, &[]); | |
| 1232 | + put_items(&mut app, UVec2::new(8, 5), ItemKind::Carcass, 8); | |
| 1233 | + spawn_colonist(&mut app, UVec2::new(2, 5)); | |
| 1234 | + run_ticks(&mut app, 61 + 60 * 3 + BUTCHER_WORK); | |
| 1235 | + assert_eq!(items_of(&mut app, ItemKind::Carcass), 0); | |
| 1236 | + assert_eq!(items_of(&mut app, ItemKind::Meat), 8); | |
| 1237 | +} | |
src/selection.rs +4 -1
| @@ -11,7 +11,7 @@ use crate::{ | ||
| 11 | 11 | family::{Child, Partner}, |
| 12 | 12 | food::Hunger, |
| 13 | 13 | items::{Carrying, Item, stock}, |
| 14 | - jobs::{COOK_WORK, CurrentJob, EXPAND_WORK, Job, Order, Task, WorkKind}, | |
| 14 | + jobs::{BUTCHER_WORK, COOK_WORK, CurrentJob, EXPAND_WORK, Job, Order, Task, WorkKind}, | |
| 15 | 15 | light::daylight, |
| 16 | 16 | needs::Rest, |
| 17 | 17 | population::ColonyLog, |
| @@ -466,6 +466,7 @@ pub fn progress_fraction(task: Task) -> Option<f32> { | ||
| 466 | 466 | Task::Build { kind, progress, .. } => Some(progress as f32 / kind.work() as f32), |
| 467 | 467 | Task::Hunt { kind, progress, .. } => Some(progress as f32 / kind.hunt_work() as f32), |
| 468 | 468 | Task::Cook { progress, .. } => Some(progress as f32 / COOK_WORK as f32), |
| 469 | + Task::Butcher { progress, .. } => Some(progress as f32 / BUTCHER_WORK as f32), | |
| 469 | 470 | _ => None, |
| 470 | 471 | } |
| 471 | 472 | } |
| @@ -565,6 +566,8 @@ pub fn describe_colonist(view: &ColonistView) -> String { | ||
| 565 | 566 | ) => "fetching ingredients".to_string(), |
| 566 | 567 | (Some(Task::Cook { .. }), n) if n > 0 => "carrying ingredients to the fire".to_string(), |
| 567 | 568 | (Some(task @ Task::Cook { .. }), _) => format!("cooking a meal, {}%", percent(task)), |
| 569 | + (Some(Task::Butcher { .. }), n) if n > 0 => "walking to a carcass".to_string(), | |
| 570 | + (Some(task @ Task::Butcher { .. }), _) => format!("butchering, {}%", percent(task)), | |
| 568 | 571 | (Some(task @ Task::Build { kind, .. }), _) => { |
| 569 | 572 | format!("building a {}, {}%", kind.name(), percent(task)) |
| 570 | 573 | } |