Fix ego sprite and text wrap on the web build
ego_view() decided whether a game overrode the default party robot by stat'ing sprites/ego.png on disk. A wasm build has no disk — the game is baked into the binary with include_dir — so the check always failed and every web player was a party robot regardless of what the game shipped. Read the already-loaded sprite table instead, which both loaders populate by file stem, so native and web now agree and native does one less read. The transcript was drawn one draw_text per line with no wrapping, so any line wider than the window ran off the right edge and was simply lost. Room descriptions here are whole paragraphs, so this ate the end of most of them. Greedy word-wrap on measured width, and the visible row count is derived from the panel geometry rather than hardcoded at 5. Panel grows 150 -> 186 so a wrapped paragraph fits without pushing its own opening line off the top. Also: Game > Manual opened monsterrobot.games/engine/manual.html, which has not served the manual since that domain moved hosts — it silently returns the arcade index (200, so nothing looked wrong). Point native at the repo's own web/manual.html, which always matches the build. Verified: all 16 policesquadquest win paths still replay to max score, and both fixes confirmed rendering in the native --shot and on the live deploy. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
827797ad1f
commit
2bf19d983e
@ -498,16 +498,15 @@ impl World {
|
||||
/// 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") {
|
||||
// Read the already-loaded sprite table rather than stat'ing the folder:
|
||||
// a wasm build is baked into the binary and has no `sprites/` to look at,
|
||||
// so the filesystem version silently fell back to the robot on the web.
|
||||
match self.sprite_cel("ego") {
|
||||
Some(c0) => {
|
||||
let cels = match cel("ego1.png") {
|
||||
Some(c1) => vec![c0, c1],
|
||||
None => vec![c0],
|
||||
};
|
||||
let mut cels = vec![c0.clone()];
|
||||
if let Some(c1) = self.sprite_cel("ego1") {
|
||||
cels.push(c1.clone());
|
||||
}
|
||||
view::View { loops: vec![view::ViewLoop { cels }] }
|
||||
}
|
||||
None => view::default_robot_view(),
|
||||
|
||||
@ -26,7 +26,8 @@ fn window_conf() -> Conf {
|
||||
}
|
||||
}
|
||||
|
||||
const STATUS_H: f32 = 150.0;
|
||||
// 7 text rows: wrapped room descriptions routinely run to three or four.
|
||||
const STATUS_H: f32 = 186.0;
|
||||
/// The classic Sierra menu bar across the top of the play screen.
|
||||
const MENU_H: f32 = 22.0;
|
||||
const MENU_ITEM_H: f32 = 20.0;
|
||||
@ -49,17 +50,18 @@ enum MenuAct {
|
||||
About,
|
||||
}
|
||||
|
||||
const MANUAL_URL: &str = "https://monsterrobot.games/engine/manual.html";
|
||||
|
||||
/// Open the Book: a new tab on the web, the default browser natively.
|
||||
fn open_manual() {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
unsafe {
|
||||
web_bridge::mrpgi_open_manual();
|
||||
}
|
||||
// The repo's own copy, so it always matches this build. The old
|
||||
// monsterrobot.games/engine/ URL now falls through to the arcade index.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
let _ = std::process::Command::new("open").arg(MANUAL_URL).spawn();
|
||||
let p = concat!(env!("CARGO_MANIFEST_DIR"), "/../web/manual.html");
|
||||
let _ = std::process::Command::new("open").arg(p).spawn();
|
||||
}
|
||||
}
|
||||
|
||||
@ -758,6 +760,24 @@ fn draw_scanlines(l: &Layout) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Greedy word-wrap to a pixel width. A word longer than the line is left to
|
||||
/// overhang rather than hyphenated — that only happens to URLs.
|
||||
fn wrap(s: &str, size: f32, max_w: f32) -> Vec<String> {
|
||||
let mut out: Vec<String> = Vec::new();
|
||||
let mut cur = String::new();
|
||||
for w in s.split_whitespace() {
|
||||
let trial = if cur.is_empty() { w.to_string() } else { format!("{} {}", cur, w) };
|
||||
if !cur.is_empty() && measure_text(&trial, None, size as u16, 1.0).width > max_w {
|
||||
out.push(std::mem::take(&mut cur));
|
||||
cur = w.to_string();
|
||||
} else {
|
||||
cur = trial;
|
||||
}
|
||||
}
|
||||
out.push(cur);
|
||||
out
|
||||
}
|
||||
|
||||
fn draw_play_panel(gs: &GameState, command: &str, crt: bool, ai: &str, sound_on: bool) {
|
||||
let sw = screen_width();
|
||||
let sh = screen_height();
|
||||
@ -765,15 +785,23 @@ fn draw_play_panel(gs: &GameState, command: &str, crt: bool, ai: &str, sound_on:
|
||||
draw_rectangle(0.0, top, sw, STATUS_H, color_u8!(12, 14, 18, 255));
|
||||
draw_line(0.0, top, sw, top, 1.0, color_u8!(60, 70, 85, 255));
|
||||
|
||||
// Room descriptions are whole paragraphs, so wrap to the panel before
|
||||
// drawing — draw_text will otherwise run a long line off the right edge.
|
||||
let rows = (((sh - 38.0) - (top + 20.0)) / 18.0).floor().max(1.0) as usize;
|
||||
let mut yy = top + 20.0;
|
||||
let start = gs.transcript.len().saturating_sub(5);
|
||||
let start = gs.transcript.len().saturating_sub(rows);
|
||||
let mut vis: Vec<(String, bool)> = Vec::new();
|
||||
for line in &gs.transcript[start..] {
|
||||
let col = if line.starts_with('>') {
|
||||
let cmd = line.starts_with('>');
|
||||
vis.extend(wrap(&ascii_fold(line), 18.0, sw - 24.0).into_iter().map(|l| (l, cmd)));
|
||||
}
|
||||
for (line, cmd) in vis.iter().skip(vis.len().saturating_sub(rows)) {
|
||||
let col = if *cmd {
|
||||
color_u8!(255, 135, 215, 255)
|
||||
} else {
|
||||
color_u8!(205, 213, 225, 255)
|
||||
};
|
||||
draw_text(&ascii_fold(line), 12.0, yy, 18.0, col);
|
||||
draw_text(line, 12.0, yy, 18.0, col);
|
||||
yy += 18.0;
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user