mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-23 05:58:26 +02:00
G2 support
This commit is contained in:
@@ -0,0 +1,415 @@
|
||||
-- The MAILBOX in the player's PC: _PlayerMailBoxMenu and MailboxPC
|
||||
-- (engine/pokemon/mail.asm).
|
||||
--
|
||||
-- InitMail runs first and answers z when sMailboxCount is 0, which is the one
|
||||
-- branch that never opens a menu at all -- an empty MAILBOX is
|
||||
-- _EmptyMailboxText and nothing else.
|
||||
--
|
||||
-- Layout, from the two headers:
|
||||
-- .TopMenuHeader menu_coords 8, 1, 18, 10 -- a scrolling menu, four rows,
|
||||
-- each printed by MailboxPC_PrintMailAuthor, i.e. the
|
||||
-- AUTHOR of the letter and not its message
|
||||
-- .SubMenuHeader menu_coords 0, 0, 13, 9 with STATICMENU_CURSOR, so its
|
||||
-- four labels start at (2,2) and step two rows
|
||||
--
|
||||
-- The submenu's four rows are .Jumptable's four routines:
|
||||
--
|
||||
-- READ MAIL ReadMailMessage, which is the Gen2MailRead screen
|
||||
-- PUT IN PACK "message will be lost. OK?" -> ReceiveItem, then
|
||||
-- DeleteMailFromPC. The letter is destroyed and the
|
||||
-- STATIONERY goes back in the bag, which is why the question
|
||||
-- is asked before the bag is even checked
|
||||
-- ATTACH MAIL the party list, refusing an EGG and a mon that is already
|
||||
-- holding anything, then MoveMailFromPCToParty. The cart
|
||||
-- LOOPS on both refusals (`jr .try_again`) rather than backing
|
||||
-- out, so the list comes straight back up
|
||||
-- CANCEL `ret`
|
||||
--
|
||||
-- Drawn over whatever opened the PC, so this state is not opaque.
|
||||
|
||||
local Bag = require("src.inventory.Bag")
|
||||
local Breeding = require("src.core.gen2.Breeding")
|
||||
local Chrome = require("src.ui.gen2.Chrome")
|
||||
local CommonText = require("src.core.gen2.CommonText")
|
||||
local Mail = require("src.core.gen2.Mail")
|
||||
local Screens = require("src.ui.Screens")
|
||||
|
||||
local MailboxMenu = {}
|
||||
MailboxMenu.__index = MailboxMenu
|
||||
MailboxMenu.isOpaque = false
|
||||
|
||||
local LIST_X, LIST_Y, LIST_W, LIST_H = 8, 1, 11, 10
|
||||
-- InitScrollingMenu lays its rows two inside the box's corner and steps two.
|
||||
local ROW_X, ROW_Y, ROW_STEP = LIST_X + 2, LIST_Y + 2, 2
|
||||
local VISIBLE_ROWS = 4
|
||||
|
||||
local SUB_X, SUB_Y, SUB_W, SUB_H = 0, 0, 14, 10
|
||||
local SUB_LABEL_X, SUB_LABEL_Y = SUB_X + 2, SUB_Y + 2
|
||||
|
||||
local TEXT_BOX_X, TEXT_BOX_Y, TEXT_BOX_W, TEXT_BOX_H = 0, 12, 20, 6
|
||||
local TEXT_X, TEXT_Y, TEXT_LINE = 1, 14, 2
|
||||
local YESNO_X, YESNO_Y, YESNO_W, YESNO_H = 14, 7, 6, 5
|
||||
local DOWN_ARROW = "\xe2\x96\xbc"
|
||||
local ARROW_X, ARROW_Y = 18, 17
|
||||
|
||||
-- .SubMenuData, verbatim.
|
||||
local SUB_ENTRIES = {
|
||||
{ id = "read", label = "READ MAIL" },
|
||||
{ id = "pack", label = "PUT IN PACK" },
|
||||
{ id = "attach", label = "ATTACH MAIL" },
|
||||
{ id = "cancel", label = "CANCEL" },
|
||||
}
|
||||
|
||||
local function page(...) return { ... } end
|
||||
local function pages(...) return { ... } end
|
||||
|
||||
-- data/text/common_2.asm's _EmptyMailboxText .. _MailMovedFromBoxText block.
|
||||
local TEXT = {
|
||||
empty = pages(page("There's no MAIL", "here.")),
|
||||
messageLost = pages(page("The MAIL's message", "will be lost. OK?")),
|
||||
packFull = pages(page("The PACK is full.")),
|
||||
putAway = pages(page("The cleared MAIL", "was put away.")),
|
||||
alreadyHolding = pages(page("It's already hold-", "ing an item.")),
|
||||
egg = pages(page("An EGG can't hold", "any MAIL.")),
|
||||
moved = pages(page("The MAIL was moved", "from the MAILBOX.")),
|
||||
}
|
||||
|
||||
local LABELS = {
|
||||
empty = "_EmptyMailboxText",
|
||||
messageLost = "_MailMessageLostText",
|
||||
packFull = "_MailPackFullText",
|
||||
putAway = "_MailClearedPutAwayText",
|
||||
alreadyHolding = "_MailAlreadyHoldingItemText",
|
||||
egg = "_MailEggText",
|
||||
moved = "_MailMovedFromBoxText",
|
||||
}
|
||||
|
||||
MailboxMenu.TEXT = TEXT
|
||||
MailboxMenu.LABELS = LABELS
|
||||
MailboxMenu.SUB_ENTRIES = SUB_ENTRIES
|
||||
|
||||
local function extractedText(text)
|
||||
local out = setmetatable({}, { __index = TEXT })
|
||||
for key, label in pairs(LABELS) do
|
||||
local list = CommonText.of(text, label)
|
||||
if list then out[key] = list end
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
-- opts: save, text (text.lua), onClose()
|
||||
function MailboxMenu.new(game, opts)
|
||||
opts = opts or {}
|
||||
local self = setmetatable({}, MailboxMenu)
|
||||
self.game = game
|
||||
self.save = opts.save or (game and game.save)
|
||||
self.textData = opts.text or (game and game.world and game.world.text)
|
||||
self.TEXT = extractedText(self.textData)
|
||||
self.onClose = opts.onClose
|
||||
-- wCurMessageIndex / wCurMessageScrollPosition, both reset by MailboxPC
|
||||
-- before its loop.
|
||||
self.index = 1
|
||||
self.scroll = 0
|
||||
self.submenu = nil
|
||||
self.message = nil
|
||||
self.confirm = nil
|
||||
self.picking = false
|
||||
-- InitMail's z branch: no menu, one line, gone.
|
||||
if Mail.mailboxCount(self.save) == 0 then
|
||||
self:say(self.TEXT.empty, function() self:close() end)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
function MailboxMenu:box()
|
||||
return Mail.mailbox(self.save)
|
||||
end
|
||||
|
||||
function MailboxMenu:count()
|
||||
return Mail.mailboxCount(self.save)
|
||||
end
|
||||
|
||||
function MailboxMenu:selected()
|
||||
return self:box()[self.index]
|
||||
end
|
||||
|
||||
function MailboxMenu:close()
|
||||
if self.onClose then self.onClose() end
|
||||
end
|
||||
|
||||
function MailboxMenu:say(list, onDone)
|
||||
self.message = { pages = list or {}, page = 1, onDone = onDone }
|
||||
end
|
||||
|
||||
function MailboxMenu:ask(list, onYes, onNo)
|
||||
self.confirm = { pages = list or {}, page = 1, choice = 1,
|
||||
onYes = onYes, onNo = onNo }
|
||||
end
|
||||
|
||||
function MailboxMenu:clampIndex()
|
||||
local total = self:count()
|
||||
if total == 0 then
|
||||
self.index, self.scroll = 1, 0
|
||||
return
|
||||
end
|
||||
if self.index > total then self.index = total end
|
||||
if self.index < 1 then self.index = 1 end
|
||||
if self.index <= self.scroll then
|
||||
self.scroll = self.index - 1
|
||||
elseif self.index > self.scroll + VISIBLE_ROWS then
|
||||
self.scroll = self.index - VISIBLE_ROWS
|
||||
end
|
||||
self.scroll = math.max(0, math.min(self.scroll,
|
||||
math.max(0, total - VISIBLE_ROWS)))
|
||||
end
|
||||
|
||||
-- ------------------------------------------------------------- submenu rows
|
||||
|
||||
function MailboxMenu:readMail()
|
||||
local game = self.game
|
||||
local entry = self:selected()
|
||||
if not (game and game.stack and entry) then return end
|
||||
self.picking = true
|
||||
Screens.push(game, "Gen2MailRead", {
|
||||
entry = entry,
|
||||
onClose = function()
|
||||
game.stack:pop()
|
||||
self.picking = false
|
||||
self.submenu = nil
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
-- .PutInPack. The yes/no comes first, then ReceiveItem -- and only a bag that
|
||||
-- accepted the stationery gets as far as DeleteMailFromPC, so a full PACK
|
||||
-- leaves the letter in the MAILBOX intact.
|
||||
function MailboxMenu:putInPack()
|
||||
self:ask(self.TEXT.messageLost, function()
|
||||
local entry = self:selected()
|
||||
if not entry then
|
||||
self.submenu = nil
|
||||
return
|
||||
end
|
||||
local data = self.game and self.game.data
|
||||
if not Bag.add(self.save, entry.type, 1, data) then
|
||||
return self:say(self.TEXT.packFull, function() self.submenu = nil end)
|
||||
end
|
||||
Mail.deleteFromPc(self.save, self.index)
|
||||
self:clampIndex()
|
||||
self:say(self.TEXT.putAway, function()
|
||||
self.submenu = nil
|
||||
if self:count() == 0 then
|
||||
-- The next .loop calls InitMail again, which now answers z.
|
||||
self:say(self.TEXT.empty, function() self:close() end)
|
||||
end
|
||||
end)
|
||||
end, function()
|
||||
-- `ret c`: the question was the whole thing.
|
||||
self.submenu = nil
|
||||
end)
|
||||
end
|
||||
|
||||
-- .AttachMail's loop. Both refusals go back to the party list rather than out
|
||||
-- of the submenu, which is exactly what `jr .try_again` does.
|
||||
function MailboxMenu:attachMail()
|
||||
local game = self.game
|
||||
if not (game and game.stack) then
|
||||
self.submenu = nil
|
||||
return
|
||||
end
|
||||
self.picking = true
|
||||
Screens.push(game, "Gen2PartyMenu", {
|
||||
save = self.save,
|
||||
party = self.save.party,
|
||||
prompt = "choose",
|
||||
onChoose = function(slot, mon)
|
||||
game.stack:pop()
|
||||
self.picking = false
|
||||
if Breeding.isEgg(mon) then
|
||||
return self:say(self.TEXT.egg, function() self:attachMail() end)
|
||||
end
|
||||
if mon and mon.item then
|
||||
return self:say(self.TEXT.alreadyHolding,
|
||||
function() self:attachMail() end)
|
||||
end
|
||||
if not Mail.moveFromPcToParty(self.save, self.index, slot) then
|
||||
self.submenu = nil
|
||||
return
|
||||
end
|
||||
self:clampIndex()
|
||||
self:say(self.TEXT.moved, function()
|
||||
self.submenu = nil
|
||||
if self:count() == 0 then
|
||||
self:say(self.TEXT.empty, function() self:close() end)
|
||||
end
|
||||
end)
|
||||
end,
|
||||
onCancel = function()
|
||||
game.stack:pop()
|
||||
self.picking = false
|
||||
-- `.exit2` -> CloseSubmenu: back to the letter list, not out of the PC.
|
||||
self.submenu = nil
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
function MailboxMenu:chooseSub()
|
||||
local entry = SUB_ENTRIES[self.submenu.index]
|
||||
if not entry then return end
|
||||
if entry.id == "read" then return self:readMail() end
|
||||
if entry.id == "pack" then return self:putInPack() end
|
||||
if entry.id == "attach" then return self:attachMail() end
|
||||
self.submenu = nil
|
||||
end
|
||||
|
||||
-- ------------------------------------------------------------------ update
|
||||
|
||||
function MailboxMenu:updateMessage(input)
|
||||
if not (input:wasPressed("a") or input:wasPressed("b")) then return end
|
||||
local message = self.message
|
||||
if message.page < #message.pages then
|
||||
message.page = message.page + 1
|
||||
return
|
||||
end
|
||||
self.message = nil
|
||||
if message.onDone then message.onDone() end
|
||||
end
|
||||
|
||||
function MailboxMenu:updateConfirm(input)
|
||||
local confirm = self.confirm
|
||||
if confirm.page < #confirm.pages then
|
||||
if input:wasPressed("a") or input:wasPressed("b") then
|
||||
confirm.page = confirm.page + 1
|
||||
end
|
||||
return
|
||||
end
|
||||
if input:wasPressed("up") or input:wasPressed("down") then
|
||||
confirm.choice = confirm.choice == 1 and 2 or 1
|
||||
return
|
||||
end
|
||||
if input:wasPressed("b") then
|
||||
self.confirm = nil
|
||||
if confirm.onNo then confirm.onNo() end
|
||||
return
|
||||
end
|
||||
if input:wasPressed("a") then
|
||||
local yes = confirm.choice == 1
|
||||
self.confirm = nil
|
||||
if yes then
|
||||
if confirm.onYes then confirm.onYes() end
|
||||
elseif confirm.onNo then
|
||||
confirm.onNo()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function MailboxMenu:updateSubmenu(input)
|
||||
local total = #SUB_ENTRIES
|
||||
if input:wasPressed("up") then
|
||||
self.submenu.index = self.submenu.index > 1 and self.submenu.index - 1
|
||||
or total
|
||||
elseif input:wasPressed("down") then
|
||||
self.submenu.index = self.submenu.index < total and self.submenu.index + 1
|
||||
or 1
|
||||
elseif input:wasPressed("a") then
|
||||
self:chooseSub()
|
||||
elseif input:wasPressed("b") then
|
||||
self.submenu = nil
|
||||
end
|
||||
end
|
||||
|
||||
function MailboxMenu:update(_dt)
|
||||
if self.picking then return end
|
||||
local input = self.game and self.game.input
|
||||
if not input then return end
|
||||
if self.message then return self:updateMessage(input) end
|
||||
if self.confirm then return self:updateConfirm(input) end
|
||||
if self.submenu then return self:updateSubmenu(input) end
|
||||
|
||||
local total = self:count()
|
||||
if total == 0 then return self:close() end
|
||||
if input:wasPressed("up") then
|
||||
self.index = self.index > 1 and self.index - 1 or total
|
||||
self:clampIndex()
|
||||
elseif input:wasPressed("down") then
|
||||
self.index = self.index < total and self.index + 1 or 1
|
||||
self:clampIndex()
|
||||
elseif input:wasPressed("a") then
|
||||
self.submenu = { index = 1 }
|
||||
elseif input:wasPressed("b") then
|
||||
-- .exit: PAD_B out of the scrolling menu ends _PlayerMailBoxMenu.
|
||||
self:close()
|
||||
end
|
||||
end
|
||||
|
||||
-- -------------------------------------------------------------------- draw
|
||||
|
||||
function MailboxMenu:drawTextBox(lines)
|
||||
Chrome.box(TEXT_BOX_X, TEXT_BOX_Y, TEXT_BOX_W, TEXT_BOX_H)
|
||||
for i, line in ipairs(lines or {}) do
|
||||
Chrome.print(line, TEXT_X, TEXT_Y + (i - 1) * TEXT_LINE)
|
||||
end
|
||||
end
|
||||
|
||||
function MailboxMenu:drawYesNo(choice)
|
||||
Chrome.box(YESNO_X, YESNO_Y, YESNO_W, YESNO_H)
|
||||
Chrome.print("YES", YESNO_X + 2, YESNO_Y + 1)
|
||||
Chrome.print("NO", YESNO_X + 2, YESNO_Y + 3)
|
||||
Chrome.cursor(YESNO_X + 1, YESNO_Y + (choice == 1 and 1 or 3))
|
||||
end
|
||||
|
||||
function MailboxMenu:drawList()
|
||||
Chrome.box(LIST_X, LIST_Y, LIST_W, LIST_H)
|
||||
local box = self:box()
|
||||
for row = 1, VISIBLE_ROWS do
|
||||
local i = row + self.scroll
|
||||
local entry = box[i]
|
||||
if entry then
|
||||
local ty = ROW_Y + (row - 1) * ROW_STEP
|
||||
if i == self.index then Chrome.cursor(ROW_X - 1, ty) end
|
||||
-- MailboxPC_PrintMailAuthor copies NAME_LENGTH - 1 bytes out of the
|
||||
-- struct's Author field and terminates it; a blank author draws a blank
|
||||
-- row rather than the message.
|
||||
Chrome.print(entry.author or "", ROW_X, ty)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function MailboxMenu:drawSubmenu()
|
||||
Chrome.box(SUB_X, SUB_Y, SUB_W, SUB_H)
|
||||
for row, entry in ipairs(SUB_ENTRIES) do
|
||||
local ty = SUB_LABEL_Y + (row - 1) * 2
|
||||
if row == self.submenu.index then Chrome.cursor(SUB_LABEL_X - 1, ty) end
|
||||
Chrome.print(entry.label, SUB_LABEL_X, ty)
|
||||
end
|
||||
end
|
||||
|
||||
function MailboxMenu:drawPanel()
|
||||
if self.message then
|
||||
self:drawTextBox(self.message.pages[self.message.page])
|
||||
if self.message.page < #self.message.pages then
|
||||
Chrome.print(DOWN_ARROW, ARROW_X, ARROW_Y)
|
||||
end
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
return
|
||||
end
|
||||
if self.confirm then
|
||||
self:drawTextBox(self.confirm.pages[self.confirm.page])
|
||||
if self.confirm.page >= #self.confirm.pages then
|
||||
self:drawYesNo(self.confirm.choice)
|
||||
else
|
||||
Chrome.print(DOWN_ARROW, ARROW_X, ARROW_Y)
|
||||
end
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
return
|
||||
end
|
||||
self:drawList()
|
||||
if self.submenu then self:drawSubmenu() end
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
end
|
||||
|
||||
function MailboxMenu:draw()
|
||||
self:drawPanel()
|
||||
end
|
||||
|
||||
return MailboxMenu
|
||||
Reference in New Issue
Block a user