pub mod movement; mod pathfinding; use crate::{ data::Gamedata, game::Game, protocol::{ItemIndex, Message, PacketC, PacketS, PlayerID}, }; use glam::{IVec2, Vec2}; use log::{debug, error}; use movement::MovementBase; use pathfinding::{find_path, Path}; use rand::thread_rng; use std::{ collections::{HashMap, HashSet}, sync::Arc, time::Duration, }; use tokio::{ sync::{broadcast, RwLock}, time::interval, }; struct DemandState { data: Gamedata, walkable: HashSet, chairs: HashMap, items: HashMap, customers: HashMap, } enum CustomerState { WalkingToChair { path: Path, chair: IVec2 }, Waiting { chair: IVec2, demand: ItemIndex }, Exiting { path: Path }, } struct Customer { movement: MovementBase, state: CustomerState, } pub async fn customer(game: Arc>, mut grx: broadcast::Receiver) { let mut state = DemandState { walkable: Default::default(), chairs: Default::default(), items: Default::default(), customers: Default::default(), data: Gamedata::default(), }; let initial = game.write().await.prime_client(-1); for p in initial { match p { PacketC::Init { data, .. } => { state.data = data; } PacketC::UpdateMap { pos, tile, .. } => { let tilename = &state.data.tile_names[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::ProduceItem { .. } | PacketC::ConsumeItem { .. } => { 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 DemandState, 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 tick(&mut self, packets_out: &mut Vec<(PlayerID, PacketS)>, dt: f32) { if self.customers.is_empty() { let id = -1; 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.data.customer_spawn.as_ivec2(), chair) .expect("no path"); self.customers.insert( id, Customer { movement: MovementBase { position: self.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.data.get_item("tomato").unwrap(); packets_out.push(( id, PacketS::Communicate { message: Some(Message::Item(demand)), }, )); p.state = CustomerState::Waiting { chair: *chair, demand, }; } } CustomerState::Waiting { chair, 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) { Some(pos) } else { None } }); if let Some(pos) = demand_pos { if self.items.get(&pos) == Some(demand) { packets_out.push((id, PacketS::Communicate { message: None })); let path = find_path( &self.walkable, p.movement.position.as_ivec2(), self.data.customer_spawn.as_ivec2(), ) .expect("no path to exit"); 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 }