use crate::{ pathfinding::{find_path_to_neighbour, Path}, BotAlgo, BotInput, }; use hurrycurry_client_lib::Game; use hurrycurry_protocol::{ glam::IVec2, ItemIndex, Message, PlayerID, Recipe, RecipeIndex, TileIndex, }; use log::warn; #[derive(Default)] pub struct Simple { path: Option<(Path, IVec2, f32)>, cooldown: f32, } struct SimpleContext<'a> { game: &'a Game, me: PlayerID, own_position: IVec2, state: &'a mut Simple, } type LogicRes = Result; impl BotAlgo for Simple { fn tick(&mut self, me: PlayerID, game: &Game, dt: f32) -> BotInput { let Some(player) = game.players.get(&me) else { return BotInput::default(); }; let pos = player.movement.position; if self.cooldown > 0. { self.cooldown -= dt; return BotInput::default(); } if let Some((path, target, down)) = &mut self.path { let direction = path.next_direction(pos); let arrived = path.is_done(); let target = *target; if arrived { *down -= dt; if *down < 0. { self.path = None; self.cooldown = 0.2; } } return BotInput { direction, boost: false, interact: if arrived { Some(target) } else { None }, }; } SimpleContext { game, own_position: pos.as_ivec2(), me, state: self, } .update() .ok(); BotInput::default() } } impl SimpleContext<'_> { pub fn is_hand_item(&self, item: ItemIndex) -> bool { self.game .players .get(&self.me) .map_or(false, |p| p.item.as_ref().map_or(false, |i| i.kind == item)) } fn find_demand(&self) -> Option<(ItemIndex, IVec2)> { self.game .players .iter() .find_map(|(_, pl)| match &pl.communicate_persist { Some(Message::Item(item)) => { let pos = pl.movement.position.as_ivec2(); [IVec2::X, IVec2::Y, -IVec2::X, -IVec2::Y] .into_iter() .find(|off| { self.game .tiles .get(&(pos + *off)) .map_or(false, |t| self.game.data.tile_interact[t.kind.0]) }) .map(|off| pos + off) .map(|pos| (*item, pos)) } _ => None, }) } fn find_recipe_with_output(&self, item: ItemIndex) -> Option { self.game .data .recipes .iter() .enumerate() .find(|(_, r)| r.outputs().contains(&item)) .map(|(i, _)| RecipeIndex(i)) } fn find_item_on_map(&self, item: ItemIndex) -> Option { self.game .tiles .iter() .find(|(_, t)| t.item.as_ref().map_or(false, |t| t.kind == item)) .map(|(p, _)| *p) } fn find_tile(&self, tile: TileIndex) -> Option { self.game .tiles .iter() .find(|(_, t)| t.kind == tile) .map(|(p, _)| *p) } fn find_empty_interactable_tile(&self) -> Option { self.game .tiles .iter() .find(|(_, t)| self.game.data.tile_interact[t.kind.0] && t.item.is_none()) .map(|(p, _)| *p) } pub fn aquire_placed_item(&mut self, item: ItemIndex) -> LogicRes { if let Some(pos) = self.find_item_on_map(item) { return Ok(pos); } self.aquire_item(item)?; if let Some(pos) = self.find_empty_interactable_tile() { if let Err(()) = self.interact_with(pos, 0.) { return Ok(pos); } else { warn!("no path to empty space "); Err(()) } } else { warn!("no empty space left"); Err(()) } } pub fn aquire_item(&mut self, item: ItemIndex) -> LogicRes { if self.is_hand_item(item) { return Ok(()); } if let Some(pos) = self.find_item_on_map(item) { self.interact_with(pos, 0.)?; return Ok(()); } if let Some(recipe) = self.find_recipe_with_output(item) { let r = &self.game.data.recipes[recipe.0]; match r { Recipe::Instant { tile: Some(tile), inputs: [None, None], outputs: [Some(_), None], .. } => { if let Some(pos) = self.find_tile(*tile) { self.interact_with(pos, 0.)?; } } Recipe::Instant { tile: None, inputs: [Some(a), Some(b)], .. } => { let apos = self.aquire_placed_item(*a)?; self.aquire_item(*b)?; self.interact_with(apos, 0.)?; } Recipe::Active { tile: Some(tile), input, duration, .. } => { self.aquire_item(*input)?; if let Some(pos) = self.find_tile(*tile) { self.interact_with(pos, duration + 0.5)?; } } Recipe::Passive { tile: Some(tile), input, .. } => { } _ => warn!("recipe too hard {r:?}"), } } warn!( "stuck at making item {:?}", self.game.data.item_names[item.0] ); Err(()) } pub fn interact_with(&mut self, tile: IVec2, duration: f32) -> LogicRes { if let Some(path) = find_path_to_neighbour(&self.game.walkable, self.own_position, tile) { self.state.path = Some((path, tile, duration)); Err(()) } else { Ok(()) } } pub fn update(&mut self) -> LogicRes { if let Some((item, table)) = self.find_demand() { self.aquire_item(item)?; self.interact_with(table, 0.)?; } Ok(()) } }