use crate::{ data::Gamedata, game::{Involvement, Item, Player, Tile}, protocol::{ItemIndex, TileIndex}, }; use log::info; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize)] pub enum Recipe { Passive { duration: f32, 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], }, } 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 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: TileIndex) -> bool { if let Some(tile_constraint) = self.tile() { if tile != tile_constraint { false } else { true } } else { true } } } pub enum InteractEffect { Put, Take, Produce, } pub fn interact( data: &Gamedata, edge: bool, tile: &mut Tile, player: &mut Player, ) -> Option { let interactable = data.is_tile_interactable(tile.kind); if interactable && player.item.is_none() { if let Some(item) = &mut tile.item { if let Some(active) = &mut item.active { let recipe = &data.recipe(active.recipe); if recipe.supports_tile(tile.kind) { if let Recipe::Active { outputs, .. } = recipe { if edge { active.working += 1; } else { active.working -= 1; if active.progress >= 1. { player.item = outputs[0].map(|kind| Item { kind, active: None }); tile.item = 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.kind) { continue; } match recipe { Recipe::Active { input, .. } => { if player.item.is_none() { if let Some(item) = &mut tile.item { 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 tile.item.is_none() { if let Some(item) = &player.item { if item.kind == *input { let mut item = player.item.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., }); } tile.item = Some(item); return Some(InteractEffect::Put); } } } } Recipe::Instant { inputs, outputs, .. } => { let on_tile = tile.item.as_ref().map(|i| i.kind); let in_hand = player.item.as_ref().map(|i| i.kind); let ok = (inputs[0] == on_tile && inputs[1] == in_hand) || (inputs[1] == on_tile && inputs[0] == in_hand); if ok { info!("instant recipe {ri:?}"); player.item = outputs[0].map(|kind| Item { kind, active: None }); tile.item = outputs[1].map(|kind| Item { kind, active: None }); return Some(InteractEffect::Produce); } } _ => (), } } } if interactable && tile.item.is_none() { if let Some(item) = player.item.take() { tile.item = Some(item); return Some(InteractEffect::Put); } } if player.item.is_none() { if let Some(item) = tile.item.take() { player.item = Some(item); return Some(InteractEffect::Take); } } None } pub enum TickEffect { Progress(bool), Produce, } pub fn tick_tile(dt: f32, data: &Gamedata, tile: &mut Tile) -> Option { if let Some(item) = &mut tile.item { if let Some(a) = &mut item.active { let r = &data.recipe(a.recipe); if r.supports_tile(tile.kind) { a.progress += a.working as f32 * dt / r.duration().unwrap(); if a.progress >= 1. { if let Recipe::Passive { output, .. } = &data.recipe(a.recipe) { tile.item = output.map(|kind| Item { kind, active: None }); return Some(TickEffect::Produce); }; a.progress = 1.; } return Some(TickEffect::Progress(r.warn())); } } else { for (ri, recipe) in data.recipes() { if let Some(tile_constraint) = recipe.tile() { if tile.kind != tile_constraint { continue; } } 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 }