Port the fishing rods and wild Magikarp
The Old, Good and Super Rods run FishFunction facing water: the map's fish group and Fish pick no fish, no bite, or a Pokémon from FishGroups and the time of day's TimeFishGroups. The player casts and jiggles with the fishing frames drawn from LoadFishingGFX's tiles, shows "!" on a bite, and the hooked Pokémon attacks as BATTLETYPE_FISH. Facing pieces now use signed offsets, which the rod above and left of the player needs. Wild Magikarp no longer stop a battle: LoadEnemyMon redraws their DVs through CalcMagikarpLength and its filters, as the original does. The length routine matches the original in the reference emulator. Part of #27 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4cb4676c7d
commit
397dcef44c
20 files changed
+681
-47
No files matched your search
@@ -179,7 +179,7 @@ describe.skipIf(!existsSync(romPath))(
|
||||
});
|
||||
|
||||
it("refuses battles that need untranslated rules", () => {
|
||||
const { battle: game } = battle([starter()], { species: 129, level: 5 });
|
||||
const { battle: game } = battle([starter()], { species: 201, level: 5 });
|
||||
expect(() => play(game)).toThrow(UnsupportedBattle);
|
||||
});
|
||||
},
|
||||
|
||||
+200
-2
@@ -13,13 +13,16 @@ import {
|
||||
import { StartMenuRun } from "../src/game/scripts/start-menu";
|
||||
import { defaultTextContext } from "../src/game/scripts/text";
|
||||
import { OVERWORLD } from "../src/game/rom/profile";
|
||||
import { close, useItem } from "./menu-driver";
|
||||
import { fish } from "../src/game/scripts/fish";
|
||||
import { Battle, magikarpLength } from "../src/game/battle/battle";
|
||||
import { close, open, pick, pickItem, useItem } from "./menu-driver";
|
||||
import { settle } from "./world-driver";
|
||||
|
||||
const romPath =
|
||||
process.env.CRYSTAL_ROM_PATH ??
|
||||
"Pokemon - Crystal Version (UE) (V1.1) [C][!].gbc";
|
||||
const PLAYERS_ROOM = "24:7",
|
||||
NEW_BARK_TOWN = "24:4",
|
||||
ROUTE_30 = "26:1",
|
||||
RADIO_TOWER_3F = "3:19",
|
||||
GOLDENROD_UNDERGROUND = "3:53",
|
||||
@@ -38,7 +41,16 @@ const POTION = 0x12,
|
||||
ITEMFINDER = 0x37,
|
||||
CARD_KEY = 0x7f,
|
||||
BASEMENT_KEY = 0x85,
|
||||
SQUIRTBOTTLE = 0xaf;
|
||||
SQUIRTBOTTLE = 0xaf,
|
||||
OLD_ROD = 0x3a,
|
||||
GOOD_ROD = 0x3b,
|
||||
SUPER_ROD = 0x3d;
|
||||
const MAGIKARP = 129,
|
||||
KRABBY = 98,
|
||||
CORSOLA = 222,
|
||||
STARYU = 120;
|
||||
const FISHGROUP_SHORE = 1;
|
||||
const FACING_FISH_RIGHT = 0x13;
|
||||
const SPRITEMOVEDATA_SUDOWOODO = 0x17;
|
||||
const OAK_WARNING = "OAK: CHRIS! This isn't the time to use that!";
|
||||
|
||||
@@ -410,4 +422,190 @@ describe.skipIf(!existsSync(romPath))("field items", () => {
|
||||
"CHRIS used the\nSQUIRTBOTTLE.",
|
||||
]);
|
||||
});
|
||||
|
||||
it("fishes from FishGroups by rod and time of day", () => {
|
||||
const r = new CrystalAssets(new Uint8Array(readFileSync(romPath))).reader;
|
||||
const cast = (rod: number, time: number, ...draws: number[]) =>
|
||||
fish(r, FISHGROUP_SHORE, rod, time, () => {
|
||||
const draw = draws.shift();
|
||||
if (draw === undefined) throw new Error("No draw left.");
|
||||
return draw;
|
||||
});
|
||||
// A bite below 50 percent + 1 ($80), then the first entry the roll
|
||||
// does not exceed: 70 percent + 1, 85 percent + 1, 100 percent.
|
||||
expect(cast(0, 1, 127, 0)).toEqual({ species: MAGIKARP, level: 10 });
|
||||
expect(cast(0, 1, 128)).toBeUndefined();
|
||||
expect(cast(0, 1, 0, 179)).toEqual({ species: MAGIKARP, level: 10 });
|
||||
expect(cast(0, 1, 0, 255)).toEqual({ species: KRABBY, level: 10 });
|
||||
// The Good Rod's last entry is TimeFishGroups 0: Corsola by day,
|
||||
// Staryu from the night on.
|
||||
expect(cast(1, 0, 0, 255)).toEqual({ species: CORSOLA, level: 20 });
|
||||
expect(cast(1, 1, 0, 255)).toEqual({ species: CORSOLA, level: 20 });
|
||||
expect(cast(1, 2, 0, 255)).toEqual({ species: STARYU, level: 20 });
|
||||
expect(cast(2, 1, 0, 0)).toEqual({ species: KRABBY, level: 40 });
|
||||
});
|
||||
|
||||
it("draws a wild Magikarp's DVs again below four feet, except at the Lake of Rage", () => {
|
||||
const assets = new CrystalAssets(new Uint8Array(readFileSync(romPath)));
|
||||
const id = 0x1234;
|
||||
const feet = (dvs: number) => magikarpLength(assets.reader, dvs, id).feet;
|
||||
// Nonzero, unlike the draws that follow.
|
||||
const short = [...Array(0x10000).keys()].find(
|
||||
(dvs) => dvs > 0x100 && feet(dvs) < 4,
|
||||
)!,
|
||||
long = [...Array(0x10000).keys()].find(
|
||||
(dvs) => dvs > 0x100 && feet(dvs) >= 4,
|
||||
)!;
|
||||
/** The wild Magikarp's DVs for these Random draws (then zeros). */
|
||||
const dvs = (mapId: string, ...draws: number[]) => {
|
||||
const game = new Battle({
|
||||
data: assets.pokemon,
|
||||
party: [
|
||||
generatePartyMon(assets.pokemon, {
|
||||
species: 155,
|
||||
level: 5,
|
||||
item: 0,
|
||||
otId: id,
|
||||
ot: "CHRIS",
|
||||
dvs: 0x823b,
|
||||
caughtData: 0,
|
||||
}),
|
||||
],
|
||||
playerId: id,
|
||||
playerName: "CHRIS",
|
||||
timeOfDay: 1,
|
||||
pokedex: { seen: new Set(), caught: new Set() },
|
||||
random: () => draws.shift() ?? 0,
|
||||
wild: { species: MAGIKARP, level: 10 },
|
||||
fish: true,
|
||||
mapId,
|
||||
text: () => defaultTextContext("CHRIS"),
|
||||
});
|
||||
const first = game.run().next().value;
|
||||
expect(first && "text" in first && first.text).toBe(
|
||||
"The hooked\nMAGIKARP\nattacked!",
|
||||
);
|
||||
return game.enemy.mon.dvs;
|
||||
};
|
||||
const hi = (value: number) => value >> 8,
|
||||
lo = (value: number) => value & 0xff;
|
||||
// No item (0), the DVs, then 39 percent + 1 (100) skips the floor.
|
||||
expect(dvs(NEW_BARK_TOWN, 0, hi(short), lo(short), 99)).toBe(short);
|
||||
expect(
|
||||
dvs(NEW_BARK_TOWN, 0, hi(short), lo(short), 100, hi(long), lo(long), 100),
|
||||
).toBe(long);
|
||||
// The Lake of Rage (9:6) skips it without a draw, and so does any map
|
||||
// in group 9 or numbered 6.
|
||||
for (const map of ["9:6", "9:1", "24:6"])
|
||||
expect(dvs(map, 0, hi(short), lo(short), 255)).toBe(short);
|
||||
});
|
||||
|
||||
describe("fishing in New Bark Town", () => {
|
||||
/** Facing the sea, with a rod and a constant random byte. */
|
||||
function shore(random: number, rod = OLD_ROD) {
|
||||
const assets = new CrystalAssets(new Uint8Array(readFileSync(romPath)));
|
||||
const world = new GameWorld(
|
||||
assets,
|
||||
() => new Date("2026-09-22T12:00:00"),
|
||||
() => random,
|
||||
);
|
||||
world.state.party = [
|
||||
generatePartyMon(assets.pokemon, {
|
||||
species: 155,
|
||||
level: 5,
|
||||
item: 0,
|
||||
otId: world.playerId,
|
||||
ot: "CHRIS",
|
||||
dvs: 0x823b,
|
||||
caughtData: 0,
|
||||
}),
|
||||
];
|
||||
world.state.bag.keyItems = [rod];
|
||||
world.state.scenes[NEW_BARK_TOWN] = 1;
|
||||
world.restore({
|
||||
...world.save(),
|
||||
map: NEW_BARK_TOWN,
|
||||
x: 17,
|
||||
y: 7,
|
||||
direction: "right",
|
||||
objects: [],
|
||||
});
|
||||
return world;
|
||||
}
|
||||
/** USE from the pack, then tick until a page or a battle appears,
|
||||
* recording the player's facings and emotes meanwhile. */
|
||||
function cast(world: GameWorld, rod = OLD_ROD) {
|
||||
open(world);
|
||||
pick(world, "PACK");
|
||||
pickItem(world, rod);
|
||||
const items =
|
||||
world.dialogue?.choice?.kind === "menu"
|
||||
? world.dialogue.choice.items
|
||||
: [];
|
||||
world.choose(items.indexOf("USE"));
|
||||
const facings = new Set<number | undefined>(),
|
||||
emotes = new Set<number>();
|
||||
let ticks = 0;
|
||||
for (; ticks < 400 && !world.dialogue && !world.battle; ticks++) {
|
||||
world.tick();
|
||||
facings.add(world.poses().find((pose) => pose.id === 0)?.facing);
|
||||
if (world.emote) emotes.add(world.emote.emote);
|
||||
}
|
||||
return { facings, emotes, ticks };
|
||||
}
|
||||
|
||||
it("hooks a Pokémon that attacks", () => {
|
||||
const world = shore(0);
|
||||
const { facings, emotes } = cast(world);
|
||||
// Cast, four bites with the "!" bubble (EMOTE_SHOCK), then the rod
|
||||
// is pulled in.
|
||||
expect(facings).toContain(FACING_FISH_RIGHT);
|
||||
expect([...emotes]).toEqual([0]);
|
||||
expect(world.dialogue?.pages).toEqual(["Oh!\nA bite!"]);
|
||||
// .Movement_RestoreRod hid the bubble before the text.
|
||||
expect(world.emote).toBeUndefined();
|
||||
expect(world.poses().find((pose) => pose.id === 0)?.facing).toBe(
|
||||
FACING_FISH_RIGHT,
|
||||
);
|
||||
world.advanceDialogue();
|
||||
// PutTheRodAway, then the battle: BATTLETYPE_FISH.
|
||||
for (let tick = 0; tick < 100 && !world.battle?.message; tick++)
|
||||
world.tick();
|
||||
expect(
|
||||
world.poses().find((pose) => pose.id === 0)?.facing,
|
||||
).toBeUndefined();
|
||||
expect(world.battle?.message?.text).toBe(
|
||||
"The hooked\nMAGIKARP\nattacked!",
|
||||
);
|
||||
expect(world.diagnostic).toBe("");
|
||||
});
|
||||
|
||||
it("gets no bite, or nothing at all where the map has no fish", () => {
|
||||
for (const group of [undefined, 0]) {
|
||||
const world = shore(200, SUPER_ROD);
|
||||
if (group !== undefined) world.map.fishGroup = group;
|
||||
const { facings, emotes } = cast(world, SUPER_ROD);
|
||||
expect(facings).toContain(FACING_FISH_RIGHT);
|
||||
expect(emotes.size).toBe(0);
|
||||
expect(world.dialogue?.pages).toEqual(["Not even a nibble!"]);
|
||||
expect(close(world)).toEqual(["Not even a nibble!"]);
|
||||
expect(
|
||||
world.poses().find((pose) => pose.id === 0)?.facing,
|
||||
).toBeUndefined();
|
||||
expect(world.battle).toBeUndefined();
|
||||
expect(world.state.bag.keyItems).toEqual([SUPER_ROD]);
|
||||
}
|
||||
});
|
||||
|
||||
it("needs water ahead and no surfing, or Oak objects", () => {
|
||||
const turned = shore(0, GOOD_ROD);
|
||||
turned.restore({ ...turned.save(), direction: "left" });
|
||||
expect(useItem(turned, GOOD_ROD)).toEqual([OAK_WARNING]);
|
||||
close(turned);
|
||||
const surfing = shore(0, GOOD_ROD);
|
||||
surfing.restore({ ...surfing.save(), x: 18, playerState: 4 });
|
||||
expect(useItem(surfing, GOOD_ROD)).toEqual([OAK_WARNING]);
|
||||
close(surfing);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -19,7 +19,8 @@ async function start(
|
||||
| "pokegear"
|
||||
| "mom"
|
||||
| "fly"
|
||||
| "headbutt" = false,
|
||||
| "headbutt"
|
||||
| "fishing" = false,
|
||||
) {
|
||||
await page.route("**/src/game/runtime.ts", async (route) => {
|
||||
const response = await route.fetch(),
|
||||
@@ -507,6 +508,32 @@ test("Headbutt shakes a tree and a sleeping Hoothoot falls out", async ({
|
||||
await expect(screen).toHaveAttribute("data-map", "24:3");
|
||||
});
|
||||
|
||||
test("the Old Rod hooks a Magikarp from New Bark Town's shore", async ({
|
||||
page,
|
||||
}) => {
|
||||
await start(page, "fishing");
|
||||
const text = page.locator("#dialogue-text"),
|
||||
choices = page.locator("#dialogue-choice"),
|
||||
pack = page.locator("#mart");
|
||||
await page.keyboard.press("Enter");
|
||||
await choices.getByRole("option", { name: /^PACK/ }).click();
|
||||
await page.keyboard.press("ArrowRight");
|
||||
await page.keyboard.press("ArrowRight");
|
||||
await pack.getByRole("button", { name: "OLD ROD", exact: true }).click();
|
||||
await choices.getByRole("option", { name: "USE", exact: true }).click();
|
||||
// The cast, 40 frames, four bites with "!", 40 more frames.
|
||||
await page.clock.runFor(5_000);
|
||||
await expect(text).toHaveText(/Oh!\s+A bite!/);
|
||||
await page.keyboard.press("KeyX");
|
||||
await page.clock.runFor(5_000);
|
||||
await expect(page.locator("#battle")).toBeVisible();
|
||||
await expect(page.locator("#battle-text")).toHaveText(
|
||||
/The hooked\s+MAGIKARP\s+attacked!/,
|
||||
);
|
||||
await expect(page.locator("#battle-foe-name")).toHaveText("MAGIKARP");
|
||||
await expect(page.locator("#game-notice")).toHaveText("");
|
||||
});
|
||||
|
||||
test("Mom keeps the player's money with the digit box", async ({ page }) => {
|
||||
await start(page, "mom");
|
||||
const text = page.locator("#dialogue-text"),
|
||||
|
||||
+38
-14
@@ -12,7 +12,8 @@ import { generatePartyMon } from "../src/game/party/pokemon";
|
||||
* the Storm Badge, and New Bark Town and Cherrygrove City visited.
|
||||
* "headbutt" faces Route 29's first reachable Headbutt tree from below
|
||||
* with a Pokémon that knows Headbutt; Random always returning 0 drops a
|
||||
* sleeping Hoothoot. */
|
||||
* sleeping Hoothoot. "fishing" faces the sea on New Bark Town's shore
|
||||
* with the Old Rod, which then hooks a Magikarp. */
|
||||
export function foundationFixture(
|
||||
assets: CrystalAssets,
|
||||
room:
|
||||
@@ -23,7 +24,8 @@ export function foundationFixture(
|
||||
| "pokegear"
|
||||
| "mom"
|
||||
| "fly"
|
||||
| "headbutt",
|
||||
| "headbutt"
|
||||
| "fishing",
|
||||
) {
|
||||
const world = new GameWorld(
|
||||
assets,
|
||||
@@ -45,7 +47,8 @@ export function foundationFixture(
|
||||
}),
|
||||
);
|
||||
world.state.bag.items = [{ item: 18, quantity: 5 }]; // POTION
|
||||
if (room === "candy") world.state.bag.items.push({ item: 0x20, quantity: 1 });
|
||||
if (room === "candy")
|
||||
world.state.bag.items.push({ item: 0x20, quantity: 1 });
|
||||
}
|
||||
if (room === "pokegear" || room === "mom") {
|
||||
world.state.party = [
|
||||
@@ -80,10 +83,26 @@ export function foundationFixture(
|
||||
mon.pp = [15, 0, 0, 0];
|
||||
world.state.party = [mon];
|
||||
world.state.flags.add(OW.ENGINE_STORMBADGE);
|
||||
for (const spawn of [14, 15]) // SPAWN_NEW_BARK, SPAWN_CHERRYGROVE
|
||||
for (const spawn of [14, 15])
|
||||
// SPAWN_NEW_BARK, SPAWN_CHERRYGROVE
|
||||
world.state.flags.add(assets.spawnEngineFlag(spawn)!);
|
||||
for (let event = 0; event < 8; event++) world.state.events.delete(event);
|
||||
}
|
||||
if (room === "fishing") {
|
||||
world.state.party = [
|
||||
generatePartyMon(assets.pokemon, {
|
||||
species: 155,
|
||||
level: 8,
|
||||
item: 0,
|
||||
otId: world.playerId,
|
||||
ot: "CHRIS",
|
||||
dvs: 0x9abc,
|
||||
caughtData: 0,
|
||||
}),
|
||||
];
|
||||
world.state.bag.keyItems.push(0x3a); // OLD_ROD
|
||||
for (let event = 0; event < 8; event++) world.state.events.delete(event);
|
||||
}
|
||||
if (room === "headbutt") {
|
||||
const mon = generatePartyMon(assets.pokemon, {
|
||||
species: 158, // TOTODILE
|
||||
@@ -111,21 +130,26 @@ export function foundationFixture(
|
||||
const [map, x, y] =
|
||||
room === "headbutt"
|
||||
? headbuttSpot(assets)
|
||||
: room === "center" || room === "candy"
|
||||
? ["26:5", 9, 2]
|
||||
: room === "poster"
|
||||
? ["24:7", 6, 1]
|
||||
: room === "mom"
|
||||
? ["24:6", 7, 3]
|
||||
: room && room !== "pokegear" && room !== "fly"
|
||||
? ["24:7", 2, 2]
|
||||
: ["24:4", 9, 6];
|
||||
: room === "fishing"
|
||||
? ["24:4", 17, 7]
|
||||
: room === "center" || room === "candy"
|
||||
? ["26:5", 9, 2]
|
||||
: room === "poster"
|
||||
? ["24:7", 6, 1]
|
||||
: room === "mom"
|
||||
? ["24:6", 7, 3]
|
||||
: room &&
|
||||
room !== "pokegear" &&
|
||||
room !== "fly" &&
|
||||
room !== "fishing"
|
||||
? ["24:7", 2, 2]
|
||||
: ["24:4", 9, 6];
|
||||
world.restore({
|
||||
...save,
|
||||
map,
|
||||
x,
|
||||
y,
|
||||
direction: room === "mom" ? "down" : "up",
|
||||
direction: room === "mom" ? "down" : room === "fishing" ? "right" : "up",
|
||||
objects: [],
|
||||
});
|
||||
return world;
|
||||
|
||||
@@ -28,6 +28,7 @@ export class MovementAssets extends CrystalAssets {
|
||||
environment: 1,
|
||||
paletteMode: 1,
|
||||
phoneService: 0,
|
||||
fishGroup: 0,
|
||||
blocks: new Uint8Array(16),
|
||||
warps: [],
|
||||
backgroundEvents: [],
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
Battle,
|
||||
applyTypeMatchups,
|
||||
calculateDamage,
|
||||
magikarpLength,
|
||||
restoreHealth,
|
||||
restoredPP,
|
||||
sendOutText,
|
||||
@@ -638,3 +639,75 @@ test("native switch texts, healing, PP, happiness and CalcPlayerStats match the
|
||||
).toEqual(results.stats[index]);
|
||||
});
|
||||
});
|
||||
|
||||
test("native Magikarp lengths match CalcMagikarpLength", async ({ page }) => {
|
||||
test.skip(
|
||||
!existsSync(romPath) || !existsSync(symbolsPath),
|
||||
"Requires the local user ROM and the private PRET rebuild.",
|
||||
);
|
||||
const { at, routine } = loadSymbols();
|
||||
const reader = new CrystalAssets(new Uint8Array(readFileSync(romPath))).reader;
|
||||
let seed = 0x4a39;
|
||||
const next = (limit: number) => {
|
||||
seed = (seed * 1103515245 + 12345) >>> 0;
|
||||
return (seed >>> 8) % limit;
|
||||
};
|
||||
// Random DVs and IDs, plus indexes below 10 and at the table's edges.
|
||||
const cases = [
|
||||
...Array.from({ length: 1500 }, () => ({ dvs: next(0x10000), id: next(0x10000) })),
|
||||
...[0x0000, 0x0009, 0x000a, 0x00ff, 0x0100, 0xfdff, 0xfe00, 0xff00, 0xffff].map((dvs) => ({
|
||||
dvs,
|
||||
id: 0,
|
||||
})),
|
||||
];
|
||||
await openReference(page);
|
||||
const addresses = {
|
||||
calc: routine("CalcMagikarpLength"),
|
||||
length: at("wMagikarpLength"),
|
||||
};
|
||||
const results = await page.evaluate(
|
||||
async ({ addresses, cases }) => {
|
||||
const { BinjgbEmulator } = await import("/src/emulator/binjgb.ts");
|
||||
const file = (document.querySelector("#reference-fixture") as HTMLInputElement).files![0];
|
||||
const reference = new BinjgbEmulator();
|
||||
await reference.load(new Uint8Array(await file.arrayBuffer()), document.createElement("canvas"));
|
||||
const write = (address: number, value: number) => reference.writeMemory(address, value & 0xff);
|
||||
// Trampoline: DI; SP=$c7fe; switch to the routine's bank; set BC (the
|
||||
// ID) and DE (the DVs); CALL CalcMagikarpLength; write a sentinel; loop.
|
||||
const dvs = 0xc100,
|
||||
id = 0xc102;
|
||||
const program = [
|
||||
0xf3, 0x31, 0xfe, 0xc7, 0x3e, addresses.calc.bank, 0xe0, 0x9d, 0xea, 0x00, 0x20,
|
||||
0x01, id & 0xff, id >> 8, 0x11, dvs & 0xff, dvs >> 8,
|
||||
0xcd, addresses.calc.address & 0xff, addresses.calc.address >> 8,
|
||||
0x3e, 0x42, 0xea, 0xff, 0xc6, 0x18, 0xfe,
|
||||
];
|
||||
write(0xff70, 1);
|
||||
const lengths = cases.map((input) => {
|
||||
write(dvs, input.dvs >> 8);
|
||||
write(dvs + 1, input.dvs);
|
||||
write(id, input.id >> 8);
|
||||
write(id + 1, input.id);
|
||||
write(0xc6ff, 0);
|
||||
program.forEach((value, index) => write(0xc000 + index, value));
|
||||
reference.setProgramCounter(0xc000);
|
||||
reference.advanceFrames(2);
|
||||
return {
|
||||
returned: reference.readMemory(0xc6ff) === 0x42,
|
||||
feet: reference.readMemory(addresses.length),
|
||||
inches: reference.readMemory(addresses.length + 1),
|
||||
};
|
||||
});
|
||||
reference.dispose();
|
||||
return lengths;
|
||||
},
|
||||
{ addresses, cases },
|
||||
);
|
||||
cases.forEach((input, index) => {
|
||||
const { returned, ...original } = results[index];
|
||||
expect(returned, `case ${index} returned`).toBe(true);
|
||||
expect(magikarpLength(reader, input.dvs, input.id), JSON.stringify(input)).toEqual(original);
|
||||
});
|
||||
const lengths = new Set(results.map(({ feet, inches }) => feet * 12 + inches));
|
||||
expect(lengths.size).toBeGreaterThan(30);
|
||||
});
|
||||
@@ -20,7 +20,6 @@ const BICYCLE = 7,
|
||||
ANTIDOTE = 9,
|
||||
FULL_RESTORE = 0x0e,
|
||||
POTION = 0x12,
|
||||
OLD_ROD = 0x3a,
|
||||
THUNDERSTONE = 0x17,
|
||||
HP_UP = 0x1a,
|
||||
RARE_CANDY = 0x20,
|
||||
@@ -625,10 +624,8 @@ describe.skipIf(!existsSync(romPath))("the start menu", () => {
|
||||
expect(world.state.registeredItem).toEqual({ which: 0, item: 0 });
|
||||
});
|
||||
|
||||
it("offers no USE without a field menu, and a notice for unported effects", () => {
|
||||
it("offers no USE without a field menu", () => {
|
||||
const world = fixture();
|
||||
world.state.bag.items.push({ item: BERRY, quantity: 1 });
|
||||
world.state.bag.keyItems.push(OLD_ROD);
|
||||
world.state.bag.balls = [{ item: 5, quantity: 3 }];
|
||||
open(world);
|
||||
pick(world, "PACK");
|
||||
@@ -636,11 +633,7 @@ describe.skipIf(!existsSync(romPath))("the start menu", () => {
|
||||
pickItem(world, 5);
|
||||
expect(menuItems(world)).toEqual(["GIVE", "TOSS", "QUIT"]);
|
||||
pick(world, "QUIT");
|
||||
pickItem(world, OLD_ROD);
|
||||
pick(world, "USE");
|
||||
expect(world.notice).toBe("Using OLD ROD is not ported yet.");
|
||||
close(world);
|
||||
expect(world.state.bag.keyItems).toContain(OLD_ROD);
|
||||
});
|
||||
|
||||
it("raises a level with a Rare Candy, forgetting a move to learn a new one", () => {
|
||||
|
||||
Reference in new issue
Block a user