Port Repels and the other non-fishing field items
Repel, Super Repel and Max Repel count down 100, 200 or 250 steps where CountStep does, keep weaker wild Pokémon away from the first party member that has not fainted, and print "REPEL's effect wore off." on the last step. The Coin Case and the Blue Card show their balances, Sacred Ash revives the party once one Pokémon has fainted, and the Normal and Gorgeous Boxes send their trophy home. The Itemfinder looks for unfound hidden items on the original's screen around the player. The Card Key, the Basement Key and the Squirtbottle run the original's checks for their doors and Sudowoodo, whose maps are not open yet. Saves gain the Repel's steps, the coins and the Blue Card's points. Part of #27 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
10e1560212
commit
4cb4676c7d
13 files changed
+821
-21
No files matched your search
@@ -24,6 +24,7 @@ import { TrainerCard } from "./trainer-card";
|
||||
import { OptionsMenu, menuAccount } from "./options";
|
||||
import { flyMap } from "./fly-map";
|
||||
import { OW } from "../world/constants";
|
||||
import { DecorationData } from "../world/decorations";
|
||||
import { readString, readText, type TextContext } from "./text";
|
||||
|
||||
type Input = number | string | undefined;
|
||||
@@ -77,6 +78,18 @@ const {
|
||||
PP_UP_ONE,
|
||||
} = C;
|
||||
const NOT_PORTED = "is not ported yet.";
|
||||
/** MAP_RADIO_TOWER_3F and MAP_GOLDENROD_UNDERGROUND
|
||||
* (constants/map_constants.asm). */
|
||||
const RADIO_TOWER_3F = "3:19",
|
||||
GOLDENROD_UNDERGROUND = "3:53";
|
||||
/** wPlayerDirection's UP, as the host's facing() numbers it. */
|
||||
const FACING_UP = 1;
|
||||
const FACING_VECTORS = [
|
||||
[0, 1],
|
||||
[0, -1],
|
||||
[-1, 0],
|
||||
[1, 0],
|
||||
] as const;
|
||||
|
||||
/** Menu cursors in work RAM, which CleanUpBattleRAM clears after every
|
||||
* battle: wBattleMenuCursorPosition (the start menu's 1-based position,
|
||||
@@ -129,6 +142,8 @@ export interface StartMenuHost {
|
||||
landmarkOf(mapId: string): number;
|
||||
/** wGameTime, for the trainer card. */
|
||||
gameTime(): GameTime;
|
||||
/** CheckForHiddenItems: an unfound hidden item on the screen. */
|
||||
hiddenItemOnScreen(state: ScriptState): boolean;
|
||||
}
|
||||
|
||||
/** The overworld start menu (engine/menus/start_menu.asm) with its
|
||||
@@ -946,11 +961,129 @@ export class StartMenuRun {
|
||||
this.deferredItem = item;
|
||||
return true;
|
||||
}
|
||||
case MENU.RepelEffect:
|
||||
case MENU.SuperRepelEffect:
|
||||
case MENU.MaxRepelEffect:
|
||||
return yield* this.useRepel(
|
||||
use,
|
||||
effect === MENU.RepelEffect
|
||||
? 100
|
||||
: effect === MENU.SuperRepelEffect
|
||||
? 200
|
||||
: 250,
|
||||
);
|
||||
case MENU.CoinCaseEffect:
|
||||
this.numbers[RAM.coins] = this.state.coins;
|
||||
yield* this.print(MENU["CoinCaseEffect.CoinCaseCountText"]);
|
||||
return true;
|
||||
case MENU.BlueCardEffect:
|
||||
this.numbers[RAM.blueCardBalance] = this.state.blueCardBalance;
|
||||
yield* this.print(MENU["BlueCardEffect.BlueCardBalanceText"]);
|
||||
return true;
|
||||
case MENU.SacredAshEffect:
|
||||
// _SacredAsh: SacredAshScript heals the party once the menu closes,
|
||||
// if a Pokémon that is not an Egg has fainted (CheckAnyFaintedMon).
|
||||
if (!this.state.party.some((mon) => mon.species !== EGG && !mon.hp))
|
||||
return false;
|
||||
this.useDisposableItem(use);
|
||||
this.deferred = MENU.SacredAshScript;
|
||||
this.deferredItem = item;
|
||||
return true;
|
||||
case MENU.ItemfinderEffect:
|
||||
// ItemFinder: one script or the other, and always a success.
|
||||
this.deferred = this.host.hiddenItemOnScreen(this.state)
|
||||
? OVERWORLD["ItemFinder.Script_FoundSomething"]
|
||||
: OVERWORLD["ItemFinder.Script_FoundNothing"];
|
||||
this.deferredItem = item;
|
||||
return true;
|
||||
case MENU.CardKeyEffect:
|
||||
// _CardKey: facing up at the slot on Radio Tower 3F.
|
||||
return this.useKey(
|
||||
item,
|
||||
RADIO_TOWER_3F,
|
||||
{ x: 18, y: 6 },
|
||||
OVERWORLD["_CardKey.CardKeyScript"],
|
||||
FACING_UP,
|
||||
);
|
||||
case MENU.BasementKeyEffect:
|
||||
// _BasementKey: facing the door in the Goldenrod Underground.
|
||||
return this.useKey(
|
||||
item,
|
||||
GOLDENROD_UNDERGROUND,
|
||||
{ x: 22, y: 10 },
|
||||
OVERWORLD["_BasementKey.BasementKeyScript"],
|
||||
);
|
||||
case MENU.SquirtbottleEffect:
|
||||
// _Squirtbottle: its script checks for Sudowoodo.
|
||||
this.deferred = OVERWORLD["_Squirtbottle.SquirtbottleScript"];
|
||||
this.deferredItem = item;
|
||||
return true;
|
||||
case MENU.NormalBoxEffect:
|
||||
case MENU.GorgeousBoxEffect:
|
||||
return yield* this.openBox(
|
||||
use,
|
||||
effect === MENU.NormalBoxEffect
|
||||
? C.DECOFLAG_SILVER_TROPHY_DOLL
|
||||
: C.DECOFLAG_GOLD_TROPHY_DOLL,
|
||||
);
|
||||
}
|
||||
yield* this.notice(`Using ${this.data.itemName(item)} ${NOT_PORTED}`);
|
||||
return "unported";
|
||||
}
|
||||
|
||||
/** _CardKey and _BasementKey: the key's script runs once the menu closes
|
||||
* if the player faces `tile` (in wPlayerMapX/Y coordinates, which count
|
||||
* the map's border) on `mapId`, and `facing` if given. */
|
||||
private useKey(
|
||||
item: number,
|
||||
mapId: string,
|
||||
tile: { x: number; y: number },
|
||||
script: number,
|
||||
facing?: number,
|
||||
): boolean {
|
||||
const host = this.host.scriptHost(),
|
||||
direction = host.facing(),
|
||||
[dx, dy] = FACING_VECTORS[direction],
|
||||
{ x, y } = host.position();
|
||||
if (
|
||||
host.mapId !== mapId ||
|
||||
(facing !== undefined && direction !== facing) ||
|
||||
x + dx + 4 !== tile.x ||
|
||||
y + dy + 4 !== tile.y
|
||||
)
|
||||
return false;
|
||||
this.deferred = script;
|
||||
this.deferredItem = item;
|
||||
return true;
|
||||
}
|
||||
/** UseRepel: a Repel for `steps` steps, unless one is in effect. */
|
||||
private *useRepel(use: FieldItemUse, steps: number): Gen<boolean> {
|
||||
if (this.state.repel) {
|
||||
yield* this.print(MENU.RepelUsedEarlierIsStillInEffectText);
|
||||
return false;
|
||||
}
|
||||
this.state.repel = steps;
|
||||
yield* this.useItemText(use);
|
||||
return true;
|
||||
}
|
||||
/** UseItemText: "<PLAYER> used the …", then UseDisposableItem. */
|
||||
private *useItemText(use: FieldItemUse): Gen {
|
||||
yield* this.print(BATTLE.ItemUsedText);
|
||||
this.useDisposableItem(use);
|
||||
}
|
||||
/** OpenBox: SetSpecificDecorationFlag gives the trophy doll, which the
|
||||
* player sends home. */
|
||||
private *openBox(use: FieldItemUse, flag: number): Gen<boolean> {
|
||||
const r = this.reader,
|
||||
decoration = r.byte(OVERWORLD.DecorationIDs + flag);
|
||||
this.state.events.add(
|
||||
new DecorationData(r, this.data).attribute(decoration).event,
|
||||
);
|
||||
yield* this.print(MENU["OpenBox.SentTrophyHomeText"]);
|
||||
this.useDisposableItem(use);
|
||||
return true;
|
||||
}
|
||||
|
||||
/** UseItem_SelectMon: the party menu for an item. The party index, or
|
||||
* -1 when cancelled or an Egg was chosen. */
|
||||
private *useItemSelectMon(
|
||||
|
||||
Reference in new issue
Block a user