CLOSES #1474, CLOSES #1475, CLOSES #1477, CLOSES #1482, CLOSES #1512, CLOSES #1516, CLOSES #1558, CLOSES #1569, CLOSES #1578, CLOSES #1579

This commit is contained in:
bryanthaboi
2026-08-20 16:04:12 -04:00
parent 83463a5a59
commit 0f8f6d0e4f
51 changed files with 2331 additions and 205 deletions
+30 -4
View File
@@ -2969,6 +2969,25 @@ function OverworldState:openPC(onDone)
})
end
-- PKMN LEAGUE sits between PROF.OAK's PC and LOG OFF once wNumHoFTeams
-- is nonzero (engine/pokemon/bills_pc.asm:5, :49)
if #(Game.save.hallOfFame or {}) > 0 then
table.insert(items, {
label = Strings("<PK><MN>LEAGUE"),
keepOpen = true,
onSelect = function()
-- pc.asm PKMNLeague plays SFX_ENTER_PC, then PKMNLeaguePC prints
-- AccessedHoFPCText (engine/menus/pc.asm:67, league_pc.asm:2)
require("src.core.Sound").play(Game.data, "Enter_PC")
Game.stack:push(TextBox.new(Game,
romText(Game.data, "_AccessedHoFPCText",
"Accessed POKéMON\nLEAGUE's site.\fAccessed the HALL\nOF FAME List."),
function() Screens.push(Game, "LeaguePC") end))
done()
end,
})
end
local hooked = Runtime.call("ui.pc.items", sameItems, Game, items)
if type(hooked) == "table" then
items = hooked
@@ -3366,7 +3385,8 @@ function OverworldState:engageTrainer(npc, onDone, endBattleText, skipBattleText
-- checkVictoryRewards pushes the badge/prize box and starts the map's
-- onVictory script UNDER whatever runs next, so the player still sees
-- EndBattle (now inside the battle), then the reward, then AfterBattle
self:checkVictoryRewards(d.trainerClass, d.trainerParty)
self:checkVictoryRewards(d.trainerClass, d.trainerParty,
endBattleText ~= nil)
self:afterBattle(result, battle)
if onDone then onDone() end
else
@@ -3477,7 +3497,10 @@ end
-- SetEvent / SetEventRange do after the leader victory.
-- `hide` is { { mapId, objName }, ... } -- HideObject on those toggles
-- (e.g. Brock victory clears PEWTERCITY_YOUNGSTER / ROUTE22_RIVAL1).
function OverworldState:checkVictoryRewards(trainerClass, partyIndex)
-- `shownOnBattleScreen`: `dialogue` already rode the battle screen as the
-- armed end-battle line (scripts/CeruleanGym.asm:113)
function OverworldState:checkVictoryRewards(trainerClass, partyIndex,
shownOnBattleScreen)
local victories = require("data.scripts.victories")
local reward = victories[trainerClass .. "#" .. tostring(partyIndex or 1)]
if not reward then return self:runVictoryHook() end
@@ -3510,7 +3533,9 @@ function OverworldState:checkVictoryRewards(trainerClass, partyIndex)
end
local chain = rewardChain()
if reward.dialogue then
chain.add(reward.dialogue, reward.badgeSound)
if not shownOnBattleScreen then
chain.add(reward.dialogue, reward.badgeSound)
end
if reward.item then
chain.add(reward.tmPre)
if tmGiven then
@@ -4423,7 +4448,8 @@ function OverworldState:restoreBattleContinuation(battle, origin)
if result == "win" then
game.save.defeatedTrainers[origin.npcId] = true
if origin.event then game.save.flags[origin.event] = true end
self:checkVictoryRewards(battle.oppClass, battle.partyIndex)
self:checkVictoryRewards(battle.oppClass, battle.partyIndex,
battle.endBattleText ~= nil)
end
self:afterBattle(result, battle)
self.engaging = false
+37 -9
View File
@@ -391,15 +391,19 @@ end
-- Gen 2 moveset lives in `levelMoves` (EvosAttacks). So every scripted gift,
-- the STARTER included, arrived knowing nothing: FIGHT listed no moves and the
-- battle had no legal action left in it.
local function givePokeMon(data, speciesIndex, level, itemIndex)
local function givePokeMon(data, speciesIndex, level, itemIndex, opts)
local id = speciesByIndex(data.pokemon, speciesIndex)
if not id then return nil end
return Mon.new(data, id, level or 5, {
item = itemIndex and itemIndex ~= 0
and itemByIndex(data.items, itemIndex) or nil,
nickname = opts and opts.nickname or nil,
})
end
-- GivePoke's trainer arm (engine/pokemon/move_mon.asm:1698-1736)
local RANDY_OT_ID = 1001
local function loadGenerated(path)
-- Same NX gold/ fallback Game2 uses. World:load is what surfaces
-- "Gold cache incomplete" when maps.lua is invisible at the unprefixed path.
@@ -997,13 +1001,18 @@ function World:load()
setStringBuffer = function(value)
if self.game then self.game.stringBuffer = value end
end,
givePoke = function(speciesIndex, level, item)
givePoke = function(speciesIndex, level, item, opts)
local data = self.game and self.game.data
local save = self.game and self.game.save
if not (data and save) then return end
save.party = save.party or {}
local mon = givePokeMon(data, speciesIndex, level, item)
local mon = givePokeMon(data, speciesIndex, level, item, opts)
if mon then
if opts and opts.otName then
mon.ot = opts.otName
mon.otName = opts.otName
mon.otId = RANDY_OT_ID
end
-- GivePoke -> TryAddMonToParty -> AddPartyMon (move_mon.asm:44-56, :143-149).
Mon.stampOT(save, mon)
Party.add(save.party, mon)
@@ -1458,13 +1467,11 @@ function World:mapSceneOf(group, mapNum)
end
-- wTimeOfDay (constants/ram_constants.asm): MORN_F 0, DAY_F 1, NITE_F 2,
-- DARKNESS_F 3. Palettes.daytimeFor has already resolved the clock and the
-- map's own PALETTE_* override into one of four names, so this is a rename
-- rather than a second clock.
-- DARKNESS_F 3, off the RTC hour (engine/tilesets/timeofday_pals.asm:5-11)
local TIME_OF_DAY_ID = { MORN = 0, DAY = 1, NITE = 2, DARK = 3 }
function World:timeOfDayId()
return TIME_OF_DAY_ID[self.daytime or "DAY"] or 1
return TIME_OF_DAY_ID[self.tod or self.daytime or "DAY"] or 1
end
-- GetWeekday -> wCurDay, which the RTC counts SUNDAY 0 .. SATURDAY 6 -- the
@@ -4369,6 +4376,8 @@ function World:useFieldItem(itemId)
if itemId == "SACRED_ASH" then return self:useSacredAsh() end
if itemId == "ESCAPE_ROPE" then return self:useEscapeRope(itemId) end
if itemId == "SQUIRTBOTTLE" then return self:useSquirtbottle() end
-- CoinCaseEffect (engine/items/item_effects.asm:2243).
if itemId == "COIN_CASE" then return "coin_case", self:coins() end
if REPEL_STEPS[itemId] then return self:useRepel(itemId) end
if TROPHY_BOXES[itemId] then return self:openTrophyBox(itemId) end
if not World.isRod(itemId, items) then return nil end
@@ -5802,7 +5811,9 @@ function World:battleMusicContext(opts)
members = trainer and trainer.classId and members
and members[trainer.classId] or nil,
landmark = self.map and self.map.def and self.map.def.landmark,
daytime = self.daytime,
-- PlayBattleMusic reads wTimeOfDay (engine/battle/start_battle.asm:24),
-- not the map's pinned palette set.
daytime = self.tod,
}
end
@@ -5999,6 +6010,18 @@ function World:startBattle(opts, onDone)
return true
end
-- wWinTextPointer / wLossTextPointer (home/trainers.asm:120), overwritten by
-- `winlosstext` (engine/overworld/scripting.asm:651)
function World:trainerWinLossText()
local vm = self.vm
if not vm then return nil, nil end
local obj = vm.trainerObject or {}
local text = self.text or {}
local winKey = vm.winTextOverride or obj.winText
local lossKey = vm.lossTextOverride or obj.lossText
return winKey and text[winKey] or nil, lossKey and text[lossKey] or nil
end
-- `startbattle` from a script: a trainer record (class + member) or a
-- loadwildmon pair. The VM is parked on the yield until onDone fires, so the
-- rest of the trainer script (flag set, after-battle text) runs on return.
@@ -6057,6 +6080,9 @@ function World:startScriptedBattle(record, wild, onDone)
attributes = record.attributes,
items = record.items,
}
-- wWinTextPointer / wLossTextPointer, read by PrintWinLossText on the
-- battle screen (home/trainers.asm:230) (#1512)
opts.trainer.winText, opts.trainer.lossText = self:trainerWinLossText()
elseif wild and wild.species then
local id, def = speciesByIndex(data and data.pokemon, wild.species)
-- InitEnemyMon `.NotRoaming` / BATTLETYPE.FORCESHINY: the DV pair is
@@ -9194,8 +9220,10 @@ function World:stepContext()
local def = self.map and self.map.def
return {
data = self.game and self.game.data,
-- CheckTime reads wTimeOfDay (engine/events/checktime.asm:2), so the
-- caller windows follow the clock even inside a pinned-palette room.
phone = {
map = def, maps = self.maps, daytime = self.daytime,
map = def, maps = self.maps, daytime = self.tod,
clock = self.game and self.game.clock,
},
-- GetMapPhoneService: zero means the map HAS service, which maps.lua has