Let a game supply its own ego sprite and death text
Two general-purpose additions, both needed by an external game folder and useful to any other: - World::ego_view() — a game folder with sprites/ego.png uses it as the player avatar instead of the hardcoded party robot (optional ego1.png gives the walk bob a second cel). Refreshed on LoadGame and ReloadSprites. - GameManifest.death_text — the death banner was hardcoded to morp2's "Morp is not cruel; Morp is accurate", which leaked that game's flavour into every other one. Now a manifest field; the default keeps the old wording minus the morp2 reference, so existing games are unaffected. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
23865f1483
commit
0b8fe85ccd
@ -12,7 +12,6 @@ use crate::framebuffer::{CTRL_BLOCK, PIC_H, PIC_W};
|
||||
use crate::parser::{self, Dlg, VerbDef};
|
||||
use crate::render;
|
||||
use crate::sprite::{Prop, ScreenObj};
|
||||
use crate::view::default_robot_view;
|
||||
use crate::world::{find_spawn, ObjDef, RoomDoc, Stroke, World};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
@ -147,7 +146,7 @@ pub struct GameState {
|
||||
|
||||
impl GameState {
|
||||
pub fn new(world: World, ai: ai::AiConfig) -> Self {
|
||||
let mut ego = ScreenObj::new(default_robot_view(), 80, 150);
|
||||
let mut ego = ScreenObj::new(world.ego_view(), 80, 150);
|
||||
ego.min_x = 4;
|
||||
ego.max_x = PIC_W as i32 - 4;
|
||||
ego.min_y = 16;
|
||||
@ -350,12 +349,14 @@ impl GameState {
|
||||
}
|
||||
Command::LoadGame { dir } => {
|
||||
self.world = World::load_game(dir);
|
||||
self.ego.view = self.world.ego_view();
|
||||
self.verbs = parser::build_verbs(&self.world.manifest.verbs);
|
||||
ev.push(Event::GameLoaded { name: self.world.manifest.name.clone(), room: self.world.current });
|
||||
ev.extend(self.new_game(None));
|
||||
}
|
||||
Command::ReloadSprites => {
|
||||
self.world.reload_sprites();
|
||||
self.ego.view = self.world.ego_view();
|
||||
ev.push(self.edited());
|
||||
}
|
||||
}
|
||||
@ -459,7 +460,8 @@ impl GameState {
|
||||
for l in &cause_lines {
|
||||
ev.push(self.line(l));
|
||||
}
|
||||
ev.push(self.line("\u{2020} You have died. Morp is not cruel; Morp is accurate. The day restarts. \u{2020}"));
|
||||
let death = self.world.manifest.death_text.clone();
|
||||
ev.push(self.line(&death));
|
||||
ev
|
||||
}
|
||||
|
||||
|
||||
@ -203,11 +203,18 @@ pub struct GameManifest {
|
||||
/// Total points on offer; 0 = scoring off (no HUD line, no score events).
|
||||
#[serde(default)]
|
||||
pub max_score: u32,
|
||||
/// The banner shown when the player dies and the day restarts.
|
||||
#[serde(default = "default_death")]
|
||||
pub death_text: String,
|
||||
}
|
||||
|
||||
fn default_name() -> String {
|
||||
"untitled".into()
|
||||
}
|
||||
|
||||
fn default_death() -> String {
|
||||
"\u{2020} You have died. The day restarts. \u{2020}".into()
|
||||
}
|
||||
fn default_intro() -> String {
|
||||
"You blink awake. Type 'help'.".into()
|
||||
}
|
||||
@ -221,6 +228,7 @@ impl Default for GameManifest {
|
||||
verbs: Vec::new(),
|
||||
flags: HashMap::new(),
|
||||
max_score: 0,
|
||||
death_text: default_death(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -486,6 +494,26 @@ impl World {
|
||||
self.bake();
|
||||
}
|
||||
|
||||
/// The ego's view. A game folder overrides the default party robot by
|
||||
/// dropping `sprites/ego.png` (feet on the bottom edge, like any prop);
|
||||
/// `ego1.png` is an optional second cel for the walk bob.
|
||||
pub fn ego_view(&self) -> view::View {
|
||||
let cel = |f: &str| {
|
||||
let p = self.base.join("sprites").join(f);
|
||||
p.exists().then(|| assets::load_cel(&p.to_string_lossy(), false))
|
||||
};
|
||||
match cel("ego.png") {
|
||||
Some(c0) => {
|
||||
let cels = match cel("ego1.png") {
|
||||
Some(c1) => vec![c0, c1],
|
||||
None => vec![c0],
|
||||
};
|
||||
view::View { loops: vec![view::ViewLoop { cels }] }
|
||||
}
|
||||
None => view::default_robot_view(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sanity-check a room before committing it (the lore-ingestion gate):
|
||||
/// exits must point at rooms that exist (on disk, in the overlay, or being
|
||||
/// upserted alongside), and the room must have somewhere to stand.
|
||||
|
||||
Loading…
Reference in New Issue
Block a user