/*
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 .
*/
pub mod demands;
mod pathfinding;
use super::{EntityContext, EntityT};
use crate::{data::Demand, server::Server};
use anyhow::{anyhow, bail, Result};
use fake::{faker, Fake};
use hurrycurry_protocol::{glam::IVec2, DemandIndex, Message, PacketC, PacketS, PlayerID};
use log::{info, warn};
use pathfinding::{find_path, Path};
use rand::{random, thread_rng};
use std::collections::{HashMap, VecDeque};
#[derive(Debug, Clone)]
pub struct Customers {
demands: Vec,
cpackets: VecDeque,
chairs: HashMap,
customers: HashMap,
spawn_cooldown: f32,
}
#[derive(Debug, Clone)]
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,
},
}
impl Customers {
pub fn new(chairs: HashMap, demands: Vec) -> Result {
if demands.is_empty() {
bail!("one or more demands required for customers entity")
}
Ok(Self {
chairs,
customers: Default::default(),
demands,
spawn_cooldown: 0.,
cpackets: VecDeque::new(),
})
}
}
impl EntityT for Customers {
fn tick(&mut self, c: EntityContext) -> 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::() * 10.;
// let id = game.join_player(
// faker::name::fr_fr::Name().fake(),
// -1 - (random::() as i32),
// packet_out,
// );
// let chair = self.select_chair().ok_or(anyhow!("no free chair found"))?;
// let from = game.data.customer_spawn.as_ivec2();
// let path = find_path(&game.walkable, from, chair)
// .ok_or(anyhow!("no path from {from} to {chair}"))?;
// info!("{id:?} -> entering");
// self.customers
// .insert(id, CustomerState::Entering { path, chair });
// }
// let mut customers_to_remove = Vec::new();
// for (&player, state) in &mut self.customers {
// let Some(playerdata) = game.players.get_mut(&player) else {
// continue;
// };
// match state {
// CustomerState::Entering { path, chair } => {
// playerdata
// .movement
// .input(path.next_direction(playerdata.position()), false);
// if path.is_done() {
// let demand = DemandIndex(random::() as usize % self.demands.len());
// self.cpackets.push_back(PacketS::Communicate {
// message: Some(Message::Item(self.demands[demand.0].from)),
// persist: true,
// player,
// });
// info!("{player:?} -> waiting");
// *state = CustomerState::Waiting {
// chair: *chair,
// timeout: 90. + random::() * 60.,
// demand,
// };
// }
// }
// CustomerState::Waiting {
// chair,
// demand,
// timeout,
// } => {
// playerdata
// .movement
// .input((chair.as_vec2() + 0.5) - playerdata.position(), false);
// *timeout -= dt;
// if *timeout <= 0. {
// self.cpackets.push_back(PacketS::Communicate {
// message: None,
// persist: true,
// player,
// });
// self.cpackets.push_back(PacketS::Communicate {
// message: Some(Message::Effect("angry".to_string())),
// persist: false,
// player,
// });
// let path = find_path(
// &game.walkable,
// playerdata.position().as_ivec2(),
// game.data.customer_spawn.as_ivec2(),
// )
// .expect("no path to exit");
// *self.chairs.get_mut(chair).unwrap() = true;
// game.score.demands_failed += 1;
// game.score.points -= 1;
// game.score_changed = true;
// info!("{player:?} -> exiting");
// *state = CustomerState::Exiting { path }
// } else {
// let demand_data = &self.demands[demand.0];
// let demand_pos = [IVec2::NEG_X, IVec2::NEG_Y, IVec2::X, IVec2::Y]
// .into_iter()
// .find_map(|off| {
// let pos = *chair + off;
// if game
// .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 {
// self.cpackets.push_back(PacketS::Communicate {
// persist: true,
// message: None,
// player,
// });
// self.cpackets.push_back(PacketS::Communicate {
// message: Some(Message::Effect("satisfied".to_string())),
// persist: false,
// player,
// });
// self.cpackets.push_back(PacketS::Interact {
// pos: Some(pos),
// player,
// });
// self.cpackets
// .push_back(PacketS::Interact { pos: None, player });
// info!("{player:?} -> eating");
// *state = CustomerState::Eating {
// demand: *demand,
// target: pos,
// progress: 0.,
// chair: *chair,
// }
// }
// }
// }
// CustomerState::Eating {
// demand,
// target,
// progress,
// chair,
// } => {
// playerdata
// .movement
// .input((chair.as_vec2() + 0.5) - playerdata.position(), false);
// let demand = &self.demands[demand.0];
// *progress += dt / demand.duration;
// if *progress >= 1. {
// self.cpackets.push_back(PacketS::ReplaceHand {
// player,
// item: demand.to,
// });
// if demand.to.is_some() {
// self.cpackets.push_back(PacketS::Interact {
// player,
// pos: Some(*target),
// });
// self.cpackets
// .push_back(PacketS::Interact { player, pos: None });
// }
// let path = find_path(
// &game.walkable,
// playerdata.position().as_ivec2(),
// game.data.customer_spawn.as_ivec2(),
// )
// .ok_or(anyhow!("no path to exit"))?;
// *self.chairs.get_mut(chair).unwrap() = true;
// game.score.demands_completed += 1;
// game.score.points += demand.points;
// game.score_changed = true;
// info!("{player:?} -> exiting");
// *state = CustomerState::Exiting { path }
// }
// }
// CustomerState::Exiting { path } => {
// playerdata
// .movement
// .input(path.next_direction(playerdata.position()), false);
// if path.is_done() {
// info!("{player:?} -> leave");
// self.cpackets.push_back(PacketS::Leave { player });
// customers_to_remove.push(player);
// }
// }
// }
// }
// for c in customers_to_remove {
// self.customers.remove(&c).unwrap();
// }
// for packet in self.cpackets.drain(..) {
// if let Err(err) = game.packet_in(packet, &mut vec![], packet_out) {
// warn!("demand packet {err}");
// }
// }
Ok(())
}
}
impl Customers {
fn select_chair(&mut self) -> Option {
use rand::seq::IteratorRandom;
let (chosen, free) = self
.chairs
.iter_mut()
.filter(|(_p, free)| **free)
.choose(&mut thread_rng())?;
*free = false;
Some(*chosen)
}
}