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 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);
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
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
// 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;
}
}