/*
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, Score};
use log::info;
use rand::{random, seq::IndexedRandom, thread_rng};
#[derive(Debug, Clone)]
pub enum Customer {
New,
Entering {
path: Path,
chair: IVec2,
origin: IVec2,
},
Waiting {
demand: DemandIndex,
chair: IVec2,
timeout: f32,
origin: IVec2,
},
Eating {
demand: DemandIndex,
target: IVec2,
progress: f32,
chair: IVec2,
origin: 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 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,
origin: pos.as_ivec2(),
};
}
}
BotInput::default()
}
Customer::Entering {
path,
chair,
origin,
} => {
if path.is_done() {
let demand = DemandIndex(random::() as usize % game.data.demands.len());
info!("{me:?} -> waiting");
let timeout = 90. + random::() * 60.;
*self = Customer::Waiting {
chair: *chair,
timeout,
demand,
origin: *origin,
};
BotInput {
extra: vec![PacketS::Communicate {
message: Some(Message::Item(game.data.demands[demand.0].input)),
timeout: Some(timeout),
player: me,
}],
..Default::default()
}
} else if path.is_stuck() {
if let Some(path) = find_path(&game.walkable, pos.as_ivec2(), *origin) {
*self = Customer::Exiting { path };
}
BotInput::default()
} else {
BotInput {
direction: path.next_direction(pos, dt) * 0.5,
..Default::default()
}
}
}
Customer::Waiting {
chair,
demand,
timeout,
origin,
} => {
*timeout -= dt;
if *timeout <= 0. {
let path = find_path(&game.walkable, pos.as_ivec2(), *origin)
.expect("no path to exit");
info!("{me:?} -> exiting");
*self = Customer::Exiting { path };
BotInput {
extra: vec![
PacketS::ApplyScore(Score {
points: -1,
demands_failed: 1,
..Default::default()
}),
PacketS::Communicate {
message: Some(Message::Effect("angry".to_string())),
timeout: None,
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,
origin: *origin,
};
BotInput {
extra: vec![
PacketS::Communicate {
message: Some(Message::Effect("satisfied".to_string())),
timeout: None,
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,
origin,
} => {
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(), *origin) {
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,
});
packets.push(PacketS::ApplyScore(Score {
demands_completed: 1,
points: demand.points,
..Default::default()
}));
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() || path.is_stuck() {
info!("{me:?} -> leave");
BotInput {
leave: true,
..Default::default()
}
} else {
BotInput {
direction: path.next_direction(pos, dt) * 0.5,
..Default::default()
}
}
}
}
}
}