fix(game3): start player item PC with single Potion

This commit is contained in:
1jamie
2026-09-15 17:06:11 -05:00
parent 06dd4ffba8
commit ff18f605c2
7 changed files with 128 additions and 12 deletions
+1 -1
View File
@@ -61,7 +61,7 @@ function Bridge.enterFromHost(mod, game, opts)
healY = sc.healY or 5,
move_overlay = sc.move_overlay or {},
options = sc.options or {},
storage = require("src.core.game3.storage").restore(sc.storage, sc.pc),
storage = require("src.core.game3.storage").restore(sc.storage, sc.pc, sc.pcItems or save.pcItems or save.pc_items),
enteredAt = os.time(),
}
+1 -1
View File
@@ -145,7 +145,7 @@ function Schema.fromSaveTable(save)
vars = save.vars or {},
playtime = save.playTime or save.playtime or { hours = 0, minutes = 0, seconds = 0 },
options = save.options,
storage = require("src.core.game3.storage").restore(save.storage, save.pc),
storage = require("src.core.game3.storage").restore(save.storage, save.pc, save.pcItems or save.pc_items),
registeredItem = save.registeredItem,
move_overlay = save.move_overlay or {},
trainerId = save.trainerId,
+34 -9
View File
@@ -39,7 +39,7 @@ function Storage.new()
local storage = {
currentBox = 1,
boxes = {},
items = {}, -- 50-slot Player PC item storage
items = { { id = 13, qty = 1 } }, -- 50-slot Player PC item storage (starts with 1 POTION, pokefirered/src/player_pc.c:100)
}
for b = 1, Storage.TOTAL_BOXES_COUNT do
storage.boxes[b] = {
@@ -60,7 +60,7 @@ function Storage.ensure(session)
if not session.storage.boxes or #session.storage.boxes < Storage.TOTAL_BOXES_COUNT then
local fresh = Storage.new()
fresh.currentBox = session.storage.currentBox or 1
fresh.items = session.storage.items or {}
fresh.items = session.storage.items or fresh.items
for b = 1, Storage.TOTAL_BOXES_COUNT do
if session.storage.boxes and session.storage.boxes[b] then
fresh.boxes[b] = session.storage.boxes[b]
@@ -69,7 +69,7 @@ function Storage.ensure(session)
session.storage = fresh
end
if not session.storage.items then
session.storage.items = {}
session.storage.items = { { id = 13, qty = 1 } }
end
return session.storage
end
@@ -436,10 +436,12 @@ function Storage.deserialize(data)
local storage = Storage.new()
if not data then return storage end
storage.currentBox = tonumber(data.currentBox) or 1
storage.items = {}
for _, item in ipairs(data.items or {}) do
if item and item.id and (tonumber(item.qty) or 0) > 0 then
storage.items[#storage.items + 1] = { id = item.id, qty = item.qty }
if data.items ~= nil then
storage.items = {}
for _, item in ipairs(data.items) do
if item and item.id and (tonumber(item.qty) or 0) > 0 then
storage.items[#storage.items + 1] = { id = item.id, qty = item.qty }
end
end
end
for b = 1, Storage.TOTAL_BOXES_COUNT do
@@ -459,13 +461,17 @@ function Storage.deserialize(data)
return storage
end
function Storage.restore(data, legacyPc)
function Storage.restore(data, legacyPc, pcItems)
local hasData = type(data) == "table"
local hasPc = type(legacyPc) == "table"
if not hasData and not hasPc then return nil end
local hasPcItems = type(pcItems) == "table"
if not hasData and not hasPc and not hasPcItems then
return Storage.new()
end
local storage = Storage.deserialize(hasData and data or nil)
if hasPc then
if not hasData then
storage.items = {}
for _, it in ipairs(legacyPc.items or {}) do
local id = type(it) == "table" and tonumber(it.id or it.itemId)
local qty = type(it) == "table" and (tonumber(it.qty or it.quantity) or 0) or 0
@@ -481,6 +487,25 @@ function Storage.restore(data, legacyPc)
storage.boxes[b].mons[s] = mon
end
end
elseif not hasData and hasPcItems then
storage.items = {}
for k, v in pairs(pcItems) do
local id = nil
local qty = 0
if type(k) == "number" and type(v) == "table" then
id = tonumber(v.id or v.itemId)
qty = tonumber(v.qty or v.quantity or v.count) or 0
elseif type(k) == "string" and type(v) == "number" then
id = ItemsData.toNumericId(k)
qty = v
elseif type(k) == "number" and type(v) == "number" then
id = k
qty = v
end
if id and qty > 0 and #storage.items < Storage.PC_ITEMS_COUNT then
storage.items[#storage.items + 1] = { id = id, qty = math.min(Storage.MAX_ITEM_QTY, qty) }
end
end
end
return storage
end
+1
View File
@@ -124,6 +124,7 @@ function SummaryMenu.openMenu(party, startIndex, opts)
opts = opts or {}
SummaryMenu.open = true
SummaryMenu._party = party or {}
SummaryMenu._cursor = startIndex or 1
local session = opts.playerState or opts.session
or (package.loaded["src.core.game3.runtime"] and package.loaded["src.core.game3.runtime"].getSession and package.loaded["src.core.game3.runtime"].getSession())
SummaryMenu._playerState = session
+2 -1
View File
@@ -136,7 +136,7 @@ local restored = Schema.fromSaveTable(saved)
check(restored.storage and restored.storage.items[1] and restored.storage.items[1].id == 13,
"fromSaveTable restores storage")
local legacy = Schema.fromSaveTable({ map = "FR_PALLET_TOWN", x = 1, y = 1 })
check(legacy.storage == nil, "legacy save without storage gets no seeded POTION")
check(legacy.storage and legacy.storage.items[1] and legacy.storage.items[1].id == 13, "legacy save without storage gets seeded POTION")
check(Storage.withdrawItem(session, 1, 1) == true, "withdrawItem POTION ok")
check(Bag.has(session.bag, 13, 1), "bag has POTION")
@@ -156,6 +156,7 @@ check(joined(PcMenu.ITEM_STORAGE_ACTIONS) == "WITHDRAW ITEM/DEPOSIT ITEM/CANCEL"
local seLog = {}
package.loaded["src.core.game3.audio"].playSe = function(id) seLog[#seLog + 1] = id end
local s6 = { bag = Bag.new(), storage = Storage.new() }
s6.storage.items = {} -- clear to test empty WITHDRAW message
local closed6 = 0
PcMenu.show({ session = s6, startMode = "player_pc", closeOnExit = true,
onClose = function() closed6 = closed6 + 1 end })
+85
View File
@@ -0,0 +1,85 @@
#!/usr/bin/env luajit
-- Test: Game 3 Player Item PC starting Potion fidelity (pokefirered player_pc.c:100-101)
local Storage = require("src.core.game3.storage")
local Schema = require("src.core.game3.save_schema_firered")
local Bag = require("src.core.game3.bag")
local Bridge = require("src.core.game3.bridge")
local failures = 0
local function check(cond, msg)
if cond then
print("[PASS] " .. msg)
else
failures = failures + 1
print("[FAIL] " .. msg)
end
end
print("=== [TEST 1] Storage.new() starts with 1 Potion ===")
local s = Storage.new()
check(#s.items == 1, "Storage.new has 1 PC item")
check(s.items[1].id == 13 and s.items[1].qty == 1, "Storage.new item is POTION x1 (id 13)")
print("=== [TEST 2] Storage.ensure() seeds 1 Potion for fresh sessions ===")
local sess1 = {}
Storage.ensure(sess1)
check(sess1.storage and #sess1.storage.items == 1, "Storage.ensure populated storage.items")
check(sess1.storage.items[1].id == 13 and sess1.storage.items[1].qty == 1, "Storage.ensure item is POTION x1")
local sess2 = { storage = { currentBox = 1, boxes = {} } }
Storage.ensure(sess2)
check(sess2.storage and #sess2.storage.items == 1, "Storage.ensure populated missing items list")
check(sess2.storage.items[1].id == 13 and sess2.storage.items[1].qty == 1, "Storage.ensure item is POTION x1")
print("=== [TEST 3] Storage.restore() handles fresh, legacy, and pcItems ===")
local rNil = Storage.restore(nil, nil, nil)
check(rNil and #rNil.items == 1 and rNil.items[1].id == 13, "Storage.restore(nil, nil, nil) has POTION x1")
local rMap = Storage.restore(nil, nil, { POTION = 1 })
check(rMap and #rMap.items == 1 and rMap.items[1].id == 13 and rMap.items[1].qty == 1, "Storage.restore from host pcItems={POTION=1}")
local rIdMap = Storage.restore(nil, nil, { [13] = 1 })
check(rIdMap and #rIdMap.items == 1 and rIdMap.items[1].id == 13 and rIdMap.items[1].qty == 1, "Storage.restore from id pcItems={[13]=1}")
print("=== [TEST 4] Schema.newGame() starts with 1 Potion ===")
local ng = Schema.newGame({ rngSeed = 100 })
check(ng.storage and #ng.storage.items == 1, "Schema.newGame has 1 PC item")
check(ng.storage.items[1].id == 13 and ng.storage.items[1].qty == 1, "Schema.newGame PC item is POTION x1")
print("=== [TEST 5] Schema.fromSaveTable() round-trip and legacy migration ===")
local saved = Schema.toSaveTable(ng)
local loaded = Schema.fromSaveTable(saved)
check(loaded.storage and #loaded.storage.items == 1 and loaded.storage.items[1].id == 13, "fromSaveTable loaded saved storage")
local legacySave = { map = "FR_PLAYERS_HOUSE_2F", x = 1, y = 2, pcItems = { POTION = 1 } }
local legacyLoaded = Schema.fromSaveTable(legacySave)
check(legacyLoaded.storage and #legacyLoaded.storage.items == 1 and legacyLoaded.storage.items[1].id == 13, "fromSaveTable migrated legacy pcItems")
print("=== [TEST 6] Bridge.enterFromHost() starts with 1 Potion ===")
local hostSave = {
playerName = "RED",
party = {},
inventory = {},
pcItems = { POTION = 1 },
}
local bridgeSession = Bridge.enterFromHost(nil, { save = hostSave }, { map = "FR_PLAYERS_HOUSE_2F" })
check(bridgeSession.storage and #bridgeSession.storage.items == 1, "Bridge session has 1 PC item")
check(bridgeSession.storage.items[1].id == 13 and bridgeSession.storage.items[1].qty == 1, "Bridge session item is POTION x1")
print("=== [TEST 7] Withdrawing Potion leaves PC empty and persists after save ===")
check(Storage.withdrawItem(ng, 1, 1) == true, "Withdrew POTION from PC")
check(Bag.has(ng.bag, 13, 1), "Bag has POTION")
check(#ng.storage.items == 0, "PC is now empty")
local savedEmpty = Schema.toSaveTable(ng)
local loadedEmpty = Schema.fromSaveTable(savedEmpty)
check(#loadedEmpty.storage.items == 0, "Empty PC remains empty after save/load")
if failures == 0 then
print("\nALL ITEM PC POTION TESTS PASSED (100%)")
os.exit(0)
else
print(string.format("\n%d test(s) failed", failures))
os.exit(1)
end
+4
View File
@@ -45,6 +45,9 @@ for b = 1, 14 do
assert_eq(s.boxes[b].wallpaper, ((b - 1) % 16) + 1, "Box wallpaper initialized")
assert_eq(Storage.countBoxMons(s, b), 0, "Box starts empty")
end
assert_eq(#s.items, 1, "PC starts with 1 item (Potion)")
assert_eq(s.items[1].id, 13, "PC starts with Potion (id 13)")
assert_eq(s.items[1].qty, 1, "PC starts with 1 Potion")
print("[ok] 14 boxes and 50-slot PC initialized successfully")
print("=== [TEST 2] The PC Heal Exploit ===")
@@ -165,6 +168,7 @@ local pcItemSession = {
bag = Bag.new(),
storage = Storage.new(),
}
pcItemSession.storage.items = {}
-- Give 50 Poké Balls to Bag
Bag.add(pcItemSession.bag, 4, 50) -- POKE_BALL = 4
assert_eq(Bag.get(pcItemSession.bag, 4), 50, "Bag has 50 Poké Balls")