src/food.rs 3.1 K · 115 lines · raw · history

1 use bevy::prelude::*;
2
3 use crate::{
4 colonist::{Colonist, Path},
5 items::{Item, ItemKind},
6 jobs::{CurrentJob, Reservations, Target},
7 needs::is_sleeping,
8 pathfinding::nearest_tile,
9 sim::{SimSystems, TICKS_PER_HOUR},
10 world::{TilePos, WorldMap},
11 };
12
13 pub struct FoodPlugin;
14
15 impl Plugin for FoodPlugin {
16 fn build(&self, app: &mut App) {
17 app.add_systems(FixedUpdate, update_hunger.in_set(SimSystems::Act));
18 }
19 }
20
21 pub const HUNGER_PER_TICK: f32 = 1.0 / (24.0 * TICKS_PER_HOUR as f32);
22 pub const SLEEPING_HUNGER_FACTOR: f32 = 0.5;
23 pub const HUNGRY: f32 = 0.35;
24 pub const STARVING: f32 = 0.05;
25 pub const EAT_TICKS: u32 = 90;
26 const FOOD_SEARCH_TILES: usize = 15000;
27
28 #[derive(Component, Debug, Clone, Copy, PartialEq)]
29 pub struct Hunger(pub f32);
30
31 impl Default for Hunger {
32 fn default() -> Self {
33 Self(1.0)
34 }
35 }
36
37 pub fn wants_food(fed: f32) -> bool {
38 fed < HUNGRY
39 }
40
41 pub fn portion(kind: ItemKind, fed: f32) -> u32 {
42 ((1.0 - fed) / kind.nutrition()).ceil().max(1.0) as u32
43 }
44
45 pub fn find_food(
46 map: &WorldMap,
47 reservations: &Reservations,
48 items: &[(Item, TilePos)],
49 from: UVec2,
50 ) -> Option<UVec2> {
51 let has = |p: UVec2, accept: fn(ItemKind) -> bool| {
52 reservations.is_free(Target::Pickup(p))
53 && items
54 .iter()
55 .any(|(item, pos)| pos.0 == p && accept(item.kind))
56 };
57 nearest_tile(map, from, FOOD_SEARCH_TILES, |p| {
58 has(p, |k| k == ItemKind::Meal)
59 })
60 .or_else(|| {
61 nearest_tile(map, from, FOOD_SEARCH_TILES, |p| {
62 has(p, ItemKind::is_raw_food)
63 })
64 })
65 }
66
67 pub fn update_hunger(
68 mut colonists: Query<(&mut Hunger, &CurrentJob, &TilePos, &Path), With<Colonist>>,
69 ) {
70 for (mut hunger, job, tile, path) in &mut colonists {
71 let rate = if is_sleeping(job, tile, path) {
72 HUNGER_PER_TICK * SLEEPING_HUNGER_FACTOR
73 } else {
74 HUNGER_PER_TICK
75 };
76 hunger.0 = (hunger.0 - rate).max(0.0);
77 }
78 }
79
80 #[cfg(test)]
81 mod tests {
82 use super::*;
83 use crate::world::Terrain;
84
85 #[test]
86 fn portions_fill_a_colonist_up() {
87 assert_eq!(portion(ItemKind::Meal, 0.2), 1);
88 assert_eq!(portion(ItemKind::Berries, 0.3), 7);
89 assert_eq!(portion(ItemKind::Berries, 0.99), 1);
90 }
91
92 #[test]
93 fn colonists_prefer_meals_over_raw_food() {
94 let map = WorldMap::filled(UVec2::splat(20), Terrain::Grass);
95 let reservations = Reservations::default();
96 let food = |kind, x| (Item { kind, count: 5 }, TilePos(UVec2::new(x, 0)));
97 let items = [
98 food(ItemKind::Berries, 1),
99 food(ItemKind::Meal, 15),
100 food(ItemKind::Wood, 0),
101 ];
102 assert_eq!(
103 find_food(&map, &reservations, &items, UVec2::ZERO),
104 Some(UVec2::new(15, 0))
105 );
106 assert_eq!(
107 find_food(&map, &reservations, &items[..1], UVec2::ZERO),
108 Some(UVec2::new(1, 0))
109 );
110 assert_eq!(
111 find_food(&map, &reservations, &items[2..], UVec2::ZERO),
112 None
113 );
114 }
115 }