154 lines
4.5 KiB
Rust
154 lines
4.5 KiB
Rust
//! Resource catalog WASM bridge — exposes per-tile resource metadata
|
|
//! including the 3-axis visibility schema landed in p2-54.
|
|
//!
|
|
//! The catalog is baked at compile time from `public/resources/resources.json`
|
|
//! and parsed once via `OnceLock` on first access.
|
|
|
|
use mc_core::grid::GridState;
|
|
use serde::{Deserialize, Serialize};
|
|
use std::collections::HashMap;
|
|
use std::sync::OnceLock;
|
|
|
|
#[derive(Debug, Clone, Deserialize)]
|
|
struct CatalogEntry {
|
|
id: String,
|
|
name: String,
|
|
#[serde(default)]
|
|
visibility: mc_core::resources::Visibility,
|
|
#[serde(default)]
|
|
yield_gate: Option<String>,
|
|
#[serde(default)]
|
|
indicator_decorations: Vec<IndicatorDecorationRaw>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Deserialize)]
|
|
struct IndicatorDecorationRaw {
|
|
decoration_id: String,
|
|
#[allow(dead_code)]
|
|
name: String,
|
|
#[allow(dead_code)]
|
|
description: String,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
struct ResourcesJson {
|
|
#[serde(default)]
|
|
bonus: Vec<CatalogEntry>,
|
|
#[serde(default)]
|
|
luxury: Vec<CatalogEntry>,
|
|
#[serde(default)]
|
|
strategic: Vec<CatalogEntry>,
|
|
}
|
|
|
|
static CATALOG: OnceLock<HashMap<String, CatalogEntry>> = OnceLock::new();
|
|
|
|
fn catalog() -> &'static HashMap<String, CatalogEntry> {
|
|
CATALOG.get_or_init(|| {
|
|
const JSON: &str = include_str!("../../../../public/resources/resources.json");
|
|
let bundle: ResourcesJson = serde_json::from_str(JSON).unwrap_or(ResourcesJson {
|
|
bonus: vec![],
|
|
luxury: vec![],
|
|
strategic: vec![],
|
|
});
|
|
bundle
|
|
.bonus
|
|
.into_iter()
|
|
.chain(bundle.luxury)
|
|
.chain(bundle.strategic)
|
|
.map(|e| (e.id.clone(), e))
|
|
.collect()
|
|
})
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
pub struct TileResourceDto {
|
|
pub id: String,
|
|
pub name: String,
|
|
pub visibility: String,
|
|
pub yield_gate: Option<String>,
|
|
pub indicator_decorations: Vec<String>,
|
|
}
|
|
|
|
pub fn tile_resource_json_impl(grid: &GridState, col: i32, row: i32) -> Option<String> {
|
|
let tile = grid.tile(col, row)?;
|
|
if tile.resource_id.is_empty() {
|
|
return None;
|
|
}
|
|
let entry = catalog().get(&tile.resource_id)?;
|
|
let dto = TileResourceDto {
|
|
id: entry.id.clone(),
|
|
name: entry.name.clone(),
|
|
visibility: visibility_str(&entry.visibility).to_owned(),
|
|
yield_gate: entry.yield_gate.clone(),
|
|
indicator_decorations: entry
|
|
.indicator_decorations
|
|
.iter()
|
|
.map(|d| d.decoration_id.clone())
|
|
.collect(),
|
|
};
|
|
serde_json::to_string(&dto).ok()
|
|
}
|
|
|
|
fn visibility_str(v: &mc_core::resources::Visibility) -> &'static str {
|
|
match v {
|
|
mc_core::resources::Visibility::Always => "always",
|
|
mc_core::resources::Visibility::Scout => "scout",
|
|
mc_core::resources::Visibility::TechGated => "tech_gated",
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use mc_core::grid::GridState;
|
|
|
|
fn make_grid_with_resource(resource_id: &str) -> GridState {
|
|
let mut g = GridState::new(2, 2);
|
|
g.tiles[0].resource_id = resource_id.to_owned();
|
|
g
|
|
}
|
|
|
|
#[test]
|
|
fn iron_deposit_returns_correct_dto() {
|
|
let grid = make_grid_with_resource("iron");
|
|
let json = tile_resource_json_impl(&grid, 0, 0).expect("should return Some for iron");
|
|
let val: serde_json::Value = serde_json::from_str(&json).unwrap();
|
|
assert_eq!(val["id"], "iron");
|
|
assert_eq!(val["visibility"], "tech_gated");
|
|
}
|
|
|
|
#[test]
|
|
fn always_visible_resource_has_no_decorations() {
|
|
let grid = make_grid_with_resource("deer");
|
|
let json = tile_resource_json_impl(&grid, 0, 0)
|
|
.expect("should return Some for deer");
|
|
let val: serde_json::Value = serde_json::from_str(&json).unwrap();
|
|
assert_eq!(val["visibility"], "always");
|
|
assert_eq!(val["indicator_decorations"].as_array().unwrap().len(), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn empty_resource_id_returns_none() {
|
|
let grid = make_grid_with_resource("");
|
|
assert!(tile_resource_json_impl(&grid, 0, 0).is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn out_of_bounds_returns_none() {
|
|
let grid = GridState::new(2, 2);
|
|
assert!(tile_resource_json_impl(&grid, 99, 99).is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn unknown_resource_id_returns_none() {
|
|
let grid = make_grid_with_resource("dragon_bones");
|
|
assert!(tile_resource_json_impl(&grid, 0, 0).is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn catalog_loads_known_entries() {
|
|
let cat = catalog();
|
|
assert!(cat.contains_key("iron"));
|
|
assert!(cat.contains_key("furs"));
|
|
}
|
|
}
|