mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-09-29 15:08:50 -04:00
CLOSES #2199, CLOSES #2200, CLOSES #2203, CLOSES #2204, CLOSES #2205, CLOSES #2206, CLOSES #2212, CLOSES #2214, CLOSES #2218, CLOSES #2219, CLOSES #2223, CLOSES #2224, CLOSES #2225, CLOSES #2226
This commit is contained in:
+50
-17
@@ -50,19 +50,30 @@ end
|
||||
local function startGame(game, ow, t, done, balls, introText)
|
||||
game.save.safari = { balls = balls or BALLS, steps = STEPS }
|
||||
game.save.safariNags = nil
|
||||
-- ResetEventReuseHL EVENT_SAFARI_GAME_OVER -- scripts/SafariZoneGate.asm:196
|
||||
game.save.safariGameOver = nil
|
||||
local TextBox = require("src.render.TextBox")
|
||||
local paid = introText
|
||||
or (t._SafariZoneGateSafariZoneWorker1ThatllBe500PleaseText
|
||||
or "That'll be ¥500\nplease!\f{PLAYER} received\n30 SAFARI BALLs!")
|
||||
:gsub("{NUM:[^}]*}", "500")
|
||||
paid = paid:gsub("{PLAYER}", game.save.player.name)
|
||||
local pa = t._SafariZoneGateSafariZoneWorker1CallYouOnThePAText
|
||||
or "\fWe'll call you on\nthe PA when you\nrun out of time\nor SAFARI BALLs!"
|
||||
local luck = t._SafariZoneGateSafariZoneWorker1GoodLuckText or "Good Luck!"
|
||||
game.stack:push(TextBox.new(game, paid .. pa .. "\f" .. luck, function()
|
||||
if done then done() end
|
||||
walkIntoZone(game, ow)
|
||||
end))
|
||||
local pa = (t._SafariZoneGateSafariZoneWorker1CallYouOnThePAText
|
||||
or "\fWe'll call you on\nthe PA when you\nrun out of time\nor SAFARI BALLs!")
|
||||
:gsub("^\f", "")
|
||||
local money = function() return game.save.money end
|
||||
-- scripts/SafariZoneGate.asm:181
|
||||
local opts = { money = money }
|
||||
-- scripts/SafariZoneGate.asm:215, pokeyellow scripts/SafariZoneGate_2.asm:57
|
||||
if not introText then
|
||||
opts = TextBox.soundOpts(game, "Get_Item1", opts)
|
||||
end
|
||||
game.stack:push(TextBox.new(game, paid, function()
|
||||
game.stack:push(TextBox.new(game, pa, function()
|
||||
if done then done() end
|
||||
walkIntoZone(game, ow)
|
||||
end, { money = money }))
|
||||
end, opts))
|
||||
end
|
||||
|
||||
-- Yellow's soft-lock fix (scripts/SafariZoneGate_2.asm): a player short of
|
||||
@@ -107,18 +118,20 @@ end
|
||||
local function joinPrompt(game, ow, done)
|
||||
done = done or function() end
|
||||
local TextBox = require("src.render.TextBox")
|
||||
local ChoiceBox = require("src.ui.ChoiceBox")
|
||||
local t = game.data.text
|
||||
local back = function(text)
|
||||
game.stack:push(TextBox.new(game, text, function()
|
||||
ow:scriptMove(ow.player, "down", 1, done, { collide = true })
|
||||
end))
|
||||
end
|
||||
-- scripts/SafariZoneGate.asm:150-154
|
||||
game.stack:push(TextBox.new(game,
|
||||
t._SafariZoneGateSafariZoneWorker1WouldYouLikeToJoinText
|
||||
or "For just ¥500 you\ncan join the hunt!\fWould you like to\njoin the hunt?",
|
||||
function()
|
||||
game.stack:push(ChoiceBox.new(game, function(yes)
|
||||
nil,
|
||||
{ money = function() return game.save.money end,
|
||||
moneyWithChoice = true,
|
||||
choice = function(yes)
|
||||
if not yes then
|
||||
back(t._SafariZoneGateSafariZoneWorker1PleaseComeAgainText
|
||||
or "OK! Please come\nagain!")
|
||||
@@ -133,8 +146,7 @@ local function joinPrompt(game, ow, done)
|
||||
game.save.money = game.save.money - FEE
|
||||
startGame(game, ow, t, done)
|
||||
end
|
||||
end))
|
||||
end))
|
||||
end }))
|
||||
end
|
||||
|
||||
M.SAFARI_ZONE_GATE = {
|
||||
@@ -154,10 +166,26 @@ M.SAFARI_ZONE_GATE = {
|
||||
},
|
||||
|
||||
-- the join trigger cells in front of the worker
|
||||
-- (SafariZoneGateDefaultScript, scripts/SafariZoneGate.asm:18-50)
|
||||
onStep = function(game, ow, x, y)
|
||||
if y ~= 2 or (x ~= 3 and x ~= 4) then return false end
|
||||
if game.save.safari then return false end -- paid, walking in
|
||||
joinPrompt(game, ow, nil)
|
||||
local TextBox = require("src.render.TextBox")
|
||||
game.stack:push(TextBox.new(game,
|
||||
game.data.text._SafariZoneGateSafariZoneWorker1Text
|
||||
or "Welcome to the\nSAFARI ZONE!",
|
||||
function()
|
||||
-- scripts/SafariZoneGate.asm:29
|
||||
ow.player.facing = "right"
|
||||
-- scripts/SafariZoneGate.asm:31-40, home/map_objects.asm:107-121
|
||||
if x == 3 then
|
||||
ow:scriptMove(ow.player, "right", 1, function()
|
||||
joinPrompt(game, ow, nil)
|
||||
end)
|
||||
else
|
||||
joinPrompt(game, ow, nil)
|
||||
end
|
||||
end))
|
||||
return true
|
||||
end,
|
||||
|
||||
@@ -165,6 +193,15 @@ M.SAFARI_ZONE_GATE = {
|
||||
-- "Leaving early?" -- yes ends the game and takes the leftover balls,
|
||||
-- no walks you back into the zone
|
||||
onEnter = function(game, ow)
|
||||
-- scripts/SafariZoneGate.asm:79-94
|
||||
if game.save.safariGameOver then
|
||||
game.save.safariGameOver = nil
|
||||
ow:queueScript({
|
||||
{ "show_text", "_SafariZoneGateSafariZoneWorker1GoodHaulComeAgainText" },
|
||||
{ "move_player", "down", 3 },
|
||||
})
|
||||
return
|
||||
end
|
||||
if not game.save.safari or ow.player.cellY > 1 then return end
|
||||
-- QUEUED, never pushed: onEnter runs inside the arriving warp's
|
||||
-- Transition midpoint, and Transition:finish pops whatever is on top
|
||||
@@ -183,11 +220,7 @@ M.SAFARI_ZONE_GATE = {
|
||||
ow:queueScript({
|
||||
{ "ask", "_SafariZoneGateSafariZoneWorker1LeavingEarlyText" },
|
||||
{ "jump_if_false", "stay" },
|
||||
-- the port never reaches SafariZoneGateLeavingSafariScript's own
|
||||
-- GOOD_HAUL_COME_AGAIN branch (safariGameOver warps straight to the
|
||||
-- counter), so the sign-off rides on this path
|
||||
{ "show_text", "_SafariZoneGateSafariZoneWorker1ReturnSafariBallsText" },
|
||||
{ "show_text", "_SafariZoneGateSafariZoneWorker1GoodHaulComeAgainText" },
|
||||
-- no value: set_field assigns nil, which is how save.safari is cleared
|
||||
{ "set_field", "safari" },
|
||||
-- move_player runs through scriptMove, which skips onStepComplete, so
|
||||
|
||||
+22
-18
@@ -751,35 +751,39 @@ M.WARDENS_HOUSE = {
|
||||
{ "jump_if_true", "got_hm04" }, -- 3
|
||||
{ "check_item", "GOLD_TEETH" }, -- 4
|
||||
{ "jump_if_false", "no_teeth" }, -- 5
|
||||
{ "show_text", "_WardensHouseWardenGaveTheGoldTeethText" }, -- 6
|
||||
{ "take_item", "GOLD_TEETH", 1 }, -- 7
|
||||
{ "set_flag", "EVENT_GAVE_GOLD_TEETH" }, -- 8
|
||||
{ "show_text", "_WardensHouseWardenThanksText" }, -- 9
|
||||
-- .GaveTheGoldTeethText has no text_end and falls through into
|
||||
-- .PoppedInHisTeethText -- scripts/WardensHouse.asm:71
|
||||
{ "text_sound", "Get_Item1" }, -- 6
|
||||
{ "show_text", "_WardensHouseWardenGaveTheGoldTeethText" }, -- 7
|
||||
{ "show_text", "_WardensHouseWardenTeethPoppedInHisTeethText" }, -- 8
|
||||
{ "take_item", "GOLD_TEETH", 1 }, -- 9
|
||||
{ "set_flag", "EVENT_GAVE_GOLD_TEETH" }, -- 10
|
||||
{ "show_text", "_WardensHouseWardenThanksText" }, -- 11
|
||||
-- give-then-print like scripts/WardensHouse.asm
|
||||
{ "give_item", "HM_STRENGTH", 1, false }, -- 10
|
||||
{ "show_text", "_WardensHouseWardenReceivedHM04Text" }, -- 11
|
||||
{ "set_flag", "EVENT_GOT_HM04" }, -- 12
|
||||
{ "jump", "end" }, -- 13 (jp .done)
|
||||
{ "give_item", "HM_STRENGTH", 1, false }, -- 12
|
||||
{ "show_text", "_WardensHouseWardenReceivedHM04Text" }, -- 13
|
||||
{ "set_flag", "EVENT_GOT_HM04" }, -- 14
|
||||
{ "jump", "end" }, -- 15 (jp .done)
|
||||
|
||||
-- #645: WardensHouseWardenText prints Gibberish1, then YesNoChoice,
|
||||
-- and the warden answers the same gibberish either way -- Gibberish2
|
||||
-- on yes, Gibberish3 on no (scripts/WardensHouse.asm). The port
|
||||
-- printed the question and walked off before the answer.
|
||||
{ "label", "no_teeth" }, -- 14
|
||||
{ "ask", "_WardensHouseWardenGibberish1Text" }, -- 15
|
||||
{ "jump_if_true", "gibberish_yes" }, -- 16
|
||||
{ "show_text", "_WardensHouseWardenGibberish3Text" }, -- 17
|
||||
{ "jump", "end" }, -- 18
|
||||
{ "label", "gibberish_yes" }, -- 19
|
||||
{ "show_text", "_WardensHouseWardenGibberish2Text" }, -- 20
|
||||
{ "jump", "end" }, -- 21
|
||||
{ "label", "no_teeth" }, -- 16
|
||||
{ "ask", "_WardensHouseWardenGibberish1Text" }, -- 17
|
||||
{ "jump_if_true", "gibberish_yes" }, -- 18
|
||||
{ "show_text", "_WardensHouseWardenGibberish3Text" }, -- 19
|
||||
{ "jump", "end" }, -- 20
|
||||
{ "label", "gibberish_yes" }, -- 21
|
||||
{ "show_text", "_WardensHouseWardenGibberish2Text" }, -- 22
|
||||
{ "jump", "end" }, -- 23
|
||||
|
||||
-- #535: pokered .got_item branch (scripts/WardensHouse.asm) --
|
||||
-- printed on every subsequent talk once EVENT_GOT_HM04 is set.
|
||||
-- Text is _WardensHouseWardenHM04ExplanationText (text/WardensHouse.asm):
|
||||
-- HM04 teaches Strength, and hints at the Safari Zone secret house.
|
||||
{ "label", "got_hm04" }, -- 22
|
||||
{ "show_text", "_WardensHouseWardenHM04ExplanationText" }, -- 23
|
||||
{ "label", "got_hm04" }, -- 24
|
||||
{ "show_text", "_WardensHouseWardenHM04ExplanationText" }, -- 25
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -357,13 +357,15 @@ local function saffronGate(guardText, triggers, horizontal)
|
||||
return
|
||||
end
|
||||
if takeGuardDrink(game) then
|
||||
-- SaffronGateGuardGiveDrinkText: sound_get_key_item sits between the
|
||||
-- two text_far halves (scripts/Route5Gate.asm:102-106)
|
||||
game.stack:push(TextBox.new(game,
|
||||
t._SaffronGateGuardImParchedText or "Whoa, boy!\nI'm parched!",
|
||||
function()
|
||||
game.stack:push(TextBox.new(game,
|
||||
(t._SaffronGateGuardYouCanGoOnThroughText or
|
||||
"You can go on\nthrough!"), done))
|
||||
end))
|
||||
end, TextBox.soundOpts(game, "Get_Key_Item")))
|
||||
return
|
||||
end
|
||||
game.stack:push(TextBox.new(game,
|
||||
@@ -403,7 +405,7 @@ local function saffronGate(guardText, triggers, horizontal)
|
||||
game.stack:push(TextBox.new(game,
|
||||
(t._SaffronGateGuardYouCanGoOnThroughText or
|
||||
"You can go on\nthrough!")))
|
||||
end))
|
||||
end, TextBox.soundOpts(game, "Get_Key_Item")))
|
||||
return true
|
||||
end
|
||||
local back
|
||||
|
||||
@@ -3970,7 +3970,11 @@ function BattleState:executeAction(user, target, action)
|
||||
-- nickname on purpose (no "Enemy " in AIPrintItemUseText), so the
|
||||
-- prefix splice must not touch them.
|
||||
for _, m in ipairs(TrainerAI.useItem(self, action.item)) do
|
||||
self:sayNext(m)
|
||||
if type(m) == "table" then
|
||||
self:animNext(m.anim, false)
|
||||
else
|
||||
self:sayNext(m)
|
||||
end
|
||||
end
|
||||
self:drainNext()
|
||||
require("src.core.Sound").play(self.data, "Heal_Ailment")
|
||||
@@ -5764,6 +5768,18 @@ local function balls()
|
||||
return ballQuads or nil
|
||||
end
|
||||
|
||||
-- engine/battle/draw_hud_pokeball_gfx.asm:97-108
|
||||
local function ballObpSheet()
|
||||
local PaletteFX = require("src.render.PaletteFX")
|
||||
if not PaletteFX.usesSpriteObp() then return nil end
|
||||
local colors, group = PaletteFX.ogObj()
|
||||
if not colors then return nil end
|
||||
local SpriteRenderer = require("src.render.SpriteRenderer")
|
||||
local ok, img = pcall(SpriteRenderer.obpImage,
|
||||
"assets/generated/battle/balls.png", colors, group)
|
||||
return ok and img or nil
|
||||
end
|
||||
|
||||
function BattleState:drawCaughtBall(x, y)
|
||||
local quads = balls()
|
||||
if not quads then return end
|
||||
@@ -5774,10 +5790,16 @@ end
|
||||
function BattleState:drawBallRow(party, x, y, dx)
|
||||
local quads = balls()
|
||||
if not quads then return end
|
||||
local obp = ballObpSheet()
|
||||
local img = obp or quads.img
|
||||
for i = 1, 6 do
|
||||
local mon = party[i]
|
||||
local tile = not mon and 3 or mon.hp <= 0 and 2 or mon.status and 1 or 0
|
||||
love.graphics.draw(quads.img, quads[tile], x + (i - 1) * dx, y)
|
||||
love.graphics.draw(img, quads[tile], x + (i - 1) * dx, y)
|
||||
if obp then
|
||||
require("src.render.PaletteFX")
|
||||
.markUiSpriteRedraw(img, quads[tile], x + (i - 1) * dx, y)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -135,6 +135,8 @@ function TrainerAI.useItem(battle, item)
|
||||
-- trainer_ai.asm:719 -> effects.asm:414-415
|
||||
Status.afterStatChange(battle, enemy, stat, battle.player)
|
||||
enemy.hazeStatReset = nil
|
||||
-- trainer_ai.asm:716 -> effects.asm:484 (PlayCurrentMoveAnimation)
|
||||
table.insert(msgs, { anim = "XSTATITEM_DUPLICATE_ANIM" })
|
||||
table.insert(msgs, Strings("%s's\n%s rose!", displayName(enemy), Strings(STAT_LABEL[stat])))
|
||||
elseif item == "GUARD_SPEC" then
|
||||
enemy.mist = true
|
||||
|
||||
@@ -627,6 +627,10 @@ function ItemEffects.use(data, save, itemId, target, battle, moveIndex, ow)
|
||||
end
|
||||
|
||||
if itemId == "ESCAPE_ROPE" then
|
||||
-- ItemUseEscapeRope: engine/items/item_effects.asm:1492
|
||||
if battle then
|
||||
return "failed", { notTime(data, save) }
|
||||
end
|
||||
return "escape_rope"
|
||||
end
|
||||
if itemId == "TOWN_MAP" then
|
||||
|
||||
@@ -146,10 +146,23 @@ local keyedShader = nil
|
||||
local keyedFailed = false
|
||||
local remapShader = nil
|
||||
local remapFailed = false
|
||||
local shaderCtor = nil
|
||||
|
||||
local function compiler()
|
||||
local ctor = love and love.graphics and love.graphics.newShader or nil
|
||||
if ctor ~= shaderCtor then
|
||||
shaderCtor = ctor
|
||||
shader, failed = nil, false
|
||||
keyedShader, keyedFailed = nil, false
|
||||
remapShader, remapFailed = nil, false
|
||||
end
|
||||
return ctor
|
||||
end
|
||||
|
||||
-- nil (and a one-shot warning) if shaders are unavailable, so callers can fall
|
||||
-- back to the plain grayscale draw instead of crashing a whole boot.
|
||||
function GbcPalette.shader()
|
||||
compiler()
|
||||
if shader or failed then return shader end
|
||||
if not (love and love.graphics and love.graphics.newShader) then
|
||||
failed = true
|
||||
@@ -165,6 +178,7 @@ function GbcPalette.shader()
|
||||
end
|
||||
|
||||
function GbcPalette.keyedShader()
|
||||
compiler()
|
||||
if keyedShader or keyedFailed then return keyedShader end
|
||||
if not (love and love.graphics and love.graphics.newShader) then
|
||||
keyedFailed = true
|
||||
@@ -182,6 +196,7 @@ end
|
||||
-- The same contract as GbcPalette.shader for the backwards pass: nil rather
|
||||
-- than an error, so a caller can fall back to its own approximation.
|
||||
function GbcPalette.remapShader()
|
||||
compiler()
|
||||
if remapShader or remapFailed then return remapShader end
|
||||
if not (love and love.graphics and love.graphics.newShader) then
|
||||
remapFailed = true
|
||||
|
||||
@@ -82,6 +82,25 @@ local function darkGroups(groups)
|
||||
return out
|
||||
end
|
||||
|
||||
-- home/fade.asm:52-58
|
||||
local fadeObpMap = nil
|
||||
local fadeObpGroups = setmetatable({}, { __mode = "k" })
|
||||
|
||||
function PaletteFX.setFadeObp(map)
|
||||
fadeObpMap = map
|
||||
end
|
||||
|
||||
function PaletteFX.fadeObp(colors, group)
|
||||
if not (colors and fadeObpMap) then return colors, group end
|
||||
local suffix = fadeObpGroups[fadeObpMap]
|
||||
if not suffix then
|
||||
suffix = "#fade" .. fadeObpMap[0] .. fadeObpMap[1]
|
||||
.. fadeObpMap[2] .. fadeObpMap[3]
|
||||
fadeObpGroups[fadeObpMap] = suffix
|
||||
end
|
||||
return PaletteFX.permute(colors, fadeObpMap), tostring(group) .. suffix
|
||||
end
|
||||
|
||||
-- Classic DMG pea-soup greens (#9BBC0F / #8BAC0F / #306230 / #0F380F)
|
||||
PaletteFX.CLASSIC = {
|
||||
{ 155, 188, 15 }, { 139, 172, 15 }, { 48, 98, 48 }, { 15, 56, 15 },
|
||||
@@ -143,9 +162,9 @@ end
|
||||
-- is unused there and kept as Red green only as a safe leftover.
|
||||
function PaletteFX.ogObj()
|
||||
if GameVersion.isBlue() then
|
||||
return PaletteFX.darkObp(PaletteFX.GBC_OBJ_BLUE, "gbcobj_blue")
|
||||
return PaletteFX.fadeObp(PaletteFX.darkObp(PaletteFX.GBC_OBJ_BLUE, "gbcobj_blue"))
|
||||
end
|
||||
return PaletteFX.darkObp(PaletteFX.GBC_OBJ, "gbcobj")
|
||||
return PaletteFX.fadeObp(PaletteFX.darkObp(PaletteFX.GBC_OBJ, "gbcobj"))
|
||||
end
|
||||
|
||||
-- The DMG object ramp every mode except OG RED bakes onto overworld sprites,
|
||||
|
||||
@@ -358,6 +358,7 @@ function Renderer:beginFrame(transparent)
|
||||
-- rBGP is a per-frame register here: the state that draws a dark map
|
||||
-- re-arms it while it draws (#322), so nothing inherits last frame's
|
||||
PaletteFX.setShadeMap(nil)
|
||||
PaletteFX.setFadeObp(nil)
|
||||
PaletteFX.setPass("ui")
|
||||
love.graphics.setCanvas(self.canvas)
|
||||
if transparent then
|
||||
|
||||
+20
-3
@@ -173,6 +173,9 @@ function TextBox.new(game, text, onDone, opts)
|
||||
-- opts.pauseSounds[i] is the sfx the i-th marker fires once its wait is
|
||||
-- over (text_asm SFX_SWAP, engine/pokemon/learn_move.asm:210-213)
|
||||
self.pauseSounds = opts and opts.pauseSounds
|
||||
-- opts.pauseSoundWait: the mark is TextCommand_SOUND, not TextCommand_PAUSE
|
||||
-- (home/text.asm:506-534)
|
||||
self.pauseSoundWait = opts and opts.pauseSoundWait
|
||||
self.pauseAt = marks and mapPauses(self.pages, marks) or nil
|
||||
self.pageIndex = 1
|
||||
self.lineIndex = 1
|
||||
@@ -445,11 +448,24 @@ function TextBox:update(dt)
|
||||
end
|
||||
self.pauseFrames = nil
|
||||
local snd = self.pauseSounds and self.pauseSounds[self.pauseMark]
|
||||
local src
|
||||
if type(snd) == "function" then
|
||||
snd()
|
||||
src = snd()
|
||||
elseif snd then
|
||||
require("src.core.Sound").play(self.game.data, snd)
|
||||
src = require("src.core.Sound").play(self.game.data, snd)
|
||||
end
|
||||
-- home/text.asm:530
|
||||
if src and self.pauseSoundWait then
|
||||
self.pauseSrc = src
|
||||
self.pauseSrcLeft = sfxWaitFrames(src)
|
||||
end
|
||||
end
|
||||
if self.pauseSrc then
|
||||
self.pauseSrcLeft = (self.pauseSrcLeft or 0) - sfxWaitStep(self.game)
|
||||
local playing = self.pauseSrc.isPlaying and self.pauseSrc:isPlaying()
|
||||
if playing and self.pauseSrcLeft > 0 then return end
|
||||
if playing then pcall(self.pauseSrc.stop, self.pauseSrc) end
|
||||
self.pauseSrc, self.pauseSrcLeft = nil, nil
|
||||
end
|
||||
if self.done then
|
||||
-- opts.stay: the box is finished but stays up under whatever the caller
|
||||
@@ -607,7 +623,8 @@ function TextBox:update(dt)
|
||||
if marks and marks[self.charIndex] then
|
||||
self.pauseMark = marks[self.charIndex]
|
||||
-- TextCommand_PAUSE reads hJoyHeld, so a held A/B skips the wait
|
||||
self.pauseFrames = (input:isDown("a") or input:isDown("b"))
|
||||
self.pauseFrames = (self.pauseSoundWait
|
||||
or input:isDown("a") or input:isDown("b"))
|
||||
and 0 or PAUSE_FRAMES
|
||||
break
|
||||
end
|
||||
|
||||
@@ -30,6 +30,13 @@ local FADE_BGP = {
|
||||
white = { out = { 0x90, 0x40, 0x00 },
|
||||
["in"] = { 0x40, 0x90, 0xE4 } },
|
||||
}
|
||||
-- FadePal1..FadePal8 rOBP0 column, pokered home/fade.asm:65-73
|
||||
local FADE_OBP0 = {
|
||||
black = { out = { 0xD0, 0xE4, 0xFE, 0xFF },
|
||||
["in"] = { 0xFF, 0xFE, 0xE4, 0xD0 } },
|
||||
white = { out = { 0x80, 0x40, 0x00 },
|
||||
["in"] = { 0x40, 0x80, 0xD0 } },
|
||||
}
|
||||
local BGP_IDENTITY = 0xE4
|
||||
|
||||
-- Veil alpha `t` frames into a `len`-frame fade out. GBFadeOutToBlack
|
||||
@@ -155,20 +162,28 @@ function Transition.shadeMapFor(byte)
|
||||
return map
|
||||
end
|
||||
|
||||
function Transition:fadeTable()
|
||||
function Transition:fadeTable(column)
|
||||
local c = self.color or { 0, 0, 0 }
|
||||
local white = (c[1] or 0) > 0.5 and (c[2] or 0) > 0.5 and (c[3] or 0) > 0.5
|
||||
local set = FADE_BGP[white and "white" or "black"]
|
||||
local set = (column or FADE_BGP)[white and "white" or "black"]
|
||||
return set[self.phase == "in" and "in" or "out"]
|
||||
end
|
||||
|
||||
local function fadeByte(self, tab)
|
||||
local len = (self.phase == "out") and self.frames or self.framesIn
|
||||
local step = math.min(#tab,
|
||||
math.floor(fadeAlpha(self.t, len) * (#tab - 1) + 0.5) + 1)
|
||||
return tab[step] or 0xFF
|
||||
end
|
||||
|
||||
-- pokered home/fade.asm:58
|
||||
function Transition:bgp()
|
||||
local len = (self.phase == "out") and self.frames or self.framesIn
|
||||
local tab = self:fadeTable()
|
||||
local step = math.min(#tab,
|
||||
math.floor(fadeAlpha(self.t, len) * (#tab - 1) + 0.5) + 1)
|
||||
return tab[step] or 0xFF
|
||||
return fadeByte(self, self:fadeTable())
|
||||
end
|
||||
|
||||
-- pokered home/fade.asm:55
|
||||
function Transition:obp0()
|
||||
return fadeByte(self, self:fadeTable(FADE_OBP0))
|
||||
end
|
||||
|
||||
function Transition:draw()
|
||||
|
||||
+21
-7
@@ -124,6 +124,9 @@ local DIVIDER = {
|
||||
0x6B, 0x6B, 0x69, 0x6B, 0x69, 0x6B, 0x69, 0x6B, 0x69, 0x6A,
|
||||
}
|
||||
|
||||
-- engine/menus/pokedex.asm:500-504
|
||||
local PIC_DELAY = 36
|
||||
|
||||
function DexEntryMenu.new(game, speciesOrOpts, onDone)
|
||||
local species, forceOwned = resolveArgs(speciesOrOpts)
|
||||
local self = setmetatable({ game = game, forceOwned = forceOwned,
|
||||
@@ -140,8 +143,8 @@ function DexEntryMenu.new(game, speciesOrOpts, onDone)
|
||||
self.blink = 0
|
||||
local pages = descPages(game, self.def, forceOwned)
|
||||
self.pageCount = pages and #pages or 1
|
||||
-- engine/menus/pokedex.asm:500-506, home/pokemon.asm:145-148
|
||||
self.crySrc = require("src.core.Sound").playCry(game.data, species)
|
||||
self.species = species
|
||||
self.picDelay = PIC_DELAY
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -158,6 +161,15 @@ end
|
||||
function DexEntryMenu:update(dt)
|
||||
local input = self.game.input
|
||||
self.blink = ((self.blink or 0) + 1) % 60
|
||||
if (self.picDelay or 0) > 0 then
|
||||
self.picDelay = self.picDelay - 1
|
||||
if self.picDelay == 0 then
|
||||
-- engine/menus/pokedex.asm:504-506, home/pokemon.asm:145-148
|
||||
self.crySrc = require("src.core.Sound").playCry(self.game.data,
|
||||
self.species)
|
||||
end
|
||||
return
|
||||
end
|
||||
if self:crying() then return end
|
||||
if input:wasPressed("a") or input:wasPressed("b") then
|
||||
-- home/text.asm:245
|
||||
@@ -171,9 +183,10 @@ function DexEntryMenu:update(dt)
|
||||
end
|
||||
|
||||
function DexEntryMenu:draw()
|
||||
DexEntryMenu.render(self.game, self.def, self.sprite, self.forceOwned,
|
||||
self.spriteTrueColor, self.page,
|
||||
{ crying = self:crying(),
|
||||
local waiting = (self.picDelay or 0) > 0
|
||||
DexEntryMenu.render(self.game, self.def, not waiting and self.sprite or nil,
|
||||
self.forceOwned, self.spriteTrueColor, self.page,
|
||||
{ crying = self:crying(), waiting = waiting,
|
||||
arrow = (self.blink or 0) < 30 })
|
||||
end
|
||||
|
||||
@@ -242,7 +255,8 @@ function DexEntryMenu.render(game, def, sprite, forceOwned, trueColor, page, sta
|
||||
Font.draw("″", 136, 48)
|
||||
Font.draw(Strings("WT"), 72, 64)
|
||||
Font.draw(Strings("lb"), 136, 64)
|
||||
if not numbers then
|
||||
-- engine/menus/pokedex.asm:449-450, overwritten at :518-520
|
||||
if not numbers or state.crying or state.waiting then
|
||||
Font.draw("?", 104, 48)
|
||||
Font.draw("??", 120, 48)
|
||||
Font.draw("???", 112, 64)
|
||||
@@ -250,7 +264,7 @@ function DexEntryMenu.render(game, def, sprite, forceOwned, trueColor, page, sta
|
||||
end
|
||||
-- engine/menus/pokedex.asm:516: everything below the divider waits on the
|
||||
-- cry the line above it started
|
||||
if state.crying then
|
||||
if state.crying or state.waiting then
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
return
|
||||
end
|
||||
|
||||
+11
-6
@@ -185,10 +185,11 @@ function StartMenu.new(game)
|
||||
local maxVisible = math.floor((Renderer.HEIGHT / 8 - 2) / rowStep)
|
||||
menu = Menu.new(game, items,
|
||||
-- the START menu hugs the top-right corner of the SCREEN, not of a
|
||||
-- centred letterbox: at 9,0 x 11 it is already flush with the top and
|
||||
-- right of the 20x18 grid, so the anchor keeps it flush when the view
|
||||
-- is zoomed out and the letterbox no longer fills the window
|
||||
{ tx = 9, ty = 0, tw = 11, maxVisible = maxVisible, startCloses = true,
|
||||
-- centred letterbox: at 10,0 x 10 (engine/menus/draw_start_menu.asm:5-12)
|
||||
-- it is already flush with the top and right of the 20x18 grid, so the
|
||||
-- anchor keeps it flush when the view is zoomed out and the letterbox no
|
||||
-- longer fills the window
|
||||
{ tx = 10, ty = 0, tw = 10, maxVisible = maxVisible, startCloses = true,
|
||||
anchor = "topright" })
|
||||
-- the cursor position survives closing the menu
|
||||
-- (wBattleAndStartSavedMenuItem, home/start_menu.asm)
|
||||
@@ -202,7 +203,7 @@ function StartMenu.new(game)
|
||||
|
||||
-- inside the Safari Zone the start menu also shows remaining steps and
|
||||
-- SAFARI BALLs (PrintSafariZoneSteps, engine/overworld/player_state.asm:
|
||||
-- 219-224): a 9x5 border at the top-left with "steps/500" and "BALL xx".
|
||||
-- 219-224): a 9x5 border at the top-left with "steps/500" and "BALL×xx".
|
||||
-- It opens with `cp SAFARI_ZONE_EAST / ret c`, so only the nine interior
|
||||
-- maps ($D9..$E1) get it -- SAFARI_ZONE_GATE is $9C and falls under that
|
||||
-- early out, and used to show "502/500" while the player was still
|
||||
@@ -220,7 +221,11 @@ function StartMenu.new(game)
|
||||
Font.draw(("%3d"):format(math.floor(safari.steps or 0)), 8, 8)
|
||||
Font.draw("/500", 32, 8)
|
||||
Font.draw(Strings("BALL"), 8, 24)
|
||||
Font.draw(("%2d"):format(math.floor(safari.balls or 0)), 48, 24)
|
||||
-- engine/overworld/player_state.asm:236-249, home/print_num.asm:205-217
|
||||
local digits = tostring(math.max(0, math.floor(safari.balls or 0)))
|
||||
local bx = (7 - #digits) * 8
|
||||
Font.draw("\xc3\x97", bx, 24)
|
||||
Font.draw(digits, bx + 8, 24)
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
end
|
||||
end
|
||||
|
||||
+26
-10
@@ -75,6 +75,13 @@ function TrainerCard.new(game, opts)
|
||||
game.data, "front", { kind = "trainer_card" })
|
||||
self.pic = tryImage(picPath)
|
||||
self.picTrueColor = self.pic and picTrueColor or false
|
||||
-- engine/menus/start_sub_menus.asm:480-487
|
||||
if self.pic then
|
||||
local pw, ph = self.pic:getDimensions()
|
||||
self.picW = math.max(0, math.min(40, pw))
|
||||
self.picH = math.min(56, ph)
|
||||
self.picQuad = love.graphics.newQuad(0, 0, self.picW, self.picH, pw, ph)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
@@ -123,9 +130,9 @@ function TrainerCard:draw()
|
||||
|
||||
-- top card (rows 0-7): NAME / MONEY / TIME, pic upper-right
|
||||
self:frameBox(0, 0, 20, 8)
|
||||
if self.pic then
|
||||
if self.pic and self.picQuad then
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
love.graphics.draw(self.pic, 104, 4)
|
||||
love.graphics.draw(self.pic, self.picQuad, 120, 8)
|
||||
-- True-colour portraits (e.g. mod-injected custom characters) carry their
|
||||
-- own colours and must not be re-mapped by the MEWMON zone shader.
|
||||
-- markTrueColor appends a colors=false zone that the Renderer splices at
|
||||
@@ -133,19 +140,19 @@ function TrainerCard:draw()
|
||||
-- the palette shader on top of the already-colourised frame.
|
||||
-- This matches the pattern used by OakSpeech, HallOfFame and SummaryMenu.
|
||||
if self.picTrueColor then
|
||||
local w, h = self.pic:getDimensions()
|
||||
require("src.render.PaletteFX").markTrueColor(104, 4, w, h)
|
||||
require("src.render.PaletteFX").markTrueColor(120, 8, self.picW, self.picH)
|
||||
end
|
||||
end
|
||||
love.graphics.setColor(0, 0, 0, 1)
|
||||
Font.draw(Strings("NAME/%s", save.player.name or "RED"), 16, 16)
|
||||
Font.draw(Strings("MONEY/¥%d", save.money or 0), 16, 32)
|
||||
local t = math.floor(save.playTime or 0)
|
||||
Font.draw(Strings("TIME/%3d:%02d", math.floor(t / 3600),
|
||||
-- home/print_num.asm:217
|
||||
Font.draw(Strings("TIME/ %d:%02d", math.floor(t / 3600),
|
||||
math.floor(t / 60) % 60), 16, 48)
|
||||
|
||||
-- the circle-dotted BADGES banner (TrainerInfo_BadgesText)
|
||||
self:frameBox(0, 8, 20, 3)
|
||||
-- engine/menus/start_sub_menus.asm:544
|
||||
love.graphics.setColor(0, 0, 0, 1)
|
||||
Font.draw(Strings("BADGES"), 56, 72)
|
||||
if self.circle then
|
||||
@@ -155,12 +162,21 @@ function TrainerCard:draw()
|
||||
love.graphics.setColor(0, 0, 0, 1)
|
||||
end
|
||||
|
||||
-- numbered badge grid (rows 11-17): face by default, badge when owned
|
||||
self:frameBox(0, 11, 20, 7)
|
||||
-- numbered badge grid (rows 10-17): face by default, badge when owned
|
||||
-- engine/menus/start_sub_menus.asm:537
|
||||
self:frameBox(1, 10, 18, 8)
|
||||
if self.frame then
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
for j = 0, 7 do
|
||||
love.graphics.draw(self.frame.img, self.frame.quads[8], 0, (10 + j) * 8)
|
||||
love.graphics.draw(self.frame.img, self.frame.quads[8], 152, (10 + j) * 8)
|
||||
end
|
||||
end
|
||||
local badges = Badges.list(self.game.data)
|
||||
for i = 1, #badges do
|
||||
local col, row = (i - 1) % 4, math.floor((i - 1) / 4)
|
||||
local tx, ty = 16 + col * 32, 94 + row * 24
|
||||
-- engine/menus/draw_badges.asm:46
|
||||
local tx, ty = 16 + col * 32, 88 + row * 24
|
||||
-- the extracted sheets cover the eight Kanto slots; a longer badge
|
||||
-- list draws its extra entries unnumbered rather than crashing
|
||||
if self.nums and self.nums.quads[i - 1] then
|
||||
@@ -171,7 +187,7 @@ function TrainerCard:draw()
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
local owned = save.inventory[Badges.itemFor(badges[i])]
|
||||
local sheet = owned and self.badges or self.faces
|
||||
love.graphics.draw(sheet.img, sheet.quads[i - 1], tx + 4, ty + 6)
|
||||
love.graphics.draw(sheet.img, sheet.quads[i - 1], tx + 8, ty + 8)
|
||||
end
|
||||
end
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
|
||||
+30
-8
@@ -136,6 +136,9 @@ local RELEASED = Strings.source("Released <PK><MN>.\fBye,\n%s!")
|
||||
local GOT_MON = Strings.source("Got %s!")
|
||||
local STORED_MON = Strings.source("Stored %s!")
|
||||
|
||||
-- engine/pokemon/bills_pc.asm:1804 `ld c, 50 / call DelayFrames`
|
||||
local STORE_MESSAGE_FRAMES = 50
|
||||
|
||||
-- Boxes.lua owns the storage mutation, so its finite refusals arrive here as
|
||||
-- return values. Mark their complete text for the catalog and look them up
|
||||
-- when they cross this UI boundary.
|
||||
@@ -184,6 +187,7 @@ function BoxMenu.new(game, opts)
|
||||
self.scroll = 0
|
||||
self.picCache = {}
|
||||
self.message = nil
|
||||
self.messageFrames = nil
|
||||
-- nil while the list is being browsed; "submenu" while MOVE/STATS/CANCEL is
|
||||
-- up, "insert" while the insert cursor is picking a destination. Only the
|
||||
-- move screen has phases -- the other two lists act on A.
|
||||
@@ -364,6 +368,15 @@ function BoxMenu:openStats()
|
||||
})
|
||||
end
|
||||
|
||||
-- engine/pokemon/bills_pc.asm:1785-1787 ClearBox over the left panel, then the
|
||||
-- timed hold at :1804; .Init (:161) only runs after it.
|
||||
function BoxMenu:holdMessage(text)
|
||||
self.message = text
|
||||
self.messagePage = 1
|
||||
self.messageFrames = STORE_MESSAGE_FRAMES
|
||||
self.panelCleared = true
|
||||
end
|
||||
|
||||
-- engine/pokemon/bills_pc.asm:397-411: failed withdraw stays on the submenu.
|
||||
function BoxMenu:doWithdraw()
|
||||
local ok, result = Boxes.withdraw(self.save, self.boxIndex, self.index)
|
||||
@@ -376,9 +389,7 @@ function BoxMenu:doWithdraw()
|
||||
-- engine/pokemon/bills_pc.asm:1817
|
||||
self:playMonCry(result)
|
||||
local name = result.nickname or result.name or result.species or "?"
|
||||
self.message = Strings(GOT_MON, name)
|
||||
self.phase = nil
|
||||
self:clampIndex()
|
||||
self:holdMessage(Strings(GOT_MON, name))
|
||||
end
|
||||
|
||||
-- engine/pokemon/bills_pc.asm:155 BillsPCDepositFuncDeposit
|
||||
@@ -394,10 +405,7 @@ function BoxMenu:doDeposit()
|
||||
end
|
||||
-- engine/pokemon/bills_pc.asm:1762
|
||||
self:playMonCry(result)
|
||||
self.message = Strings(STORED_MON, name)
|
||||
self.phase = nil
|
||||
self.index, self.scroll = 1, 0
|
||||
self:clampIndex()
|
||||
self:holdMessage(Strings(STORED_MON, name))
|
||||
end
|
||||
|
||||
function BoxMenu:chooseSubmenu()
|
||||
@@ -522,6 +530,20 @@ function BoxMenu:update(_dt)
|
||||
local input = self.game and self.game.input
|
||||
if not input then return end
|
||||
|
||||
-- engine/pokemon/bills_pc.asm:1804 DelayFrames reads no joypad; :161 zeroes
|
||||
-- wJumptableIndex / cursor / scroll only once it returns.
|
||||
if self.messageFrames then
|
||||
self.messageFrames = self.messageFrames - 1
|
||||
if self.messageFrames > 0 then return end
|
||||
self.messageFrames = nil
|
||||
self.message, self.messagePage = nil, nil
|
||||
self.panelCleared = nil
|
||||
self.phase = nil
|
||||
self.index, self.scroll = 1, 0
|
||||
self:clampIndex()
|
||||
return
|
||||
end
|
||||
|
||||
if self.message then
|
||||
if input:wasPressed("a") or input:wasPressed("b") then
|
||||
local page = (self.messagePage or 1) + 1
|
||||
@@ -954,7 +976,7 @@ function BoxMenu:drawPanel()
|
||||
|
||||
-- The left panel: pic, level, gender, species -- blank on CANCEL, the way
|
||||
-- PCMonInfo clears it when the selection is not a mon.
|
||||
local mon = self:panelMon()
|
||||
local mon = not self.panelCleared and self:panelMon() or nil
|
||||
if mon then
|
||||
-- `cp EGG / ret z` right after the frontpic: no name, no level, no gender
|
||||
-- (engine/pokemon/bills_pc.asm:1057-1058).
|
||||
|
||||
@@ -69,7 +69,8 @@ local SAFARI = {
|
||||
"SAFARI_ZONE_NORTH_REST_HOUSE", "SAFARI_ZONE_WEST_REST_HOUSE",
|
||||
"SAFARI_ZONE_SECRET_HOUSE",
|
||||
},
|
||||
exitWarp = { map = "SAFARI_ZONE_GATE", x = 4, y = 3, facing = "down" },
|
||||
-- wDestinationWarpID $3 = warp_event 4,0 (data/maps/objects/SafariZoneGate.asm:12)
|
||||
exitWarp = { map = "SAFARI_ZONE_GATE", x = 4, y = 0, facing = "down" },
|
||||
}
|
||||
|
||||
-- home/overworld.asm LoadPlayerSpriteGraphics / LoadSurfingPlayerSprite-
|
||||
|
||||
@@ -84,9 +84,15 @@ local TELEPORT_OUT_SFX = {
|
||||
[4] = "Teleport_Exit2", [8] = "Teleport_Exit2", [12] = "Teleport_Exit2",
|
||||
[TELEPORT_OUT_INPLACE] = "Teleport_Exit1",
|
||||
}
|
||||
-- engine/overworld/player_animations.asm:104-118
|
||||
local WARP_PAD_OUT_HOLDS = { 3, 3, 3, 3, 3, 0 }
|
||||
local WARP_PAD_OUT_FRAMES = 15
|
||||
-- counting 0 up to 8 -- engine/overworld/player_animations.asm:20
|
||||
local TELEPORT_IN_HOLDS = { 3, 3, 3, 3, 3, 0, 1, 2, 3, 4, 5, 6, 7, 0 }
|
||||
local TELEPORT_IN_FRAMES = 43
|
||||
-- engine/overworld/player_animations.asm:22-25
|
||||
local TELEPORT_IN_PAD_HOLDS = { 3, 3, 3, 3, 3, 3, 0 }
|
||||
local TELEPORT_IN_PAD_FRAMES = 18
|
||||
local SPIN_DOWN_STEPS = 6
|
||||
-- engine/overworld/player_animations.asm:41-45
|
||||
local HOLE_IN_HOLDS = { 3, 3, 3, 3, 3, 0 }
|
||||
@@ -108,20 +114,31 @@ local HEAL_FLASH_MAP = { [0] = 0, [1] = 2, [2] = 1, [3] = 3 }
|
||||
-- engine/overworld/healing_machine.asm:54
|
||||
local HEAL_FLASH_MAP_GBC = { [0] = 0, [1] = 0, [2] = 1, [3] = 2 }
|
||||
|
||||
-- engine/overworld/healing_machine.asm:13-14
|
||||
local HEAL_OBP1_MAP = { [0] = 0, [1] = 0, [2] = 2, [3] = 3 }
|
||||
-- engine/overworld/healing_machine.asm:50
|
||||
local HEAL_OBP1_FLASH_MAP = { [0] = 0, [1] = 2, [2] = 0, [3] = 3 }
|
||||
|
||||
-- engine/overworld/healing_machine.asm:74
|
||||
local function healMachineShader(visible)
|
||||
local base = PaletteFX.usesGbcPack() and PaletteFX.healMachineObp() or nil
|
||||
local ogColors = nil
|
||||
if not base and PaletteFX.usesSpriteObp() then
|
||||
ogColors = PaletteFX.ogObj()
|
||||
base = ogColors and PaletteFX.permute(ogColors,
|
||||
visible and HEAL_OBP1_MAP or HEAL_OBP1_FLASH_MAP)
|
||||
end
|
||||
if not base and visible then return nil end
|
||||
local shader = PaletteFX.shader()
|
||||
if not shader then return nil end
|
||||
local colors = base or PaletteFX.GRAYS
|
||||
if not visible then
|
||||
if not visible and not ogColors then
|
||||
colors = PaletteFX.permute(colors,
|
||||
base and HEAL_FLASH_MAP_GBC or HEAL_FLASH_MAP)
|
||||
end
|
||||
PaletteFX.sendColors(shader, colors)
|
||||
love.graphics.setShader(shader)
|
||||
return shader
|
||||
return shader, ogColors and colors or nil
|
||||
end
|
||||
|
||||
-- scripts/VermilionDock.asm:39 VermilionDockSSAnneLeavesScript: her hull is
|
||||
@@ -388,6 +405,7 @@ function OverworldState:setMap(mapId, x, y, facing, opts)
|
||||
end
|
||||
self.marchers = {}
|
||||
self.shipAnim = nil
|
||||
self.spinnerSliding = nil
|
||||
local queue = self.pendingScripts
|
||||
if queue then
|
||||
for i = #queue, 1, -1 do
|
||||
@@ -1338,12 +1356,13 @@ function OverworldState:update(dt)
|
||||
local step = self.player.spinStep or 0
|
||||
if step ~= self.teleportOut.step then
|
||||
self.teleportOut.step = step
|
||||
local key = TELEPORT_OUT_SFX[step]
|
||||
local key = (self.teleportOut.sfx or TELEPORT_OUT_SFX)[step]
|
||||
if key then require("src.core.Sound").play(Game.data, key) end
|
||||
end
|
||||
self.teleportOut.frames = self.teleportOut.frames - 1
|
||||
if self.teleportOut.frames <= 0 then
|
||||
local onDone = self.teleportOut.onDone
|
||||
local go = self.teleportOut.go
|
||||
self.teleportOut = nil
|
||||
self.player.spinning = false
|
||||
self.player.spinFrames = nil
|
||||
@@ -1356,7 +1375,7 @@ function OverworldState:update(dt)
|
||||
-- over but the arrival spin-drop is not armed until startWarpTo's
|
||||
-- midpoint, so without this the standing trainer shows under the veil
|
||||
self.playerHidden = true
|
||||
self:warpToHealPoint(onDone, { arrive = "teleport" })
|
||||
if go then go() else self:warpToHealPoint(onDone, { arrive = "teleport" }) end
|
||||
return
|
||||
end
|
||||
end
|
||||
@@ -2777,9 +2796,10 @@ function OverworldState:tryCardKeyDoor(fx, fy)
|
||||
end
|
||||
-- ../pokered/engine/events/card_key.asm:63-67
|
||||
Game.stack:push(TextBox.new(Game,
|
||||
(t._CardKeySuccessText1 or Strings("Bingo!"))
|
||||
TextBox.strip(t._CardKeySuccessText1 or Strings("Bingo!"))
|
||||
.. TextBox.PAUSE
|
||||
.. (t._CardKeySuccessText2 or romText(Game.data, "_CardKeySuccessText2", "\nThe CARD KEY\nopened the door!")),
|
||||
openDoor, TextBox.soundOpts(Game, "Get_Item1")))
|
||||
openDoor, { pauseSounds = { "Get_Item1" }, pauseSoundWait = true }))
|
||||
return true
|
||||
end
|
||||
|
||||
@@ -4620,6 +4640,7 @@ function OverworldState:runSpinnerMoves(moves, i)
|
||||
local mv = moves[i]
|
||||
if not mv then
|
||||
self.player.spinning = false
|
||||
self.spinnerSliding = nil
|
||||
-- Scripted steps skip onStepComplete while they run; once the RLE
|
||||
-- finishes, re-enter the normal landing pipeline so chained spinners,
|
||||
-- Seafoam currents, and CheckWarpsNoCollision (incl. BIT_FORCED_WARP)
|
||||
@@ -4628,6 +4649,8 @@ function OverworldState:runSpinnerMoves(moves, i)
|
||||
return
|
||||
end
|
||||
self.player.spinning = true -- spin the sprite while sliding
|
||||
-- home/overworld.asm:268-273
|
||||
self.spinnerSliding = true
|
||||
self:scriptMove(self.player, mv.dir, mv.count, function()
|
||||
self:runSpinnerMoves(moves, i + 1)
|
||||
end)
|
||||
@@ -4932,6 +4955,8 @@ end
|
||||
function OverworldState:safariGameOver(text)
|
||||
require("src.core.Sound").play(Game.data, "Safari_Zone_PA")
|
||||
Game.save.safari = nil
|
||||
-- SetEvent EVENT_SAFARI_GAME_OVER -- engine/events/hidden_events/safari_game.asm:54
|
||||
Game.save.safariGameOver = true
|
||||
local t = Game.data.text
|
||||
Game.stack:push(TextBox.new(Game,
|
||||
(text or "") .. "\f" .. (t._GameOverText or romText(Game.data, "_GameOverText", "PA: Your SAFARI\nGAME is over!")),
|
||||
@@ -5085,11 +5110,24 @@ function OverworldState:takeWarp(warpDef)
|
||||
if pad == "pad" then
|
||||
-- teleporter: spin out with the exit SFX, spin back in on arrival
|
||||
-- (player_animations.asm _LeaveMapAnim / EnterMapAnim)
|
||||
-- engine/overworld/player_animations.asm:104-118
|
||||
require("src.core.Sound").play(Game.data, "Teleport_Exit1")
|
||||
self:hidePikachuForWarp()
|
||||
self.arriveWarp = "teleport"
|
||||
self.player.inputLocked = true
|
||||
self.player.spinning = true
|
||||
self.player.spinTimer = 0
|
||||
self.arriveWarp = "teleport"
|
||||
self:startWarpTo(destMap, x, y, facing, nil, { pikachuSpawn = spawn })
|
||||
self.player.spinFrames = WARP_PAD_OUT_FRAMES
|
||||
self.player.spinTotal = WARP_PAD_OUT_FRAMES
|
||||
self.player.spinRise = true
|
||||
self.player.spinHolds = WARP_PAD_OUT_HOLDS
|
||||
self.player.spinStep = 0
|
||||
self.player.spinHold = WARP_PAD_OUT_HOLDS[1]
|
||||
self.player.spinRiseFrom = -1
|
||||
self.teleportOut = { frames = WARP_PAD_OUT_FRAMES, step = 0, sfx = {},
|
||||
go = function()
|
||||
self:startWarpTo(destMap, x, y, facing, nil, { pikachuSpawn = spawn })
|
||||
end }
|
||||
return
|
||||
elseif pad == "hole" then
|
||||
self:fallThroughHole(destMap, x, y, facing)
|
||||
@@ -5253,14 +5291,19 @@ function OverworldState:startWarpTo(mapId, x, y, facing, onDone, opts)
|
||||
self.delaySfx = { frames = HOLE_IN_FRAMES, key = "Teleport_Enter2" }
|
||||
-- the sprite spins down into place (EnterMapAnim
|
||||
-- PlayerSpinWhileMovingDown), not just the SFX
|
||||
-- engine/overworld/player_animations.asm:22-25
|
||||
local onPad = self.map.warpPadOrHoleAt
|
||||
and self.map:warpPadOrHoleAt(x, y) == "pad"
|
||||
local holds = onPad and TELEPORT_IN_PAD_HOLDS or TELEPORT_IN_HOLDS
|
||||
local frames = onPad and TELEPORT_IN_PAD_FRAMES or TELEPORT_IN_FRAMES
|
||||
self.player.spinning = true
|
||||
self.player.spinTimer = 0
|
||||
self.player.spinFrames = TELEPORT_IN_FRAMES
|
||||
self.player.spinTotal = TELEPORT_IN_FRAMES
|
||||
self.player.spinFrames = frames
|
||||
self.player.spinTotal = frames
|
||||
self.player.spinDrop = true
|
||||
self.player.spinHolds = TELEPORT_IN_HOLDS
|
||||
self.player.spinHolds = holds
|
||||
self.player.spinStep = 0
|
||||
self.player.spinHold = TELEPORT_IN_HOLDS[1]
|
||||
self.player.spinHold = holds[1]
|
||||
self.player.spinDropSteps = SPIN_DOWN_STEPS
|
||||
-- engine/overworld/player_animations.asm:19
|
||||
self.spinArrive = true
|
||||
@@ -5678,6 +5721,10 @@ function OverworldState:drawWorld()
|
||||
local fadeMap = fadeBgp and Transition.shadeMapFor(fadeBgp)
|
||||
if fadeBgp and not self:bakedWorldColors() then
|
||||
PaletteFX.setShadeMap(fadeMap)
|
||||
-- home/fade.asm:52-58
|
||||
-- home/fade.asm:65-73
|
||||
local fadeObp = fade.obp0 and fade:obp0() or fadeBgp
|
||||
PaletteFX.setFadeObp(Transition.shadeMapFor(fadeObp))
|
||||
fade.paletteStepped = true
|
||||
else
|
||||
PaletteFX.setShadeMap((self.dark and not battleOverWorld)
|
||||
@@ -5690,7 +5737,7 @@ function OverworldState:drawWorld()
|
||||
-- let the renderer know whether a spinner puzzle is currently sliding
|
||||
-- the player, so it can flicker the arrow tiles between the blur and
|
||||
-- static graphic (engine/overworld/spinners.asm LoadSpinnerArrowTiles)
|
||||
require("src.render.TileRenderer").setSpinning(self.player.spinning)
|
||||
require("src.render.TileRenderer").setSpinning(self.spinnerSliding or false)
|
||||
local cam = self.camera
|
||||
-- ShakeElevator's oscillation (engine/overworld/elevator.asm) writes
|
||||
-- hSCY, which scrolls the BG layer only -- tiles bounce while OAM
|
||||
@@ -5774,7 +5821,8 @@ function OverworldState:drawWorld()
|
||||
love.graphics.newQuad(0, 8, 8, 8, w, h), -- ball ($7d)
|
||||
}
|
||||
end
|
||||
local shader = healMachineShader(ha.visible)
|
||||
local shader, redrawColors = healMachineShader(ha.visible)
|
||||
local mark = redrawColors and PaletteFX.spriteRedrawPassActive()
|
||||
-- TileRenderer windows with -floor(cam), so the overlay must use the
|
||||
-- same snap or a fractional camera (odd fill/tilt view sizes) parks
|
||||
-- the balls a pixel off the machine tiles
|
||||
@@ -5782,14 +5830,26 @@ function OverworldState:drawWorld()
|
||||
local oy = ha.py - 64 - math.floor(cam.y)
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
love.graphics.draw(img, self.healMachineQuads[1], ox + 44, oy + 20)
|
||||
if mark then
|
||||
PaletteFX.markSpriteRedraw(img, self.healMachineQuads[1],
|
||||
ox + 44, oy + 20, 1, redrawColors)
|
||||
end
|
||||
for i = 1, math.min(ha.lit, #HEAL_BALL_XY) do
|
||||
local b = HEAL_BALL_XY[i]
|
||||
if b[3] then -- right column: OAM_XFLIP
|
||||
love.graphics.draw(img, self.healMachineQuads[2],
|
||||
ox + b[1] + 8, oy + b[2], 0, -1, 1)
|
||||
if mark then
|
||||
PaletteFX.markSpriteRedraw(img, self.healMachineQuads[2],
|
||||
ox + b[1] + 8, oy + b[2], -1, redrawColors)
|
||||
end
|
||||
else
|
||||
love.graphics.draw(img, self.healMachineQuads[2],
|
||||
ox + b[1], oy + b[2])
|
||||
if mark then
|
||||
PaletteFX.markSpriteRedraw(img, self.healMachineQuads[2],
|
||||
ox + b[1], oy + b[2], 1, redrawColors)
|
||||
end
|
||||
end
|
||||
end
|
||||
if shader then love.graphics.setShader() end
|
||||
|
||||
@@ -65,6 +65,16 @@ if not mapsFile then
|
||||
end
|
||||
mapsFile:close()
|
||||
|
||||
local CacheContract = require("src.import.CacheContract")
|
||||
local markerFile = io.open(cache .. "/rom-cache.complete", "rb")
|
||||
local marker = markerFile and markerFile:read("*a")
|
||||
if markerFile then markerFile:close() end
|
||||
if not CacheContract.markerMatches("crystal", marker) then
|
||||
check(true, "crystal cache predates the current format : SKIP")
|
||||
S.finish()
|
||||
return
|
||||
end
|
||||
|
||||
local function loadLua(rel)
|
||||
local chunk = loadfile(cache .. "/" .. rel)
|
||||
if not chunk then return nil end
|
||||
|
||||
@@ -50,7 +50,7 @@ return function(game)
|
||||
for _, species in ipairs(CASES) do
|
||||
while game.stack:top() do game.stack:pop() end
|
||||
Screens.push(game, "DexEntryMenu", { species = species, forceOwned = true })
|
||||
U.wait(30)
|
||||
U.wait(60)
|
||||
U.shot(game, SHOT_DIR .. "/dex_" .. species:lower() .. ".png")
|
||||
U.log("captured", SHOT_DIR .. "/dex_" .. species:lower() .. ".png")
|
||||
end
|
||||
|
||||
@@ -62,6 +62,9 @@ local ns = DexEntryMenu.new(game, "BULBASAUR")
|
||||
eq(ns.pageCount, 2, "the entry has two <PAGE>-separated pages")
|
||||
eq(ns.page, 1, "starts on page 1")
|
||||
|
||||
-- engine/menus/pokedex.asm:500-506
|
||||
ns.picDelay = 0
|
||||
|
||||
ns:draw()
|
||||
check(hasText("BULBASAUR", 72, 16), "name at (72,16), not the port's old (72,8)")
|
||||
check(hasText("SEED POKEMON", 72, 32), "kind at (72,32)")
|
||||
|
||||
@@ -282,7 +282,7 @@ local harvestCalls = {
|
||||
'Strings("No.%03d",', 'Strings("IDNo.%05d",',
|
||||
},
|
||||
["src/ui/TrainerCard.lua"] = {
|
||||
'Strings("MONEY/¥%d",', 'Strings("TIME/%3d:%02d",',
|
||||
'Strings("MONEY/¥%d",', 'Strings("TIME/ %d:%02d",',
|
||||
},
|
||||
["src/world/OverworldController.lua"] = {
|
||||
'Strings("BILL\'S PC")', 'Strings("%s\'s PC",', 'Strings("HEAL")',
|
||||
|
||||
@@ -30,7 +30,22 @@ local ScriptRunner = require("src.script.ScriptRunner")
|
||||
|
||||
local answer = true
|
||||
package.loaded["src.render.TextBox"] = {
|
||||
new = function(_, text, done) return { text = text, done = done } end,
|
||||
new = function(_, text, done, opts)
|
||||
local box = { text = text, opts = opts }
|
||||
box.done = function()
|
||||
if opts and opts.choice then
|
||||
opts.choice(answer)
|
||||
elseif done then
|
||||
done()
|
||||
end
|
||||
end
|
||||
return box
|
||||
end,
|
||||
soundOpts = function(_, sound, opts)
|
||||
opts = opts or {}
|
||||
opts.auto = { sound = sound, wait = true }
|
||||
return opts
|
||||
end,
|
||||
}
|
||||
package.loaded["src.ui.ChoiceBox"] = {
|
||||
new = function(_, cb) return { done = function() cb(answer) end } end,
|
||||
@@ -96,11 +111,42 @@ answer = true
|
||||
local ow = newWorld(4, 2)
|
||||
local game = newGame(ow)
|
||||
T.eq(gate.onStep(game, ow, 4, 2), true, "the trigger cell claims the step")
|
||||
-- SafariZoneGateDefaultScript displays TEXT_..._WORKER1_1 before dispatching
|
||||
-- to the join prompt (scripts/SafariZoneGate.asm:22-24) (#2205)
|
||||
T.check(ow.pushed[1] and ow.pushed[1].text:find("SAFARI ZONE"),
|
||||
"the worker welcomes you before the join prompt")
|
||||
T.eq(ow.player.facing, "right", "SPRITE_FACING_RIGHT is written on both branches")
|
||||
local joinBox = ow.pushed[2]
|
||||
T.check(joinBox ~= nil and joinBox.opts ~= nil and joinBox.opts.choice ~= nil,
|
||||
"the YES/NO rides over the still-visible join text")
|
||||
T.check(joinBox and joinBox.opts and joinBox.opts.money ~= nil
|
||||
and joinBox.opts.moneyWithChoice == true,
|
||||
"MONEY_BOX goes up with the YES/NO")
|
||||
T.check(game.save.safari ~= nil, "paying starts the game")
|
||||
T.eq(game.save.safari.steps, 502, "wSafariSteps is written as 502")
|
||||
T.eq(game.save.safari.balls, 30, "SAFARI_BALLS_RECEIVED is 30")
|
||||
T.eq(game.save.money, 2500, "the ¥500 fee is taken")
|
||||
|
||||
-- .MakePaymentText carries sound_get_item_1 between the "received 30 SAFARI
|
||||
-- BALLs!" page and the PA page (scripts/SafariZoneGate.asm:213-217), and the
|
||||
-- MONEY box is redrawn with the reduced total (:181-183) (#2205)
|
||||
local paidBox = ow.pushed[3]
|
||||
T.check(paidBox ~= nil and paidBox.text:find("SAFARI BALLs"),
|
||||
"the payment text is its own box")
|
||||
T.check(paidBox and paidBox.opts and paidBox.opts.auto
|
||||
and paidBox.opts.auto.sound == "Get_Item1",
|
||||
"the Get_Item1 fanfare holds the box the balls were handed over in")
|
||||
T.check(paidBox and paidBox.opts and paidBox.opts.money ~= nil,
|
||||
"the MONEY box stays up through the payment text")
|
||||
local paBox = ow.pushed[4]
|
||||
T.check(paBox ~= nil and paBox.text:find("PA"), "the PA line follows it")
|
||||
T.check(paBox and paBox.opts and paBox.opts.money ~= nil,
|
||||
"and the MONEY box is still up for it")
|
||||
for _, box in ipairs(ow.pushed) do
|
||||
T.check(not box.text:find("Good Luck"),
|
||||
"\"Good Luck!\" belongs to the leaving script, never the entry path")
|
||||
end
|
||||
|
||||
T.eq(#ow.moves, 1, "the payment text is followed by the entrance auto-walk")
|
||||
T.eq(ow.moves[1].dir, "up", "SafariZoneEntranceAutoWalk walks PAD_UP")
|
||||
T.eq(ow.moves[1].tiles, 2, "two cells reach the north warp row")
|
||||
@@ -111,6 +157,20 @@ T.eq(game.save.safari.steps, 500,
|
||||
T.eq(ow.warps[1], NORTH_WARP,
|
||||
"a scripted step skips CheckWarpsNoCollision, so the script takes the warp")
|
||||
|
||||
-- the LEFT trigger cell auto-walks one step right to the counter first:
|
||||
-- wCoordIndex is 1-based (home/map_objects.asm:107-121), so `cp 1` is (3,2)
|
||||
-- (scripts/SafariZoneGate.asm:31-40) (#2205)
|
||||
answer = true
|
||||
local leftCell = newWorld(3, 2)
|
||||
local leftCellGame = newGame(leftCell)
|
||||
T.eq(gate.onStep(leftCellGame, leftCell, 3, 2), true, "the left cell triggers too")
|
||||
T.eq(leftCellGame.save.safari, nil, "nothing is paid until the walk finishes")
|
||||
T.eq(#leftCell.moves, 1, "the welcome text is followed by the walk right")
|
||||
T.eq(leftCell.moves[1].dir, "right", "PAD_RIGHT, c = 1")
|
||||
T.eq(leftCell.moves[1].tiles, 1, "one cell reaches (4,2)")
|
||||
leftCell.moves[1].onDone()
|
||||
T.check(leftCellGame.save.safari ~= nil, "the join prompt runs from (4,2)")
|
||||
|
||||
-- talking to the worker from anywhere else has no warp above the player, so
|
||||
-- the auto-walk stays out of the way and he walks in himself
|
||||
answer = true
|
||||
@@ -188,8 +248,9 @@ T.eq(yes.texts[1], "_SafariZoneGateSafariZoneWorker1LeavingEarlyText",
|
||||
"the worker asks first")
|
||||
T.eq(yes.texts[2], "_SafariZoneGateSafariZoneWorker1ReturnSafariBallsText",
|
||||
"YES takes the leftover balls back")
|
||||
T.eq(yes.texts[3], "_SafariZoneGateSafariZoneWorker1GoodHaulComeAgainText",
|
||||
"the good-haul sign-off stays reachable on this branch")
|
||||
-- .leaving_early prints the return-balls text and nothing else; the good-haul
|
||||
-- sign-off belongs to the game-over branch (scripts/SafariZoneGate.asm:85-99)
|
||||
T.eq(yes.texts[3], nil, "no good-haul sign-off on the leaving-early branch")
|
||||
T.eq(#yes.fields, 1, "the game state is cleared once")
|
||||
T.eq(yes.fields[1].key, "safari", "save.safari is the field cleared")
|
||||
T.eq(yes.fields[1].value, nil, "set_field with no value assigns nil")
|
||||
@@ -215,6 +276,36 @@ gate.onEnter(leftGame, left)
|
||||
local leftStay = runRows(left.queued[1], false)
|
||||
T.eq(leftStay.warps[1].x, 14, "the left-hand warp column comes back on 14")
|
||||
|
||||
-- ---- the game-over return: EVENT_SAFARI_GAME_OVER takes the other branch
|
||||
-- (scripts/SafariZoneGate.asm:79-94) (#2206)
|
||||
|
||||
local over = newWorld(4, 0)
|
||||
local overGame = newGame(over)
|
||||
overGame.save.safariGameOver = true
|
||||
gate.onEnter(overGame, over)
|
||||
T.eq(#over.pushed, 0, "the game-over branch queues rather than pushes too")
|
||||
T.eq(#over.queued, 1, "the sign-off script is queued")
|
||||
T.eq(overGame.save.safariGameOver, nil,
|
||||
"CheckAndResetEvent clears the flag, so the next arrival asks normally")
|
||||
local overRows = over.queued[1]
|
||||
T.eq(#ScriptRunner.validate(overRows), 0, "the queued rows validate")
|
||||
local overOut = runRows(overRows, true)
|
||||
T.eq(overOut.texts[1], "_SafariZoneGateSafariZoneWorker1GoodHaulComeAgainText",
|
||||
"the worker signs off with the good-haul text")
|
||||
T.eq(#overOut.moves, 1, "then the exit auto-walk runs")
|
||||
T.eq(overOut.moves[1].dir, "down", "PAD_DOWN")
|
||||
T.eq(overOut.moves[1].tiles, 3, "c = 3, from the warp row down to the counter")
|
||||
T.eq(#overOut.warps, 0, "the game-over branch never warps back into the zone")
|
||||
|
||||
-- wDestinationWarpID $3 is warp_event 4,0
|
||||
-- (data/maps/objects/SafariZoneGate.asm:12), the walk's START, not its end
|
||||
local FieldDefaults = require("src.world.FieldDefaults")
|
||||
local exitWarp = FieldDefaults.fieldValue(nil, "safari", "exitWarp")
|
||||
T.check(exitWarp ~= nil, "the safari exit warp is defined")
|
||||
T.eq(exitWarp and exitWarp.map, "SAFARI_ZONE_GATE", "it lands in the gate")
|
||||
T.eq(exitWarp and exitWarp.x, 4, "on the right-hand north warp column")
|
||||
T.eq(exitWarp and exitWarp.y, 0, "on the warp row, not the counter row")
|
||||
|
||||
-- no game running, or arriving from the town side, asks nothing
|
||||
local idle = newWorld(4, 0)
|
||||
local idleGame = newGame(idle)
|
||||
|
||||
@@ -70,7 +70,9 @@ local aiBattle = { enemy = enemy, trainer = { name = "TRAINER" }, data = Data }
|
||||
|
||||
withCatalog({ SPEED = "VITESSE" }, function()
|
||||
local msgs = TrainerAI.useItem(aiBattle, "X_SPEED")
|
||||
T.check(msgs[2]:find("VITESSE", 1, true) ~= nil,
|
||||
T.check(type(msgs[2]) == "table" and msgs[2].anim == "XSTATITEM_DUPLICATE_ANIM",
|
||||
"the AI trainer's X item plays the stat-up animation before the rose! page")
|
||||
T.check(msgs[3]:find("VITESSE", 1, true) ~= nil,
|
||||
"a catalog translating SPEED reaches the AI trainer's X SPEED rose! message")
|
||||
end)
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||
|
||||
local T = require("tests.harness")
|
||||
local eq = T.eq
|
||||
local S = require("tests.harness").suite("gen2 arrow blink")
|
||||
local eq = S.eq
|
||||
love = love or require("tests.love_stub")
|
||||
|
||||
local Typer = require("src.ui.gen2.Typer")
|
||||
@@ -23,4 +23,4 @@ Typer.say(screen, { "HELLO", "WORLD" })
|
||||
Typer.step(screen)
|
||||
eq(screen.arrowBlink, 1, "step keeps counting while a typer runs")
|
||||
|
||||
T.finish()
|
||||
S.finish()
|
||||
|
||||
@@ -178,8 +178,12 @@ if not cache then
|
||||
.. "/Library/Application Support/LOVE/crystal-dev/crystal"
|
||||
end
|
||||
|
||||
local events = loadTable(cache .. "/data/generated/events.lua")
|
||||
local tilesets = loadTable(cache .. "/data/generated/tilesets.lua")
|
||||
local CacheContract = require("src.import.CacheContract")
|
||||
local stale = not CacheContract.markerMatches(
|
||||
"crystal", readFile(cache .. "/rom-cache.complete"))
|
||||
|
||||
local events = not stale and loadTable(cache .. "/data/generated/events.lua") or nil
|
||||
local tilesets = not stale and loadTable(cache .. "/data/generated/tilesets.lua") or nil
|
||||
|
||||
-- data/events/unown_walls.asm:2-5
|
||||
local EXPECTED = {
|
||||
|
||||
@@ -1 +1 @@
|
||||
789ccca105bbebed
|
||||
0d60c593c618791f
|
||||
|
||||
@@ -11,6 +11,7 @@ local check = S.check
|
||||
|
||||
local love = _G.love or require("tests.love_stub")
|
||||
_G.love = love
|
||||
_G.POKEPORT_LOOP_PANEL_SYNC = true
|
||||
|
||||
local Events = require("src.mods.Events")
|
||||
local Hooks = require("src.mods.Hooks")
|
||||
|
||||
@@ -65,6 +65,8 @@ for _, c in ipairs({
|
||||
"the preview marks " .. c.species .. " seen")
|
||||
check(not game.save.pokedex.owned[c.species],
|
||||
"the preview does not mark " .. c.species .. " owned")
|
||||
-- engine/menus/pokedex.asm:500-506
|
||||
top.picDelay = 0
|
||||
game.pressed = true
|
||||
top:update(0)
|
||||
local ask = game.stack:top()
|
||||
|
||||
@@ -34,7 +34,14 @@ local check, eq = S.check, S.eq
|
||||
-- suite that sorts after this one.
|
||||
local realTextBox = package.loaded["src.render.TextBox"]
|
||||
package.loaded["src.render.TextBox"] = {
|
||||
new = function(_, text, done) return { text = text, done = done } end,
|
||||
new = function(_, text, done, opts)
|
||||
return { text = text, done = done, opts = opts }
|
||||
end,
|
||||
soundOpts = function(_, sound, opts)
|
||||
opts = opts or {}
|
||||
opts.sound = sound
|
||||
return opts
|
||||
end,
|
||||
}
|
||||
|
||||
local M = dofile("data/scripts/story2.lua")
|
||||
@@ -73,6 +80,9 @@ for _, drink in ipairs({ "FRESH_WATER", "SODA_POP", "LEMONADE" }) do
|
||||
drink .. ": the guards are marked as having been given a drink")
|
||||
eq(game.save.inventory[drink], nil, drink .. ": exactly one was removed")
|
||||
check(#pushed == 1, drink .. ": the thanks text is shown")
|
||||
-- scripts/Route5Gate.asm:104
|
||||
check(pushed[1] and pushed[1].opts and pushed[1].opts.sound == "Get_Key_Item",
|
||||
drink .. ": the thanks box carries the key-item jingle")
|
||||
check(#moved == 0, drink .. ": we are NOT walked back")
|
||||
end
|
||||
|
||||
|
||||
@@ -48,6 +48,8 @@ local game = {
|
||||
local dex = DexEntryMenu.new(game, "PIKACHU")
|
||||
check(dex.spriteTrueColor == true,
|
||||
"Pokedex keeps a Pokemon sprite's trueColor flag")
|
||||
-- engine/menus/pokedex.asm:500-506
|
||||
dex.picDelay = 0
|
||||
local dexRects = uiRects(function() dex:draw() end)
|
||||
-- engine/menus/pokedex.asm:503: the pic sits in the 7x7 window at (8,8)
|
||||
local dw, dh = dex.sprite:getDimensions()
|
||||
|
||||
@@ -23,7 +23,7 @@ class RbyTranslationHarvestTest(TestCase):
|
||||
'"DEPOSIT"',
|
||||
'"The party is full!"',
|
||||
'"MONEY/¥%d"',
|
||||
'"TIME/%3d:%02d"',
|
||||
'"TIME/ %d:%02d"',
|
||||
'"No.%03d"',
|
||||
'"IDNo.%05d"',
|
||||
'"BILL\'S PC"',
|
||||
|
||||
@@ -15,6 +15,7 @@ local T = require("tests.harness")
|
||||
-- every suite below is dofile'd in this process, so a suite that reaches for
|
||||
-- T.finish must raise into runSuites' pcall instead of os.exit(0)-ing the tier
|
||||
_G.POKEPORT_TEST_CHILD = true
|
||||
_G.POKEPORT_LOOP_PANEL_SYNC = true
|
||||
-- this suite has always streamed a line per check, and it is the one a
|
||||
-- developer watches for progress through ~1600 assertions
|
||||
T.verbose = true
|
||||
|
||||
Reference in New Issue
Block a user