aboutsummaryrefslogtreecommitdiff
path: root/server/src/customer
diff options
context:
space:
mode:
Diffstat (limited to 'server/src/customer')
-rw-r--r--server/src/customer/mod.rs290
-rw-r--r--server/src/customer/pathfinding.rs109
2 files changed, 0 insertions, 399 deletions
diff --git a/server/src/customer/mod.rs b/server/src/customer/mod.rs
deleted file mode 100644
index bf385927..00000000
--- a/server/src/customer/mod.rs
+++ /dev/null
@@ -1,290 +0,0 @@
-/*
- 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 <https://www.gnu.org/licenses/>.
-
-*/
-mod pathfinding;
-
-use crate::{data::Gamedata, game::Tile};
-use anyhow::{anyhow, Result};
-use fake::{faker, Fake};
-use hurrycurry_protocol::{
- glam::IVec2, movement::MovementBase, DemandIndex, Message, PacketS, PlayerID,
-};
-use log::info;
-use pathfinding::{find_path, Path};
-use rand::{random, thread_rng};
-use std::{
- collections::{HashMap, HashSet},
- sync::Arc,
-};
-
-pub struct DemandState {
- data: Arc<Gamedata>,
- walkable: HashSet<IVec2>,
- chairs: HashMap<IVec2, bool>,
- customer_id_counter: PlayerID,
- customers: HashMap<PlayerID, Customer>,
- spawn_cooldown: f32,
-
- pub completed: usize,
- pub failed: usize,
- pub score_changed: bool,
-}
-
-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,
- },
-}
-
-pub struct Customer {
- movement: MovementBase,
- state: CustomerState,
-}
-
-impl DemandState {
- pub fn new(data: Arc<Gamedata>, map: &HashMap<IVec2, Tile>) -> Self {
- let chair = data.get_tile_by_name("chair");
- Self {
- score_changed: true,
- completed: 0,
- failed: 0,
- 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,
- spawn_cooldown: 0.,
- }
- }
-}
-
-impl DemandState {
- pub fn tick(
- &mut self,
- packets_out: &mut Vec<(PlayerID, PacketS)>,
- tiles: &mut HashMap<IVec2, Tile>,
- data: &Gamedata,
- dt: f32,
- points: &mut i64,
- ) -> 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::<f32>() * 10.;
- self.customer_id_counter.0 -= 1;
- let id = self.customer_id_counter;
- packets_out.push((
- id,
- PacketS::Join {
- name: faker::name::fr_fr::Name().fake(),
- character: -1 - (random::<u16>() as i32),
- },
- ));
- 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}"))?;
- info!("{id:?} -> entering");
- self.customers.insert(
- id,
- Customer {
- movement: MovementBase::new(data.customer_spawn),
- state: CustomerState::Entering { path, chair },
- },
- );
- }
- let mut customers_to_remove = Vec::new();
- for (&id, p) in &mut self.customers {
- match &mut p.state {
- CustomerState::Entering { path, chair } => {
- packets_out.push((id, path.execute_tick(&mut p.movement, &self.walkable, dt)));
- if path.is_done() {
- let demand = DemandIndex(random::<usize>() % self.data.demands.len());
- packets_out.push((
- id,
- PacketS::Communicate {
- message: Some(Message::Item(data.demand(demand).from)),
- persist: true,
- },
- ));
- info!("{id:?} -> waiting");
- p.state = CustomerState::Waiting {
- chair: *chair,
- timeout: 90. + random::<f32>() * 60.,
- demand,
- };
- }
- }
- CustomerState::Waiting {
- chair,
- demand,
- timeout,
- } => {
- *timeout -= dt;
- if *timeout <= 0. {
- packets_out.push((
- id,
- PacketS::Communicate {
- message: None,
- persist: true,
- },
- ));
- packets_out.push((
- id,
- PacketS::Communicate {
- message: Some(Message::Effect("angry".to_string())),
- persist: false,
- },
- ));
- let path = find_path(
- &self.walkable,
- p.movement.position.as_ivec2(),
- data.customer_spawn.as_ivec2(),
- )
- .expect("no path to exit");
- *self.chairs.get_mut(&chair).unwrap() = true;
- self.failed += 1;
- *points -= 1;
- self.score_changed = true;
- info!("{id:?} -> exiting");
- p.state = CustomerState::Exiting { path }
- } else {
- 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 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 {
- packets_out.push((
- id,
- PacketS::Communicate {
- persist: true,
- message: None,
- },
- ));
- packets_out.push((
- id,
- PacketS::Communicate {
- message: Some(Message::Effect("satisfied".to_string())),
- persist: false,
- },
- ));
- packets_out.push((id, PacketS::Interact { pos: Some(pos) }));
- packets_out.push((id, PacketS::Interact { pos: None }));
- info!("{id:?} -> eating");
- p.state = CustomerState::Eating {
- demand: *demand,
- target: pos,
- progress: 0.,
- chair: *chair,
- }
- }
- }
- }
- CustomerState::Eating {
- demand,
- target,
- progress,
- chair,
- } => {
- let demand = data.demand(*demand);
- *progress += dt / demand.duration;
- if *progress >= 1. {
- packets_out.push((id, PacketS::ReplaceHand { item: demand.to }));
- if demand.to.is_some() {
- packets_out.push((id, PacketS::Interact { pos: Some(*target) }));
- packets_out.push((id, PacketS::Interact { pos: None }));
- }
- let path = find_path(
- &self.walkable,
- p.movement.position.as_ivec2(),
- data.customer_spawn.as_ivec2(),
- )
- .ok_or(anyhow!("no path to exit"))?;
- *self.chairs.get_mut(&chair).unwrap() = true;
- self.completed += 1;
- *points += demand.points;
- self.score_changed = true;
- info!("{id:?} -> exiting");
- p.state = CustomerState::Exiting { path }
- }
- }
- CustomerState::Exiting { path } => {
- packets_out.push((id, path.execute_tick(&mut p.movement, &self.walkable, dt)));
- if path.is_done() {
- info!("{id:?} -> leave");
- packets_out.push((id, PacketS::Leave));
- customers_to_remove.push(id);
- }
- }
- }
- }
- for c in customers_to_remove {
- self.customers.remove(&c).unwrap();
- }
- Ok(())
- }
-
- 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
deleted file mode 100644
index d1e1e997..00000000
--- a/server/src/customer/pathfinding.rs
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- 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 <https://www.gnu.org/licenses/>.
-
-*/
-use hurrycurry_protocol::{
- glam::{IVec2, Vec2}, movement::MovementBase, PacketS
-};
-use log::trace;
-use std::{
- cmp::Ordering,
- collections::{BinaryHeap, HashMap, HashSet},
-};
-
-pub struct Path(Vec<Vec2>);
-
-impl Path {
- pub fn execute_tick(
- &mut self,
- player: &mut MovementBase,
- walkable: &HashSet<IVec2>,
- dt: f32,
- ) -> PacketS {
- if let Some(next) = self.0.last().copied() {
- trace!("next {next}");
- if next.distance(player.position) < if self.0.len() == 1 { 0.1 } else { 0.6 } {
- self.0.pop();
- }
- player.update(
- &walkable,
- (next - player.position).normalize_or_zero() * 0.5,
- false,
- dt,
- )
- } else {
- player.update(&walkable, Vec2::ZERO, false, dt)
- }
- }
- pub fn is_done(&self) -> bool {
- self.0.is_empty()
- }
-}
-
-pub fn find_path(walkable: &HashSet<IVec2>, from: IVec2, to: IVec2) -> Option<Path> {
- #[derive(Debug, PartialEq, Eq)]
- struct Open(i32, IVec2, IVec2, i32);
- impl PartialOrd for Open {
- fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
- 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, 0));
-
- loop {
- let Some(Open(_, pos, f, distance)) = open.pop() else {
- return None;
- };
- if visited.contains_key(&pos) {
- continue;
- }
- visited.insert(pos, f);
- if pos == to {
- break;
- }
- for dir in [IVec2::NEG_X, IVec2::NEG_Y, IVec2::X, IVec2::Y] {
- let next = pos + dir;
- if walkable.contains(&next) {
- open.push(Open(
- -(distance + next.distance_squared(to).isqrt()),
- next,
- pos,
- distance + 1,
- ));
- }
- }
- }
-
- 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(path))
-}