/*
Undercooked - 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::{
data::Gamedata,
game::{Involvement, Item},
protocol::{ItemIndex, TileIndex},
};
use log::info;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Recipe {
Passive {
duration: f32,
revert_duration: Option,
tile: Option,
input: ItemIndex,
output: Option,
warn: bool,
},
Active {
duration: f32,
tile: Option,
input: ItemIndex,
outputs: [Option; 2],
},
Instant {
tile: Option,
inputs: [Option; 2],
outputs: [Option; 2],
points: i64,
},
}
impl Recipe {
pub fn tile(&self) -> Option {
match self {
Recipe::Passive { tile, .. } => *tile,
Recipe::Active { tile, .. } => *tile,
Recipe::Instant { tile, .. } => *tile,
}
}
pub fn duration(&self) -> Option {
match self {
Recipe::Passive { duration, .. } => Some(*duration),
Recipe::Active { duration, .. } => Some(*duration),
_ => None,
}
}
pub fn revert_duration(&self) -> Option {
match self {
Recipe::Passive {
revert_duration, ..
} => *revert_duration,
_ => None,
}
}
pub fn warn(&self) -> bool {
match self {
Recipe::Passive { warn, .. } => *warn,
_ => false,
}
}
pub fn inputs(&self) -> Vec {
match self {
Recipe::Passive { input, .. } => vec![*input],
Recipe::Active { input, .. } => vec![*input],
Recipe::Instant { inputs, .. } => {
inputs.into_iter().flat_map(|e| e.to_owned()).collect()
}
}
}
pub fn outputs(&self) -> Vec {
match self {
Recipe::Passive { output, .. } => output.to_owned().into_iter().collect(),
Recipe::Active { outputs, .. } => {
outputs.into_iter().flat_map(|e| e.to_owned()).collect()
}
Recipe::Instant { outputs, .. } => {
outputs.into_iter().flat_map(|e| e.to_owned()).collect()
}
}
}
pub fn supports_tile(&self, tile: Option) -> bool {
if let Some(tile_constraint) = self.tile() {
if let Some(tile) = tile {
tile == tile_constraint
} else {
false
}
} else {
true
}
}
}
pub enum InteractEffect {
Put,
Take,
Produce,
}
pub fn interact(
data: &Gamedata,
edge: bool,
tile: Option,
this: &mut Option- ,
other: &mut Option
- ,
points: &mut i64,
) -> Option {
let interactable = tile
.map(|tile| data.is_tile_interactable(tile))
.unwrap_or(true);
if interactable && 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, .. } = recipe {
if edge {
active.working += 1;
} else {
active.working -= 1;
if active.progress >= 1. {
*other = outputs[0].map(|kind| Item { kind, active: None });
*this = outputs[1].map(|kind| Item { kind, active: None });
return Some(InteractEffect::Produce);
}
}
}
}
}
}
}
if !edge {
return None;
}
if interactable {
for (ri, recipe) in data.recipes() {
if !recipe.supports_tile(tile) {
continue;
}
match recipe {
Recipe::Active { input, .. } => {
if other.is_none() {
if let Some(item) = this {
if item.kind == *input {
if item.active.is_none() {
info!("start active recipe {ri:?}");
item.active = Some(Involvement {
recipe: ri,
working: 1,
progress: 0.,
});
}
}
}
}
if this.is_none() {
if let Some(item) = &other {
if item.kind == *input {
let mut item = other.take().unwrap();
if let Some(active) = &mut item.active {
active.working += 1;
} else {
info!("start active recipe {ri:?}");
item.active = Some(Involvement {
recipe: ri,
working: 1,
progress: 0.,
});
}
*this = Some(item);
return Some(InteractEffect::Put);
}
}
}
}
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;
*other = outputs[1 - ok_rev].map(|kind| Item { kind, active: None });
*this = outputs[ok_rev].map(|kind| Item { kind, active: None });
*points += pd;
return Some(InteractEffect::Produce);
}
}
_ => (),
}
}
}
if interactable && this.is_none() {
if let Some(item) = other.take() {
*this = Some(item);
return Some(InteractEffect::Put);
}
}
if other.is_none() {
if let Some(item) = this.take() {
*other = Some(item);
return Some(InteractEffect::Take);
}
}
None
}
pub enum TickEffect {
Progress(bool),
Produce,
}
pub fn tick_slot(
dt: f32,
data: &Gamedata,
tile: Option,
slot: &mut Option
- ,
) -> Option {
if let Some(item) = slot {
if let Some(a) = &mut item.active {
let r = &data.recipe(a.recipe);
if r.supports_tile(tile) {
a.progress += a.working as f32 * dt / r.duration().unwrap();
} else if let Some(revert_duration) = r.revert_duration() {
a.progress -= dt / revert_duration;
}
if a.progress >= 1. {
if let Recipe::Passive { output, .. } = &data.recipe(a.recipe) {
*slot = output.map(|kind| Item { kind, active: None });
return Some(TickEffect::Produce);
};
a.progress = 1.;
}
if a.progress < 0. {
item.active = None;
}
return Some(TickEffect::Progress(r.warn()));
} else {
for (ri, recipe) in data.recipes() {
if recipe.supports_tile(tile) {
if let Recipe::Passive { input, .. } = recipe {
if *input == item.kind {
item.active = Some(Involvement {
recipe: ri,
progress: 0.,
working: 1,
});
return Some(TickEffect::Progress(recipe.warn()));
}
}
}
}
}
}
None
}