Pokémon that levelled up evolve after a won battle, following EvolveAfterBattle's checks over the ROM's evolution bytes, with the evolution screen that B can stop and move learning outside battle. The pack uses Rare Candy, stones (with ABLE/NOT ABLE labels) and vitamins, and its pocket tabs now sit in their own row. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1af2beacdc
commit
45275ba83b
19 files changed
+1032
-39
No files matched your search
+111
-4
@@ -28,6 +28,7 @@ import {
|
||||
} from "../scripts/interpreter";
|
||||
import { IntroRun } from "../scripts/intro";
|
||||
import { StartMenuRun, type MenuCursors } from "../scripts/start-menu";
|
||||
import { PartyEvolution } from "../scripts/evolution";
|
||||
import type { MovementPose, MovementStep } from "../scripts/movement";
|
||||
import { emptyBag, validBag, type Bag, type ItemSlot } from "../party/bag";
|
||||
import {
|
||||
@@ -254,6 +255,26 @@ export interface BattleView {
|
||||
/** Yes/No cursor, as on the original boxes. */
|
||||
selected: 0 | 1;
|
||||
}
|
||||
/** EvolutionAnimation in overworld ticks (two frames each): 80 frames on
|
||||
* the old species, then .AnimationSequence's seven rounds of a B-checking
|
||||
* wait (14, 12 … 2 frames) followed by that many flashes of the new and
|
||||
* old pictures (about four frames each). */
|
||||
function evolutionSchedule(from: number, to: number) {
|
||||
const frames: { species: number; cancel: boolean }[] = [];
|
||||
const show = (species: number, ticks: number, cancel = false) => {
|
||||
for (let i = 0; i < ticks; i++) frames.push({ species, cancel });
|
||||
};
|
||||
show(from, 40);
|
||||
for (let round = 1; round <= 7; round++) {
|
||||
show(from, 8 - round, true);
|
||||
for (let flash = 0; flash < round; flash++) {
|
||||
show(to, 2);
|
||||
show(from, 2);
|
||||
}
|
||||
}
|
||||
return frames;
|
||||
}
|
||||
|
||||
/** Ticks a `done` battle message stays before the battle continues. */
|
||||
const BATTLE_MESSAGE_TICKS = 30;
|
||||
|
||||
@@ -288,6 +309,12 @@ interface ActiveScript {
|
||||
warp?: { map: string; x: number; y: number; facing?: Direction };
|
||||
/** warpcheck: the player's tile events run on the tick after the script. */
|
||||
warpCheck?: boolean;
|
||||
/** Routines that run before the script resumes with `result`: the
|
||||
* evolutions ExitBattle starts after a won battle. */
|
||||
interlude?: {
|
||||
run: Generator<ScriptEffect, unknown, number | string | undefined>;
|
||||
result: number;
|
||||
};
|
||||
/** LoadMemScript: queued for the next PlayerEvents once this commits. */
|
||||
memScript?: MemScript;
|
||||
/** The trainer whose sight started this script, if any. */
|
||||
@@ -479,6 +506,17 @@ export class GameWorld {
|
||||
picture: number | undefined;
|
||||
/** The introduction's trainer or player picture, if any. */
|
||||
portrait: Portrait | undefined;
|
||||
/** An evolution animation in progress: the frame shown on each tick, and
|
||||
* whether B may stop it then. */
|
||||
evolution:
|
||||
| {
|
||||
from: number;
|
||||
to: number;
|
||||
cancelable: boolean;
|
||||
step: number;
|
||||
schedule: { species: number; cancel: boolean }[];
|
||||
}
|
||||
| undefined;
|
||||
/** True from a new game until the introduction finishes. */
|
||||
private introPending = false;
|
||||
/** The battle in progress, if any. */
|
||||
@@ -1609,6 +1647,14 @@ export class GameWorld {
|
||||
this.pump();
|
||||
}
|
||||
|
||||
private evolutionHost() {
|
||||
return {
|
||||
reader: this.assets.reader,
|
||||
data: this.assets.pokemon,
|
||||
timeOfDay: () => this.clockTimeOfDay,
|
||||
text: () => this.host(PLAYER).text(),
|
||||
};
|
||||
}
|
||||
/** Start in PlayerEvents (StartMenuScript): the start menu opens while
|
||||
* the player stands still. Its changes commit when it closes. */
|
||||
openStartMenu(): boolean {
|
||||
@@ -1622,6 +1668,8 @@ export class GameWorld {
|
||||
data: this.assets.pokemon,
|
||||
text: () => this.host(PLAYER).text(),
|
||||
bicycleScript: (state) => this.bicycleScript(state),
|
||||
timeOfDay: () => this.clockTimeOfDay,
|
||||
landmark: () => this.assets.map(this.mapId).landmark,
|
||||
},
|
||||
this.menuCursors,
|
||||
),
|
||||
@@ -1679,8 +1727,21 @@ export class GameWorld {
|
||||
if (!script) return;
|
||||
try {
|
||||
for (;;) {
|
||||
const effect = script.run.resume(input);
|
||||
input = undefined;
|
||||
let effect: ScriptEffect | undefined;
|
||||
const interlude = script.interlude;
|
||||
if (interlude) {
|
||||
const next = interlude.run.next(input);
|
||||
input = undefined;
|
||||
if (next.done) {
|
||||
script.interlude = undefined;
|
||||
input = interlude.result;
|
||||
continue;
|
||||
}
|
||||
effect = next.value;
|
||||
} else {
|
||||
effect = script.run.resume(input);
|
||||
input = undefined;
|
||||
}
|
||||
script.effect = effect;
|
||||
if (!effect) {
|
||||
this.finishScript();
|
||||
@@ -1955,6 +2016,16 @@ export class GameWorld {
|
||||
},
|
||||
};
|
||||
return true;
|
||||
case "evolve":
|
||||
this.picture = effect.from;
|
||||
this.evolution = {
|
||||
from: effect.from,
|
||||
to: effect.to,
|
||||
cancelable: effect.cancelable,
|
||||
step: 0,
|
||||
schedule: evolutionSchedule(effect.from, effect.to),
|
||||
};
|
||||
return true;
|
||||
case "pack":
|
||||
this.dialogue = {
|
||||
pages: [""],
|
||||
@@ -2350,6 +2421,7 @@ export class GameWorld {
|
||||
this.emote = undefined;
|
||||
this.picture = undefined;
|
||||
this.portrait = undefined;
|
||||
this.evolution = undefined;
|
||||
if (script.sighted !== undefined)
|
||||
this.refusedSighting = {
|
||||
map: this.mapId,
|
||||
@@ -2380,9 +2452,19 @@ export class GameWorld {
|
||||
);
|
||||
this.battle = undefined;
|
||||
this.battleRun = undefined;
|
||||
// CleanUpBattleRAM
|
||||
// ExitBattle: EvolveAfterBattle after a win, then CleanUpBattleRAM.
|
||||
const { result, evolvable } = next.value;
|
||||
this.menuCursors = { start: 0, party: 0, pocket: 0 };
|
||||
this.pump(next.value.result);
|
||||
if (this.script && evolvable && !(result & 0x0f)) {
|
||||
this.script.interlude = {
|
||||
run: new PartyEvolution(
|
||||
this.script.run.state,
|
||||
this.evolutionHost(),
|
||||
).evolveAfterBattle(evolvable),
|
||||
result,
|
||||
};
|
||||
this.pump();
|
||||
} else this.pump(result);
|
||||
return;
|
||||
}
|
||||
const effect = next.value;
|
||||
@@ -2793,6 +2875,13 @@ export class GameWorld {
|
||||
}
|
||||
const script = this.script;
|
||||
if (script) {
|
||||
const evolution = this.evolution;
|
||||
if (evolution) {
|
||||
const frame = evolution.schedule[++evolution.step];
|
||||
if (frame) this.picture = frame.species;
|
||||
else this.finishEvolution(false);
|
||||
return;
|
||||
}
|
||||
const dialogue = this.dialogue;
|
||||
if (dialogue) {
|
||||
if (
|
||||
@@ -3198,9 +3287,25 @@ export class GameWorld {
|
||||
)
|
||||
state.playerState = OW.PLAYER_NORMAL;
|
||||
}
|
||||
/** EvolutionAnimation ends: the new species, or the old one if B
|
||||
* stopped it. */
|
||||
private finishEvolution(stopped: boolean) {
|
||||
const evolution = this.evolution!;
|
||||
this.evolution = undefined;
|
||||
this.picture = stopped ? evolution.from : evolution.to;
|
||||
this.pump(stopped ? 1 : 0);
|
||||
}
|
||||
/** B, or A on a page without a choice. */
|
||||
advanceDialogue() {
|
||||
if (this.battle) return this.battleB();
|
||||
const evolution = this.evolution;
|
||||
if (evolution) {
|
||||
// .WaitFrames_CheckPressedB: only between flashes, and never for a
|
||||
// forced (evolution stone) evolution.
|
||||
if (evolution.cancelable && evolution.schedule[evolution.step]?.cancel)
|
||||
this.finishEvolution(true);
|
||||
return;
|
||||
}
|
||||
const dialogue = this.dialogue;
|
||||
if (!dialogue || !this.script) return;
|
||||
const last = dialogue.index === dialogue.pages.length - 1;
|
||||
@@ -3587,9 +3692,11 @@ export class GameWorld {
|
||||
this.emote = undefined;
|
||||
this.picture = undefined;
|
||||
this.portrait = undefined;
|
||||
this.evolution = undefined;
|
||||
this.battle = undefined;
|
||||
this.battleRun = undefined;
|
||||
this.memScript = undefined;
|
||||
this.menuCursors = { start: 0, party: 0, pocket: 0 };
|
||||
this.clearNotice();
|
||||
this.introPending = false;
|
||||
// A save made during the introduction starts it again.
|
||||
|
||||
Reference in new issue
Block a user