mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-17 11:11:10 +02:00
feat(yellow): port SurfingPikachu behaviour
Yellow's IsSurfingPikachuInParty swaps the player's overworld sheet to a Pikachu-on-a-surfboard when the party mon that knows SURF is a Pikachu. The recomp was missing both halves of this: the sheet was never extracted, and the engine had no seam for the swap. Extraction: SurfingPikachuSprite (gfx/sprites/surfing_pikachu.2bpp) loads outside SpriteSheetPointerTable via LoadSurfingPlayerSpriteGraphics2, the same bypass RedBikeSprite uses. Added the symbol to the Yellow manifest and a parallel extract in RomExtractor / build_rom_data / extract/sprites, minting SPRITE_SURFING_PIKACHU. PaletteFX colors it (player OBP palette, same as the bike). Engine: new field.playerSprites.surfPikachu (default SPRITE_SURFING_PIKACHU, guarded so before extraction the ride keeps the Seel). Player.new caches surfPikachuSprite; pose() picks it when surfing and the SURF-mon is a Pikachu. New OverworldState:syncSurfingPikachu derives the flag from partyKnows at every surf-state toggle (mount, dismount, fly, teleport, blackout, forced-surf tile, boot-restore). Runtime-only, re-derived at load so a party change between save and load is honored. Lane B: RFC at docs/rfcs/0001-surfing-pikachu-sprite.md. Backward-compatible — existing mods see no change (surf still defaults to SPRITE_SEEL; surfPikachu only resolves on a Yellow import after regeneration). Parity tests in tests/parity_surfing_pikachu_sprite.lua (12/12) and tests/mod_world_tests.lua (19229/19229 with the new boot-seed checks). tests/parity_cinnabar_east_surf.lua (24/24) unchanged. Regeneration required: re-run make_yellow_manifest.py against a pret/pokeyellow checkout, then re-import the Yellow ROM.
This commit is contained in:
@@ -77,6 +77,12 @@ local SAFARI = {
|
||||
local PLAYER_SPRITES = {
|
||||
walk = "SPRITE_RED", surf = "SPRITE_SEEL",
|
||||
bike = "SPRITE_RED_BIKE", fly = "SPRITE_BIRD",
|
||||
-- Yellow's IsSurfingPikachuInParty: when the SURF-mon is a Pikachu,
|
||||
-- the player rides this sheet. GFX loads via
|
||||
-- LoadSurfingPlayerSpriteGraphics2, not SpriteSheetPointerTable, so
|
||||
-- it needs a manifest extract (RFC 0001). Guarded in Player.new so
|
||||
-- before extraction lands the ride keeps the Seel.
|
||||
surfPikachu = "SPRITE_SURFING_PIKACHU",
|
||||
}
|
||||
|
||||
-- The player's own trainer art: RedPicBack (the battle back pic, up until
|
||||
|
||||
@@ -389,6 +389,10 @@ function OverworldState:setMap(mapId, x, y, facing, opts)
|
||||
and not self.map:isWalkableCell(x, y)
|
||||
and self.map:isWaterCell(x, y)
|
||||
end
|
||||
-- re-derive from the live party: a reloaded save with the SURF-Pikachu
|
||||
-- since deposited should not render the Pikachu sheet.
|
||||
-- ponytail: re-derived rather than persisted.
|
||||
self:syncSurfingPikachu()
|
||||
end
|
||||
-- crossConnection re-arms this after setMap; clear so a warp/reload
|
||||
-- cannot leave a stale deferred PlayMapMusic pending
|
||||
@@ -1484,6 +1488,21 @@ function OverworldState:partyKnows(moveId)
|
||||
return partyKnowsVanilla(moveId)
|
||||
end
|
||||
|
||||
-- IsSurfingPikachuInParty (home/map_objects.asm): when the SURF-mon
|
||||
-- is a Pikachu, pose() renders the Pikachu surf sprite. Called at
|
||||
-- every surf-state change so a reloaded save picks the right sheet
|
||||
-- after a party change. No-op when not surfing.
|
||||
function OverworldState:syncSurfingPikachu()
|
||||
local p = self.player
|
||||
if not p then return end
|
||||
if not p.surfing then
|
||||
p.surfingPikachu = false
|
||||
return
|
||||
end
|
||||
local mon = self:partyKnows("SURF")
|
||||
p.surfingPikachu = mon ~= nil and mon.species == "PIKACHU" or false
|
||||
end
|
||||
|
||||
-- The rejection loop shared by the Good and Super Rods
|
||||
-- (item_effects.asm ItemUseGoodRod .RandomLoop / ReadSuperRodData): an
|
||||
-- odd random byte is no bite; otherwise a 2-bit pick rerolls until it
|
||||
@@ -1580,6 +1599,7 @@ function OverworldState:flyTo(mapId)
|
||||
Game.save.onBike = false
|
||||
Game.save.forcedBike = nil -- HandleFlyWarpOrDungeonWarp res BIT_ALWAYS_ON_BIKE
|
||||
self.player.surfing = false
|
||||
self:syncSurfingPikachu()
|
||||
-- the bird carries the player off westward before the warp
|
||||
-- (engine/overworld/player_animations.asm LoadBirdSpriteGraphics)
|
||||
self.flyAnim = { frames = 48 }
|
||||
@@ -1607,6 +1627,7 @@ function OverworldState:beginTeleportOut(onDone)
|
||||
end
|
||||
require("src.core.Sound").play(Game.data, "Teleport_Exit1")
|
||||
self.player.surfing = false
|
||||
self:syncSurfingPikachu()
|
||||
self.player.inputLocked = true
|
||||
-- rising spin: the mirror of the arrival spin-drop set in startWarpTo, so
|
||||
-- spinRise lifts the sprite (Player:pose) while spinFrames counts down
|
||||
@@ -2270,6 +2291,7 @@ function OverworldState:trySurf(fx, fy, onClose)
|
||||
Game.stack:push(TextBox.new(Game, text, function()
|
||||
if onClose then onClose() end
|
||||
p.surfing = true
|
||||
self:syncSurfingPikachu()
|
||||
require("src.core.Music").setSurfing(Game.data, true)
|
||||
Game.stack:push(require("src.render.Transition").whiteFlash(Game, nil,
|
||||
function() self:stepForwardOrCrossEdge(p.facing) end))
|
||||
@@ -3154,6 +3176,7 @@ function OverworldState:onStepComplete()
|
||||
-- dismounting a surf: landing on a walkable cell ends it
|
||||
if p.surfing and self.map:isWalkableCell(p.cellX, p.cellY) then
|
||||
p.surfing = false
|
||||
self:syncSurfingPikachu()
|
||||
require("src.core.Music").setSurfing(Game.data, false)
|
||||
end
|
||||
|
||||
@@ -3455,6 +3478,7 @@ function OverworldState:checkForcedMovement()
|
||||
end
|
||||
elseif tile.mode == "surf" then
|
||||
p.surfing = true
|
||||
self:syncSurfingPikachu()
|
||||
require("src.core.Music").setSurfing(Game.data, true)
|
||||
end
|
||||
return false
|
||||
@@ -3733,6 +3757,7 @@ end
|
||||
function OverworldState:warpToHealPoint(onDone, opts)
|
||||
local heal = self:healPoint()
|
||||
self.player.surfing = false
|
||||
self:syncSurfingPikachu()
|
||||
-- HandleFlyWarpOrDungeonWarp + DisplayPlayerBlackedOutText both clear
|
||||
-- BIT_ALWAYS_ON_BIKE (home/overworld.asm / home/text_script.asm)
|
||||
Game.save.forcedBike = nil
|
||||
|
||||
@@ -41,11 +41,18 @@ function Player.new(data, cx, cy, facing)
|
||||
-- LoadSurfingPlayerSpriteGraphics, home/overworld.asm)
|
||||
local walkId = FieldDefaults.fieldValue(data, "playerSprites", "walk")
|
||||
local surfId = FieldDefaults.fieldValue(data, "playerSprites", "surf")
|
||||
local surfPikaId = FieldDefaults.fieldValue(data, "playerSprites", "surfPikachu")
|
||||
local bikeId = FieldDefaults.fieldValue(data, "playerSprites", "bike")
|
||||
self.sprite = SpriteRenderer.new(data.sprites[walkId], "player")
|
||||
if surfId and data.sprites[surfId] then
|
||||
self.surfSprite = SpriteRenderer.new(data.sprites[surfId], "player")
|
||||
end
|
||||
-- Yellow's surfing-Pikachu ride (Yellow LoadSurfingPlayerSpriteGraphics2,
|
||||
-- paired with field.playerSprites.surfPikachu). rotated in at pose()
|
||||
-- when the SURF-mon is a Pikachu.
|
||||
if surfPikaId and data.sprites[surfPikaId] then
|
||||
self.surfPikachuSprite = SpriteRenderer.new(data.sprites[surfPikaId], "player")
|
||||
end
|
||||
if bikeId and data.sprites[bikeId] then
|
||||
self.bikeSprite = SpriteRenderer.new(data.sprites[bikeId], "player")
|
||||
end
|
||||
@@ -288,6 +295,7 @@ function Player:pose()
|
||||
-- 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.surfingPikachu and self.surfPikachuSprite)
|
||||
or (self.surfing and self.surfSprite)
|
||||
or (self.onBike and self.bikeSprite) or self.sprite
|
||||
return sprite, self.px, py, facing, phase, flip, hopping
|
||||
|
||||
Reference in New Issue
Block a user