mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-18 19:54:21 +02:00
G2 support
This commit is contained in:
@@ -0,0 +1,244 @@
|
||||
-- Mom spends the money she is saving for you.
|
||||
--
|
||||
-- engine/events/mom_phone.asm, with data/items/mom_phone.asm beside it. This
|
||||
-- is the other end of Bank of Mom: the quarter WinTrainerBattle skims off
|
||||
-- every won trainer battle (src/battle/gen2/Prize.lua) piles up in
|
||||
-- wMomsMoney, and MomTriesToBuySomething is what she does with it. Nothing
|
||||
-- else in the game spends her savings.
|
||||
--
|
||||
-- Two shopping lists, and they behave completely differently:
|
||||
--
|
||||
-- MomItems_2 is a LADDER, walked once in order by wWhichMomItem. Each row
|
||||
-- carries the savings balance that unlocks it, so the four DOLLS -- the
|
||||
-- only way a Gold player gets a CHARMANDER, CLEFAIRY or PIKACHU doll or
|
||||
-- the BIG SNORLAX at all -- arrive at 10000, 30000, 50000 and 100000
|
||||
-- saved. A row is bought once and the index moves on.
|
||||
-- MomItems_1 is a RANDOM consolation buy that fires only when the savings
|
||||
-- land EXACTLY on a multiple of MOM_MONEY (2300) that
|
||||
-- wMomItemTriggerBalance has not already passed. It never advances
|
||||
-- wWhichMomItem, so it cannot cost the player a rung of the ladder.
|
||||
--
|
||||
-- love-free and save-shaped: takes the Gold save (src/core/gen2/Save.lua) and
|
||||
-- the event bitfield (src/world/gen2/Events.lua), so World, and the tests,
|
||||
-- drive the same routine.
|
||||
|
||||
local Strings = require("src.core.Strings")
|
||||
local Decorations = require("src.core.gen2.Decorations")
|
||||
|
||||
local MomShopping = {}
|
||||
|
||||
-- constants/misc_constants.asm.
|
||||
local MOM_MONEY = 2300
|
||||
MomShopping.MOM_MONEY = MOM_MONEY
|
||||
|
||||
-- constants/misc_constants.asm again; the same cap Prize and Save carry.
|
||||
local MAX_MONEY = 999999
|
||||
|
||||
-- The `momitem kind` const_def 1 block at the top of mom_phone.asm.
|
||||
local MOM_ITEM, MOM_DOLL = 1, 2
|
||||
|
||||
-- wNumPCItems: PC_ITEM_CAPACITY stacks of at most 99, which is what
|
||||
-- ReceiveItem enforces for the PC list the way it does for the bag.
|
||||
local PC_ITEM_CAPACITY = 50
|
||||
local MAX_STACK = 99
|
||||
|
||||
-- data/items/mom_phone.asm, both tables verbatim and in order. `trigger` is
|
||||
-- MOMITEM_TRIGGER, `cost` MOMITEM_COST, `kind` MOMITEM_KIND and `item` is
|
||||
-- MOMITEM_ITEM -- an item id for a MOM_ITEM row and a DECO_* id for a
|
||||
-- MOM_DOLL one, because Mom_GiveItemOrDoll reaches the doll through
|
||||
-- DecorationFlagAction_c, which takes the decoration itself rather than a
|
||||
-- DECOFLAG_*. The DECO numbers are the same ones
|
||||
-- src/core/gen2/Decorations.lua indexes its ATTRIBUTES table by.
|
||||
local function momitem(trigger, cost, kind, item)
|
||||
return { trigger = trigger, cost = cost, kind = kind, item = item }
|
||||
end
|
||||
|
||||
local DECO_BIG_SNORLAX_DOLL = 26
|
||||
local DECO_PIKACHU_DOLL = 30
|
||||
local DECO_CLEFAIRY_DOLL = 32
|
||||
local DECO_CHARMANDER_DOLL = 35
|
||||
|
||||
MomShopping.ITEMS_1 = {
|
||||
momitem(0, 600, MOM_ITEM, "SUPER_POTION"),
|
||||
momitem(0, 90, MOM_ITEM, "ANTIDOTE"),
|
||||
momitem(0, 180, MOM_ITEM, "POKE_BALL"),
|
||||
momitem(0, 450, MOM_ITEM, "ESCAPE_ROPE"),
|
||||
momitem(0, 500, MOM_ITEM, "GREAT_BALL"),
|
||||
}
|
||||
|
||||
MomShopping.ITEMS_2 = {
|
||||
momitem(900, 600, MOM_ITEM, "SUPER_POTION"),
|
||||
momitem(4000, 270, MOM_ITEM, "REPEL"),
|
||||
momitem(7000, 600, MOM_ITEM, "SUPER_POTION"),
|
||||
momitem(10000, 1800, MOM_DOLL, DECO_CHARMANDER_DOLL),
|
||||
momitem(15000, 3000, MOM_ITEM, "MOON_STONE"),
|
||||
momitem(19000, 600, MOM_ITEM, "SUPER_POTION"),
|
||||
momitem(30000, 4800, MOM_DOLL, DECO_CLEFAIRY_DOLL),
|
||||
momitem(40000, 900, MOM_ITEM, "HYPER_POTION"),
|
||||
momitem(50000, 8000, MOM_DOLL, DECO_PIKACHU_DOLL),
|
||||
momitem(100000, 22800, MOM_DOLL, DECO_BIG_SNORLAX_DOLL),
|
||||
}
|
||||
|
||||
-- data/text/common_1.asm, transcribed the way Specials' MOM_TEXT transcribes
|
||||
-- the bank's own bank. Mom never names what she bought, in either script.
|
||||
-- `cont` folds into the same `\n` as `line`.
|
||||
local MOM_HI = Strings.source("Hi, {PLAYER}!\nHow are you?")
|
||||
local FOUND_AN_ITEM = Strings.source(
|
||||
"I found a useful\nitem shopping, so")
|
||||
local FOUND_A_DOLL = Strings.source(
|
||||
"While shopping\ntoday, I saw this\nadorable doll, so")
|
||||
local BOUGHT_WITH_YOUR_MONEY = Strings.source(
|
||||
"I bought it with\nyour money. Sorry!")
|
||||
local ITS_IN_PC = Strings.source("It's in your PC.\nYou'll like it!")
|
||||
local ITS_IN_YOUR_ROOM = Strings.source("It's in your room.\nYou'll love it!")
|
||||
|
||||
--------------------------------------------------------------------------
|
||||
-- State
|
||||
--------------------------------------------------------------------------
|
||||
|
||||
-- wWhichMomItem and wMomItemTriggerBalance, both seeded by NewGame
|
||||
-- (engine/menus/intro_menu.asm): the ladder starts at its first rung and the
|
||||
-- consolation threshold starts at MOM_MONEY. Filled in lazily so a save made
|
||||
-- before this existed gets the same two defaults rather than an unlocked
|
||||
-- ladder.
|
||||
function MomShopping.state(save)
|
||||
local mom = save and save.mom
|
||||
if type(mom) ~= "table" then return nil end
|
||||
if mom.whichItem == nil then mom.whichItem = 0 end
|
||||
if mom.triggerBalance == nil then mom.triggerBalance = MOM_MONEY end
|
||||
return mom
|
||||
end
|
||||
|
||||
local function savedMoney(save)
|
||||
local mom = save and save.mom
|
||||
return (mom and mom.savedMoney) or 0
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------
|
||||
-- CheckBalance_MomItem2
|
||||
--------------------------------------------------------------------------
|
||||
|
||||
-- Answers the row Mom is about to buy, as { row, set }: set 2 is the ladder
|
||||
-- and set 1 the random consolation buy. nil is the routine's `xor a / ret`,
|
||||
-- i.e. she buys nothing this time.
|
||||
--
|
||||
-- `random(n)` returns 0..n-1, the way RandomRange does; injected so the test
|
||||
-- is deterministic.
|
||||
function MomShopping.pick(save, random)
|
||||
local mom = MomShopping.state(save)
|
||||
if not mom then return nil end
|
||||
local saved = savedMoney(save)
|
||||
|
||||
-- `cp (MomItems_2.End - MomItems_2) / MOMITEM_SIZE / jr nc, .nope`: a
|
||||
-- ladder that has run out falls through to the consolation test rather
|
||||
-- than reading off the end of the table.
|
||||
local row = MomShopping.ITEMS_2[mom.whichItem + 1]
|
||||
if row and saved >= row.trigger then
|
||||
return { row = row, set = 2 }
|
||||
end
|
||||
|
||||
-- .check_have_2300, which is a WHILE and not an IF: the balance is walked
|
||||
-- up in MOM_MONEY steps until it reaches or passes the savings, and only an
|
||||
-- EXACT landing buys anything. Overshooting is `.less_than`, which returns
|
||||
-- with no carry and leaves the balance where the walk left it -- so the
|
||||
-- next call starts from the rung above and the same 2300 cannot pay twice.
|
||||
while mom.triggerBalance < saved do
|
||||
mom.triggerBalance = mom.triggerBalance + MOM_MONEY
|
||||
end
|
||||
if mom.triggerBalance ~= saved then return nil end
|
||||
mom.triggerBalance = mom.triggerBalance + MOM_MONEY
|
||||
local roll = 0
|
||||
if random then roll = math.floor(random(#MomShopping.ITEMS_1) or 0) end
|
||||
return { row = MomShopping.ITEMS_1[roll + 1], set = 1 }
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------
|
||||
-- Mom_GiveItemOrDoll
|
||||
--------------------------------------------------------------------------
|
||||
|
||||
-- The PC half of ReceiveItem, over save.pcItems. Returns false for a full
|
||||
-- PC, which is the no-carry Mom_GiveItemOrDoll passes straight back up: the
|
||||
-- purchase does not happen and nothing is deducted.
|
||||
local function receiveItemToPc(save, id, data)
|
||||
if type(save) ~= "table" then return false end
|
||||
save.pcItems = save.pcItems or {}
|
||||
local pc = save.pcItems
|
||||
local held = pc[id] or 0
|
||||
if held == 0 then
|
||||
local cap = (data and data.field and data.field.pcItemCap) or PC_ITEM_CAPACITY
|
||||
local stacks = 0
|
||||
for _ in pairs(pc) do stacks = stacks + 1 end
|
||||
if stacks >= cap then return false end
|
||||
elseif held + 1 > MAX_STACK then
|
||||
return false
|
||||
end
|
||||
pc[id] = held + 1
|
||||
return true
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------
|
||||
-- MomTriesToBuySomething
|
||||
--------------------------------------------------------------------------
|
||||
|
||||
-- opts:
|
||||
-- events the src/world/gen2/Events.lua bitfield, for a doll's flag
|
||||
-- data the cache, for the PC's stack cap
|
||||
-- random(n) 0..n-1, RandomRange
|
||||
-- phoneService GetMapPhoneService: false on a map with no reception, and
|
||||
-- the routine `ret`s before it looks at the balance at all
|
||||
--
|
||||
-- Returns the purchase, or nil. A purchase is
|
||||
-- { kind = "item" | "doll", item, cost, set, saved }, and MomShopping.pages
|
||||
-- turns it into the four lines the phone call speaks.
|
||||
function MomShopping.tryBuy(save, opts)
|
||||
opts = opts or {}
|
||||
if opts.phoneService == false then return nil end
|
||||
local mom = MomShopping.state(save)
|
||||
if not mom then return nil end
|
||||
|
||||
-- wWhichMomItemSet is cleared before the balance check and only written by
|
||||
-- the consolation arm, which is what makes .ASMFunction's `and a / jr nz`
|
||||
-- advance wWhichMomItem for a LADDER buy alone.
|
||||
local pick = MomShopping.pick(save, opts.random)
|
||||
if not (pick and pick.row) then return nil end
|
||||
local row = pick.row
|
||||
|
||||
if row.kind == MOM_DOLL then
|
||||
-- DecorationFlagAction_c with b = SET_FLAG, and the arm ends `scf`: a
|
||||
-- doll cannot fail, there is nowhere for it to not fit.
|
||||
Decorations.give(opts.events, row.item)
|
||||
elseif not receiveItemToPc(save, row.item, opts.data) then
|
||||
return nil
|
||||
end
|
||||
|
||||
-- MomBuysItem_DeductFunds: TakeMoney out of wMomsMoney, which floors at
|
||||
-- zero rather than borrowing.
|
||||
mom.savedMoney = math.max(0, math.min(savedMoney(save), MAX_MONEY) - row.cost)
|
||||
if pick.set == 2 then mom.whichItem = mom.whichItem + 1 end
|
||||
|
||||
return {
|
||||
kind = (row.kind == MOM_DOLL) and "doll" or "item",
|
||||
item = row.item,
|
||||
cost = row.cost,
|
||||
set = pick.set,
|
||||
saved = mom.savedMoney,
|
||||
}
|
||||
end
|
||||
|
||||
-- Mom_GetScriptPointer's two scripts, .ItemScript and .DollScript: four
|
||||
-- writetexts each, differing only in the middle line and the last.
|
||||
--
|
||||
-- The SOURCE strings, not looked-up ones: the caller feeds them to `rawtext`,
|
||||
-- which is where the Strings lookup happens (src/script/gen2/Vm.lua). A
|
||||
-- module-level template resolved here would freeze the English before
|
||||
-- Strings.load has a catalog, which is exactly what Strings.source exists to
|
||||
-- avoid.
|
||||
function MomShopping.pages(purchase)
|
||||
if not purchase then return {} end
|
||||
if purchase.kind == "doll" then
|
||||
return { MOM_HI, FOUND_A_DOLL, BOUGHT_WITH_YOUR_MONEY, ITS_IN_YOUR_ROOM }
|
||||
end
|
||||
return { MOM_HI, FOUND_AN_ITEM, BOUGHT_WITH_YOUR_MONEY, ITS_IN_PC }
|
||||
end
|
||||
|
||||
return MomShopping
|
||||
Reference in New Issue
Block a user