use super::expand::{ExpandReport, asset_name, type_norm};
use concinnity_core::components::EngineDefaults;
pub(crate) fn inject_menu_defaults(
assets: &mut Vec<serde_json::Value>,
report: &mut ExpandReport,
) -> Result<(), String> {
let toggles = declared_toggles(assets)?;
if toggles.hud {
inject_stat_hud(assets, report)?;
}
if toggles.story_pause_menu {
inject_story_pause_menu(assets, report)?;
}
Ok(())
}
fn declared_toggles(assets: &[serde_json::Value]) -> Result<EngineDefaults, String> {
let mut declared = assets.iter().filter(|v| type_norm(v) == "enginedefaults");
let Some(value) = declared.next() else {
return Ok(EngineDefaults::default());
};
if let Some(second) = declared.next() {
return Err(format!(
"EngineDefaults '{}': the world already declares EngineDefaults '{}'; \
declare at most one",
asset_name(second),
asset_name(value)
));
}
let args = value
.get("args")
.cloned()
.unwrap_or_else(|| serde_json::json!({}));
serde_json::from_value(args).map_err(|e| {
format!(
"EngineDefaults '{}': invalid args: {}",
asset_name(value),
e
)
})
}
fn inject_stat_hud(
assets: &mut Vec<serde_json::Value>,
report: &mut ExpandReport,
) -> Result<(), String> {
let has_menu = assets.iter().any(|v| type_norm(v) == "mainmenu");
let has_hud = assets.iter().any(|v| type_norm(v) == "stathud");
if !has_menu || has_hud {
return Ok(());
}
if name_claimed(assets, report, "hud", "stat_hud", "StatHud")? {
return Ok(());
}
inject(
assets,
report,
"hud",
"stat_hud",
"StatHud",
serde_json::json!({}),
);
Ok(())
}
fn inject_story_pause_menu(
assets: &mut Vec<serde_json::Value>,
report: &mut ExpandReport,
) -> Result<(), String> {
if assets.iter().any(|v| type_norm(v) == "mainmenu") {
return Ok(());
}
let Some(prefix) = assets
.iter()
.find(|v| type_norm(v) == "story")
.map(asset_name)
.filter(|n| !n.is_empty())
else {
return Ok(());
};
let name = format!("{}_pause", prefix);
if name_claimed(assets, report, "story_pause_menu", &name, "MainMenu")? {
return Ok(());
}
let title_screen = format!("{}_title", prefix);
let has_title = assets
.iter()
.any(|v| type_norm(v) == "screen" && asset_name(v) == title_screen);
let mut items = vec![
serde_json::json!({ "label": "Resume", "action": "story:pause" }),
serde_json::json!({ "label": "Save", "action": "story:save" }),
serde_json::json!({ "label": "Load", "action": "story:load" }),
serde_json::json!({ "label": "Settings", "action": "story:settings" }),
];
if has_title {
items.push(serde_json::json!({
"label": "Main Menu",
"action": format!("screen:show:{}", title_screen),
}));
}
items.push(serde_json::json!({ "label": "Quit", "action": "quit" }));
let args = serde_json::json!({
"toggle_key": "",
"dim": [0.0, 0.0, 0.0, 0.6],
"settings_profile": "minimal",
"settings_back_action": "story:settings_back",
"items": items,
});
inject(assets, report, "story_pause_menu", &name, "MainMenu", args);
inject(
assets,
report,
"story_pause_menu",
&format!("{}_key", name),
"KeyBinding",
serde_json::json!({ "key": "Escape", "action": "story:pause" }),
);
patch_story_scaffold(assets, prefix.as_str(), &name);
Ok(())
}
fn patch_story_scaffold(assets: &mut [serde_json::Value], story_name: &str, menu_name: &str) {
let pause_screen = menu_name.to_string();
let settings_screen = format!("{}_settings_video", menu_name);
for v in assets.iter_mut() {
if type_norm(v) != "story" || asset_name(v) != story_name {
continue;
}
let Some(args) = v.get_mut("args").and_then(|a| a.as_object_mut()) else {
return;
};
let Some(scaffold) = args
.entry("scaffold")
.or_insert_with(|| serde_json::json!({}))
.as_object_mut()
else {
return;
};
scaffold.insert("pause".to_string(), serde_json::json!(pause_screen));
scaffold.insert("settings".to_string(), serde_json::json!(settings_screen));
return;
}
}
fn name_claimed(
assets: &mut [serde_json::Value],
report: &mut ExpandReport,
injected_by: &'static str,
name: &str,
asset_type: &str,
) -> Result<bool, String> {
let Some(claim) = assets.iter().find(|v| asset_name(v) == name) else {
return Ok(false);
};
if type_norm(claim) != asset_type.to_lowercase().replace('_', "") {
return Err(format!(
"engine default '{}' ({}) collides with your {} asset of the same name; \
rename that asset or disable the default with an EngineDefaults entry",
name,
asset_type,
claim.get("type").and_then(|t| t.as_str()).unwrap_or("?"),
));
}
report.record_shadowed(name, asset_type, injected_by, serde_json::json!({}));
Ok(true)
}
fn inject(
assets: &mut Vec<serde_json::Value>,
report: &mut ExpandReport,
injected_by: &'static str,
name: &str,
asset_type: &str,
args: serde_json::Value,
) {
assets.push(serde_json::json!({
"name": name,
"type": asset_type,
"args": args.clone(),
}));
report.record(name, asset_type, args, injected_by);
}
#[cfg(test)]
mod tests;