pub mod movement; mod pathfinding; use crate::{ data::Gamedata, game::Game, protocol::{DemandIndex, ItemIndex, Message, PacketC, PacketS, PlayerID}, }; use glam::{IVec2, Vec2}; use log::{debug, error}; use movement::MovementBase; use pathfinding::{find_path, Path}; use rand::{random, thread_rng}; use std::{ collections::{HashMap, HashSet}, sync::Arc, time::Duration, }; use tokio::{ sync::{broadcast, RwLock}, time::interval, }; struct CustomerManager { walkable: HashSet, chairs: HashMap, items: HashMap, customers: HashMap, customer_id_counter: PlayerID, demand: DemandState, } struct DemandState { data: Gamedata, } enum CustomerState { WalkingToChair { path: Path, chair: IVec2 }, Waiting { chair: IVec2, demand: DemandIndex }, Exiting { path: Path }, } struct Customer { movement: MovementBase, state: CustomerState, } pub async fn customer(game: Arc>, mut grx: broadcast::Receiver) { let mut state = CustomerManager { customer_id_counter: PlayerID(0), walkable: Default::default(), chairs: Default::default(), items: Default::default(), customers: Default::default(), demand: DemandState { data: Gamedata::default(), }, }; let initial = game.write().await.prime_client(PlayerID(-1)); for p in initial { match p { PacketC::Init { data, .. } => { state.demand.data = data; } PacketC::UpdateMap { pos, tile, .. } => { let tilename = state.demand.data.tile_name(tile); if tilename == "floor" || tilename == "door" || tilename == "chair" { state.walkable.insert(pos); } if tilename == "chair" { state.chairs.insert(pos, true); } } _ => (), } } let mut interval = interval(Duration::from_millis(40)); let mut packets_out = Vec::new(); loop { tokio::select! { packet = grx.recv() => { match packet.unwrap() { PacketC::PutItem { .. } | PacketC::TakeItem { .. } | PacketC::SetTileItem { .. } => { let g = game.read().await; update_items(&mut state, &g) }, _ => () } } _ = interval.tick() => { state.tick(&mut packets_out, 0.04); for (player,packet) in packets_out.drain(..) { if let Err(e) = game.write().await.packet_in(player, packet) { error!("customer misbehaved: {e}") } } } } } } fn update_items(state: &mut CustomerManager, game: &Game) { state.items.clear(); for (&pos, tile) in game.tiles() { if let Some(item) = &tile.item { state.items.insert(pos, item.kind); } } } impl DemandState { pub fn target_customer_count(&self) -> usize { // TODO insert sofa magic formula 5 } pub fn generate_demand(&self) -> DemandIndex { // TODO insert sofa magic formula DemandIndex(random::() % self.data.demands.len()) } } impl CustomerManager { pub fn tick(&mut self, packets_out: &mut Vec<(PlayerID, PacketS)>, dt: f32) { if self.customers.len() < self.demand.target_customer_count() { self.customer_id_counter.0 -= 1; let id = self.customer_id_counter; packets_out.push(( id, PacketS::Join { name: "George".to_string(), character: 0, }, )); let chair = select_chair(&mut self.chairs); let path = find_path( &self.walkable, self.demand.data.customer_spawn.as_ivec2(), chair, ) .expect("no path"); self.customers.insert( id, Customer { movement: MovementBase { position: self.demand.data.customer_spawn, facing: Vec2::X, vel: Vec2::ZERO, }, state: CustomerState::WalkingToChair { path, chair }, }, ); } let mut customers_to_remove = Vec::new(); for (&id, p) in &mut self.customers { match &mut p.state { CustomerState::WalkingToChair { path, chair } => { packets_out.push((id, path.execute_tick(&mut p.movement, &self.walkable, dt))); if path.is_done() { let demand = self.demand.generate_demand(); packets_out.push(( id, PacketS::Communicate { message: Some(Message::Item(self.demand.data.demand(demand).from)), }, )); p.state = CustomerState::Waiting { chair: *chair, demand, }; } } CustomerState::Waiting { chair, demand } => { let demand = &self.demand.data.demand(*demand); let demand_pos = [IVec2::NEG_X, IVec2::NEG_Y, IVec2::X, IVec2::Y] .into_iter() .find_map(|off| { let pos = *chair + off; if self.items.get(&pos) == Some(&demand.from) { Some(pos) } else { None } }); if let Some(pos) = demand_pos { packets_out.push((id, PacketS::Communicate { message: None })); for edge in [true, false] { packets_out.push((id, PacketS::Interact { pos, edge })) } packets_out.push(( id, PacketS::ReplaceHand { item: Some(demand.to), }, )); for edge in [true, false] { packets_out.push((id, PacketS::Interact { pos, edge })) } let path = find_path( &self.walkable, p.movement.position.as_ivec2(), self.demand.data.customer_spawn.as_ivec2(), ) .expect("no path to exit"); *self.chairs.get_mut(&chair).unwrap() = true; p.state = CustomerState::Exiting { path } } debug!("waiting") } CustomerState::Exiting { path } => { packets_out.push((id, path.execute_tick(&mut p.movement, &self.walkable, dt))); if path.is_done() { packets_out.push((id, PacketS::Leave)); customers_to_remove.push(id); } } } } for c in customers_to_remove { self.customers.remove(&c).unwrap(); } } } pub fn select_chair(chairs: &mut HashMap) -> IVec2 { use rand::seq::IteratorRandom; let (chosen, free) = chairs .iter_mut() .filter(|(_p, free)| **free) .choose(&mut thread_rng()) .unwrap(); *free = false; *chosen }