diff options
Diffstat (limited to 'server/src/customer')
| -rw-r--r-- | server/src/customer/mod.rs | 205 | ||||
| -rw-r--r-- | server/src/customer/pathfinding.rs | 22 | 
2 files changed, 74 insertions, 153 deletions
| diff --git a/server/src/customer/mod.rs b/server/src/customer/mod.rs index 7f32b094..e6f999e6 100644 --- a/server/src/customer/mod.rs +++ b/server/src/customer/mod.rs @@ -20,39 +20,27 @@ mod pathfinding;  use crate::{      data::Gamedata, -    game::Game, -    protocol::{DemandIndex, ItemIndex, Message, PacketC, PacketS, PlayerID}, -    state::State, +    game::Tile, +    protocol::{DemandIndex, Message, PacketS, PlayerID},  };  use anyhow::{anyhow, Result};  use fake::{faker, Fake};  use glam::{IVec2, Vec2}; -use log::{debug, error, warn}; +use log::debug;  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 { -    disabled: bool, +pub struct DemandState { +    data: Arc<Gamedata>,      walkable: HashSet<IVec2>,      chairs: HashMap<IVec2, bool>, -    items: HashMap<IVec2, ItemIndex>, -    customers: HashMap<PlayerID, Customer>,      customer_id_counter: PlayerID, -    demand: DemandState, -} - -struct DemandState { -    data: Gamedata, +    customers: HashMap<PlayerID, Customer>,  }  enum CustomerState { @@ -76,119 +64,41 @@ enum CustomerState {      },  } -struct Customer { +pub struct Customer {      movement: MovementBase,      state: CustomerState,  } -pub async fn customer(gstate: Arc<RwLock<State>>, mut grx: broadcast::Receiver<PacketC>) { -    let mut state = CustomerManager { -        disabled: true, -        customer_id_counter: PlayerID(0), -        walkable: Default::default(), -        chairs: Default::default(), -        items: Default::default(), -        customers: Default::default(), -        demand: DemandState { -            data: Gamedata::default(), -        }, -    }; -    let initial = gstate.write().await.game.prime_client(); -    for packet in initial { -        state.packet(packet); -    } - -    let mut interval = interval(Duration::from_millis(40)); -    let mut packets_out = Vec::new(); -    loop { -        tokio::select! { -            packet = grx.recv() => { -                let packet = match packet { -                    Ok(p) => p, -                    Err(e) => { -                        warn!("{e}"); -                        continue; -                    } -                }; -                match packet { -                    PacketC::PutItem { .. } -                    | PacketC::TakeItem { .. } -                    | PacketC::SetTileItem { .. } => { -                        let g = gstate.read().await; -                        update_items(&mut state, &g.game) -                    }, -                    _ => () -                } -                state.packet(packet); -            } -            _ = interval.tick() => { -                if !state.disabled { -                    if let Err(e) = state.tick(&mut packets_out, 0.04) { -                        warn!("error caught: {e}") -                    } -                    for (player,packet) in packets_out.drain(..) { -                        if let Err(e) = gstate.write().await.packet_in(player, packet).await { -                            error!("customer misbehaved: {e}") -                        } -                    } -                } -            } -        } -    } -} - -// TODO very inefficient, please do that incrementally -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 new(data: Arc<Gamedata>, map: &HashMap<IVec2, Tile>) -> Self { +        let chair = data.get_tile_by_name("chair"); +        Self { +            walkable: map +                .iter() +                .filter(|(_, v)| !data.is_tile_colliding(v.kind)) +                .map(|(e, _)| *e) +                .collect(), +            chairs: map +                .iter() +                .filter(|(_, v)| Some(v.kind) == chair) +                .map(|(e, _)| (*e, true)) +                .collect(), +            customer_id_counter: PlayerID(0), +            customers: Default::default(), +            data,          }      }  }  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::<usize>() % self.data.demands.len()) -    } -} - -impl CustomerManager { -    pub fn packet(&mut self, packet: PacketC) { -        match packet { -            PacketC::Data { data } => { -                self.disabled = data.demands.is_empty(); -                self.demand.data = data; -            } -            PacketC::RemovePlayer { id } => { -                self.customers.remove(&id); -            } -            PacketC::UpdateMap { -                tile: pos, kind, .. -            } => { -                if let Some(kind) = kind { -                    let tilename = self.demand.data.tile_name(kind); -                    if !self.demand.data.is_tile_colliding(kind) { -                        self.walkable.insert(pos); -                    } -                    if tilename == "chair" { -                        self.chairs.insert(pos, true); -                    } -                } else { -                    self.chairs.remove(&pos); -                    self.walkable.remove(&pos); -                } -            } -            _ => (), -        } -    } -    pub fn tick(&mut self, packets_out: &mut Vec<(PlayerID, PacketS)>, dt: f32) -> Result<()> { -        if self.customers.len() < self.demand.target_customer_count() { +    pub fn tick( +        &mut self, +        packets_out: &mut Vec<(PlayerID, PacketS)>, +        tiles: &mut HashMap<IVec2, Tile>, +        data: &Gamedata, +        dt: f32, +    ) -> Result<()> { +        if self.customers.len() < 5 {              self.customer_id_counter.0 -= 1;              let id = self.customer_id_counter;              packets_out.push(( @@ -198,14 +108,15 @@ impl CustomerManager {                      character: -2,                  },              )); -            let chair = select_chair(&mut self.chairs).ok_or(anyhow!("no free chair found"))?; -            let to = self.demand.data.customer_spawn.as_ivec2(); -            let path = find_path(&self.walkable, to, chair).ok_or(anyhow!("no path to {to}"))?; +            let chair = self.select_chair().ok_or(anyhow!("no free chair found"))?; +            let from = data.customer_spawn.as_ivec2(); +            let path = find_path(&self.walkable, from, chair) +                .ok_or(anyhow!("no path from {from} to {chair}"))?;              self.customers.insert(                  id,                  Customer {                      movement: MovementBase { -                        position: self.demand.data.customer_spawn, +                        position: data.customer_spawn,                          facing: Vec2::X,                          vel: Vec2::ZERO,                      }, @@ -220,11 +131,11 @@ impl CustomerManager {                      debug!("{id:?} entering");                      packets_out.push((id, path.execute_tick(&mut p.movement, &self.walkable, dt)));                      if path.is_done() { -                        let demand = self.demand.generate_demand(); +                        let demand = DemandIndex(random::<usize>() % self.data.demands.len());                          packets_out.push((                              id,                              PacketS::Communicate { -                                message: Some(Message::Item(self.demand.data.demand(demand).from)), +                                message: Some(Message::Item(data.demand(demand).from)),                              },                          ));                          p.state = CustomerState::Waiting { @@ -246,18 +157,27 @@ impl CustomerManager {                          let path = find_path(                              &self.walkable,                              p.movement.position.as_ivec2(), -                            self.demand.data.customer_spawn.as_ivec2(), +                            data.customer_spawn.as_ivec2(),                          )                          .expect("no path to exit");                          *self.chairs.get_mut(&chair).unwrap() = true;                          p.state = CustomerState::Exiting { path }                      } else { -                        let demand_data = &self.demand.data.demand(*demand); +                        let demand_data = &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_data.from) { +                                if 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 @@ -284,7 +204,7 @@ impl CustomerManager {                      chair,                  } => {                      debug!("{id:?} eating"); -                    let demand = self.demand.data.demand(*demand); +                    let demand = data.demand(*demand);                      *progress += dt / demand.duration;                      if *progress >= 1. {                          packets_out.push(( @@ -299,7 +219,7 @@ impl CustomerManager {                          let path = find_path(                              &self.walkable,                              p.movement.position.as_ivec2(), -                            self.demand.data.customer_spawn.as_ivec2(), +                            data.customer_spawn.as_ivec2(),                          )                          .ok_or(anyhow!("no path to exit"))?;                          *self.chairs.get_mut(&chair).unwrap() = true; @@ -321,14 +241,15 @@ impl CustomerManager {          }          Ok(())      } -} -pub fn select_chair(chairs: &mut HashMap<IVec2, bool>) -> Option<IVec2> { -    use rand::seq::IteratorRandom; -    let (chosen, free) = chairs -        .iter_mut() -        .filter(|(_p, free)| **free) -        .choose(&mut thread_rng())?; -    *free = false; -    Some(*chosen) +    pub 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) +    }  } diff --git a/server/src/customer/pathfinding.rs b/server/src/customer/pathfinding.rs index 5354407f..5056975e 100644 --- a/server/src/customer/pathfinding.rs +++ b/server/src/customer/pathfinding.rs @@ -1,19 +1,19 @@  /*      Undercooked - 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/>. -     +  */  use super::movement::MovementBase;  use crate::protocol::PacketS; @@ -29,22 +29,22 @@ pub struct Path(Vec<Vec2>);  impl Path {      pub fn execute_tick(          &mut self, -        customer: &mut MovementBase, +        player: &mut MovementBase,          walkable: &HashSet<IVec2>,          dt: f32,      ) -> PacketS {          if let Some(next) = self.0.last().copied() {              debug!("next {next}"); -            if next.distance(customer.position) < if self.0.len() == 1 { 0.1 } else { 0.6 } { +            if next.distance(player.position) < if self.0.len() == 1 { 0.1 } else { 0.6 } {                  self.0.pop();              } -            customer.update( +            player.update(                  &walkable, -                (next - customer.position).normalize_or_zero() * 0.5, +                (next - player.position).normalize_or_zero() * 0.5,                  dt,              )          } else { -            customer.update(&walkable, Vec2::ZERO, dt) +            player.update(&walkable, Vec2::ZERO, dt)          }      }      pub fn is_done(&self) -> bool { @@ -52,7 +52,7 @@ impl Path {      }  } -pub fn find_path(map: &HashSet<IVec2>, from: IVec2, to: IVec2) -> Option<Path> { +pub fn find_path(walkable: &HashSet<IVec2>, from: IVec2, to: IVec2) -> Option<Path> {      #[derive(Debug, PartialEq, Eq)]      struct Open(i32, IVec2, IVec2);      impl PartialOrd for Open { @@ -84,7 +84,7 @@ pub fn find_path(map: &HashSet<IVec2>, from: IVec2, to: IVec2) -> Option<Path> {          }          for d in [IVec2::NEG_X, IVec2::NEG_Y, IVec2::X, IVec2::Y] {              let n = p + d; -            if map.contains(&n) { +            if walkable.contains(&n) {                  open.push(Open(-d.distance_squared(to), n, p));              }          } | 
