mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-12 08:21:02 +02:00
1312 lines
66 KiB
Lua
1312 lines
66 KiB
Lua
-- Hand-ported story-critical scripts, one table per map, all using real
|
|
-- extracted text. Each cites its pokered source. Registered in
|
|
-- data/scripts/init.lua.
|
|
|
|
local M = {}
|
|
|
|
-- -------------------------------------------------------------------
|
|
-- Oak's Parcel chain (scripts/ViridianMart.asm, OaksLab.asm,
|
|
-- ViridianCity.asm)
|
|
-- -------------------------------------------------------------------
|
|
|
|
M.VIRIDIAN_MART = {
|
|
-- scripts/ViridianMart.asm: the parcel hand-off is the map's DEFAULT
|
|
-- script, not a talk. Entering with a starter and no parcel runs
|
|
-- ViridianMartDefaultScript -- the clerk calls out, then
|
|
-- StartSimulatingJoypadStates walks the player to the counter
|
|
-- (.PlayerMovement: PAD_LEFT 1, PAD_UP 2, door (3,7) -> counter (2,5))
|
|
-- and ViridianMartOaksParcelScript hands the parcel over. The player
|
|
-- never presses A.
|
|
--
|
|
-- The RLE list is consumed back to front: DecodeRLEList fills forward
|
|
-- from wSimulatedJoypadStatesEnd, the script seeds wSimulatedJoypadStates-
|
|
-- Index with the count, and home/overworld.asm decrements that index
|
|
-- before each read. So PAD_LEFT 1, PAD_UP 2 actually plays up, up, left,
|
|
-- and the walk ends facing the clerk. Replaying the list front to back
|
|
-- put the player at the counter facing up instead (#235). The player
|
|
-- never presses A. This matters beyond convenience: the parcel gates
|
|
-- Oak's Pokedex and the old man clearing Route 2, so vanilla guarantees
|
|
-- it on entry rather than letting you walk out without it.
|
|
--
|
|
-- The talk branch below is kept as the fallback for a save that reaches
|
|
-- the counter without this having fired.
|
|
onEnter = function(game, ow)
|
|
local f = game.save.flags
|
|
if f.EVENT_OAK_GOT_PARCEL or f.EVENT_GOT_OAKS_PARCEL then return end
|
|
if not f.EVENT_GOT_STARTER then return end
|
|
ow:queueScript({
|
|
{ "show_text", "_ViridianMartClerkYouCameFromPalletTownText" },
|
|
{ "move_player", "up", 2 },
|
|
{ "move_player", "left", 1 },
|
|
-- the quest text's last page is "{PLAYER} got\nOAK's PARCEL!"
|
|
{ "give_item", "OAKS_PARCEL", 1, "_ViridianMartClerkParcelQuestText" },
|
|
{ "set_flag", "EVENT_GOT_OAKS_PARCEL" },
|
|
})
|
|
end,
|
|
talk = {
|
|
TEXT_VIRIDIANMART_CLERK = {
|
|
{ "check_flag", "EVENT_OAK_GOT_PARCEL" }, -- 1
|
|
{ "jump_if_true", 11 }, -- 2
|
|
{ "check_flag", "EVENT_GOT_OAKS_PARCEL" }, -- 3
|
|
{ "jump_if_true", 13 }, -- 4
|
|
{ "check_flag", "EVENT_GOT_STARTER" }, -- 5
|
|
{ "jump_if_false", 11 }, -- 6
|
|
{ "show_text", "_ViridianMartClerkYouCameFromPalletTownText" }, -- 7
|
|
-- the parcel-quest text's last page is "{PLAYER} got\nOAK's
|
|
-- PARCEL!" (scripts/ViridianMart.asm, sound_get_key_item)
|
|
{ "give_item", "OAKS_PARCEL", 1, "_ViridianMartClerkParcelQuestText" }, -- 8
|
|
{ "set_flag", "EVENT_GOT_OAKS_PARCEL" }, -- 9
|
|
{ "jump", 14 }, -- 10
|
|
{ "open_mart", "TEXT_VIRIDIANMART_CLERK" }, -- 11
|
|
{ "jump", 14 }, -- 12
|
|
{ "show_text", "_ViridianMartClerkSayHiToOakText" }, -- 13
|
|
},
|
|
},
|
|
}
|
|
|
|
M.VIRIDIAN_CITY = {
|
|
talk = {
|
|
-- The GAMBLER_ASLEEP at (18,9) (ViridianCityOldManSleepyText): he
|
|
-- only ever grumbles and shoves you back down -- he never wakes,
|
|
-- moves or hides. The coffee ask and the catch tutorial belong to
|
|
-- the *other* old man, the walking SPRITE_GAMBLER at (17,5)
|
|
-- (TEXT_VIRIDIANCITY_OLD_MAN, below). The two are swapped by the
|
|
-- Pokédex in data/scripts/oaks_lab.lua, not by talking to either.
|
|
TEXT_VIRIDIANCITY_OLD_MAN_SLEEPY = {
|
|
{ "show_text", "_ViridianCityOldManSleepyPrivatePropertyText" }, -- 1
|
|
{ "move_player", "down", 1 }, -- 2
|
|
},
|
|
|
|
-- The walking old man at (17,5), shown once the Pokédex swaps him in
|
|
-- (ViridianCityOldManText). "Are you in a hurry?" -- YES brushes you
|
|
-- off, NO leads into the catch tutorial: he explains, demos a catch
|
|
-- on a wild WEEDLE (BATTLE_TYPE_OLD_MAN), then comments afterwards.
|
|
-- pokered prints YouNeedToWeakenTheTarget *after* the demo battle
|
|
-- (ViridianCityOldManEndCatchTrainingScript), not before it.
|
|
TEXT_VIRIDIANCITY_OLD_MAN = {
|
|
{ "face_player" }, -- 1
|
|
{ "ask", "_ViridianCityOldManHadMyCoffeeNowText" }, -- 2
|
|
{ "jump_if_true", 8 }, -- 3 (yes = in a hurry)
|
|
{ "show_text", "_ViridianCityOldManKnowHowToCatchPokemonText" }, -- 4
|
|
{ "old_man_demo" }, -- 5
|
|
{ "show_text", "_ViridianCityOldManYouNeedToWeakenTheTargetText" },-- 6
|
|
{ "jump", 9 }, -- 7
|
|
{ "show_text", "_ViridianCityOldManTimeIsMoneyText" }, -- 8 (9 = end)
|
|
},
|
|
},
|
|
-- Re-apply the Pokedex old-man swap for a save that already holds the flag
|
|
-- but was never standing here when it fired: a vanilla .sav imported
|
|
-- through src/save_convert, whose codec does not model pokered's
|
|
-- wToggleableObjectFlags array, so save.objectToggles arrives empty and
|
|
-- both objects fall back to their compiled-in defaults -- the sleeper ON
|
|
-- (data/maps/toggleable_objects.asm toggleable_objects_for VIRIDIAN_CITY),
|
|
-- still lying across the north path (#234).
|
|
-- OaksLabOakGivesPokedexScript (scripts/OaksLab.asm) sets EVENT_GOT_POKEDEX
|
|
-- and, with no branch in between, runs HideObject TOGGLE_LYING_OLD_MAN /
|
|
-- ShowObject TOGGLE_OLD_MAN, so that one flag settles both toggles exactly.
|
|
-- Same shape as M.OAKS_LAB.onEnter in data/scripts/oaks_lab.lua, which
|
|
-- re-applies the same script's other two HideObjects on entry (#106).
|
|
onEnter = function(game, ow)
|
|
if not (game.save.flags and game.save.flags.EVENT_GOT_POKEDEX) then
|
|
return
|
|
end
|
|
local Commands = require("src.script.Commands")
|
|
local ctx = { save = game.save, game = game, overworld = ow }
|
|
Commands.hide_object(ctx, "VIRIDIAN_CITY", "VIRIDIANCITY_OLD_MAN_SLEEPY")
|
|
Commands.show_object(ctx, "VIRIDIAN_CITY", "VIRIDIANCITY_OLD_MAN")
|
|
end,
|
|
-- ViridianCityCheckGotPokedexScript: the north corridor is gated on
|
|
-- EVENT_GOT_POKEDEX, NOT on the sleeper being hidden, and it triggers
|
|
-- on exactly one cell -- (19,9), the gap east of the sleeper (18,9)
|
|
-- and the girl (17,9). With the Pokédex the check returns immediately
|
|
-- and you simply walk past at x=19.
|
|
onStep = function(game, ow, x, y)
|
|
if game.save.flags and game.save.flags.EVENT_GOT_POKEDEX then return false end
|
|
if x ~= 19 or y ~= 9 then return false end
|
|
local TextBox = require("src.render.TextBox")
|
|
game.stack:push(TextBox.new(game,
|
|
game.data.text._ViridianCityOldManSleepyPrivatePropertyText
|
|
or "You can't go\nthrough here!\fThis is private\nproperty!",
|
|
function() ow:scriptMove(ow.player, "down", 1) end))
|
|
return true
|
|
end,
|
|
}
|
|
|
|
-- -------------------------------------------------------------------
|
|
-- Bill's SS ticket (scripts/BillsHouse.asm; the cell-separation cutscene
|
|
-- is compressed into the dialogue)
|
|
-- -------------------------------------------------------------------
|
|
|
|
-- Daisy hands over the TOWN MAP once Oak's errand is under way
|
|
-- (scripts/BluesHouse.asm BluesHouseDaisySittingText)
|
|
M.BLUES_HOUSE = {
|
|
talk = {
|
|
TEXT_BLUESHOUSE_DAISY_SITTING = {
|
|
{ "face_player" }, -- 1
|
|
{ "check_flag", "EVENT_GOT_TOWN_MAP" }, -- 2
|
|
{ "jump_if_true", 10 }, -- 3
|
|
{ "check_flag", "EVENT_GOT_STARTER" }, -- 4
|
|
{ "jump_if_false", 12 }, -- 5
|
|
{ "show_text", "_BluesHouseDaisyOfferMapText" }, -- 6
|
|
-- _GotMapText: "{PLAYER} got a\n{RAM:wStringBuffer}!" -- the
|
|
-- buffer supplies "TOWN MAP" (scripts/BluesHouse.asm GotMapText)
|
|
{ "give_item", "TOWN_MAP", 1, "_GotMapText" }, -- 7
|
|
{ "set_flag", "EVENT_GOT_TOWN_MAP" }, -- 8
|
|
{ "jump", 13 }, -- 9
|
|
{ "show_text", "_BluesHouseDaisyUseMapText" }, -- 10
|
|
{ "jump", 13 }, -- 11
|
|
{ "show_text", "_BluesHouseDaisyRivalAtLabText" }, -- 12
|
|
},
|
|
},
|
|
}
|
|
|
|
M.BILLS_HOUSE = {
|
|
talk = {
|
|
-- BillsHouseBillPokemonText: "I'm not a POKéMON!", a YES/NO choice
|
|
-- (NO only adds "No, you gotta help!" before rejoining the YES
|
|
-- path), then the "get in the TELEPORTER" line and the monster
|
|
-- walking into the cell-separator machine
|
|
-- (BillsHousePokemonWalkToMachineScript: up 3, or around the player
|
|
-- when they stand in the way facing down), where it is hidden and
|
|
-- EVENT_BILL_SAID_USE_CELL_SEPARATOR arms the PC at (1,4) -- see
|
|
-- OverworldState.billsHousePC for the separator itself.
|
|
TEXT_BILLSHOUSE_BILL_POKEMON = function(game, ow, npc, done)
|
|
local TextBox = require("src.render.TextBox")
|
|
local t = game.data.text
|
|
local function toMachine()
|
|
game.stack:push(TextBox.new(game,
|
|
t._BillsHouseBillUseSeparationSystemText
|
|
or "When I'm in the\nTELEPORTER, run\nthe Cell\nSeparation System!", function()
|
|
local function entered()
|
|
local Commands = require("src.script.Commands")
|
|
Commands.hide_object({ game = game, save = game.save,
|
|
overworld = ow },
|
|
"BILLS_HOUSE", "BILLSHOUSE_BILL_POKEMON")
|
|
game.save.flags.EVENT_BILL_SAID_USE_CELL_SEPARATOR = true
|
|
require("src.world.PikachuFollower").onBillEnteredMachine(game, ow)
|
|
done()
|
|
end
|
|
if ow.player.facing == "down" then
|
|
-- the player is standing on his straight path: walk around
|
|
-- (.PokemonWalkAroundPlayerMovement). BillsHouseScript2 runs
|
|
-- BillsHousePikachuWatchPlayer first on this branch, so a
|
|
-- Pikachu that is still following steps clear of Bill's detour
|
|
-- and turns to watch the player (#455).
|
|
require("src.world.PikachuFollower")
|
|
.onBillWalksAroundPlayer(game, ow)
|
|
ow:scriptMove(npc, "right", 1, function()
|
|
ow:scriptMove(npc, "up", 2, function()
|
|
ow:scriptMove(npc, "left", 1, function()
|
|
ow:scriptMove(npc, "up", 1, entered)
|
|
end)
|
|
end)
|
|
end)
|
|
else
|
|
ow:scriptMove(npc, "up", 3, entered)
|
|
end
|
|
end))
|
|
end
|
|
game.stack:push(TextBox.new(game,
|
|
t._BillsHouseBillImNotAPokemonText or "Hey! I'm not a\nPOKéMON!",
|
|
nil, { choice = function(yes)
|
|
if yes then
|
|
toMachine()
|
|
else
|
|
game.stack:push(TextBox.new(game,
|
|
t._BillsHouseBillNoYouGottaHelpText
|
|
or "No! You gotta\nhelp me!", toMachine))
|
|
end
|
|
end }))
|
|
end,
|
|
|
|
-- BillsHouseBillSSTicketText (human Bill after the separation)
|
|
TEXT_BILLSHOUSE_BILL_SS_TICKET = {
|
|
{ "face_player" }, -- 1
|
|
{ "check_flag", "EVENT_GOT_SS_TICKET" }, -- 2
|
|
{ "jump_if_true", 12 }, -- 3
|
|
{ "show_text", "_BillsHouseBillThankYouText" }, -- 4
|
|
-- pokered gives first (GiveItem fills wStringBuffer), then prints
|
|
-- the received text that reads it (scripts/BillsHouse.asm; the
|
|
-- item id is S_S_TICKET in generated items.lua -- keyItem, so the
|
|
-- sound_get_key_item jingle plays like BillsHouse.asm:196)
|
|
{ "give_item", "S_S_TICKET", 1, false }, -- 5
|
|
{ "show_text", "_SSTicketReceivedText" }, -- 6
|
|
{ "set_flag", "EVENT_GOT_SS_TICKET" }, -- 7
|
|
-- The two Cerulean guards are a SWAP PAIR, not scenery
|
|
-- (BillsHouse.asm:174-178): handing over the ticket shows GUARD1 at
|
|
-- (28,12) and hides GUARD2 at (27,12). This matters far more than it
|
|
-- looks: (27,12) is the ONLY walkable neighbour of the trashed
|
|
-- house's south door at (27,11), and that house is one of the two
|
|
-- ways through the fence that splits Cerulean in half (the badge
|
|
-- house is the other). Leaving GUARD2 up forever severs the city --
|
|
-- the gym/mart half can never reach the Route 5 exit.
|
|
-- Same swap fires after the TM28 Rocket (CeruleanCity_2.asm
|
|
-- CeruleanHideRocket), so either route opens the path.
|
|
{ "show_object", "CERULEAN_CITY", "CERULEANCITY_GUARD1" }, -- 8
|
|
{ "hide_object", "CERULEAN_CITY", "CERULEANCITY_GUARD2" }, -- 9
|
|
{ "show_text", "_BillsHouseBillWhyDontYouGoInsteadOfMeText" }, -- 10
|
|
{ "jump", 13 }, -- 11
|
|
{ "show_text", "_BillsHouseBillWhyDontYouGoInsteadOfMeText" }, -- 12
|
|
},
|
|
|
|
TEXT_BILLSHOUSE_BILL_CHECK_OUT_MY_RARE_POKEMON = {
|
|
{ "face_player" }, -- 1
|
|
{ "show_text", "_BillsHouseBillCheckOutMyRarePokemonText" }, -- 2
|
|
},
|
|
},
|
|
-- repair saves that already got the ticket under the old collapsed
|
|
-- script (the monster never hidden, human Bill never shown)
|
|
onEnter = function(game, ow)
|
|
local flags = game.save.flags
|
|
if flags.EVENT_GOT_SS_TICKET
|
|
and not flags.EVENT_USED_CELL_SEPARATOR_ON_BILL then
|
|
local Commands = require("src.script.Commands")
|
|
local ctx = { game = game, save = game.save, overworld = ow }
|
|
Commands.hide_object(ctx, "BILLS_HOUSE", "BILLSHOUSE_BILL_POKEMON")
|
|
Commands.show_object(ctx, "BILLS_HOUSE", "BILLSHOUSE_BILL1")
|
|
flags.EVENT_BILL_SAID_USE_CELL_SEPARATOR = true
|
|
flags.EVENT_USED_CELL_SEPARATOR_ON_BILL = true
|
|
flags.EVENT_MET_BILL = true
|
|
flags.EVENT_MET_BILL_2 = true
|
|
end
|
|
-- After Route25ToggleBillsScript, BILL2 is the visible human Bill
|
|
-- ("check out my rare POKéMON"). Re-apply on enter so a mid-house
|
|
-- load still matches the toggles if the pool was rebuilt.
|
|
if flags.EVENT_LEFT_BILLS_HOUSE_AFTER_HELPING then
|
|
local Commands = require("src.script.Commands")
|
|
local ctx = { game = game, save = game.save, overworld = ow }
|
|
Commands.hide_object(ctx, "BILLS_HOUSE", "BILLSHOUSE_BILL_POKEMON")
|
|
Commands.hide_object(ctx, "BILLS_HOUSE", "BILLSHOUSE_BILL1")
|
|
Commands.show_object(ctx, "BILLS_HOUSE", "BILLSHOUSE_BILL2")
|
|
end
|
|
if not flags.EVENT_MET_BILL_2 then
|
|
require("src.world.PikachuFollower").onBillsHouseEnter(game, ow)
|
|
end
|
|
end,
|
|
}
|
|
|
|
-- -------------------------------------------------------------------
|
|
-- Route 25 outside Bill's house (scripts/Route25.asm
|
|
-- Route25ToggleBillsScript): leaving after the SS Ticket arms the
|
|
-- Eevee PC list and swaps human Bill to his post-help dialogue NPC.
|
|
-- Leaving mid-quest (Bill still in the machine) puts the monster back.
|
|
-- -------------------------------------------------------------------
|
|
|
|
M.ROUTE_25 = {
|
|
onEnter = function(game, ow)
|
|
local flags = game.save.flags
|
|
if flags.EVENT_LEFT_BILLS_HOUSE_AFTER_HELPING then return end
|
|
local Commands = require("src.script.Commands")
|
|
local ctx = { game = game, save = game.save, overworld = ow }
|
|
if not flags.EVENT_MET_BILL_2 then
|
|
flags.EVENT_BILL_SAID_USE_CELL_SEPARATOR = nil
|
|
Commands.show_object(ctx, "BILLS_HOUSE", "BILLSHOUSE_BILL_POKEMON")
|
|
return
|
|
end
|
|
if not flags.EVENT_GOT_SS_TICKET then return end
|
|
flags.EVENT_LEFT_BILLS_HOUSE_AFTER_HELPING = true
|
|
-- TOGGLE_NUGGET_BRIDGE_GUY (ROUTE24_COOLTRAINER_M1)
|
|
Commands.hide_object(ctx, "ROUTE_24", "ROUTE24_COOLTRAINER_M1")
|
|
Commands.hide_object(ctx, "BILLS_HOUSE", "BILLSHOUSE_BILL1")
|
|
Commands.show_object(ctx, "BILLS_HOUSE", "BILLSHOUSE_BILL2")
|
|
end,
|
|
}
|
|
|
|
-- -------------------------------------------------------------------
|
|
-- SS Anne (scripts/VermilionCity.asm, SSAnne2F.asm,
|
|
-- SSAnneCaptainsRoom.asm)
|
|
-- -------------------------------------------------------------------
|
|
|
|
M.VERMILION_CITY = {
|
|
-- VermilionCity_Script .setFirstLockTrashCanIndex (scripts/
|
|
-- VermilionCity.asm): every load of this map re-rolls which Vermilion
|
|
-- Gym trash can hides the first-lock switch (Random & $0e: a random
|
|
-- EVEN can, 0-14). The roll is unconditional -- pokered does not
|
|
-- care whether the first lock is already open, because the index is
|
|
-- only read while EVENT_1ST_LOCK_OPENED is unset (the gym is only
|
|
-- reachable through this map, so a fresh visit always re-rolls).
|
|
onEnter = function(game, ow)
|
|
local puz = game.save.trashPuzzle or {}
|
|
game.save.trashPuzzle = puz
|
|
puz.first = love.math.random(0, 7) * 2
|
|
end,
|
|
-- VermilionCityDefaultScript's per-frame SSAnneTicketCheckCoords check:
|
|
-- the unguarded cell (18,30) just west of the sailor leads straight
|
|
-- onto the dock warp. Stepping onto it facing down always runs the
|
|
-- sailor dialog (DisplayTextID TEXT_VERMILIONCITY_SAILOR1); only a
|
|
-- ticket while the ship is still docked lets the player continue --
|
|
-- otherwise they are walked back up. The sailor himself never hides.
|
|
onStep = function(game, ow, x, y)
|
|
if x ~= 18 or y ~= 30 then return false end
|
|
if ow.player.facing ~= "down" then return false end
|
|
local Flags = require("src.script.Flags")
|
|
local t = game.data.text
|
|
local TextBox = require("src.render.TextBox")
|
|
local shipLeft = Flags.get(game.save, "EVENT_SS_ANNE_LEFT")
|
|
local hasTicket = (game.save.inventory.S_S_TICKET or 0) > 0
|
|
if shipLeft then
|
|
game.stack:push(TextBox.new(game,
|
|
t._VermilionCitySailor1ShipSetSailText or "The ship set sail.",
|
|
function() ow:scriptMove(ow.player, "up", 1) end))
|
|
return true
|
|
end
|
|
-- Walk-past is never facing-right / inFrontOfOrBehindGuardCoords, so
|
|
-- VermilionCitySailor1Text always takes .greet_player_and_check_ticket:
|
|
-- DoYouHaveATicket, then FlashedTicket (allow through) or YouNeedATicket
|
|
-- (walk back). Returning true while the box is up also blocks the
|
|
-- dock warp on this step.
|
|
local ask = t._VermilionCitySailor1DoYouHaveATicketText
|
|
or "Welcome to S.S.\nANNE!\fExcuse me, do you\nhave a ticket?"
|
|
if hasTicket then
|
|
game.stack:push(TextBox.new(game,
|
|
ask .. "\f"
|
|
.. (t._VermilionCitySailor1FlashedTicketText
|
|
or "{PLAYER} flashed\nthe S.S.TICKET!")))
|
|
return true
|
|
end
|
|
game.stack:push(TextBox.new(game,
|
|
ask .. "\f"
|
|
.. (t._VermilionCitySailor1YouNeedATicketText
|
|
or "You need a ticket\nto get aboard."),
|
|
function() ow:scriptMove(ow.player, "up", 1) end))
|
|
return true
|
|
end,
|
|
talk = {
|
|
-- the sailor guarding the dock gangway (VermilionCitySailor1Text):
|
|
-- flashing the ticket just lets you through -- he never hides, and
|
|
-- once the ship has sailed he only reports it gone
|
|
TEXT_VERMILIONCITY_SAILOR1 = {
|
|
{ "face_player" }, -- 1
|
|
{ "check_flag", "EVENT_SS_ANNE_LEFT" }, -- 2
|
|
{ "jump_if_true", 11 }, -- 3
|
|
{ "show_text", "_VermilionCitySailor1DoYouHaveATicketText" }, -- 4
|
|
{ "check_item", "S_S_TICKET" }, -- 5
|
|
{ "jump_if_false", 9 }, -- 6
|
|
{ "show_text", "_VermilionCitySailor1FlashedTicketText" }, -- 7
|
|
{ "jump", 12 }, -- 8
|
|
{ "show_text", "_VermilionCitySailor1YouNeedATicketText" }, -- 9
|
|
{ "jump", 12 }, -- 10
|
|
{ "show_text", "_VermilionCitySailor1ShipSetSailText" }, -- 11
|
|
},
|
|
},
|
|
}
|
|
|
|
M.SS_ANNE_2F = {
|
|
talk = {
|
|
TEXT_SSANNE2F_RIVAL = {
|
|
{ "face_player" }, -- 1
|
|
{ "check_flag", "EVENT_BEAT_SS_ANNE_RIVAL" }, -- 2
|
|
{ "jump_if_true", 9 }, -- 3
|
|
{ "show_text", "_SSAnne2FRivalText" }, -- 4
|
|
{ "rival_battle", "OPP_RIVAL2", 1 }, -- 5
|
|
{ "jump_if_false", 10 }, -- 6
|
|
{ "set_flag", "EVENT_BEAT_SS_ANNE_RIVAL" }, -- 7
|
|
{ "show_text", "_SSAnne2FRivalDefeatedText" }, -- 8
|
|
{ "jump", 10 }, -- 9 (already beaten: silent)
|
|
},
|
|
},
|
|
}
|
|
|
|
M.SS_ANNE_CAPTAINS_ROOM = {
|
|
talk = {
|
|
-- SSAnneCaptainsRoomCaptainText: after the rub line's text_asm tail,
|
|
-- pokered plays MUSIC_PKMN_HEALED (scripts/SSAnneCaptainsRoom.asm).
|
|
TEXT_SSANNECAPTAINSROOM_CAPTAIN = {
|
|
{ "check_flag", "EVENT_GOT_HM01" }, -- 1
|
|
{ "jump_if_true", 10 }, -- 2
|
|
{ "show_text", "_SSAnneCaptainsRoomRubCaptainsBackText" }, -- 3
|
|
{ "play_once", "Music_PkmnHealed" }, -- 4
|
|
{ "show_text", "_SSAnneCaptainsRoomCaptainIFeelMuchBetterText" }, -- 5
|
|
-- give-then-print like scripts/SSAnneCaptainsRoom.asm (GiveItem
|
|
-- fills wStringBuffer; the received text reads it)
|
|
{ "give_item", "HM_CUT", 1, false }, -- 6
|
|
{ "show_text", "_SSAnneCaptainsRoomCaptainReceivedHM01Text" }, -- 7
|
|
{ "set_flag", "EVENT_GOT_HM01" }, -- 8
|
|
{ "jump", 11 }, -- 9
|
|
{ "show_text", "_SSAnneCaptainsRoomCaptainNotSickAnymoreText" }, -- 10
|
|
},
|
|
},
|
|
}
|
|
|
|
-- -------------------------------------------------------------------
|
|
-- Pokémon Tower / Poké Flute (scripts/PokemonTower7F.asm,
|
|
-- MrFujisHouse.asm; the teleport back to his house is a warp)
|
|
-- -------------------------------------------------------------------
|
|
|
|
-- scripts/PokemonTower7F.asm: after each Rocket loses,
|
|
-- PokemonTower7FEndBattleScript shows its EndBattle text (EndTrainerBattle),
|
|
-- prints its AfterBattle text (DisplayTextID), then
|
|
-- PokemonTower7FRocketLeaveMovementScript walks the grunt off toward the
|
|
-- (9,16) stairs (MoveSprite) and PokemonTower7FHideNPCScript despawns it
|
|
-- (HideObject). Without this the beaten grunts stood in the corridor
|
|
-- forever (#200).
|
|
local POKEMON_TOWER_7F_ROCKETS = {
|
|
{ index = 1, name = "POKEMONTOWER7F_ROCKET1",
|
|
beat = "EVENT_BEAT_POKEMONTOWER_7_TRAINER_0",
|
|
after = "_PokemonTower7FRocket1AfterBattleText" },
|
|
{ index = 2, name = "POKEMONTOWER7F_ROCKET2",
|
|
beat = "EVENT_BEAT_POKEMONTOWER_7_TRAINER_1",
|
|
after = "_PokemonTower7FRocket2AfterBattleText" },
|
|
{ index = 3, name = "POKEMONTOWER7F_ROCKET3",
|
|
beat = "EVENT_BEAT_POKEMONTOWER_7_TRAINER_2",
|
|
after = "_PokemonTower7FRocket3AfterBattleText" },
|
|
}
|
|
|
|
-- PokemonTower7FNPCCoordMovementTable, keyed by the PLAYER's tile ("x,y")
|
|
-- at the engagement (both the talk and the sight walk-up leave the player on
|
|
-- the tile the vanilla table indexes). Each list is that entry's
|
|
-- NPC_MOVEMENT_* exit, applied from the grunt's current tile. The exact
|
|
-- tables are one per grunt (dbmapcoord bytes decoded back to x,y).
|
|
local POKEMON_TOWER_7F_EXITS = {
|
|
[1] = { -- ROCKET1 @ (9,11), faces RIGHT
|
|
["9,12"] = { "right", "down", "down", "down", "down", "down", "left" },
|
|
["10,11"] = { "down", "right", "down", "down", "down", "down" },
|
|
["11,11"] = { "down", "down", "down", "down", "down" },
|
|
["12,11"] = { "down", "down", "down", "down", "down" },
|
|
},
|
|
[2] = { -- ROCKET2 @ (12,9), faces LEFT
|
|
["12,10"] = { "left", "down", "down", "down", "down", "down", "down" },
|
|
["11,9"] = { "down", "down", "down", "left", "down", "down" },
|
|
["10,9"] = { "down", "down", "down", "down", "down" },
|
|
["9,9"] = { "down", "down", "down", "down", "down" },
|
|
},
|
|
[3] = { -- ROCKET3 @ (9,7), faces RIGHT
|
|
["9,8"] = { "right", "down", "down", "down", "down", "down", "down" },
|
|
["10,7"] = { "down", "down", "down", "down", "down" },
|
|
["11,7"] = { "down", "down", "down", "down", "down" },
|
|
["12,7"] = { "down", "down", "down", "down", "down" },
|
|
},
|
|
}
|
|
-- The vanilla table only lists the sight/talk tiles the map geometry allows;
|
|
-- a plain down-walk keeps any other engagement tile (e.g. talking from the
|
|
-- side) safe -- it still clears the corridor and despawns the grunt.
|
|
local POKEMON_TOWER_7F_EXIT_FALLBACK =
|
|
{ "down", "down", "down", "down", "down" }
|
|
|
|
M.POKEMON_TOWER_7F = {
|
|
-- Repair saves that beat a grunt before the exit walk was ported: in
|
|
-- vanilla HideObject persists (wMissableObjectFlags), so a beaten grunt is
|
|
-- still gone on re-entry. hide_object below writes objectToggles for new
|
|
-- wins; this hides any grunt whose BEAT flag is already set.
|
|
onEnter = function(game, ow)
|
|
local Commands = require("src.script.Commands")
|
|
local ctx = { game = game, save = game.save, overworld = ow }
|
|
for _, r in ipairs(POKEMON_TOWER_7F_ROCKETS) do
|
|
if game.save.flags[r.beat] then
|
|
Commands.hide_object(ctx, "POKEMON_TOWER_7F", r.name)
|
|
end
|
|
end
|
|
end,
|
|
-- runVictoryHook fires after every trainer win on the map (both the sight
|
|
-- and talk paths route through engageTrainer -> checkVictoryRewards), so
|
|
-- this walks off whichever grunt was just beaten but is still standing.
|
|
-- The AfterBattle text + walk queue UNDER the EndBattle box engageTrainer
|
|
-- pushes right after, giving the vanilla order: EndBattle, AfterBattle,
|
|
-- walk, despawn.
|
|
onVictory = function(game, ow)
|
|
if ow.runner:isRunning() then return end
|
|
for _, r in ipairs(POKEMON_TOWER_7F_ROCKETS) do
|
|
local npc = ow:npcByIndex(r.index)
|
|
if npc and game.save.flags[r.beat] then
|
|
local key = ow.player.cellX .. "," .. ow.player.cellY
|
|
local dirs = (POKEMON_TOWER_7F_EXITS[r.index] or {})[key]
|
|
or POKEMON_TOWER_7F_EXIT_FALLBACK
|
|
ow.runner:run({
|
|
{ "show_text", r.after }, -- DisplayTextID
|
|
{ "walk_npc", r.index, dirs }, -- MoveSprite
|
|
{ "hide_object", "POKEMON_TOWER_7F", r.name }, -- HideObject
|
|
}, { npc = npc })
|
|
return
|
|
end
|
|
end
|
|
end,
|
|
talk = {
|
|
TEXT_POKEMONTOWER7F_MR_FUJI = {
|
|
{ "face_player" }, -- 1
|
|
{ "show_text", "_PokemonTower7FMrFujiRescueText" }, -- 2
|
|
{ "set_flag", "EVENT_RESCUED_MR_FUJI" }, -- 3
|
|
{ "set_flag", "EVENT_RESCUED_MR_FUJI_2" }, -- 4
|
|
-- pokered shows Fuji at home and swaps the Silph Co door
|
|
-- guard (ROCKET8 on the door tile -> ROCKET9 beside it)
|
|
{ "show_object", "MR_FUJIS_HOUSE", "MRFUJISHOUSE_MR_FUJI" }, -- 5
|
|
{ "hide_object", "SAFFRON_CITY", "SAFFRONCITY_ROCKET8" }, -- 6
|
|
{ "show_object", "SAFFRON_CITY", "SAFFRONCITY_ROCKET9" }, -- 7
|
|
-- pokered warps to wDestinationWarpID $1 (0-based) -- the house's
|
|
-- SECOND warp, the door mat at (3,7) -- facing UP
|
|
-- (PokemonTower7FWarpToMrFujiHouseScript: SPRITE_FACING_UP +
|
|
-- hWarpDestinationMap MR_FUJIS_HOUSE). Landing (3,3) instead put
|
|
-- the player at the Pokédex table, and the route's first waypoint
|
|
-- (3,7) then stepped onto a LIVE door mat and exited the house
|
|
-- before ever talking to Fuji -- so the POKE_FLUTE was never
|
|
-- collected and the Route 16 SNORLAX sealed the map. The arrival
|
|
-- mat itself is inert until stepped off (warpEntryCell), which is
|
|
-- what makes the vanilla coordinates safe.
|
|
{ "warp", "MR_FUJIS_HOUSE", 3, 7, "up" }, -- 8
|
|
},
|
|
},
|
|
}
|
|
|
|
M.MR_FUJIS_HOUSE = {
|
|
-- repair saves from before the rescue toggled him visible
|
|
onEnter = function(game, ow)
|
|
if game.save.flags.EVENT_RESCUED_MR_FUJI then
|
|
local Commands = require("src.script.Commands")
|
|
Commands.show_object({ game = game, save = game.save, overworld = ow },
|
|
"MR_FUJIS_HOUSE", "MRFUJISHOUSE_MR_FUJI")
|
|
end
|
|
end,
|
|
talk = {
|
|
TEXT_MRFUJISHOUSE_MR_FUJI = {
|
|
{ "face_player" }, -- 1
|
|
{ "check_flag", "EVENT_GOT_POKE_FLUTE" }, -- 2
|
|
{ "jump_if_true", 12 }, -- 3
|
|
{ "check_flag", "EVENT_RESCUED_MR_FUJI" }, -- 4
|
|
{ "jump_if_false", 14 }, -- 5
|
|
{ "show_text", "_MrFujisHouseMrFujiIThinkThisMayHelpYourQuestText" }, -- 6
|
|
-- give-then-print like scripts/MrFujisHouse.asm
|
|
{ "give_item", "POKE_FLUTE", 1, false }, -- 7
|
|
{ "show_text", "_MrFujisHouseMrFujiReceivedPokeFluteText" }, -- 8
|
|
{ "set_flag", "EVENT_GOT_POKE_FLUTE" }, -- 9
|
|
{ "show_text", "_MrFujisHouseMrFujiPokeFluteExplanationText" }, -- 10
|
|
{ "jump", 15 }, -- 11
|
|
{ "show_text", "_MrFujisHouseMrFujiHasMyFluteHelpedYouText" }, -- 12
|
|
{ "jump", 15 }, -- 13
|
|
{ "show_text", "_MrFujisHouseMrFujiPokedexText" }, -- 14
|
|
},
|
|
},
|
|
}
|
|
|
|
-- -------------------------------------------------------------------
|
|
-- Snorlax (scripts/Route12.asm, Route16.asm)
|
|
-- -------------------------------------------------------------------
|
|
|
|
-- each route has its own strings (text/Route12.asm, text/Route16.asm;
|
|
-- Route 16's sleeping line is the unnamed _Route16Text7). Talking to
|
|
-- Snorlax before it's beaten always just shows the sleeping line --
|
|
-- Route12DefaultScript/Route16DefaultScript only special-case
|
|
-- EVENT_FIGHT_ROUTEnn_SNORLAX, which ItemUsePokeFlute sets when the
|
|
-- player USES the POKé FLUTE from the item-use menu while standing next
|
|
-- to Snorlax (see ItemEffects.lua's POKE_FLUTE branch); merely talking
|
|
-- to it with the flute in the bag does nothing. From the woke-up text
|
|
-- on, snorlaxWake below mirrors Route12DefaultScript's fight branch /
|
|
-- Route12SnorlaxPostBattleScript (scripts/Route12.asm, Route16.asm):
|
|
-- HideObject runs BEFORE the battle (so Snorlax is gone even after a
|
|
-- blackout), then the battle, then the calmed-down/returned line only
|
|
-- when it was NOT caught (`ld a, [wBattleResult] / cp $2` skips it),
|
|
-- and EVENT_BEAT_ROUTEnn_SNORLAX on any non-blackout result.
|
|
local function snorlaxWake(mapId, objName, beatFlag, wokeUpText, calmedText)
|
|
return {
|
|
{ "show_text", wokeUpText }, -- 1
|
|
{ "hide_object", mapId, objName }, -- 2 HideObject pre-battle
|
|
{ "static_battle", "SNORLAX", 30, beatFlag }, -- 3
|
|
{ "check_battle_result", "win", "run" }, -- 4 not caught, not blackout
|
|
{ "jump_if_false", 7 }, -- 5 end (skip calmed-down)
|
|
{ "show_text", calmedText }, -- 6
|
|
}
|
|
end
|
|
|
|
-- A beaten Snorlax is gone for good: Route12/Route16DefaultScript run
|
|
-- HideObject in the same breath as the battle that sets
|
|
-- EVENT_BEAT_ROUTEnn_SNORLAX, so "event set, object still on the map" is a
|
|
-- state the asm cannot produce. Here it can (a mod's world:toggleObject, a
|
|
-- save edited or migrated from a build older than the flag), and it is a
|
|
-- dead end: ItemEffects' adjacentSleepingSnorlax refuses to wake a Snorlax
|
|
-- whose beat flag is set (ItemUsePokeFlute's CheckEvent, engine/items/
|
|
-- item_effects.asm), so the sleeper sits in the road forever and Cycling
|
|
-- Road is unreachable (#585). Reconcile the toggle from the flag on every
|
|
-- entry -- the mirror of SaveData.lua's toggle -> flag backfill, and the
|
|
-- same repair shape the Silph Co. floors use below.
|
|
local function hideBeatenSnorlax(mapId, objName, beatFlag)
|
|
return function(game, ow)
|
|
if not game.save.flags[beatFlag] then return end
|
|
local Commands = require("src.script.Commands")
|
|
Commands.hide_object({ game = game, save = game.save, overworld = ow },
|
|
mapId, objName)
|
|
end
|
|
end
|
|
|
|
-- snorlaxWake is looked up by ItemEffects.lua/BagMenu.lua (via
|
|
-- data/scripts/init.lua's M.get) and run when the flute wakes Snorlax;
|
|
-- objName/beatFlag let ItemEffects find the NPC and check whether it's
|
|
-- already been beaten before allowing the wake.
|
|
M.ROUTE_12 = {
|
|
talk = { TEXT_ROUTE12_SNORLAX = { { "show_text", "_Route12SnorlaxText" } } },
|
|
onEnter = hideBeatenSnorlax("ROUTE_12", "ROUTE12_SNORLAX",
|
|
"EVENT_BEAT_ROUTE12_SNORLAX"),
|
|
snorlaxWake = {
|
|
objName = "ROUTE12_SNORLAX", beatFlag = "EVENT_BEAT_ROUTE12_SNORLAX",
|
|
script = snorlaxWake("ROUTE_12", "ROUTE12_SNORLAX", "EVENT_BEAT_ROUTE12_SNORLAX",
|
|
"_Route12SnorlaxWokeUpText", "_Route12SnorlaxCalmedDownText"),
|
|
},
|
|
}
|
|
M.ROUTE_16 = {
|
|
talk = { TEXT_ROUTE16_SNORLAX = { { "show_text", "_Route16Text7" } } },
|
|
onEnter = hideBeatenSnorlax("ROUTE_16", "ROUTE16_SNORLAX",
|
|
"EVENT_BEAT_ROUTE16_SNORLAX"),
|
|
snorlaxWake = {
|
|
objName = "ROUTE16_SNORLAX", beatFlag = "EVENT_BEAT_ROUTE16_SNORLAX",
|
|
script = snorlaxWake("ROUTE_16", "ROUTE16_SNORLAX", "EVENT_BEAT_ROUTE16_SNORLAX",
|
|
"_Route16SnorlaxWokeUpText", "_Route16SnorlaxReturnedToMountainsText"),
|
|
},
|
|
}
|
|
|
|
-- -------------------------------------------------------------------
|
|
-- Safari Zone HMs (scripts/SafariZoneSecretHouse.asm, WardensHouse.asm)
|
|
-- -------------------------------------------------------------------
|
|
|
|
M.SAFARI_ZONE_SECRET_HOUSE = {
|
|
talk = {
|
|
TEXT_SAFARIZONESECRETHOUSE_FISHING_GURU = {
|
|
{ "face_player" }, -- 1
|
|
{ "check_flag", "EVENT_GOT_HM03" }, -- 2
|
|
{ "jump_if_true", 9 }, -- 3
|
|
{ "show_text", "_SafariZoneSecretHouseFishingGuruYouHaveWonText" }, -- 4
|
|
-- give-then-print like scripts/SafariZoneSecretHouse.asm
|
|
{ "give_item", "HM_SURF", 1, false }, -- 5
|
|
{ "show_text", "_SafariZoneSecretHouseFishingGuruReceivedHM03Text" }, -- 6
|
|
{ "set_flag", "EVENT_GOT_HM03" }, -- 7
|
|
{ "jump", 10 }, -- 8
|
|
{ "show_text", "_SafariZoneSecretHouseFishingGuruHM03ExplanationText" }, -- 9
|
|
},
|
|
},
|
|
}
|
|
|
|
M.WARDENS_HOUSE = {
|
|
talk = {
|
|
TEXT_WARDENSHOUSE_WARDEN = {
|
|
-- Labelled rather than hand-numbered: the branches here have been
|
|
-- re-pointed twice now (#535, #645), and every insert used to mean
|
|
-- renumbering three jumps that had no way of announcing they were stale.
|
|
{ "face_player" }, -- 1
|
|
{ "check_flag", "EVENT_GOT_HM04" }, -- 2
|
|
-- #535: previously jumped to the same silent-end target as the
|
|
-- give-then-thank fallthrough, so the warden said nothing on every
|
|
-- visit after the trade. pokered's .got_item branch
|
|
-- (scripts/WardensHouse.asm) instead prints HM04ExplanationText.
|
|
{ "jump_if_true", "got_hm04" }, -- 3
|
|
{ "check_item", "GOLD_TEETH" }, -- 4
|
|
{ "jump_if_false", "no_teeth" }, -- 5
|
|
{ "show_text", "_WardensHouseWardenGaveTheGoldTeethText" }, -- 6
|
|
{ "take_item", "GOLD_TEETH", 1 }, -- 7
|
|
{ "set_flag", "EVENT_GAVE_GOLD_TEETH" }, -- 8
|
|
{ "show_text", "_WardensHouseWardenThanksText" }, -- 9
|
|
-- give-then-print like scripts/WardensHouse.asm
|
|
{ "give_item", "HM_STRENGTH", 1, false }, -- 10
|
|
{ "show_text", "_WardensHouseWardenReceivedHM04Text" }, -- 11
|
|
{ "set_flag", "EVENT_GOT_HM04" }, -- 12
|
|
{ "jump", "end" }, -- 13 (jp .done)
|
|
|
|
-- #645: WardensHouseWardenText prints Gibberish1, then YesNoChoice,
|
|
-- and the warden answers the same gibberish either way -- Gibberish2
|
|
-- on yes, Gibberish3 on no (scripts/WardensHouse.asm). The port
|
|
-- printed the question and walked off before the answer.
|
|
{ "label", "no_teeth" }, -- 14
|
|
{ "ask", "_WardensHouseWardenGibberish1Text" }, -- 15
|
|
{ "jump_if_true", "gibberish_yes" }, -- 16
|
|
{ "show_text", "_WardensHouseWardenGibberish3Text" }, -- 17
|
|
{ "jump", "end" }, -- 18
|
|
{ "label", "gibberish_yes" }, -- 19
|
|
{ "show_text", "_WardensHouseWardenGibberish2Text" }, -- 20
|
|
{ "jump", "end" }, -- 21
|
|
|
|
-- #535: pokered .got_item branch (scripts/WardensHouse.asm) --
|
|
-- printed on every subsequent talk once EVENT_GOT_HM04 is set.
|
|
-- Text is _WardensHouseWardenHM04ExplanationText (text/WardensHouse.asm):
|
|
-- HM04 teaches Strength, and hints at the Safari Zone secret house.
|
|
{ "label", "got_hm04" }, -- 22
|
|
{ "show_text", "_WardensHouseWardenHM04ExplanationText" }, -- 23
|
|
},
|
|
},
|
|
}
|
|
|
|
-- -------------------------------------------------------------------
|
|
-- Silph Co. president (scripts/SilphCo11F.asm; Giovanni there is the
|
|
-- generic OPP_GIOVANNI#2 battle)
|
|
-- -------------------------------------------------------------------
|
|
|
|
-- SilphCo11FTeamRocketLeavesScript (scripts/SilphCo11F.asm) hides every
|
|
-- ROCKET and rocket-aligned SCIENTIST toggle on 2F-11F the moment Giovanni
|
|
-- falls; the item balls, the 2F/10F rescued workers and the 7F rival keep
|
|
-- theirs. Names and order are data/maps/toggleable_objects.asm's
|
|
-- TOGGLE_SILPH_CO_<floor>_n entries. Saffron City's half of the same
|
|
-- script lives in M.SAFFRON_CITY (story4.lua) (#392).
|
|
local SILPH_ROCKET_OBJECTS = {
|
|
{ "SILPH_CO_2F", { "SILPHCO2F_SCIENTIST1", "SILPHCO2F_SCIENTIST2",
|
|
"SILPHCO2F_ROCKET1", "SILPHCO2F_ROCKET2" } },
|
|
{ "SILPH_CO_3F", { "SILPHCO3F_ROCKET", "SILPHCO3F_SCIENTIST" } },
|
|
{ "SILPH_CO_4F", { "SILPHCO4F_ROCKET1", "SILPHCO4F_SCIENTIST",
|
|
"SILPHCO4F_ROCKET2" } },
|
|
{ "SILPH_CO_5F", { "SILPHCO5F_ROCKET1", "SILPHCO5F_SCIENTIST",
|
|
"SILPHCO5F_ROCKER", "SILPHCO5F_ROCKET2" } },
|
|
{ "SILPH_CO_6F", { "SILPHCO6F_ROCKET1", "SILPHCO6F_SCIENTIST",
|
|
"SILPHCO6F_ROCKET2" } },
|
|
{ "SILPH_CO_7F", { "SILPHCO7F_ROCKET1", "SILPHCO7F_SCIENTIST",
|
|
"SILPHCO7F_ROCKET2", "SILPHCO7F_ROCKET3" } },
|
|
{ "SILPH_CO_8F", { "SILPHCO8F_ROCKET1", "SILPHCO8F_SCIENTIST",
|
|
"SILPHCO8F_ROCKET2" } },
|
|
{ "SILPH_CO_9F", { "SILPHCO9F_ROCKET1", "SILPHCO9F_SCIENTIST",
|
|
"SILPHCO9F_ROCKET2" } },
|
|
{ "SILPH_CO_10F", { "SILPHCO10F_ROCKET", "SILPHCO10F_SCIENTIST" } },
|
|
{ "SILPH_CO_11F", { "SILPHCO11F_GIOVANNI", "SILPHCO11F_ROCKET1",
|
|
"SILPHCO11F_ROCKET2" } },
|
|
}
|
|
|
|
-- hide_object writes save.objectToggles, so the single pass at the win
|
|
-- covers floors the player is not standing on; onlyMap is the per-floor
|
|
-- repair path below.
|
|
local function silphRocketsLeave(game, ow, onlyMap)
|
|
local Commands = require("src.script.Commands")
|
|
local ctx = { game = game, save = game.save, overworld = ow }
|
|
for _, floor in ipairs(SILPH_ROCKET_OBJECTS) do
|
|
if not onlyMap or onlyMap == floor[1] then
|
|
for _, name in ipairs(floor[2]) do
|
|
Commands.hide_object(ctx, floor[1], name)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- SilphCo11FGiovanniAfterBattleScript (scripts/SilphCo11F.asm) is the whole
|
|
-- aftermath: DisplayTextID TEXT_SILPHCO11F_GIOVANNI_YOU_RUINED_OUR_PLANS,
|
|
-- GBFadeOutToBlack, SilphCo11FTeamRocketLeavesScript, Delay3,
|
|
-- GBFadeInFromBlack, then SetEvent. The port had only the hide pass, so the
|
|
-- speech never played and every rocket blinked out in front of the player
|
|
-- (#722). Same hide list as silphRocketsLeave, spelled as script rows so the
|
|
-- fade can hold over it.
|
|
local function silphAftermathRows()
|
|
local rows = {
|
|
{ "show_text", "_SilphCo11FGiovanniYouRuinedOurPlansText" },
|
|
{ "fade", "out" },
|
|
}
|
|
for _, floor in ipairs(SILPH_ROCKET_OBJECTS) do
|
|
for _, name in ipairs(floor[2]) do
|
|
rows[#rows + 1] = { "hide_object", floor[1], name }
|
|
end
|
|
end
|
|
rows[#rows + 1] = { "wait", 3 } -- Delay3
|
|
rows[#rows + 1] = { "fade", "in" }
|
|
return rows
|
|
end
|
|
|
|
M.SILPH_CO_11F = {
|
|
-- Giovanni's battle is a COORDINATE TRIGGER, not a talk.
|
|
-- SilphCo11FDefaultScript (scripts/SilphCo11F.asm) checks
|
|
-- .PlayerCoordsArray -- (6,13) and (7,12) -- every frame while
|
|
-- EVENT_BEAT_SILPH_CO_GIOVANNI is unset: standing there shows his text,
|
|
-- walks him three tiles down (.GiovanniMovement), and starts the fight.
|
|
-- He also has no trainer-header entry, so sight engagement never fires
|
|
-- either. Without this hook he was a talk-only statue four tiles away
|
|
-- from anything the route (or a vanilla-faithful player walking the same
|
|
-- line) would touch, and the whole Silph ending -- the flag, the Master
|
|
-- Ball, the Saffron streets clearing -- silently never happened.
|
|
--
|
|
-- engageTrainer shows TEXT_SILPHCO11F_GIOVANNI as the battle text and,
|
|
-- via victories.lua OPP_GIOVANNI#2, sets the event on a win; a loss
|
|
-- sets nothing, so the trigger re-arms exactly as vanilla does.
|
|
onStep = function(game, ow, x, y)
|
|
if game.save.flags.EVENT_BEAT_SILPH_CO_GIOVANNI then return false end
|
|
if not ((x == 6 and y == 13) or (x == 7 and y == 12)) then return false end
|
|
local gio
|
|
for _, npc in ipairs(ow.npcs) do
|
|
if npc.def and npc.def.name == "SILPHCO11F_GIOVANNI" then gio = npc break end
|
|
end
|
|
if not gio or ow:trainerDefeated(gio) then return false end
|
|
ow:scriptMove(gio, "down", 3, function()
|
|
gio:facePlayer(ow.player)
|
|
ow:engageTrainer(gio, function()
|
|
-- SilphCo11FGiovanniAfterBattleScript: the "Blast it all!" speech,
|
|
-- then SilphCo11FTeamRocketLeavesScript behind a fade so every Silph
|
|
-- rocket leaves off-screen (the street rockets are handled by
|
|
-- M.SAFFRON_CITY.onEnter in story4.lua). Queued, not run here: the
|
|
-- battle's own callbacks are still unwinding, so queueScript starts
|
|
-- it on the first idle overworld frame -- after the end-battle
|
|
-- "Arrgh!!" box victories.lua OPP_GIOVANNI#2 pushes (#722).
|
|
if game.save.flags.EVENT_BEAT_SILPH_CO_GIOVANNI then
|
|
ow:queueScript(silphAftermathRows())
|
|
end
|
|
end)
|
|
end)
|
|
return true
|
|
end,
|
|
onEnter = function(game, ow)
|
|
if game.save.flags.EVENT_BEAT_SILPH_CO_GIOVANNI then
|
|
silphRocketsLeave(game, ow)
|
|
end
|
|
end,
|
|
talk = {
|
|
-- SilphCo11FSilphPresidentText branches on EVENT_GOT_MASTER_BALL only:
|
|
-- the teleport pads reach him without passing Giovanni's trigger, and
|
|
-- afterwards he describes the ball forever. The old rows fell off the
|
|
-- end of the script on both branches, so a second talk was silence
|
|
-- (#392).
|
|
TEXT_SILPHCO11F_SILPH_PRESIDENT = {
|
|
{ "face_player" }, -- 1
|
|
{ "check_flag", "EVENT_GOT_MASTER_BALL" }, -- 2
|
|
{ "jump_if_true", 9 }, -- 3
|
|
{ "show_text", "_SilphCo11FSilphPresidentText" }, -- 4
|
|
-- give-then-print like scripts/SilphCo11F.asm
|
|
{ "give_item", "MASTER_BALL", 1, false }, -- 5
|
|
{ "show_text", "_SilphCo11FSilphPresidentReceivedMasterBallText" }, -- 6
|
|
{ "set_flag", "EVENT_GOT_MASTER_BALL" }, -- 7
|
|
{ "jump", "end" }, -- 8
|
|
{ "show_text",
|
|
"_SilphCo11FSilphPresidentMasterBallDescriptionText" }, -- 9
|
|
},
|
|
},
|
|
}
|
|
|
|
-- Floors 2F-10F carry no other onEnter, so this repairs saves that beat
|
|
-- Giovanni before the hide list existed (same shape as
|
|
-- M.POKEMON_TOWER_7F.onEnter); 11F hides its own inside the table above.
|
|
for _, floor in ipairs(SILPH_ROCKET_OBJECTS) do
|
|
local mapId = floor[1]
|
|
if mapId ~= "SILPH_CO_11F" then
|
|
M[mapId] = M[mapId] or {}
|
|
M[mapId].onEnter = function(game, ow)
|
|
if game.save.flags.EVENT_BEAT_SILPH_CO_GIOVANNI then
|
|
silphRocketsLeave(game, ow, mapId)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- -------------------------------------------------------------------
|
|
-- Victory Road boulder switches (scripts/VictoryRoad1F/2F/3F.asm):
|
|
-- a boulder resting on a switch removes a barrier block; the 3F hole
|
|
-- drops a boulder down to the 2F switch, and also dungeon-warps the
|
|
-- player (IsPlayerOnDungeonWarp + DungeonWarpList/Data -> 2F at 22,16).
|
|
-- -------------------------------------------------------------------
|
|
|
|
local function boulderAt(ow, x, y)
|
|
local npc = ow:npcAtCell(x, y)
|
|
return npc and npc.def.sprite == "SPRITE_BOULDER" and npc or nil
|
|
end
|
|
|
|
-- Each barrier is stamped BOTH ways on entry. pokered only ever stamps the
|
|
-- OPEN block (ReplaceTileBlock edits the loaded block map, which the next map
|
|
-- load throws away, so a fresh entry always shows the .blk's closed barrier),
|
|
-- but OverworldState:replaceBlock writes through Map:setBlock into the SHARED
|
|
-- map record in Game.data, so the port has to put the closed block back
|
|
-- itself or a solved barrier stays open for the rest of the session (#258).
|
|
-- The closed ids are the shipped .blk bytes: VictoryRoad1F.blk (4,6) = $25,
|
|
-- VictoryRoad2F.blk (3,4) = $37 and (11,7) = $25, VictoryRoad3F.blk
|
|
-- (3,5) = $25. Same both-ways pattern as the card-key doors in
|
|
-- OverworldState:setMap.
|
|
M.VICTORY_ROAD_1F = {
|
|
onEnter = function(game, ow)
|
|
ow:replaceBlock(4, 6,
|
|
game.save.flags.EVENT_VICTORY_ROAD_1_BOULDER_ON_SWITCH and 0x1D or 0x25)
|
|
end,
|
|
onBoulderMoved = function(game, ow, npc)
|
|
if npc.cellX == 17 and npc.cellY == 13
|
|
and not game.save.flags.EVENT_VICTORY_ROAD_1_BOULDER_ON_SWITCH then
|
|
game.save.flags.EVENT_VICTORY_ROAD_1_BOULDER_ON_SWITCH = true
|
|
ow:replaceBlock(4, 6, 0x1D)
|
|
end
|
|
end,
|
|
}
|
|
|
|
M.VICTORY_ROAD_2F = {
|
|
onEnter = function(game, ow)
|
|
-- VictoryRoad2FResetBoulderEventScript (scripts/VictoryRoad2F.asm:19):
|
|
-- entering 2F clears the 1F switch event, so 1F's barrier is closed
|
|
-- again the next time you climb down (#258)
|
|
game.save.flags.EVENT_VICTORY_ROAD_1_BOULDER_ON_SWITCH = nil
|
|
ow:replaceBlock(3, 4,
|
|
game.save.flags.EVENT_VICTORY_ROAD_2_BOULDER_ON_SWITCH1 and 0x15 or 0x37)
|
|
ow:replaceBlock(11, 7,
|
|
game.save.flags.EVENT_VICTORY_ROAD_2_BOULDER_ON_SWITCH2 and 0x1D or 0x25)
|
|
end,
|
|
onBoulderMoved = function(game, ow, npc)
|
|
if npc.cellX == 1 and npc.cellY == 16
|
|
and not game.save.flags.EVENT_VICTORY_ROAD_2_BOULDER_ON_SWITCH1 then
|
|
game.save.flags.EVENT_VICTORY_ROAD_2_BOULDER_ON_SWITCH1 = true
|
|
ow:replaceBlock(3, 4, 0x15)
|
|
end
|
|
if npc.cellX == 9 and npc.cellY == 16
|
|
and not game.save.flags.EVENT_VICTORY_ROAD_2_BOULDER_ON_SWITCH2 then
|
|
game.save.flags.EVENT_VICTORY_ROAD_2_BOULDER_ON_SWITCH2 = true
|
|
ow:replaceBlock(11, 7, 0x1D)
|
|
end
|
|
end,
|
|
}
|
|
|
|
M.VICTORY_ROAD_3F = {
|
|
onEnter = function(game, ow)
|
|
ow:replaceBlock(3, 5,
|
|
game.save.flags.EVENT_VICTORY_ROAD_3_BOULDER_ON_SWITCH1 and 0x1D or 0x25)
|
|
end,
|
|
onBoulderMoved = function(game, ow, npc)
|
|
if npc.cellX == 3 and npc.cellY == 5
|
|
and not game.save.flags.EVENT_VICTORY_ROAD_3_BOULDER_ON_SWITCH1 then
|
|
game.save.flags.EVENT_VICTORY_ROAD_3_BOULDER_ON_SWITCH1 = true
|
|
ow:replaceBlock(3, 5, 0x1D)
|
|
end
|
|
-- the hole at (23,15): the boulder drops to 2F next to switch 2.
|
|
-- VictoryRoad3FDefaultScript .handle_hole gates the swap on
|
|
-- CheckAndSetEvent EVENT_VICTORY_ROAD_3_BOULDER_ON_SWITCH2, then
|
|
-- HideObject TOGGLE_VICTORY_ROAD_3F_BOULDER / ShowObject
|
|
-- TOGGLE_VICTORY_ROAD_2F_BOULDER. Those two toggles are
|
|
-- VICTORYROAD3F_BOULDER4 and VICTORYROAD2F_BOULDER3
|
|
-- (data/maps/toggleable_objects.asm); the old "VICTORYROAD2F_BOULDER"
|
|
-- matched no object_event name, so the toggle landed under a key
|
|
-- objectVisible never reads: harmless only while nothing hid that
|
|
-- boulder, and fatal once Route 23's reset does (#258).
|
|
if npc.cellX == 23 and npc.cellY == 15
|
|
and not game.save.flags.EVENT_VICTORY_ROAD_3_BOULDER_ON_SWITCH2 then
|
|
game.save.flags.EVENT_VICTORY_ROAD_3_BOULDER_ON_SWITCH2 = true
|
|
local Commands = require("src.script.Commands")
|
|
local ctx = { save = game.save, overworld = ow, game = game }
|
|
Commands.hide_object(ctx, "VICTORY_ROAD_3F", npc.def.name)
|
|
Commands.show_object(ctx, "VICTORY_ROAD_2F", "VICTORYROAD2F_BOULDER3")
|
|
end
|
|
end,
|
|
-- scripts/VictoryRoad3F.asm VictoryRoad3FDefaultScript: the same hole
|
|
-- is a dungeon warp for the player (wDungeonWarpDestinationMap =
|
|
-- VICTORY_ROAD_2F, wWhichDungeonWarp = 2 -> DungeonWarpData 22,16).
|
|
-- Mirrors POKEMON_MANSION_3F.onStep; CAVERN $22 is walkable so the
|
|
-- fall is onStep, not a collision block.
|
|
onStep = function(game, ow, x, y)
|
|
if x == 23 and y == 15 then
|
|
require("src.core.Sound").play(game.data, "Faint_Fall")
|
|
ow:startWarpTo("VICTORY_ROAD_2F", 22, 16, ow.player.facing)
|
|
return true
|
|
end
|
|
return false
|
|
end,
|
|
}
|
|
|
|
-- -------------------------------------------------------------------
|
|
-- Champion (scripts/ChampionsRoom.asm): rival with the Rival3 party for
|
|
-- your starter, then Oak's congratulations (Hall of Fame-lite).
|
|
-- -------------------------------------------------------------------
|
|
|
|
-- The battle gate is the run-scoped EVENT_BEAT_CHAMPION_RIVAL_THIS_RUN,
|
|
-- cleared by the Indigo lobby's Elite Four reset (scripts/
|
|
-- IndigoPlateauLobby.asm) so the champion is re-fightable on rematches,
|
|
-- like pokered's re-entry cutscene. EVENT_BEAT_CHAMPION_RIVAL stays set
|
|
-- forever (postgame gates like the Cerulean cave guard read it).
|
|
--
|
|
-- pokered forces the fight on map entry: Agatha's victory arms
|
|
-- SCRIPT_CHAMPIONSROOM_PLAYER_ENTERS (scripts/AgathasRoom.asm), and
|
|
-- ChampionsRoomPlayerEntersScript then runs RivalEntrance_RLEMovement
|
|
-- (up 1, right 1, up 3) before ChampionsRoomRivalReadyToBattleScript.
|
|
-- The rival object has no trainer header / sight range, so without that
|
|
-- entrance script the player can walk past (issue #99). We arm on the
|
|
-- run flag instead of Agatha's victory bit: same observable effect for
|
|
-- first clear and Indigo rematches, without a cross-map script pointer.
|
|
--
|
|
-- scripts/ChampionsRoom.asm ChampionsRoomRivalDefeatedScript ->
|
|
-- OakArrivesScript -> OakCongratulatesPlayerScript ->
|
|
-- OakDisappointedWithRivalScript -> OakComeWithMeScript -> OakExitsScript,
|
|
-- then the simulated walk up-and-left into the HALL_OF_FAME warp
|
|
-- (PlayerFollowsOakScript / WalkToHallOfFame_RLEMovement). The induction
|
|
-- itself is NOT run here: it belongs to the HALL_OF_FAME room script
|
|
-- (scripts/HallOfFame.asm), so we set a one-shot marker and warp; the room
|
|
-- onEnter (M.HALL_OF_FAME below) drives the HoF Oak speech + record.
|
|
local championsRoomRivalScript = {
|
|
{ "face_player" }, -- 1
|
|
{ "check_flag", "EVENT_BEAT_CHAMPION_RIVAL_THIS_RUN" }, -- 2
|
|
-- "end" rather than a row number past the tail: this script grew by a row
|
|
-- when the follow-Oak walk landed (#704), which silently turned the old
|
|
-- numeric 26 into a jump ONTO the closing HALL_OF_FAME warp instead of past
|
|
-- it, so a returning champion warped straight into the induction.
|
|
{ "jump_if_true", "end" }, -- 3
|
|
{ "show_text", "_ChampionsRoomRivalIntroText" }, -- 4
|
|
-- ChampionsRoomRivalReadyToBattleScript plays MUSIC_FINAL_BATTLE after
|
|
-- the intro text, before the battle itself (#706); pushBattle's wipe-time
|
|
-- playBattle("final") then no-ops on the same song, so the theme stays
|
|
-- continuous into the fight
|
|
{ "play_music", "Music_FinalBattle" }, -- 5
|
|
{ "rival_battle", "OPP_RIVAL3", 1 }, -- 6
|
|
-- losing halts here; the numeric target this replaced pointed at the
|
|
-- closing warp, which inducted a player who had just lost the fight (#704)
|
|
{ "jump_if_false", "end" }, -- 7
|
|
{ "set_flag", "EVENT_BEAT_CHAMPION_RIVAL_THIS_RUN" }, -- 8
|
|
{ "set_flag", "EVENT_BEAT_CHAMPION_RIVAL" }, -- 9
|
|
-- ChampionsRoomRivalDefeatedScript re-displays TEXT_CHAMPIONSROOM_RIVAL,
|
|
-- whose text_asm takes the EVENT_BEAT_CHAMPION_RIVAL branch =
|
|
-- _ChampionsRoomRivalAfterBattleText (the in-battle _RivalDefeatedText
|
|
-- is the port's generic "<PLAYER> defeated BLUE!" engine line instead).
|
|
{ "show_text", "_ChampionsRoomRivalAfterBattleText" }, -- 10
|
|
-- ChampionsRoomOakArrivesScript: Music_Cities1AlternateTempo
|
|
-- (Cities1, kept into HALL_OF_FAME like BIT_NO_MAP_MUSIC after
|
|
-- defeating RIVAL3), then Oak's "{PLAYER}!" + reveal + walk in.
|
|
-- audio/alternate_tempo.asm Music_Cities1AlternateTempo is not a plain
|
|
-- PlayMusic: it fades the current song out (wAudioFadeOutControl = 10),
|
|
-- waits 100 frames for the fade, then restarts Cities1 with channel 1
|
|
-- pointed at Music_Cities1_Ch1_AlternateTempo -- `tempo 232` where the
|
|
-- normal Music_Cities1_Ch1 opens `tempo 144`, i.e. the slower, heavier
|
|
-- reading of the town theme this scene is known for (#847).
|
|
{ "fade_music", 10 }, -- 11
|
|
{ "wait", 100 }, -- 12
|
|
{ "play_music", "Music_Cities1", { keep = true, tempo = 232 } }, -- 13
|
|
{ "show_text", "_ChampionsRoomOakText" }, -- 14
|
|
{ "show_object", "CHAMPIONS_ROOM", "CHAMPIONSROOM_OAK" }, -- 15
|
|
{ "move_npc", 2, "up", 5 }, -- 16 OakEntranceAfterVictoryMovement
|
|
-- OakCongratulatesPlayerScript: rival faces left, Oak faces down
|
|
{ "face_object", 1, "left" }, -- 17
|
|
{ "face_object", 2, "down" }, -- 18
|
|
{ "show_text", "_ChampionsRoomOakCongratulatesPlayerText" }, -- 19
|
|
-- OakDisappointedWithRivalScript: Oak turns to the rival (right)
|
|
{ "face_object", 2, "right" }, -- 20
|
|
{ "show_text", "_ChampionsRoomOakDisappointedWithRivalText" }, -- 21
|
|
-- OakComeWithMeScript: Oak faces down again, then exits up
|
|
{ "face_object", 2, "down" }, -- 22
|
|
{ "show_text", "_ChampionsRoomOakComeWithMeText" }, -- 23
|
|
{ "move_npc", 2, "up", 2 }, -- 24 OakExitChampionsRoomMovement
|
|
{ "hide_object", "CHAMPIONS_ROOM", "CHAMPIONSROOM_OAK" }, -- 25
|
|
-- ChampionsRoomPlayerFollowsOakScript / WalkToHallOfFame_RLEMovement
|
|
-- (PAD_UP 4, PAD_LEFT 1): the player walks out after Oak instead of the
|
|
-- screen just fading on the spot (#704). The entrance walk leaves the
|
|
-- player at (4,3) and both north-wall warps sit on row 0, so the original
|
|
-- only ever spends three of those simulated steps -- CheckWarpsNoCollision
|
|
-- takes the HALL_OF_FAME warp the moment the walk lands on (4,0) and the
|
|
-- trailing UP/LEFT are dropped. Scripted steps ignore collision here just
|
|
-- as they do in the original (CollisionCheckOnLand skips its checks while
|
|
-- wSimulatedJoypadStatesIndex is non-zero), so stepping through the
|
|
-- rival's cell at (4,2) is the ported behavior, not a clip. Re-reported
|
|
-- as a clip in #847 and re-checked against home/overworld.asm
|
|
-- CollisionCheckOnLand, which is still the authority: do not "fix" it.
|
|
{ "move_player", "up", 3 }, -- 26
|
|
-- hand the induction off to the HALL_OF_FAME room (consumed by its
|
|
-- onEnter), then warp up into it (destWarp 1 lands at (4,7) facing up)
|
|
{ "set_field", "pendingHallOfFame", true }, -- 27
|
|
{ "warp", "HALL_OF_FAME", 4, 7, "up" }, -- 28
|
|
}
|
|
|
|
M.CHAMPIONS_ROOM = {
|
|
onEnter = function(game, ow)
|
|
if game.save.flags.EVENT_BEAT_CHAMPION_RIVAL_THIS_RUN then return end
|
|
-- Lance entrance warps land at y=7; HoF return warps land at y=0.
|
|
-- Only the south entry should run ChampionsRoomPlayerEntersScript.
|
|
if ow.player.cellY < 7 then return end
|
|
local rival
|
|
for _, npc in ipairs(ow.npcs) do
|
|
if npc.def and npc.def.name == "CHAMPIONSROOM_RIVAL" then
|
|
rival = npc
|
|
break
|
|
end
|
|
end
|
|
-- RivalEntrance_RLEMovement, then the battle/Oak script (queued
|
|
-- separately so talk-script jump indices stay 1-based as written).
|
|
ow:queueScript({
|
|
{ "move_player", "up", 1 },
|
|
{ "move_player", "right", 1 },
|
|
{ "move_player", "up", 3 },
|
|
})
|
|
ow:queueScript(championsRoomRivalScript, { npc = rival })
|
|
end,
|
|
talk = {
|
|
TEXT_CHAMPIONSROOM_RIVAL = championsRoomRivalScript,
|
|
},
|
|
}
|
|
|
|
-- -------------------------------------------------------------------
|
|
-- Hall of Fame (scripts/HallOfFame.asm): the induction's entry point is
|
|
-- the ROOM, not the Champions Room script. HallOfFameDefaultScript walks
|
|
-- the player up 5 into Oak (HallOfFameEntryMovement), then
|
|
-- HallOfFameOakCongratulationsScript turns them face-to-face, shows
|
|
-- _HallOfFameOakText, hides Oak (predef HideObject) and runs
|
|
-- HallOfFameResetEventsAndSaveScript's predef HallOfFamePC (the induction +
|
|
-- credits, here Commands.record_hall_of_fame).
|
|
--
|
|
-- onEnter fires DURING the Champions Room warp's setMap, while that warp
|
|
-- command's runner is still suspended-alive (yielded at the warp), so we
|
|
-- cannot start a runner here directly (ScriptRunner:run asserts the runner
|
|
-- is idle). Instead we QUEUE the cutscene: OverworldState:update drains
|
|
-- self.pendingScript once the warp's transition has finished and its runner
|
|
-- has gone dead, so the room cutscene begins a frame after the warp fully
|
|
-- completes. The one-shot save.pendingHallOfFame marker (set by the
|
|
-- Champions Room script) is consumed here so re-entering the room later
|
|
-- does not replay the induction.
|
|
M.HALL_OF_FAME = {
|
|
onEnter = function(game, ow)
|
|
-- self-heal saves poisoned by a prior version of this script, which
|
|
-- wrongly hid Oak here instead of the Cerulean Cave guard (see
|
|
-- CERULEAN_CITY onEnter, which handles the real HideObject target)
|
|
local toggles = game.save.objectToggles and game.save.objectToggles.HALL_OF_FAME
|
|
if toggles then toggles.HALLOFFAME_OAK = nil end
|
|
|
|
-- Make the Hall of Fame recording machine interactable. The console
|
|
-- juts from the north wall as the two solid cells (4,1) and (5,1); a
|
|
-- sign on each lets the player face the machine (from below, or from
|
|
-- either side) and press A to run the TEXT_HALLOFFAME_PC talk script.
|
|
-- maps.lua is generated from the ROM and gitignored, so the sign is
|
|
-- injected here (a tracked script) rather than baked into map data.
|
|
local def = ow.map.def
|
|
def.signs = def.signs or {}
|
|
local present = false
|
|
for _, s in ipairs(def.signs) do
|
|
if s.text == "TEXT_HALLOFFAME_PC" then present = true break end
|
|
end
|
|
if not present then
|
|
table.insert(def.signs, { text = "TEXT_HALLOFFAME_PC", x = 4, y = 1 })
|
|
table.insert(def.signs, { text = "TEXT_HALLOFFAME_PC", x = 5, y = 1 })
|
|
end
|
|
-- the live Map instance built its signAt lookup from def.signs before
|
|
-- this hook ran, so rebuild it (idempotent) to pick up the injection
|
|
ow.map.signAt = {}
|
|
for _, s in ipairs(def.signs) do
|
|
ow.map.signAt[s.y * ow.map.widthCells + s.x] = s
|
|
end
|
|
|
|
if not game.save.pendingHallOfFame then return end
|
|
game.save.pendingHallOfFame = false
|
|
ow:queueScript({
|
|
{ "move_player", "up", 5 }, -- (4,7)->(4,2) beside Oak (5,2)
|
|
{ "face_object", 1, "left" }, -- HALLOFFAME_OAK faces the player
|
|
{ "face_player_dir", "right" }, -- player faces Oak (PLAYER_DIR_RIGHT)
|
|
{ "show_text", "_HallOfFameOakText" }, -- TEXT_HALLOFFAME_OAK
|
|
-- HallOfFameOakCongratulationsScript hides TOGGLE_CERULEAN_CAVE_GUY
|
|
-- here, not Oak; that's handled separately by CERULEAN_CITY onEnter
|
|
-- gating on EVENT_BEAT_CHAMPION_RIVAL
|
|
{ "record_hall_of_fame" }, -- predef HallOfFamePC: induction + credits
|
|
})
|
|
end,
|
|
talk = {
|
|
-- The recording machine doubles as a "warp home" PC: a YES/NO prompt
|
|
-- that teleports back to the new-game bedroom spawn (special_warps.asm
|
|
-- NewGameWarp: REDS_HOUSE_2F, 3, 6, facing down). Fabricated
|
|
-- convenience -- there is no such prompt in the original ROM.
|
|
-- Heal + remember_outdoor so house LAST_MAP mats land in Pallet, not
|
|
-- Indigo Plateau (issue #103 escape path for already-stuck saves).
|
|
TEXT_HALLOFFAME_PC = {
|
|
{ "ask", "Return to\nPALLET TOWN?" }, -- 1 YES/NO -> lastCheck
|
|
{ "jump_if_false", "end" }, -- 2 NO: back away
|
|
{ "heal_party" }, -- 3
|
|
{ "remember_outdoor", "PALLET_TOWN", 5, 6 }, -- 4 LAST_MAP -> Pallet door
|
|
{ "warp", "REDS_HOUSE_2F", 3, 6, "down" }, -- 5 YES: home to your room
|
|
},
|
|
},
|
|
}
|
|
|
|
-- -------------------------------------------------------------------
|
|
-- Mid-game rival battles (scripts/CeruleanCity.asm, PokemonTower2F.asm);
|
|
-- Rival1 parties 7-9 are the Cerulean set, Rival2 parties 4-6 the Tower
|
|
-- set (data/trainers/parties.asm)
|
|
-- -------------------------------------------------------------------
|
|
|
|
M.CERULEAN_CITY = {
|
|
talk = {
|
|
-- CeruleanCityRivalText: Bill line once beaten; pre-battle otherwise.
|
|
-- Post-fight walk matches CeruleanCityMovement4 (right then into town).
|
|
TEXT_CERULEANCITY_RIVAL = {
|
|
{ "face_player" }, -- 1
|
|
{ "check_flag", "EVENT_BEAT_CERULEAN_RIVAL" }, -- 2
|
|
{ "jump_if_true", 13 }, -- 3
|
|
{ "show_text", "_CeruleanCityRivalPreBattleText" }, -- 4
|
|
{ "rival_battle", "OPP_RIVAL1", 7 }, -- 5
|
|
{ "jump_if_false", "end" }, -- 6
|
|
{ "set_flag", "EVENT_BEAT_CERULEAN_RIVAL" }, -- 7
|
|
{ "show_text", "_CeruleanCityRivalDefeatedText" }, -- 8
|
|
{ "show_text", "_CeruleanCityRivalIWentToBillsText" }, -- 9
|
|
{ "walk_npc", 1, { "right", "down", "down", "down",
|
|
"down", "down", "down" } }, -- 10
|
|
{ "hide_object", "CERULEAN_CITY", "CERULEANCITY_RIVAL" }, -- 11
|
|
{ "jump", "end" }, -- 12
|
|
{ "show_text", "_CeruleanCityRivalIWentToBillsText" }, -- 13
|
|
},
|
|
},
|
|
}
|
|
|
|
-- PokemonTower2F.asm: after the battle, PokemonTower2FDefeatedRivalScript
|
|
-- walks him out (RightThenDown vs DownThenRight from EVENT_POKEMON_TOWER_
|
|
-- RIVAL_ON_LEFT) then HideObject TOGGLE_POKEMON_TOWER_2F_RIVAL. Player
|
|
-- at (15,5) is the ON_LEFT case.
|
|
local TOWER_RIVAL_EXIT_RIGHT_THEN_DOWN =
|
|
{ "right", "down", "down", "right", "down", "down", "right", "right" }
|
|
local TOWER_RIVAL_EXIT_DOWN_THEN_RIGHT =
|
|
{ "down", "down", "right", "right", "right", "right", "down", "down" }
|
|
|
|
local function pokemonTower2FRivalScript(playerX)
|
|
local exitDirs = (playerX == 15)
|
|
and TOWER_RIVAL_EXIT_DOWN_THEN_RIGHT
|
|
or TOWER_RIVAL_EXIT_RIGHT_THEN_DOWN
|
|
return {
|
|
{ "face_player" }, -- 1
|
|
{ "check_flag", "EVENT_BEAT_POKEMON_TOWER_RIVAL" }, -- 2
|
|
{ "jump_if_true", 12 }, -- 3
|
|
{ "show_text", "_PokemonTower2FRivalWhatBringsYouHereText" }, -- 4
|
|
{ "rival_battle", "OPP_RIVAL2", 4 }, -- 5
|
|
{ "jump_if_false", "end" }, -- 6 loss: stay
|
|
{ "set_flag", "EVENT_BEAT_POKEMON_TOWER_RIVAL" }, -- 7
|
|
{ "show_text", "_PokemonTower2FRivalDefeatedText" }, -- 8
|
|
{ "walk_npc", 1, exitDirs }, -- 9
|
|
{ "hide_object", "POKEMON_TOWER_2F", "POKEMONTOWER2F_RIVAL" }, -- 10
|
|
{ "jump", "end" }, -- 11
|
|
{ "show_text", "_PokemonTower2FRivalHowsYourDexText" }, -- 12
|
|
}
|
|
end
|
|
|
|
M.POKEMON_TOWER_2F = {
|
|
rivalScript = pokemonTower2FRivalScript,
|
|
talk = {
|
|
TEXT_POKEMONTOWER2F_RIVAL = function(game, ow, npc, done)
|
|
ow.runner:run(pokemonTower2FRivalScript(ow.player.cellX),
|
|
{ npc = npc, onDone = done })
|
|
end,
|
|
},
|
|
-- Saves that beat him before the exit walk was ported still have the
|
|
-- flag but a visible rival; hide on enter like BillsHouse repairs.
|
|
onEnter = function(game, ow)
|
|
if not game.save.flags.EVENT_BEAT_POKEMON_TOWER_RIVAL then return end
|
|
local Commands = require("src.script.Commands")
|
|
Commands.hide_object({ game = game, save = game.save, overworld = ow },
|
|
"POKEMON_TOWER_2F", "POKEMONTOWER2F_RIVAL")
|
|
end,
|
|
-- PokemonTower2FDefaultScript: walking past the rival's tile forces
|
|
-- the encounter (ArePlayerCoordsInArray on (15,5)/(14,6)) -- he never
|
|
-- waits to be talked to
|
|
onStep = function(game, ow, x, y)
|
|
if game.save.flags.EVENT_BEAT_POKEMON_TOWER_RIVAL then return false end
|
|
if not ((x == 15 and y == 5) or (x == 14 and y == 6)) then return false end
|
|
if ow.runner:isRunning() then return false end
|
|
local rival = ow:npcByIndex(1)
|
|
if not rival then return false end
|
|
ow.player.facing = (x == 15) and "left" or "up"
|
|
require("src.core.Music").play(game.data, "Music_MeetRival")
|
|
ow.runner:run(pokemonTower2FRivalScript(x), { npc = rival })
|
|
return true
|
|
end,
|
|
}
|
|
|
|
-- -------------------------------------------------------------------
|
|
-- In-game trades (data/events/trades.asm; the NPC<->trade mapping is
|
|
-- from each map's DoInGameTradeDialogue call)
|
|
-- -------------------------------------------------------------------
|
|
|
|
M.ROUTE_2_TRADE_HOUSE = {
|
|
talk = {
|
|
-- The Scientist is plain flavor; the Game Boy Kid holds the trade
|
|
-- (data/scripts/flavor/route_2_trade_house.lua), per Route2TradeHouse.asm.
|
|
TEXT_ROUTE2TRADEHOUSE_SCIENTIST = {
|
|
{ "face_player" },
|
|
{ "show_text", "_Route2TradeHouseScientistText" },
|
|
},
|
|
},
|
|
}
|
|
|
|
M.CERULEAN_TRADE_HOUSE = {
|
|
talk = {
|
|
-- The Granny is plain flavor (points you to her husband); the Gambler
|
|
-- holds the trade (data/scripts/flavor/cerulean_trade_house.lua), per
|
|
-- CeruleanTradeHouse.asm.
|
|
TEXT_CERULEANTRADEHOUSE_GRANNY = {
|
|
{ "face_player" },
|
|
{ "show_text", "_CeruleanTradeHouseGrannyText" },
|
|
},
|
|
},
|
|
}
|
|
|
|
M.VERMILION_TRADE_HOUSE = {
|
|
talk = {
|
|
TEXT_VERMILIONTRADEHOUSE_LITTLE_GIRL = {
|
|
{ "face_player" },
|
|
{ "trade", 5, "EVENT_TRADED_SPEAROW_FOR_FARFETCHD" }, -- DUX
|
|
},
|
|
},
|
|
}
|
|
|
|
return M
|