mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-22 21:46:51 +02:00
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:
+108
-14
@@ -38,6 +38,9 @@ local Sound = require("src.core.Sound")
|
||||
-- Only for playerPic: the player.sprite raiser both generations share.
|
||||
local Sprites = require("src.pokemon.Sprites")
|
||||
local Strings = require("src.core.Strings")
|
||||
-- Only for TextBox.substitute: the {PLAYER} / {RIVAL} markers a map text
|
||||
-- carries into the battle box (PrintWinLossText, home/trainers.asm:230).
|
||||
local TextBox = require("src.render.TextBox")
|
||||
local Unown = require("src.core.gen2.Unown")
|
||||
|
||||
local BattleState = {}
|
||||
@@ -87,6 +90,20 @@ local TRAINER_SLIDE_STEPS = 8
|
||||
local TRAINER_SLIDE_FRAMES_PER_STEP = 2
|
||||
local TRAINER_SLIDE_FRAMES = TRAINER_SLIDE_STEPS * TRAINER_SLIDE_FRAMES_PER_STEP
|
||||
|
||||
-- BattleWinSlideInEnemyTrainerFrontpic (engine/battle/core.asm:6279-6318) and
|
||||
-- WinTrainerBattle's DelayFrames 40 (:2311)
|
||||
local WIN_SLIDE_STEPS = 6
|
||||
local WIN_SLIDE_FRAMES_PER_STEP = 4
|
||||
local WIN_SLIDE_FRAMES = WIN_SLIDE_STEPS * WIN_SLIDE_FRAMES_PER_STEP
|
||||
local WIN_SLIDE_REST_TILES = 2
|
||||
local WIN_SLIDE_DELAY_FRAMES = 40
|
||||
|
||||
local function winSlideTiles(frames)
|
||||
local step = math.min(WIN_SLIDE_STEPS,
|
||||
math.floor(frames / WIN_SLIDE_FRAMES_PER_STEP) + 1)
|
||||
return WIN_SLIDE_STEPS + WIN_SLIDE_REST_TILES - step
|
||||
end
|
||||
|
||||
-- MonFaintedAnimation (engine/battle/core.asm), which PlayerMonFaintedAnimation
|
||||
-- and EnemyMonFaintedAnimation both fall into with the fainted side's pic
|
||||
-- corner: the pic's tilemap rows are copied DOWN one row per step and the row
|
||||
@@ -141,6 +158,10 @@ local MENU_COL_SPACING = 6
|
||||
-- the count PrintNum writes after it (two digits, leading zeros) at (13,16).
|
||||
local CONTEST_MENU_BOX_X = 2
|
||||
local CONTEST_MENU_COL_SPACING = 12
|
||||
|
||||
-- PrintMoveType prints the type table's own names; only these two differ from
|
||||
-- the constant (data/types/names.asm).
|
||||
local TYPE_NAMES = { PSYCHIC_TYPE = "PSYCHIC", CURSE_TYPE = "???" }
|
||||
-- charmap.asm's quantity glyph, spelled the way MartMenu spells it.
|
||||
local CONTEST_BALL_LABEL = "PARKBALL\xc3\x97"
|
||||
|
||||
@@ -508,6 +529,14 @@ function BattleState:pushAll(events)
|
||||
for _, event in ipairs(events or {}) do self:push(event) end
|
||||
end
|
||||
|
||||
-- LearnMove returns before HandleEnemyMonFaint's send-out/prize arms
|
||||
-- (engine/battle/core.asm:1959-2010).
|
||||
function BattleState:pushFront(events)
|
||||
for i = #(events or {}), 1, -1 do
|
||||
table.insert(self.queue, 1, events[i])
|
||||
end
|
||||
end
|
||||
|
||||
function BattleState:pic(mon, back)
|
||||
local def = self.pokemon and mon and self.pokemon[mon.species]
|
||||
local path = def and (back and def.spriteBack or def.spriteFront)
|
||||
@@ -686,6 +715,8 @@ function BattleState:drawPic(mon, back)
|
||||
-- One tile per two frames to the right, SlideBattlePicOut's own step.
|
||||
if enemyTrainer and self.trainerSlide then
|
||||
px = px + math.floor(self.trainerSlide / TRAINER_SLIDE_FRAMES_PER_STEP) * 8
|
||||
elseif enemyTrainer and self.winSlide then
|
||||
px = px + winSlideTiles(self.winSlide) * 8
|
||||
end
|
||||
-- The pic's own scale (battle_sprite_scales, then the species record, then
|
||||
-- 1x) composed with whatever square BattleBGEffect_RunPicResizeScript has
|
||||
@@ -1371,7 +1402,7 @@ function BattleState:advanceQueue()
|
||||
and self.shownHp[event.side] ~= event.hp then
|
||||
self.hpAnim = { side = event.side, to = event.hp }
|
||||
elseif event.kind == "send" and event.side and event.mon and self.shownHp then
|
||||
self.shownHp[event.side] = event.mon.hp or 0
|
||||
self.shownHp[event.side] = event.hp or event.mon.hp or 0
|
||||
if self.hpAnim and self.hpAnim.side == event.side then self.hpAnim = nil end
|
||||
end
|
||||
-- And the same lag for the status tag (home/battle.asm:150); a send snaps it
|
||||
@@ -1379,8 +1410,9 @@ function BattleState:advanceQueue()
|
||||
if self.shownStatus and event.side
|
||||
and (event.kind == "status"
|
||||
or (event.kind == "send" and event.mon)) then
|
||||
self.shownStatus[event.side] =
|
||||
(event.kind == "send" and event.mon.status or event.status) or false
|
||||
local shown = event.status
|
||||
if event.kind == "send" and shown == nil then shown = event.mon.status end
|
||||
self.shownStatus[event.side] = shown or false
|
||||
end
|
||||
-- AnimateExpBar (engine/battle/core.asm:7191) is called from INSIDE
|
||||
-- GiveExperiencePoints before the exp is committed (the call at :6888 sits
|
||||
@@ -1435,6 +1467,24 @@ function BattleState:advanceQueue()
|
||||
self.trainerSlide = 0
|
||||
return
|
||||
end
|
||||
-- BattleWinSlideInEnemyTrainerFrontpic and the DelayFrames 40 behind it
|
||||
-- (engine/battle/core.asm:2310-2312)
|
||||
if event.kind == "trainer-return" then
|
||||
if not self.enemyTrainerImage then return self:advanceQueue() end
|
||||
self.showEnemyTrainer = true
|
||||
self.picHidden.enemy = false
|
||||
self.winSlide = 0
|
||||
self.winSliding = true
|
||||
return
|
||||
end
|
||||
-- PrintWinLossText (home/trainers.asm:230): one FarPrintText of the trainer
|
||||
-- struct's own line, paged and held for A/B like any other map text.
|
||||
if event.kind == "win-text" then
|
||||
local text = event.text
|
||||
if self.game then text = TextBox.substitute(self.game, text) end
|
||||
self:showPages(text)
|
||||
return
|
||||
end
|
||||
-- The shiny sparkle: hBattleTurn 1 and wBattleAnimParam 1 pick
|
||||
-- BattleAnim_SendOutMon's `.Shiny` arm on the enemy (core.asm:8708-8715).
|
||||
if event.kind == "shiny-flash" then
|
||||
@@ -1917,6 +1967,17 @@ function BattleState:update(_dt)
|
||||
return
|
||||
end
|
||||
|
||||
-- BattleWinSlideInEnemyTrainerFrontpic plus WinTrainerBattle's DelayFrames
|
||||
-- 40 (engine/battle/core.asm:6279-6318, :2310-2312)
|
||||
if self.winSliding then
|
||||
self.winSlide = self.winSlide + 1
|
||||
if self.winSlide >= WIN_SLIDE_FRAMES + WIN_SLIDE_DELAY_FRAMES then
|
||||
self.winSliding = nil
|
||||
self:advanceQueue()
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
-- SlideBattlePicOut is a plain loop with DelayFrames in it, so it owns the
|
||||
-- screen the same way (engine/battle/core.asm:2882).
|
||||
if self.trainerSlide then
|
||||
@@ -2004,6 +2065,8 @@ function BattleState:update(_dt)
|
||||
self.phase = "stats-box"
|
||||
return
|
||||
end
|
||||
-- PrintWinLossText's line pages like any map text (home/text.asm:403-448)
|
||||
if self:nextPage() then return end
|
||||
self:advanceQueue()
|
||||
return
|
||||
end
|
||||
@@ -2292,7 +2355,7 @@ function BattleState:update(_dt)
|
||||
learn.move, learn.moveName)
|
||||
self.pendingLearn = nil
|
||||
self.phase = "resolving"
|
||||
self:pushAll(self.battle:takeEvents())
|
||||
self:pushFront(self.battle:takeEvents())
|
||||
self:advanceQueue()
|
||||
end
|
||||
return
|
||||
@@ -2872,7 +2935,7 @@ function BattleState:finishDecline()
|
||||
self.pendingLearn = nil
|
||||
self.phase = "resolving"
|
||||
if learn then self.battle:declineForget(learn.index, learn.moveName) end
|
||||
self:pushAll(self.battle:takeEvents())
|
||||
self:pushFront(self.battle:takeEvents())
|
||||
self:advanceQueue()
|
||||
end
|
||||
|
||||
@@ -3475,6 +3538,23 @@ function BattleState:printMessage()
|
||||
end
|
||||
end
|
||||
|
||||
-- MoveInfoBox (engine/battle/core.asm:5403-5478): "TYPE/" at (1,9), the type
|
||||
-- at (2,10), cur/max PP at (5,11), or "Disabled!" at (1,10).
|
||||
function BattleState:drawMoveInfoBox(move)
|
||||
if not move then return end
|
||||
local fighter = self.battle and self.battle.player
|
||||
if fighter and self.battle:moveDisabled(fighter, move.id) then
|
||||
Chrome.print("Disabled!", 1, 10)
|
||||
return
|
||||
end
|
||||
local def = self.game and self.game.data and self.game.data.moves
|
||||
and self.game.data.moves[move.id]
|
||||
Chrome.print("TYPE/", 1, 9)
|
||||
local moveType = def and def.type
|
||||
Chrome.print(moveType and (TYPE_NAMES[moveType] or moveType) or "", 2, 10)
|
||||
Chrome.print(("%2d/%2d"):format(move.pp or 0, move.maxPp or 0), 5, 11)
|
||||
end
|
||||
|
||||
function BattleState:drawPanel()
|
||||
Chrome.clear()
|
||||
-- A tutorial battle legitimately has no player mon, so only the enemy is
|
||||
@@ -3494,7 +3574,15 @@ function BattleState:drawPanel()
|
||||
-- Message box across the bottom, with the menu window over its right half --
|
||||
-- the cart draws the prompt into the full-width box and then opens the menu
|
||||
-- on top, so the tail of a long name is simply covered.
|
||||
Chrome.box(0, 12, 20, 6)
|
||||
-- MoveSelectionScreen type 0 is two boxes: the name-only list
|
||||
-- (engine/battle/core.asm:5074-5084) and MoveInfoBox's (:5407-5410).
|
||||
local moveMenu = self.phase == "moves"
|
||||
if moveMenu then
|
||||
Chrome.box(0, 8, 11, 5)
|
||||
Chrome.box(4, 12, 16, 6)
|
||||
else
|
||||
Chrome.box(0, 12, 20, 6)
|
||||
end
|
||||
if self.phase == "menu" then
|
||||
self:printMessage()
|
||||
local boxX = self.contest and CONTEST_MENU_BOX_X or MENU_BOX_X
|
||||
@@ -3520,24 +3608,30 @@ function BattleState:drawPanel()
|
||||
moves = (mon and mon.moves) or moves
|
||||
end
|
||||
local cursorRow = forgetting and self.forgetIndex or self.moveIndex
|
||||
-- w2DMenuCursorInitX 5 with the names at hlcoord 6 (core.asm:5086-5107).
|
||||
local cursorCol = moveMenu and 5 or 1
|
||||
local nameCol = moveMenu and 6 or 2
|
||||
for i, move in ipairs(moves) do
|
||||
local ty = 13 + (i - 1)
|
||||
-- Cursor in the box's own gutter, not clipped against the border.
|
||||
if i == cursorRow then Chrome.cursor(1, ty) end
|
||||
if i == cursorRow then Chrome.cursor(cursorCol, ty) end
|
||||
-- The held slot's marker. `.battle_player_moves` writes '▷' into the
|
||||
-- row wSwappingMove names (engine/battle/core.asm:5157-5165) so a move
|
||||
-- picked up for a swap is visible while the cursor moves off it. It
|
||||
-- sits a column right of the cursor gutter, where the cart puts it
|
||||
-- (hlcoord 5, 13 against the cursor's own column), and only while the
|
||||
-- move list itself is up -- the forget picker has no swapping.
|
||||
if not forgetting and self.moveSwapIndex == i then
|
||||
Chrome.print("\u{25B7}", 0, ty)
|
||||
-- hlcoord 5, 13 is the cursor's own gutter, so PlaceMenuCursor covers
|
||||
-- the marker on the cursor's row.
|
||||
if not forgetting and self.moveSwapIndex == i and i ~= cursorRow then
|
||||
Chrome.print("\u{25B7}", cursorCol, ty)
|
||||
end
|
||||
local def = self.game and self.game.data and self.game.data.moves
|
||||
and self.game.data.moves[move.id]
|
||||
Chrome.print((def and def.name) or move.id, 2, ty)
|
||||
Chrome.printRight(("%d/%d"):format(move.pp or 0, move.maxPp or 0), 19, ty)
|
||||
Chrome.print((def and def.name) or move.id, nameCol, ty)
|
||||
if not moveMenu then
|
||||
Chrome.printRight(("%d/%d"):format(move.pp or 0, move.maxPp or 0),
|
||||
19, ty)
|
||||
end
|
||||
end
|
||||
if moveMenu then self:drawMoveInfoBox(moves[cursorRow]) end
|
||||
else
|
||||
-- Battle messages wrap inside the box rather than running off the frame.
|
||||
self:printMessage()
|
||||
|
||||
@@ -337,10 +337,15 @@ function PackMenu:useSelected()
|
||||
return
|
||||
end
|
||||
local world = self.world
|
||||
local result = world and world.useFieldItem and world:useFieldItem(row.id)
|
||||
local result, extra = nil, nil
|
||||
if world and world.useFieldItem then result, extra = world:useFieldItem(row.id) end
|
||||
if result then
|
||||
if result == "nowhere" then
|
||||
self.message = OAK_THIS_ISNT_THE_TIME
|
||||
elseif result == "coin_case" then
|
||||
-- _CoinCaseCountText (data/text/common_3.asm:336): "Coins:" then the
|
||||
-- count, text_decimal 4 digits with PRINTNUM_LEFTALIGN_F so no padding.
|
||||
self.message = { "Coins:", tostring(extra or 0) }
|
||||
elseif result == "repel_used" then
|
||||
-- ItemUsedText (data/text/common_3.asm): "<PLAYER> used the\n<ITEM>."
|
||||
-- World already wrote the counter and took the item out of the bag, so
|
||||
|
||||
@@ -150,7 +150,11 @@ function PokedexMenu.new(game, opts)
|
||||
self.pokemon = opts.pokemon or data.pokemon
|
||||
self.palettes = opts.palettes or data.gen2Palettes
|
||||
self.onClose = opts.onClose
|
||||
-- InitPokedex: wLastDexMode -> wCurDexMode (engine/pokedex/pokedex.asm:97).
|
||||
self.modeIndex = 1
|
||||
for i, name in ipairs(MODES) do
|
||||
if self.save and name == self.save.lastDexMode then self.modeIndex = i end
|
||||
end
|
||||
self.index = 1
|
||||
self.scroll = 0
|
||||
self.view = "list" -- list | entry | area | option | search | results | unown
|
||||
@@ -317,6 +321,13 @@ function PokedexMenu:cursorVisible()
|
||||
return ((self.entryBlink or 0) % 32) < 20
|
||||
end
|
||||
|
||||
-- Pokedex: wCurDexMode -> wLastDexMode on the way out
|
||||
-- (engine/pokedex/pokedex.asm:60), which lives in the saved game data.
|
||||
function PokedexMenu:close()
|
||||
if self.save then self.save.lastDexMode = MODES[self.modeIndex] end
|
||||
if self.onClose then self.onClose() end
|
||||
end
|
||||
|
||||
function PokedexMenu:update(_dt)
|
||||
self.entryBlink = (self.entryBlink or 0) + 1
|
||||
local input = self.game and self.game.input
|
||||
@@ -328,8 +339,8 @@ function PokedexMenu:update(_dt)
|
||||
if input:wasPressed("a") or input:wasPressed("b") then
|
||||
if self.page == 1 then
|
||||
self.page = 2
|
||||
elseif self.onClose then
|
||||
self.onClose()
|
||||
else
|
||||
self:close()
|
||||
end
|
||||
end
|
||||
return
|
||||
@@ -367,7 +378,7 @@ function PokedexMenu:update(_dt)
|
||||
if self.view == "search" then return self:updateSearch(input) end
|
||||
if self.view == "unown" then return self:updateUnown(input) end
|
||||
if input:wasPressed("b") then
|
||||
if self.onClose then self.onClose() end
|
||||
self:close()
|
||||
return
|
||||
elseif input:wasPressed("select") then
|
||||
-- Pokedex_UpdateMainScreen: SELECT opens the OPTION screen and START the
|
||||
|
||||
@@ -2280,7 +2280,9 @@ function Pokegear:drawPanel()
|
||||
self:drawClock()
|
||||
end
|
||||
-- Last: the arrow is an OBJ and composites over whatever the card drew.
|
||||
self:drawModeArrow()
|
||||
-- _FlyMap has no card strip and never animates it
|
||||
-- (engine/pokegear/pokegear.asm:1999).
|
||||
if not self.fly then self:drawModeArrow() end
|
||||
G.setColor(1, 1, 1, 1)
|
||||
end
|
||||
|
||||
|
||||
@@ -36,9 +36,8 @@
|
||||
-- $3f the shiny ⁂ icon (stats_tiles tile 14)
|
||||
-- $40 / $41 the left and right HP/exp bar end caps
|
||||
--
|
||||
-- The extractor does not carry that sheet yet, so `pageTile` draws those seven
|
||||
-- shapes directly and takes the sheet the moment menu_gfx grows a `stats`
|
||||
-- entry. Everything that IS a glyph goes through the font: ◀ ($71), ▶ ($ed),
|
||||
-- The extractor writes that sheet as menu_gfx.stats, which `pageTile` draws.
|
||||
-- Everything that IS a glyph goes through the font: ◀ ($71), ▶ ($ed),
|
||||
-- № ($74), <ID> ($73), <LV> ($6e) and the row-7 rule's $62 (the empty HP/exp
|
||||
-- bar cell, which is FontBattleExtra's -- hence Font.useBattleExtra(true)
|
||||
-- around the whole screen, exactly as the party menu does).
|
||||
@@ -95,6 +94,14 @@ local TILE_BAR_CAP_RIGHT = 0x41
|
||||
-- made of (StatsScreen_PlaceHorizontalDivider).
|
||||
local TILE_HORIZONTAL_DIVIDER = 0x62
|
||||
|
||||
-- gfx/stats/pages.pal, the three palettes _CGB_StatsScreenHPPals copies to
|
||||
-- wBGPals1 slots 3-5 (engine/gfx/cgb_layouts.asm:199-212)
|
||||
local PAGE_PALETTES = {
|
||||
{ { 255, 255, 255 }, { 255, 156, 255 }, { 255, 123, 255 }, { 0, 0, 0 } },
|
||||
{ { 255, 255, 255 }, { 173, 255, 115 }, { 140, 255, 0 }, { 0, 0, 0 } },
|
||||
{ { 255, 255, 255 }, { 140, 255, 255 }, { 140, 255, 255 }, { 0, 0, 0 } },
|
||||
}
|
||||
|
||||
-- PrintTempMonStats' .StatNames, and the wTempMon fields it prints beside
|
||||
-- them. <NEXT> steps two rows, so the five labels are 2 rows apart and the
|
||||
-- values start one row below the first label.
|
||||
@@ -821,12 +828,37 @@ end
|
||||
|
||||
-- ----------------------------------------------------------------- drawing
|
||||
|
||||
-- A tile out of StatsScreenPageTilesGFX. The extractor does not carry that
|
||||
-- sheet, so each of the seven shapes it needs is drawn here; the moment
|
||||
-- menu_gfx grows a `stats` entry this can take the real tiles instead.
|
||||
-- menu_gfx.stats, the 17 tiles LoadStatsScreenPageTilesGFX lands at vTiles2
|
||||
-- tile $31 (engine/gfx/load_font.asm:90-95)
|
||||
function SummaryMenu:statsTiles()
|
||||
if self.statsSheet ~= nil then return self.statsSheet or nil end
|
||||
local gfx = (self.menuGfx or {}).stats
|
||||
local image = gfx and self:picImage(gfx.sheet)
|
||||
if not image then
|
||||
self.statsSheet = false
|
||||
return nil
|
||||
end
|
||||
local w, h = image:getDimensions()
|
||||
local quads = {}
|
||||
for index = 0, (gfx.tiles or 17) - 1 do
|
||||
quads[(gfx.firstTile or 0x31) + index] =
|
||||
love.graphics.newQuad(index * 8, 0, 8, 8, w, h)
|
||||
end
|
||||
self.statsSheet = { image = image, quads = quads }
|
||||
return self.statsSheet
|
||||
end
|
||||
|
||||
-- A tile out of StatsScreenPageTilesGFX. The fallback arm draws each of the
|
||||
-- seven shapes by hand for a cache built before menu_gfx.stats existed.
|
||||
function SummaryMenu:pageTile(id, tx, ty)
|
||||
local G = love.graphics
|
||||
local px, py = tx * 8, ty * 8
|
||||
local sheet = self:statsTiles()
|
||||
if sheet and sheet.quads[id] then
|
||||
G.setColor(1, 1, 1, 1)
|
||||
G.draw(sheet.image, sheet.quads[id], px, py)
|
||||
return
|
||||
end
|
||||
G.setColor(0, 0, 0, 1)
|
||||
if id == TILE_VERTICAL_DIVIDER then
|
||||
G.rectangle("fill", px + 3, py, 2, 8)
|
||||
@@ -847,11 +879,29 @@ end
|
||||
-- (17,5), all small ($36) first, then the one for this page redrawn large
|
||||
-- ($3a). The routine writes the four tiles as [hli]/[hld], a row down, then
|
||||
-- [hli]/[hl] -- which is why it is a 2x2 block and not a 2x1 strip.
|
||||
function SummaryMenu:drawPageSquare(tx, ty, large)
|
||||
function SummaryMenu:drawPageSquare(tx, ty, large, colors)
|
||||
local G = love.graphics
|
||||
local px, py = tx * 8, ty * 8
|
||||
-- $3a..$3d for the page that is up, $36..$39 for the other two.
|
||||
local first = large and TILE_SQUARE_LARGE or TILE_SQUARE_SMALL
|
||||
local sheet = self:statsTiles()
|
||||
if sheet and sheet.quads[first] then
|
||||
-- [hli] / [hld], a row down, [hli] / [hl]: the four tiles in that order.
|
||||
local function body()
|
||||
G.setColor(1, 1, 1, 1)
|
||||
G.draw(sheet.image, sheet.quads[first], px, py)
|
||||
G.draw(sheet.image, sheet.quads[first + 1], px + 8, py)
|
||||
G.draw(sheet.image, sheet.quads[first + 2], px, py + 8)
|
||||
G.draw(sheet.image, sheet.quads[first + 3], px + 8, py + 8)
|
||||
end
|
||||
if colors and GbcPalette.available() then
|
||||
GbcPalette.with(colors, body)
|
||||
else
|
||||
body()
|
||||
end
|
||||
G.setColor(1, 1, 1, 1)
|
||||
return
|
||||
end
|
||||
local inset = first == TILE_SQUARE_LARGE and 2 or 5
|
||||
local size = 16 - inset * 2
|
||||
G.setColor(0, 0, 0, 1)
|
||||
@@ -862,7 +912,7 @@ end
|
||||
function SummaryMenu:drawPageIndicators()
|
||||
local columns = { 13, 15, 17 }
|
||||
for i, tx in ipairs(columns) do
|
||||
self:drawPageSquare(tx, 5, i == self.page)
|
||||
self:drawPageSquare(tx, 5, i == self.page, PAGE_PALETTES[i])
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user