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
@@ -8,7 +8,8 @@ import {
|
||||
restoredPP,
|
||||
} from "../battle/battle";
|
||||
import { receiveItem, removeFromSlot, type ItemSlot } from "../party/bag";
|
||||
import { maxPP, type PartyMon } from "../party/pokemon";
|
||||
import { calcStats, maxPP, type PartyMon } from "../party/pokemon";
|
||||
import { PartyEvolution, evoStoneCompatible } from "./evolution";
|
||||
import {
|
||||
copyScriptState,
|
||||
type MartEntry,
|
||||
@@ -86,6 +87,10 @@ export interface StartMenuHost {
|
||||
/** BikeFunction: the script that getting on or off the Bicycle queues
|
||||
* here, or undefined when .CheckEnvironment refuses it. */
|
||||
bicycleScript(state: ScriptState): number | undefined;
|
||||
/** wTimeOfDay: morning 0, day 1, night 2. */
|
||||
timeOfDay(): number;
|
||||
/** GetWorldMapLocation for the current map (LevelUpHappinessMod). */
|
||||
landmark(): number;
|
||||
}
|
||||
|
||||
/** The overworld start menu (engine/menus/start_menu.asm) with its
|
||||
@@ -269,7 +274,11 @@ export class StartMenuRun {
|
||||
|
||||
/** The party menu with PlacePartyMenuText's prompt. Returns the party
|
||||
* index, or -1 when cancelled. `from` marks SwitchPartyMons' source. */
|
||||
private *partyMenu(action: number, from?: number): Gen<number> {
|
||||
private *partyMenu(
|
||||
action: number,
|
||||
from?: number,
|
||||
item = 0,
|
||||
): Gen<number> {
|
||||
const r = this.reader,
|
||||
entry = MENU.PartyMenuStrings + action * 2;
|
||||
const prompt = this.string(r.near(entry, r.bank(entry)));
|
||||
@@ -277,9 +286,17 @@ export class StartMenuRun {
|
||||
const choice = yield* this.menu(
|
||||
[prompt],
|
||||
party.map((mon, slot) => (slot === from ? `▷${mon.nickname}` : mon.nickname)),
|
||||
party.map(
|
||||
(mon) =>
|
||||
`:L${mon.level} HP ${mon.hp}/${mon.stats[0]}${mon.hp ? statusName(mon.status) : " FNT"}`,
|
||||
party.map((mon) =>
|
||||
// PlacePartyMonEvoStoneCompatibility replaces the HP bar.
|
||||
action === C.PARTYMENUACTION_EVO_STONE
|
||||
? mon.species === EGG
|
||||
? ""
|
||||
: this.string(
|
||||
evoStoneCompatible(r, mon.species, item)
|
||||
? MENU["PlacePartyMonEvoStoneCompatibility.string_able"]
|
||||
: MENU["PlacePartyMonEvoStoneCompatibility.string_not_able"],
|
||||
)
|
||||
: `:L${mon.level} HP ${mon.hp}/${mon.stats[0]}${mon.hp ? statusName(mon.status) : " FNT"}`,
|
||||
),
|
||||
// InitPartyMenuWithCancel: the last row chosen, if still listed.
|
||||
this.cursors.party >= 1 && this.cursors.party <= party.length
|
||||
@@ -698,6 +715,12 @@ export class StartMenuRun {
|
||||
}
|
||||
case BATTLE.RestorePPEffect:
|
||||
return yield* this.restorePPEffect(use);
|
||||
case MENU.RareCandyEffect:
|
||||
return yield* this.rareCandyEffect(use);
|
||||
case MENU.VitaminEffect:
|
||||
return yield* this.vitaminEffect(use);
|
||||
case MENU.EvoStoneEffect:
|
||||
return yield* this.evoStoneEffect(use);
|
||||
case MENU.BicycleEffect: {
|
||||
const script = this.host.bicycleScript(this.state);
|
||||
if (script === undefined) return false;
|
||||
@@ -712,8 +735,11 @@ export class StartMenuRun {
|
||||
|
||||
/** UseItem_SelectMon: the party menu for an item. The party index, or
|
||||
* -1 when cancelled or an Egg was chosen. */
|
||||
private *useItemSelectMon(): Gen<number> {
|
||||
const slot = yield* this.partyMenu(PARTYMENUACTION_HEALING_ITEM);
|
||||
private *useItemSelectMon(
|
||||
action: number = PARTYMENUACTION_HEALING_ITEM,
|
||||
item = 0,
|
||||
): Gen<number> {
|
||||
const slot = yield* this.partyMenu(action, undefined, item);
|
||||
if (slot < 0) return -1;
|
||||
if (this.state.party[slot].species === EGG) {
|
||||
yield* this.print(BATTLE.ItemCantUseOnEggText);
|
||||
@@ -825,6 +851,109 @@ export class StartMenuRun {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** Evolution and move learning on this menu's draft. */
|
||||
private evolution() {
|
||||
return new PartyEvolution(this.state, this.host);
|
||||
}
|
||||
|
||||
/** RareCandyEffect: one level at the level's minimum experience, the
|
||||
* stats (and HP, even of a fainted Pokémon) raised, the level-up
|
||||
* happiness, new moves and a possible evolution. */
|
||||
private *rareCandyEffect(use: FieldItemUse): Gen<boolean> {
|
||||
const data = this.data;
|
||||
const target = yield* this.useItemSelectMon();
|
||||
if (target < 0) return false;
|
||||
const mon = this.state.party[target];
|
||||
this.strings[RAM.stringBuffer1] = mon.nickname;
|
||||
if (mon.level >= C.MAX_LEVEL) {
|
||||
yield* this.print(BATTLE.ItemWontHaveEffectText);
|
||||
return false;
|
||||
}
|
||||
const level = ++mon.level;
|
||||
mon.exp = data.expAtLevel(data.baseData(mon.species).growthRate, level);
|
||||
const oldMax = mon.stats[0];
|
||||
mon.stats = calcStats(data, mon.species, mon.dvs, level, mon.statExp);
|
||||
mon.hp = (mon.hp + mon.stats[0] - oldMax) & 0xffff;
|
||||
// LevelUpHappinessMod: more on the map where it was caught.
|
||||
this.changeHappiness(
|
||||
target,
|
||||
(mon.caughtData & 0x7f) === this.host.landmark()
|
||||
? C.HAPPINESS_GAINLEVELATHOME
|
||||
: C.HAPPINESS_GAINLEVEL,
|
||||
);
|
||||
this.numbers[RAM.curPartyLevel] = level;
|
||||
yield* this.itemActionText(target, C.PARTYMENUTEXT_LEVEL_UP);
|
||||
// PrintTempMonStats: Attack, Defense, Sp. Atk, Sp. Def and Speed.
|
||||
const labels = readString(
|
||||
this.reader,
|
||||
BATTLE["PrintTempMonStats.StatNames"],
|
||||
this.text(),
|
||||
64,
|
||||
)
|
||||
.split("\n")
|
||||
.filter(Boolean);
|
||||
const values = [1, 2, 4, 5, 3].map((stat) => mon.stats[stat]);
|
||||
yield {
|
||||
kind: "text",
|
||||
pages: [labels.map((label, i) => `${label} ${values[i]}`).join("\n")],
|
||||
};
|
||||
const evolution = this.evolution();
|
||||
yield* evolution.learnLevelMoves(target, level);
|
||||
// EvolvePokemon, not forced: a level or happiness evolution.
|
||||
yield* evolution.evolveAfterBattle(1 << target, false, use.item);
|
||||
this.useDisposableItem(use);
|
||||
return true;
|
||||
}
|
||||
|
||||
/** VitaminEffect: ten more in the high byte of a stat's experience, up
|
||||
* to 100, then CalcMonStats (the current HP stays). */
|
||||
private *vitaminEffect(use: FieldItemUse): Gen<boolean> {
|
||||
const r = this.reader;
|
||||
const target = yield* this.useItemSelectMon();
|
||||
if (target < 0) return false;
|
||||
const mon = this.state.party[target];
|
||||
this.strings[RAM.stringBuffer1] = mon.nickname;
|
||||
// GetStatExpRelativePointer: the stat experience word for the item.
|
||||
let p = MENU.StatExpItemPointerOffsets;
|
||||
while (r.byte(p) !== use.item) p += 2;
|
||||
const offset = r.byte(p + 1),
|
||||
stat = offset >> 1;
|
||||
if (mon.statExp[stat] >> 8 >= 100) {
|
||||
yield* this.print(BATTLE.ItemWontHaveEffectText);
|
||||
return false;
|
||||
}
|
||||
mon.statExp[stat] += 10 << 8;
|
||||
mon.stats = calcStats(this.data, mon.species, mon.dvs, mon.level, mon.statExp);
|
||||
const entry = MENU.StatStrings + offset;
|
||||
this.strings[RAM.stringBuffer2] = this.string(r.near(entry, r.bank(entry)));
|
||||
yield* this.print(MENU.ItemStatRoseText);
|
||||
this.changeHappiness(target, C.HAPPINESS_USEDITEM);
|
||||
this.useDisposableItem(use);
|
||||
return true;
|
||||
}
|
||||
|
||||
/** EvoStoneEffect: a forced EvolvePokemon with the stone as wCurItem.
|
||||
* An Everstone or no matching evolution has no effect. */
|
||||
private *evoStoneEffect(use: FieldItemUse): Gen<boolean> {
|
||||
const target = yield* this.useItemSelectMon(
|
||||
C.PARTYMENUACTION_EVO_STONE,
|
||||
use.item,
|
||||
);
|
||||
if (target < 0) return false;
|
||||
if (this.state.party[target].item === C.EVERSTONE) {
|
||||
yield* this.print(BATTLE.ItemWontHaveEffectText);
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
!(yield* this.evolution().evolveAfterBattle(1 << target, true, use.item))
|
||||
) {
|
||||
yield* this.print(BATTLE.ItemWontHaveEffectText);
|
||||
return false;
|
||||
}
|
||||
this.useDisposableItem(use);
|
||||
return true;
|
||||
}
|
||||
|
||||
/** RestorePPEffect for Ethers, Elixers, the MysteryBerry and PP Up. B on
|
||||
* the move list goes back to the party menu. */
|
||||
private *restorePPEffect(use: FieldItemUse): Gen<boolean> {
|
||||
|
||||
Reference in new issue
Block a user