diff options
author | metamuffin <metamuffin@disroot.org> | 2024-07-18 15:42:11 +0200 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2024-07-18 15:42:11 +0200 |
commit | 5f883c80e7fc63c97910d003c44aea814ab8a5d6 (patch) | |
tree | b73a8c8f78db103671128e686136f08aa276923a /server/src/entity/customers/mod.rs | |
parent | efc29c03f7be043ae8d037a93efce8cfa7c384cc (diff) | |
download | hurrycurry-5f883c80e7fc63c97910d003c44aea814ab8a5d6.tar hurrycurry-5f883c80e7fc63c97910d003c44aea814ab8a5d6.tar.bz2 hurrycurry-5f883c80e7fc63c97910d003c44aea814ab8a5d6.tar.zst |
reimplement customers as entity
Diffstat (limited to 'server/src/entity/customers/mod.rs')
-rw-r--r-- | server/src/entity/customers/mod.rs | 274 |
1 files changed, 274 insertions, 0 deletions
diff --git a/server/src/entity/customers/mod.rs b/server/src/entity/customers/mod.rs new file mode 100644 index 00000000..7f0b0c22 --- /dev/null +++ b/server/src/entity/customers/mod.rs @@ -0,0 +1,274 @@ +/* + Hurry Curry! - a game about cooking + Copyright 2024 metamuffin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, version 3 of the License only. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + +*/ +pub mod demands; +mod pathfinding; + +use super::EntityT; +use crate::{data::Demand, game::Game}; +use anyhow::{anyhow, Result}; +use fake::{faker, Fake}; +use hurrycurry_protocol::{glam::IVec2, DemandIndex, Message, PacketS, PlayerID}; +use log::{info, warn}; +use pathfinding::{find_path, Path}; +use rand::{random, thread_rng}; +use std::collections::{HashMap, VecDeque}; + +#[derive(Debug, Clone)] +pub struct Customers { + demands: Vec<Demand>, + cpackets: VecDeque<(PlayerID, PacketS)>, + chairs: HashMap<IVec2, bool>, + customer_id_counter: PlayerID, + customers: HashMap<PlayerID, CustomerState>, + spawn_cooldown: f32, +} + +#[derive(Debug, Clone)] +enum CustomerState { + Entering { + path: Path, + chair: IVec2, + }, + Waiting { + demand: DemandIndex, + chair: IVec2, + timeout: f32, + }, + Eating { + demand: DemandIndex, + target: IVec2, + progress: f32, + chair: IVec2, + }, + Exiting { + path: Path, + }, +} + +impl Customers { + pub fn new(chairs: HashMap<IVec2, bool>, demands: Vec<Demand>) -> Self { + Self { + chairs, + customer_id_counter: PlayerID(0), + customers: Default::default(), + demands, + spawn_cooldown: 0., + cpackets: VecDeque::new(), + } + } +} + +impl EntityT for Customers { + fn tick(&mut self, game: &mut Game, dt: f32) -> Result<()> { + self.spawn_cooldown -= dt; + self.spawn_cooldown = self.spawn_cooldown.max(0.); + if self.customers.len() < 5 && self.spawn_cooldown <= 0. { + self.spawn_cooldown = 10. + random::<f32>() * 10.; + self.customer_id_counter.0 -= 1; + let id = self.customer_id_counter; + self.cpackets.push_back(( + id, + PacketS::Join { + name: faker::name::fr_fr::Name().fake(), + character: -1 - (random::<u16>() as i32), + }, + )); + let chair = self.select_chair().ok_or(anyhow!("no free chair found"))?; + let from = game.data.customer_spawn.as_ivec2(); + let path = find_path(&game.walkable, from, chair) + .ok_or(anyhow!("no path from {from} to {chair}"))?; + info!("{id:?} -> entering"); + self.customers + .insert(id, CustomerState::Entering { path, chair }); + } + let mut customers_to_remove = Vec::new(); + for (&id, state) in &mut self.customers { + let Some(player) = game.players.get_mut(&id) else { + continue; + }; + + match state { + CustomerState::Entering { path, chair } => { + player.direction = path.next_direction(player.position()); + if path.is_done() { + let demand = DemandIndex(random::<usize>() % self.demands.len()); + self.cpackets.push_back(( + id, + PacketS::Communicate { + message: Some(Message::Item(self.demands[demand.0].from)), + persist: true, + }, + )); + info!("{id:?} -> waiting"); + *state = CustomerState::Waiting { + chair: *chair, + timeout: 90. + random::<f32>() * 60., + demand, + }; + } + } + CustomerState::Waiting { + chair, + demand, + timeout, + } => { + player.direction *= 0.; + *timeout -= dt; + if *timeout <= 0. { + self.cpackets.push_back(( + id, + PacketS::Communicate { + message: None, + persist: true, + }, + )); + self.cpackets.push_back(( + id, + PacketS::Communicate { + message: Some(Message::Effect("angry".to_string())), + persist: false, + }, + )); + let path = find_path( + &game.walkable, + player.position().as_ivec2(), + game.data.customer_spawn.as_ivec2(), + ) + .expect("no path to exit"); + *self.chairs.get_mut(&chair).unwrap() = true; + game.demands_failed += 1; + game.points -= 1; + game.score_changed = true; + info!("{id:?} -> exiting"); + *state = CustomerState::Exiting { path } + } else { + let demand_data = &self.demands[demand.0]; + let demand_pos = [IVec2::NEG_X, IVec2::NEG_Y, IVec2::X, IVec2::Y] + .into_iter() + .find_map(|off| { + let pos = *chair + off; + if game + .tiles + .get(&pos) + .map(|t| { + t.item + .as_ref() + .map(|i| i.kind == demand_data.from) + .unwrap_or_default() + }) + .unwrap_or_default() + { + Some(pos) + } else { + None + } + }); + if let Some(pos) = demand_pos { + self.cpackets.push_back(( + id, + PacketS::Communicate { + persist: true, + message: None, + }, + )); + self.cpackets.push_back(( + id, + PacketS::Communicate { + message: Some(Message::Effect("satisfied".to_string())), + persist: false, + }, + )); + self.cpackets + .push_back((id, PacketS::Interact { pos: Some(pos) })); + self.cpackets + .push_back((id, PacketS::Interact { pos: None })); + info!("{id:?} -> eating"); + *state = CustomerState::Eating { + demand: *demand, + target: pos, + progress: 0., + chair: *chair, + } + } + } + } + CustomerState::Eating { + demand, + target, + progress, + chair, + } => { + player.direction *= 0.; + let demand = &self.demands[demand.0]; + *progress += dt / demand.duration; + if *progress >= 1. { + self.cpackets + .push_back((id, PacketS::ReplaceHand { item: demand.to })); + if demand.to.is_some() { + self.cpackets + .push_back((id, PacketS::Interact { pos: Some(*target) })); + self.cpackets + .push_back((id, PacketS::Interact { pos: None })); + } + let path = find_path( + &game.walkable, + player.position().as_ivec2(), + game.data.customer_spawn.as_ivec2(), + ) + .ok_or(anyhow!("no path to exit"))?; + *self.chairs.get_mut(&chair).unwrap() = true; + game.demands_completed += 1; + game.points += demand.points; + game.score_changed = true; + info!("{id:?} -> exiting"); + *state = CustomerState::Exiting { path } + } + } + CustomerState::Exiting { path } => { + player.direction = path.next_direction(player.position()); + if path.is_done() { + info!("{id:?} -> leave"); + self.cpackets.push_back((id, PacketS::Leave)); + customers_to_remove.push(id); + } + } + } + } + for c in customers_to_remove { + self.customers.remove(&c).unwrap(); + } + for (player, packet) in self.cpackets.drain(..) { + if let Err(err) = game.packet_in(player, packet) { + warn!("demand packet {err}"); + } + } + Ok(()) + } +} +impl Customers { + fn select_chair(&mut self) -> Option<IVec2> { + use rand::seq::IteratorRandom; + let (chosen, free) = self + .chairs + .iter_mut() + .filter(|(_p, free)| **free) + .choose(&mut thread_rng())?; + *free = false; + Some(*chosen) + } +} |