aboutsummaryrefslogtreecommitdiff
path: root/server/data/src/recipes.rs
blob: e4d68a7de8a73e4bd496084ad1dd8fce71fcc40d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
/*
    Hurry Curry! - a game about cooking
    Copyright (C) 2026 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 <https://www.gnu.org/licenses/>.

*/

use crate::registry::ItemTileRegistry;
use anyhow::{Result, anyhow};
use hurrycurry_protocol::{Demand, ItemIndex, Recipe};
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, BTreeSet};

#[rustfmt::skip]
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct RecipeDecl {
    tile: Option<String>,
    #[serde(default)] inputs: Vec<String>,
    #[serde(default)] outputs: Vec<String>,
    #[serde(default)] action: RecipeDeclAction,
    #[serde(default)] warn: bool,
    revert_duration: Option<f32>,
    duration: Option<f32>,
    points: Option<i64>,
    group: Option<String>,
    #[serde(default)] group_hidden: bool,
}

#[derive(Debug, Deserialize, Serialize, Clone, Copy, Default)]
#[serde(rename_all = "snake_case")]
pub enum RecipeDeclAction {
    #[default]
    Never,
    Passive,
    Active,
    Instant,
    Demand,
}

#[allow(clippy::type_complexity)]
pub(crate) fn load_recipes(
    recipes_in: Vec<RecipeDecl>,
    reg: &ItemTileRegistry,
) -> Result<(
    Vec<Recipe>,
    Vec<Demand>,
    BTreeMap<String, BTreeSet<ItemIndex>>,
)> {
    let mut recipes = Vec::new();
    let mut demands = Vec::new();
    let mut recipe_groups = BTreeMap::<String, BTreeSet<ItemIndex>>::new();

    for mut r in recipes_in {
        #[cfg(feature = "fast_recipes")]
        match r.action {
            RecipeDeclAction::Passive | RecipeDeclAction::Active => {
                if !r.warn {
                    r.duration = Some(0.5)
                }
            }
            _ => (),
        }

        let r2 = r.clone();
        let mut inputs = r.inputs.into_iter().map(|i| reg.register_item(i));
        let mut outputs = r.outputs.into_iter().map(|o| reg.register_item(o));
        let tile = r.tile.map(|t| reg.register_tile(t));
        if let Some(g) = r.group
            && !r.group_hidden
        {
            recipe_groups.entry(g).or_default().extend(inputs.clone());
        }
        match r.action {
            RecipeDeclAction::Never => {}
            RecipeDeclAction::Passive => recipes.push(Recipe::Passive {
                speed: 1. / r.duration.ok_or(anyhow!("duration for passive missing"))?,
                warn: r.warn,
                tile,
                revert_speed: r.revert_duration.map(|d| 1. / d),
                input: inputs
                    .next()
                    .ok_or(anyhow!("passive recipe without input"))?,
                output: outputs.next(),
            }),
            RecipeDeclAction::Active => recipes.push(Recipe::Active {
                speed: 1. / r.duration.ok_or(anyhow!("duration for active missing"))?,
                tile,
                input: inputs
                    .next()
                    .ok_or(anyhow!("active recipe without input"))?,
                outputs: [outputs.next(), outputs.next()],
            }),
            RecipeDeclAction::Instant => {
                recipes.push(Recipe::Instant {
                    points: r.points.take().unwrap_or(0),
                    tile,
                    inputs: [inputs.next(), inputs.next()],
                    outputs: [outputs.next(), outputs.next()],
                });
            }
            RecipeDeclAction::Demand => demands.push(Demand {
                input: inputs.next().ok_or(anyhow!("demand needs inputs"))?,
                output: outputs.next(),
                duration: r.duration.unwrap_or(10.),
                points: 0, // assigned later when filtering
            }),
        }
        assert_eq!(inputs.next(), None, "{r2:?} inputs left over");
        assert_eq!(outputs.next(), None, "{r2:?} outputs left over");
        assert_eq!(r.points, None, "points specified where not possible")
    }

    Ok((recipes, demands, recipe_groups))
}