aboutsummaryrefslogtreecommitdiff
path: root/server/tools
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2025-10-06 23:03:32 +0200
committermetamuffin <metamuffin@disroot.org>2025-10-06 23:03:40 +0200
commit176e6bc6c4c29bea3be2aceca99743b997c76c97 (patch)
tree1161e7a966843324756340da4b6452492902fa07 /server/tools
parentea86b11b682500160f37b35ea8f06b081cd05036 (diff)
downloadhurrycurry-176e6bc6c4c29bea3be2aceca99743b997c76c97.tar
hurrycurry-176e6bc6c4c29bea3be2aceca99743b997c76c97.tar.bz2
hurrycurry-176e6bc6c4c29bea3be2aceca99743b997c76c97.tar.zst
Move data code to own crate + general data refactor
Diffstat (limited to 'server/tools')
-rw-r--r--server/tools/Cargo.toml1
-rw-r--r--server/tools/src/book.rs123
-rw-r--r--server/tools/src/diagram_layout.rs78
-rw-r--r--server/tools/src/graph.rs4
-rw-r--r--server/tools/src/graph_summary.rs5
-rw-r--r--server/tools/src/main.rs23
-rw-r--r--server/tools/src/map_linter.rs4
-rw-r--r--server/tools/src/recipe_diagram.rs239
8 files changed, 18 insertions, 459 deletions
diff --git a/server/tools/Cargo.toml b/server/tools/Cargo.toml
index db1c5ebf..1c427c81 100644
--- a/server/tools/Cargo.toml
+++ b/server/tools/Cargo.toml
@@ -11,6 +11,7 @@ clap = { version = "4.5.47", features = ["derive"] }
hurrycurry-protocol = { path = "../protocol" }
hurrycurry-server = { path = ".." }
hurrycurry-locale = { path = "../locale" }
+hurrycurry-data = { path = "../data" }
serde_json = "1.0.145"
serde = { version = "1.0.225", features = ["derive"] }
markup = "0.15.0"
diff --git a/server/tools/src/book.rs b/server/tools/src/book.rs
deleted file mode 100644
index bffbe836..00000000
--- a/server/tools/src/book.rs
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- Hurry Curry! - a game about cooking
- Copyright (C) 2025 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::{diagram_layout::diagram_layout, recipe_diagram::recipe_diagram};
-use anyhow::Result;
-use hurrycurry_locale::trm;
-use hurrycurry_protocol::{
- Gamedata, Message,
- book::{Book, BookPage},
-};
-use hurrycurry_server::data::Serverdata;
-
-struct RecipePageParams<'a> {
- name: &'a str,
- repr_items: &'a [&'a str],
-}
-static RECIPE_PAGES: &[RecipePageParams] = &[
- RecipePageParams {
- name: "cheese-leek-soup",
- repr_items: &["plate:cheese-leek-soup"],
- },
- RecipePageParams {
- name: "tomato-soup",
- repr_items: &["plate:tomato-soup"],
- },
- RecipePageParams {
- name: "mushroom-soup",
- repr_items: &["plate:mushroom-soup"],
- },
- RecipePageParams {
- name: "burger",
- repr_items: &[
- "plate:seared-patty,sliced-bun,sliced-lettuce,sliced-tomato",
- "plate:seared-patty,sliced-bun,sliced-cheese,sliced-tomato",
- ],
- },
- RecipePageParams {
- name: "noodles",
- repr_items: &["plate:cooked-noodles,sliced-cheese,tomato-juice"],
- },
- RecipePageParams {
- name: "pizza",
- repr_items: &["plate:baked-rolled-dough:sliced-cheese,sliced-mushroom,tomato-juice"],
- },
- RecipePageParams {
- name: "curry",
- repr_items: &["plate:cooked-rice,curry"],
- },
- RecipePageParams {
- name: "drinks",
- repr_items: &[
- "glass:water",
- "glass:tomato-juice",
- "glass:strawberry-shake",
- ],
- },
- RecipePageParams {
- name: "mochi",
- repr_items: &["plate:strawberry-mochi"],
- },
- RecipePageParams {
- name: "doughnut",
- repr_items: &["plate:doughnut"],
- },
- RecipePageParams {
- name: "doughnut",
- repr_items: &["plate:doughnut"],
- },
-];
-
-pub fn book(data: &Gamedata, serverdata: &Serverdata) -> Result<Book> {
- let mut pages = Vec::new();
-
- pages.push(BookPage::Contents {
- title: trm!("b.toc.title"),
- table: vec![],
- });
- let mut toc = Vec::new();
-
- for &RecipePageParams { name, repr_items } in RECIPE_PAGES {
- let mut diagram = recipe_diagram(data, serverdata, repr_items)?;
- diagram_layout(&mut diagram)?;
- let title = Message::Translation {
- id: format!("b.{name}.title"),
- params: vec![],
- };
- toc.push((title.clone(), pages.len()));
- pages.push(BookPage::Recipe {
- title,
- description: Message::Translation {
- id: format!("b.{name}.desc"),
- params: vec![],
- },
- diagram,
- });
- }
-
- if let BookPage::Contents { table, .. } = &mut pages[0] {
- *table = toc;
- }
- Ok(Book { pages })
-}
-
-pub fn print_book(data: &Gamedata, serverdata: &Serverdata) -> Result<()> {
- let book = book(data, serverdata)?;
- println!("{}", serde_json::to_string_pretty(&book).unwrap());
- Ok(())
-}
diff --git a/server/tools/src/diagram_layout.rs b/server/tools/src/diagram_layout.rs
deleted file mode 100644
index 0ea26a69..00000000
--- a/server/tools/src/diagram_layout.rs
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- Hurry Curry! - a game about cooking
- Copyright (C) 2025 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 anyhow::{Result, anyhow};
-use hurrycurry_protocol::{book::Diagram, glam::Vec2};
-use serde::Deserialize;
-use std::{
- collections::BTreeMap,
- fmt::Write as W2,
- io::Write,
- process::{Command, Stdio},
-};
-
-pub struct Layout {
- pub nodes: BTreeMap<String, Vec2>,
- pub edges: Vec<(usize, usize)>,
-}
-
-pub fn diagram_layout(diagram: &mut Diagram) -> Result<()> {
- let mut child = Command::new("dot")
- .arg("-Tjson")
- .arg("-Kdot")
- .stdin(Stdio::piped())
- .stdout(Stdio::piped())
- .spawn()?;
-
- let mut out = String::new();
- writeln!(out, "digraph {{")?;
- for (i, _) in diagram.nodes.iter().enumerate() {
- writeln!(out, "k{i} [width=1, height=1]")?;
- }
- for edge in &diagram.edges {
- writeln!(out, "k{} -> k{}", edge.src, edge.dst)?;
- }
- writeln!(out, "}}")?;
-
- child.stdin.as_mut().unwrap().write_all(out.as_bytes())?;
- let output = child.wait_with_output()?;
-
- #[derive(Deserialize)]
- struct Out {
- objects: Vec<Object>,
- }
- #[derive(Deserialize)]
- struct Object {
- name: String,
- pos: String,
- }
- let graph: Out = serde_json::from_slice(&output.stdout)?;
- for o in graph.objects {
- let pos = o.pos.split_once(",").ok_or(anyhow!("malformed position"))?;
- let pos = Vec2::new(pos.0.parse()?, pos.1.parse()?);
-
- let index = o
- .name
- .strip_prefix("k")
- .ok_or(anyhow!("invalid node name"))?
- .parse::<usize>()?;
- diagram.nodes[index].position = pos
- }
-
- Ok(())
-}
diff --git a/server/tools/src/graph.rs b/server/tools/src/graph.rs
index 53f70d99..a65ffc97 100644
--- a/server/tools/src/graph.rs
+++ b/server/tools/src/graph.rs
@@ -16,8 +16,8 @@
*/
use anyhow::Result;
+use hurrycurry_data::index::DataIndex;
use hurrycurry_protocol::{Demand, ItemIndex, Recipe, RecipeIndex};
-use hurrycurry_server::data::DataIndex;
pub(crate) fn graph() -> Result<()> {
let mut index = DataIndex::default();
@@ -25,7 +25,7 @@ pub(crate) fn graph() -> Result<()> {
println!("digraph {{");
- let (data, _, _) = index.generate("5star")?;
+ let (data, _) = index.generate("5star")?;
for i in 0..data.item_names.len() {
println!("i{i} [label=\"{}\"]", data.item_name(ItemIndex(i)))
}
diff --git a/server/tools/src/graph_summary.rs b/server/tools/src/graph_summary.rs
index be53e768..bfdcc955 100644
--- a/server/tools/src/graph_summary.rs
+++ b/server/tools/src/graph_summary.rs
@@ -15,9 +15,10 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
+
use anyhow::Result;
+use hurrycurry_data::index::DataIndex;
use hurrycurry_protocol::{ItemIndex, Recipe, TileIndex};
-use hurrycurry_server::data::DataIndex;
use std::collections::HashSet;
pub(crate) fn graph_summary() -> Result<()> {
@@ -26,7 +27,7 @@ pub(crate) fn graph_summary() -> Result<()> {
println!("digraph {{");
- let (data, sdata, _) = index.generate("5star")?;
+ let (data, sdata) = index.generate("5star")?;
struct Node {
inputs: Vec<ItemIndex>,
diff --git a/server/tools/src/main.rs b/server/tools/src/main.rs
index b550dabb..f70c5755 100644
--- a/server/tools/src/main.rs
+++ b/server/tools/src/main.rs
@@ -16,31 +16,28 @@
*/
-pub mod book;
pub mod book_html;
pub mod diagram_dot;
-pub mod diagram_layout;
pub mod diagram_svg;
pub mod graph;
pub mod graph_summary;
pub mod map_linter;
-pub mod recipe_diagram;
use crate::{
- book::{book, print_book},
book_html::render_html_book,
diagram_dot::{diagram_dot, diagram_dot_svg},
- diagram_layout::diagram_layout,
diagram_svg::diagram_svg,
graph::graph,
graph_summary::graph_summary,
map_linter::check_map,
- recipe_diagram::recipe_diagram,
};
use anyhow::Result;
use clap::Parser;
+use hurrycurry_data::{
+ book::{book, diagram_layout::diagram_layout, print_book, recipe_diagram::recipe_diagram},
+ index::DataIndex,
+};
use hurrycurry_locale::FALLBACK_LOCALE;
-use hurrycurry_server::data::DataIndex;
#[derive(Parser)]
enum Action {
@@ -83,7 +80,7 @@ fn main() -> Result<()> {
} => {
let mut index = DataIndex::default();
index.reload()?;
- let (data, serverdata, _) = index.generate("5star")?;
+ let (data, serverdata) = index.generate("5star")?;
let mut diagram = recipe_diagram(&data, &serverdata, &[&out])?;
let out = if dot_out {
diagram_dot(&data, &diagram, false)?
@@ -99,20 +96,20 @@ fn main() -> Result<()> {
Action::Book => {
let mut index = DataIndex::default();
index.reload()?;
- let (data, serverdata, _) = index.generate("5star")?;
+ let (data, serverdata) = index.generate("5star")?;
print_book(&data, &serverdata)?
}
Action::BookHtml => {
let mut index = DataIndex::default();
index.reload()?;
- let (data, serverdata, _) = index.generate("5star")?;
+ let (data, serverdata) = index.generate("5star")?;
let book = book(&data, &serverdata)?;
println!("{}", render_html_book(&data, &book, &FALLBACK_LOCALE));
}
Action::MapDemands { map } => {
let mut index = DataIndex::default();
index.reload()?;
- let (data, _, _) = index.generate(&map)?;
+ let (data, _) = index.generate(&map)?;
for demand in &data.demands {
println!("{}", data.item_name(demand.input))
}
@@ -120,7 +117,7 @@ fn main() -> Result<()> {
Action::MapItems { map } => {
let mut index = DataIndex::default();
index.reload()?;
- let (data, _, _) = index.generate(&map)?;
+ let (data, _) = index.generate(&map)?;
for name in &data.item_names {
println!("{name}")
}
@@ -128,7 +125,7 @@ fn main() -> Result<()> {
Action::MapTiles { map } => {
let mut index = DataIndex::default();
index.reload()?;
- let (data, _, _) = index.generate(&map)?;
+ let (data, _) = index.generate(&map)?;
for name in &data.tile_names {
println!("{name}")
}
diff --git a/server/tools/src/map_linter.rs b/server/tools/src/map_linter.rs
index 738a9e10..678f2930 100644
--- a/server/tools/src/map_linter.rs
+++ b/server/tools/src/map_linter.rs
@@ -17,6 +17,7 @@
*/
use anyhow::Result;
+use hurrycurry_data::{Serverdata, index::DataIndex};
use hurrycurry_locale::{
FALLBACK_LOCALE,
message::{COLORED, MessageDisplayExt},
@@ -26,7 +27,6 @@ use hurrycurry_protocol::{
Gamedata, TileIndex,
glam::{IVec2, ivec2},
};
-use hurrycurry_server::data::{DataIndex, Serverdata};
use std::{
collections::{BTreeSet, HashMap, HashSet},
sync::LazyLock,
@@ -148,7 +148,7 @@ pub fn check_map(map: &str) -> Result<()> {
let locale = &*FALLBACK_LOCALE;
let mut index = DataIndex::default();
index.reload()?;
- let (data, serverdata, _) = index.generate(map)?;
+ let (data, serverdata) = index.generate(map)?;
let mut warnings = Vec::new();
diff --git a/server/tools/src/recipe_diagram.rs b/server/tools/src/recipe_diagram.rs
deleted file mode 100644
index 0be75433..00000000
--- a/server/tools/src/recipe_diagram.rs
+++ /dev/null
@@ -1,239 +0,0 @@
-/*
- Hurry Curry! - a game about cooking
- Copyright (C) 2025 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 anyhow::Result;
-use hurrycurry_protocol::{
- Gamedata, ItemIndex, Message, Recipe, RecipeIndex,
- book::{Diagram, DiagramEdge, DiagramNode, NodeStyle},
- glam::Vec2,
-};
-use hurrycurry_server::data::Serverdata;
-use std::{
- cmp::Reverse,
- collections::{BTreeMap, BTreeSet, HashSet},
-};
-
-pub(crate) fn recipe_diagram(
- data: &Gamedata,
- serverdata: &Serverdata,
- target_items: &[&str],
-) -> Result<Diagram> {
- let ambient_items = serverdata
- .initial_map
- .iter()
- .flat_map(|(_, (_, item))| item)
- .copied()
- .collect::<HashSet<_>>();
-
- let mut need = BTreeSet::from_iter(
- target_items
- .iter()
- .map(|name| data.get_item_by_name(name).unwrap()),
- );
- let mut have = BTreeSet::<ItemIndex>::new();
- let mut recipes = BTreeSet::new();
-
- #[derive(PartialEq, PartialOrd, Eq, Ord)]
- struct GraphRecipe {
- index: RecipeIndex,
- inputs: Vec<ItemIndex>,
- outputs: Vec<ItemIndex>,
- }
-
- while let Some(item) = need.pop_last() {
- for (ri, r) in data.recipes() {
- if r.outputs().contains(&item) {
- let gr = GraphRecipe {
- inputs: r
- .inputs()
- .iter()
- .filter(|i| !ambient_items.contains(i))
- .copied()
- .collect(),
- outputs: r
- .outputs()
- .iter()
- .filter(|i| !ambient_items.contains(i))
- .copied()
- .collect(),
- index: ri,
- };
- need.extend(gr.inputs.iter().filter(|i| !have.contains(&i)));
- have.extend(&gr.outputs);
- recipes.insert(gr);
- }
- }
- }
-
- let mut diag = Diagram::default();
- let mut item_index = BTreeMap::new();
- for i in have {
- item_index.insert(i, diag.nodes.len());
- diag.nodes.push(DiagramNode {
- label: Message::Item(i),
- position: Vec2::ZERO,
- style: if target_items.contains(&data.item_name(i)) {
- NodeStyle::FinalProduct
- } else {
- NodeStyle::IntermediateProduct
- },
- });
- }
- for r in recipes {
- let index = diag.nodes.len();
- let recipe = data.recipe(r.index);
-
- if matches!(recipe, Recipe::Instant { .. }) && r.inputs.len() >= 1 && r.outputs.len() >= 1 {
- for i in r.inputs {
- diag.edges.push(DiagramEdge {
- src: item_index[&i],
- dst: item_index[&r.outputs[0]],
- label: None,
- });
- }
- continue;
- }
- if matches!(recipe, Recipe::Passive { tile: None, .. })
- && r.inputs.len() == 1
- && r.outputs.len() == 1
- {
- diag.nodes[item_index[&r.inputs[0]]].style = NodeStyle::ProcessPassive;
- diag.edges.push(DiagramEdge {
- src: item_index[&r.inputs[0]],
- dst: item_index[&r.outputs[0]],
- label: None,
- });
- continue;
- }
-
- let (kind, style) = match recipe {
- Recipe::Passive { .. } => ("Passive", NodeStyle::ProcessPassive),
- Recipe::Active { .. } => ("Active", NodeStyle::ProcessActive),
- Recipe::Instant { .. } => ("Instant", NodeStyle::ProcessInstant),
- };
- diag.nodes.push(DiagramNode {
- position: Vec2::ZERO,
- label: if let Some(tile) = recipe.tile() {
- Message::Tile(tile)
- } else {
- Message::Text(kind.to_string())
- },
- style,
- });
-
- for i in r.inputs {
- diag.edges.push(DiagramEdge {
- src: item_index[&i],
- dst: index,
- label: None,
- });
- }
- for o in r.outputs {
- diag.edges.push(DiagramEdge {
- src: index,
- dst: item_index[&o],
- label: None,
- });
- }
- }
-
- merge_combine_clusters(&mut diag);
- remove_orphan_nodes(&mut diag);
-
- Ok(diag)
-}
-
-fn merge_combine_clusters(diag: &mut Diagram) {
- let instant_nodes = diag
- .nodes
- .iter()
- .enumerate()
- .filter(|(_, n)| matches!(n.style, NodeStyle::IntermediateProduct))
- .map(|(i, _)| i)
- .collect::<Vec<_>>();
-
- for ni in instant_nodes {
- let inputs = diag
- .edges
- .iter()
- .enumerate()
- .filter(|(_, e)| e.dst == ni)
- .map(|(i, e)| (i, e.src))
- .collect::<Vec<_>>();
- let outputs = diag
- .edges
- .iter()
- .enumerate()
- .filter(|(_, e)| e.src == ni)
- .map(|(i, e)| (i, e.dst))
- .collect::<Vec<_>>();
-
- if outputs
- .iter()
- .all(|&(_, ni)| diag.nodes[ni].style.is_procuct())
- && inputs
- .iter()
- .all(|&(_, ni)| diag.nodes[ni].style.is_procuct())
- {
- let mut to_remove = inputs
- .iter()
- .map(|&(i, _)| i)
- .chain(outputs.iter().map(|&(i, _)| i))
- .collect::<Vec<_>>();
- to_remove.sort_by_key(|x| Reverse(*x));
- for i in to_remove {
- diag.edges.remove(i);
- }
-
- for &input in &inputs {
- for &output in &outputs {
- if !diag
- .edges
- .iter()
- .any(|e| e.src == input.1 && e.dst == output.1)
- {
- diag.edges.push(DiagramEdge {
- src: input.1,
- dst: output.1,
- label: None,
- });
- }
- }
- }
- }
- }
-}
-
-fn remove_orphan_nodes(diag: &mut Diagram) {
- let mut to_remove = diag
- .nodes
- .iter()
- .enumerate()
- .filter(|&(i, _)| !diag.edges.iter().any(|e| e.src == i || e.dst == i))
- .map(|(i, _)| i)
- .collect::<Vec<_>>();
- to_remove.sort_by_key(|x| Reverse(*x));
-
- for e in &mut diag.edges {
- e.src -= to_remove.iter().filter(|&&i| i < e.src).count();
- e.dst -= to_remove.iter().filter(|&&i| i < e.dst).count();
- }
- for i in to_remove {
- diag.nodes.remove(i);
- }
-}