mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-26 07:21:22 +02:00
CLOSES #941, CLOSES #1081, CLOSES #1349, CLOSES #1602, CLOSES #1618, CLOSES #1624, CLOSES #1630, CLOSES #1633, CLOSES #1661, CLOSES #1669, CLOSES #1676, CLOSES #1731, CLOSES #1739, CLOSES #1740, CLOSES #1742, CLOSES #1744, CLOSES #1753, CLOSES #1757, CLOSES #1758, CLOSES #1760, CLOSES #1761, CLOSES #1762, CLOSES #1763, CLOSES #1765, CLOSES #1770, CLOSES #1777, CLOSES #1781, CLOSES #1783, CLOSES #1784, CLOSES #1785, CLOSES #1787, CLOSES #1788, CLOSES #1789, CLOSES #1790, CLOSES #1792, CLOSES #1797, CLOSES #1803
This commit is contained in:
@@ -42,27 +42,29 @@ local LINK_HEADINGS = { "HOW TO LINK", "COLOSSEUM", "TRADE CENTER" }
|
|||||||
|
|
||||||
local function linkCableHelp(game)
|
local function linkCableHelp(game)
|
||||||
local text = game.data.text or {}
|
local text = game.data.text or {}
|
||||||
local items, showMenu, askHeading
|
local items, menu, openMenu
|
||||||
function showMenu()
|
local function closeAll()
|
||||||
game.stack:push(Menu.new(game, items,
|
game.stack:pop()
|
||||||
{ tx = 0, ty = 0, tw = 15, th = 10, rowStep = 1 }))
|
|
||||||
end
|
|
||||||
function askHeading()
|
|
||||||
game.stack:push(TextBox.new(game,
|
|
||||||
text._LinkCableHelpText2 or "Which heading do\nyou want to read?",
|
|
||||||
showMenu))
|
|
||||||
end
|
end
|
||||||
items = {}
|
items = {}
|
||||||
for i, label in ipairs(LINK_HEADINGS) do
|
for i, label in ipairs(LINK_HEADINGS) do
|
||||||
items[i] = { label = label, onSelect = function()
|
items[i] = { label = label, keepOpen = true, onSelect = function()
|
||||||
game.stack:push(TextBox.new(game,
|
game.stack:push(TextBox.new(game,
|
||||||
text["_LinkCableInfoText" .. i] or label, askHeading))
|
text["_LinkCableInfoText" .. i] or label))
|
||||||
end }
|
end }
|
||||||
end
|
end
|
||||||
items[#items + 1] = { label = "STOP READING" }
|
items[#items + 1] = { label = "STOP READING", onSelect = closeAll }
|
||||||
|
menu = Menu.new(game, items,
|
||||||
|
{ tx = 0, ty = 0, tw = 15, th = 10, rowStep = 2, itemY = 2,
|
||||||
|
onCancel = closeAll })
|
||||||
|
function openMenu() game.stack:push(menu) end
|
||||||
game.stack:push(TextBox.new(game,
|
game.stack:push(TextBox.new(game,
|
||||||
text._LinkCableHelpText1 or "TRAINER TIPS\fUsing a Game Link\nCable",
|
text._LinkCableHelpText1 or "TRAINER TIPS\fUsing a Game Link\nCable",
|
||||||
askHeading))
|
function()
|
||||||
|
game.stack:push(TextBox.new(game,
|
||||||
|
text._LinkCableHelpText2 or "Which heading do\nyou want to read?",
|
||||||
|
nil, { stay = { onShown = openMenu } }))
|
||||||
|
end))
|
||||||
end
|
end
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -84,21 +86,15 @@ return {
|
|||||||
{ "jump_if_false", 5 }, -- 2
|
{ "jump_if_false", 5 }, -- 2
|
||||||
{ "hide_object", "CELADON_MANSION_ROOF_HOUSE",
|
{ "hide_object", "CELADON_MANSION_ROOF_HOUSE",
|
||||||
"CELADONMANSION_ROOF_HOUSE_EEVEE_POKEBALL" }, -- 3 (old saves)
|
"CELADONMANSION_ROOF_HOUSE_EEVEE_POKEBALL" }, -- 3 (old saves)
|
||||||
{ "jump", 13 }, -- 4
|
{ "jump", 11 }, -- 4
|
||||||
{ "give_pokemon", "EEVEE", 25 }, -- 5
|
{ "give_pokemon", "EEVEE", 25, false, true }, -- 5
|
||||||
{ "jump_if_false", 12 }, -- 6 (party+box full)
|
{ "jump_if_false", 10 }, -- 6 (party+box full)
|
||||||
-- flag + HideObject before the jingle and GotMonText: the nickname
|
-- scripts/CeladonMansionRoofHouse.asm:17-20 (#426)
|
||||||
-- prompt inside give_pokemon and the text row both yield, and a
|
|
||||||
-- script that dies there would leave the EEVEE taken with the ball
|
|
||||||
-- still on the table and the gift claimable again (#426). The row
|
|
||||||
-- count is unchanged, so the numeric jump targets still hold.
|
|
||||||
{ "set_flag", "EVENT_GOT_EEVEE" }, -- 7
|
{ "set_flag", "EVENT_GOT_EEVEE" }, -- 7
|
||||||
{ "hide_object", "CELADON_MANSION_ROOF_HOUSE",
|
{ "hide_object", "CELADON_MANSION_ROOF_HOUSE",
|
||||||
"CELADONMANSION_ROOF_HOUSE_EEVEE_POKEBALL" }, -- 8
|
"CELADONMANSION_ROOF_HOUSE_EEVEE_POKEBALL" }, -- 8
|
||||||
{ "text_sound", "Get_Item1" }, -- 9 (GotMonText jingle)
|
{ "jump", 11 }, -- 9
|
||||||
{ "show_text", "_GotMonText", { RAM = "EEVEE" } }, -- 10
|
{ "show_text", "_BoxIsFullText" }, -- 10
|
||||||
{ "jump", 13 }, -- 11
|
|
||||||
{ "show_text", "_BoxIsFullText" }, -- 12
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,10 @@
|
|||||||
return {
|
return {
|
||||||
CELADON_CITY = {
|
CELADON_CITY = {
|
||||||
talk = {
|
talk = {
|
||||||
-- CeladonCityPoliwrathText (scripts/CeladonCity.asm): text_far
|
|
||||||
-- _CeladonCityPoliwrathText, then plays the POLIWRATH cry and ends.
|
|
||||||
-- The cry playback has no port-side equivalent command, so we just
|
|
||||||
-- show the flavor line.
|
|
||||||
TEXT_CELADONCITY_POLIWRATH = {
|
TEXT_CELADONCITY_POLIWRATH = {
|
||||||
{"face_player"},
|
{"face_player"},
|
||||||
|
-- pokered/scripts/CeladonCity.asm:90
|
||||||
|
{"play_cry", "POLIWRATH", true},
|
||||||
{"show_text", "_CeladonCityPoliwrathText"},
|
{"show_text", "_CeladonCityPoliwrathText"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,30 +2,24 @@
|
|||||||
return {
|
return {
|
||||||
CELADON_MANSION_1F = {
|
CELADON_MANSION_1F = {
|
||||||
talk = {
|
talk = {
|
||||||
-- CeladonMansion1FClefairyText (scripts/CeladonMansion1F.asm): text_far
|
|
||||||
-- _CeladonMansion1FClefairyText, then plays the CLEFAIRY cry and ends.
|
|
||||||
-- The cry playback has no port-side equivalent command, so we just
|
|
||||||
-- show the flavor line.
|
|
||||||
TEXT_CELADONMANSION1F_CLEFAIRY = {
|
TEXT_CELADONMANSION1F_CLEFAIRY = {
|
||||||
{"face_player"},
|
{"face_player"},
|
||||||
|
-- pokered/scripts/CeladonMansion1F.asm:27
|
||||||
|
{"play_cry", "CLEFAIRY", true},
|
||||||
{"show_text", "_CeladonMansion1FClefairyText"},
|
{"show_text", "_CeladonMansion1FClefairyText"},
|
||||||
},
|
},
|
||||||
|
|
||||||
-- CeladonMansion1FMeowthText (scripts/CeladonMansion1F.asm): text_far
|
|
||||||
-- _CeladonMansion1FMeowthText, then plays the MEOWTH cry and ends.
|
|
||||||
-- The cry playback has no port-side equivalent command, so we just
|
|
||||||
-- show the flavor line.
|
|
||||||
TEXT_CELADONMANSION1F_MEOWTH = {
|
TEXT_CELADONMANSION1F_MEOWTH = {
|
||||||
{"face_player"},
|
{"face_player"},
|
||||||
|
-- pokered/scripts/CeladonMansion1F.asm:18
|
||||||
|
{"play_cry", "MEOWTH", true},
|
||||||
{"show_text", "_CeladonMansion1FMeowthText"},
|
{"show_text", "_CeladonMansion1FMeowthText"},
|
||||||
},
|
},
|
||||||
|
|
||||||
-- CeladonMansion1FNidoranFText (scripts/CeladonMansion1F.asm): text_far
|
|
||||||
-- _CeladonMansion1FNidoranFText, then plays the NIDORAN_F cry and ends.
|
|
||||||
-- The cry playback has no port-side equivalent command, so we just
|
|
||||||
-- show the flavor line.
|
|
||||||
TEXT_CELADONMANSION1F_NIDORANF = {
|
TEXT_CELADONMANSION1F_NIDORANF = {
|
||||||
{"face_player"},
|
{"face_player"},
|
||||||
|
-- pokered/scripts/CeladonMansion1F.asm:33
|
||||||
|
{"play_cry", "NIDORAN_F", true},
|
||||||
{"show_text", "_CeladonMansion1FNidoranFText"},
|
{"show_text", "_CeladonMansion1FNidoranFText"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,11 +4,10 @@
|
|||||||
return {
|
return {
|
||||||
LAVENDER_CUBONE_HOUSE = {
|
LAVENDER_CUBONE_HOUSE = {
|
||||||
talk = {
|
talk = {
|
||||||
-- LavenderCuboneHouseCuboneText: text_far _LavenderCuboneHouseCuboneText,
|
-- LavenderCuboneHouse.asm:10
|
||||||
-- then text_asm plays the CUBONE cry. Cry playback has no Commands
|
|
||||||
-- equivalent in this port, so just show the line.
|
|
||||||
TEXT_LAVENDERCUBONEHOUSE_CUBONE = {
|
TEXT_LAVENDERCUBONEHOUSE_CUBONE = {
|
||||||
{ "face_player" },
|
{ "face_player" },
|
||||||
|
{ "play_cry", "CUBONE", true },
|
||||||
{ "show_text", "_LavenderCuboneHouseCuboneText" },
|
{ "show_text", "_LavenderCuboneHouseCuboneText" },
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -31,16 +31,15 @@ M.MR_FUJIS_HOUSE = {
|
|||||||
{ "show_text", "_MrFujisHouseLittleGirlPokemonAreNiceToHugText" }, -- 6
|
{ "show_text", "_MrFujisHouseLittleGirlPokemonAreNiceToHugText" }, -- 6
|
||||||
},
|
},
|
||||||
|
|
||||||
-- scripts/MrFujisHouse.asm MrFujisHousePsyduckText: text_far then
|
-- scripts/MrFujisHouse.asm:56
|
||||||
-- PlayCry(PSYDUCK). Cry playback isn't modeled by Commands, so just
|
|
||||||
-- show the flavor text.
|
|
||||||
TEXT_MRFUJISHOUSE_PSYDUCK = {
|
TEXT_MRFUJISHOUSE_PSYDUCK = {
|
||||||
|
{ "play_cry", "PSYDUCK", true },
|
||||||
{ "show_text", "_MrFujisHousePsyduckText" },
|
{ "show_text", "_MrFujisHousePsyduckText" },
|
||||||
},
|
},
|
||||||
|
|
||||||
-- scripts/MrFujisHouse.asm MrFujisHouseNidorinoText: text_far then
|
-- scripts/MrFujisHouse.asm:63
|
||||||
-- PlayCry(NIDORINO).
|
|
||||||
TEXT_MRFUJISHOUSE_NIDORINO = {
|
TEXT_MRFUJISHOUSE_NIDORINO = {
|
||||||
|
{ "play_cry", "NIDORINO", true },
|
||||||
{ "show_text", "_MrFujisHouseNidorinoText" },
|
{ "show_text", "_MrFujisHouseNidorinoText" },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,10 +4,9 @@
|
|||||||
return {
|
return {
|
||||||
ROUTE_16_FLY_HOUSE = {
|
ROUTE_16_FLY_HOUSE = {
|
||||||
talk = {
|
talk = {
|
||||||
-- Route16FlyHouseFearowText: text_asm just prints the one line and
|
-- scripts/Route16FlyHouse.asm:45-51
|
||||||
-- plays the FEAROW cry (no cry-playback command exists in this
|
|
||||||
-- port's Commands vocabulary, so only the text is ported).
|
|
||||||
TEXT_ROUTE16FLYHOUSE_FEAROW = {
|
TEXT_ROUTE16FLYHOUSE_FEAROW = {
|
||||||
|
{ "play_cry", "FEAROW", true },
|
||||||
{ "show_text", "_Route16FlyHouseFearowText" },
|
{ "show_text", "_Route16FlyHouseFearowText" },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1047,6 +1047,7 @@ local championsRoomRivalScript = {
|
|||||||
-- numeric 26 into a jump ONTO the closing HALL_OF_FAME warp instead of past
|
-- numeric 26 into a jump ONTO the closing HALL_OF_FAME warp instead of past
|
||||||
-- it, so a returning champion warped straight into the induction.
|
-- it, so a returning champion warped straight into the induction.
|
||||||
{ "jump_if_true", "end" }, -- 3
|
{ "jump_if_true", "end" }, -- 3
|
||||||
|
{ "set_option", "animations", true }, -- scripts/ChampionsRoom.asm:57
|
||||||
{ "show_text", "_ChampionsRoomRivalIntroText" }, -- 4
|
{ "show_text", "_ChampionsRoomRivalIntroText" }, -- 4
|
||||||
-- ChampionsRoomRivalReadyToBattleScript plays MUSIC_FINAL_BATTLE after
|
-- ChampionsRoomRivalReadyToBattleScript plays MUSIC_FINAL_BATTLE after
|
||||||
-- the intro text, before the battle itself (#706); pushBattle's wipe-time
|
-- the intro text, before the battle itself (#706); pushBattle's wipe-time
|
||||||
|
|||||||
@@ -359,18 +359,22 @@ M.ROUTE_16_FLY_HOUSE = {
|
|||||||
TEXT_ROUTE16FLYHOUSE_BRUNETTE_GIRL = function(game, ow, npc, done)
|
TEXT_ROUTE16FLYHOUSE_BRUNETTE_GIRL = function(game, ow, npc, done)
|
||||||
local t = text(game)
|
local t = text(game)
|
||||||
if game.save.flags.EVENT_GOT_HM02 then
|
if game.save.flags.EVENT_GOT_HM02 then
|
||||||
push(game, t._Route16FlyHouseBrunetteGirlHm02ExplanationText
|
push(game, t._Route16FlyHouseBrunetteGirlHM02ExplanationText
|
||||||
or "HM02 is FLY!\fIt will whisk you\nback to any town!", done)
|
or "HM02 is FLY.\nIt will take you\vback to any town.\fPut it to good\nuse!", done)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
push(game, t._Route16FlyHouseBrunetteGirlText
|
push(game, t._Route16FlyHouseBrunetteGirlText
|
||||||
or "Shh! It's a\nsecret!\fMy POKéMON's\nHM02, take it!", function()
|
or "Oh, you found my\nsecret retreat!", function()
|
||||||
if not require("src.inventory.Bag").add(game.save, "HM_FLY", 1) then
|
if not require("src.inventory.Bag").add(game.save, "HM_FLY", 1) then
|
||||||
push(game, "You don't have\nroom for HM02!", done)
|
push(game, t._Route16FlyHouseBrunetteGirlHM02NoRoomText
|
||||||
|
or "You don't have any\nroom for this.", done)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
game.save.flags.EVENT_GOT_HM02 = true
|
game.save.flags.EVENT_GOT_HM02 = true
|
||||||
push(game, ("%s got\nHM02!"):format(game.save.player.name), done)
|
-- scripts/Route16FlyHouse.asm:32-35
|
||||||
|
push(game, fill(t._Route16FlyHouseBrunetteGirlReceivedHM02Text
|
||||||
|
or "{PLAYER} received\nHM02!", { player = game.save.player.name }),
|
||||||
|
done, require("src.render.TextBox").soundOpts(game, "Get_Key_Item"))
|
||||||
end)
|
end)
|
||||||
end,
|
end,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1170,6 +1170,17 @@ function love.run()
|
|||||||
-- per-frame sleep-granularity jitter.
|
-- per-frame sleep-granularity jitter.
|
||||||
local nextFrame = love.timer and love.timer.getTime() or 0
|
local nextFrame = love.timer and love.timer.getTime() or 0
|
||||||
local dt = 0
|
local dt = 0
|
||||||
|
local idleFor = 0
|
||||||
|
local SLEEP_FLOOR = 0.001
|
||||||
|
local WAKE = {
|
||||||
|
keypressed = true, keyreleased = true, textinput = true,
|
||||||
|
mousepressed = true, mousereleased = true, mousemoved = true,
|
||||||
|
wheelmoved = true, touchpressed = true, touchreleased = true,
|
||||||
|
touchmoved = true, joystickpressed = true, joystickreleased = true,
|
||||||
|
joystickhat = true, gamepadpressed = true, gamepadreleased = true,
|
||||||
|
joystickadded = true, joystickremoved = true, filedropped = true,
|
||||||
|
directorydropped = true, focus = true, visible = true, resize = true,
|
||||||
|
}
|
||||||
|
|
||||||
return function()
|
return function()
|
||||||
-- process events
|
-- process events
|
||||||
@@ -1188,17 +1199,34 @@ function love.run()
|
|||||||
return a or 0
|
return a or 0
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
if WAKE[name] then
|
||||||
|
idleFor = 0
|
||||||
|
elseif name == "joystickaxis" and type(c) == "number" and math.abs(c) > 0.5 then
|
||||||
|
idleFor = 0
|
||||||
|
end
|
||||||
love.handlers[name](a, b, c, d, e, f)
|
love.handlers[name](a, b, c, d, e, f)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- update dt
|
-- update dt
|
||||||
if love.timer then dt = love.timer.step() end
|
if love.timer then dt = love.timer.step() end
|
||||||
|
idleFor = idleFor + dt
|
||||||
|
|
||||||
-- call update and draw
|
-- call update and draw
|
||||||
if love.update then love.update(dt) end
|
if love.update then love.update(dt) end
|
||||||
|
|
||||||
if love.graphics and love.graphics.isActive() then
|
local visible = not (love.window and love.window.isVisible)
|
||||||
|
or love.window.isVisible()
|
||||||
|
local focused = not (love.window and love.window.hasFocus)
|
||||||
|
or love.window.hasFocus()
|
||||||
|
local cap = FrameCap.current
|
||||||
|
if not visible then
|
||||||
|
cap = 10
|
||||||
|
elseif Importer and (not focused or idleFor > 30) then
|
||||||
|
cap = 15
|
||||||
|
end
|
||||||
|
|
||||||
|
if visible and love.graphics and love.graphics.isActive() then
|
||||||
love.graphics.origin()
|
love.graphics.origin()
|
||||||
love.graphics.clear(love.graphics.getBackgroundColor())
|
love.graphics.clear(love.graphics.getBackgroundColor())
|
||||||
if love.draw then love.draw() end
|
if love.draw then love.draw() end
|
||||||
@@ -1209,9 +1237,9 @@ function love.run()
|
|||||||
if paced then
|
if paced then
|
||||||
-- Sleep out the remainder of the frame budget, measured from the
|
-- Sleep out the remainder of the frame budget, measured from the
|
||||||
-- carried deadline, in small chunks so the OS timer stays
|
-- carried deadline, in small chunks so the OS timer stays
|
||||||
-- responsive. vsync is untouched: when it already paces slower
|
-- responsive. The pacer yields to vsync inside a 1ms dead band, so
|
||||||
-- than the cap the remainder is <= 0 and this rounds to a no-op.
|
-- when the panel already paces at or below the cap it is a no-op.
|
||||||
local budget = 1 / FrameCap.current
|
local budget = 1 / cap
|
||||||
nextFrame = nextFrame + budget
|
nextFrame = nextFrame + budget
|
||||||
local now = love.timer.getTime()
|
local now = love.timer.getTime()
|
||||||
-- A stall (alt-tab, a GC pause, a blocked import) can leave the
|
-- A stall (alt-tab, a GC pause, a blocked import) can leave the
|
||||||
@@ -1222,8 +1250,8 @@ function love.run()
|
|||||||
end
|
end
|
||||||
while true do
|
while true do
|
||||||
local remaining = nextFrame - love.timer.getTime()
|
local remaining = nextFrame - love.timer.getTime()
|
||||||
if remaining <= 0 then break end
|
if remaining <= SLEEP_FLOOR then break end
|
||||||
love.timer.sleep(remaining < 0.001 and remaining or 0.001)
|
love.timer.sleep(0.001)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
love.timer.sleep(0.001)
|
love.timer.sleep(0.001)
|
||||||
|
|||||||
+77
-28
@@ -97,7 +97,7 @@ end
|
|||||||
-- space.
|
-- space.
|
||||||
function BattleState:extendedHUD()
|
function BattleState:extendedHUD()
|
||||||
local options = self.game and self.game.save and self.game.save.options
|
local options = self.game and self.game.save and self.game.save.options
|
||||||
local bg = options and options.battleBg
|
local bg = options and self:bgMode()
|
||||||
return self:wideLayout()
|
return self:wideLayout()
|
||||||
and options and options.battleHud == "extended"
|
and options and options.battleHud == "extended"
|
||||||
and ((options.battleFit == "fixed"
|
and ((options.battleFit == "fixed"
|
||||||
@@ -108,12 +108,12 @@ end
|
|||||||
function BattleState:extendedWorldHUD()
|
function BattleState:extendedWorldHUD()
|
||||||
local options = self.game and self.game.save and self.game.save.options
|
local options = self.game and self.game.save and self.game.save.options
|
||||||
return self:extendedHUD() and options
|
return self:extendedHUD() and options
|
||||||
and options.battleFit == "fixed" and options.battleBg == "world"
|
and options.battleFit == "fixed" and self:bgMode() == "world"
|
||||||
end
|
end
|
||||||
|
|
||||||
function BattleState:extendedBlackHUD()
|
function BattleState:extendedBlackHUD()
|
||||||
local options = self.game and self.game.save and self.game.save.options
|
local options = self.game and self.game.save and self.game.save.options
|
||||||
return self:extendedHUD() and options and options.battleBg == "black"
|
return self:extendedHUD() and options and self:bgMode() == "black"
|
||||||
end
|
end
|
||||||
|
|
||||||
-- BATTLE BG: what fills the screen AROUND the battle -- the letterbox voids
|
-- BATTLE BG: what fills the screen AROUND the battle -- the letterbox voids
|
||||||
@@ -130,6 +130,10 @@ end
|
|||||||
-- opaque 160x144 field over the top, so only the surround changes.
|
-- opaque 160x144 field over the top, so only the surround changes.
|
||||||
function BattleState:bgMode()
|
function BattleState:bgMode()
|
||||||
local options = self.game and self.game.save and self.game.save.options
|
local options = self.game and self.game.save and self.game.save.options
|
||||||
|
if options and self:wideLayout() and options.battleFit == "fill"
|
||||||
|
and options.battleHud == "extended" then
|
||||||
|
return "white"
|
||||||
|
end
|
||||||
local mode = options and options.battleBg
|
local mode = options and options.battleBg
|
||||||
if mode == "black" or mode == "world" then return mode end
|
if mode == "black" or mode == "world" then return mode end
|
||||||
return "white"
|
return "white"
|
||||||
@@ -212,6 +216,13 @@ local BALL_ANIMS = {
|
|||||||
SHAKE_ANIM = true, SHOWPIC_ANIM = true,
|
SHAKE_ANIM = true, SHOWPIC_ANIM = true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-- engine/battle/effects.asm:552
|
||||||
|
local ENEMY_STAT_DOWN_MISS = {
|
||||||
|
ATTACK_DOWN1_EFFECT = true, DEFENSE_DOWN1_EFFECT = true,
|
||||||
|
DEFENSE_DOWN2_EFFECT = true, SPEED_DOWN1_EFFECT = true,
|
||||||
|
ACCURACY_DOWN1_EFFECT = true,
|
||||||
|
}
|
||||||
|
|
||||||
local imageCache = {}
|
local imageCache = {}
|
||||||
-- The three tables below are keyed by the Image OBJECT, not by a path, and a
|
-- The three tables below are keyed by the Image OBJECT, not by a path, and a
|
||||||
-- running battle holds the pics it built at enter() (battler.sprite,
|
-- running battle holds the pics it built at enter() (battler.sprite,
|
||||||
@@ -229,6 +240,11 @@ local imagePadLeft = setmetatable({}, WEAK_KEYS)
|
|||||||
-- image -> { path, pal } so palette-fade variants (see fadeImage) can be
|
-- image -> { path, pal } so palette-fade variants (see fadeImage) can be
|
||||||
-- rebuilt for any battle pic, whatever code loaded it
|
-- rebuilt for any battle pic, whatever code loaded it
|
||||||
local imageMeta = setmetatable({}, WEAK_KEYS)
|
local imageMeta = setmetatable({}, WEAK_KEYS)
|
||||||
|
local function mattedPic(path)
|
||||||
|
return path:sub(1, 17) == "assets/generated/"
|
||||||
|
or path:sub(1, 17) == "save/mod-derived/"
|
||||||
|
end
|
||||||
|
|
||||||
-- pal = { name, colors } recolors the 4 GB shades like the Super Game Boy.
|
-- pal = { name, colors } recolors the 4 GB shades like the Super Game Boy.
|
||||||
-- trueColor art (14 §the 4-shade contract) opts out of the quantize
|
-- trueColor art (14 §the 4-shade contract) opts out of the quantize
|
||||||
-- entirely, so its palette variant collapses back onto the plain path.
|
-- entirely, so its palette variant collapses back onto the plain path.
|
||||||
@@ -254,29 +270,31 @@ local function getImage(path, pal, trueColor)
|
|||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
local w, h = id:getDimensions()
|
local w, h = id:getDimensions()
|
||||||
local bottom = h - 1
|
if mattedPic(Assets.resolve(path)) then
|
||||||
while bottom >= 0 do
|
local bottom = h - 1
|
||||||
local opaque = false
|
while bottom >= 0 do
|
||||||
for x = 0, w - 1 do
|
local opaque = false
|
||||||
local _, _, _, a = id:getPixel(x, bottom)
|
for x = 0, w - 1 do
|
||||||
if a > 0 then opaque = true break end
|
local _, _, _, a = id:getPixel(x, bottom)
|
||||||
|
if a > 0 then opaque = true break end
|
||||||
|
end
|
||||||
|
if opaque then break end
|
||||||
|
bottom = bottom - 1
|
||||||
end
|
end
|
||||||
if opaque then break end
|
local left = 0
|
||||||
bottom = bottom - 1
|
while left < w do
|
||||||
end
|
local opaque = false
|
||||||
local left = 0
|
for y = 0, h - 1 do
|
||||||
while left < w do
|
local _, _, _, a = id:getPixel(left, y)
|
||||||
local opaque = false
|
if a > 0 then opaque = true break end
|
||||||
for y = 0, h - 1 do
|
end
|
||||||
local _, _, _, a = id:getPixel(left, y)
|
if opaque then break end
|
||||||
if a > 0 then opaque = true break end
|
left = left + 1
|
||||||
end
|
end
|
||||||
if opaque then break end
|
pad = h - 1 - bottom
|
||||||
left = left + 1
|
padL = left
|
||||||
end
|
end
|
||||||
img = love.graphics.newImage(id)
|
img = love.graphics.newImage(id)
|
||||||
pad = h - 1 - bottom
|
|
||||||
padL = left
|
|
||||||
else
|
else
|
||||||
img = Assets.image(path) -- headless stub: no pixel access
|
img = Assets.image(path) -- headless stub: no pixel access
|
||||||
end
|
end
|
||||||
@@ -2214,6 +2232,9 @@ function BattleState:update(dt)
|
|||||||
self.waitFrames = nil
|
self.waitFrames = nil
|
||||||
if destination == "menu" then
|
if destination == "menu" then
|
||||||
self.introSlide = nil
|
self.introSlide = nil
|
||||||
|
-- engine/battle/core.asm:2007
|
||||||
|
self.msgHold = nil
|
||||||
|
self.shown = nil
|
||||||
self.phase = "menu"
|
self.phase = "menu"
|
||||||
elseif destination == "finish" then
|
elseif destination == "finish" then
|
||||||
self:finish()
|
self:finish()
|
||||||
@@ -3041,18 +3062,33 @@ local BGP_INVERT = { [0] = 3, 2, 1, 0 } -- $1b (flash phase 1)
|
|||||||
local BGP_WHITE = { [0] = 0, 0, 0, 0 } -- $00 (flash phase 2)
|
local BGP_WHITE = { [0] = 0, 0, 0, 0 } -- $00 (flash phase 2)
|
||||||
local BGP_DARK = { [0] = 3, 3, 2, 1 } -- $6f DarkScreenPalette
|
local BGP_DARK = { [0] = 3, 3, 2, 1 } -- $6f DarkScreenPalette
|
||||||
local BGP_LIGHT = { [0] = 0, 0, 1, 2 } -- $90 LightScreenPalette
|
local BGP_LIGHT = { [0] = 0, 0, 1, 2 } -- $90 LightScreenPalette
|
||||||
local BGP_DARKEN = { [0] = 0, 1, 3, 3 } -- $f4 DarkenMonPalette (SGB)
|
local BGP_DARKEN_SGB = { [0] = 0, 1, 3, 3 } -- $f4
|
||||||
|
local BGP_DARKEN_MONO = { [0] = 1, 2, 3, 3 } -- $f9
|
||||||
|
|
||||||
|
-- engine/battle/animations.asm:1090
|
||||||
|
local function onSgb()
|
||||||
|
local m = require("src.render.PaletteFX").mode
|
||||||
|
return m == "gbc" or m == "gbc_inv"
|
||||||
|
end
|
||||||
|
|
||||||
-- FlashScreenLongSGB (animations.asm:1010): 12 BGP values per cycle,
|
-- FlashScreenLongSGB (animations.asm:1010): 12 BGP values per cycle,
|
||||||
-- 3 cycles; the first cycle holds each for 2 frames, the rest for 1
|
-- 3 cycles; the first cycle holds each for 2 frames, the rest for 1
|
||||||
-- (FlashScreenLongDelay)
|
-- (FlashScreenLongDelay)
|
||||||
local FLASH_LONG_MAPS = {
|
local FLASH_LONG_SGB = {
|
||||||
{ [0] = 0, 2, 3, 3 }, { [0] = 0, 3, 3, 3 }, { [0] = 3, 3, 3, 3 },
|
{ [0] = 0, 2, 3, 3 }, { [0] = 0, 3, 3, 3 }, { [0] = 3, 3, 3, 3 },
|
||||||
{ [0] = 0, 3, 3, 3 }, { [0] = 0, 2, 3, 3 }, { [0] = 0, 1, 2, 3 },
|
{ [0] = 0, 3, 3, 3 }, { [0] = 0, 2, 3, 3 }, { [0] = 0, 1, 2, 3 },
|
||||||
{ [0] = 0, 0, 1, 2 }, { [0] = 0, 0, 0, 1 }, { [0] = 0, 0, 0, 0 },
|
{ [0] = 0, 0, 1, 2 }, { [0] = 0, 0, 0, 1 }, { [0] = 0, 0, 0, 0 },
|
||||||
{ [0] = 0, 0, 0, 1 }, { [0] = 0, 0, 1, 2 }, { [0] = 0, 1, 2, 3 },
|
{ [0] = 0, 0, 0, 1 }, { [0] = 0, 0, 1, 2 }, { [0] = 0, 1, 2, 3 },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-- engine/battle/animations.asm:992
|
||||||
|
local FLASH_LONG_MONO = {
|
||||||
|
{ [0] = 1, 2, 3, 3 }, { [0] = 2, 3, 3, 3 }, { [0] = 3, 3, 3, 3 },
|
||||||
|
{ [0] = 2, 3, 3, 3 }, { [0] = 1, 2, 3, 3 }, { [0] = 0, 1, 2, 3 },
|
||||||
|
{ [0] = 0, 0, 1, 2 }, { [0] = 0, 0, 0, 1 }, { [0] = 0, 0, 0, 0 },
|
||||||
|
{ [0] = 0, 0, 0, 1 }, { [0] = 0, 0, 1, 2 }, { [0] = 0, 1, 2, 3 },
|
||||||
|
}
|
||||||
|
|
||||||
-- the shade map in force this frame (a running flash wins over the
|
-- the shade map in force this frame (a running flash wins over the
|
||||||
-- persistent palette)
|
-- persistent palette)
|
||||||
function BattleState:activeBgp()
|
function BattleState:activeBgp()
|
||||||
@@ -3186,7 +3222,7 @@ function BattleState:applyAnimEffect(ev)
|
|||||||
elseif e == "SE_LIGHT_SCREEN_PALETTE" then
|
elseif e == "SE_LIGHT_SCREEN_PALETTE" then
|
||||||
fx.bgp = BGP_LIGHT
|
fx.bgp = BGP_LIGHT
|
||||||
elseif e == "SE_DARKEN_MON_PALETTE" then
|
elseif e == "SE_DARKEN_MON_PALETTE" then
|
||||||
fx.bgp = BGP_DARKEN
|
fx.bgp = onSgb() and BGP_DARKEN_SGB or BGP_DARKEN_MONO
|
||||||
elseif e == "SE_RESET_SCREEN_PALETTE" then
|
elseif e == "SE_RESET_SCREEN_PALETTE" then
|
||||||
fx.bgp = nil
|
fx.bgp = nil
|
||||||
elseif e == "SE_DARK_SCREEN_FLASH" then
|
elseif e == "SE_DARK_SCREEN_FLASH" then
|
||||||
@@ -3196,8 +3232,9 @@ function BattleState:applyAnimEffect(ev)
|
|||||||
idx = 1, left = 2 }
|
idx = 1, left = 2 }
|
||||||
elseif e == "SE_FLASH_SCREEN_LONG" then
|
elseif e == "SE_FLASH_SCREEN_LONG" then
|
||||||
local steps = {}
|
local steps = {}
|
||||||
|
local maps = onSgb() and FLASH_LONG_SGB or FLASH_LONG_MONO
|
||||||
for cycle = 1, 3 do
|
for cycle = 1, 3 do
|
||||||
for _, m in ipairs(FLASH_LONG_MAPS) do
|
for _, m in ipairs(maps) do
|
||||||
steps[#steps + 1] = { map = m, frames = (cycle == 1) and 2 or 1 }
|
steps[#steps + 1] = { map = m, frames = (cycle == 1) and 2 or 1 }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -4139,6 +4176,12 @@ function BattleState:performMove(user, target, moveInst, isCalled)
|
|||||||
|
|
||||||
-- pure status moves
|
-- pure status moves
|
||||||
if move.power == 0 and record and record.kind == "primary" and record.run then
|
if move.power == 0 and record and record.kind == "primary" and record.run then
|
||||||
|
if ENEMY_STAT_DOWN_MISS[move.effect] and not user.isPlayer
|
||||||
|
and self.kind ~= "link" and self.rng(0, 255) < 64 then
|
||||||
|
self:cancelMoveAnim()
|
||||||
|
self:sayNext(self:romText("_AttackMissedText", "%s's\nattack missed!", displayName(user)))
|
||||||
|
return
|
||||||
|
end
|
||||||
-- accuracy-checked status effects run MoveHitTest, which has no
|
-- accuracy-checked status effects run MoveHitTest, which has no
|
||||||
-- 100%-accuracy early-out (even Thunder Wave misses on the 255
|
-- 100%-accuracy early-out (even Thunder Wave misses on the 255
|
||||||
-- roll) and misses outright against a mid-Fly/Dig target; the
|
-- roll) and misses outright against a mid-Fly/Dig target; the
|
||||||
@@ -4417,7 +4460,11 @@ function BattleState:awardExp()
|
|||||||
-- new current HP (house convention: potions drain the bar too, see
|
-- new current HP (house convention: potions drain the bar too, see
|
||||||
-- itemUsed) so the bar grows instead. Only the active player battler
|
-- itemUsed) so the bar grows instead. Only the active player battler
|
||||||
-- shares its table with the HUD; other party mons (EXP.ALL) have no bar.
|
-- shares its table with the HUD; other party mons (EXP.ALL) have no bar.
|
||||||
if mon == self.player.mon then self:drainNext() end
|
if mon == self.player.mon then
|
||||||
|
-- engine/battle/experience.asm:236
|
||||||
|
self.player.badgeExtraBoosts = nil
|
||||||
|
self:drainNext()
|
||||||
|
end
|
||||||
for _, moveId in ipairs(Experience.movesLearnedAt(
|
for _, moveId in ipairs(Experience.movesLearnedAt(
|
||||||
self.data.pokemon[mon.species], lv)) do
|
self.data.pokemon[mon.species], lv)) do
|
||||||
self:learnMove(mon, moveId)
|
self:learnMove(mon, moveId)
|
||||||
@@ -5920,7 +5967,9 @@ function BattleState:animSpriteColors(s, px, py)
|
|||||||
local P
|
local P
|
||||||
-- engine/battle/animations.asm:551 (.notSGB)
|
-- engine/battle/animations.asm:551 (.notSGB)
|
||||||
if PaletteFX.usesSpriteObp() then
|
if PaletteFX.usesSpriteObp() then
|
||||||
P = PaletteFX.ogObj()
|
-- engine/battle/init_battle_variables.asm:18
|
||||||
|
P = require("src.core.GameVersion").isBlue() and PaletteFX.GBC_OBJ_BLUE
|
||||||
|
or PaletteFX.GBC_OBJ
|
||||||
if key == "f0" then key = "e4" elseif key == "f0x" then key = "e4x" end
|
if key == "f0" then key = "e4" elseif key == "f0x" then key = "e4x" end
|
||||||
else
|
else
|
||||||
P = self:zoneColorsAt(px or (s.x - 8 + 4), py or (s.y - 16 + 4))
|
P = self:zoneColorsAt(px or (s.x - 8 + 4), py or (s.y - 16 + 4))
|
||||||
|
|||||||
+30
-8
@@ -41,6 +41,34 @@ local function badgeBoost(battler, stat)
|
|||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- engine/battle/core.asm:6454
|
||||||
|
function Damage.applyBadgeBoost(battler, stat, value)
|
||||||
|
if battler.hazeStatReset then return value end
|
||||||
|
local row = badgeBoost(battler, stat)
|
||||||
|
if not row then return value end
|
||||||
|
local extra = battler.badgeExtraBoosts and battler.badgeExtraBoosts[stat] or 0
|
||||||
|
for _ = 1, 1 + extra do
|
||||||
|
value = math.min(999, math.floor(value * (row.num or 9) / (row.den or 8)))
|
||||||
|
end
|
||||||
|
return value
|
||||||
|
end
|
||||||
|
|
||||||
|
-- engine/battle/effects.asm:498,689
|
||||||
|
function Damage.reapplyBadgeBoosts(battler, changedStat)
|
||||||
|
if not battler or not battler.badges then return end
|
||||||
|
local extra = battler.badgeExtraBoosts
|
||||||
|
if not extra then extra = {} battler.badgeExtraBoosts = extra end
|
||||||
|
for _, row in ipairs(battler.badgeBoosts or Damage.BADGE_BOOSTS) do
|
||||||
|
if battler.badges[row.badge] then
|
||||||
|
if row.stat == changedStat then
|
||||||
|
extra[row.stat] = 0
|
||||||
|
else
|
||||||
|
extra[row.stat] = (extra[row.stat] or 0) + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- the merged status record for a battler's persistent condition, or nil
|
-- the merged status record for a battler's persistent condition, or nil
|
||||||
local function statusRecord(battler)
|
local function statusRecord(battler)
|
||||||
return Status.recordFor(battler.statuses, battler.mon.status)
|
return Status.recordFor(battler.statuses, battler.mon.status)
|
||||||
@@ -178,14 +206,8 @@ function Damage.compute(ruleset, attacker, defender, move, opts)
|
|||||||
-- badge boosts (x9/8), engine/battle/core.asm ApplyBadgeStatBoosts:
|
-- badge boosts (x9/8), engine/battle/core.asm ApplyBadgeStatBoosts:
|
||||||
-- Boulder -> attack, Thunder -> defense, Soul -> speed (TurnOrder),
|
-- Boulder -> attack, Thunder -> defense, Soul -> speed (TurnOrder),
|
||||||
-- Volcano -> special
|
-- Volcano -> special
|
||||||
local atkBoost = badgeBoost(attacker, atkStat)
|
atk = Damage.applyBadgeBoost(attacker, atkStat, atk)
|
||||||
if atkBoost then
|
dfn = Damage.applyBadgeBoost(defender, defStat, dfn)
|
||||||
atk = math.floor(atk * (atkBoost.num or 9) / (atkBoost.den or 8))
|
|
||||||
end
|
|
||||||
local defBoost = badgeBoost(defender, defStat)
|
|
||||||
if defBoost then
|
|
||||||
dfn = math.floor(dfn * (defBoost.num or 9) / (defBoost.den or 8))
|
|
||||||
end
|
|
||||||
-- burn halves physical attack (applied as part of the stat in Gen 1;
|
-- burn halves physical attack (applied as part of the stat in Gen 1;
|
||||||
-- the status record's statPenalty names the stat it cuts).
|
-- the status record's statPenalty names the stat it cuts).
|
||||||
-- hazeStatReset suppresses it: Haze (haze.asm ResetStats) copied the
|
-- hazeStatReset suppresses it: Haze (haze.asm ResetStats) copied the
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
-- is the registry view of all three -- the merged Data.move_effects a
|
-- is the registry view of all three -- the merged Data.move_effects a
|
||||||
-- battle dispatches on serves these same objects.
|
-- battle dispatches on serves these same objects.
|
||||||
|
|
||||||
|
local Damage = require("src.battle.Damage")
|
||||||
local Logger = require("src.core.Logger")
|
local Logger = require("src.core.Logger")
|
||||||
local StatusRegistry = require("src.battle.StatusRegistry")
|
local StatusRegistry = require("src.battle.StatusRegistry")
|
||||||
local TurnOrder = require("src.battle.TurnOrder")
|
local TurnOrder = require("src.battle.TurnOrder")
|
||||||
@@ -57,6 +58,10 @@ local function changeStage(battle, who, stat, delta, fromEnemy)
|
|||||||
-- recomputed and QuarterSpeedDueToParalysis/HalveAttackDueToBurn re-run,
|
-- recomputed and QuarterSpeedDueToParalysis/HalveAttackDueToBurn re-run,
|
||||||
-- re-baking the burn/para penalty and ending Haze's temporary lift.
|
-- re-baking the burn/para penalty and ending Haze's temporary lift.
|
||||||
who.hazeStatReset = nil
|
who.hazeStatReset = nil
|
||||||
|
if battle.ruleset and battle.ruleset.badgeBoostReapplyBug
|
||||||
|
and battle.kind ~= "link" and who == battle.player then
|
||||||
|
Damage.reapplyBadgeBoosts(who, stat)
|
||||||
|
end
|
||||||
-- _MonsStatsRoseText/_MonsStatsFellText: "X's / STAT rose!"; the
|
-- _MonsStatsRoseText/_MonsStatsFellText: "X's / STAT rose!"; the
|
||||||
-- two-stage variants scroll "greatly" onto a third line
|
-- two-stage variants scroll "greatly" onto a third line
|
||||||
local label = Strings(STAT_LABEL[stat]) -- looked up here, not at require (#811)
|
local label = Strings(STAT_LABEL[stat]) -- looked up here, not at require (#811)
|
||||||
@@ -132,6 +137,11 @@ end
|
|||||||
|
|
||||||
local function statDownSide(stat)
|
local function statDownSide(stat)
|
||||||
return function(battle, user, target)
|
return function(battle, user, target)
|
||||||
|
-- engine/battle/effects.asm:552
|
||||||
|
if not user.isPlayer and battle.kind ~= "link"
|
||||||
|
and battle.rng(0, 255) < 64 then
|
||||||
|
return {}
|
||||||
|
end
|
||||||
if target.substituteHP then return {} end
|
if target.substituteHP then return {} end
|
||||||
if battle.rng(0, 255) >= 85 then return {} end -- 33 percent + 1 (85/256)
|
if battle.rng(0, 255) >= 85 then return {} end -- 33 percent + 1 (85/256)
|
||||||
-- StatModifierDownEffect's side-effect branch never runs MoveHitTest,
|
-- StatModifierDownEffect's side-effect branch never runs MoveHitTest,
|
||||||
@@ -254,6 +264,7 @@ MoveEffects.primary = {
|
|||||||
-- Attack-halving and paralysis Speed-quartering on BOTH battlers
|
-- Attack-halving and paralysis Speed-quartering on BOTH battlers
|
||||||
-- until the next stat recompute (a stage change or switch-in).
|
-- until the next stat recompute (a stage change or switch-in).
|
||||||
b.hazeStatReset = true
|
b.hazeStatReset = true
|
||||||
|
b.badgeExtraBoosts = nil
|
||||||
end
|
end
|
||||||
-- Gen 1 also removes the enemy's major status; if that cured sleep
|
-- Gen 1 also removes the enemy's major status; if that cured sleep
|
||||||
-- or freeze, the target forfeits its move this turn (haze.asm
|
-- or freeze, the target forfeits its move this turn (haze.asm
|
||||||
|
|||||||
@@ -14,15 +14,7 @@ local function effectiveSpeed(battler)
|
|||||||
battler.stages and battler.stages.speed or 0)
|
battler.stages and battler.stages.speed or 0)
|
||||||
-- ApplyBadgeStatBoosts: the SOULBADGE boosts speed; the rows come from
|
-- ApplyBadgeStatBoosts: the SOULBADGE boosts speed; the rows come from
|
||||||
-- the battler's merged badgeBoosts with the vanilla list as fallback
|
-- the battler's merged badgeBoosts with the vanilla list as fallback
|
||||||
local badges = battler.badges
|
spd = Damage.applyBadgeBoost(battler, "speed", spd)
|
||||||
if badges then
|
|
||||||
for _, row in ipairs(battler.badgeBoosts or Damage.BADGE_BOOSTS) do
|
|
||||||
if row.stat == "speed" and badges[row.badge] then
|
|
||||||
spd = math.floor(spd * (row.num or 9) / (row.den or 8))
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
-- paralysis quarters speed (the status record's statPenalty);
|
-- paralysis quarters speed (the status record's statPenalty);
|
||||||
-- hazeStatReset suppresses it because Haze (haze.asm ResetStats)
|
-- hazeStatReset suppresses it because Haze (haze.asm ResetStats)
|
||||||
-- copied the unmodified speed over the quartered battle stat, lifting
|
-- copied the unmodified speed over the quartered battle stat, lifting
|
||||||
|
|||||||
@@ -97,6 +97,19 @@ local function battleIsTopState(battle)
|
|||||||
return not (stack and stack.top) or stack:top() == battle
|
return not (stack and stack.top) or stack:top() == battle
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- engine/menus/party_menu.asm:4
|
||||||
|
local function coveredByOpaqueState(battle)
|
||||||
|
local stack = battle.game and battle.game.stack
|
||||||
|
local states = stack and stack.states
|
||||||
|
if not states then return false end
|
||||||
|
local above = false
|
||||||
|
for i = 1, #states do
|
||||||
|
if above and states[i] and states[i].isOpaque then return true end
|
||||||
|
if states[i] == battle then above = true end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
local function anchorHUD(battle, x, y, w, h, anchor)
|
local function anchorHUD(battle, x, y, w, h, anchor)
|
||||||
if not battle:extendedHUD() or not battleIsTopState(battle) then return end
|
if not battle:extendedHUD() or not battleIsTopState(battle) then return end
|
||||||
local renderer = battle.game and battle.game.renderer
|
local renderer = battle.game and battle.game.renderer
|
||||||
@@ -351,7 +364,7 @@ function WideBattle.draw(battle)
|
|||||||
g.rectangle("fill", 0, 0, WideBattle.WIDTH, WideBattle.HEIGHT)
|
g.rectangle("fill", 0, 0, WideBattle.WIDTH, WideBattle.HEIGHT)
|
||||||
end
|
end
|
||||||
-- AskName clears the field the same way the classic layout does
|
-- AskName clears the field the same way the classic layout does
|
||||||
if battle.blankForAskName then return end
|
if battle.blankForAskName or coveredByOpaqueState(battle) then return end
|
||||||
|
|
||||||
local fx = battle.fx
|
local fx = battle.fx
|
||||||
local sx = (fx and fx.shakeX) or 0
|
local sx = (fx and fx.shakeX) or 0
|
||||||
|
|||||||
@@ -645,6 +645,7 @@ function Battle:smartAiState()
|
|||||||
-- wPlayerSubStatus5 & SUBSTATUS_LOCK_ON: the enemy's OWN Lock-On, since
|
-- wPlayerSubStatus5 & SUBSTATUS_LOCK_ON: the enemy's OWN Lock-On, since
|
||||||
-- BattleCommand_LockOn sets the bit on the target it was aimed at.
|
-- BattleCommand_LockOn sets the bit on the target it was aimed at.
|
||||||
playerLockOn = playerState.lockOn or nil,
|
playerLockOn = playerState.lockOn or nil,
|
||||||
|
playerIdentified = playerState.identified or nil,
|
||||||
playerPhysicalMoves = physical,
|
playerPhysicalMoves = physical,
|
||||||
enemyRage = enemyState.rage,
|
enemyRage = enemyState.rage,
|
||||||
enemyRageCount = enemyState.rageCount,
|
enemyRageCount = enemyState.rageCount,
|
||||||
@@ -1082,7 +1083,7 @@ function Battle:hitOnce(attacker, defender, def, opts)
|
|||||||
local attackerStages = self.stages[self:sideOf(attacker)]
|
local attackerStages = self.stages[self:sideOf(attacker)]
|
||||||
local defenderStages = self.stages[self:sideOf(defender)]
|
local defenderStages = self.stages[self:sideOf(defender)]
|
||||||
local types = self.data.type_chart and self.data.type_chart.types
|
local types = self.data.type_chart and self.data.type_chart.types
|
||||||
local matchups = self.data.type_chart and self.data.type_chart.matchups
|
local matchups = self:matchupsAgainst(defender)
|
||||||
|
|
||||||
local heldEffect, heldParam = self:heldEffect(attacker, "damage")
|
local heldEffect, heldParam = self:heldEffect(attacker, "damage")
|
||||||
-- BattleCommand_Critical: SUBSTATUS_FOCUS_ENERGY (Focus Energy or a
|
-- BattleCommand_Critical: SUBSTATUS_FOCUS_ENERGY (Focus Energy or a
|
||||||
@@ -1202,6 +1203,8 @@ function Battle:hitOnce(attacker, defender, def, opts)
|
|||||||
if def.effect == "EFFECT_FALSE_SWIPE" and damage >= (defender.hp or 0) then
|
if def.effect == "EFFECT_FALSE_SWIPE" and damage >= (defender.hp or 0) then
|
||||||
damage = math.max(0, (defender.hp or 0) - 1)
|
damage = math.max(0, (defender.hp or 0) - 1)
|
||||||
end
|
end
|
||||||
|
-- engine/battle_anims/anim_commands.asm:1200
|
||||||
|
if self.moveEvent then self.moveEvent.effectiveness = info.effectiveness end
|
||||||
return self:dealDamage(attacker, defender, damage, {
|
return self:dealDamage(attacker, defender, damage, {
|
||||||
critical = critical, effectiveness = info.effectiveness,
|
critical = critical, effectiveness = info.effectiveness,
|
||||||
-- Counter answers physical damage and Mirror Coat special, so what kind
|
-- Counter answers physical damage and Mirror Coat special, so what kind
|
||||||
@@ -1690,6 +1693,13 @@ function Battle:useMove(attacker, defender, moveId)
|
|||||||
text = ("Magnitude %d!"):format(number) })
|
text = ("Magnitude %d!"):format(number) })
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- data/moves/effects.asm:1607, :1649
|
||||||
|
if def.effect == "EFFECT_RETURN" then
|
||||||
|
powerOverride = Effects.happinessPower(attacker.happiness)
|
||||||
|
elseif def.effect == "EFFECT_FRUSTRATION" then
|
||||||
|
powerOverride = Effects.happinessPower(attacker.happiness, true)
|
||||||
|
end
|
||||||
|
|
||||||
if not sureHit
|
if not sureHit
|
||||||
and not self:accuracyRoll(def, attacker, defender) then
|
and not self:accuracyRoll(def, attacker, defender) then
|
||||||
-- data/moves/effects.asm:148-151: `selfdestruct` sits between checkhit and
|
-- data/moves/effects.asm:148-151: `selfdestruct` sits between checkhit and
|
||||||
@@ -1744,7 +1754,7 @@ function Battle:useMove(attacker, defender, moveId)
|
|||||||
-- thing that stops SONIC BOOM, NIGHT SHADE or SUPER FANG.
|
-- thing that stops SONIC BOOM, NIGHT SHADE or SUPER FANG.
|
||||||
local defenderTypes = (self:speciesDef(defender) or {}).types
|
local defenderTypes = (self:speciesDef(defender) or {}).types
|
||||||
or defender.types
|
or defender.types
|
||||||
local matchups = self.data.type_chart and self.data.type_chart.matchups
|
local matchups = self:matchupsAgainst(defender)
|
||||||
if Damage.typeMultiplier(def.type, defenderTypes, matchups) == 0 then
|
if Damage.typeMultiplier(def.type, defenderTypes, matchups) == 0 then
|
||||||
self:markMissed()
|
self:markMissed()
|
||||||
self:emit({ kind = "message",
|
self:emit({ kind = "message",
|
||||||
@@ -2119,6 +2129,21 @@ Battle.MOVE_EFFECTS.EFFECT_LOCK_ON = function(self, attacker, defender)
|
|||||||
text = self:monName(attacker) .. " took aim!" })
|
text = self:monName(attacker) .. " took aim!" })
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- engine/battle/move_effects/foresight.asm
|
||||||
|
Battle.MOVE_EFFECTS.EFFECT_FORESIGHT = function(self, attacker, defender,
|
||||||
|
def, _, sureHit)
|
||||||
|
if not sureHit
|
||||||
|
and not self:accuracyRoll(def, attacker, defender) then
|
||||||
|
return fail(self)
|
||||||
|
end
|
||||||
|
local target = self:volatile(defender)
|
||||||
|
if target.vanished or target.identified then return fail(self) end
|
||||||
|
target.identified = true
|
||||||
|
self:emit({ kind = "message",
|
||||||
|
text = self:monName(attacker) .. " identified "
|
||||||
|
.. self:monName(defender) .. "!" })
|
||||||
|
end
|
||||||
|
|
||||||
-- BattleCommand_CheckHit's .LockOn: the flag is read AND cleared by the next
|
-- BattleCommand_CheckHit's .LockOn: the flag is read AND cleared by the next
|
||||||
-- move aimed at the mon carrying it, whether or not that move was the one the
|
-- move aimed at the mon carrying it, whether or not that move was the one the
|
||||||
-- lock-on was meant for, and whether or not the exception at :1683-1688 then
|
-- lock-on was meant for, and whether or not the exception at :1683-1688 then
|
||||||
@@ -2166,9 +2191,15 @@ function Battle:accuracyRoll(def, attacker, defender, accuracy)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function Battle:vanillaAccuracyRoll(accuracy, attacker, defender)
|
function Battle:vanillaAccuracyRoll(accuracy, attacker, defender)
|
||||||
return Damage.rollHit(self:moveAccuracy(accuracy, defender),
|
local acc = self.stages[self:sideOf(attacker)].accuracy
|
||||||
self.stages[self:sideOf(attacker)].accuracy,
|
local eva = self.stages[self:sideOf(defender)].evasion
|
||||||
self.stages[self:sideOf(defender)].evasion, self.random)
|
-- engine/battle/effect_commands.asm:1786
|
||||||
|
if defender and self:volatile(defender).identified
|
||||||
|
and (eva or 0) >= (acc or 0) then
|
||||||
|
acc, eva = 0, 0
|
||||||
|
end
|
||||||
|
return Damage.rollHit(self:moveAccuracy(accuracy, defender), acc, eva,
|
||||||
|
self.random)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- BattleCommand_StatDown's SUBSTATUS_MIST arm (a GUARD SPEC): a drop the FOE
|
-- BattleCommand_StatDown's SUBSTATUS_MIST arm (a GUARD SPEC): a drop the FOE
|
||||||
@@ -2354,7 +2385,7 @@ Battle.MOVE_EFFECTS.EFFECT_FUTURE_SIGHT = function(self, attacker, defender, def
|
|||||||
stages = self.stages[self:sideOf(defender)],
|
stages = self.stages[self:sideOf(defender)],
|
||||||
},
|
},
|
||||||
types = self.data.type_chart and self.data.type_chart.types,
|
types = self.data.type_chart and self.data.type_chart.types,
|
||||||
matchups = self.data.type_chart and self.data.type_chart.matchups,
|
matchups = self:matchupsAgainst(defender),
|
||||||
random = self.random,
|
random = self.random,
|
||||||
})
|
})
|
||||||
state.futureSight = Effects.FUTURE_SIGHT_TURNS
|
state.futureSight = Effects.FUTURE_SIGHT_TURNS
|
||||||
@@ -3072,6 +3103,30 @@ function Battle:safeguarded(mon)
|
|||||||
return (self.screens[self:sideOf(mon)].safeguard or 0) > 0
|
return (self.screens[self:sideOf(mon)].safeguard or 0) > 0
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- engine/battle/effect_commands.asm:1305
|
||||||
|
function Battle:matchupsAgainst(defender)
|
||||||
|
local chart = self.data.type_chart
|
||||||
|
local rows = chart and chart.matchups
|
||||||
|
if not rows or not defender then return rows end
|
||||||
|
if not self:volatile(defender).identified then return rows end
|
||||||
|
local skipped = chart.foresightMatchups
|
||||||
|
if not skipped or #skipped == 0 then return rows end
|
||||||
|
if not self.identifiedMatchups then
|
||||||
|
local drop = {}
|
||||||
|
for _, row in ipairs(skipped) do
|
||||||
|
drop[tostring(row.attacker) .. "/" .. tostring(row.defender)] = true
|
||||||
|
end
|
||||||
|
local out = {}
|
||||||
|
for _, row in ipairs(rows) do
|
||||||
|
if not drop[tostring(row.attacker) .. "/" .. tostring(row.defender)] then
|
||||||
|
out[#out + 1] = row
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self.identifiedMatchups = out
|
||||||
|
end
|
||||||
|
return self.identifiedMatchups
|
||||||
|
end
|
||||||
|
|
||||||
-- `source` is the battler that inflicted it, carried only so
|
-- `source` is the battler that inflicted it, carried only so
|
||||||
-- battle.status_inflicted can name it the way Gen 1's does.
|
-- battle.status_inflicted can name it the way Gen 1's does.
|
||||||
-- BattleCommand_Paralyze and BattleCommand_Poison refuse on a zero matchup,
|
-- BattleCommand_Paralyze and BattleCommand_Poison refuse on a zero matchup,
|
||||||
@@ -3084,7 +3139,7 @@ function Battle:statusRefusedByType(defender, moveType, status)
|
|||||||
end
|
end
|
||||||
local types = (self:speciesDef(defender) or {}).types or defender.types or {}
|
local types = (self:speciesDef(defender) or {}).types or defender.types or {}
|
||||||
if moveType then
|
if moveType then
|
||||||
local matchups = self.data.type_chart and self.data.type_chart.matchups
|
local matchups = self:matchupsAgainst(defender)
|
||||||
if Damage.typeMultiplier(moveType, types, matchups) == 0 then return true end
|
if Damage.typeMultiplier(moveType, types, matchups) == 0 then return true end
|
||||||
end
|
end
|
||||||
if status == "poison" or status == "toxic" then
|
if status == "poison" or status == "toxic" then
|
||||||
@@ -3917,6 +3972,8 @@ end
|
|||||||
-- check. Split out because playerAttack needs the same answer.
|
-- check. Split out because playerAttack needs the same answer.
|
||||||
function Battle:lockedInMove(mon)
|
function Battle:lockedInMove(mon)
|
||||||
local state = self:volatile(mon)
|
local state = self:volatile(mon)
|
||||||
|
-- engine/battle/core.asm:543
|
||||||
|
if state.chargeMove then return state.chargeMove end
|
||||||
if state.rolloutLock then return state.rolloutLock end
|
if state.rolloutLock then return state.rolloutLock end
|
||||||
if state.rampageMove and (state.rampageTurns or 0) > 0 then
|
if state.rampageMove and (state.rampageTurns or 0) > 0 then
|
||||||
return state.rampageMove
|
return state.rampageMove
|
||||||
|
|||||||
@@ -284,6 +284,14 @@ function Effects.magnitudePower(random)
|
|||||||
return last[2], last[3]
|
return last[2], last[3]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- engine/battle/move_effects/return.asm:1-24, frustration.asm:1-25
|
||||||
|
function Effects.happinessPower(happiness, frustration)
|
||||||
|
local h = happiness or 0
|
||||||
|
if h < 0 then h = 0 elseif h > 255 then h = 255 end
|
||||||
|
if frustration then h = 255 - h end
|
||||||
|
return math.floor(h * 10 / 25)
|
||||||
|
end
|
||||||
|
|
||||||
-- ------------------------------------------------------------------- weather
|
-- ------------------------------------------------------------------- weather
|
||||||
--
|
--
|
||||||
-- BattleCommand_StartRain / StartSun / StartSandstorm all set wWeatherCount to
|
-- BattleCommand_StartRain / StartSun / StartSandstorm all set wWeatherCount to
|
||||||
|
|||||||
@@ -58,9 +58,12 @@ Prize.AMULET_COIN = "AMULET_COIN"
|
|||||||
-- data/text/battle.asm. Declared here and formatted at the call site so
|
-- data/text/battle.asm. Declared here and formatted at the call site so
|
||||||
-- Strings.source is what registers them, the same way Decorations declares
|
-- Strings.source is what registers them, the same way Decorations declares
|
||||||
-- its own five. No line markers: every battle message in this port is one
|
-- its own five. No line markers: every battle message in this port is one
|
||||||
-- flowing string that Chrome.wrap breaks to the box.
|
-- flowing string that Chrome.wrap breaks to the box, except SentSomeToMomText,
|
||||||
|
-- which keeps the cart's own `line`/`cont` breaks because it does not fit two
|
||||||
|
-- rows.
|
||||||
local GOT_MONEY = Strings.source("%s got %s%d for winning!")
|
local GOT_MONEY = Strings.source("%s got %s%d for winning!")
|
||||||
local SENT_SOME = Strings.source("%s got %s%d for winning! Sent some to MOM!")
|
-- data/text/battle.asm:179-185
|
||||||
|
local SENT_SOME = Strings.source("%s got %s%d\nfor winning!\vSent some to MOM!")
|
||||||
-- The half and all texts really are this short on the cart: they replace the
|
-- The half and all texts really are this short on the cart: they replace the
|
||||||
-- money line rather than following it, which is a quirk no Gold player can
|
-- money line rather than following it, which is a quirk no Gold player can
|
||||||
-- see because BankOfMom only ever writes MOM_SAVING_SOME_MONEY_F.
|
-- see because BankOfMom only ever writes MOM_SAVING_SOME_MONEY_F.
|
||||||
|
|||||||
@@ -23,4 +23,6 @@ return {
|
|||||||
-- (core.asm:426-464): poison/burn/leech seed tick before the slower
|
-- (core.asm:426-464): poison/burn/leech seed tick before the slower
|
||||||
-- mon acts, not in an end-of-round sweep like Gen 3+.
|
-- mon acts, not in an end-of-round sweep like Gen 3+.
|
||||||
residualAfterMove = true,
|
residualAfterMove = true,
|
||||||
|
-- engine/battle/effects.asm:498,689
|
||||||
|
badgeBoostReapplyBug = true,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ local BATTLER_FIELDS = {
|
|||||||
"chargeReady", "invulnerable", "mustRecharge",
|
"chargeReady", "invulnerable", "mustRecharge",
|
||||||
"thrashTurns", "thrashAnnounced", "focusEnergy", "leechSeeded",
|
"thrashTurns", "thrashAnnounced", "focusEnergy", "leechSeeded",
|
||||||
"lightScreen", "reflect", "mist", "xAccuracy", "lastMove", "flinched",
|
"lightScreen", "reflect", "mist", "xAccuracy", "lastMove", "flinched",
|
||||||
"skipMove", "hazeStatReset", "drainFloor", "drainHold", "trappingTurns",
|
"skipMove", "hazeStatReset", "badgeExtraBoosts", "drainFloor", "drainHold", "trappingTurns",
|
||||||
"trapMove", "trapDamage", "fainted",
|
"trapMove", "trapDamage", "fainted",
|
||||||
"aiLayer2",
|
"aiLayer2",
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-1
@@ -6,6 +6,9 @@ local FixedStep = {}
|
|||||||
|
|
||||||
FixedStep.STEP = 1 / 60
|
FixedStep.STEP = 1 / 60
|
||||||
local MAX_ACCUM = 0.25 -- avoid spiral of death after a stall
|
local MAX_ACCUM = 0.25 -- avoid spiral of death after a stall
|
||||||
|
local SMOOTH_FRAMES = 4
|
||||||
|
local SMOOTH_MAX = 1 / 60 * 2.5
|
||||||
|
local STEP_EPS = 1 / 60 * 0.02
|
||||||
|
|
||||||
-- Phase the accumulator is re-seeded with once an absorbed hitch frame has
|
-- Phase the accumulator is re-seeded with once an absorbed hitch frame has
|
||||||
-- been paid for. Half a step is the balanced point: a frame has to come in
|
-- been paid for. Half a step is the balanced point: a frame has to come in
|
||||||
@@ -23,6 +26,7 @@ function FixedStep:init(callback)
|
|||||||
self.accum = 0
|
self.accum = 0
|
||||||
self.callback = callback
|
self.callback = callback
|
||||||
self.suppressCatchup = false
|
self.suppressCatchup = false
|
||||||
|
self.dtHistory, self.dtSum = nil, 0
|
||||||
end
|
end
|
||||||
|
|
||||||
-- The anti-spiral clamp doubles as a steps-per-frame ceiling (0.25s = 15
|
-- The anti-spiral clamp doubles as a steps-per-frame ceiling (0.25s = 15
|
||||||
@@ -39,12 +43,26 @@ function FixedStep:update(dt)
|
|||||||
-- the burst it would otherwise release doesn't play out as a slide.
|
-- the burst it would otherwise release doesn't play out as a slide.
|
||||||
if self.suppressCatchup then
|
if self.suppressCatchup then
|
||||||
self.suppressCatchup = false
|
self.suppressCatchup = false
|
||||||
|
self.dtHistory, self.dtSum = nil, 0
|
||||||
self.accum = self.STEP * RESEED_PHASE
|
self.accum = self.STEP * RESEED_PHASE
|
||||||
self.callback(self.STEP)
|
self.callback(self.STEP)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
if dt > 0 and dt <= SMOOTH_MAX then
|
||||||
|
local hist = self.dtHistory
|
||||||
|
if not hist then hist = {}; self.dtHistory = hist; self.dtSum = 0 end
|
||||||
|
hist[#hist + 1] = dt
|
||||||
|
self.dtSum = self.dtSum + dt
|
||||||
|
if #hist > SMOOTH_FRAMES then
|
||||||
|
self.dtSum = self.dtSum - hist[1]
|
||||||
|
table.remove(hist, 1)
|
||||||
|
end
|
||||||
|
dt = self.dtSum / #hist
|
||||||
|
else
|
||||||
|
self.dtHistory, self.dtSum = nil, 0
|
||||||
|
end
|
||||||
self.accum = math.min(self.accum + dt, self.maxAccum or MAX_ACCUM)
|
self.accum = math.min(self.accum + dt, self.maxAccum or MAX_ACCUM)
|
||||||
while self.accum >= self.STEP do
|
while self.accum >= self.STEP - STEP_EPS do
|
||||||
self.accum = self.accum - self.STEP
|
self.accum = self.accum - self.STEP
|
||||||
self.callback(self.STEP)
|
self.callback(self.STEP)
|
||||||
end
|
end
|
||||||
|
|||||||
+1
-3
@@ -1324,9 +1324,7 @@ function Game:restoreSave(loaded, recovered, opts)
|
|||||||
self:adoptSave(loaded)
|
self:adoptSave(loaded)
|
||||||
-- SaveData.load already attached the standalone options.lua table
|
-- SaveData.load already attached the standalone options.lua table
|
||||||
self:applyOptions(loaded.options)
|
self:applyOptions(loaded.options)
|
||||||
-- saves from before OT/ID stamping: backfill with the player's (after
|
-- saves from before OT/ID stamping: backfill with the player's
|
||||||
-- the scrub, so every mon the stamp loop sees is known)
|
|
||||||
SaveData.repairTradedOtIds(loaded)
|
|
||||||
local stamp = require("src.battle.BattleState").stampOT
|
local stamp = require("src.battle.BattleState").stampOT
|
||||||
for _, mon in ipairs(loaded.party or {}) do stamp(loaded, mon) end
|
for _, mon in ipairs(loaded.party or {}) do stamp(loaded, mon) end
|
||||||
for _, box in ipairs(loaded.boxes or {}) do
|
for _, box in ipairs(loaded.boxes or {}) do
|
||||||
|
|||||||
@@ -350,6 +350,10 @@ function Game2:showTitle()
|
|||||||
onContinue = function()
|
onContinue = function()
|
||||||
self:showMainMenu()
|
self:showMainMenu()
|
||||||
end,
|
end,
|
||||||
|
-- engine/menus/intro_menu.asm:848-889
|
||||||
|
onTimeout = function()
|
||||||
|
self:showCopyright()
|
||||||
|
end,
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1983,6 +1983,11 @@ SaveData.addCoreMigration(3, function(save)
|
|||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
-- #1461 repair, one-shot on pre-format-5 saves
|
||||||
|
SaveData.addCoreMigration(4, function(save)
|
||||||
|
SaveData.repairTradedOtIds(save)
|
||||||
|
end)
|
||||||
|
|
||||||
-- ------- write
|
-- ------- write
|
||||||
|
|
||||||
-- Game progress only; options are written separately via saveOptions.
|
-- Game progress only; options are written separately via saveOptions.
|
||||||
@@ -2394,8 +2399,8 @@ function SaveData.applyPostGameHome(save, boot)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- 0.1.82-0.1.9x loads stamped the player's own id onto traded mons and saved
|
-- 0.1.82-0.1.9x loads stamped the player's own id onto traded mons and saved
|
||||||
-- it, which reads back as home-caught. A caught mon can never carry
|
-- it, which reads back as home-caught. Run only as the pre-format-5
|
||||||
-- traded=true, so clearing that pair is safe on every load. #1461
|
-- migration: a same-id trade partner makes that pair legitimate. #1461
|
||||||
function SaveData.repairTradedOtIds(save)
|
function SaveData.repairTradedOtIds(save)
|
||||||
local playerId = save and save.player and save.player.id
|
local playerId = save and save.player and save.player.id
|
||||||
if playerId == nil then return 0 end
|
if playerId == nil then return 0 end
|
||||||
|
|||||||
@@ -217,10 +217,6 @@ function TouchControls.defaultLayout(ww, wh, ox, oy, scale)
|
|||||||
local abW = dpadW * 0.46
|
local abW = dpadW * 0.46
|
||||||
local ssW = dpadW * 0.30
|
local ssW = dpadW * 0.30
|
||||||
local margin = dpadW * 0.12
|
local margin = dpadW * 0.12
|
||||||
local ok, GameVersion = pcall(require, "src.core.GameVersion")
|
|
||||||
if ok and GameVersion.generation and GameVersion.generation() == 2 then
|
|
||||||
margin = math.max(margin, math.min(ww * 0.10, 72))
|
|
||||||
end
|
|
||||||
return {
|
return {
|
||||||
dpad = { cx = ox + margin + dpadW / 2, cy = oy + wh - margin - dpadW / 2, w = dpadW },
|
dpad = { cx = ox + margin + dpadW / 2, cy = oy + wh - margin - dpadW / 2, w = dpadW },
|
||||||
a = { cx = ox + ww - margin - abW * 0.55, cy = oy + wh - margin - abW * 1.75, w = abW },
|
a = { cx = ox + ww - margin - abW * 0.55, cy = oy + wh - margin - abW * 1.75, w = abW },
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ local Version = {
|
|||||||
-- exceeds the shell it provides.
|
-- exceeds the shell it provides.
|
||||||
modApi = 2, -- mod API major (manifest `api`)
|
modApi = 2, -- mod API major (manifest `api`)
|
||||||
linkProtocol = 2, -- link handshake wire version (Handshake.PROTOCOL)
|
linkProtocol = 2, -- link handshake wire version (Handshake.PROTOCOL)
|
||||||
saveFormat = 4, -- save.meta.format
|
saveFormat = 5, -- save.meta.format
|
||||||
cache = "rom-cache-v5", -- ROM import cache generation (RomImporter marker)
|
cache = "rom-cache-v5", -- ROM import cache generation (RomImporter marker)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -89,10 +89,12 @@ local function handle(cmd)
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local idleWait = false
|
||||||
|
|
||||||
while true do
|
while true do
|
||||||
-- drain every pending command first, so a stop/new-play is seen promptly
|
-- drain every pending command first, so a stop/new-play is seen promptly
|
||||||
local quit = false
|
local quit = false
|
||||||
local cmd = cmdCh:pop()
|
local cmd = idleWait and cmdCh:demand(0.05) or cmdCh:pop()
|
||||||
while cmd do
|
while cmd do
|
||||||
if handle(cmd) then quit = true end
|
if handle(cmd) then quit = true end
|
||||||
cmd = cmdCh:pop()
|
cmd = cmdCh:pop()
|
||||||
@@ -100,6 +102,7 @@ while true do
|
|||||||
if quit then break end
|
if quit then break end
|
||||||
|
|
||||||
if engine and not finished and gen and outCh:getCount() < LOOKAHEAD then
|
if engine and not finished and gen and outCh:getCount() < LOOKAHEAD then
|
||||||
|
idleWait = false
|
||||||
local activeGen = gen
|
local activeGen = gen
|
||||||
local ok, sd = pcall(ChipSynth.soundData, engine, BUF, 2)
|
local ok, sd = pcall(ChipSynth.soundData, engine, BUF, 2)
|
||||||
if not ok then
|
if not ok then
|
||||||
@@ -113,8 +116,10 @@ while true do
|
|||||||
finished = true
|
finished = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
elseif engine and not finished and gen then
|
||||||
|
idleWait = false
|
||||||
|
love.timer.sleep(0.005)
|
||||||
else
|
else
|
||||||
-- nothing to do (idle, or the look-ahead is full): yield the core
|
idleWait = true
|
||||||
love.timer.sleep(0.001)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -164,14 +164,12 @@ local function receiveItemToPc(save, id, data)
|
|||||||
save.pcItems = save.pcItems or {}
|
save.pcItems = save.pcItems or {}
|
||||||
local pc = save.pcItems
|
local pc = save.pcItems
|
||||||
local held = pc[id] or 0
|
local held = pc[id] or 0
|
||||||
if held == 0 then
|
local cap = (data and data.field and data.field.pcItemCap) or PC_ITEM_CAPACITY
|
||||||
local cap = (data and data.field and data.field.pcItemCap) or PC_ITEM_CAPACITY
|
local function stacksFor(n) return math.ceil((n or 0) / MAX_STACK) end
|
||||||
local stacks = 0
|
local used = 0
|
||||||
for _ in pairs(pc) do stacks = stacks + 1 end
|
for _, count in pairs(pc) do used = used + stacksFor(count) end
|
||||||
if stacks >= cap then return false end
|
-- engine/items/items.asm:156 PutItemInPocket
|
||||||
elseif held + 1 > MAX_STACK then
|
if used + stacksFor(held + 1) - stacksFor(held) > cap then return false end
|
||||||
return false
|
|
||||||
end
|
|
||||||
pc[id] = held + 1
|
pc[id] = held + 1
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -343,6 +343,7 @@ Save.OPTIONS_KEY = "gold"
|
|||||||
|
|
||||||
local SHARED_KEYS = {
|
local SHARED_KEYS = {
|
||||||
touchControls = true, haptics = true, screenPos = true,
|
touchControls = true, haptics = true, screenPos = true,
|
||||||
|
videoMode = true,
|
||||||
mods = true, modsByVersion = true, modsGen2 = true,
|
mods = true, modsByVersion = true, modsGen2 = true,
|
||||||
modOptions = true, modProfiles = true, modProfilesSeeded = true,
|
modOptions = true, modProfiles = true, modProfilesSeeded = true,
|
||||||
activeProfile = true,
|
activeProfile = true,
|
||||||
@@ -361,12 +362,9 @@ function Save.loadOptions(fs)
|
|||||||
end
|
end
|
||||||
if type(loaded) == "table" then
|
if type(loaded) == "table" then
|
||||||
for key in pairs(SHARED_KEYS) do
|
for key in pairs(SHARED_KEYS) do
|
||||||
if loaded[key] ~= nil then options[key] = loaded[key] end
|
if loaded[key] ~= nil then
|
||||||
end
|
options[key] = loaded[key]
|
||||||
end
|
elseif type(stored) == "table" and stored[key] ~= nil then
|
||||||
if type(stored) == "table" then
|
|
||||||
for key in pairs(SHARED_KEYS) do
|
|
||||||
if options[key] == nil and stored[key] ~= nil then
|
|
||||||
options[key] = stored[key]
|
options[key] = stored[key]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ local CacheContract = {}
|
|||||||
|
|
||||||
CacheContract.FORMAT = "rom-cache-v10:"
|
CacheContract.FORMAT = "rom-cache-v10:"
|
||||||
CacheContract.VERSION_FORMAT = {
|
CacheContract.VERSION_FORMAT = {
|
||||||
crystal = "rom-cache-v10-crystal2:",
|
crystal = "rom-cache-v10-crystal3:",
|
||||||
}
|
}
|
||||||
CacheContract.MARKER_PATH = "rom-cache.complete"
|
CacheContract.MARKER_PATH = "rom-cache.complete"
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,11 @@ local SaveData = require("src.core.SaveData")
|
|||||||
|
|
||||||
local LauncherSettings = {}
|
local LauncherSettings = {}
|
||||||
|
|
||||||
|
local function bgLocked(opts)
|
||||||
|
return opts.battleLayout == "wide" and opts.battleFit == "fill"
|
||||||
|
and opts.battleHud == "extended"
|
||||||
|
end
|
||||||
|
|
||||||
local function wrapIndex(i, n)
|
local function wrapIndex(i, n)
|
||||||
i = i % n
|
i = i % n
|
||||||
if i < 0 then i = i + n end
|
if i < 0 then i = i + n end
|
||||||
@@ -156,8 +161,6 @@ local function coreRows(opts, hooks)
|
|||||||
opts.battleLayout = opts.battleLayout == "wide" and "og" or "wide"
|
opts.battleLayout = opts.battleLayout == "wide" and "og" or "wide"
|
||||||
if opts.battleLayout ~= "wide" then
|
if opts.battleLayout ~= "wide" then
|
||||||
opts.battleHud = "standard"
|
opts.battleHud = "standard"
|
||||||
elseif opts.battleFit == "fill" and opts.battleHud == "extended" then
|
|
||||||
opts.battleBg = "white"
|
|
||||||
end
|
end
|
||||||
return true
|
return true
|
||||||
end)
|
end)
|
||||||
@@ -167,10 +170,6 @@ local function coreRows(opts, hooks)
|
|||||||
end,
|
end,
|
||||||
function()
|
function()
|
||||||
opts.battleFit = opts.battleFit == "fill" and "fixed" or "fill"
|
opts.battleFit = opts.battleFit == "fill" and "fixed" or "fill"
|
||||||
if opts.battleFit == "fill" and opts.battleLayout == "wide"
|
|
||||||
and opts.battleHud == "extended" then
|
|
||||||
opts.battleBg = "white"
|
|
||||||
end
|
|
||||||
return true
|
return true
|
||||||
end)
|
end)
|
||||||
add(Strings("BATTLE HUD"),
|
add(Strings("BATTLE HUD"),
|
||||||
@@ -185,28 +184,17 @@ local function coreRows(opts, hooks)
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
opts.battleHud = opts.battleHud == "extended" and "standard" or "extended"
|
opts.battleHud = opts.battleHud == "extended" and "standard" or "extended"
|
||||||
if opts.battleHud == "extended" and opts.battleFit == "fill" then
|
|
||||||
opts.battleBg = "white"
|
|
||||||
end
|
|
||||||
return true
|
return true
|
||||||
end)
|
end)
|
||||||
add(Strings("BATTLE BG"),
|
add(Strings("BATTLE BG"),
|
||||||
function()
|
function()
|
||||||
if opts.battleLayout == "wide" and opts.battleFit == "fill"
|
if bgLocked(opts) then return Strings("AUTO (FILL HUD)") end
|
||||||
and opts.battleHud == "extended" then
|
|
||||||
opts.battleBg = "white"
|
|
||||||
return Strings("AUTO")
|
|
||||||
end
|
|
||||||
if opts.battleBg == "black" then return Strings("BLACK") end
|
if opts.battleBg == "black" then return Strings("BLACK") end
|
||||||
if opts.battleBg == "world" then return Strings("WORLD") end
|
if opts.battleBg == "world" then return Strings("WORLD") end
|
||||||
return Strings("WHITE")
|
return Strings("WHITE")
|
||||||
end,
|
end,
|
||||||
function(dir)
|
function(dir)
|
||||||
if opts.battleLayout == "wide" and opts.battleFit == "fill"
|
if bgLocked(opts) then return false end
|
||||||
and opts.battleHud == "extended" then
|
|
||||||
opts.battleBg = "white"
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
local order = { "white", "black", "world" }
|
local order = { "white", "black", "world" }
|
||||||
local cur = 1
|
local cur = 1
|
||||||
for i, mode in ipairs(order) do
|
for i, mode in ipairs(order) do
|
||||||
@@ -563,7 +551,7 @@ end
|
|||||||
-- src/ui/gen2/OptionsMenu.lua's ROWS; when editing one, keep the two in sync.
|
-- src/ui/gen2/OptionsMenu.lua's ROWS; when editing one, keep the two in sync.
|
||||||
local GEN2_KEY = "gold"
|
local GEN2_KEY = "gold"
|
||||||
|
|
||||||
local function gen2Rows(opts, hooks)
|
local function gen2Rows(opts, hooks, shared)
|
||||||
local rows = {}
|
local rows = {}
|
||||||
local function add(label, value, step)
|
local function add(label, value, step)
|
||||||
rows[#rows + 1] = { label = label, value = value, step = step }
|
rows[#rows + 1] = { label = label, value = value, step = step }
|
||||||
@@ -669,9 +657,9 @@ local function gen2Rows(opts, hooks)
|
|||||||
local okVm, VideoMode = pcall(require, "src.core.VideoMode")
|
local okVm, VideoMode = pcall(require, "src.core.VideoMode")
|
||||||
if okVm then
|
if okVm then
|
||||||
add(Strings("VIDEO MODE"),
|
add(Strings("VIDEO MODE"),
|
||||||
function() return VideoMode.modeLabel(opts.videoMode) end,
|
function() return VideoMode.modeLabel(shared.videoMode) end,
|
||||||
function(dir)
|
function(dir)
|
||||||
opts.videoMode = VideoMode.cycle(opts.videoMode, dir)
|
shared.videoMode = VideoMode.cycle(shared.videoMode, dir)
|
||||||
return true
|
return true
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
@@ -690,7 +678,7 @@ local function gen2Rows(opts, hooks)
|
|||||||
add(Strings("BATTLE BG"), ladder(opts, "battleBg",
|
add(Strings("BATTLE BG"), ladder(opts, "battleBg",
|
||||||
{ { "white", "WHITE" }, { "black", "BLACK" } }, "white"))
|
{ { "white", "WHITE" }, { "black", "BLACK" } }, "white"))
|
||||||
|
|
||||||
addTouchRows(rows, add, opts, hooks)
|
addTouchRows(rows, add, shared, hooks)
|
||||||
|
|
||||||
return rows
|
return rows
|
||||||
end
|
end
|
||||||
@@ -718,7 +706,7 @@ function LauncherSettings.open(hooks, version)
|
|||||||
opts[GEN2_KEY] = block
|
opts[GEN2_KEY] = block
|
||||||
end
|
end
|
||||||
sections = {
|
sections = {
|
||||||
{ title = Strings("OPTIONS"), rows = gen2Rows(block, hooks) },
|
{ title = Strings("OPTIONS"), rows = gen2Rows(block, hooks, opts) },
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
sections = {
|
sections = {
|
||||||
|
|||||||
@@ -151,6 +151,19 @@ local TEXT_BUFFERS = {
|
|||||||
[0xc602] = "wOTTrademonSpeciesName",
|
[0xc602] = "wOTTrademonSpeciesName",
|
||||||
[0xc618] = "wOTTrademonSenderName",
|
[0xc618] = "wOTTrademonSenderName",
|
||||||
}
|
}
|
||||||
|
-- ../pokecrystal/ram/wram.asm:1925,2333
|
||||||
|
local TEXT_BUFFERS_CRYSTAL = {
|
||||||
|
[0xd050] = "wMonOrItemNameBuffer",
|
||||||
|
[0xd073] = "wStringBuffer1",
|
||||||
|
[0xd086] = "wStringBuffer2",
|
||||||
|
[0xd099] = "wStringBuffer3",
|
||||||
|
[0xd0ac] = "wStringBuffer4",
|
||||||
|
[0xd0bf] = "wStringBuffer5",
|
||||||
|
[0xc6d1] = "wPlayerTrademonSpeciesName",
|
||||||
|
[0xc6e7] = "wPlayerTrademonSenderName",
|
||||||
|
[0xc703] = "wOTTrademonSpeciesName",
|
||||||
|
[0xc719] = "wOTTrademonSenderName",
|
||||||
|
}
|
||||||
-- The text commands that print nothing and carry no argument
|
-- The text commands that print nothing and carry no argument
|
||||||
-- (macros/scripts/text.asm, in TextCommands order): TX_LOW, TX_SCROLL,
|
-- (macros/scripts/text.asm, in TextCommands order): TX_LOW, TX_SCROLL,
|
||||||
-- TX_PAUSE, TX_WAIT_BUTTON, TX_DAY, and the six TX_SOUND_* jingles.
|
-- TX_PAUSE, TX_WAIT_BUTTON, TX_DAY, and the six TX_SOUND_* jingles.
|
||||||
@@ -2295,6 +2308,8 @@ function RomExtractorGen2:extractTitle()
|
|||||||
trailBobAmplitude = silver and 3 or 2,
|
trailBobAmplitude = silver and 3 or 2,
|
||||||
trailPhaseStep = silver and 7 or 3,
|
trailPhaseStep = silver and 7 or 3,
|
||||||
trailPhase = silver and 0 or nil,
|
trailPhase = silver and 0 or nil,
|
||||||
|
-- TitleScreenTimer (engine/menus/intro_menu.asm:951-966).
|
||||||
|
timeoutFrames = silver and (73 * 60 + 36) or (84 * 60 + 16),
|
||||||
}
|
}
|
||||||
self:write("title", data)
|
self:write("title", data)
|
||||||
return data
|
return data
|
||||||
@@ -2805,6 +2820,8 @@ end
|
|||||||
-- is which alongside the text lets a caller fill them in order without
|
-- is which alongside the text lets a caller fill them in order without
|
||||||
-- changing a marker every screen already reads.
|
-- changing a marker every screen already reads.
|
||||||
function RomExtractorGen2:decodeGen2Text(bank, address, charmap, buffers)
|
function RomExtractorGen2:decodeGen2Text(bank, address, charmap, buffers)
|
||||||
|
local textBuffers = (self.edition == "crystal") and TEXT_BUFFERS_CRYSTAL
|
||||||
|
or TEXT_BUFFERS
|
||||||
local out = {}
|
local out = {}
|
||||||
local i = 0
|
local i = 0
|
||||||
local hops = 0
|
local hops = 0
|
||||||
@@ -2840,7 +2857,7 @@ function RomExtractorGen2:decodeGen2Text(bank, address, charmap, buffers)
|
|||||||
out[#out + 1] = "{STRBUF}"
|
out[#out + 1] = "{STRBUF}"
|
||||||
if buffers then
|
if buffers then
|
||||||
local target = self.rom:word(bank, address + i + 1)
|
local target = self.rom:word(bank, address + i + 1)
|
||||||
buffers[#buffers + 1] = TEXT_BUFFERS[target] or target
|
buffers[#buffers + 1] = textBuffers[target] or target
|
||||||
end
|
end
|
||||||
i = i + 2
|
i = i + 2
|
||||||
elseif b == 0x4e or b == 0x4f then
|
elseif b == 0x4e or b == 0x4f then
|
||||||
|
|||||||
@@ -310,6 +310,11 @@ function ItemEffects.use(data, save, itemId, target, battle, moveIndex, ow)
|
|||||||
"Nothing happened!") }
|
"Nothing happened!") }
|
||||||
end
|
end
|
||||||
b.stages[stat] = cur + 1
|
b.stages[stat] = cur + 1
|
||||||
|
b.hazeStatReset = nil
|
||||||
|
if battle.ruleset and battle.ruleset.badgeBoostReapplyBug
|
||||||
|
and battle.kind ~= "link" then
|
||||||
|
require("src.battle.Damage").reapplyBadgeBoosts(b, stat)
|
||||||
|
end
|
||||||
return "consumed", { Strings("%s's\n%s rose!", b.name, Strings(STAT_LABEL[stat])) }
|
return "consumed", { Strings("%s's\n%s rose!", b.name, Strings(STAT_LABEL[stat])) }
|
||||||
end
|
end
|
||||||
-- ItemUseDireHit/ItemUseGuardSpec always set the bit and consume
|
-- ItemUseDireHit/ItemUseGuardSpec always set the bit and consume
|
||||||
|
|||||||
@@ -817,7 +817,7 @@ function Renderer:frameRects()
|
|||||||
-- GB pixel stops being a whole number of screen pixels, which is the trade
|
-- GB pixel stops being a whole number of screen pixels, which is the trade
|
||||||
-- the setting exists to offer. Clamped on the horizontal too, so a narrow
|
-- the setting exists to offer. Clamped on the horizontal too, so a narrow
|
||||||
-- window scales to fit instead of overflowing off both sides.
|
-- window scales to fit instead of overflowing off both sides.
|
||||||
if self.uiFill then
|
if self.uiFill and not FaithfulRes.scaleCap() then
|
||||||
Up = math.min(ph / uih, pw / uiw)
|
Up = math.min(ph / uih, pw / uiw)
|
||||||
end
|
end
|
||||||
if uiw * Up > pw or uih * Up > ph then
|
if uiw * Up > pw or uih * Up > ph then
|
||||||
|
|||||||
+24
-5
@@ -529,6 +529,16 @@ function Commands.set_field(ctx, key, value)
|
|||||||
ctx.save[key] = value
|
ctx.save[key] = value
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- scripts/ChampionsRoom.asm:57
|
||||||
|
function Commands.set_option(ctx, key, value)
|
||||||
|
local o = ctx.save.options
|
||||||
|
if not o then
|
||||||
|
o = {}
|
||||||
|
ctx.save.options = o
|
||||||
|
end
|
||||||
|
o[key] = value
|
||||||
|
end
|
||||||
|
|
||||||
function Commands.load_player_starter_name(ctx)
|
function Commands.load_player_starter_name(ctx)
|
||||||
local flags = ctx.save.flags or {}
|
local flags = ctx.save.flags or {}
|
||||||
local species = flags.EVENT_CHOSE_PIKACHU and "PIKACHU"
|
local species = flags.EVENT_CHOSE_PIKACHU and "PIKACHU"
|
||||||
@@ -720,7 +730,8 @@ end
|
|||||||
-- Box deposits also print SentToBoxText (give_pokemon.asm:36-37).
|
-- Box deposits also print SentToBoxText (give_pokemon.asm:36-37).
|
||||||
-- skipNickname suppresses AskName for callers that name the gift themselves;
|
-- skipNickname suppresses AskName for callers that name the gift themselves;
|
||||||
-- no vanilla script uses it (pokeyellow scripts/OaksLab.asm, #1013)
|
-- no vanilla script uses it (pokeyellow scripts/OaksLab.asm, #1013)
|
||||||
function Commands.give_pokemon(ctx, species, level, skipNickname)
|
-- gotText prints GotMonText ahead of AskName (give_pokemon.asm:46).
|
||||||
|
function Commands.give_pokemon(ctx, species, level, skipNickname, gotText)
|
||||||
-- Native mods can transform a gift before the Pokémon object is created.
|
-- Native mods can transform a gift before the Pokémon object is created.
|
||||||
-- This is intentionally an event rather than a special-case starter hook:
|
-- This is intentionally an event rather than a special-case starter hook:
|
||||||
-- mods can use the same seam for story gifts, fossils, or custom scripts.
|
-- mods can use the same seam for story gifts, fossils, or custom scripts.
|
||||||
@@ -754,6 +765,11 @@ function Commands.give_pokemon(ctx, species, level, skipNickname)
|
|||||||
ctx.lastCheck = true
|
ctx.lastCheck = true
|
||||||
ctx.addedToParty = addedToParty
|
ctx.addedToParty = addedToParty
|
||||||
ctx.boxNum = boxNum
|
ctx.boxNum = boxNum
|
||||||
|
-- engine/events/give_pokemon.asm:46
|
||||||
|
if gotText and ctx.runner then
|
||||||
|
Commands.text_sound(ctx, "Get_Item1")
|
||||||
|
Commands.show_text(ctx, "_GotMonText", { RAM = species })
|
||||||
|
end
|
||||||
-- AskName: both AddPartyMon and SendNewMonToBox; skip mod-set nicks
|
-- AskName: both AddPartyMon and SendNewMonToBox; skip mod-set nicks
|
||||||
-- and callback-style callers with no script runner to yield on.
|
-- and callback-style callers with no script runner to yield on.
|
||||||
if not gift.nickname and not skipNickname and ctx.runner then
|
if not gift.nickname and not skipNickname and ctx.runner then
|
||||||
@@ -1303,11 +1319,14 @@ function FadeOverlay:update()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function FadeOverlay:draw()
|
function FadeOverlay:draw()
|
||||||
if self.color == "white" then
|
local shade = (self.color == "white") and 1 or 0
|
||||||
love.graphics.setColor(1, 1, 1, self.alpha)
|
-- home/fade.asm:26
|
||||||
else
|
local r = self.game and self.game.renderer
|
||||||
love.graphics.setColor(0, 0, 0, self.alpha)
|
if r then
|
||||||
|
r.screenVeil = { shade, self.alpha }
|
||||||
|
return
|
||||||
end
|
end
|
||||||
|
love.graphics.setColor(shade, shade, shade, self.alpha)
|
||||||
love.graphics.rectangle("fill", 0, 0, 160, 144)
|
love.graphics.rectangle("fill", 0, 0, 160, 144)
|
||||||
love.graphics.setColor(1, 1, 1, 1)
|
love.graphics.setColor(1, 1, 1, 1)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -120,6 +120,67 @@ CallAsm.SITES = {
|
|||||||
["04:66d1"] = "TryReceiveItem",
|
["04:66d1"] = "TryReceiveItem",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-- pokegold-symbols/pokesilver.sym: bank $03 sits two bytes earlier in Silver.
|
||||||
|
CallAsm.SITES_SILVER = {
|
||||||
|
["03:4749"] = "GetPartyNickname",
|
||||||
|
["03:4853"] = "CutDownTreeOrGrass",
|
||||||
|
["03:4b47"] = "CheckContinueWaterfall",
|
||||||
|
["03:4d13"] = "SetStrengthFlag",
|
||||||
|
["03:4d79"] = "TryStrengthOW",
|
||||||
|
["03:4e1e"] = "DisappearWhirlpool",
|
||||||
|
["03:4f7d"] = "HasRockSmash",
|
||||||
|
["03:5094"] = "PutTheRodAway",
|
||||||
|
["03:506b"] = "Fishing_CheckFacingUp",
|
||||||
|
["03:51c5"] = "AskCutScript_CheckMap",
|
||||||
|
}
|
||||||
|
|
||||||
|
-- pokecrystal-symbols/pokecrystal.sym: Crystal's own addresses for the same sites.
|
||||||
|
CallAsm.SITES_CRYSTAL = {
|
||||||
|
["25:6f76"] = "GiveItemScript_DummyFunction",
|
||||||
|
["04:65cd"] = "StartMenu",
|
||||||
|
["04:7327"] = "SelectMenu",
|
||||||
|
["05:6f5e"] = "OverworldHatchEgg",
|
||||||
|
["25:6706"] = "EnableWildEncounters",
|
||||||
|
["24:426f"] = "RingTwice_StartCall",
|
||||||
|
["24:42eb"] = "HangUp",
|
||||||
|
["04:53e5"] = "InitCallReceiveDelay",
|
||||||
|
["24:425c"] = "LoadBillScript",
|
||||||
|
["24:426a"] = "LoadElmScript",
|
||||||
|
["3f:5017"] = "MomTriesToBuySomething_ASMFunction",
|
||||||
|
["04:6599"] = "ItemfinderSound",
|
||||||
|
["14:46ef"] = "SweetScentEncounter",
|
||||||
|
["02:431e"] = "TrainerWalkToPlayer",
|
||||||
|
["14:4753"] = "CheckCanUseSquirtbottle",
|
||||||
|
["04:764f"] = "SetMemEvent",
|
||||||
|
["14:4658"] = "PlayPoisonSFX",
|
||||||
|
["14:467b"] = "CheckWhitedOut",
|
||||||
|
["04:64fa"] = "OverworldBGMap",
|
||||||
|
["04:650a"] = "BattleBGMap",
|
||||||
|
["04:6513"] = "HalveMoney",
|
||||||
|
["04:6527"] = "GetWhiteoutSpawn",
|
||||||
|
["03:4706"] = "GetPartyNickname",
|
||||||
|
["03:4810"] = "CutDownTreeOrGrass",
|
||||||
|
["23:47e1"] = "BlindingFlash",
|
||||||
|
["00:3016"] = "HideSprites",
|
||||||
|
["23:4aed"] = "FlyFromAnim",
|
||||||
|
["05:54f1"] = "SkipUpdateMapSprites",
|
||||||
|
["23:4b33"] = "FlyToAnim",
|
||||||
|
["05:4157"] = "LoadWalkingSpritesGFX",
|
||||||
|
["03:4b38"] = "CheckContinueWaterfall",
|
||||||
|
["03:4d12"] = "SetStrengthFlag",
|
||||||
|
["03:4d78"] = "TryStrengthOW",
|
||||||
|
["03:4e1d"] = "DisappearWhirlpool",
|
||||||
|
["23:480a"] = "ShakeHeadbuttTree",
|
||||||
|
["03:4f7c"] = "HasRockSmash",
|
||||||
|
["03:5095"] = "PutTheRodAway",
|
||||||
|
["03:506c"] = "Fishing_CheckFacingUp",
|
||||||
|
["2e:44b3"] = "LoadFishingGFX",
|
||||||
|
["03:51ba"] = "AskCutScript_CheckMap",
|
||||||
|
["2e:41ea"] = "TreeMonEncounter",
|
||||||
|
["2e:4219"] = "RockMonEncounter",
|
||||||
|
["04:62f8"] = "TryReceiveItem",
|
||||||
|
}
|
||||||
|
|
||||||
-- The three WRAM addresses `memcall` / `memcallasm` / `memjump` take instead of
|
-- The three WRAM addresses `memcall` / `memcallasm` / `memjump` take instead of
|
||||||
-- a routine. Not sites: the pointer AT the address is written at run time
|
-- a routine. Not sites: the pointer AT the address is written at run time
|
||||||
-- (LoadMemScript for the queued script, the phone engine for the other two),
|
-- (LoadMemScript for the queued script, the phone engine for the other two),
|
||||||
@@ -714,7 +775,8 @@ end
|
|||||||
-- at import time), and the address pair is the fallback that always works.
|
-- at import time), and the address pair is the fallback that always works.
|
||||||
function CallAsm.nameFor(label, bank, addr)
|
function CallAsm.nameFor(label, bank, addr)
|
||||||
if label and CallAsm.ALL[label] then return label end
|
if label and CallAsm.ALL[label] then return label end
|
||||||
return CallAsm.SITES[CallAsm.key(bank, addr)]
|
local key = CallAsm.key(bank, addr)
|
||||||
|
return CallAsm.SITES[key] or CallAsm.SITES_SILVER[key] or CallAsm.SITES_CRYSTAL[key]
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Run a routine by name. Returns the byte the asm leaves in wScriptVar, or
|
-- Run a routine by name. Returns the byte the asm leaves in wScriptVar, or
|
||||||
|
|||||||
+35
-14
@@ -8,8 +8,13 @@ local Assets = require("src.render.Assets")
|
|||||||
local Font = require("src.render.Font")
|
local Font = require("src.render.Font")
|
||||||
local PaletteFX = require("src.render.PaletteFX")
|
local PaletteFX = require("src.render.PaletteFX")
|
||||||
local Sprites = require("src.pokemon.Sprites")
|
local Sprites = require("src.pokemon.Sprites")
|
||||||
|
local SpriteRenderer = require("src.render.SpriteRenderer")
|
||||||
local Strings = require("src.core.Strings")
|
local Strings = require("src.core.Strings")
|
||||||
|
|
||||||
|
-- engine/events/diploma.asm:65
|
||||||
|
local OBP0_90 = { { 255, 255, 255 }, { 255, 255, 255 },
|
||||||
|
{ 170, 170, 170 }, { 85, 85, 85 } }
|
||||||
|
|
||||||
local Diploma = {}
|
local Diploma = {}
|
||||||
Diploma.__index = Diploma
|
Diploma.__index = Diploma
|
||||||
Diploma.isOpaque = true
|
Diploma.isOpaque = true
|
||||||
@@ -49,20 +54,18 @@ local function drawFrameBox(frame, tx, ty, tw, th)
|
|||||||
local img = frame.img
|
local img = frame.img
|
||||||
local q = frame.quads
|
local q = frame.quads
|
||||||
love.graphics.setColor(1, 1, 1, 1)
|
love.graphics.setColor(1, 1, 1, 1)
|
||||||
-- corners
|
-- engine/link/cable_club.asm:937
|
||||||
love.graphics.draw(img, q[0], tx * 8, ty * 8)
|
love.graphics.draw(img, q[2], tx * 8, ty * 8)
|
||||||
love.graphics.draw(img, q[2], (tx + tw - 1) * 8, ty * 8)
|
love.graphics.draw(img, q[4], (tx + tw - 1) * 8, ty * 8)
|
||||||
love.graphics.draw(img, q[6], tx * 8, (ty + th - 1) * 8)
|
love.graphics.draw(img, q[6], tx * 8, (ty + th - 1) * 8)
|
||||||
love.graphics.draw(img, q[8], (tx + tw - 1) * 8, (ty + th - 1) * 8)
|
love.graphics.draw(img, q[7], (tx + tw - 1) * 8, (ty + th - 1) * 8)
|
||||||
-- horizontal edges
|
|
||||||
for x = 1, tw - 2 do
|
for x = 1, tw - 2 do
|
||||||
love.graphics.draw(img, q[1], (tx + x) * 8, ty * 8)
|
love.graphics.draw(img, q[3], (tx + x) * 8, ty * 8)
|
||||||
love.graphics.draw(img, q[7], (tx + x) * 8, (ty + th - 1) * 8)
|
love.graphics.draw(img, q[0], (tx + x) * 8, (ty + th - 1) * 8)
|
||||||
end
|
end
|
||||||
-- vertical edges
|
|
||||||
for y = 1, th - 2 do
|
for y = 1, th - 2 do
|
||||||
love.graphics.draw(img, q[3], tx * 8, (ty + y) * 8)
|
love.graphics.draw(img, q[5], tx * 8, (ty + y) * 8)
|
||||||
love.graphics.draw(img, q[5], (tx + tw - 1) * 8, (ty + y) * 8)
|
love.graphics.draw(img, q[1], (tx + tw - 1) * 8, (ty + y) * 8)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -91,13 +94,31 @@ function Diploma.render(game)
|
|||||||
drawFrameBox(frame, 0, 0, 20, 18)
|
drawFrameBox(frame, 0, 0, 20, 18)
|
||||||
|
|
||||||
-- 2. Draw Player character sprite: farcall DrawPlayerCharacter
|
-- 2. Draw Player character sprite: farcall DrawPlayerCharacter
|
||||||
-- Shifted +33 px right from title screen base (82 + 33 = 115, y = 80)
|
-- engine/movie/title.asm:321
|
||||||
local picPath, picTrueColor = Sprites.playerPath(
|
local title = (game.data and game.data.field and game.data.field.title) or {}
|
||||||
game.data, "front", { kind = "diploma" })
|
local titlePlayer = title.player
|
||||||
local pic = tryImage(picPath)
|
if type(titlePlayer) == "table" then titlePlayer = titlePlayer.path end
|
||||||
|
local picPath, picTrueColor = Sprites.playerPic(
|
||||||
|
titlePlayer or "assets/generated/title/player.png",
|
||||||
|
{ side = "front", kind = "diploma", data = game.data })
|
||||||
|
local pic
|
||||||
|
if picPath then
|
||||||
|
if picTrueColor then
|
||||||
|
pic = tryImage(picPath)
|
||||||
|
else
|
||||||
|
local ok, faded = pcall(SpriteRenderer.obpImage, picPath, OBP0_90,
|
||||||
|
"diploma")
|
||||||
|
pic = (ok and faded) or tryImage(picPath)
|
||||||
|
end
|
||||||
|
end
|
||||||
if pic then
|
if pic then
|
||||||
love.graphics.setColor(1, 1, 1, 1)
|
love.graphics.setColor(1, 1, 1, 1)
|
||||||
|
local sx, sy, sw, sh = love.graphics.getScissor()
|
||||||
|
-- engine/events/diploma.asm:44
|
||||||
|
love.graphics.setScissor(8, 8, 144, 128)
|
||||||
love.graphics.draw(pic, 115, 80)
|
love.graphics.draw(pic, 115, 80)
|
||||||
|
if sx then love.graphics.setScissor(sx, sy, sw, sh)
|
||||||
|
else love.graphics.setScissor() end
|
||||||
if picTrueColor then
|
if picTrueColor then
|
||||||
PaletteFX.markTrueColor(115, 80, pic:getDimensions())
|
PaletteFX.markTrueColor(115, 80, pic:getDimensions())
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -95,11 +95,28 @@ function EvolutionState.new(game, mon, newSpecies, onDone, via)
|
|||||||
self.t = 0
|
self.t = 0
|
||||||
self.done = false
|
self.done = false
|
||||||
self.canceled = false
|
self.canceled = false
|
||||||
Music.play(game.data, Music.special(game.data, "evolution"))
|
-- engine/movie/evolution.asm:41-46
|
||||||
|
Music.stop()
|
||||||
|
self.crySrc = require("src.core.Sound").playCry(game.data, mon.species)
|
||||||
|
self.cryT = 0
|
||||||
|
self.cryWait = self.crySrc ~= nil
|
||||||
|
if not self.cryWait then
|
||||||
|
Music.play(game.data, Music.special(game.data, "evolution"))
|
||||||
|
end
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
function EvolutionState:update(dt)
|
function EvolutionState:update(dt)
|
||||||
|
if self.cryWait then
|
||||||
|
self.cryT = self.cryT + 1
|
||||||
|
local src = self.crySrc
|
||||||
|
local playing = src and src.isPlaying and src:isPlaying()
|
||||||
|
if self.cryT >= 3 and (not playing or self.cryT > 180) then
|
||||||
|
self.cryWait, self.crySrc = false, nil
|
||||||
|
Music.play(self.game.data, Music.special(self.game.data, "evolution"))
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
self.t = self.t + 1
|
self.t = self.t + 1
|
||||||
if self.done then return end
|
if self.done then return end
|
||||||
local game = self.game
|
local game = self.game
|
||||||
|
|||||||
+7
-19
@@ -157,6 +157,11 @@ end
|
|||||||
-- new SHADER FX 2 row below it -- when both are set, ShaderFX.render() runs
|
-- new SHADER FX 2 row below it -- when both are set, ShaderFX.render() runs
|
||||||
-- main's chain into secondary's, same as stacking two RetroArch presets.
|
-- main's chain into secondary's, same as stacking two RetroArch presets.
|
||||||
|
|
||||||
|
local function bgLocked(o)
|
||||||
|
return o.battleLayout == "wide" and o.battleFit == "fill"
|
||||||
|
and o.battleHud == "extended"
|
||||||
|
end
|
||||||
|
|
||||||
-- the vanilla rows as descriptors; each step body is the old per-index
|
-- the vanilla rows as descriptors; each step body is the old per-index
|
||||||
-- ladder's, so the save.options mutations are unchanged
|
-- ladder's, so the save.options mutations are unchanged
|
||||||
local function buildRows(game)
|
local function buildRows(game)
|
||||||
@@ -197,8 +202,6 @@ local function buildRows(game)
|
|||||||
o.battleLayout = o.battleLayout == "wide" and "og" or "wide"
|
o.battleLayout = o.battleLayout == "wide" and "og" or "wide"
|
||||||
if o.battleLayout ~= "wide" then
|
if o.battleLayout ~= "wide" then
|
||||||
o.battleHud = "standard"
|
o.battleHud = "standard"
|
||||||
elseif o.battleFit == "fill" and o.battleHud == "extended" then
|
|
||||||
o.battleBg = "white"
|
|
||||||
end
|
end
|
||||||
return true
|
return true
|
||||||
end },
|
end },
|
||||||
@@ -215,10 +218,6 @@ local function buildRows(game)
|
|||||||
step = function(g)
|
step = function(g)
|
||||||
local o = g.save.options
|
local o = g.save.options
|
||||||
o.battleFit = o.battleFit == "fill" and "fixed" or "fill"
|
o.battleFit = o.battleFit == "fill" and "fixed" or "fill"
|
||||||
if o.battleFit == "fill" and o.battleLayout == "wide"
|
|
||||||
and o.battleHud == "extended" then
|
|
||||||
o.battleBg = "white"
|
|
||||||
end
|
|
||||||
return true
|
return true
|
||||||
end },
|
end },
|
||||||
{ id = "battleHud", label = Strings("BATTLE HUD"),
|
{ id = "battleHud", label = Strings("BATTLE HUD"),
|
||||||
@@ -237,9 +236,6 @@ local function buildRows(game)
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
o.battleHud = o.battleHud == "extended" and "standard" or "extended"
|
o.battleHud = o.battleHud == "extended" and "standard" or "extended"
|
||||||
if o.battleHud == "extended" and o.battleFit == "fill" then
|
|
||||||
o.battleBg = "white"
|
|
||||||
end
|
|
||||||
return true
|
return true
|
||||||
end },
|
end },
|
||||||
-- What sits behind and around the battle. WHITE is the classic paper
|
-- What sits behind and around the battle. WHITE is the classic paper
|
||||||
@@ -249,11 +245,7 @@ local function buildRows(game)
|
|||||||
{ id = "battleBg", label = Strings("BATTLE BG"),
|
{ id = "battleBg", label = Strings("BATTLE BG"),
|
||||||
value = function(g)
|
value = function(g)
|
||||||
local o = g.save.options
|
local o = g.save.options
|
||||||
if o.battleLayout == "wide" and o.battleFit == "fill"
|
if bgLocked(o) then return Strings("AUTO (FILL HUD)") end
|
||||||
and o.battleHud == "extended" then
|
|
||||||
o.battleBg = "white"
|
|
||||||
return Strings("AUTO")
|
|
||||||
end
|
|
||||||
local m = o.battleBg
|
local m = o.battleBg
|
||||||
if m == "black" then return Strings("BLACK") end
|
if m == "black" then return Strings("BLACK") end
|
||||||
if m == "world" then return Strings("WORLD") end
|
if m == "world" then return Strings("WORLD") end
|
||||||
@@ -261,11 +253,7 @@ local function buildRows(game)
|
|||||||
end,
|
end,
|
||||||
step = function(g, dir)
|
step = function(g, dir)
|
||||||
local o = g.save.options
|
local o = g.save.options
|
||||||
if o.battleLayout == "wide" and o.battleFit == "fill"
|
if bgLocked(o) then return false end
|
||||||
and o.battleHud == "extended" then
|
|
||||||
o.battleBg = "white"
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
local order = { "white", "black", "world" }
|
local order = { "white", "black", "world" }
|
||||||
local cur = 1
|
local cur = 1
|
||||||
for i, m in ipairs(order) do if o.battleBg == m then cur = i break end end
|
for i, m in ipairs(order) do if o.battleBg == m then cur = i break end end
|
||||||
|
|||||||
+17
-15
@@ -19,17 +19,22 @@ local function itemName(game, id)
|
|||||||
return def and def.name or id
|
return def and def.name or id
|
||||||
end
|
end
|
||||||
|
|
||||||
local function buildItems(game, store)
|
local function buildItems(game, store, order)
|
||||||
local items = {}
|
local items = {}
|
||||||
local ids = {}
|
local ids = order
|
||||||
for id in pairs(store) do table.insert(ids, id) end
|
if not ids then
|
||||||
table.sort(ids)
|
ids = {}
|
||||||
|
for id in pairs(store) do table.insert(ids, id) end
|
||||||
|
table.sort(ids)
|
||||||
|
end
|
||||||
for _, id in ipairs(ids) do
|
for _, id in ipairs(ids) do
|
||||||
table.insert(items, {
|
if store[id] then
|
||||||
value = id,
|
table.insert(items, {
|
||||||
label = itemName(game, id),
|
value = id,
|
||||||
right = "x" .. store[id],
|
label = itemName(game, id),
|
||||||
})
|
right = "x" .. store[id],
|
||||||
|
})
|
||||||
|
end
|
||||||
end
|
end
|
||||||
return items
|
return items
|
||||||
end
|
end
|
||||||
@@ -105,12 +110,9 @@ local function deposit(game)
|
|||||||
local pc = game.save.pcItems
|
local pc = game.save.pcItems
|
||||||
local inv = game.save.inventory
|
local inv = game.save.inventory
|
||||||
local Bag = require("src.inventory.Bag")
|
local Bag = require("src.inventory.Bag")
|
||||||
-- badges live in save.inventory alongside items but are not depositable
|
-- engine/menus/players_pc.asm:99 wListPointer = wNumBagItems, so deposit order == bag order
|
||||||
local depositable = {}
|
local order = Bag.order(game.save, game.data)
|
||||||
for id, count in pairs(inv) do
|
game.stack:push(ListMenu.new(game, "DEPOSIT ITEM", buildItems(game, inv, order), {
|
||||||
if not Bag.isBadge(id) then depositable[id] = count end
|
|
||||||
end
|
|
||||||
game.stack:push(ListMenu.new(game, "DEPOSIT ITEM", buildItems(game, depositable), {
|
|
||||||
kind = "pc_item_deposit",
|
kind = "pc_item_deposit",
|
||||||
messageBox = true,
|
messageBox = true,
|
||||||
noSound = true, -- PlayerPCMenu holds BIT_NO_MENU_BUTTON_SOUND (#570)
|
noSound = true, -- PlayerPCMenu holds BIT_NO_MENU_BUTTON_SOUND (#570)
|
||||||
|
|||||||
@@ -687,8 +687,10 @@ function Studio.deleteEntry(id)
|
|||||||
local opts = SaveData.loadOptions()
|
local opts = SaveData.loadOptions()
|
||||||
local tc = type(opts.touchControls) == "table" and opts.touchControls or {}
|
local tc = type(opts.touchControls) == "table" and opts.touchControls or {}
|
||||||
if tc.skin == id then
|
if tc.skin == id then
|
||||||
tc.enabled, tc.skin = false, nil
|
tc.enabled, tc.skin = true, nil
|
||||||
|
opts.touchControls = tc
|
||||||
SaveData.saveOptions(opts)
|
SaveData.saveOptions(opts)
|
||||||
|
TouchControls:applyOptions(opts)
|
||||||
end
|
end
|
||||||
Studio.refreshAvailable()
|
Studio.refreshAvailable()
|
||||||
setStatus("Deleted " .. id)
|
setStatus("Deleted " .. id)
|
||||||
|
|||||||
+4
-9
@@ -334,10 +334,9 @@ function TownMap:update(dt)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- OG RED bakes the boot-ROM OBJ palette in, so the marker has to be replayed
|
-- OG RED and ADVANCED bake an OBJ palette in, so the marker replays over the TOWNMAP zone pass (#301)
|
||||||
-- over the screen-wide TOWNMAP zone pass the way every other OBJ is (#301)
|
|
||||||
function TownMap:markPlayerRedraw(x, y)
|
function TownMap:markPlayerRedraw(x, y)
|
||||||
if not PaletteFX.usesSpriteObp() then return end
|
if not (PaletteFX.usesSpriteObp() or PaletteFX.usesGbcPack()) then return end
|
||||||
PaletteFX.markUiSpriteRedraw(self.playerSheet, self.playerQuad, x, y)
|
PaletteFX.markUiSpriteRedraw(self.playerSheet, self.playerQuad, x, y)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -402,11 +401,7 @@ function TownMap:draw()
|
|||||||
love.graphics.setColor(1, 1, 1, 1)
|
love.graphics.setColor(1, 1, 1, 1)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
-- blinking cursor on the selected location. markerXY is the 8x8 cell's
|
-- WriteTownMapSpriteOAM carry quirk: -4 X, -3 Y for cursor and player alike -- engine/items/town_map.asm:454
|
||||||
-- top-left; the cursor asset is a 16x16 hollow frame centered on its own
|
|
||||||
-- (8,8), so draw it -4,-4 to enclose the cell (engine/menus/town_map.asm
|
|
||||||
-- draws the box cursor CENTERED on the selected location). Drawing it at
|
|
||||||
-- the cell top-left put the square in the frame's top-left quadrant (#152).
|
|
||||||
local showCursor = true
|
local showCursor = true
|
||||||
if GameVersion.generation() == 1 then
|
if GameVersion.generation() == 1 then
|
||||||
showCursor = self.blink < 25
|
showCursor = self.blink < 25
|
||||||
@@ -416,7 +411,7 @@ function TownMap:draw()
|
|||||||
if selected and showCursor then
|
if selected and showCursor then
|
||||||
local x, y = markerXY(selected)
|
local x, y = markerXY(selected)
|
||||||
if self.bg.cursor then
|
if self.bg.cursor then
|
||||||
love.graphics.draw(self.bg.cursor, x - 4, y - 4)
|
love.graphics.draw(self.bg.cursor, x - 4, y - 3)
|
||||||
else
|
else
|
||||||
love.graphics.setColor(0, 0, 0, 1)
|
love.graphics.setColor(0, 0, 0, 1)
|
||||||
love.graphics.rectangle("line", x + 0.5, y + 0.5, 7, 7)
|
love.graphics.rectangle("line", x + 0.5, y + 0.5, 7, 7)
|
||||||
|
|||||||
@@ -826,7 +826,10 @@ function BattleState:drawPic(mon, back)
|
|||||||
if trainerBack then
|
if trainerBack then
|
||||||
-- PAL_BATTLE_OB_PLAYER: the player's own colours, which are row 0 of
|
-- PAL_BATTLE_OB_PLAYER: the player's own colours, which are row 0 of
|
||||||
-- TrainerPalettes (Chris shares Cal's).
|
-- TrainerPalettes (Chris shares Cal's).
|
||||||
colors = Palettes.trainerColors(self.palettes, "PLAYER") or colors
|
-- engine/gfx/color.asm:683-696
|
||||||
|
local row = Gen2Save.isFemale(self.save) and "FALKNER" or "PLAYER"
|
||||||
|
colors = Palettes.trainerColors(self.palettes, row)
|
||||||
|
or Palettes.trainerColors(self.palettes, "PLAYER") or colors
|
||||||
elseif enemyTrainer then
|
elseif enemyTrainer then
|
||||||
-- The opponent's class row out of the same TrainerPalettes table.
|
-- The opponent's class row out of the same TrainerPalettes table.
|
||||||
colors = Palettes.trainerColors(self.palettes, self.enemyTrainerClass)
|
colors = Palettes.trainerColors(self.palettes, self.enemyTrainerClass)
|
||||||
@@ -1258,7 +1261,15 @@ function BattleState:afterAnimFor(side)
|
|||||||
return "ANIM_PLAYER_DAMAGE"
|
return "ANIM_PLAYER_DAMAGE"
|
||||||
end
|
end
|
||||||
|
|
||||||
function BattleState:animForMove(moveId, side, param)
|
-- engine/battle_anims/anim_commands.asm:1200 PlayHitSound
|
||||||
|
function BattleState:playHitSound(effectiveness)
|
||||||
|
if not effectiveness or effectiveness == 0 then return end
|
||||||
|
if effectiveness > 10 then self:playSfx("Sfx_SuperEffective")
|
||||||
|
elseif effectiveness < 10 then self:playSfx("Sfx_NotVeryEffective")
|
||||||
|
else self:playSfx("Sfx_Damage") end
|
||||||
|
end
|
||||||
|
|
||||||
|
function BattleState:animForMove(moveId, side, param, effectiveness)
|
||||||
local key = self.anims and self.anims.moves and self.anims.moves[moveId]
|
local key = self.anims and self.anims.moves and self.anims.moves[moveId]
|
||||||
local started = self:startAnim(key, {
|
local started = self:startAnim(key, {
|
||||||
turn = self:turnFor(side), animId = moveId, isMove = true, param = param,
|
turn = self:turnFor(side), animId = moveId, isMove = true, param = param,
|
||||||
@@ -1267,7 +1278,8 @@ function BattleState:animForMove(moveId, side, param)
|
|||||||
-- BattleAnimRunScript (anim_commands.asm:55-72): after the move script
|
-- BattleAnimRunScript (anim_commands.asm:55-72): after the move script
|
||||||
-- restores HUDs it immediately runs wBattleAfterAnim (the hit shake).
|
-- restores HUDs it immediately runs wBattleAfterAnim (the hit shake).
|
||||||
-- Queue it so stepAnim chains without waiting on the next event.
|
-- Queue it so stepAnim chains without waiting on the next event.
|
||||||
self.pendingAfterAnim = { name = self:afterAnimFor(side), side = side }
|
self.pendingAfterAnim = { name = self:afterAnimFor(side), side = side,
|
||||||
|
effectiveness = effectiveness }
|
||||||
end
|
end
|
||||||
return started
|
return started
|
||||||
end
|
end
|
||||||
@@ -1278,6 +1290,7 @@ function BattleState:startPendingAfterAnim()
|
|||||||
if not pending then return false end
|
if not pending then return false end
|
||||||
self.pendingAfterAnim = nil
|
self.pendingAfterAnim = nil
|
||||||
if self:animForId(pending.name, pending.side) then
|
if self:animForId(pending.name, pending.side) then
|
||||||
|
self:playHitSound(pending.effectiveness)
|
||||||
-- dealDamage's default ANIM_x_DAMAGE is this same shake; skip it there.
|
-- dealDamage's default ANIM_x_DAMAGE is this same shake; skip it there.
|
||||||
self.afterAnimPlayed = true
|
self.afterAnimPlayed = true
|
||||||
return true
|
return true
|
||||||
@@ -1575,6 +1588,11 @@ function BattleState:advanceQueue()
|
|||||||
self:showPages(text)
|
self:showPages(text)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
-- data/text/battle.asm:184
|
||||||
|
if event.kind == "money" then
|
||||||
|
self:showPages(event.text or "")
|
||||||
|
return
|
||||||
|
end
|
||||||
-- The shiny sparkle: hBattleTurn 1 and wBattleAnimParam 1 pick
|
-- The shiny sparkle: hBattleTurn 1 and wBattleAnimParam 1 pick
|
||||||
-- BattleAnim_SendOutMon's `.Shiny` arm on the enemy (core.asm:8708-8715).
|
-- BattleAnim_SendOutMon's `.Shiny` arm on the enemy (core.asm:8708-8715).
|
||||||
if event.kind == "shiny-flash" then
|
if event.kind == "shiny-flash" then
|
||||||
@@ -1692,12 +1710,14 @@ function BattleState:advanceQueue()
|
|||||||
if event.kind == "move" and not event.missed then
|
if event.kind == "move" and not event.missed then
|
||||||
self.afterAnimPlayed = nil
|
self.afterAnimPlayed = nil
|
||||||
self.pendingAfterAnim = nil
|
self.pendingAfterAnim = nil
|
||||||
if not self:animForMove(event.move, event.side, event.animParam) then
|
if not self:animForMove(event.move, event.side, event.animParam,
|
||||||
|
event.effectiveness) then
|
||||||
-- BATTLE SCENE off skips the move script but still runs wBattleAfterAnim
|
-- BATTLE SCENE off skips the move script but still runs wBattleAfterAnim
|
||||||
-- (anim_commands.asm:55-72 .disabled fallthrough).
|
-- (anim_commands.asm:55-72 .disabled fallthrough).
|
||||||
local options = self.game and self.game.options
|
local options = self.game and self.game.options
|
||||||
if options and options.battleScene == false then
|
if options and options.battleScene == false then
|
||||||
if self:animForId(self:afterAnimFor(event.side), event.side) then
|
if self:animForId(self:afterAnimFor(event.side), event.side) then
|
||||||
|
self:playHitSound(event.effectiveness)
|
||||||
self.afterAnimPlayed = true
|
self.afterAnimPlayed = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -1725,6 +1745,7 @@ function BattleState:advanceQueue()
|
|||||||
and (hit == "ANIM_ENEMY_DAMAGE" or hit == "ANIM_PLAYER_DAMAGE") then
|
and (hit == "ANIM_ENEMY_DAMAGE" or hit == "ANIM_PLAYER_DAMAGE") then
|
||||||
self.afterAnimPlayed = nil
|
self.afterAnimPlayed = nil
|
||||||
else
|
else
|
||||||
|
self:playHitSound(event.effectiveness)
|
||||||
self:animForId(hit, from)
|
self:animForId(hit, from)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
+22
-16
@@ -41,6 +41,8 @@ ItemPcMenu.isOpaque = true
|
|||||||
local PC_ITEM_CAPACITY = 50
|
local PC_ITEM_CAPACITY = 50
|
||||||
local MAX_STACK = 99
|
local MAX_STACK = 99
|
||||||
|
|
||||||
|
local function stacksFor(n) return math.ceil((n or 0) / MAX_STACK) end
|
||||||
|
|
||||||
-- PlayersPCMenuData .PlayersPCMenuPointers strings, verbatim. .WhichPC picks
|
-- PlayersPCMenuData .PlayersPCMenuPointers strings, verbatim. .WhichPC picks
|
||||||
-- which rows a caller sees: PLAYERSPC_NORMAL ends on LOG OFF, PLAYERSPC_HOUSE
|
-- which rows a caller sees: PLAYERSPC_NORMAL ends on LOG OFF, PLAYERSPC_HOUSE
|
||||||
-- carries DECORATION and ends on TURN OFF.
|
-- carries DECORATION and ends on TURN OFF.
|
||||||
@@ -167,18 +169,24 @@ function ItemPcMenu:rebuild()
|
|||||||
for id, count in pairs(pc) do
|
for id, count in pairs(pc) do
|
||||||
if (count or 0) > 0 then
|
if (count or 0) > 0 then
|
||||||
local def = self:def(id)
|
local def = self:def(id)
|
||||||
rows[#rows + 1] = {
|
local remaining = count
|
||||||
id = id, count = count,
|
while remaining > 0 do
|
||||||
name = (def and def.name) or id,
|
local n = math.min(remaining, MAX_STACK)
|
||||||
index = def and def.index or math.huge,
|
rows[#rows + 1] = {
|
||||||
}
|
id = id, count = n,
|
||||||
|
name = (def and def.name) or id,
|
||||||
|
index = def and def.index or math.huge,
|
||||||
|
}
|
||||||
|
remaining = remaining - n
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
-- wPCItems keeps acquisition order; without that recorded, item id order is
|
-- wPCItems keeps acquisition order; without that recorded, item id order is
|
||||||
-- the stable choice, the same sort the PACK uses.
|
-- the stable choice, the same sort the PACK uses.
|
||||||
table.sort(rows, function(a, b)
|
table.sort(rows, function(a, b)
|
||||||
if a.index ~= b.index then return a.index < b.index end
|
if a.index ~= b.index then return a.index < b.index end
|
||||||
return a.id < b.id
|
if a.id ~= b.id then return a.id < b.id end
|
||||||
|
return a.count > b.count
|
||||||
end)
|
end)
|
||||||
self.rows = rows
|
self.rows = rows
|
||||||
if self.listIndex > #rows + 1 then self.listIndex = #rows + 1 end
|
if self.listIndex > #rows + 1 then self.listIndex = #rows + 1 end
|
||||||
@@ -200,20 +208,18 @@ function ItemPcMenu:ensureVisible()
|
|||||||
math.max(0, self:listTotal() - VISIBLE_ROWS)))
|
math.max(0, self:listTotal() - VISIBLE_ROWS)))
|
||||||
end
|
end
|
||||||
|
|
||||||
-- ReceiveItem over wPCItems: a new id needs one of the fifty stacks, a grown
|
-- ReceiveItem over wPCItems: the add tops up every existing stack of that id
|
||||||
-- one may not pass 99. False is the no-carry the deposit turns into
|
-- and spills the rest into a new one, so it only needs a free stack when the
|
||||||
|
-- room in place is short. False is the no-carry the deposit turns into
|
||||||
-- _PlayersPCNoRoomDepositText.
|
-- _PlayersPCNoRoomDepositText.
|
||||||
|
-- engine/items/items.asm:156 PutItemInPocket
|
||||||
function ItemPcMenu:pcAdd(id, qty)
|
function ItemPcMenu:pcAdd(id, qty)
|
||||||
local pc = self.save.pcItems
|
local pc = self.save.pcItems
|
||||||
local held = pc[id] or 0
|
local held = pc[id] or 0
|
||||||
if held == 0 then
|
local used = 0
|
||||||
local stacks = 0
|
for _, count in pairs(pc) do used = used + stacksFor(count) end
|
||||||
for _, count in pairs(pc) do
|
local need = stacksFor(held + qty) - stacksFor(held)
|
||||||
if (count or 0) > 0 then stacks = stacks + 1 end
|
if used + need > PC_ITEM_CAPACITY then return false end
|
||||||
end
|
|
||||||
if stacks >= PC_ITEM_CAPACITY then return false end
|
|
||||||
end
|
|
||||||
if held + qty > MAX_STACK then return false end
|
|
||||||
pc[id] = held + qty
|
pc[id] = held + qty
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -156,7 +156,9 @@ function TitleState.new(game, opts)
|
|||||||
self.entranceHideBelow = entrance and tonumber(entrance.hideBelow) or nil
|
self.entranceHideBelow = entrance and tonumber(entrance.hideBelow) or nil
|
||||||
self.gemY = entrance and (tonumber(title.gemFromY) or -50) or self.gemRestY
|
self.gemY = entrance and (tonumber(title.gemFromY) or -50) or self.gemRestY
|
||||||
self.entranceSfx = title.entranceSfx
|
self.entranceSfx = title.entranceSfx
|
||||||
|
-- engine/menus/intro_menu.asm:951-966
|
||||||
self.timeoutFrames = tonumber(title.timeoutFrames)
|
self.timeoutFrames = tonumber(title.timeoutFrames)
|
||||||
|
or ((self.trailMode == "silver") and (73 * 60 + 36) or (84 * 60 + 16))
|
||||||
self.onTimeout = opts.onTimeout
|
self.onTimeout = opts.onTimeout
|
||||||
-- The copyright window line is pal 7 (engine/movie/title.asm:40-43); its
|
-- The copyright window line is pal 7 (engine/movie/title.asm:40-43); its
|
||||||
-- colour 0 backs the whole band.
|
-- colour 0 backs the whole band.
|
||||||
@@ -295,12 +297,17 @@ function TitleState:update(_dt)
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
-- TitleScreenTimer / TitleScreenMain's run-down back into the attract
|
-- engine/menus/intro_menu.asm:1023-1059
|
||||||
-- loop (engine/menus/intro_menu.asm:1125-1236).
|
if self.timeoutFrames and self.onTimeout then
|
||||||
if self.timeoutFrames and self.onTimeout
|
if self.fadeStart then
|
||||||
and self.frameCounter - (self.timeoutStart or 0) >= self.timeoutFrames then
|
if self.frameCounter - self.fadeStart >= 60 then self.onTimeout() end
|
||||||
self.onTimeout()
|
return
|
||||||
return
|
end
|
||||||
|
if self.frameCounter - (self.timeoutStart or 0) >= self.timeoutFrames then
|
||||||
|
self.fadeStart = self.frameCounter
|
||||||
|
Music.fadeOut(8)
|
||||||
|
return
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local input = self.game.input
|
local input = self.game.input
|
||||||
|
|||||||
@@ -1232,6 +1232,10 @@ function OverworldState:update(dt)
|
|||||||
self.player.inputLocked = false
|
self.player.inputLocked = false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
if self.spinArrive and not self.player.spinFrames then
|
||||||
|
self.spinArrive = nil
|
||||||
|
self.player.inputLocked = false
|
||||||
|
end
|
||||||
|
|
||||||
-- EnterMapAnim's .done tail re-enables the companion once the swoop or the
|
-- EnterMapAnim's .done tail re-enables the companion once the swoop or the
|
||||||
-- spin-down has landed (player_animations.asm:40)
|
-- spin-down has landed (player_animations.asm:40)
|
||||||
@@ -1311,7 +1315,7 @@ function OverworldState:update(dt)
|
|||||||
local scripted = self.runner:isRunning() or #self.scriptMoves > 0
|
local scripted = self.runner:isRunning() or #self.scriptMoves > 0
|
||||||
or (self.hopLand or 0) > 0
|
or (self.hopLand or 0) > 0
|
||||||
or self.engaging or self.emote or self.teleportOut
|
or self.engaging or self.emote or self.teleportOut
|
||||||
or self.flyAnim or self.flyArrive
|
or self.flyAnim or self.flyArrive or self.spinArrive
|
||||||
if not scripted and not self.transitioning then
|
if not scripted and not self.transitioning then
|
||||||
self:checkTrainerSight()
|
self:checkTrainerSight()
|
||||||
-- CheckFightingMapTrainers (home/trainers.asm) zeroes hJoyHeld and
|
-- CheckFightingMapTrainers (home/trainers.asm) zeroes hJoyHeld and
|
||||||
@@ -1321,7 +1325,7 @@ function OverworldState:update(dt)
|
|||||||
scripted = self.runner:isRunning() or #self.scriptMoves > 0
|
scripted = self.runner:isRunning() or #self.scriptMoves > 0
|
||||||
or (self.hopLand or 0) > 0
|
or (self.hopLand or 0) > 0
|
||||||
or self.engaging or self.emote or self.teleportOut
|
or self.engaging or self.emote or self.teleportOut
|
||||||
or self.flyAnim or self.flyArrive
|
or self.flyAnim or self.flyArrive or self.spinArrive
|
||||||
end
|
end
|
||||||
-- a scriptMove's onDone can push a text box on the frame it retires, and
|
-- a scriptMove's onDone can push a text box on the frame it retires, and
|
||||||
-- DisplayTextID owns the loop from there (home/text_script.asm:3)
|
-- DisplayTextID owns the loop from there (home/text_script.asm:3)
|
||||||
@@ -4802,6 +4806,10 @@ function OverworldState:startWarpTo(mapId, x, y, facing, onDone, opts)
|
|||||||
self.doorWarp = nil
|
self.doorWarp = nil
|
||||||
local arriveWarp = self.arriveWarp
|
local arriveWarp = self.arriveWarp
|
||||||
self.arriveWarp = nil
|
self.arriveWarp = nil
|
||||||
|
if self.spinArrive then
|
||||||
|
self.spinArrive = nil
|
||||||
|
self.player.inputLocked = false
|
||||||
|
end
|
||||||
-- PlayMapChangeSound (home/overworld.asm) plays before the tail-called
|
-- PlayMapChangeSound (home/overworld.asm) plays before the tail-called
|
||||||
-- GBFadeOutToBlack, so the SFX starts with the fade (#961)
|
-- GBFadeOutToBlack, so the SFX starts with the fade (#961)
|
||||||
if doorWarp then
|
if doorWarp then
|
||||||
@@ -4849,6 +4857,9 @@ function OverworldState:startWarpTo(mapId, x, y, facing, onDone, opts)
|
|||||||
self.player.spinFrames = 48
|
self.player.spinFrames = 48
|
||||||
self.player.spinTotal = 48
|
self.player.spinTotal = 48
|
||||||
self.player.spinDrop = true
|
self.player.spinDrop = true
|
||||||
|
-- engine/overworld/player_animations.asm:19
|
||||||
|
self.spinArrive = true
|
||||||
|
self.player.inputLocked = true
|
||||||
end
|
end
|
||||||
if doorWarp then
|
if doorWarp then
|
||||||
-- PlayerStepOutFromDoor (engine/overworld/auto_movement.asm): any
|
-- PlayerStepOutFromDoor (engine/overworld/auto_movement.asm): any
|
||||||
@@ -5329,8 +5340,9 @@ function OverworldState:drawWorld()
|
|||||||
-- cry with no bubble still pauses the world for its beat)
|
-- cry with no bubble still pauses the world for its beat)
|
||||||
if self.emote.bubble == false then return end
|
if self.emote.bubble == false then return end
|
||||||
local npc = self.emote.npc
|
local npc = self.emote.npc
|
||||||
local ex = npc.px - cam.x + 4
|
-- engine/overworld/emotion_bubbles.asm:41
|
||||||
local ey = npc.py - cam.y - 14
|
local ex = npc.px - cam.x
|
||||||
|
local ey = npc.py - cam.y - 20
|
||||||
local bubble = Game.data.field.emotionBubbles
|
local bubble = Game.data.field.emotionBubbles
|
||||||
local drawn = false
|
local drawn = false
|
||||||
if bubble and bubble.path then
|
if bubble and bubble.path then
|
||||||
|
|||||||
@@ -551,6 +551,7 @@ function World.new(game)
|
|||||||
lastSfx = nil,
|
lastSfx = nil,
|
||||||
pokePic = nil,
|
pokePic = nil,
|
||||||
pendingSceneScript = false,
|
pendingSceneScript = false,
|
||||||
|
startedOverworld = false,
|
||||||
-- GBC color state (engine/gfx/color.asm). `daytime` is the resolved
|
-- GBC color state (engine/gfx/color.asm). `daytime` is the resolved
|
||||||
-- MORN/DAY/NITE/DARK the map is currently lit by; clockHour overrides
|
-- MORN/DAY/NITE/DARK the map is currently lit by; clockHour overrides
|
||||||
-- World:hour for drivers and tests, so the palette, the hour windows and
|
-- World:hour for drivers and tests, so the palette, the hour windows and
|
||||||
@@ -9045,10 +9046,9 @@ function World:setMap(mapId, cx, cy, facing, opts)
|
|||||||
if not opts.seamless then
|
if not opts.seamless then
|
||||||
self.pendingSceneScript = true
|
self.pendingSceneScript = true
|
||||||
end
|
end
|
||||||
-- StartMap (engine/overworld/events.asm): `farcall InitCallReceiveDelay` on
|
-- engine/overworld/events.asm:98
|
||||||
-- every map entry, connections included -- which is why a player who keeps
|
if not self.startedOverworld and self.game and self.game.save then
|
||||||
-- warping is never rung (src/core/gen2/Phone.lua's receive timer).
|
self.startedOverworld = true
|
||||||
if self.game and self.game.save then
|
|
||||||
require("src.core.gen2.Phone").onMapLoad(self.game.save,
|
require("src.core.gen2.Phone").onMapLoad(self.game.save,
|
||||||
self:stepContext().phone)
|
self:stepContext().phone)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -23,10 +23,10 @@ return function(game)
|
|||||||
|
|
||||||
fitRow.step(game, 1)
|
fitRow.step(game, 1)
|
||||||
assert(options.battleFit == "fill", "battle size switched to FILL")
|
assert(options.battleFit == "fill", "battle size switched to FILL")
|
||||||
assert(options.battleBg == "white", "FILL + EXTENDED normalized background to WHITE")
|
assert(options.battleBg == "black", "FILL + EXTENDED preserves the stored background")
|
||||||
assert(bgRow.value(game) == "AUTO", "adaptive background is labeled AUTO")
|
assert(bgRow.value(game) == "AUTO (FILL HUD)", "adaptive background is labeled AUTO")
|
||||||
assert(bgRow.step(game, 1) == false, "AUTO background row is locked")
|
assert(bgRow.step(game, 1) == false, "AUTO background row is locked")
|
||||||
assert(options.battleBg == "white", "locked AUTO retains the WHITE value")
|
assert(options.battleBg == "black", "locked AUTO does not overwrite the stored value")
|
||||||
|
|
||||||
menu.index = bgIndex
|
menu.index = bgIndex
|
||||||
menu.scroll = math.max(0, bgIndex - 5)
|
menu.scroll = math.max(0, bgIndex - 5)
|
||||||
@@ -37,11 +37,11 @@ return function(game)
|
|||||||
|
|
||||||
fitRow.step(game, -1)
|
fitRow.step(game, -1)
|
||||||
assert(options.battleFit == "fixed", "battle size switched back to FIXED")
|
assert(options.battleFit == "fixed", "battle size switched back to FIXED")
|
||||||
assert(bgRow.value(game) == "WHITE", "FIXED exposes the stored WHITE choice")
|
assert(bgRow.value(game) == "BLACK", "FIXED exposes the stored BLACK choice")
|
||||||
assert(bgRow.step(game, 1) == true and options.battleBg == "black",
|
|
||||||
"FIXED can select BLACK")
|
|
||||||
assert(bgRow.step(game, 1) == true and options.battleBg == "world",
|
assert(bgRow.step(game, 1) == true and options.battleBg == "world",
|
||||||
"FIXED can select WORLD")
|
"FIXED can select WORLD")
|
||||||
|
assert(bgRow.step(game, 1) == true and options.battleBg == "white",
|
||||||
|
"FIXED can select WHITE")
|
||||||
U.wait(2)
|
U.wait(2)
|
||||||
local fixedPath = DIR .. "/fixed_extended_background_choices.png"
|
local fixedPath = DIR .. "/fixed_extended_background_choices.png"
|
||||||
os.remove(fixedPath)
|
os.remove(fixedPath)
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ end
|
|||||||
|
|
||||||
-- #1461: saves that passed through 0.1.82-0.1.9x already have the player's
|
-- #1461: saves that passed through 0.1.82-0.1.9x already have the player's
|
||||||
-- own id written onto traded mons, so stamping correctly from now on does
|
-- own id written onto traded mons, so stamping correctly from now on does
|
||||||
-- not help them. The repair runs on every load, not behind a format gate.
|
-- not help them. The repair is a one-shot pre-format-5 migration.
|
||||||
do
|
do
|
||||||
local SaveData = require("src.core.SaveData")
|
local SaveData = require("src.core.SaveData")
|
||||||
local save = newSave()
|
local save = newSave()
|
||||||
|
|||||||
@@ -229,16 +229,16 @@ check(runScript("VERMILION_TRADE_HOUSE", "TEXT_VERMILIONTRADEHOUSE_LITTLE_GIRL")
|
|||||||
"DUX post-trade script completes")
|
"DUX post-trade script completes")
|
||||||
shownIs({ "_AfterTrade3Text" }, "DUX uses the happy dialogset")
|
shownIs({ "_AfterTrade3Text" }, "DUX uses the happy dialogset")
|
||||||
|
|
||||||
-- === 9) Celadon Eevee: no confirm prompt, AskName then GotMonText,
|
-- === 9) Celadon Eevee: no confirm prompt, GotMonText then AskName,
|
||||||
-- ball hidden (GivePokemon -> AddPartyMon AskName; script still
|
-- ball hidden (GivePokemon -> SetPokedexOwnedFlag prints GotMonText
|
||||||
-- prints GotMonText after the silent give_pokemon row) ===
|
-- before AddPartyMon's AskName) ===
|
||||||
local EEVEE_MAP, EEVEE_BALL =
|
local EEVEE_MAP, EEVEE_BALL =
|
||||||
"CELADON_MANSION_ROOF_HOUSE", "CELADONMANSION_ROOF_HOUSE_EEVEE_POKEBALL"
|
"CELADON_MANSION_ROOF_HOUSE", "CELADONMANSION_ROOF_HOUSE_EEVEE_POKEBALL"
|
||||||
local EEVEE_TEXT = "TEXT_CELADONMANSION_ROOF_HOUSE_EEVEE_POKEBALL"
|
local EEVEE_TEXT = "TEXT_CELADONMANSION_ROOF_HOUSE_EEVEE_POKEBALL"
|
||||||
Game.save = SaveData.newGame()
|
Game.save = SaveData.newGame()
|
||||||
check(runScript(EEVEE_MAP, EEVEE_TEXT), "Eevee ball script completes")
|
check(runScript(EEVEE_MAP, EEVEE_TEXT), "Eevee ball script completes")
|
||||||
shownIs({ "_DoYouWantToNicknameText", "_GotMonText" },
|
shownIs({ "_GotMonText", "_DoYouWantToNicknameText" },
|
||||||
"Eevee gives immediately (nickname ask, then GotMonText)")
|
"Eevee gives immediately (GotMonText, then nickname ask)")
|
||||||
eq(#Game.save.party, 1, "Eevee joins the party")
|
eq(#Game.save.party, 1, "Eevee joins the party")
|
||||||
eq(Game.save.party[1].species, "EEVEE", "gift species is EEVEE")
|
eq(Game.save.party[1].species, "EEVEE", "gift species is EEVEE")
|
||||||
eq(Game.save.party[1].level, 25, "Eevee is level 25")
|
eq(Game.save.party[1].level, 25, "Eevee is level 25")
|
||||||
@@ -261,13 +261,13 @@ shownIs({}, "old save: silent")
|
|||||||
eq(#Game.save.party, 0, "old save: no Eevee re-gift")
|
eq(#Game.save.party, 0, "old save: no Eevee re-gift")
|
||||||
eq(toggleOf(EEVEE_MAP, EEVEE_BALL), false, "old save: leftover ball hidden")
|
eq(toggleOf(EEVEE_MAP, EEVEE_BALL), false, "old save: leftover ball hidden")
|
||||||
|
|
||||||
-- === 11) GivePokemon party full, box has room: AskName + SentToBoxText
|
-- === 11) GivePokemon party full, box has room: GotMonText, then AskName
|
||||||
-- (SendNewMonToBox), then script GotMonText; ball hidden ===
|
-- + SentToBoxText (SendNewMonToBox); ball hidden ===
|
||||||
Game.save = SaveData.newGame()
|
Game.save = SaveData.newGame()
|
||||||
for i = 1, 6 do Game.save.party[i] = Pokemon.new(Data, "PIDGEY", 5) end
|
for i = 1, 6 do Game.save.party[i] = Pokemon.new(Data, "PIDGEY", 5) end
|
||||||
check(runScript(EEVEE_MAP, EEVEE_TEXT), "full-party Eevee script completes")
|
check(runScript(EEVEE_MAP, EEVEE_TEXT), "full-party Eevee script completes")
|
||||||
shownIs({ "_DoYouWantToNicknameText", "_SentToBoxText", "_GotMonText" },
|
shownIs({ "_GotMonText", "_DoYouWantToNicknameText", "_SentToBoxText" },
|
||||||
"full-party gift: nickname, sent-to-box, then GotMonText")
|
"full-party gift: GotMonText, nickname, then sent-to-box")
|
||||||
eq(#Game.save.party, 6, "full-party gift leaves party size at 6")
|
eq(#Game.save.party, 6, "full-party gift leaves party size at 6")
|
||||||
local Boxes = require("src.pokemon.Boxes")
|
local Boxes = require("src.pokemon.Boxes")
|
||||||
local boxed = false
|
local boxed = false
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ local yellow = GameVersion.isYellow()
|
|||||||
-- pokered/scripts/*.asm, PlayCry line in brackets:
|
-- pokered/scripts/*.asm, PlayCry line in brackets:
|
||||||
-- SSAnne1FRooms.asm:66 [:70], SSAnneB1FRooms.asm:82 [:86],
|
-- SSAnne1FRooms.asm:66 [:70], SSAnneB1FRooms.asm:82 [:86],
|
||||||
-- VermilionPidgeyHouse.asm:15 [:19], VermilionCity.asm:224 [:228],
|
-- VermilionPidgeyHouse.asm:15 [:19], VermilionCity.asm:224 [:228],
|
||||||
-- PokemonFanClub.asm:71 [:76] and :84 [:89].
|
-- PokemonFanClub.asm:71 [:76] and :84 [:89],
|
||||||
|
-- MrFujisHouse.asm:56 [:60] and :63 [:67], LavenderCuboneHouse.asm:10 [:14].
|
||||||
local PETS = {
|
local PETS = {
|
||||||
{ map = "SS_ANNE_1F_ROOMS", const = "TEXT_SSANNE1FROOMS_WIGGLYTUFF",
|
{ map = "SS_ANNE_1F_ROOMS", const = "TEXT_SSANNE1FROOMS_WIGGLYTUFF",
|
||||||
object = "SSANNE1FROOMS_WIGGLYTUFF", species = "WIGGLYTUFF",
|
object = "SSANNE1FROOMS_WIGGLYTUFF", species = "WIGGLYTUFF",
|
||||||
@@ -40,6 +41,15 @@ local PETS = {
|
|||||||
{ map = "POKEMON_FAN_CLUB", const = "TEXT_POKEMONFANCLUB_SEEL",
|
{ map = "POKEMON_FAN_CLUB", const = "TEXT_POKEMONFANCLUB_SEEL",
|
||||||
object = "POKEMONFANCLUB_SEEL", species = "SEEL",
|
object = "POKEMONFANCLUB_SEEL", species = "SEEL",
|
||||||
label = "_PokemonFanClubSeelText" },
|
label = "_PokemonFanClubSeelText" },
|
||||||
|
{ map = "MR_FUJIS_HOUSE", const = "TEXT_MRFUJISHOUSE_PSYDUCK",
|
||||||
|
object = "MRFUJISHOUSE_PSYDUCK", species = "PSYDUCK",
|
||||||
|
label = "_MrFujisHousePsyduckText" },
|
||||||
|
{ map = "MR_FUJIS_HOUSE", const = "TEXT_MRFUJISHOUSE_NIDORINO",
|
||||||
|
object = "MRFUJISHOUSE_NIDORINO", species = "NIDORINO",
|
||||||
|
label = "_MrFujisHouseNidorinoText" },
|
||||||
|
{ map = "LAVENDER_CUBONE_HOUSE", const = "TEXT_LAVENDERCUBONEHOUSE_CUBONE",
|
||||||
|
object = "LAVENDERCUBONEHOUSE_CUBONE", species = "CUBONE",
|
||||||
|
label = "_LavenderCuboneHouseCuboneText" },
|
||||||
}
|
}
|
||||||
|
|
||||||
if yellow then
|
if yellow then
|
||||||
|
|||||||
Reference in New Issue
Block a user