Port field moves from the menu, SEL with Select, and moving Pokémon in Bill's PC
Checks / web (push) Canceled after 0s
Checks / web (push) Canceled after 0s
Field moves chosen in the party menu run their original checks and the ROM's from-menu scripts (Cut, Surf, Strength, Flash, Whirlpool, Waterfall, Sweet Scent), and Softboiled and Milk Drink share HP. SEL registers a key item that Select then uses, replacing the Key Items shortcut, and Bill's PC moves Pokémon between the party and every box. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
45275ba83b
commit
3456aa4e11
25 files changed
+1129
-186
No files matched your search
+129
-7
@@ -1,14 +1,23 @@
|
||||
import type { RomReader } from "../rom/reader";
|
||||
import type { PokemonData } from "../rom/pokemon";
|
||||
import { ENGINE_FLAGS, PC, RAM } from "../rom/profile";
|
||||
import { ENGINE_FLAGS, MENU, PC, RAM } from "../rom/profile";
|
||||
import { receiveItem, removeFromSlot, type ItemSlot } from "../party/bag";
|
||||
import { BOX_NAME_PATTERN, PARTY_LENGTH } from "../party/pokemon";
|
||||
import {
|
||||
BOX_NAME_PATTERN,
|
||||
PARTY_LENGTH,
|
||||
type PartyMon,
|
||||
} from "../party/pokemon";
|
||||
import {
|
||||
MONS_PER_BOX,
|
||||
NUM_BOXES,
|
||||
bufferMon,
|
||||
currentBox,
|
||||
depositMon,
|
||||
receivePCItem,
|
||||
restorePP,
|
||||
toBoxMon,
|
||||
withdrawnMon,
|
||||
type BoxMon,
|
||||
} from "../party/storage";
|
||||
import { readString, readText, type TextContext } from "./text";
|
||||
import type { MartEntry, ScriptEffect, ScriptState } from "./interpreter";
|
||||
@@ -422,15 +431,128 @@ export class PcSession {
|
||||
if (choice === BILLSPC_WITHDRAW) yield* this.withdrawMons();
|
||||
else if (choice === BILLSPC_DEPOSIT) yield* this.depositMons();
|
||||
else if (choice === BILLSPC_CHANGE_BOX) yield* this.changeBox();
|
||||
else if (choice === BILLSPC_MOVE)
|
||||
yield {
|
||||
kind: "notice",
|
||||
text: "Moving Pokémon between boxes is not ported yet.",
|
||||
};
|
||||
else if (choice === BILLSPC_MOVE) yield* this.movePKMNMenu();
|
||||
else return;
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------ MOVE PKMN W/O MAIL
|
||||
|
||||
/** BillsPC_MovePKMNMenu: refused while a party Pokémon holds MAIL
|
||||
* (IsAnyMonHoldingMail), then StartMoveMonWOMail_SaveGame's question. The
|
||||
* native session saves by itself, so no save messages follow. */
|
||||
private *movePKMNMenu(): Gen {
|
||||
const r = this.host.reader;
|
||||
const mail = new Set<number>();
|
||||
for (let p = MENU.MailItems; r.byte(p) !== 0xff; p++) mail.add(r.byte(p));
|
||||
if (this.state.party.some((mon) => mail.has(mon.item)))
|
||||
return yield* this.print(PC["BillsPC_MovePKMNMenu.PCMonHoldingMailText"]);
|
||||
const ok = yield { kind: "yesno", pages: this.pages(PC.MoveMonWOMailSaveText) };
|
||||
if (ok) yield* this.moveWithoutMail();
|
||||
}
|
||||
/** wBillsPC_LoadedBox: 0 is the party, 1-14 the boxes. */
|
||||
private loadedBox(box: number): (PartyMon | BoxMon)[] {
|
||||
return box ? this.state.storage.boxes[box - 1] : this.state.party;
|
||||
}
|
||||
private loadedBoxName(box: number) {
|
||||
return box
|
||||
? this.boxName(box - 1)
|
||||
: this.string(PC["BillsPC_BoxName.PartyPKMN"]);
|
||||
}
|
||||
/** Choose the party or a box (left and right in the original). */
|
||||
private *chooseLoadedBox(prompt: string, selected: number): Gen<number> {
|
||||
const boxes = Array.from({ length: NUM_BOXES + 1 }, (_, box) => box);
|
||||
return yield* this.menu(
|
||||
[prompt],
|
||||
boxes.map((box) => this.loadedBoxName(box)),
|
||||
boxes.map((box) => `${this.loadedBox(box).length}/${box ? MONS_PER_BOX : PARTY_LENGTH}`),
|
||||
selected,
|
||||
);
|
||||
}
|
||||
/** _MovePKMNWithoutMail: choose a Pokémon in the party or a box, MOVE it
|
||||
* (STATS shows it) and choose where it goes; the list is presented per
|
||||
* box instead of with left and right. */
|
||||
private *moveWithoutMail(): Gen {
|
||||
const data = this.host.data;
|
||||
const choose = this.string(PC.PCString_ChooseaPKMN),
|
||||
where = this.string(PC.PCString_MoveToWhere),
|
||||
cancel = this.string(PC["BillsPC_RefreshTextboxes.CancelString"]),
|
||||
options = this.strings(PC["_MovePKMNWithoutMail.MenuData"] + 2, 3);
|
||||
let loaded = this.state.storage.current + 1;
|
||||
for (;;) {
|
||||
const from = yield* this.chooseLoadedBox(choose, loaded);
|
||||
if (from < 0) return;
|
||||
loaded = from;
|
||||
const list = this.loadedBox(from);
|
||||
const index = yield* this.menu(
|
||||
[`${this.loadedBoxName(from)}\n${choose}`],
|
||||
[...list.map((mon) => mon.nickname), cancel],
|
||||
this.monDetails(list),
|
||||
);
|
||||
if (index < 0 || index >= list.length) continue;
|
||||
const action = yield* this.menu([this.string(PC.PCString_WhatsUp)], options);
|
||||
if (action === 1) {
|
||||
const mon = list[index];
|
||||
yield {
|
||||
kind: "stats",
|
||||
mon: from ? bufferMon(data, mon as BoxMon) : structuredClone(mon as PartyMon),
|
||||
};
|
||||
continue;
|
||||
}
|
||||
if (action !== 0) continue;
|
||||
// BillsPC_CheckMail_PreventBlackout, even within the party.
|
||||
if (!from && !(yield* this.partyMonMayLeave(index))) continue;
|
||||
// Where: a box, then the position to insert before.
|
||||
for (;;) {
|
||||
const to = yield* this.chooseLoadedBox(where, from);
|
||||
if (to < 0) break;
|
||||
const destination = this.loadedBox(to);
|
||||
const position = yield* this.menu(
|
||||
[`${this.loadedBoxName(to)}\n${where}`],
|
||||
[...destination.map((mon) => mon.nickname), "▶"],
|
||||
this.monDetails(destination),
|
||||
destination.length,
|
||||
);
|
||||
if (position < 0) continue;
|
||||
// BillsPC_CheckSpaceInDestination
|
||||
if (
|
||||
to !== from &&
|
||||
destination.length >= (to ? MONS_PER_BOX : PARTY_LENGTH)
|
||||
) {
|
||||
yield* this.placeString(this.string(PC.PCString_TheresNoRoom));
|
||||
continue;
|
||||
}
|
||||
yield* this.placeString(
|
||||
this.string(PC["MovePKMNWithoutMail_InsertMon.Saving_LeaveOn"]),
|
||||
10,
|
||||
);
|
||||
this.insertMon(from, index, to, position);
|
||||
loaded = to;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
/** MovePKMNWithoutMail_InsertMon: remove, then insert before `position`
|
||||
* (one earlier when moving down the same list, .CheckTrivialMove). A
|
||||
* boxed Pokémon joining the party gets CalcBufferMonStats' full HP;
|
||||
* InsertPokemonIntoBox restores the PP. */
|
||||
private insertMon(from: number, index: number, to: number, position: number) {
|
||||
const data = this.host.data;
|
||||
const source = this.loadedBox(from);
|
||||
const [moved] = source.splice(index, 1);
|
||||
if (from === to && index < position) position--;
|
||||
if (to) {
|
||||
const stored = from ? (moved as BoxMon) : toBoxMon(moved as PartyMon);
|
||||
restorePP(data, stored);
|
||||
this.state.storage.boxes[to - 1].splice(position, 0, stored);
|
||||
} else
|
||||
this.state.party.splice(
|
||||
position,
|
||||
0,
|
||||
from ? bufferMon(data, moved as BoxMon) : (moved as PartyMon),
|
||||
);
|
||||
}
|
||||
|
||||
/** BillsPC_CheckMail_PreventBlackout for a party Pokémon: the last one,
|
||||
* or the last that has not fainted, stays. No MAIL exists yet. */
|
||||
private *partyMonMayLeave(index: number): Gen<boolean> {
|
||||
|
||||
Reference in new issue
Block a user