Open the Battle Tower and add the badges' type boosts (Closes #32)
The Route 40 gate, the Battle Tower and its rooms are open with the offline challenge: the rules, the save before entering, the level menu with its level and uber checks, seven trainers drawn from BattleTowerTrainers with Pokémon from BattleTowerMons, BattleTowerText, save and quit with the receptionist resuming it, cancelling, losing and the vitamin reward. After each battle the party and Pokédex return to the quick save's, as LoadPokemonData does. Battle Tower battles turn off obedience, badge boosts, experience, items, AI items and prize money, use Falkner's AI attributes, keep Sleep to one to three turns and never fail the AI's status moves at random. New script command battletowertext; new specials BattleTowerAction (offline actions), BattleTowerRoomMenu, CheckForBattleTowerRules, BattleTowerBattle, LoadOpponentTrainerAndPokemonWithOTSprite, Menu_ChallengeExplanationCancel, TryQuickSave and Reset. DoBadgeTypeBoosts was missing from every battle; it now adds an eighth of the damage for the first badge matching the move's type on the player's turn, checked against the original routine. This completes Johto from Azalea to Blackthorn: Kanto, the League and the S.S. Aqua continue on #33; radio, rematches and Buena's password on #30. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3fbc6798eb
commit
fe2b0262c5
13 files changed
+1267
-14
No files matched your search
@@ -0,0 +1,86 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { CrystalAssets } from "../src/game/rom/assets";
|
||||
import { TrainerData } from "../src/game/rom/trainers";
|
||||
import { Battle } from "../src/game/battle/battle";
|
||||
import { C } from "../src/game/battle/constants";
|
||||
import { battleOptions, battleSteps, partyMon, playBattle } from "./battle-driver";
|
||||
|
||||
const romPath =
|
||||
process.env.CRYSTAL_ROM_PATH ?? "Pokemon - Crystal Version (UE) (V1.1) [C][!].gbc";
|
||||
|
||||
describe.skipIf(!existsSync(romPath))("Battle Tower battles (wInBattleTowerBattle)", () => {
|
||||
const assets = existsSync(romPath)
|
||||
? new CrystalAssets(new Uint8Array(readFileSync(romPath)))
|
||||
: undefined;
|
||||
const trainers = assets && new TrainerData(assets.reader);
|
||||
const TYPHLOSION = 157,
|
||||
RATTATA = 19;
|
||||
/** A Battle Tower trainer as the world builds one: Falkner's AI, no
|
||||
* items or prize, and the texts BattleTowerText chose. */
|
||||
const tower = (party = [partyMon(assets!, RATTATA, 5)]) => ({
|
||||
name: "HANSON",
|
||||
className: "FISHER",
|
||||
party,
|
||||
aiFlags: trainers!.attributes(1).aiMoveFlags,
|
||||
switchFlags: trainers!.attributes(1).aiItemSwitchFlags,
|
||||
items: [0, 0] as [number, number],
|
||||
baseReward: 0,
|
||||
reward: 0,
|
||||
});
|
||||
|
||||
it("gives no experience or prize, and refuses items", () => {
|
||||
const party = [partyMon(assets!, TYPHLOSION, 50)];
|
||||
const exp = party[0].exp;
|
||||
const options = battleOptions(assets!, trainers!, party, { wild: { species: RATTATA, level: 5 } });
|
||||
const battle = new Battle({
|
||||
...options,
|
||||
wild: undefined,
|
||||
trainer: tower(),
|
||||
battleTower: true,
|
||||
bag: { items: [{ item: C.POTION, quantity: 1 }], balls: [], keyItems: [], tmsHms: [] } as never,
|
||||
});
|
||||
const steps = battleSteps(battle);
|
||||
expect(steps.prompt?.kind).toBe("action");
|
||||
steps.answer({ action: "item", item: C.POTION, slot: 0 });
|
||||
expect(steps.texts.join(" ")).toContain("Items can't be used here.");
|
||||
expect(steps.prompt?.kind).toBe("action");
|
||||
for (let turn = 0; turn < 20 && !steps.done; turn++)
|
||||
steps.answer(steps.prompt?.kind === "action" ? { action: "fight", move: 0 } : 1);
|
||||
expect(steps.done).toBe(true);
|
||||
expect(party[0].exp).toBe(exp);
|
||||
expect(steps.texts.join(" ")).not.toContain("EXP. Points");
|
||||
expect(steps.texts.join(" ")).not.toContain("for winning");
|
||||
});
|
||||
|
||||
it("lets a traded Pokémon over the badge level obey", () => {
|
||||
const traded = { ...partyMon(assets!, TYPHLOSION, 80), otId: 4321 };
|
||||
const options = battleOptions(assets!, trainers!, [traded], { wild: { species: RATTATA, level: 5 } });
|
||||
const run = (battleTower: boolean) =>
|
||||
playBattle(new Battle({ ...options, wild: undefined, trainer: tower(), battleTower, johtoBadges: 0 }));
|
||||
// Without badges an 80th-level traded Pokémon disobeys sometimes; in
|
||||
// the Battle Tower it never does.
|
||||
const disobeys = /ignored orders|loafing|won't obey|turned away|began to nap/;
|
||||
expect(run(false).texts.join(" ")).toMatch(disobeys);
|
||||
expect(run(true).texts.join(" ")).not.toMatch(disobeys);
|
||||
});
|
||||
|
||||
it("ends a loss with the trainer's text and no whiteout prompt", () => {
|
||||
const party = [partyMon(assets!, RATTATA, 2)];
|
||||
const options = battleOptions(assets!, trainers!, party, { wild: { species: RATTATA, level: 5 } });
|
||||
const winText = assets!.reader.near(
|
||||
assets!.reader.near(0x11f332 + 2, assets!.reader.bank(0x11f332)),
|
||||
assets!.reader.bank(0x11f332),
|
||||
);
|
||||
const result = playBattle(
|
||||
new Battle({
|
||||
...options,
|
||||
wild: undefined,
|
||||
trainer: { ...tower([partyMon(assets!, TYPHLOSION, 60)]), lossText: winText },
|
||||
battleTower: true,
|
||||
}),
|
||||
);
|
||||
expect(result.result & 0x3f).toBe(C.LOSE);
|
||||
expect(result.texts.join(" ")).not.toContain("whited out");
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user