mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-12 00:10:56 +02:00
Merge branch 'dev' into feat/switch-nx
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -981,10 +981,15 @@ M.VICTORY_ROAD_3F = {
|
||||
local championsRoomRivalScript = {
|
||||
{ "face_player" }, -- 1
|
||||
{ "check_flag", "EVENT_BEAT_CHAMPION_RIVAL_THIS_RUN" }, -- 2
|
||||
{ "jump_if_true", 25 }, -- 3 past end
|
||||
{ "jump_if_true", 26 }, -- 3 past end
|
||||
{ "show_text", "_ChampionsRoomRivalIntroText" }, -- 4
|
||||
{ "rival_battle", "OPP_RIVAL3", 1 }, -- 5
|
||||
{ "jump_if_false", 25 }, -- 6 past end
|
||||
-- ChampionsRoomRivalReadyToBattleScript plays MUSIC_FINAL_BATTLE after
|
||||
-- the intro text, before the battle itself (#706); pushBattle's wipe-time
|
||||
-- playBattle("final") then no-ops on the same song, so the theme stays
|
||||
-- continuous into the fight
|
||||
{ "play_music", "Music_FinalBattle" }, -- 5
|
||||
{ "rival_battle", "OPP_RIVAL3", 1 }, -- 6
|
||||
{ "jump_if_false", 26 }, -- 6 past end
|
||||
{ "set_flag", "EVENT_BEAT_CHAMPION_RIVAL_THIS_RUN" }, -- 7
|
||||
{ "set_flag", "EVENT_BEAT_CHAMPION_RIVAL" }, -- 8
|
||||
-- ChampionsRoomRivalDefeatedScript re-displays TEXT_CHAMPIONSROOM_RIVAL,
|
||||
|
||||
@@ -3540,8 +3540,16 @@ function BattleState:onFaint(battler)
|
||||
self:actNext(function()
|
||||
battler.fainted = true
|
||||
local Sound = require("src.core.Sound")
|
||||
Sound.playCry(self.data, battler.mon.species)
|
||||
Sound.play(self.data, "Faint_Fall")
|
||||
if battler.isPlayer then
|
||||
-- RemoveFaintedPlayerMon (core.asm:1040-1042): the player mon's
|
||||
-- faint plays its ordinary species cry -- no Faint_Fall
|
||||
Sound.playCry(self.data, battler.mon.species)
|
||||
elseif self.kind ~= "wild" then
|
||||
-- FaintEnemyPokemon (core.asm:732-771): the enemy faint plays no
|
||||
-- species cry; trainer battles get SFX_FAINT_FALL, then SFX_FAINT_THUD
|
||||
-- once it finishes (wild battles skip straight to the victory music)
|
||||
Sound.play(self.data, "Faint_Fall")
|
||||
end
|
||||
self.fx = self.fx or {}
|
||||
-- SlideDownFaintedMonPic: PIC_HEIGHT (7) slide steps, each closing with
|
||||
-- DelayFrames 2 (core.asm:1186-1222). The port held this one twice as
|
||||
@@ -3550,6 +3558,13 @@ function BattleState:onFaint(battler)
|
||||
end)
|
||||
self.nextInsert = (self.nextInsert or 0) + 1
|
||||
table.insert(self.queue, self.nextInsert, { wait = Timing.FAINT_SLIDE })
|
||||
if not battler.isPlayer and self.kind ~= "wild" then
|
||||
-- FaintEnemyPokemon's SFX_FAINT_THUD lands as the slide does (after
|
||||
-- Faint_Fall, before EnemyMonFaintedText)
|
||||
self:actNext(function()
|
||||
require("src.core.Sound").play(self.data, "Faint_Thud")
|
||||
end)
|
||||
end
|
||||
if not battler.isPlayer and self.kind == "wild" then
|
||||
-- FaintEnemyPokemon .wild_win (core.asm:792-795): beating a wild
|
||||
-- mon calls EndLowHealthAlarm and starts MUSIC_DEFEATED_WILD_MON
|
||||
|
||||
@@ -542,6 +542,15 @@ function RomImporter:_romAction(version)
|
||||
else self:choose(version) end
|
||||
end
|
||||
|
||||
-- Sanitize a string before it is interpolated into a picker shell command:
|
||||
-- * "%" would be eaten as a string.format directive (#665);
|
||||
-- * '"' would break the AppleScript / zenity double-quoted argument and
|
||||
-- "'" the surrounding single-quoted shell string.
|
||||
local function shellSafe(s)
|
||||
s = tostring(s):gsub("%%", "%%%%")
|
||||
return s:gsub('"', '\\"'):gsub("'", "''")
|
||||
end
|
||||
|
||||
-- LOVE 11.5 on Android has no native file picker (love.window.showFileDialog
|
||||
-- is a LOVE 12 nightly-only addition) and never fires love.filedropped, so
|
||||
-- neither desktop path below works there. conf.lua points the Android save
|
||||
@@ -648,7 +657,7 @@ end
|
||||
|
||||
local function chooseRom(promptName)
|
||||
promptName = promptName or "Pokemon"
|
||||
local prompt = "Choose your " .. promptName .. " ROM"
|
||||
local prompt = shellSafe("Choose your " .. promptName .. " ROM")
|
||||
local platform = love.system.getOS()
|
||||
if platform == "OS X" then
|
||||
return commandOutput(
|
||||
@@ -660,10 +669,16 @@ local function chooseRom(promptName)
|
||||
"$d=New-Object System.Windows.Forms.OpenFileDialog;",
|
||||
"$d.Title='" .. prompt .. "';",
|
||||
"$d.Filter='Game Boy ROM (*.gb;*.gbc)|*.gb;*.gbc|All files (*.*)|*.*';",
|
||||
-- write the pick as UTF-8: the console's OEM codepage would mangle
|
||||
-- non-ASCII names (Pokémon -> Pok\x82mon) and crash any text draw
|
||||
-- that shows them (#325)
|
||||
"if($d.ShowDialog() -eq 'OK'){[Console]::OutputEncoding=[Text.Encoding]::UTF8; [Console]::Write($d.FileName)}",
|
||||
-- copy the pick to a plain-ASCII temp name and answer with that:
|
||||
-- the console's OEM codepage would mangle a non-ASCII path
|
||||
-- (Pokémon -> Pok\x82mon) and io.open on Windows needs ANSI bytes,
|
||||
-- so returning the original name both crashed the notice draw and
|
||||
-- could never have opened the file (#325, #665)
|
||||
"if($d.ShowDialog() -eq 'OK'){",
|
||||
"$t=Join-Path $env:TEMP 'pokeport_rom_pick.gb';",
|
||||
"Copy-Item -LiteralPath $d.FileName -Destination $t -Force;",
|
||||
"[Console]::OutputEncoding=[Text.Encoding]::UTF8;",
|
||||
"[Console]::Write($t)}",
|
||||
})
|
||||
return commandOutput(
|
||||
'powershell -NoProfile -STA -Command "' .. script .. '"')
|
||||
@@ -682,7 +697,7 @@ end
|
||||
-- Returns the chosen absolute path or nil. Android uses love.system.pickFile
|
||||
-- ("mod") instead -- see RomImporter:chooseMod.
|
||||
local function chooseZip()
|
||||
local prompt = Strings("Choose a mod .zip")
|
||||
local prompt = shellSafe(Strings("Choose a mod .zip"))
|
||||
local platform = love.system.getOS()
|
||||
if platform == "OS X" then
|
||||
return commandOutput(
|
||||
@@ -722,7 +737,7 @@ end
|
||||
-- dialogs). Returns the chosen absolute path or nil. Android uses
|
||||
-- love.system.pickFile("sav") instead -- see RomImporter:chooseSaveImport.
|
||||
local function chooseSav()
|
||||
local prompt = Strings("Choose a .sav save file")
|
||||
local prompt = shellSafe(Strings("Choose a .sav save file"))
|
||||
local platform = love.system.getOS()
|
||||
if platform == "OS X" then
|
||||
return commandOutput(
|
||||
@@ -734,8 +749,14 @@ local function chooseSav()
|
||||
"$d=New-Object System.Windows.Forms.OpenFileDialog;",
|
||||
"$d.Title='" .. prompt .. "';",
|
||||
"$d.Filter='Game Boy save (*.sav)|*.sav|All files (*.*)|*.*';",
|
||||
-- UTF-8, like the ROM and mod pickers (#325)
|
||||
"if($d.ShowDialog() -eq 'OK'){[Console]::OutputEncoding=[Text.Encoding]::UTF8; [Console]::Write($d.FileName)}",
|
||||
-- copy the pick to a plain-ASCII temp name: io.open on Windows
|
||||
-- needs ANSI bytes, so a non-ASCII path (Pokémon -> Pok\x82mon)
|
||||
-- could never have been opened (#325, #665)
|
||||
"if($d.ShowDialog() -eq 'OK'){",
|
||||
"$t=Join-Path $env:TEMP 'pokeport_sav_pick.sav';",
|
||||
"Copy-Item -LiteralPath $d.FileName -Destination $t -Force;",
|
||||
"[Console]::OutputEncoding=[Text.Encoding]::UTF8;",
|
||||
"[Console]::Write($t)}",
|
||||
})
|
||||
return commandOutput(
|
||||
'powershell -NoProfile -STA -Command "' .. script .. '"')
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
-- Parity test: the faint sound sequence matches pokered, per side.
|
||||
--
|
||||
-- RemoveFaintedPlayerMon (engine/battle/core.asm:1003-1045): the player
|
||||
-- mon's faint plays its ordinary species cry (PlayCry) -- no Faint_Fall.
|
||||
--
|
||||
-- FaintEnemyPokemon (engine/battle/core.asm:732-796): the enemy faint
|
||||
-- plays NO species cry; trainer battles play SFX_FAINT_FALL, wait for it
|
||||
-- to finish, then SFX_FAINT_THUD. Wild battles skip both and go
|
||||
-- straight to the victory music (.wild_win).
|
||||
--
|
||||
-- The port previously played the species cry AND Faint_Fall on every
|
||||
-- faint, so a fainted enemy sounded its full battle cry and a fainted
|
||||
-- player mon got the fall whistle the hardware never plays (#709).
|
||||
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||
|
||||
local T = require("tests.modkit")
|
||||
local Data = T.fixtures.fresh()
|
||||
local Font = require("src.render.Font")
|
||||
Font.load(Data)
|
||||
local TypeChart = require("src.battle.TypeChart")
|
||||
TypeChart.load(Data)
|
||||
local Pokemon = require("src.pokemon.Pokemon")
|
||||
local SaveData = require("src.core.SaveData")
|
||||
local BattleState = require("src.battle.BattleState")
|
||||
local Sound = require("src.core.Sound")
|
||||
|
||||
-- record cries and sfx instead of sounding them
|
||||
local cries, sfx = {}, {}
|
||||
Sound.playCry = function(_, species) cries[#cries + 1] = species end
|
||||
Sound.play = function(_, name) sfx[#sfx + 1] = name end
|
||||
|
||||
local function freshGame()
|
||||
local save = SaveData.newGame()
|
||||
save.player.name = "RED"
|
||||
save.player.rival = "GARY"
|
||||
save.party = { Pokemon.new(Data, "FIXMON_A", 30) }
|
||||
return { data = Data, save = save,
|
||||
input = { wasPressed = function() return false end,
|
||||
wasJustPressed = function() return false end },
|
||||
stack = { top = function() return nil end,
|
||||
push = function() end, pop = function() end } }
|
||||
end
|
||||
|
||||
local function reset()
|
||||
cries, sfx = {}, {}
|
||||
end
|
||||
|
||||
-- pump the queue until it drains or the faint sounds have all fired (the
|
||||
-- faint's sounds are actNext rows ahead of the faint text, which needs a
|
||||
-- text input stub this driver does not bother to provide)
|
||||
local function pump(battle, expectedSounds)
|
||||
local count = expectedSounds or 0
|
||||
local seen = 0
|
||||
for _ = 1, 60 do
|
||||
local before = #sfx
|
||||
local ok = pcall(battle.updateQueue, battle)
|
||||
if not ok then return false end
|
||||
if #sfx > before then seen = #sfx end
|
||||
if seen >= count then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- player mon faint: only the species cry, no Faint_Fall
|
||||
do
|
||||
reset()
|
||||
local game = freshGame()
|
||||
local battle = BattleState.newTrainer(game, "OPP_FIX_YOUNGSTER", 1)
|
||||
battle.participants = {}
|
||||
battle.playVictoryMusic = function() end
|
||||
battle:onFaint(battle.player)
|
||||
pump(battle, 1)
|
||||
T.eq(cries[1], "FIXMON_A", "the player mon's faint plays its species cry")
|
||||
T.eq(#cries, 1, "no other cry on the player faint")
|
||||
for _, name in ipairs(sfx) do
|
||||
T.check(name ~= "Faint_Fall",
|
||||
"the player faint never plays Faint_Fall (#709)")
|
||||
end
|
||||
end
|
||||
|
||||
-- enemy faint, trainer battle: Faint_Fall then Faint_Thud, no species cry
|
||||
do
|
||||
reset()
|
||||
local game = freshGame()
|
||||
local battle = BattleState.newTrainer(game, "OPP_FIX_YOUNGSTER", 1)
|
||||
battle.participants = {}
|
||||
battle.playVictoryMusic = function() end
|
||||
battle:onFaint(battle.enemy)
|
||||
pump(battle, 2)
|
||||
T.eq(#cries, 0, "the enemy faint plays no species cry")
|
||||
local fall, thud = false, false
|
||||
for i, name in ipairs(sfx) do
|
||||
if name == "Faint_Fall" then
|
||||
T.check(not fall, "Faint_Fall plays once")
|
||||
fall = true
|
||||
T.check(not thud, "Faint_Fall precedes Faint_Thud")
|
||||
elseif name == "Faint_Thud" then
|
||||
thud = true
|
||||
end
|
||||
end
|
||||
T.check(fall and thud, "trainer enemy faint plays Faint_Fall and Faint_Thud")
|
||||
end
|
||||
|
||||
-- enemy faint, wild battle: no faint sfx at all (victory music only)
|
||||
do
|
||||
reset()
|
||||
local game = freshGame()
|
||||
local battle = BattleState.newWild(game, "FIXMON_B", 5)
|
||||
battle.participants = {}
|
||||
battle.playVictoryMusic = function() end
|
||||
battle:onFaint(battle.enemy)
|
||||
pump(battle)
|
||||
T.eq(#cries, 0, "the wild enemy faint plays no species cry")
|
||||
for _, name in ipairs(sfx) do
|
||||
T.check(name ~= "Faint_Fall" and name ~= "Faint_Thud",
|
||||
"the wild enemy faint plays no faint sfx (.wild_win)")
|
||||
end
|
||||
end
|
||||
|
||||
T.finish("parity faint cry bug709")
|
||||
Reference in New Issue
Block a user