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
+53
-3
@@ -59,6 +59,12 @@ import {
|
||||
type DayTimerName,
|
||||
} from "../scripts/interpreter";
|
||||
import { initialMagikarpRecord, type MagikarpRecord } from "../scripts/lake-of-rage";
|
||||
import {
|
||||
copyBattleTowerState,
|
||||
emptyBattleTowerState,
|
||||
validBattleTower,
|
||||
type BattleTowerState,
|
||||
} from "../scripts/battle-tower";
|
||||
import { IntroRun } from "../scripts/intro";
|
||||
import { FLY_MAP_BUTTONS, type FlyMapView } from "../scripts/fly-map";
|
||||
import { SCREEN_BUTTONS, type TileScreen } from "../scripts/screen";
|
||||
@@ -211,6 +217,8 @@ export interface NativeSave {
|
||||
luckyNumber: { id: number; day: number };
|
||||
/** The Magikarp length record and its holder. */
|
||||
magikarpRecord: MagikarpRecord;
|
||||
/** The Battle Tower's SRAM and its scripts' work RAM. */
|
||||
battleTower: BattleTowerState;
|
||||
/** wRoamMon1/2/3 (Raikou, Entei, Suicune). */
|
||||
roamMons: RoamMon[];
|
||||
/** wRoamMons_CurMapGroup/Number, kept from the last UpdateRoamMons or
|
||||
@@ -282,6 +290,9 @@ interface Actor {
|
||||
/** An appear or disappear this visit: its visibility, not its flag,
|
||||
* is what a save keeps. */
|
||||
toggled?: boolean;
|
||||
/** A map object sprite a script set (LoadOpponentTrainerAndPokemonWith-
|
||||
* OTSprite) until the map loads again. */
|
||||
sprite?: number;
|
||||
/** OBJECT_ACTION_FISHING, from fish_cast_rod or fish_got_bite until
|
||||
* PutTheRodAway: the fishing frames replace the standing ones. */
|
||||
fishing?: boolean;
|
||||
@@ -932,6 +943,8 @@ export class GameWorld {
|
||||
}
|
||||
/** GetSprite's sprite id: SPRITE_VARS ids read wVariableSprites. */
|
||||
private spriteOf(object: MapObject, state = this.liveState): number {
|
||||
const override = this.actors?.get(object.id)?.sprite;
|
||||
if (override !== undefined) return override;
|
||||
if (object.sprite < OW.SPRITE_VARS) return object.sprite;
|
||||
return state.variables[object.sprite - OW.SPRITE_VARS] ?? 0;
|
||||
}
|
||||
@@ -2628,13 +2641,37 @@ export class GameWorld {
|
||||
},
|
||||
};
|
||||
return true;
|
||||
case "objectsprite": {
|
||||
const object = this.map.objects.find((candidate) => candidate.id === effect.object);
|
||||
if (!object) throw new Error("Script names an unknown object.");
|
||||
this.actor(object).sprite = effect.sprite;
|
||||
return false;
|
||||
}
|
||||
case "battle": {
|
||||
const draft = script.run.state;
|
||||
const trainers = new TrainerData(this.assets.reader);
|
||||
const opponent =
|
||||
effect.trainer &&
|
||||
trainers.trainer(effect.trainer.classId, effect.trainer.id);
|
||||
const trainer = opponent && {
|
||||
// A Battle Tower trainer: its own name and Pokémon, and the first
|
||||
// class's (Falkner's) AI attributes, which the AI layers and
|
||||
// AI_SwitchOrTryItem read in the Battle Tower; no items or prize.
|
||||
const tower = effect.tower;
|
||||
const falkner = tower && trainers.attributes(1);
|
||||
const towerTrainer = tower &&
|
||||
falkner && {
|
||||
name: tower.name,
|
||||
className: trainers.className(tower.trainerClass),
|
||||
party: structuredClone(tower.party),
|
||||
aiFlags: falkner.aiMoveFlags,
|
||||
switchFlags: falkner.aiItemSwitchFlags,
|
||||
items: [0, 0] as [number, number],
|
||||
baseReward: 0,
|
||||
reward: 0,
|
||||
winText: tower.lossText,
|
||||
lossText: tower.winText,
|
||||
};
|
||||
const trainer = towerTrainer ?? (opponent && {
|
||||
name: opponent.name,
|
||||
className: opponent.className,
|
||||
party: trainers.createParty(opponent, {
|
||||
@@ -2648,7 +2685,7 @@ export class GameWorld {
|
||||
reward: opponent.reward,
|
||||
winText: effect.trainer?.winText,
|
||||
lossText: effect.trainer?.lossText,
|
||||
};
|
||||
});
|
||||
const engine = new Battle({
|
||||
data: this.assets.pokemon,
|
||||
party: draft.party,
|
||||
@@ -2658,6 +2695,10 @@ export class GameWorld {
|
||||
johtoBadges: Array.from({ length: 8 }, (_, bit) =>
|
||||
draft.flags.has(OW.ENGINE_ZEPHYRBADGE + bit) ? 1 << bit : 0,
|
||||
).reduce((bits, badge) => bits | badge, 0),
|
||||
// wKantoBadges: ENGINE_BOULDERBADGE follows the Johto badges.
|
||||
kantoBadges: Array.from({ length: 8 }, (_, bit) =>
|
||||
draft.flags.has(OW.ENGINE_ZEPHYRBADGE + 8 + bit) ? 1 << bit : 0,
|
||||
).reduce((bits, badge) => bits | badge, 0),
|
||||
timeOfDay: this.clockTimeOfDay,
|
||||
random: () => this.random() & 0xff,
|
||||
wild: effect.wild,
|
||||
@@ -2694,7 +2735,9 @@ export class GameWorld {
|
||||
saving: Number(draft.flags.has(OW.ENGINE_MOM_SAVING_MONEY)),
|
||||
money: draft.momsMoney,
|
||||
},
|
||||
shift: shiftBattleStyle(draft.options),
|
||||
// RunBattleTowerTrainer sets BATTLE_SHIFT: the SET style.
|
||||
shift: tower ? false : shiftBattleStyle(draft.options),
|
||||
battleTower: Boolean(tower),
|
||||
battleScene: battleSceneOn(draft.options),
|
||||
unown: draft,
|
||||
// wUnlockedUnowns: the ENGINE_UNLOCKED_UNOWNS_* flags' bits.
|
||||
@@ -4576,6 +4619,7 @@ export class GameWorld {
|
||||
timers: structuredClone(state.timers),
|
||||
luckyNumber: { ...state.luckyNumber },
|
||||
magikarpRecord: { ...state.magikarpRecord },
|
||||
battleTower: copyBattleTowerState(state.battleTower),
|
||||
roamMons: structuredClone(state.roamMons),
|
||||
roamMonsMap: state.roamMonsMap,
|
||||
options: { ...state.options },
|
||||
@@ -4737,6 +4781,7 @@ export class GameWorld {
|
||||
!integer(save.magikarpRecord.inches, 0, 0xff) ||
|
||||
typeof save.magikarpRecord.holder !== "string" ||
|
||||
save.magikarpRecord.holder.length > 10 ||
|
||||
!validBattleTower(this.assets.pokemon, save.battleTower) ||
|
||||
!Array.isArray(save.roamMons) ||
|
||||
save.roamMons.length !== 3 ||
|
||||
!save.roamMons.every(validRoamMon) ||
|
||||
@@ -4839,6 +4884,7 @@ export class GameWorld {
|
||||
timers: structuredClone(save.timers),
|
||||
luckyNumber: { ...save.luckyNumber },
|
||||
magikarpRecord: { ...save.magikarpRecord },
|
||||
battleTower: copyBattleTowerState(save.battleTower),
|
||||
roamMons: structuredClone(save.roamMons),
|
||||
roamMonsMap: save.roamMonsMap,
|
||||
options: { ...save.options },
|
||||
@@ -5001,6 +5047,9 @@ export function migrateSave(
|
||||
...(!Object.hasOwn(save, "magikarpRecord") && {
|
||||
magikarpRecord: initialMagikarpRecord(),
|
||||
}),
|
||||
...(!Object.hasOwn(save, "battleTower") && {
|
||||
battleTower: emptyBattleTowerState(),
|
||||
}),
|
||||
...(!Object.hasOwn(save, "roamMons") && {
|
||||
roamMons: emptyRoamMons(),
|
||||
roamMonsMap: null,
|
||||
@@ -5065,6 +5114,7 @@ export function migrateSave(
|
||||
timers: {},
|
||||
luckyNumber: { id: 0, day: 0 },
|
||||
magikarpRecord: initialMagikarpRecord(),
|
||||
battleTower: emptyBattleTowerState(),
|
||||
roamMons: emptyRoamMons(),
|
||||
roamMonsMap: null,
|
||||
intro: false,
|
||||
|
||||
Reference in new issue
Block a user