A new game now runs NewGame's InitGender and OakSpeech natively before the bedroom: Boy or Girl, InitClock's hour and minute steppers with their YES/NO confirmations, Oak's speech with Wooper, and NamePlayer's preset names or naming field (a blank name takes CHRIS or KRIS). Texts, menus and time strings come from a new INTRO profile table verified by check:reference; Oak's and the player's pictures decode from TrainerPicPointers, ChrisPic/KrisPic and TrainerPalettes. The chosen time is kept as a minute offset from the device clock, which time of day, the weekday, NPC hours and the daily reset now read. The intro runs only when nothing is saved; a save made during it starts it again. Saves gain intro and clock.minuteOffset, migrated by absence. The shell's name and character fields are gone. Intentional change: the steppers start at the device time rather than 10:00. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5a0fe1e313
commit
b13fd20be4
28 files changed
+975
-101
No files matched your search
+46
-23
@@ -42,8 +42,7 @@ function showWorld(world: GameWorld) {
|
||||
`You are at ${world.x}, ${world.y}. Red marks your position.`;
|
||||
get("minimap-position").textContent = `${world.x}, ${world.y}`;
|
||||
get("map-empty").hidden = true;
|
||||
get<HTMLInputElement>("player-name").value = world.name;
|
||||
get<HTMLSelectElement>("player-avatar").value = world.avatar;
|
||||
get("intro-screen").hidden = !world.intro;
|
||||
get("location").textContent = world.map.name;
|
||||
get("tiles-walked").textContent = `${world.tilesWalked} tiles walked`;
|
||||
get("game-notice").textContent = world.notice;
|
||||
@@ -81,6 +80,7 @@ function showWorld(world: GameWorld) {
|
||||
canvas.dataset.x = String(world.x);
|
||||
canvas.dataset.y = String(world.y);
|
||||
canvas.dataset.activity = world.activity;
|
||||
canvas.dataset.intro = String(world.intro);
|
||||
canvas.setAttribute(
|
||||
"aria-label",
|
||||
`${world.map.name}. Position ${world.x}, ${world.y}. Arrow keys move. X interacts. Z continues dialogue.`,
|
||||
@@ -134,6 +134,25 @@ function showChoice(choice: Choice | undefined) {
|
||||
);
|
||||
container.firstElementChild?.setAttribute("aria-label", "Previous day");
|
||||
container.children[2]?.setAttribute("aria-label", "Next day");
|
||||
} else if (choice.kind === "clock") {
|
||||
// InitClock's hour or minutes: Up for later, Down for earlier.
|
||||
const value = document.createElement("output");
|
||||
value.textContent = choice.labels[choice.selected];
|
||||
value.setAttribute("aria-live", "polite");
|
||||
container.replaceChildren(
|
||||
button("▼", undefined, () => {
|
||||
runtime.world?.moveCursor("down");
|
||||
if (runtime.world) showWorld(runtime.world);
|
||||
}),
|
||||
value,
|
||||
button("▲", undefined, () => {
|
||||
runtime.world?.moveCursor("up");
|
||||
if (runtime.world) showWorld(runtime.world);
|
||||
}),
|
||||
button("OK", true, () => runtime.choose(choice.selected)),
|
||||
);
|
||||
container.firstElementChild?.setAttribute("aria-label", "Earlier");
|
||||
container.children[2]?.setAttribute("aria-label", "Later");
|
||||
} else if (choice.kind === "name")
|
||||
container.replaceChildren(
|
||||
...nameField(choice.current, choice.max, (name) => runtime.choose(name)),
|
||||
@@ -687,20 +706,37 @@ function setHp(bar: HTMLElement, hp: number, max: number) {
|
||||
bar.classList.toggle("low", fraction <= 0.5 && fraction > 0.2);
|
||||
bar.classList.toggle("critical", fraction <= 0.2);
|
||||
}
|
||||
let shownPicture: number | undefined;
|
||||
/** Draw a pokepic from the ROM's front picture tiles and palette. */
|
||||
let shownPicture = "";
|
||||
/** Draw a pokepic, or the introduction's trainer or player picture, from
|
||||
* the ROM's picture tiles and palette. */
|
||||
function showPicture(world: GameWorld) {
|
||||
const box = get("pokepic");
|
||||
box.hidden = world.picture === undefined;
|
||||
if (world.picture === shownPicture) return;
|
||||
shownPicture = world.picture;
|
||||
if (world.picture === undefined) return;
|
||||
const portrait = world.portrait;
|
||||
box.hidden = world.picture === undefined && !portrait;
|
||||
const key = JSON.stringify([world.picture, portrait]);
|
||||
if (key === shownPicture) return;
|
||||
shownPicture = key;
|
||||
if (box.hidden) return;
|
||||
const data = world.assets.pokemon;
|
||||
const picture = get<HTMLCanvasElement>("pokepic-canvas");
|
||||
const context = picture.getContext("2d")!;
|
||||
const image = context.createImageData(56, 56);
|
||||
const tiles = data.frontPic(world.picture),
|
||||
let tiles: Uint8Array, palette: Palette, label: string;
|
||||
if (world.picture !== undefined) {
|
||||
tiles = data.frontPic(world.picture);
|
||||
palette = data.palette(world.picture);
|
||||
label = data.speciesName(world.picture);
|
||||
} else if (portrait && "player" in portrait) {
|
||||
// DrawIntroPlayerPic: CHRIS's palette, or KRIS's (class 1).
|
||||
tiles = data.playerPic(portrait.player);
|
||||
palette = data.trainerPalette(portrait.player ? 1 : 0);
|
||||
label = portrait.player ? "Kris" : "Chris";
|
||||
} else {
|
||||
const trainer = (portrait as { trainer: number }).trainer;
|
||||
tiles = data.trainerPic(trainer);
|
||||
palette = data.trainerPalette(trainer);
|
||||
label = "Professor Oak";
|
||||
}
|
||||
// Picture tiles are stored column by column.
|
||||
for (let tile = 0; tile < 49; tile++)
|
||||
drawTile(
|
||||
@@ -714,7 +750,7 @@ function showPicture(world: GameWorld) {
|
||||
palette,
|
||||
);
|
||||
context.putImageData(image, 0, 0);
|
||||
picture.setAttribute("aria-label", data.speciesName(world.picture));
|
||||
picture.setAttribute("aria-label", label);
|
||||
}
|
||||
const runtime = new NativeRuntime(
|
||||
showWorld,
|
||||
@@ -724,17 +760,6 @@ const runtime = new NativeRuntime(
|
||||
showError(error);
|
||||
update();
|
||||
},
|
||||
() => ({
|
||||
name:
|
||||
get<HTMLInputElement>("player-name")
|
||||
.value.trim()
|
||||
.replace(/[^A-Za-z0-9 -]/g, "")
|
||||
.slice(0, 7) || "CHRIS",
|
||||
avatar:
|
||||
get<HTMLSelectElement>("player-avatar").value === "kris"
|
||||
? "kris"
|
||||
: "chris",
|
||||
}),
|
||||
handleBattleInput,
|
||||
);
|
||||
const session = new GameSession(
|
||||
@@ -793,8 +818,6 @@ function update() {
|
||||
get("pause-overlay").hidden = session.state !== "paused";
|
||||
get<HTMLInputElement>("rom-file").disabled = busy;
|
||||
get("rom-label").classList.toggle("disabled", busy);
|
||||
get<HTMLInputElement>("player-name").disabled = busy || ready();
|
||||
get<HTMLSelectElement>("player-avatar").disabled = busy || ready();
|
||||
get<HTMLButtonElement>("dialogue-next").disabled =
|
||||
busy || session.state !== "playing";
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user