/*
Hurry Curry! - a game about cooking
Copyright (C) 2025 Hurry Curry! Contributors
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 hurrycurry_client_lib::{gamedata_index::GamedataIndex, Involvement, Item};
use hurrycurry_protocol::{Gamedata, ItemLocation, PacketC, PlayerID, Recipe, Score, TileIndex};
use log::info;
use std::collections::VecDeque;
#[allow(clippy::too_many_arguments)]
pub fn interact(
data: &Gamedata,
edge: bool,
tile: Option,
player: Option,
this: &mut Option- ,
this_loc: ItemLocation,
other: &mut Option
- ,
other_loc: ItemLocation,
score: &mut Score,
score_changed: &mut bool,
automated: bool,
packet_out: &mut VecDeque,
) {
let _ = automated; //? what was this used for??
if other.is_none() {
if let Some(item) = this {
if let Some(active) = &mut item.active {
let recipe = &data.recipe(active.recipe);
if recipe.supports_tile(tile) {
if let Recipe::Active { outputs, speed, .. } = recipe {
if edge {
active.speed += speed;
} else {
active.speed -= speed;
active.speed = active.speed.max(0.); // in case of "release without press" when items cool on active tile
}
if active.position >= 1. {
let this_had_item = this.is_some();
let other_had_item = other.is_some();
*other = outputs[0].map(|kind| Item { kind, active: None });
*this = outputs[1].map(|kind| Item { kind, active: None });
produce(
this_had_item,
other_had_item,
this,
this_loc,
other,
other_loc,
score_changed,
packet_out,
);
} else {
packet_out.push_back(PacketC::SetProgress {
player,
item: this_loc,
position: active.position,
speed: active.speed,
warn: active.warn,
});
}
return;
}
}
}
}
}
if !edge {
return;
}
for (ri, recipe) in data.recipes() {
if !recipe.supports_tile(tile) {
continue;
}
match recipe {
Recipe::Active { input, speed, .. } => {
if other.is_none() {
if let Some(item) = this {
if item.kind == *input && item.active.is_none() {
info!("start active recipe {ri:?}");
item.active = Some(Involvement {
player,
recipe: ri,
speed: *speed,
position: 0.,
warn: false,
});
}
}
}
if this.is_none() {
if let Some(item) = &other {
if item.kind == *input && item.active.is_none() {
let mut item = other.take().unwrap();
info!("start active recipe {ri:?}");
item.active = Some(Involvement {
player,
recipe: ri,
speed: *speed,
position: 0.,
warn: false,
});
*this = Some(item);
score.active_recipes += 1;
packet_out.push_back(PacketC::MoveItem {
from: other_loc,
to: this_loc,
});
packet_out.push_back(PacketC::SetProgress {
player,
item: this_loc,
position: 0.,
speed: *speed,
warn: false,
});
return;
}
}
}
}
Recipe::Instant {
inputs,
outputs,
points: pd,
..
} => {
let on_tile = this.as_ref().map(|i| i.kind);
let in_hand = other.as_ref().map(|i| i.kind);
let ok = inputs[0] == on_tile && inputs[1] == in_hand;
let ok_rev = inputs[1] == on_tile && inputs[0] == in_hand;
if ok || ok_rev {
info!("instant recipe {ri:?} reversed={ok_rev}");
let ok_rev = ok_rev as usize;
let this_had_item = this.is_some();
let other_had_item = other.is_some();
*other = outputs[1 - ok_rev].map(|kind| Item { kind, active: None });
*this = outputs[ok_rev].map(|kind| Item { kind, active: None });
score.points += pd;
score.instant_recipes += 1;
*score_changed = true;
produce(
this_had_item,
other_had_item,
this,
this_loc,
other,
other_loc,
score_changed,
packet_out,
);
return;
}
}
_ => (),
}
}
let can_place = tile.is_none_or(|tile| {
other.as_ref().is_some_and(|other| {
data.tile_placeable_items
.get(&tile)
.is_none_or(|pl| pl.contains(&other.kind))
})
});
if can_place && this.is_none() {
if let Some(item) = other.take() {
*this = Some(item);
packet_out.push_back(PacketC::MoveItem {
from: other_loc,
to: this_loc,
});
return;
}
}
if other.is_none() {
if let Some(item) = this.take() {
*other = Some(item);
packet_out.push_back(PacketC::MoveItem {
from: this_loc,
to: other_loc,
});
}
}
}
pub enum TickEffect {
Progress {
speed: f32,
position: f32,
warn: bool,
},
ClearProgress,
Produce,
}
#[allow(clippy::too_many_arguments)]
pub fn tick_slot(
dt: f32,
data: &Gamedata,
data_index: &GamedataIndex,
tile: Option,
slot: &mut Option
- ,
slot_loc: ItemLocation,
score: &mut Score,
score_changed: &mut bool,
packet_out: &mut VecDeque,
) {
if let Some(item) = slot {
if let Some(a) = &mut item.active {
let r = &data.recipe(a.recipe);
let prev_speed = a.speed;
if r.supports_tile(tile) {
if a.speed <= 0. {
if let Recipe::Passive { speed, .. } = &data.recipe(a.recipe) {
a.speed = *speed;
}
}
} else if let Some(revert_speed) = r.revert_speed() {
a.speed = -revert_speed
} else {
a.speed = 0.;
}
if a.position < 0. {
item.active = None;
packet_out.push_back(PacketC::ClearProgress { item: slot_loc });
return;
}
if a.position >= 1. {
if let Recipe::Passive { output, warn, .. } = &data.recipe(a.recipe) {
*slot = output.map(|kind| Item { kind, active: None });
score.passive_recipes += 1;
*score_changed = true;
packet_out.push_back(PacketC::SetProgress {
player: None,
warn: *warn,
item: slot_loc,
position: 1.,
speed: 0.,
});
packet_out.push_back(PacketC::SetItem {
location: slot_loc,
item: slot.as_ref().map(|i| i.kind),
});
return;
};
}
a.position += dt * a.speed;
a.position = a.position.min(1.);
if a.speed != prev_speed {
packet_out.push_back(PacketC::SetProgress {
player: None,
position: a.position,
speed: a.speed,
warn: a.warn,
item: slot_loc,
});
}
} else if let Some(recipes) = data_index.recipe_passive_by_input.get(&item.kind) {
for &ri in recipes {
let recipe = data.recipe(ri);
if recipe.supports_tile(tile) {
if let Recipe::Passive {
input, warn, speed, ..
} = recipe
{
if *input == item.kind {
item.active = Some(Involvement {
player: None,
recipe: ri,
position: 0.,
warn: *warn,
speed: *speed,
});
packet_out.push_back(PacketC::SetProgress {
player: None,
position: 0.,
speed: *speed,
warn: *warn,
item: slot_loc,
});
return;
}
}
}
}
}
}
}
#[allow(clippy::too_many_arguments)]
fn produce(
this_had_item: bool,
other_had_item: bool,
this: &Option
- ,
this_loc: ItemLocation,
other: &Option
- ,
other_loc: ItemLocation,
score_changed: &mut bool,
packet_out: &mut VecDeque,
) {
info!("produce {this_loc} <~ {other_loc}");
*score_changed = true;
if this_had_item {
packet_out.push_back(PacketC::SetItem {
location: this_loc,
item: None,
});
}
if other_had_item {
packet_out.push_back(PacketC::MoveItem {
from: other_loc,
to: this_loc,
});
packet_out.push_back(PacketC::SetItem {
location: this_loc,
item: None,
});
}
if let Some(i) = &other {
packet_out.push_back(PacketC::SetItem {
location: this_loc,
item: Some(i.kind),
});
packet_out.push_back(PacketC::MoveItem {
from: this_loc,
to: other_loc,
})
}
if let Some(i) = &this {
packet_out.push_back(PacketC::SetItem {
location: this_loc,
item: Some(i.kind),
});
}
}