aboutsummaryrefslogtreecommitdiff
path: root/server/src/customer/mod.rs
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2024-06-19 23:21:45 +0200
committermetamuffin <metamuffin@disroot.org>2024-06-23 19:21:49 +0200
commit7be7848b65bd16139569389961541fcc52c905d2 (patch)
treeb2b5a26f9e98d82bc4b54fdf7f76d9dfd583aa0f /server/src/customer/mod.rs
parent6ca76cc0568f3d60b280f11ae07a34303c317f34 (diff)
downloadhurrycurry-7be7848b65bd16139569389961541fcc52c905d2.tar
hurrycurry-7be7848b65bd16139569389961541fcc52c905d2.tar.bz2
hurrycurry-7be7848b65bd16139569389961541fcc52c905d2.tar.zst
split up customer code. customer can enter, order and leave but not take meal
Diffstat (limited to 'server/src/customer/mod.rs')
-rw-r--r--server/src/customer/mod.rs201
1 files changed, 201 insertions, 0 deletions
diff --git a/server/src/customer/mod.rs b/server/src/customer/mod.rs
new file mode 100644
index 00000000..ab3accdd
--- /dev/null
+++ b/server/src/customer/mod.rs
@@ -0,0 +1,201 @@
+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<IVec2>,
+ chairs: HashMap<IVec2, bool>,
+ items: HashMap<IVec2, ItemIndex>,
+ customers: HashMap<PlayerID, Customer>,
+}
+
+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<RwLock<Game>>, mut grx: broadcast::Receiver<PacketC>) {
+ 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, bool>) -> IVec2 {
+ use rand::seq::IteratorRandom;
+ let (chosen, free) = chairs
+ .iter_mut()
+ .filter(|(_p, free)| **free)
+ .choose(&mut thread_rng())
+ .unwrap();
+ *free = false;
+ *chosen
+}