/* 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_to_neighbour, Path}, BotAlgo, BotInput, }; use hurrycurry_client_lib::Game; use hurrycurry_protocol::{glam::Vec2, Message, PacketS, PlayerClass, PlayerID}; use rand::{random, seq::IndexedRandom, thread_rng}; #[derive(Default)] pub struct Frank { sleep: f32, idle: f32, idle_dir: f32, target: Option, path: Option, } impl BotAlgo for Frank { fn tick(&mut self, me: PlayerID, game: &Game, dt: f32) -> BotInput { let Some(player) = game.players.get(&me) else { return BotInput::default(); }; if self.sleep > 0. { self.sleep -= dt; return BotInput::default(); } if self.idle > 0. { self.idle -= dt; self.idle_dir += self.idle.sin() * dt * (self.idle / 10.).fract() * 10.; return BotInput { direction: Vec2::from_angle(self.idle_dir), ..Default::default() }; } let pos = player.movement.position; if let Some(target) = self.target { if let Some(player) = game.players.get(&target) { let tpos = player.movement.position; if let Some(path) = &mut self.path { let direction = path.next_direction(pos, dt); if path.is_stuck() { self.path = None; } else if path.is_done() { self.path = None; if pos.distance(tpos) < 2. { self.sleep = 8.; self.idle = 15.; return BotInput { extra: vec![PacketS::Communicate { player: me, message: Some(Message::Translation { id: format!("s.bot.frank.line.{}", random::() % 8), params: vec![Message::Text(player.name.clone())], }), timeout: Some(3.), pin: Some(false), }], ..Default::default() }; } } else { return BotInput { direction, boost: false, interact: None, ..Default::default() }; } } else if let Some(path) = find_path_to_neighbour(&game.walkable, pos.as_ivec2(), tpos.as_ivec2()) { self.path = Some(path); } } else { self.target = None; } } else if let Some(target) = find_chef(game, me) { self.target = Some(target); } BotInput::default() } } fn find_chef(game: &Game, me: PlayerID) -> Option { let chefs = game .players .iter() .filter(|(i, p)| p.class == PlayerClass::Chef && **i != me) .map(|(i, _)| *i) .collect::>(); chefs.choose(&mut thread_rng()).copied() }