/*
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::{
interaction::Recipe,
protocol::{DemandIndex, ItemIndex, RecipeIndex, TileIndex},
};
use glam::{IVec2, Vec2};
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, sync::RwLock};
#[derive(Debug, Deserialize, Serialize, Clone, Copy, Default)]
#[serde(rename_all = "snake_case")]
pub enum Action {
#[default]
Never,
Passive,
Active,
Instant,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct RecipeDecl {
#[serde(default)]
tile: Option,
#[serde(default)]
inputs: Vec,
#[serde(default)]
outputs: Vec,
#[serde(default)]
action: Action,
#[serde(default)]
warn: bool,
#[serde(default)]
revert_duration: Option,
#[serde(default)]
duration: Option,
}
#[derive(Debug, Clone, Deserialize)]
pub struct InitialMap {
map: Vec,
tiles: HashMap,
items: HashMap,
collider: Vec,
walkable: Vec,
chef_spawn: char,
customer_spawn: char,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DemandDecl {
from: String,
to: String,
duration: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Demand {
pub from: ItemIndex,
pub to: ItemIndex,
pub duration: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Gamedata {
recipes: Vec,
pub demands: Vec,
pub item_names: Vec,
pub tile_names: Vec,
pub tile_collide: Vec,
pub tile_interact: Vec,
#[serde(skip)]
pub initial_map: HashMap)>,
pub chef_spawn: Vec2,
pub customer_spawn: Vec2,
}
pub fn build_gamedata(
recipes_in: Vec,
map_in: InitialMap,
demands_in: Vec,
) -> Gamedata {
let item_names = RwLock::new(Vec::new());
let tile_names = RwLock::new(Vec::new());
let mut recipes = Vec::new();
let mut demands = Vec::new();
for r in recipes_in {
let r2 = r.clone();
let mut inputs = r
.inputs
.into_iter()
.map(|i| ItemIndex(register(&item_names, i)));
let mut outputs = r
.outputs
.into_iter()
.map(|o| ItemIndex(register(&item_names, o)));
let tile = r.tile.map(|t| TileIndex(register(&tile_names, t)));
match r.action {
Action::Never => {}
Action::Passive => recipes.push(Recipe::Passive {
duration: r.duration.expect("duration for passive missing"),
warn: r.warn,
tile,
revert_duration: r.revert_duration,
input: inputs.next().expect("passive recipe without input"),
output: outputs.next(),
}),
Action::Active => recipes.push(Recipe::Active {
duration: r.duration.expect("duration for active missing"),
tile,
input: inputs.next().expect("active recipe without input"),
outputs: [outputs.next(), outputs.next()],
}),
Action::Instant => {
recipes.push(Recipe::Instant {
tile,
inputs: [inputs.next(), inputs.next()],
outputs: [outputs.next(), outputs.next()],
});
}
}
assert_eq!(inputs.next(), None, "{r2:?}");
assert_eq!(outputs.next(), None, "{r2:?}");
}
for d in demands_in {
demands.push(Demand {
from: ItemIndex(register(&item_names, d.from)),
to: ItemIndex(register(&item_names, d.to)),
duration: d.duration,
})
}
let mut chef_spawn = Vec2::new(0., 0.);
let mut customer_spawn = Vec2::new(0., 0.);
let mut initial_map = HashMap::new();
for (y, line) in map_in.map.iter().enumerate() {
for (x, tile) in line.trim().char_indices() {
let pos = IVec2::new(x as i32, y as i32);
if tile == map_in.chef_spawn {
chef_spawn = pos.as_vec2() + Vec2::splat(0.5);
}
if tile == map_in.customer_spawn {
customer_spawn = pos.as_vec2() + Vec2::splat(0.5);
}
let tilename = map_in.tiles[&tile].clone();
let itemname = map_in.items.get(&tile).cloned();
let tile = TileIndex(register(&tile_names, tilename));
let item = itemname.map(|i| ItemIndex(register(&item_names, i)));
initial_map.insert(pos, (tile, item));
}
}
let item_names = item_names.into_inner().unwrap();
let tile_names = tile_names.into_inner().unwrap();
let tile_collide = tile_names
.iter()
.map(|i| !map_in.walkable.contains(i))
.collect();
let tile_interact = tile_names
.iter()
.map(|i| !map_in.collider.contains(i) && !map_in.walkable.contains(i))
.collect();
Gamedata {
demands,
tile_collide,
tile_interact,
recipes,
initial_map,
item_names,
tile_names,
chef_spawn,
customer_spawn,
}
}
fn register(db: &RwLock>, name: String) -> usize {
let mut db = db.write().unwrap();
if let Some(index) = db.iter().position(|e| e == &name) {
index
} else {
let index = db.len();
db.push(name);
index
}
}
impl Gamedata {
pub fn tile_name(&self, index: TileIndex) -> &String {
&self.tile_names[index.0]
}
pub fn is_tile_colliding(&self, index: TileIndex) -> bool {
self.tile_collide[index.0]
}
pub fn is_tile_interactable(&self, index: TileIndex) -> bool {
self.tile_interact[index.0]
}
pub fn item_name(&self, index: ItemIndex) -> &String {
&self.item_names[index.0]
}
pub fn recipe(&self, index: RecipeIndex) -> &Recipe {
&self.recipes[index.0]
}
pub fn demand(&self, index: DemandIndex) -> &Demand {
&self.demands[index.0]
}
pub fn get_tile_by_name(&self, name: &str) -> Option {
self.tile_names
.iter()
.position(|t| t == name)
.map(TileIndex)
}
pub fn get_item_by_name(&self, name: &str) -> Option {
self.item_names
.iter()
.position(|t| t == name)
.map(ItemIndex)
}
pub fn recipes(&self) -> impl Iterator- {
self.recipes
.iter()
.enumerate()
.map(|(i, e)| (RecipeIndex(i), e))
}
}