Files
gen1recomp/src/ui/PlayerPC.lua
T

315 lines
11 KiB
Lua

-- The player's item-storage PC (engine/menus/players_pc.asm):
-- WITHDRAW ITEM / DEPOSIT ITEM / TOSS ITEM / LOG OFF over
-- game.save.pcItems ({ ITEM_ID = count }; New Game seeds { POTION = 1 },
-- older saves may still be empty until first deposit). Withdraw and
-- deposit ask "How many?" via the quantity selector (key items always
-- move one); toss discards after a YES/NO confirm. Follows the
-- BoxMenu/BagMenu list idioms.
local ChoiceBox = require("src.ui.ChoiceBox")
local ListMenu = require("src.ui.ListMenu")
local Menu = require("src.ui.Menu")
local Sound = require("src.core.Sound")
local Strings = require("src.core.Strings")
local TextBox = require("src.render.TextBox")
local romText = require("src.core.RomText")
local Font = require("src.render.Font")
local Theme = require("src.ui.Theme")
local PlayerPC = {}
local function itemName(game, id)
local def = game.data.items[id]
return def and def.name or id
end
local function buildItems(game, store, order)
local items = {}
local ids = order
if not ids then
ids = {}
for id in pairs(store) do table.insert(ids, id) end
table.sort(ids)
end
for _, id in ipairs(ids) do
if store[id] then
local def = game.data.items[id]
local keyItem = (def and def.keyItem) or id:find("^HM_") ~= nil
table.insert(items, {
value = id,
label = itemName(game, id),
count = not keyItem and store[id] or nil,
})
end
end
-- the $ff terminator's row (home/list_menu.asm:371-372, 523-528)
items[#items + 1] = { cancel = true, label = Strings("CANCEL") }
return items
end
-- A on CANCEL leaves the list exactly like B (home/list_menu.asm:105-110)
local function leftOnCancel(item, list)
if item and item.cancel then
list:close()
return true
end
return false
end
-- Ask "How many?" (DepositHowManyText/WithdrawHowManyText →
-- DisplayChooseQuantityMenu) capped at the stack count. Key items and
-- HMs always move one, with no prompt (IsKeyItem in players_pc.asm).
-- cb(qty) runs only on confirm.
local function askQuantity(game, list, count, id, cb)
local def = game.data.items[id]
if (def and def.keyItem) or id:find("^HM_") then
cb(1)
return
end
local prompt = list.footer
list.footer = Strings("How many?")
local QuantityBox = require("src.ui.QuantityBox")
game.stack:push(QuantityBox.new(game, {
max = count,
onDone = function(qty)
if qty then cb(qty) else list.footer = prompt end
end,
}))
end
-- refresh the chosen row's count from `store` (or drop the row)
local function refreshRow(list, store, id)
for i, it in ipairs(list.items) do
if it.value == id then
if store[id] then
it.count = store[id]
else
table.remove(list.items, i)
list.index, list.scroll = 1, 0 -- engine/items/inventory.asm:131
end
break
end
end
list.index = math.max(1, math.min(list.index, #list.items))
end
local function pcList(game, items, opts)
local list = ListMenu.new(game, nil, items, opts)
list.pcPrompt = list.footer
list.pcCompletionBlink = 0
local draw = list.draw
list.draw = function(self)
draw(self)
if self.pcCompletion
and self.pcCompletionBlink % 60 < 30 then
Font.drawCode(Theme.moreArrow, 144, 128)
end
end
local update = list.update
list.update = function(self, dt)
if self.pcCompletion then
self.pcCompletionBlink = (self.pcCompletionBlink + 1) % 60
local input = game.input
if input:wasPressed("a") or input:wasPressed("b") then
Sound.playPress(game.data)
self.pcCompletion = nil
local after = self.pcAfter
self.pcAfter = nil
if after then after() end
self.footer = self.pcPrompt
end
return
end
update(self, dt)
end
function list:showCompletion(text, after)
self.footer = text
self.pcCompletion = true
self.pcCompletionBlink = 0
self.pcAfter = after
end
return list
end
local function withdraw(game)
local pc = game.save.pcItems
-- players_pc.asm:144-149: an empty list is never opened
if next(pc) == nil then
game.stack:push(TextBox.new(game, romText(game.data, "_NothingStoredText",
"There is nothing\nstored."), nil, { noSound = true }))
return
end
game.stack:push(pcList(game, buildItems(game, pc), {
kind = "pc_item_withdraw",
messageBox = true,
-- players_pc.asm:151-152 WhatToWithdrawText, printed before the list
footer = romText(game.data, "_WhatToWithdrawText",
"What do you want\nto withdraw?"),
noSound = true, -- PlayerPCMenu holds BIT_NO_MENU_BUTTON_SOUND (#570)
onChoose = function(item, list)
if leftOnCancel(item, list) then return end
list.hollowIndex = list.index -- home/list_menu.asm:91
askQuantity(game, list, pc[item.value] or 1, item.value, function(qty)
local Bag = require("src.inventory.Bag")
if not Bag.add(game.save, item.value, qty, game.data) then
list.footer = Strings("You can't carry\nany more items.")
return
end
pc[item.value] = pc[item.value] - qty
if pc[item.value] <= 0 then pc[item.value] = nil end
Sound.play(game.data, "Withdraw_Deposit")
list:showCompletion(romText(game.data, "_WithdrewItemText",
"Withdrew\n%s.{PROMPT}", itemName(game, item.value)),
function() refreshRow(list, pc, item.value) end) -- engine/menus/players_pc.asm:191
end)
end,
}))
end
-- wNumBoxItems capacity: 50 stacks (PC_ITEM_CAPACITY)
local function pcFull(game, pc, id)
if pc[id] then return false end -- growing an existing stack is fine
local cap = game.data.field.pcItemCap or 50
local stacks = 0
for _ in pairs(pc) do stacks = stacks + 1 end
return stacks >= cap
end
local function deposit(game)
local pc = game.save.pcItems
local inv = game.save.inventory
local Bag = require("src.inventory.Bag")
-- engine/menus/players_pc.asm:99 wListPointer = wNumBagItems, so deposit order == bag order
local order = Bag.order(game.save, game.data)
-- players_pc.asm:90-95: an empty bag never reaches the list
if #order == 0 then
game.stack:push(TextBox.new(game, romText(game.data, "_NothingToDepositText",
"You have nothing\nto deposit."), nil, { noSound = true }))
return
end
game.stack:push(pcList(game, buildItems(game, inv, order), {
kind = "pc_item_deposit",
messageBox = true,
-- players_pc.asm:97-98 WhatToDepositText, printed before the list
footer = romText(game.data, "_WhatToDepositText",
"What do you want\nto deposit?"),
noSound = true, -- PlayerPCMenu holds BIT_NO_MENU_BUTTON_SOUND (#570)
onChoose = function(item, list)
if leftOnCancel(item, list) then return end
list.hollowIndex = list.index -- home/list_menu.asm:91
askQuantity(game, list, inv[item.value] or 1, item.value, function(qty)
if pcFull(game, pc, item.value) then
list.footer = Strings("No room left to\nstore items.")
return
end
require("src.inventory.Bag").remove(game.save, item.value, qty)
pc[item.value] = (pc[item.value] or 0) + qty
Sound.play(game.data, "Withdraw_Deposit")
list:showCompletion(romText(game.data, "_ItemWasStoredText",
"%s was\nstored via PC.{PROMPT}", itemName(game, item.value)),
function() refreshRow(list, inv, item.value) end) -- engine/menus/players_pc.asm:137
end)
end,
}))
end
local function toss(game)
local pc = game.save.pcItems
-- players_pc.asm:196-201: an empty list is never opened
if next(pc) == nil then
game.stack:push(TextBox.new(game, romText(game.data, "_NothingStoredText",
"There is nothing\nstored."), nil, { noSound = true }))
return
end
game.stack:push(pcList(game, buildItems(game, pc), {
kind = "pc_item_toss",
messageBox = true,
-- players_pc.asm:205-206 WhatToTossText, printed before the list
footer = romText(game.data, "_WhatToTossText",
"What do you want\nto toss away?"),
noSound = true, -- PlayerPCMenu holds BIT_NO_MENU_BUTTON_SOUND (#570)
onChoose = function(item, list)
if leftOnCancel(item, list) then return end
list.hollowIndex = list.index -- home/list_menu.asm:91
local def = game.data.items[item.value]
if (def and def.keyItem) or item.value:find("^HM_") then
list.footer = Strings("That's too impor-\ntant to toss!")
return
end
local QuantityBox = require("src.ui.QuantityBox")
game.stack:push(QuantityBox.new(game, {
max = pc[item.value] or 1,
onDone = function(qty)
if not qty then return end
list.footer = Strings("Toss %s?", itemName(game, item.value))
game.stack:push(ChoiceBox.new(game, function(yes)
if yes then
pc[item.value] = pc[item.value] - qty
if pc[item.value] <= 0 then pc[item.value] = nil end
list:showCompletion(romText(game.data, "_ThrewAwayItemText",
"Threw away\n%s.{PROMPT}", itemName(game, item.value)),
function() refreshRow(list, pc, item.value) end) -- engine/menus/players_pc.asm:240
else
list.footer = nil
end
end, { noSound = true }))
end,
}))
end,
}))
end
-- opts.direct marks the bedroom PC (OpenRedsPC), the one entry outside the main menu
function PlayerPC.new(game, opts)
game.save.pcItems = game.save.pcItems or {}
-- ExitPlayerPC (players_pc.asm) rings SFX_TURN_OFF_PC only while
-- BIT_USING_GENERIC_PC is clear (#960)
local logOff = function()
if opts and opts.direct then Sound.play(game.data, "Turn_Off_PC") end
end
local rows = {
-- keepOpen so B in the item lists returns here instead of dropping the
-- whole PC session (players_pc.asm re-shows the PC menu); same pattern
-- as BoxMenu's rows
{ label = Strings("WITHDRAW ITEM"), keepOpen = true, onSelect = function() withdraw(game) end },
{ label = Strings("DEPOSIT ITEM"), keepOpen = true, onSelect = function() deposit(game) end },
{ label = Strings("TOSS ITEM"), keepOpen = true, onSelect = function() toss(game) end },
{ label = Strings("LOG OFF"), onSelect = logOff },
}
-- silent PC session (BIT_NO_MENU_BUTTON_SOUND); players_pc.asm
-- PlayersPCMenu TextBoxBorder (0,0) b=8 c=14 → 16x10
local menu = Menu.new(game, rows, { tx = 0, ty = 0, tw = 16, th = 10,
noSound = true, onCancel = logOff })
local prompt = TextBox.strip(romText(game.data, "_WhatDoYouWantText",
"What do you want\nto do?"))
for _, row in ipairs(rows) do
local onSelect = row.onSelect
if row.keepOpen and onSelect then
row.onSelect = function()
menu.hollowIndex = menu.index -- engine/menus/players_pc.asm:55
onSelect()
end
end
end
local baseUpdate = menu.update
function menu:update(dt)
if self.game.stack:top() == self then self.hollowIndex = nil end
return baseUpdate(self, dt)
end
local baseDraw = menu.draw
function menu:draw()
baseDraw(self)
Font.drawBox(0, 12, 20, 6) -- engine/menus/players_pc.asm:50
love.graphics.setColor(0, 0, 0, 1)
local y = 112
for line in (prompt .. "\n"):gmatch("([^\n]*)\n") do
Font.draw(line, 8, y)
y = y + 16
end
love.graphics.setColor(1, 1, 1, 1)
end
return menu
end
return PlayerPC