mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-20 20:50:21 +02:00
G2 support
This commit is contained in:
@@ -0,0 +1,279 @@
|
||||
-- The bedroom PC's DECORATION option: _PlayerDecorationMenu
|
||||
-- (engine/overworld/decorations.asm), reached from PLAYERSPCITEM_DECORATION in
|
||||
-- the PLAYERSPC_HOUSE list (engine/events/pokecenter_pc.asm).
|
||||
--
|
||||
-- Two menus stacked, plus one question:
|
||||
--
|
||||
-- .MenuHeader the categories the player owns something in, at
|
||||
-- menu_coords 5, 0, 19, 17, with EXIT always last
|
||||
-- .NonscrollingMenuHeader / .ScrollingMenuHeader
|
||||
-- that category's decorations, then its PUT IT AWAY
|
||||
-- row, then CANCEL. The cart swaps to the scrolling
|
||||
-- menu past eight rows; one list that scrolls covers
|
||||
-- both, since the rows are identical either way
|
||||
-- DecoSideMenuHeader RIGHT SIDE / LEFT SIDE / CANCEL, for the two
|
||||
-- ornament slots
|
||||
--
|
||||
-- Nothing here touches the map. wChangedDecorations is carried back through
|
||||
-- onDone so the PC can answer TRUE to `special PlayersHousePC`, whose script
|
||||
-- then does `warp NONE, 0, 0` -- the map reload that runs the two callbacks
|
||||
-- and is the only thing that makes a placement visible.
|
||||
|
||||
local Chrome = require("src.ui.gen2.Chrome")
|
||||
local Decorations = require("src.core.gen2.Decorations")
|
||||
local Strings = require("src.core.Strings")
|
||||
|
||||
local DecorationMenu = {}
|
||||
DecorationMenu.__index = DecorationMenu
|
||||
DecorationMenu.isOpaque = true
|
||||
|
||||
-- .category_pointers' eighth row, which is not a category and is always shown.
|
||||
local EXIT = Strings.source("EXIT")
|
||||
-- DecoSideMenuHeader's three items, in its order.
|
||||
local SIDES = {
|
||||
{ id = "right", label = Strings.source("RIGHT SIDE") },
|
||||
{ id = "left", label = Strings.source("LEFT SIDE") },
|
||||
{ id = nil, label = Strings.source("CANCEL") },
|
||||
}
|
||||
|
||||
-- The scrolling list shows eight rows (.ScrollingMenuData's `db 8, 0`).
|
||||
local VISIBLE = 8
|
||||
|
||||
function DecorationMenu:wantsFillScale() return true end
|
||||
|
||||
-- opts: save, events (the wEventFlags bitfield), onDone(changed)
|
||||
function DecorationMenu.new(game, opts)
|
||||
opts = opts or {}
|
||||
local self = setmetatable({}, DecorationMenu)
|
||||
self.game = game
|
||||
self.save = opts.save or (game and game.save)
|
||||
self.events = opts.events
|
||||
self.onDone = opts.onDone
|
||||
self.state = Decorations.state(self.save)
|
||||
-- wChangedDecorations, cleared once on the way in.
|
||||
self.changed = false
|
||||
self.mode = "category"
|
||||
self.index = 1
|
||||
self.scroll = 0
|
||||
self.pages = nil
|
||||
self:buildCategories()
|
||||
return self
|
||||
end
|
||||
|
||||
function DecorationMenu:buildCategories()
|
||||
self.categories = Decorations.ownedCategories(self.events)
|
||||
self.index = math.min(self.index, #self.categories + 1)
|
||||
end
|
||||
|
||||
function DecorationMenu:finish()
|
||||
if self.done then return end
|
||||
self.done = true
|
||||
if self.onDone then self.onDone(self.changed) end
|
||||
end
|
||||
|
||||
-- The name of one row of the item list. Row 0 is CANCEL and the category's
|
||||
-- own row is PUT IT AWAY; both are plain DecorationAttributes rows, so they
|
||||
-- spell themselves.
|
||||
function DecorationMenu:rowName(decoId)
|
||||
return Decorations.name(decoId)
|
||||
end
|
||||
|
||||
function DecorationMenu:openCategory(category)
|
||||
local rows = Decorations.rows(self.events, category)
|
||||
if #rows == 0 then
|
||||
-- PopulateDecoCategoryMenu .empty. Cannot happen from the filtered
|
||||
-- category list, but the routine is reachable from a driver that sets a
|
||||
-- flag by hand, and the cart answers rather than opening an empty menu.
|
||||
self:say({ Strings(Decorations.NOTHING_TO_CHOOSE) })
|
||||
return
|
||||
end
|
||||
self.category = category
|
||||
self.rows = rows
|
||||
self.mode = "items"
|
||||
self.index = 1
|
||||
self.scroll = 0
|
||||
end
|
||||
|
||||
function DecorationMenu:say(pages)
|
||||
if not pages or #pages == 0 then return end
|
||||
self.pages = pages
|
||||
self.pageIndex = 1
|
||||
end
|
||||
|
||||
-- MenuTextboxBackup returns to whatever menu was underneath, so a message is
|
||||
-- not a mode of its own: it sits over the list it was printed from.
|
||||
function DecorationMenu:advanceMessage()
|
||||
self.pageIndex = self.pageIndex + 1
|
||||
if self.pageIndex <= #self.pages then return end
|
||||
self.pages = nil
|
||||
end
|
||||
|
||||
-- One row of the item list chosen: DoDecorationAction2. An ornament row asks
|
||||
-- which side first, and only then applies.
|
||||
function DecorationMenu:chooseRow(decoId)
|
||||
local attr = Decorations.attributes(decoId)
|
||||
local action = attr and attr.action and Decorations.ACTIONS[attr.action]
|
||||
if not action then
|
||||
-- DecoAction_nothing sets carry, which drops the player back to the
|
||||
-- category list without a word.
|
||||
self.mode = "category"
|
||||
self:buildCategories()
|
||||
return
|
||||
end
|
||||
if action.ornament then
|
||||
self.mode = "side"
|
||||
self.pendingDeco = decoId
|
||||
self.sideIndex = 1
|
||||
return
|
||||
end
|
||||
self:applyRow(decoId, nil)
|
||||
end
|
||||
|
||||
function DecorationMenu:applyRow(decoId, side)
|
||||
local changed, pages = Decorations.apply(self.state, decoId, side)
|
||||
if changed then
|
||||
self.changed = true
|
||||
local attr = Decorations.attributes(decoId)
|
||||
local action = attr and attr.action and Decorations.ACTIONS[attr.action]
|
||||
if action and action.ornament and not action.put then
|
||||
Decorations.clearOtherSide(self.state, decoId, side)
|
||||
end
|
||||
end
|
||||
self:say(pages)
|
||||
end
|
||||
|
||||
function DecorationMenu:update(_dt)
|
||||
local input = self.game and self.game.input
|
||||
if not (input and not self.done) then return end
|
||||
|
||||
if self.pages then
|
||||
if input:wasPressed("a") or input:wasPressed("b") then
|
||||
self:advanceMessage()
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
if self.mode == "side" then
|
||||
if input:wasPressed("up") then
|
||||
self.sideIndex = self.sideIndex > 1 and self.sideIndex - 1 or #SIDES
|
||||
elseif input:wasPressed("down") then
|
||||
self.sideIndex = self.sideIndex < #SIDES and self.sideIndex + 1 or 1
|
||||
elseif input:wasPressed("a") then
|
||||
local pick = SIDES[self.sideIndex]
|
||||
self.mode = "items"
|
||||
-- .nope: CANCEL and B are the same arm, and neither prints anything.
|
||||
self:applyRow(self.pendingDeco, pick and pick.id)
|
||||
self.pendingDeco = nil
|
||||
elseif input:wasPressed("b") then
|
||||
self.mode = "items"
|
||||
self.pendingDeco = nil
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
if self.mode == "items" then
|
||||
local total = #self.rows
|
||||
if input:wasPressed("up") then
|
||||
self.index = self.index > 1 and self.index - 1 or total
|
||||
elseif input:wasPressed("down") then
|
||||
self.index = self.index < total and self.index + 1 or 1
|
||||
elseif input:wasPressed("a") then
|
||||
self:chooseRow(self.rows[self.index])
|
||||
elseif input:wasPressed("b") then
|
||||
self.mode = "category"
|
||||
self:buildCategories()
|
||||
end
|
||||
self.scroll = math.max(0,
|
||||
math.min(self.scroll, math.max(0, total - VISIBLE)))
|
||||
if self.index - 1 < self.scroll then self.scroll = self.index - 1 end
|
||||
if self.index > self.scroll + VISIBLE then
|
||||
self.scroll = self.index - VISIBLE
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
local total = #self.categories + 1
|
||||
if input:wasPressed("up") then
|
||||
self.index = self.index > 1 and self.index - 1 or total
|
||||
elseif input:wasPressed("down") then
|
||||
self.index = self.index < total and self.index + 1 or 1
|
||||
elseif input:wasPressed("a") then
|
||||
local category = self.categories[self.index]
|
||||
if category then
|
||||
self:openCategory(category)
|
||||
else
|
||||
self:finish() -- DecoExitMenu: `scf`, which leaves the whole menu
|
||||
end
|
||||
elseif input:wasPressed("b") then
|
||||
self:finish()
|
||||
end
|
||||
end
|
||||
|
||||
function DecorationMenu:drawList(x, y, w, h, labels, index, scroll)
|
||||
Chrome.box(x, y, w, h)
|
||||
-- GetMenuTextStartCoord: the first label sits one row inside the border and
|
||||
-- one more down for STATICMENU_CURSOR, and rows are two apart -- the same
|
||||
-- spacing every other Gold list menu in this port draws with.
|
||||
for row = 1, math.min(#labels - scroll, VISIBLE) do
|
||||
local i = row + scroll
|
||||
local ty = y + row * 2
|
||||
if i == index then Chrome.cursor(x + 1, ty) end
|
||||
Chrome.print(labels[i], x + 2, ty)
|
||||
end
|
||||
end
|
||||
|
||||
function DecorationMenu:drawPanel()
|
||||
Chrome.clear()
|
||||
|
||||
if self.mode == "category" then
|
||||
local labels = {}
|
||||
for i, category in ipairs(self.categories) do labels[i] = category.label end
|
||||
labels[#labels + 1] = Strings(EXIT)
|
||||
-- menu_coords 5, 0, 19, 17: the list hugs the right edge and the room
|
||||
-- stays visible down the left, which is the whole point of the offset.
|
||||
self:drawList(5, 0, 15, 18, labels, self.index, 0)
|
||||
elseif self.mode == "items" then
|
||||
local labels = {}
|
||||
for i, decoId in ipairs(self.rows) do labels[i] = self:rowName(decoId) end
|
||||
self:drawList(0, 0, 20, 18, labels, self.index, self.scroll)
|
||||
end
|
||||
|
||||
if self.mode == "side" then
|
||||
local labels = {}
|
||||
for i, side in ipairs(SIDES) do labels[i] = Strings(side.label) end
|
||||
-- menu_coords 0, 0, 12, 7
|
||||
self:drawList(0, 0, 13, 8, labels, self.sideIndex, 0)
|
||||
end
|
||||
|
||||
if self.pages then
|
||||
Chrome.box(0, 12, 20, 6)
|
||||
local line = 14
|
||||
for part in ((self.pages[self.pageIndex] or "") .. "\n"):gmatch("(.-)\n") do
|
||||
Chrome.print(part, 1, line)
|
||||
line = line + 2
|
||||
end
|
||||
end
|
||||
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
end
|
||||
|
||||
function DecorationMenu:draw()
|
||||
self:drawPanel()
|
||||
end
|
||||
|
||||
function DecorationMenu:drawWidescreen(winW, winH)
|
||||
local G = love.graphics
|
||||
G.setColor(1, 1, 1, 1)
|
||||
G.rectangle("fill", 0, 0, winW, winH)
|
||||
local scale = Chrome.fitScale(winW, winH)
|
||||
G.push()
|
||||
G.translate(math.floor((winW - 160 * scale) / 2),
|
||||
math.floor((winH - 144 * scale) / 2))
|
||||
G.scale(scale, scale)
|
||||
self:drawPanel()
|
||||
G.pop()
|
||||
end
|
||||
|
||||
function DecorationMenu:drawsWidescreen() return true end
|
||||
|
||||
return DecorationMenu
|
||||
Reference in New Issue
Block a user