mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-16 16:21:30 +02:00
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:
+21
-10
@@ -261,11 +261,30 @@ local function useOn(game, battle, id, target, list, moveIndex, picker)
|
||||
Evolution.evolve(game, target, extra.evolveTo)
|
||||
return
|
||||
end
|
||||
-- refresh counts in the list. Hoisted out of the tail below because the
|
||||
-- RARE CANDY path returns early and still leaves the list on screen, so
|
||||
-- both paths have to agree on what the row reads (#796).
|
||||
local function refreshCounts()
|
||||
for i, it in ipairs(list.items) do
|
||||
if it.value == id then
|
||||
local left = game.save.inventory[id]
|
||||
if left then it.right = "x" .. left else table.remove(list.items, i) end
|
||||
break
|
||||
end
|
||||
end
|
||||
list.index = math.min(list.index, math.max(1, #list.items))
|
||||
end
|
||||
-- RARE CANDY: after the level text, the stat window, any level-up
|
||||
-- moves and a level evolution follow (item_effects.asm .useRareCandy
|
||||
-- runs PrintStatsBox, LearnMoveFromLevelUp and TryEvolvingMon)
|
||||
if extra and extra.leveledTo and target then
|
||||
list:close()
|
||||
-- .useItem_partyMenu re-enters StartMenu_Item after the stat box, it
|
||||
-- does not CloseStartMenu, so out of battle the bag list stays up with
|
||||
-- the decremented count showing. In battle the item spends the turn
|
||||
-- and the bag has to go (dead today: ItemUseVitamin refuses RARE CANDY
|
||||
-- mid-battle, kept so the boundary stays explicit). #796
|
||||
refreshCounts()
|
||||
if battle then list:close() end
|
||||
showMessages(game, payload, function()
|
||||
local StatBox = require("src.battle.BattleState").StatBox
|
||||
game.stack:push(StatBox.new(game, target, function()
|
||||
@@ -304,15 +323,7 @@ local function useOn(game, battle, id, target, list, moveIndex, picker)
|
||||
end)
|
||||
return
|
||||
end
|
||||
-- refresh counts in the list
|
||||
for i, it in ipairs(list.items) do
|
||||
if it.value == id then
|
||||
local left = game.save.inventory[id]
|
||||
if left then it.right = "x" .. left else table.remove(list.items, i) end
|
||||
break
|
||||
end
|
||||
end
|
||||
list.index = math.min(list.index, math.max(1, #list.items))
|
||||
refreshCounts()
|
||||
-- HP medicine: fill the bar in the still-open picker first, then print
|
||||
-- and close, the order item_effects.asm .doneHealing runs in
|
||||
-- (SFX_HEAL_HP -> UpdateHPBar2 -> RedrawPartyMenu prints the message).
|
||||
|
||||
+99
-17
@@ -1,6 +1,7 @@
|
||||
-- Hall of Fame induction (engine/movie/hall_of_fame.asm): each party
|
||||
-- member's front sprite scrolls onto the right side of the screen
|
||||
-- (HoFShowMonOrPlayer's .ScrollPic), then HoFDisplayMonInfo draws the
|
||||
-- member's back sprite sweeps across the screen and its front sprite then
|
||||
-- scrolls onto the right side (HoFShowMonOrPlayer's two .ScrollPic passes,
|
||||
-- #847), then HoFDisplayMonInfo draws the
|
||||
-- left-side LEVEL/TYPE box, plays the cry, holds, and pops the bottom
|
||||
-- "HALL OF FAME" text box before fading to the next mon. After the
|
||||
-- party, the player pic scrolls in and HoFDisplayPlayerStats shows the
|
||||
@@ -41,6 +42,25 @@ end
|
||||
local SCROLL_SPEED = 4 -- px/frame @ 60fps
|
||||
local PIC_X, PIC_Y = 12 * 8, 5 * 8
|
||||
|
||||
-- The BACK pic pass that runs in front of every front pic (#847).
|
||||
-- HoFShowMonOrPlayer loads the back pic (predef LoadMonBackPic, or
|
||||
-- RedPicBack through HoFLoadPlayerPics) into the same 7x7 window at
|
||||
-- hlcoord 12,5, points the tilemap at base tile $31, and sweeps hSCX from
|
||||
-- $c0 to $a0 at e = 4 -- in screen pixels the pic enters at x = 160 and
|
||||
-- leaves at x = -64, 56 frames. Only then is the window re-pointed at the
|
||||
-- front pic (base tile 0) with hSCY back to 0 and e = -4, so the front pic
|
||||
-- starts at x = -64 rather than at its own width.
|
||||
-- hSCY = $d0 during the back pass puts the window's top row at y = 88, so
|
||||
-- its 7 tiles sit flush with the bottom of the screen. ScaleSpriteByTwo
|
||||
-- (engine/battle/scale_sprites.asm) doubles the 32x32 back sprite into that
|
||||
-- 7x7 = 56x56 buffer with the last 4 source rows and the last source column
|
||||
-- dropped, so what shows is the sprite's top-left 28x28 at 2x.
|
||||
local BACK_START_X, BACK_END_X = 160, -64
|
||||
local BACK_Y = 88
|
||||
local BACK_SCALE = 2
|
||||
local BACK_CROP = 28
|
||||
local FRONT_START_X = -64
|
||||
|
||||
-- After HoFDisplayAndRecordMonInfo: 80 DelayFrames, then the bottom
|
||||
-- HALL OF FAME box for 180 DelayFrames, then GBFadeOutToWhite.
|
||||
local INFO_HOLD = 80
|
||||
@@ -74,6 +94,8 @@ function HallOfFame.new(game, onDone)
|
||||
self.phase = "mons"
|
||||
self.sprites = {} -- species -> image or false
|
||||
self.spriteTrueColor = {} -- species -> full-color art flag (#637)
|
||||
self.backs = {} -- species (or "@player") -> back image or false (#847)
|
||||
self.backTrueColor = {}
|
||||
local playerPath, playerTrueColor =
|
||||
require("src.pokemon.Sprites").playerPath(
|
||||
game.data, "front", { kind = "hof" })
|
||||
@@ -98,20 +120,14 @@ function HallOfFame:nextMon()
|
||||
local mon = self.game.save.party[self.index]
|
||||
self.showHofBanner = false
|
||||
self.fade = 0
|
||||
if mon then
|
||||
self.phase = "mons"
|
||||
self.timer = INFO_HOLD
|
||||
Sound.playCry(self.game.data, mon.species)
|
||||
local sprite = self:spriteFor(mon.species)
|
||||
local w = sprite and sprite:getWidth() or 56
|
||||
self.scrollX = -w
|
||||
else
|
||||
-- HoFShowMonOrPlayer with wHoFMonOrPlayer = player
|
||||
self.phase = "player"
|
||||
self.timer = 0
|
||||
local w = self.playerPic and self.playerPic:getWidth() or 56
|
||||
self.scrollX = -w
|
||||
end
|
||||
self.backQuad = nil
|
||||
-- HoFShowMonOrPlayer runs the back pic sweep for a party member and for
|
||||
-- the player alike (wHoFMonOrPlayer only picks which pics get loaded), so
|
||||
-- both enter through the "back" phase and the front pic follows it (#847)
|
||||
self.phase = "back"
|
||||
self.afterBack = mon and "mons" or "player"
|
||||
self.timer = 0
|
||||
self.scrollX = BACK_START_X
|
||||
end
|
||||
|
||||
function HallOfFame:spriteFor(species)
|
||||
@@ -126,6 +142,30 @@ function HallOfFame:spriteFor(species)
|
||||
return cached or nil
|
||||
end
|
||||
|
||||
-- The back pic for the pass ahead of the current front pic: the party
|
||||
-- member's own back sprite (predef LoadMonBackPic), or RedPicBack once the
|
||||
-- party is done (HoFLoadPlayerPics). Cached like spriteFor (#847).
|
||||
function HallOfFame:backPicFor()
|
||||
local mon = self.game.save.party[self.index]
|
||||
local key = mon and mon.species or "@player"
|
||||
local cached = self.backs[key]
|
||||
if cached == nil then
|
||||
local Sprites = require("src.pokemon.Sprites")
|
||||
local path, trueColor
|
||||
if mon then
|
||||
path, trueColor = Sprites.path(self.game.data, mon.species, "back",
|
||||
{ kind = "hof" })
|
||||
else
|
||||
path, trueColor = Sprites.playerPath(self.game.data, "back",
|
||||
{ kind = "hof" })
|
||||
end
|
||||
cached = tryImage(path) or false
|
||||
self.backs[key] = cached
|
||||
self.backTrueColor[key] = cached and trueColor or false
|
||||
end
|
||||
return cached or nil, self.backTrueColor[key]
|
||||
end
|
||||
|
||||
-- HoFDisplayPlayerStats' DisplayDexRating tally (also
|
||||
-- OverworldController:dexRating / PokedexMenu.new's seen+owned counts)
|
||||
function HallOfFame:dexSeenOwned()
|
||||
@@ -154,9 +194,28 @@ function HallOfFame:update(dt)
|
||||
local input = self.game.input
|
||||
local skip = input:wasPressed("a")
|
||||
|
||||
-- .ScrollPic with d = $a0, e = 4: the back pic crosses the screen right to
|
||||
-- left and is gone before the front pic starts. Both scrolls are plain
|
||||
-- DelayFrame loops in the ROM, so neither takes a button (#847).
|
||||
if self.phase == "back" then
|
||||
self.scrollX = self.scrollX - SCROLL_SPEED
|
||||
if self.scrollX <= BACK_END_X then
|
||||
self.phase = self.afterBack
|
||||
self.timer = self.phase == "mons" and INFO_HOLD or 0
|
||||
self.scrollX = FRONT_START_X
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
if self.phase == "mons" or self.phase == "fade" then
|
||||
if self.phase == "mons" and self.scrollX < PIC_X then
|
||||
self.scrollX = math.min(PIC_X, self.scrollX + SCROLL_SPEED)
|
||||
-- PlayCry is the tail of HoFDisplayMonInfo, which runs only after
|
||||
-- HoFShowMonOrPlayer's two scrolls have both settled (#847)
|
||||
if self.scrollX >= PIC_X then
|
||||
local mon = self.game.save.party[self.index]
|
||||
if mon then Sound.playCry(self.game.data, mon.species) end
|
||||
end
|
||||
return
|
||||
end
|
||||
if self.phase == "fade" then
|
||||
@@ -277,6 +336,27 @@ function HallOfFame:drawPic(img, trueColor)
|
||||
end
|
||||
end
|
||||
|
||||
-- The back pic sweep (see the BACK_* constants): the same 7x7 window as the
|
||||
-- front pic, one screen lower, cropped to the 28x28 ScaleSpriteByTwo keeps.
|
||||
function HallOfFame:drawBackPic()
|
||||
local img, trueColor = self:backPicFor()
|
||||
if not img then return end
|
||||
local w = math.min(img:getWidth(), BACK_CROP)
|
||||
local h = math.min(img:getHeight(), BACK_CROP)
|
||||
if not self.backQuad then
|
||||
self.backQuad = love.graphics.newQuad(0, 0, w, h, img:getDimensions())
|
||||
end
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
love.graphics.draw(img, self.backQuad, self.scrollX, BACK_Y, 0,
|
||||
BACK_SCALE, BACK_SCALE)
|
||||
-- same whole-screen palette exemption the front pic takes (#637)
|
||||
if trueColor then
|
||||
require("src.render.PaletteFX").markTrueColor(self.scrollX, BACK_Y,
|
||||
w * BACK_SCALE,
|
||||
h * BACK_SCALE)
|
||||
end
|
||||
end
|
||||
|
||||
-- HoFDisplayPlayerStats boxes + labels (player pic already on the right)
|
||||
function HallOfFame:drawPlayerStats()
|
||||
local save = self.game.save
|
||||
@@ -301,7 +381,9 @@ function HallOfFame:draw()
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
love.graphics.rectangle("fill", 0, 0, 160, 144)
|
||||
|
||||
if self.phase == "mons" or self.phase == "fade" then
|
||||
if self.phase == "back" then
|
||||
self:drawBackPic()
|
||||
elseif self.phase == "mons" or self.phase == "fade" then
|
||||
local mon = self.game.save.party[self.index]
|
||||
if mon then
|
||||
self:drawPic(self:spriteFor(mon.species),
|
||||
|
||||
+14
-1
@@ -99,7 +99,20 @@ end
|
||||
function NamingScreen:confirm()
|
||||
local name = table.concat(self.glyphs)
|
||||
if name == "" then
|
||||
name = (self.presets and self.presets[1]) or self.default or "A"
|
||||
-- An empty confirm (START, or the ED cell with nothing typed) must not
|
||||
-- invent a letter (#833). DisplayNamingScreen seeds wStringBuffer with
|
||||
-- '@' (engine/menus/naming_screen.asm) and every caller checks that first
|
||||
-- byte: AskName falls through to .declinedNickname, copying the species
|
||||
-- name over the nick slot -- vanilla's "un-nicknamed", which this port
|
||||
-- models as mon.nickname == nil (src/save_convert/GenSave.lua), so
|
||||
-- evolution can still rename the mon. DisplayNameRaterScreen takes
|
||||
-- .playerCancelled and keeps the old nick, which is why an explicit
|
||||
-- opts.default still wins here. Player/rival naming
|
||||
-- (oak_speech2.asm ChoosePlayerName) re-opens on '@' and never accepts an
|
||||
-- empty result; the port keeps its preset fallback for that.
|
||||
-- Contract for callers: "" means NO name -- BattleState:askNicknameUI and
|
||||
-- Commands.give_pokemon both guard on #name > 0 before setting nickname.
|
||||
name = (self.presets and self.presets[1]) or self.default or ""
|
||||
end
|
||||
Sound.play(self.game.data, "Press_AB")
|
||||
self.game.stack:pop()
|
||||
|
||||
Reference in New Issue
Block a user