mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-09-28 23:00:21 -04:00
Merge pull request #2530 from bryanthaboi/Nexhas28
This commit is contained in:
@@ -347,7 +347,8 @@ function Schema.fromSaveTable(save)
|
||||
engine = save.engine or "game3",
|
||||
version = version,
|
||||
generation = tonumber(save.generation) or 3,
|
||||
party = save.party or {},
|
||||
-- A party saved with gaps (older PC builds) is closed up on load.
|
||||
party = require("src.core.game3.storage").compactParty(save.party or {}),
|
||||
bag = bag,
|
||||
dex = save.dex or {},
|
||||
money = save.money or 0,
|
||||
|
||||
+22
-15
@@ -32,6 +32,23 @@ local function script_store(session)
|
||||
return (Space and Space.store) or (session and session.store) or nil
|
||||
end
|
||||
|
||||
--- Close gaps in the party in place so mons fill slots 1..n in order.
|
||||
-- pokefirered/src/pokemon.c CompactPartySlots
|
||||
function Storage.compactParty(party)
|
||||
if type(party) ~= "table" then return party end
|
||||
local keys = {}
|
||||
for k, m in pairs(party) do
|
||||
local n = tonumber(k)
|
||||
if n and m ~= nil then keys[#keys + 1] = { n = n, k = k } end
|
||||
end
|
||||
table.sort(keys, function(a, b) return a.n < b.n end)
|
||||
local mons = {}
|
||||
for i, e in ipairs(keys) do mons[i] = party[e.k] end
|
||||
for _, e in ipairs(keys) do party[e.k] = nil end
|
||||
for i, m in ipairs(mons) do party[i] = m end
|
||||
return party
|
||||
end
|
||||
|
||||
--- Create a fresh Storage instance (14 boxes, 30 slots each, 50-item PC).
|
||||
function Storage.new()
|
||||
local storage = {
|
||||
@@ -240,7 +257,9 @@ function Storage.moveMon(session, srcLoc, srcIdx, destLoc, destIdx, srcBox, dest
|
||||
if not srcMon then return false, "src_empty" end
|
||||
|
||||
-- Cannot leave party empty if withdrawing/moving away
|
||||
if srcLoc == "party" and destLoc == "box" and not destMon and #session.party <= 1 then
|
||||
local partyCount = 0
|
||||
for _, m in pairs(session.party) do if m ~= nil then partyCount = partyCount + 1 end end
|
||||
if srcLoc == "party" and destLoc == "box" and not destMon and partyCount <= 1 then
|
||||
return false, "last_pokemon"
|
||||
end
|
||||
|
||||
@@ -259,20 +278,6 @@ function Storage.moveMon(session, srcLoc, srcIdx, destLoc, destIdx, srcBox, dest
|
||||
-- Assign to src
|
||||
if srcLoc == "party" then
|
||||
session.party[srcIdx] = destMon
|
||||
-- Clean up trailing nils in party array if moved without swap
|
||||
if not destMon and srcIdx > #session.party then
|
||||
local keys = {}
|
||||
for k in pairs(session.party) do
|
||||
if type(k) == "number" then keys[#keys + 1] = k end
|
||||
end
|
||||
table.sort(keys)
|
||||
local newParty = {}
|
||||
for _, k in ipairs(keys) do
|
||||
local m = session.party[k]
|
||||
if m then newParty[#newParty + 1] = m end
|
||||
end
|
||||
session.party = newParty
|
||||
end
|
||||
elseif srcLoc == "box" then
|
||||
local box = storage.boxes[srcBox or storage.currentBox]
|
||||
box.mons[srcIdx] = destMon
|
||||
@@ -297,6 +302,8 @@ function Storage.moveMon(session, srcLoc, srcIdx, destLoc, destIdx, srcBox, dest
|
||||
elseif srcLoc=="party" then
|
||||
Q.event(session,"DepositedMonInPC",{D0=srcName,D1=dstBoxName})
|
||||
else Q.event(session,"WithdrewMonFromPC",{D0=srcBoxName,D1=srcName}) end
|
||||
-- A mon moved out of (or into) the middle of the party must not leave a gap.
|
||||
Storage.compactParty(session.party)
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
@@ -153,6 +153,8 @@ function BoxStorageUI.show(opts)
|
||||
BoxStorageUI._actionSource = nil
|
||||
BoxStorageUI._actionTarget = nil
|
||||
local storage = Storage.ensure(BoxStorageUI._session)
|
||||
-- Repair a party left with gaps by an older build before any slot is indexed.
|
||||
if BoxStorageUI._session then Storage.compactParty(BoxStorageUI._session.party) end
|
||||
-- pokefirered/src/pokemon_storage_system_tasks.c:426
|
||||
BoxStorageUI._lastUsedBox = storage and ((tonumber(storage.currentBox) or 1) - 1) or nil
|
||||
|
||||
@@ -379,6 +381,8 @@ function BoxStorageUI.handleInput(input)
|
||||
se(se_id("SE_BAG_POCKET"))
|
||||
end
|
||||
end
|
||||
-- pokefirered/src/pokemon_storage_system_tasks.c SetUpHidePartyMenu -> CompactPartySlots
|
||||
Storage.compactParty(party)
|
||||
elseif mon then
|
||||
BoxStorageUI._actionSource = "party"
|
||||
BoxStorageUI._actionTarget = { mon = mon, loc = "party", boxId = nil, slot = pIdx }
|
||||
|
||||
@@ -2490,6 +2490,16 @@ local function slot_description(slot, mon)
|
||||
local item = PartyMenu._item
|
||||
if not item or PartyMenu._battle then return nil end
|
||||
if PartyMenu.mode ~= "use" and PartyMenu.mode ~= "message" then return nil end
|
||||
-- pokefirered/src/party_menu.c:856 TM/HM -> DisplayPartyPokemonDataToTeachMove
|
||||
if ItemsData.isTm(item) then
|
||||
-- pokefirered/src/party_menu.c:4760 CanMonLearnTMTutor
|
||||
if Pokemon.isEgg(mon) then return desc_text("NOT_ABLE_2") end
|
||||
local moveId = Pokemon.moveFromTmItem(item)
|
||||
local species = tonumber(mon.species or mon.speciesId)
|
||||
if not moveId or not Pokemon.canLearnTmItem(species, item) then return desc_text("NOT_ABLE_2") end
|
||||
if Pokemon.knowsMove(mon, moveId) then return desc_text("LEARNED") end
|
||||
return desc_text("ABLE_2")
|
||||
end
|
||||
if not is_evolution_stone(item) then return nil end
|
||||
local Evolution = require("src.core.game3.evolution")
|
||||
if Evolution.itemCheck(mon, item) then return nil end
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env luajit
|
||||
-- #2494: moving a party mon out of the middle of the party in the PC left a
|
||||
-- hole, so later slots showed as empty in battle and could not be filled.
|
||||
-- pokefirered/src/pokemon.c CompactPartySlots keeps the party packed.
|
||||
|
||||
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||
|
||||
require("tests.game3_cache").stubSpeciesNames()
|
||||
require("tests.fixture_data.game3_items").install()
|
||||
package.loaded["src.core.game3.rom_text"] = {
|
||||
plain = function(key) return key end, box = function(key) return key end,
|
||||
ascii = function(key) return key end, has = function() return true end,
|
||||
key = function(n, i) return n .. "[" .. i .. "]" end,
|
||||
at = function(n, i) return n .. "[" .. i .. "]" end,
|
||||
count = function() return 0 end, list = function() return {} end,
|
||||
lazy = function(map) return setmetatable({}, { __index = function(_, k) return map[k] end }) end,
|
||||
}
|
||||
|
||||
local Storage = require("src.core.game3.storage")
|
||||
local BoxStorageUI = require("src.ui.game3.box_storage_ui")
|
||||
|
||||
local failed = 0
|
||||
local function check(cond, msg)
|
||||
if cond then
|
||||
print("[ok] " .. msg)
|
||||
else
|
||||
failed = failed + 1
|
||||
print("[FAIL] " .. msg)
|
||||
end
|
||||
end
|
||||
|
||||
local function mon(species)
|
||||
return { species = species, speciesId = species, level = 5, hp = 20, maxHp = 20, moves = { 33 }, pp = { 35 } }
|
||||
end
|
||||
|
||||
local function packed(party, n)
|
||||
for i = 1, n do
|
||||
if party[i] == nil then return false end
|
||||
end
|
||||
for k in pairs(party) do
|
||||
if type(k) ~= "number" or k > n then return false end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
print("[test] 1. moving slot 3 of 6 into a box closes the gap")
|
||||
do
|
||||
local session = { party = { mon(1), mon(4), mon(7), mon(10), mon(13), mon(16) } }
|
||||
local party = session.party
|
||||
Storage.ensure(session)
|
||||
local ok = Storage.moveMon(session, "party", 3, "box", 1, nil, 1)
|
||||
check(ok == true, "the move succeeds")
|
||||
check(session.party == party, "the party table keeps its identity")
|
||||
check(packed(session.party, 5), "the party is packed into slots 1..5")
|
||||
check(session.party[3].species == 10 and session.party[5].species == 16,
|
||||
"later mons shift up in order")
|
||||
end
|
||||
|
||||
print("[test] 2. placing a box mon into an empty far party slot packs it")
|
||||
do
|
||||
local session = { party = { mon(1), mon(4) } }
|
||||
Storage.ensure(session)
|
||||
BoxStorageUI.show({ session = session })
|
||||
local storage = Storage.ensure(session)
|
||||
storage.boxes[1].mons[1] = mon(25)
|
||||
BoxStorageUI.mode = "party_drawer"
|
||||
BoxStorageUI.drawerOpen = true
|
||||
BoxStorageUI.holdingMon = storage.boxes[1].mons[1]
|
||||
BoxStorageUI.holdingSource = { loc = "box", boxId = 1, slot = 1 }
|
||||
BoxStorageUI.partyCursor = 6
|
||||
BoxStorageUI.handleInput({
|
||||
wasPressed = function(_, k) return k == "a" end,
|
||||
isDown = function(_, k) return k == "a" end,
|
||||
})
|
||||
check(packed(session.party, 3), "the party is packed into slots 1..3")
|
||||
check(session.party[3] and session.party[3].species == 25, "the placed mon lands in slot 3")
|
||||
check(storage.boxes[1].mons[1] == nil, "the box slot is emptied")
|
||||
BoxStorageUI.close()
|
||||
end
|
||||
|
||||
print("[test] 3. a party already saved with a gap is repaired")
|
||||
do
|
||||
local party = { mon(1), mon(4), nil, mon(10), mon(13) }
|
||||
party[3] = nil
|
||||
Storage.compactParty(party)
|
||||
check(packed(party, 4), "numeric gaps close")
|
||||
local stringy = { ["1"] = mon(1), ["2"] = mon(4), ["4"] = mon(10) }
|
||||
Storage.compactParty(stringy)
|
||||
check(packed(stringy, 3) and stringy[3].species == 10, "string-keyed slots close too")
|
||||
end
|
||||
|
||||
if failed > 0 then
|
||||
print(string.format("[FAIL] %d check(s) failed", failed))
|
||||
os.exit(1)
|
||||
end
|
||||
print("[PASS] game3_pc_party_gap_2494")
|
||||
@@ -0,0 +1,80 @@
|
||||
#!/usr/bin/env luajit
|
||||
-- #2520: choosing a TM/HM from the bag opens the party menu without the
|
||||
-- ABLE! / NOT ABLE! / LEARNED labels on each slot.
|
||||
-- pokefirered/src/party_menu.c:856 DisplayPartyPokemonDataForMoveTutorOrEvolutionItem
|
||||
-- -> DisplayPartyPokemonDataToTeachMove.
|
||||
|
||||
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||
|
||||
require("tests.game3_cache").stubSpeciesNames()
|
||||
require("tests.fixture_data.game3_items").install()
|
||||
|
||||
-- pokefirered/src/data/party_menu.h:634 sDescriptionStringTable
|
||||
local DESCRIPTIONS = { [0] = "NO USE", "ABLE", "FIRST", "SECOND", "THIRD", "ABLE",
|
||||
"NOT ABLE", "ABLE!", "NOT ABLE!", "LEARNED" }
|
||||
package.loaded["src.core.game3.rom_text"] = {
|
||||
plain = function(key) return key end, box = function(key) return key end,
|
||||
ascii = function(key) return key end, has = function() return true end,
|
||||
key = function(n, i) return n .. "[" .. tostring(i) .. "]" end,
|
||||
at = function(n, i)
|
||||
if n == "sDescriptionStringTable" then return DESCRIPTIONS[i] end
|
||||
return n .. "[" .. tostring(i) .. "]"
|
||||
end,
|
||||
count = function() return 0 end, list = function() return {} end,
|
||||
lazy = function(map) return setmetatable({}, { __index = function(_, k) return map[k] end }) end,
|
||||
}
|
||||
|
||||
local ItemsData = require("src.core.game3.items_data")
|
||||
local Pokemon = require("src.core.game3.pokemon")
|
||||
|
||||
local TM06, TOXIC = 294, 92
|
||||
local BULBASAUR, CHARMANDER, GEODUDE = 1, 4, 74
|
||||
|
||||
ItemsData.isTm = function(id) return id == TM06 end
|
||||
Pokemon.moveFromTmItem = function(id) return id == TM06 and TOXIC or nil end
|
||||
Pokemon.canLearnTmItem = function(species) return species ~= CHARMANDER end
|
||||
|
||||
local PartyMenu = require("src.ui.game3.party_menu")
|
||||
|
||||
local failed = 0
|
||||
local function check(cond, msg)
|
||||
if cond then
|
||||
print("[ok] " .. msg)
|
||||
else
|
||||
failed = failed + 1
|
||||
print("[FAIL] " .. msg)
|
||||
end
|
||||
end
|
||||
|
||||
local function mon(species, moves)
|
||||
return { species = species, speciesId = species, level = 10, hp = 20, maxHp = 20,
|
||||
moves = moves or { 33 }, pp = { 35 } }
|
||||
end
|
||||
|
||||
local party = {
|
||||
mon(BULBASAUR),
|
||||
mon(CHARMANDER),
|
||||
mon(GEODUDE, { 33, TOXIC }),
|
||||
{ species = BULBASAUR, speciesId = BULBASAUR, isEgg = true, hp = 0, maxHp = 0, moves = {} },
|
||||
}
|
||||
PartyMenu.show(party, nil, { mode = "use", item = TM06 })
|
||||
PartyMenu._item = TM06
|
||||
PartyMenu.mode = "use"
|
||||
|
||||
check(PartyMenu.slotDescription(1) == "ABLE!",
|
||||
"a compatible mon reads ABLE!, got " .. tostring(PartyMenu.slotDescription(1)))
|
||||
check(PartyMenu.slotDescription(2) == "NOT ABLE!",
|
||||
"an incompatible mon reads NOT ABLE!, got " .. tostring(PartyMenu.slotDescription(2)))
|
||||
check(PartyMenu.slotDescription(3) == "LEARNED",
|
||||
"a mon that knows the move reads LEARNED, got " .. tostring(PartyMenu.slotDescription(3)))
|
||||
check(PartyMenu.slotDescription(4) == "NOT ABLE!",
|
||||
"an egg reads NOT ABLE!, got " .. tostring(PartyMenu.slotDescription(4)))
|
||||
|
||||
PartyMenu._item = 13 -- a Potion: no label
|
||||
check(PartyMenu.slotDescription(1) == nil, "a non-TM item shows no label")
|
||||
|
||||
if failed > 0 then
|
||||
print(string.format("[FAIL] %d check(s) failed", failed))
|
||||
os.exit(1)
|
||||
end
|
||||
print("[PASS] game3_tm_able_desc_2520")
|
||||
Reference in New Issue
Block a user