/* 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 . */ use crate::{ pathfinding::{find_path, Path}, BotAlgo, BotInput, }; use hurrycurry_client_lib::Game; use hurrycurry_protocol::{glam::IVec2, DemandIndex, Message, PacketS, PlayerID}; use log::info; use rand::{random, seq::IndexedRandom, thread_rng}; #[derive(Debug, Clone)] pub enum Customer { New, 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 Default for Customer { fn default() -> Self { Customer::New } } impl BotAlgo for Customer { fn tick(&mut self, me: PlayerID, game: &Game, dt: f32) -> BotInput { let _ = (me, game, dt); let Some(playerdata) = game.players.get(&me) else { return BotInput::default(); }; let pos = playerdata.movement.position; match self { Customer::New => { if let Some(&chair) = game .tiles .iter() .filter(|(_, t)| game.data.tile_name(t.kind) == "chair") .map(|(p, _)| *p) .collect::>() .choose(&mut thread_rng()) { if let Some(path) = find_path(&game.walkable, pos.as_ivec2(), chair) { info!("{me:?} -> entering"); *self = Customer::Entering { path, chair }; } } BotInput::default() } Customer::Entering { path, chair } => { if path.is_done() { let demand = DemandIndex(random::() as usize % game.data.demands.len()); info!("{me:?} -> waiting"); *self = Customer::Waiting { chair: *chair, timeout: 90. + random::() * 60., demand, }; BotInput { extra: vec![PacketS::Communicate { message: Some(Message::Item(game.data.demands[demand.0].input)), persist: true, player: me, }], ..Default::default() } } else { BotInput { direction: path.next_direction(pos), ..Default::default() } } } Customer::Waiting { chair, demand, timeout, } => { *timeout -= dt; if *timeout <= 0. { let path = find_path( &game.walkable, pos.as_ivec2(), pos.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!("{me:?} -> exiting"); *self = Customer::Exiting { path }; BotInput { extra: vec![ PacketS::Communicate { message: None, persist: true, player: me, }, PacketS::Communicate { message: Some(Message::Effect("angry".to_string())), persist: false, player: me, }, ], ..Default::default() } } else { let demand_data = &game.data.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.input) .unwrap_or_default() }) .unwrap_or_default() { Some(pos) } else { None } }); if let Some(pos) = demand_pos { info!("{me:?} -> eating"); *self = Customer::Eating { demand: *demand, target: pos, progress: 0., chair: *chair, }; BotInput { extra: vec![ PacketS::Communicate { persist: true, message: None, player: me, }, PacketS::Communicate { message: Some(Message::Effect("satisfied".to_string())), persist: false, player: me, }, PacketS::Interact { pos: Some(pos), player: me, }, PacketS::Interact { pos: None, player: me, }, ], ..Default::default() } } else { BotInput { direction: (chair.as_vec2() + 0.5) - pos, ..Default::default() } } } } Customer::Eating { demand, target, progress, chair, } => { let demand = &game.data.demands[demand.0]; *progress += dt / demand.duration; if *progress >= 1. { if let Some(path) = find_path( &game.walkable, pos.as_ivec2(), pos.as_ivec2(), // game.data.customer_spawn.as_ivec2(), ) { let mut packets = Vec::new(); packets.push(PacketS::ReplaceHand { player: me, item: demand.output, }); if demand.output.is_some() { packets.push(PacketS::Interact { player: me, pos: Some(*target), }); packets.push(PacketS::Interact { player: me, pos: None, }); // *self.chairs.get_mut(chair).unwrap() = true; // game.score.demands_completed += 1; // game.score.points += demand.points; // game.score_changed = true; info!("{me:?} -> exiting"); *self = Customer::Exiting { path }; return BotInput { extra: packets, ..Default::default() }; } } } BotInput { direction: (chair.as_vec2() + 0.5) - pos, ..Default::default() } } Customer::Exiting { path } => { if path.is_done() { info!("{me:?} -> leave"); BotInput { leave: true, ..Default::default() } } else { BotInput { direction: path.next_direction(pos), ..Default::default() } } } } } }