/* 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::{ 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, automated: bool, ) -> Option { let interactable = automated || 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 }