CLOSES #788, CLOSES #795, CLOSES #796, CLOSES #797, CLOSES #805, CLOSES #826, CLOSES #833, CLOSES #835, CLOSES #837, CLOSES #844, CLOSES #845, CLOSES #846, CLOSES #847

This commit is contained in:
bryanthaboi
2026-08-05 11:09:05 -04:00
parent d18824cfae
commit f6392e8932
32 changed files with 2776 additions and 114 deletions
+81 -21
View File
@@ -2362,8 +2362,21 @@ function OverworldState:trySurf(fx, fy, onClose)
Game.stack:push(TextBox.new(Game, text, function()
if onClose then onClose() end
p.surfing = true
-- walking / biking / surfing is ONE state byte in the original:
-- ItemUseSurfboard (engine/items/item_effects.asm) writes 2 over
-- whatever wWalkBikeSurfState held, so mounting a surf ends the bike
-- outright -- no bike step cadence in Player:tryMove and no bike
-- theme on the water (#846). Music.playMap re-picks the override
-- with BOTH flags, which setSurfing alone cannot do: effectiveMapSong
-- (src/core/Music.lua) prefers state.onBike over state.surfing.
Game.save.onBike = false
self:syncSurfingPikachu()
require("src.core.Music").setSurfing(Game.data, true)
local Music = require("src.core.Music")
if self.map then
Music.playMap(Game.data, self.map.id, false, true)
else
Music.setSurfing(Game.data, true) -- headless harness with no map loaded
end
Game.stack:push(require("src.render.Transition").whiteFlash(Game, nil,
function() self:stepForwardOrCrossEdge(p.facing) end))
end))
@@ -3036,9 +3049,16 @@ function OverworldState:checkVictoryRewards(trainerClass, partyIndex)
if reward.badge then
Game.save.inventory[reward.badge] = 1
end
-- GiveItem can refuse at the bag cap: the leaders' post-battle scripts
-- (`call GiveItem` / `jr nc, .BagFull` in scripts/PewterGym.asm
-- PewterGymScriptReceiveTM34 and its siblings) then skip the received-TM
-- text and print the gym's *TM*NoRoomText instead, while .gymVictory
-- still awards the badge. Going through Bag.add keeps the cap and the
-- wBagItems order honest -- the raw inventory write bypassed both (#797).
local gotItem = false
if reward.item then
local inv = Game.save.inventory
inv[reward.item] = (inv[reward.item] or 0) + 1
gotItem = require("src.inventory.Bag").add(
Game.save, reward.item, 1, Game.data)
local idef = Game.data.items[reward.item]
-- GiveItem -> CopyToStringBuffer for "{RAM:wStringBuffer}" received texts
Game.stringBuffer = idef and idef.name or reward.item
@@ -3046,7 +3066,21 @@ function OverworldState:checkVictoryRewards(trainerClass, partyIndex)
local lines = {}
if reward.dialogue then
local text = Game.data.text or {}
for _, label in ipairs(reward.dialogue) do
-- itemDialogue is the received-TM tail the original prints only when
-- GiveItem succeeded; bagFull is the alternate line for when it did not
local chain = reward.dialogue
if reward.itemDialogue or reward.bagFull then
chain = {}
for _, label in ipairs(reward.dialogue) do chain[#chain + 1] = label end
if gotItem then
for _, label in ipairs(reward.itemDialogue or {}) do
chain[#chain + 1] = label
end
elseif reward.bagFull then
chain[#chain + 1] = reward.bagFull
end
end
for _, label in ipairs(chain) do
if text[label] and text[label] ~= "" then
table.insert(lines, text[label])
end
@@ -3057,7 +3091,7 @@ function OverworldState:checkVictoryRewards(trainerClass, partyIndex)
or reward.badge
table.insert(lines, Strings("%s received\nthe %s!", Game.save.player.name, name))
end
if reward.item then
if reward.item and gotItem then
local name = Game.stringBuffer or reward.item
table.insert(lines, Strings("%s received\n%s!", Game.save.player.name, name))
end
@@ -3620,9 +3654,13 @@ function OverworldState:checkForcedMovement()
return true
end
elseif tile.mode == "surf" then
-- scripts/SeafoamIslandsB4F.asm writes wWalkBikeSurfState = 2 and
-- jp ForceBikeOrSurf, so a forced surf clears the bike state the
-- same way the party-menu mount does (#846)
p.surfing = true
Game.save.onBike = false
self:syncSurfingPikachu()
require("src.core.Music").setSurfing(Game.data, true)
require("src.core.Music").playMap(Game.data, self.map.id, false, true)
end
return false
end
@@ -3913,25 +3951,47 @@ function OverworldState:warpToHealPoint(onDone, opts)
-- Dig/Teleport/Escape Rope land OUTSIDE at the last Pokemon Center TOWN
-- door, like Fly (#196) -- NOT the interior heal cell a blackout returns
-- to. pret routes escape-warp and blackout both through wLastBlackoutMap
-- (both appear inside in front of the nurse), but this port has decided
-- the escape-warp destination is the town PC door. Prefer the canonical
-- Fly landing (field.flyWarps, one tile south of the PC door warp), else
-- the remembered outdoor door cell; fall back to the interior heal cell
-- only for an old save with no recorded outdoor.
local out = heal.outdoor
if out then
local fw = (Game.data.field.flyWarps or {})[out.id]
map = out.id
x = fw and fw.x or out.x
y = fw and fw.y or out.y
-- (LoadSpecialWarpData .usedFlyWarp, engine/overworld/special_warps.asm),
-- and that map is ALWAYS an outdoor one: SetLastBlackoutMap copies
-- wLastMap (engine/events/set_blackout_map.asm) and WarpFound2 only
-- writes wLastMap on outside maps (home/overworld.asm), with the landing
-- cell read from FlyWarpDataPtr. Prefer the canonical Fly landing
-- (field.flyWarps, one tile south of the PC door warp), else the
-- remembered outdoor door cell.
--
-- A heal record naming no outdoor town, or naming a map that is not
-- outdoors at all, is never a legal escape-warp destination: a .sav
-- import stamps lastHeal from wherever the cartridge was saved
-- (SaveConvert mergeDefaults), so ESCAPE ROPE was dropping the player
-- into the dungeon that save sat in, whose LAST_MAP exits then still
-- pointed at the door they had walked in through (#805). Vanilla's
-- zero-filled wLastBlackoutMap is map 0, so an unusable record falls
-- back to the boot heal town exactly as a never-healed game does.
local out = heal.outdoor or { id = heal.map, x = heal.x, y = heal.y }
local fw = (Game.data.field.flyWarps or {})[out.id]
local outX = fw and fw.x or out.x
local outY = fw and fw.y or out.y
local outDef = Game.data.maps[out.id]
if not (outDef and outX and outY
and Map.isOutside(outDef,
FieldDefaults.field(Game.data, "outsideTilesets"))) then
local zeroFill = require("src.core.SaveData")
.defaultHeal(Game.data.field.boot)
out, outX, outY = { id = zeroFill.map }, zeroFill.x, zeroFill.y
end
map, x, y = out.id, outX, outY
end
self:startWarpTo(map, x, y, "down", onDone)
-- Blackouts land at the interior heal cell, so re-point LAST_MAP exits at
-- the remembered town door. The teleport branch already lands ON that
-- outdoor map, so startWarpTo remembers it on the next exit; re-pointing
-- here would wrongly steer exits away from where the player now stands.
if heal.outdoor and not teleport then
-- the remembered town door. The teleport branch re-points at the town it
-- just landed on: PrepareForSpecialWarp (engine/overworld/special_warps.asm)
-- writes the special-warp destination straight back into wLastMap for every
-- fly/escape warp that is not a dungeon warp, so the next LAST_MAP exit
-- resolves against that town instead of the dungeon door the player walked
-- in through before using the rope (#805).
if teleport then
self:rememberOutdoor(map, x, y)
elseif heal.outdoor then
self:rememberOutdoor(heal.outdoor.id, heal.outdoor.x, heal.outdoor.y)
end
end