mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-18 11:44:42 +02:00
so many bugs i cannot even breathe
This commit is contained in:
@@ -128,6 +128,23 @@ FieldDefaults.FIELD = {
|
||||
-- the one-shot flag the gate's pass text is gated on; a gate a mod adds
|
||||
-- gets "PASSED_<mapId>" instead of this pre-v2 spelling
|
||||
badgeGates = { ROUTE_22_GATE = { passedFlag = "PASSED_ROUTE22_GATE" } },
|
||||
-- the Rocket Hideout lift gates the floor callbacks stamp shut
|
||||
-- (scripts/RocketHideoutB1F.asm / RocketHideoutB4F.asm
|
||||
-- ...DoorCallbackScript); seeds caches imported before the manifest
|
||||
-- carried them, which left both gates standing open (#372)
|
||||
cardKeyDoors = {
|
||||
closedDoors = {
|
||||
ROCKET_HIDEOUT_B1F = {
|
||||
{ block = 0x54, bx = 12, by = 8, open = 0x0e,
|
||||
event = "EVENT_BEAT_ROCKET_HIDEOUT_1_TRAINER_4" },
|
||||
},
|
||||
ROCKET_HIDEOUT_B4F = {
|
||||
{ block = 0x2d, bx = 12, by = 5, open = 0x0e,
|
||||
events = { "EVENT_BEAT_ROCKET_HIDEOUT_4_TRAINER_0",
|
||||
"EVENT_BEAT_ROCKET_HIDEOUT_4_TRAINER_1" } },
|
||||
},
|
||||
},
|
||||
},
|
||||
-- VermilionGymSetDoorTile opens the motorized door once both locks are hit
|
||||
hiddenExtras = {
|
||||
-- PrintTrashText bins (#188); seeds stale caches missing the key
|
||||
|
||||
+166
-144
@@ -65,6 +65,16 @@ local ROD_OAM = {
|
||||
right = { dx = 16, dy = 4, tile = 1, flip = true }, -- dbsprite 11, 10, 0, 0, $fe, XFLIP
|
||||
}
|
||||
|
||||
-- field.darkMaps (home/overworld.asm's dark-map check): the floors that run
|
||||
-- with wMapPalOffset = 6 until FLASH
|
||||
local function isDarkMap(mapId)
|
||||
local darkDef = Game.data.field.darkMaps
|
||||
for _, m in ipairs(darkDef and darkDef.maps or {}) do
|
||||
if m == mapId then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- object_event spawn filter (toggleable_objects, items taken, beaten
|
||||
-- static encounters), shared by the current map's real NPCs and the
|
||||
-- visual-only ghosts on connected neighbor maps
|
||||
@@ -188,8 +198,50 @@ function OverworldState:enter(mapId, x, y, facing)
|
||||
-- survives save/load: a loaded game may start inside a building whose
|
||||
-- exit mat is a LAST_MAP warp
|
||||
self.lastOutdoor = Game.save.lastOutdoor
|
||||
self.justWarped = false
|
||||
self:setMap(mapId, x, y, facing, { via = "boot" })
|
||||
-- boot/load: derive the flag from the tile the save left us standing on,
|
||||
-- like MapEntryAfterBattle's IsPlayerStandingOnWarp, so a game saved on a
|
||||
-- door mat can still walk straight back out (issue #378)
|
||||
self:refreshStandingOnWarp()
|
||||
end
|
||||
|
||||
-- Silph Co card key doors + Rocket Hideout elevator gates: the .blk
|
||||
-- layouts ship with the doorways open; each floor's map script stamps
|
||||
-- the closed door block on load until its unlock event is set
|
||||
-- (scripts/SilphCo2F.asm SilphCo2FGateCallbackScript et al., closed
|
||||
-- blocks $54/$5f/$20; scripts/RocketHideoutB1F.asm +
|
||||
-- RocketHideoutB4F.asm ...DoorCallbackScript, closed blocks $54/$2d over
|
||||
-- the lift doorway). A door opens on its single `event`, or on `events`
|
||||
-- when every listed flag must be set (Rocket Hideout B4F's lift gate
|
||||
-- needs both guard trainers beaten -- CheckBothEventsSet). The callbacks
|
||||
-- run whenever BIT_CUR_MAP_LOADED_1 is set, which is map load AND the end
|
||||
-- of a battle on that map (home/trainers.asm EndTrainerBattle), so the
|
||||
-- gate opens with SFX_GO_INSIDE the moment the last guard falls (#372).
|
||||
function OverworldState:stampClosedDoors()
|
||||
local closedDoors = FieldDefaults.fieldValue(Game.data, "cardKeyDoors",
|
||||
"closedDoors")
|
||||
local floorDoors = self.map and closedDoors and closedDoors[self.map.id]
|
||||
if not floorDoors then return end
|
||||
local stamped, unlocked = false, false
|
||||
for _, door in ipairs(floorDoors) do
|
||||
local open
|
||||
if door.events then
|
||||
open = true
|
||||
for _, ev in ipairs(door.events) do
|
||||
if not Game.save.flags[ev] then open = false break end
|
||||
end
|
||||
else
|
||||
open = Game.save.flags[door.event]
|
||||
end
|
||||
local want = open and door.open or door.block
|
||||
if self.map:blockAt(door.bx, door.by) ~= want then
|
||||
self.map:setBlock(door.bx, door.by, want)
|
||||
stamped = true
|
||||
if open then unlocked = true end
|
||||
end
|
||||
end
|
||||
if stamped then self.map.renderer:rebuild() end
|
||||
if unlocked then require("src.core.Sound").play(Game.data, "Go_Inside") end
|
||||
end
|
||||
|
||||
function OverworldState:setMap(mapId, x, y, facing, opts)
|
||||
@@ -222,6 +274,13 @@ function OverworldState:setMap(mapId, x, y, facing, opts)
|
||||
self.tileAnimOverride.tileset.animation = self.tileAnimOverride.animation
|
||||
self.tileAnimOverride = nil
|
||||
end
|
||||
-- ADVANCED bakes colour into the tileset atlas, so the dark-cave shift has
|
||||
-- to be armed before that atlas is built for this map (#383); self.dark is
|
||||
-- settled below, once the map record is in hand.
|
||||
if PaletteFX.setDarkWorld(isDarkMap(mapId) and not Game.save.flashLit)
|
||||
and PaletteFX.usesGbcPack() then
|
||||
MapLoader.invalidateAll()
|
||||
end
|
||||
self.map = MapLoader.load(Game.data, mapId)
|
||||
-- STRENGTH deactivates on every real map load (home/overworld.asm
|
||||
-- EnterMap -> ResetUsingStrengthOutOfBattleBit clears BIT_STRENGTH_ACTIVE
|
||||
@@ -241,38 +300,7 @@ function OverworldState:setMap(mapId, x, y, facing, opts)
|
||||
self.map.renderer:rebuild()
|
||||
self.cutBlocks[mapId] = nil
|
||||
end
|
||||
-- Silph Co card key doors + Rocket Hideout elevator gates: the .blk
|
||||
-- layouts ship with the doorways open; each floor's map script stamps
|
||||
-- the closed door block on load until its unlock event is set
|
||||
-- (scripts/SilphCo2F.asm SilphCo2FGateCallbackScript et al., closed
|
||||
-- blocks $54/$5f/$20; scripts/RocketHideoutB1F.asm +
|
||||
-- RocketHideoutB4F.asm ...DoorCallbackScript, closed blocks $54/$2d over
|
||||
-- the lift doorway). A door opens on its single `event`, or on `events`
|
||||
-- when every listed flag must be set (Rocket Hideout B4F's lift gate
|
||||
-- needs both guard trainers beaten -- CheckBothEventsSet).
|
||||
local closedDoors = FieldDefaults.fieldValue(Game.data, "cardKeyDoors",
|
||||
"closedDoors")
|
||||
local floorDoors = closedDoors and closedDoors[mapId]
|
||||
if floorDoors then
|
||||
local stamped = false
|
||||
for _, door in ipairs(floorDoors) do
|
||||
local open
|
||||
if door.events then
|
||||
open = true
|
||||
for _, ev in ipairs(door.events) do
|
||||
if not Game.save.flags[ev] then open = false break end
|
||||
end
|
||||
else
|
||||
open = Game.save.flags[door.event]
|
||||
end
|
||||
local want = open and door.open or door.block
|
||||
if self.map:blockAt(door.bx, door.by) ~= want then
|
||||
self.map:setBlock(door.bx, door.by, want)
|
||||
stamped = true
|
||||
end
|
||||
end
|
||||
if stamped then self.map.renderer:rebuild() end
|
||||
end
|
||||
self:stampClosedDoors()
|
||||
-- forced dismount only where riding is disallowed (IsBikeRidingAllowed,
|
||||
-- home/overworld.asm: bike_riding_tilesets.asm tilesets plus the
|
||||
-- ROUTE_23/INDIGO_PLATEAU map exceptions)
|
||||
@@ -294,18 +322,11 @@ function OverworldState:setMap(mapId, x, y, facing, opts)
|
||||
-- Rock Tunnel darkness (wMapPalOffset, home/overworld.asm): dark
|
||||
-- until FLASH is used; the light persists between the tunnel floors
|
||||
-- and resets once outside
|
||||
local darkDef = Game.data.field.darkMaps
|
||||
self.dark = false
|
||||
if darkDef then
|
||||
local isDark = false
|
||||
for _, m in ipairs(darkDef.maps) do
|
||||
if m == mapId then isDark = true break end
|
||||
end
|
||||
if isDark then
|
||||
self.dark = not Game.save.flashLit
|
||||
else
|
||||
Game.save.flashLit = nil
|
||||
end
|
||||
if isDarkMap(mapId) then
|
||||
self:setDark(not Game.save.flashLit)
|
||||
else
|
||||
Game.save.flashLit = nil
|
||||
self:setDark(false)
|
||||
end
|
||||
if Game.data.field.flyWarps[mapId] then
|
||||
Game.save.visited = Game.save.visited or {}
|
||||
@@ -576,17 +597,19 @@ function OverworldState:sgbWorldZones()
|
||||
return zones
|
||||
end
|
||||
|
||||
-- Whether the dark-map shade shift (PaletteFX.DARK_BGP, armed in drawWorld)
|
||||
-- can actually reach this frame's world pass. It cannot in RED++: that mode
|
||||
-- bakes real per-tile colour into the tileset atlas and sgbWorldZones returns
|
||||
-- an EMPTY zone list above, so the world blits with no shade-remap shader at
|
||||
-- all and there is no palette left to permute. Only then does fxDark
|
||||
-- composite the darkness by hand (#322).
|
||||
function OverworldState:darkNeedsOverlay()
|
||||
if not self.dark then return false end
|
||||
local renderer = self.map and self.map.renderer
|
||||
return PaletteFX.usesGbcPack() and renderer ~= nil
|
||||
and renderer.gbcAtlas ~= nil
|
||||
-- wMapPalOffset, the one piece of state both halves of the darkness read:
|
||||
-- drawWorld arms PaletteFX.DARK_BGP off self.dark for the shade-remapped
|
||||
-- modes, and PaletteFX.setDarkWorld feeds the bakes ADVANCED does instead of
|
||||
-- shading (tileset atlas, sprite sheets) plus their cache keys. A bake cannot
|
||||
-- be re-shaded in place, so a change there rebuilds every resident map --
|
||||
-- every dark floor, not just this one, since FLASH lights them all (#383).
|
||||
function OverworldState:setDark(on)
|
||||
on = on and true or false
|
||||
self.dark = on
|
||||
if PaletteFX.setDarkWorld(on) and PaletteFX.usesGbcPack() and self.map then
|
||||
MapLoader.invalidateAll()
|
||||
self:reloadMap(self.map.id, "dark")
|
||||
end
|
||||
end
|
||||
|
||||
function OverworldState:npcByIndex(index)
|
||||
@@ -795,6 +818,15 @@ function OverworldState:update(dt)
|
||||
if ca.onDone then ca.onDone() end
|
||||
end
|
||||
end
|
||||
-- fishing pose tail: the rod is already gone, the pose holds for the
|
||||
-- frames the original spends unwinding the item menu (#384)
|
||||
if self.fishPose then
|
||||
self.fishPose = self.fishPose - 1
|
||||
if self.fishPose <= 0 then
|
||||
self.fishPose = nil
|
||||
self.player.fishing = nil
|
||||
end
|
||||
end
|
||||
-- Yellow's companion hopping up onto the Poke Center counter owns the
|
||||
-- world for its arc, the same way the heal machine below does (#417)
|
||||
if self.pikaHop then
|
||||
@@ -961,19 +993,33 @@ function OverworldState:dirHeld()
|
||||
or input:isDown("left") or input:isDown("right")
|
||||
end
|
||||
|
||||
-- The warp cell the player WARPED IN on is inert until they physically step
|
||||
-- off it: standing on it, or bonking a wall/edge from it, must not re-fire a
|
||||
-- warp (CheckWarpsNoCollision's arrival-disable; see setMap where
|
||||
-- warpEntryCell/justWarped are set and onStepComplete where they clear).
|
||||
-- Both stand-still warp triggers -- the map-edge exit (checkEdgeExit) and the
|
||||
-- blocked-step collision warp (handleInput) -- must consult this, or a corner
|
||||
-- staircase whose warp tile sits on the map edge (Red's-house (7,1)) bounces
|
||||
-- floors every input frame (issue #230).
|
||||
function OverworldState:onWarpArrivalCell()
|
||||
if self.justWarped then return true end
|
||||
local entry = self.warpEntryCell
|
||||
return entry ~= nil and self.player.cellX == entry.x
|
||||
and self.player.cellY == entry.y
|
||||
-- BIT_STANDING_ON_WARP (wMovementFlags): the warp under the player's feet may
|
||||
-- only fire from a collision -- the blocked-step warp (handleInput) and the
|
||||
-- map-edge exit (checkEdgeExit) -- while this flag is set. pokered clears it
|
||||
-- on every completed step, sets it again when that step lands on a warp
|
||||
-- square, then clears it once more when the square is a warp-activating tile
|
||||
-- that is not also a door tile (CheckWarpsNoCollisionLoop ->
|
||||
-- IsPlayerStandingOnDoorTileOrWarpTile, engine/overworld/player_state.asm);
|
||||
-- onStepComplete maintains it. ClearVariablesOnEnterMap does not clear
|
||||
-- wMovementFlags, so the flag rides through the warp itself: a house door
|
||||
-- tile ($1B) leaves it set, so you land on the interior mat still able to
|
||||
-- walk back out on that same tile (issue #378), while a staircase tile
|
||||
-- ($1A/$1C) clears it and cannot bounce you between floors (issue #230).
|
||||
function OverworldState:canCollisionWarp()
|
||||
return self.standingOnWarp == true
|
||||
end
|
||||
|
||||
-- Re-derive the flag from the tile under the player, the way a completed step
|
||||
-- does (and the way MapEntryAfterBattle's IsPlayerStandingOnWarp does after a
|
||||
-- battle): a door tile keeps it, a stair/ladder warp tile clears it.
|
||||
function OverworldState:refreshStandingOnWarp()
|
||||
local p = self.player
|
||||
self.standingOnWarp = false
|
||||
if self.map:warpAtCell(p.cellX, p.cellY)
|
||||
and not (self.map:isWarpTileCell(p.cellX, p.cellY)
|
||||
and not self.map:isDoorTileCell(p.cellX, p.cellY)) then
|
||||
self.standingOnWarp = true
|
||||
end
|
||||
end
|
||||
|
||||
function OverworldState:handleInput()
|
||||
@@ -1017,9 +1063,9 @@ function OverworldState:handleInput()
|
||||
local result, why = self.player:tryMove(dir, self.map, self.entities)
|
||||
-- a collision while standing on a warp square fires the warp when the
|
||||
-- extra check passes (CheckWarpsCollision: route-gate doorways, dock
|
||||
-- entrances, ...) -- but never on the inert cell we just warped in on
|
||||
-- (issue #230), which the completed-step path guards the same way.
|
||||
if result == "blocked" and not self:onWarpArrivalCell() then
|
||||
-- entrances, ...), and only while BIT_STANDING_ON_WARP is set (issue
|
||||
-- #230), which the map-edge path guards the same way.
|
||||
if result == "blocked" and self:canCollisionWarp() then
|
||||
local w = Warp.onCollision(self.map, Game.data.field.warpCarpets,
|
||||
self.player.cellX, self.player.cellY, dir)
|
||||
if w then
|
||||
@@ -1175,11 +1221,11 @@ function OverworldState:checkEdgeExit(dir)
|
||||
|
||||
local w = Warp.onEdge(self.map, p.cellX, p.cellY, dir)
|
||||
if w then
|
||||
-- ...but not while still standing on the warp cell we just arrived on
|
||||
-- (issue #230): fall through so pushing into the edge bonks (SFX +
|
||||
-- walk-in-place) instead of instantly re-warping. A real step onto an
|
||||
-- exit-carpet edge cleared warpEntryCell first, so those still fire.
|
||||
if self:onWarpArrivalCell() then return false end
|
||||
-- ...but only with BIT_STANDING_ON_WARP set: a staircase tile clears it,
|
||||
-- so pushing into the edge beside one bonks (SFX + walk-in-place) instead
|
||||
-- of bouncing floors (issue #230), while the door mat you warped in on
|
||||
-- keeps it and exits on that same tile (issue #378).
|
||||
if not self:canCollisionWarp() then return false end
|
||||
self:takeWarp(w.def)
|
||||
return true
|
||||
end
|
||||
@@ -1405,6 +1451,7 @@ function OverworldState:goFishing(rod)
|
||||
-- the bobber waits a beat before the verdict (the original's
|
||||
-- FishingInit dot animation); the rod pose draws in the meantime
|
||||
self.fishing = { facing = self.player.facing }
|
||||
self.player.fishing = true
|
||||
Game.stack:push(TextBox.new(Game, ". . .", function()
|
||||
-- FishingAnim (engine/overworld/player_animations.asm) holds
|
||||
-- BIT_LEDGE_OR_FISHING -- the rod OAM and the fishing pose -- through
|
||||
@@ -1412,12 +1459,20 @@ function OverworldState:goFishing(rod)
|
||||
-- must NOT vanish with the dots box (#321).
|
||||
if not enc then
|
||||
Game.stack:push(TextBox.new(Game, Strings("Not even a nibble!"), function()
|
||||
-- the rod OAM goes out with the verdict box (res BIT_LEDGE_OR_FISHING
|
||||
-- straight after PrintText) but the player keeps the patched tiles
|
||||
-- until the overworld reloads them a few frames later
|
||||
-- (RestoreScreenTilesAndReloadTilePatterns, home/palettes.asm ->
|
||||
-- ReloadMapSpriteTilePatterns, home/reload_sprites.asm) -- #384
|
||||
self.fishing = nil
|
||||
self.fishPose = 10
|
||||
end))
|
||||
return
|
||||
end
|
||||
Game.stack:push(TextBox.new(Game, Strings("Oh!\nIt's a bite!"), function()
|
||||
-- the bite goes straight into battle, which reloads the sprite tiles
|
||||
self.fishing = nil
|
||||
self.player.fishing = nil
|
||||
local BattleState = require("src.battle.BattleState")
|
||||
local battle = BattleState.newWild(Game, enc.species, enc.level, { hooked = true })
|
||||
if Game.save.safari and Map.inRegion(self.map.def, "SAFARI", "SAFARI_ZONE") then
|
||||
@@ -2098,25 +2153,26 @@ end
|
||||
-- Gen 1 has no confirmation prompt: using SURF gets straight on
|
||||
-- (_SurfingGotOnText, item_effects.asm .surf). Called from the party
|
||||
-- menu's SURF action (via useSurfFieldMove) once the facing tile has been
|
||||
-- confirmed to be water -- there is no overworld A-press hook.
|
||||
function OverworldState:trySurf(fx, fy)
|
||||
-- confirmed to be water -- there is no overworld A-press hook. onClose is
|
||||
-- that menu's own close, called when the got-on text ends (see below).
|
||||
function OverworldState:trySurf(fx, fy, onClose)
|
||||
local mon = self:partyKnows("SURF")
|
||||
if not mon then return end
|
||||
local name = mon.nickname or Game.data.pokemon[mon.species].name
|
||||
local p = self.player
|
||||
local text = (Game.data.text._SurfingGotOnText or Strings("{PLAYER} got on\n{RAM:wNameBuffer}!"))
|
||||
:gsub("{RAM:wNameBuffer}", name)
|
||||
-- GBPalWhiteOutWithDelay3 runs while the got-on text is still up
|
||||
-- (start_sub_menus.asm .surf), so the blink reads as a text flash
|
||||
-- instead of a flashbang on the empty map (#320). The flash sits
|
||||
-- under the textbox on the stack: only the top state updates, so it
|
||||
-- holds its frames until the text closes. surfing (and the sprite
|
||||
-- swap) only applies when the step happens -- no paddling on land.
|
||||
Game.stack:push(require("src.render.Transition").whiteFlash(Game))
|
||||
-- UseItem prints the got-on text with the party menu still on screen and
|
||||
-- GBPalWhiteOutWithDelay3 + .goBackToMap only run after it
|
||||
-- (start_sub_menus.asm .surf), so the text reads over the menu and the
|
||||
-- blink is the menu closing, not a flashbang on the empty map (#320,
|
||||
-- #385). The mount rides the blink, so nothing paddles on land.
|
||||
Game.stack:push(TextBox.new(Game, text, function()
|
||||
if onClose then onClose() end
|
||||
p.surfing = true
|
||||
require("src.core.Music").setSurfing(Game.data, true)
|
||||
self:stepForwardOrCrossEdge(p.facing)
|
||||
Game.stack:push(require("src.render.Transition").whiteFlash(Game, nil,
|
||||
function() self:stepForwardOrCrossEdge(p.facing) end))
|
||||
end))
|
||||
end
|
||||
|
||||
@@ -3050,18 +3106,16 @@ function OverworldState:onStepComplete()
|
||||
entry = nil
|
||||
end
|
||||
-- The arrival disable is POSITIONAL: warpEntryCell above is the whole
|
||||
-- test. justWarped only records that an arrival happened (it still
|
||||
-- backs onWarpArrivalCell's bonk guard for issue #230), so consuming a
|
||||
-- completed step with it swallowed the warp under the player's feet,
|
||||
-- which is why a second ladder one cell from the first did nothing
|
||||
-- (Seafoam B3F has warp tiles on (25,3) and (25,4)) -- issue #265.
|
||||
-- pokered has no such counter: every completed step runs
|
||||
-- CheckWarpsNoCollision (home/overworld.asm), and BIT_STANDING_ON_WARP,
|
||||
-- the flag the bonk path needs, is only set by
|
||||
-- CheckWarpsNoCollisionLoop itself or by IsPlayerStandingOnWarp from
|
||||
-- MapEntryAfterBattle, never on a plain warp arrival -- which is
|
||||
-- exactly what warpEntryCell reproduces.
|
||||
self.justWarped = false
|
||||
-- test. Consuming a completed step with a one-shot "just warped" counter
|
||||
-- instead swallowed the warp under the player's feet, which is why a
|
||||
-- second ladder one cell from the first did nothing (Seafoam B3F has warp
|
||||
-- tiles on (25,3) and (25,4)) -- issue #265. pokered has no such counter:
|
||||
-- every completed step runs CheckWarpsNoCollision (home/overworld.asm).
|
||||
-- That same step is where BIT_STANDING_ON_WARP is maintained: cleared
|
||||
-- before the check (home/overworld.asm:324), set again while standing on a
|
||||
-- warp square, then cleared once more when the square is a warp-activating
|
||||
-- tile that is not a door tile (IsPlayerStandingOnDoorTileOrWarpTile).
|
||||
self:refreshStandingOnWarp()
|
||||
if entry then
|
||||
-- still standing on the warp we arrived through; do not re-trigger it
|
||||
else
|
||||
@@ -3499,6 +3553,10 @@ function OverworldState:afterBattle(result, battle)
|
||||
{ save = Game.save, healTarget = self:healPoint() })
|
||||
self:warpToHealPoint(evolutions)
|
||||
else
|
||||
-- EndTrainerBattle sets BIT_CUR_MAP_LOADED_1 (home/trainers.asm), which
|
||||
-- re-runs the floor's door callback: beating the last Rocket Hideout guard
|
||||
-- opens the lift gate without leaving the map (#372)
|
||||
self:stampClosedDoors()
|
||||
-- throwing the last SAFARI BALL ends the game
|
||||
if Game.save.safari and Game.save.safari.balls <= 0 then
|
||||
self:safariGameOver(Strings("PA: You're out of\nSAFARI BALLs!"))
|
||||
@@ -3632,12 +3690,12 @@ function OverworldState:startWarpTo(mapId, x, y, facing, onDone, opts)
|
||||
self.arriveWarp = nil
|
||||
Game.stack:push(Transition.new(Game, function()
|
||||
self:setMap(mapId, x, y, facing or "down", opts)
|
||||
self.justWarped = true
|
||||
-- The warp we land ON stays inert until we physically step off it, so a
|
||||
-- warp whose destination cell is itself a warp cannot bounce us straight
|
||||
-- back (elevator cars, stacked stair/door mats). This generalizes the
|
||||
-- one-step justWarped guard, which only skipped the very next frame's
|
||||
-- check and so let a mon walked back onto the pad re-trigger it.
|
||||
-- The warp we land ON stays inert for the completed-step check until we
|
||||
-- physically step off it, so a warp whose destination cell is itself a
|
||||
-- warp cannot bounce us straight back (elevator cars, stacked stair/door
|
||||
-- mats). BIT_STANDING_ON_WARP is deliberately NOT touched here:
|
||||
-- ClearVariablesOnEnterMap leaves wMovementFlags alone, so the flag the
|
||||
-- departing tile set rides through the warp (issue #378).
|
||||
self.warpEntryCell = { x = x, y = y }
|
||||
-- Fly/Teleport/Dig/Escape-Rope landings poof the player back in
|
||||
-- (player_animations.asm EnterMapAnim). Blackouts and ordinary
|
||||
@@ -3663,8 +3721,8 @@ function OverworldState:startWarpTo(mapId, x, y, facing, onDone, opts)
|
||||
-- PlayerStepOutFromDoor (engine/overworld/auto_movement.asm): any
|
||||
-- warp that lands on a door tile auto-steps south once, indoor or
|
||||
-- outdoor. Auto-walk leaves the mat, so the arrival disable
|
||||
-- (warpEntryCell / justWarped) is unnecessary -- and would let you
|
||||
-- stand on the door without re-entering if you hold back into it.
|
||||
-- (warpEntryCell) is unnecessary -- and would let you stand on the
|
||||
-- door without re-entering if you hold back into it.
|
||||
-- The walk-out is a simulated d-pad press (wSimulatedJoypadStates),
|
||||
-- not a forced move, so it obeys collision: on a landing with a
|
||||
-- solid cell south of the door (the mansion stair landings back
|
||||
@@ -3673,7 +3731,6 @@ function OverworldState:startWarpTo(mapId, x, y, facing, onDone, opts)
|
||||
if self.map:isDoorTileCell(self.player.cellX, self.player.cellY) then
|
||||
if Collision.canMove(self.map, self.entities, self.player, "down") then
|
||||
self.warpEntryCell = nil
|
||||
self.justWarped = false
|
||||
self:scriptMove(self.player, "down", 1)
|
||||
else
|
||||
self.player.facing = "down"
|
||||
@@ -4116,23 +4173,6 @@ function OverworldState:drawWorld()
|
||||
end
|
||||
end
|
||||
|
||||
-- Rock Tunnel darkness. The original never cuts a window of light around
|
||||
-- the player: it shifts the BG palette for the WHOLE screen (wMapPalOffset
|
||||
-- = 6 -> home/fade.asm LoadGBPal -> FadePal2 `dc 3,3,3,2`) and FLASH shifts
|
||||
-- it back (#322). PaletteFX.DARK_BGP does that for every shade-remapped
|
||||
-- mode, armed at the top of drawWorld. RED++ is the one mode with no
|
||||
-- palette left to shift -- TileRenderer bakes true colour into the tileset
|
||||
-- atlas and sgbWorldZones hands the blit an EMPTY zone list, so no shader
|
||||
-- runs over the world at all -- so there the darkness is composited instead:
|
||||
-- a flat veil over the whole world view (surveying still cannot peek past
|
||||
-- it) at the 85/255 brightness FadePal2 leaves DMG white on.
|
||||
local function fxDark()
|
||||
if not self:darkNeedsOverlay() then return end
|
||||
love.graphics.setColor(0, 0, 0, 1 - 85 / 255)
|
||||
love.graphics.rectangle("fill", 0, 0, vw, vh)
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
end
|
||||
|
||||
-- the FLY bird sweeping off with the player
|
||||
local function fxBird()
|
||||
if not self.flyAnim then return end
|
||||
@@ -4210,7 +4250,7 @@ function OverworldState:drawWorld()
|
||||
return PaletteFX.pal(Game.data, self:paletteNameFor(map or self.map))
|
||||
end,
|
||||
fx = { heal = fxHeal, dust = fxDust, cutTree = fxCutTree,
|
||||
emote = fxEmote, dark = fxDark, bird = fxBird, rod = fxRod },
|
||||
emote = fxEmote, bird = fxBird, rod = fxRod },
|
||||
}
|
||||
-- Draw every active field FX into the finished scene. `project(wx, wy)`
|
||||
-- maps a world point to canvas pixels (nil when it is behind the
|
||||
@@ -4261,17 +4301,6 @@ function OverworldState:drawWorld()
|
||||
if self.fishing then
|
||||
at(fxRod, self.player.px + 8, self.player.py + 16)
|
||||
end
|
||||
-- Rock Tunnel darkness is a screen-space veil, not a ground object:
|
||||
-- draw it flat over the finished scene like the tilt path. It fills
|
||||
-- the view in world-pixel units, so it only needs the scale, and it is
|
||||
-- only needed at all in the mode whose palette cannot carry the
|
||||
-- darkening itself (see fxDark).
|
||||
if self:darkNeedsOverlay() then
|
||||
love.graphics.push()
|
||||
love.graphics.scale(scale, scale)
|
||||
fxDark()
|
||||
love.graphics.pop()
|
||||
end
|
||||
end
|
||||
override = Pipelines.drawWorld(pipelineId, ctx)
|
||||
-- world post-processes (a miniature-diorama blur, a colour grade) fold
|
||||
@@ -4334,7 +4363,6 @@ function OverworldState:drawWorld()
|
||||
fxDust()
|
||||
fxCutTree()
|
||||
fxEmote()
|
||||
fxDark()
|
||||
fxBird()
|
||||
fxRod()
|
||||
else
|
||||
@@ -4423,12 +4451,6 @@ function OverworldState:drawWorld()
|
||||
self:billboard(fx, fy, vw, vh, zoneColorsAt(zones, fx, fy), false, fxRod)
|
||||
end
|
||||
|
||||
-- Rock Tunnel darkness is a screen-space veil, not a ground object --
|
||||
-- draw it flat into the upright canvas so it darkens the final
|
||||
-- composited scene uniformly. It no-ops unless this mode needs the
|
||||
-- composited fallback (see fxDark).
|
||||
fxDark()
|
||||
|
||||
Game.renderer:endUprightPass()
|
||||
end
|
||||
|
||||
|
||||
+29
-1
@@ -42,6 +42,20 @@ function Player.new(data, cx, cy, facing)
|
||||
local ok, img = pcall(love.graphics.newImage, fx.shadow.path)
|
||||
self.shadowImg = ok and img or nil
|
||||
end
|
||||
-- FishingAnim (engine/overworld/player_animations.asm) patches tiles
|
||||
-- $02/$06/$0a -- the bottom tile row of each standing frame -- with
|
||||
-- RedFishingTiles before it parks the rod OAM, so the rod stroke meets a
|
||||
-- pair of hands instead of ending in mid air (#384)
|
||||
if fx then
|
||||
local function posePath(name)
|
||||
local def = fx[name]
|
||||
return def and def.path or nil
|
||||
end
|
||||
local pose = { down = posePath("redFishFront"), up = posePath("redFishBack") }
|
||||
pose.left = posePath("redFishSide")
|
||||
pose.right = pose.left -- the side pose mirrors like the sprite (OAM_XFLIP)
|
||||
if pose.down or pose.up or pose.left then self.fishTiles = pose end
|
||||
end
|
||||
self.cellX, self.cellY = cx, cy
|
||||
self.px, self.py = cx * 16, cy * 16
|
||||
self.facing = facing or "down"
|
||||
@@ -231,7 +245,10 @@ function Player:pose()
|
||||
py = py - math.floor((total - self.spinFrames) * 24 / total)
|
||||
end
|
||||
end
|
||||
local sprite = (self.surfing and self.surfSprite)
|
||||
-- RodResponse (engine/items/item_effects.asm) zeroes wWalkBikeSurfState
|
||||
-- across FishingAnim, so casting from the water shows the on-foot sheet
|
||||
local sprite = (self.fishing and self.sprite)
|
||||
or (self.surfing and self.surfSprite)
|
||||
or (self.onBike and self.bikeSprite) or self.sprite
|
||||
return sprite, self.px, py, facing, phase, flip, hopping
|
||||
end
|
||||
@@ -263,6 +280,17 @@ function Player:draw(camX, camY)
|
||||
love.graphics.draw(self.shadowImg, sx + 16, sy + 16, 0, -1, -1)
|
||||
end
|
||||
end
|
||||
-- Fishing pose: the standing frame with its bottom tile row swapped for
|
||||
-- RedFishingTiles, which is where the hands and the near half of the rod
|
||||
-- live; the far half is the rod OAM OverworldState draws (FishingRodOAM,
|
||||
-- engine/overworld/player_animations.asm) -- #384
|
||||
local fishTile = self.fishing and self.fishTiles and self.fishTiles[facing]
|
||||
if fishTile then
|
||||
sprite:draw(px, py, camX, camY, facing, 0, false, true)
|
||||
sprite:drawTile(fishTile, math.floor(px - camX),
|
||||
math.floor(py - camY) - 4 + 8, facing == "right")
|
||||
return
|
||||
end
|
||||
sprite:draw(px, py, camX, camY, facing, phase, flip)
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user