/* 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 . */ #![feature(isqrt)] pub mod pathfinding; use anyhow::Result; use hurrycurry_client_lib::{network::sync::Network, Game}; use hurrycurry_protocol::{ glam::{IVec2, Vec2}, ItemIndex, Message, PacketC, PacketS, PlayerID, RecipeIndex, }; use log::{info, warn}; use pathfinding::{find_path, Path}; use std::{thread::sleep, time::Duration}; fn main() -> Result<()> { env_logger::init_from_env("LOG"); let mut network = Network::connect("ws://127.0.0.1")?; let mut game = Game::default(); network.queue_out.push_back(PacketS::Join { name: "bot".to_string(), character: 1, }); let mut bots = Vec::new(); loop { let dt = 1. / 50.; network.poll()?; while let Some(packet) = network.queue_in.pop_front() { match &packet { PacketC::Joined { id } => bots.push(Bot::new(*id)), _ => (), } game.apply_packet(packet); } for b in &mut bots { let (dir, boost, interact) = b.tick(&game); if interact.is_some() != b.interacting { b.interacting = interact.is_some(); network.queue_out.push_back(PacketS::Interact { player: b.id, pos: interact, }) } network.queue_out.push_back(PacketS::Movement { player: b.id, dir, boost, pos: None, }); } sleep(Duration::from_secs_f32(dt)); } } pub struct Bot { pub interacting: bool, id: PlayerID, want: Option, take: Option, put: Option, path: Option, } impl Bot { pub fn new(id: PlayerID) -> Self { Self { id, want: None, path: None, take: None, put: None, interacting: false, } } pub fn tick(&mut self, game: &Game) -> (Vec2, bool, Option) { if let Some(player) = game.players.get(&self.id) { let pos = player.movement.position; if let Some(path) = &mut self.path { let dir = path.next_direction(pos); if path.is_done() { self.path = None; } return (dir, false, None); } if let Some(interact) = self.take.take() { return (Vec2::ZERO, false, Some(interact)); } if let Some(item) = &player.item { if Some(item.kind) == self.want { if let Some(interact) = self.put.take() { return (Vec2::ZERO, false, Some(interact)); } } } if let Some(item) = self.want { if let Some((path, target)) = find_item_on_map(game, pos.as_ivec2(), item) { info!("target={target}"); info!("path found"); self.path = Some(path); self.take = Some(target); } else if let Some(recipe) = find_item_as_recipe_output(game, item) { info!("recipe={recipe:?}"); self.want = game.data.recipes[recipe.0].outputs().first().copied(); info!("want={:?}", self.want) } else { warn!("stuck"); } } else { if let Some((item, dest)) = select_demand(game) { info!("want={item:?}"); self.want = Some(item); self.put = Some(dest); } } } (Vec2::ZERO, false, None) } } fn find_item_as_recipe_output(game: &Game, item: ItemIndex) -> Option { game.data .recipes .iter() .enumerate() .find(|(_, r)| r.inputs().contains(&item)) .map(|r| RecipeIndex(r.0)) } fn find_item_on_map(game: &Game, player: IVec2, item: ItemIndex) -> Option<(Path, IVec2)> { game.tiles.iter().find_map(|(pos, tile)| { if let Some(i) = &tile.item { if i.kind == item { for xo in -1..=1 { for yo in -1..=1 { let t = *pos + IVec2::new(xo, yo); if let Some(path) = find_path(&game.walkable, player, t) { return Some((path, *pos)); } } } } } None }) } fn select_demand(game: &Game) -> Option<(ItemIndex, IVec2)> { game.players .iter() .find_map(|(_, pl)| match &pl.communicate_persist { Some(Message::Item(item)) => { let pos = pl.movement.position.as_ivec2(); for xo in -1..=1 { for yo in -1..=1 { let t = pos + IVec2::new(xo, yo); if let Some(tile) = game.tiles.get(&t) { if game.data.tile_interact[tile.kind.0] { return Some((*item, t)); } } } } None } _ => None, }) }