/* 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 . */ pub mod demands; mod pathfinding; use super::{EntityContext, EntityT}; use crate::{data::Demand, server::Server}; use anyhow::{anyhow, bail, Result}; use fake::{faker, Fake}; use hurrycurry_protocol::{glam::IVec2, DemandIndex, Message, PacketC, 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, cpackets: VecDeque, chairs: HashMap, customers: HashMap, 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, demands: Vec) -> Result { if demands.is_empty() { bail!("one or more demands required for customers entity") } Ok(Self { chairs, customers: Default::default(), demands, spawn_cooldown: 0., cpackets: VecDeque::new(), }) } } impl EntityT for Customers { fn tick(&mut self, c: EntityContext) -> 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::() * 10.; // let id = game.join_player( // faker::name::fr_fr::Name().fake(), // -1 - (random::() as i32), // packet_out, // ); // 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 (&player, state) in &mut self.customers { // let Some(playerdata) = game.players.get_mut(&player) else { // continue; // }; // match state { // CustomerState::Entering { path, chair } => { // playerdata // .movement // .input(path.next_direction(playerdata.position()), false); // if path.is_done() { // let demand = DemandIndex(random::() as usize % self.demands.len()); // self.cpackets.push_back(PacketS::Communicate { // message: Some(Message::Item(self.demands[demand.0].from)), // persist: true, // player, // }); // info!("{player:?} -> waiting"); // *state = CustomerState::Waiting { // chair: *chair, // timeout: 90. + random::() * 60., // demand, // }; // } // } // CustomerState::Waiting { // chair, // demand, // timeout, // } => { // playerdata // .movement // .input((chair.as_vec2() + 0.5) - playerdata.position(), false); // *timeout -= dt; // if *timeout <= 0. { // self.cpackets.push_back(PacketS::Communicate { // message: None, // persist: true, // player, // }); // self.cpackets.push_back(PacketS::Communicate { // message: Some(Message::Effect("angry".to_string())), // persist: false, // player, // }); // let path = find_path( // &game.walkable, // playerdata.position().as_ivec2(), // game.data.customer_spawn.as_ivec2(), // ) // .expect("no path to exit"); // *self.chairs.get_mut(chair).unwrap() = true; // game.score.demands_failed += 1; // game.score.points -= 1; // game.score_changed = true; // info!("{player:?} -> 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(PacketS::Communicate { // persist: true, // message: None, // player, // }); // self.cpackets.push_back(PacketS::Communicate { // message: Some(Message::Effect("satisfied".to_string())), // persist: false, // player, // }); // self.cpackets.push_back(PacketS::Interact { // pos: Some(pos), // player, // }); // self.cpackets // .push_back(PacketS::Interact { pos: None, player }); // info!("{player:?} -> eating"); // *state = CustomerState::Eating { // demand: *demand, // target: pos, // progress: 0., // chair: *chair, // } // } // } // } // CustomerState::Eating { // demand, // target, // progress, // chair, // } => { // playerdata // .movement // .input((chair.as_vec2() + 0.5) - playerdata.position(), false); // let demand = &self.demands[demand.0]; // *progress += dt / demand.duration; // if *progress >= 1. { // self.cpackets.push_back(PacketS::ReplaceHand { // player, // item: demand.to, // }); // if demand.to.is_some() { // self.cpackets.push_back(PacketS::Interact { // player, // pos: Some(*target), // }); // self.cpackets // .push_back(PacketS::Interact { player, pos: None }); // } // let path = find_path( // &game.walkable, // playerdata.position().as_ivec2(), // game.data.customer_spawn.as_ivec2(), // ) // .ok_or(anyhow!("no path to exit"))?; // *self.chairs.get_mut(chair).unwrap() = true; // game.score.demands_completed += 1; // game.score.points += demand.points; // game.score_changed = true; // info!("{player:?} -> exiting"); // *state = CustomerState::Exiting { path } // } // } // CustomerState::Exiting { path } => { // playerdata // .movement // .input(path.next_direction(playerdata.position()), false); // if path.is_done() { // info!("{player:?} -> leave"); // self.cpackets.push_back(PacketS::Leave { player }); // customers_to_remove.push(player); // } // } // } // } // for c in customers_to_remove { // self.customers.remove(&c).unwrap(); // } // for packet in self.cpackets.drain(..) { // if let Err(err) = game.packet_in(packet, &mut vec![], packet_out) { // warn!("demand packet {err}"); // } // } Ok(()) } } impl Customers { fn select_chair(&mut self) -> Option { use rand::seq::IteratorRandom; let (chosen, free) = self .chairs .iter_mut() .filter(|(_p, free)| **free) .choose(&mut thread_rng())?; *free = false; Some(*chosen) } }