use crate::{ data::Gamedata, game::Game, protocol::{PacketC, PacketS, PlayerID}, }; use glam::{IVec2, Vec2}; use log::{debug, error}; use rand::thread_rng; use std::{ cmp::Ordering, collections::{BinaryHeap, HashMap, HashSet}, sync::Arc, time::Duration, }; use tokio::{ sync::{broadcast, RwLock}, time::interval, }; struct DemandState { data: Gamedata, walkable: HashSet, chairs: HashMap, customers: Vec, } enum CustomerState { WalkingToChair { path: Vec, chair: IVec2 }, Waiting { chair: IVec2 }, } struct Customer { id: PlayerID, position: Vec2, facing: Vec2, vel: Vec2, state: CustomerState, } pub async fn customer(game: Arc>, mut grx: broadcast::Receiver) { let mut state = DemandState { walkable: Default::default(), chairs: 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() { // TODO handle map update _ => () } } _ = 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}") } } } } } } impl DemandState { pub fn tick(&mut self, packets_out: &mut Vec<(PlayerID, PacketS)>, dt: f32) { if self.customers.is_empty() { packets_out.push(( -1, 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.push(Customer { id: -1, position: self.data.customer_spawn, facing: Vec2::X, vel: Vec2::ZERO, state: CustomerState::WalkingToChair { path, chair }, }); } for p in &mut self.customers { match &mut p.state { CustomerState::WalkingToChair { path, chair } => { if let Some(next) = path.last().copied() { debug!("next {next}"); if next.distance(p.position) < if path.len() == 1 { 0.1 } else { 0.6 } { path.pop(); } packets_out .push((p.id, move_player(p, &self.walkable, next - p.position, dt))); } else { p.state = CustomerState::Waiting { chair: *chair }; } } CustomerState::Waiting { chair } => { debug!("waiting") } } } } } 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 } pub fn find_path(map: &HashSet, from: IVec2, to: IVec2) -> Option> { #[derive(Debug, PartialEq, Eq)] struct Open(i32, IVec2, IVec2); impl PartialOrd for Open { fn partial_cmp(&self, other: &Self) -> Option { self.0.partial_cmp(&other.0) } } impl Ord for Open { fn cmp(&self, other: &Self) -> Ordering { self.0.cmp(&other.0) } } let mut visited = HashMap::new(); let mut open = BinaryHeap::new(); open.push(Open(1, from, from)); loop { let Some(Open(_, p, f)) = open.pop() else { eprintln!("{visited:?}"); return None; }; if visited.contains_key(&p) { continue; } visited.insert(p, f); if p == to { break; } for d in [IVec2::NEG_X, IVec2::NEG_Y, IVec2::X, IVec2::Y] { let n = p + d; if map.contains(&n) { open.push(Open(-d.distance_squared(to), n, p)); } } } let mut path = Vec::new(); let mut c = to; loop { path.push(c.as_vec2() + 0.5); let cn = visited[&c]; if cn == c { break; } c = cn } Some(path) } fn move_player(p: &mut Customer, map: &HashSet, direction: Vec2, dt: f32) -> PacketS { let direction = direction.normalize_or_zero(); if direction.length() > 0.1 { p.facing = direction + (p.facing - direction) * (-dt * 10.).exp(); } let rot = p.facing.x.atan2(p.facing.y); p.vel += direction * dt * 0.5; p.position += p.vel; p.vel = p.vel * (-dt * 5.).exp(); collide_player(p, map); PacketS::Position { pos: p.position, rot, } } const PLAYER_SIZE: f32 = 0.4; fn collide_player(p: &mut Customer, map: &HashSet) { for xo in -1..=1 { for yo in -1..=1 { let tile = IVec2::new(xo, yo) + p.position.as_ivec2(); if map.contains(&tile) { continue; } let tile = tile.as_vec2(); let d = aabb_circle_distance(tile, tile + Vec2::ONE, p.position); if d > PLAYER_SIZE { continue; } let h = 0.01; let d_sample_x = aabb_circle_distance(tile, tile + Vec2::ONE, p.position + Vec2::new(h, 0.)); let d_sample_y = aabb_circle_distance(tile, tile + Vec2::ONE, p.position + Vec2::new(0., h)); let grad = (Vec2::new(d_sample_x, d_sample_y) - d) / h; p.position += (PLAYER_SIZE - d) * grad; p.vel -= grad * grad.dot(p.vel); } } } fn aabb_circle_distance(min: Vec2, max: Vec2, p: Vec2) -> f32 { (p - p.clamp(min, max)).length() }