Newer
Older
use bevy::prelude::{Component, Entity, EventReader, Mut, Query, With, Without};
use serde::{Deserialize, Serialize};
use crate::const_data::get_goods_from_name_checked;
use crate::states::Player;
use crate::world::travel::WorldTickEvent;
use crate::world::{TownName, TradeGood, TradingState};
#[derive(Clone, Debug, Default, Serialize, Deserialize, Component)]
pub struct HungerState {
pub sustenance: usize,
pub starvation_ticks: f32,
}
impl HungerState {
pub fn initial_player() -> Self {
Self {
sustenance: PLAYER_FOOD_TARGET,
starvation_ticks: 0.0,
}
}
pub fn initial_town() -> Self {
Self {
sustenance: TOWN_FOOD_TARGET,
starvation_ticks: 0.0,
}
}
pub fn get_town_descriptor(&self) -> String {
let percent = self.sustenance as f32 / TOWN_FOOD_TARGET as f32;
if percent > 1.0 {
String::from("The townsfolk have never looked healthier. They are bustling and joyous, brimming with energy. You'll have one of whatever they've been having!")
} else if percent > 0.75 {
String::from("The atmosphere is generally lively and upbeat, but as you glance around you notice a few worried faces.")
} else if percent > 0.55 {
String::from("You find yourself unable to strike up a conversation with any of the local townsfolk. There's a certain desperation about the people you see, but you can't quite place why. In the background, you do not hear the usual clinking of crockery and cups.")
} else if percent > 0.15 {
String::from("The tavern is looking quite empty, with only a few elderly or especially scruffy patrons within sight. None have a beer or food in front of them; instead they count the rings in the wooden tables.")
} else if percent > 0.001 {
String::from("Every wretched soul whose path you have crossed to reach the tavern has had the same hungry look in their eyes. The atmosphere is tense, as if at any moment the whole town could erupt into chaos. You decide to make your stay short.")
} else {
String::from("You look around tavern and see only the emaciated corpses of the former townsfolk. Checking one pile of corpses, you notice some covered in several bite marks. Are those the marks of human teeth? You dare not think too much about it.")
}
}
}
pub struct StarvationMarker;
pub const PLAYER_FOOD_TARGET: usize = 25;
pub const TOWN_FOOD_TARGET: usize = 75;
pub fn process_player_hunger_ticks(
world_ticks: EventReader<WorldTickEvent>,
mut query: Query<(&mut TradingState, &mut HungerState), (With<Player>, Without<TownName>)>,
) {
if !world_ticks.is_empty() {
log::info!("Processing player hunger");
process_hunger_state(PLAYER_FOOD_TARGET, 1, query.iter_mut());
}
}
pub fn process_towns_hunger_ticks(
world_ticks: EventReader<WorldTickEvent>,
mut query: Query<(&mut TradingState, &mut HungerState), (With<TownName>, Without<Player>)>,
) {
if !world_ticks.is_empty() {
log::info!("Processing town hunger");
process_hunger_state(TOWN_FOOD_TARGET, 5, query.iter_mut());
}
}
/// Run system logic for processing hunger state. We extract this out to a separate function
/// so that we can parallelise player & town hunger processing as they are identical but have
/// different target values. The systems have disjoint query sets (second tuple in the Query params),
/// so will run in parallel despite mutably accessing the same components
fn process_hunger_state<'a>(
target: usize,
mut entries: impl Iterator<Item = (Mut<'a, TradingState>, Mut<'a, HungerState>)>,
) {
for (mut trading_state, mut hunger_state) in &mut entries {
// Tick down our sustenance every time we check our food state
hunger_state.sustenance = hunger_state.sustenance.saturating_sub(decay);
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
// We don't need to consume if we somehow exceed our target
if hunger_state.sustenance >= target {
continue;
}
// The amount of sustenance that we need to get from food in the inventory
let mut deficit = target - hunger_state.sustenance;
// A list of what items we have that satisfy the following conditions:
// - Has a food_value of at least 1 (We aren't eating spare wheels over here)
// - Has a food value _less_ than the deficit we're trying to fill. We won't eat foods
// that we can't take full advantage of
let mut foodstuffs = trading_state
.items
.iter()
.filter_map(|(name, amount)| {
get_goods_from_name_checked(name)
.filter(|good| good.food_value > 0 && good.food_value <= deficit)
.map(|good| (name, good, amount))
})
.collect::<Vec<(&String, TradeGood, &usize)>>();
// Sort by highest food value. We eat the most nourishing foods first
foodstuffs.sort_by(|(_, tga, _), (_, tgb, _)| tga.food_value.cmp(&tgb.food_value));
// Track what we're going to consume
let mut to_consume = Vec::with_capacity(foodstuffs.len() / 4);
'consumption_loop: for (name, trade_good, available_amount) in &foodstuffs {
let mut available = **available_amount;
let mut consume_amount = 0;
'deficit_loop: while deficit > 0 && available > 0 {
if trade_good.food_value > deficit {
break 'deficit_loop;
}
available -= 1;
consume_amount += 1;
deficit -= trade_good.food_value;
}
if consume_amount > 0 {
to_consume.push(((*name).clone(), consume_amount));
}
// If we've satisfied our deficit, or we no longer have foods we can consume to take
// full effect of their food_value (no wastage)
if deficit == 0 || trade_good.food_value > deficit {
break 'consumption_loop;
}
}
// Actually remove items we're consuming for food
for (item_name, amount) in to_consume {
trading_state.remove_items(item_name, amount);
}
// Restore hunger to max, less any deficit we haven't satisfied
hunger_state.sustenance = target - deficit;
}
}