mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-16 16:21:30 +02:00
G2 support
This commit is contained in:
@@ -0,0 +1,523 @@
|
||||
-- The ornaments in the player's bedroom: what the player owns, what is set up
|
||||
-- where, and the two routines that put both on the map.
|
||||
-- engine/overworld/decorations.asm, with data/decorations/attributes.asm,
|
||||
-- names.asm and decorations.asm beside it.
|
||||
--
|
||||
-- Three separate pieces of state, and keeping them apart is the whole model:
|
||||
--
|
||||
-- OWNED one wEventFlags bit per decoration (DECOATTR_EVENT_FLAG). Set
|
||||
-- means the player has it; nothing else ever clears one. This is
|
||||
-- the same bitfield `setevent` writes, which is why owning a
|
||||
-- decoration survives in the save with no new field.
|
||||
-- PLACED eight bytes (wDecoBed .. wDecoRightOrnament), each holding the
|
||||
-- DECO_* id standing in that slot or 0 for nothing. A slot holds
|
||||
-- ONE thing: setting up a second bed puts the first away.
|
||||
-- VISIBLE what the map shows, which is neither of the above. It is
|
||||
-- rebuilt from PLACED by ToggleDecorationsVisibility (the four
|
||||
-- object slots) and ToggleMaptileDecorations (the four blocks) --
|
||||
-- and only ever on a MAP LOAD, because those are the
|
||||
-- PLAYERS_HOUSE_2F NEWMAP and TILES callbacks. A flag a running
|
||||
-- script sets does not move an object; the object list is read
|
||||
-- when the map loads and not again, which is why the PC's own
|
||||
-- `warp NONE, 0, 0` (Script_warp's MAPSETUP_BADWARP arm) is what
|
||||
-- makes a placement appear.
|
||||
--
|
||||
-- Everything here is love-free and takes its state by argument, so the menu on
|
||||
-- top of it (src/ui/gen2/DecorationMenu.lua) and the tests can drive the same
|
||||
-- routines the map callbacks do.
|
||||
|
||||
local Strings = require("src.core.Strings")
|
||||
|
||||
local Decorations = {}
|
||||
|
||||
-- constants/deco_constants.asm, decoration types. The type decides how
|
||||
-- GetDecoName spells the row and, for the four maptile kinds, that
|
||||
-- DECOATTR_SPRITE is a BLOCK id rather than a sprite one.
|
||||
local PLANT, BED, CARPET, POSTER, DOLL, BIGDOLL = 1, 2, 3, 4, 5, 6
|
||||
|
||||
-- The eight wDeco* bytes. `slot` on an action names one of these.
|
||||
Decorations.SLOTS = {
|
||||
"bed", "carpet", "plant", "poster", "console", "bigDoll",
|
||||
"leftOrnament", "rightOrnament",
|
||||
}
|
||||
|
||||
-- DoDecorationAction2.DecoActions, as a slot plus a direction rather than a
|
||||
-- jumptable index: the fourteen entries are seven pairs, and the pair is the
|
||||
-- only thing any caller cares about. The ornament pair is the odd one out --
|
||||
-- it asks which side first, so its slot is decided at run time.
|
||||
local ACTIONS = {
|
||||
SET_UP_BED = { slot = "bed" },
|
||||
PUT_AWAY_BED = { slot = "bed", put = true },
|
||||
SET_UP_CARPET = { slot = "carpet" },
|
||||
PUT_AWAY_CARPET = { slot = "carpet", put = true },
|
||||
SET_UP_PLANT = { slot = "plant" },
|
||||
PUT_AWAY_PLANT = { slot = "plant", put = true },
|
||||
SET_UP_POSTER = { slot = "poster" },
|
||||
PUT_AWAY_POSTER = { slot = "poster", put = true },
|
||||
SET_UP_CONSOLE = { slot = "console" },
|
||||
PUT_AWAY_CONSOLE = { slot = "console", put = true },
|
||||
SET_UP_BIG_DOLL = { slot = "bigDoll" },
|
||||
PUT_AWAY_BIG_DOLL = { slot = "bigDoll", put = true },
|
||||
SET_UP_DOLL = { ornament = true },
|
||||
PUT_AWAY_DOLL = { ornament = true, put = true },
|
||||
}
|
||||
Decorations.ACTIONS = ACTIONS
|
||||
|
||||
-- wEventFlags bit numbers, from constants/event_flags.asm. Numbers rather
|
||||
-- than names because that is what the bitfield is keyed by everywhere else in
|
||||
-- this port (src/world/gen2/Events.lua), and because the extracted scripts
|
||||
-- that share these bits carry numbers too.
|
||||
local EVENT_TEMPORARY_UNTIL_MAP_RELOAD_1 = 0
|
||||
local EVENT_DECO_BED_1 = 676
|
||||
local EVENT_DECO_CARPET_1 = 680
|
||||
local EVENT_DECO_PLANT_1 = 684
|
||||
local EVENT_DECO_POSTER_1 = 687
|
||||
local EVENT_DECO_FAMICOM = 691
|
||||
local EVENT_DECO_PIKACHU_DOLL = 695
|
||||
local EVENT_PLAYERS_ROOM_POSTER = 716
|
||||
local EVENT_DECO_GOLD_TROPHY = 717
|
||||
local EVENT_DECO_SILVER_TROPHY = 718
|
||||
local EVENT_DECO_BIG_SNORLAX_DOLL = 719
|
||||
|
||||
Decorations.EVENT_PLAYERS_ROOM_POSTER = EVENT_PLAYERS_ROOM_POSTER
|
||||
|
||||
-- The four objects PLAYERS_HOUSE_2F hangs its decorations off. Each is a
|
||||
-- wVariableSprites slot (SPRITE_VARS-relative, the way `variablesprite`'s byte
|
||||
-- already is) paired with the object's own event flag, and
|
||||
-- ToggleDecorationVisibility writes both: the sprite byte says WHAT stands
|
||||
-- there and the flag says WHETHER it stands there at all.
|
||||
Decorations.OBJECT_SLOTS = {
|
||||
{ slot = "console", sprite = 0, flag = 1857 }, -- SPRITE_CONSOLE
|
||||
{ slot = "leftOrnament", sprite = 1, flag = 1858 }, -- SPRITE_DOLL_1
|
||||
{ slot = "rightOrnament", sprite = 2, flag = 1859 }, -- SPRITE_DOLL_2
|
||||
{ slot = "bigDoll", sprite = 3, flag = 1860 }, -- SPRITE_BIG_DOLL
|
||||
}
|
||||
|
||||
-- data/decorations/attributes.asm, verbatim and in its order: row 0 is the
|
||||
-- unnamed CANCEL row every category menu ends on, and the seven rows whose
|
||||
-- name is PUT_IT_AWAY are the category headers the deco constants share their
|
||||
-- numbering with (BEDS = 1, CARPETS = 6, ...). So this table is indexed by
|
||||
-- DECO_*, and `wMenuSelection` on the cart is an index straight into it.
|
||||
--
|
||||
-- `sprite` is one byte with two meanings, exactly as DECOATTR_SPRITE is: a
|
||||
-- BLOCK id for the four kinds ToggleMaptileDecorations paints, and a SPRITE_*
|
||||
-- byte for the four an object stands on. The SPRITE_* names are in comments
|
||||
-- because the value the cart stores IS the byte -- wVariableSprites holds it
|
||||
-- raw and World:resolveSprite looks it up in constants.spriteOrder.
|
||||
local function deco(kind, name, action, flag, sprite)
|
||||
return { type = kind, name = name, action = action, flag = flag,
|
||||
sprite = sprite }
|
||||
end
|
||||
|
||||
local TEMP = EVENT_TEMPORARY_UNTIL_MAP_RELOAD_1
|
||||
|
||||
local ATTRIBUTES = {
|
||||
[0] = deco(PLANT, "CANCEL", nil, TEMP, 0),
|
||||
deco(PLANT, "PUT IT AWAY", "PUT_AWAY_BED", TEMP, 0), -- BEDS
|
||||
deco(BED, "FEATHERY", "SET_UP_BED", EVENT_DECO_BED_1 + 0, 0x1b),
|
||||
deco(BED, "PINK", "SET_UP_BED", EVENT_DECO_BED_1 + 1, 0x1c),
|
||||
deco(BED, "POLKADOT", "SET_UP_BED", EVENT_DECO_BED_1 + 2, 0x1d),
|
||||
deco(BED, "PIKACHU", "SET_UP_BED", EVENT_DECO_BED_1 + 3, 0x1e),
|
||||
deco(PLANT, "PUT IT AWAY", "PUT_AWAY_CARPET", TEMP, 0), -- CARPETS
|
||||
deco(CARPET, "RED", "SET_UP_CARPET", EVENT_DECO_CARPET_1 + 0, 0x08),
|
||||
deco(CARPET, "BLUE", "SET_UP_CARPET", EVENT_DECO_CARPET_1 + 1, 0x0b),
|
||||
deco(CARPET, "YELLOW", "SET_UP_CARPET", EVENT_DECO_CARPET_1 + 2, 0x0e),
|
||||
deco(CARPET, "GREEN", "SET_UP_CARPET", EVENT_DECO_CARPET_1 + 3, 0x11),
|
||||
deco(PLANT, "PUT IT AWAY", "PUT_AWAY_PLANT", TEMP, 0), -- PLANTS
|
||||
deco(PLANT, "MAGNAPLANT", "SET_UP_PLANT", EVENT_DECO_PLANT_1 + 0, 0x20),
|
||||
deco(PLANT, "TROPICPLANT", "SET_UP_PLANT", EVENT_DECO_PLANT_1 + 1, 0x21),
|
||||
deco(PLANT, "JUMBOPLANT", "SET_UP_PLANT", EVENT_DECO_PLANT_1 + 2, 0x22),
|
||||
deco(PLANT, "PUT IT AWAY", "PUT_AWAY_POSTER", TEMP, 0), -- POSTERS
|
||||
-- The TOWN MAP poster is a DECO_PLANT: its name is a DecorationNames entry
|
||||
-- rather than a species, so GetDecoName must not append " POSTER" to it.
|
||||
deco(PLANT, "TOWN MAP", "SET_UP_POSTER", EVENT_DECO_POSTER_1 + 0, 0x1f),
|
||||
deco(POSTER, "PIKACHU", "SET_UP_POSTER", EVENT_DECO_POSTER_1 + 1, 0x23),
|
||||
deco(POSTER, "CLEFAIRY", "SET_UP_POSTER", EVENT_DECO_POSTER_1 + 2, 0x24),
|
||||
deco(POSTER, "JIGGLYPUFF", "SET_UP_POSTER", EVENT_DECO_POSTER_1 + 3, 0x25),
|
||||
deco(PLANT, "PUT IT AWAY", "PUT_AWAY_CONSOLE", TEMP, 0), -- CONSOLES
|
||||
deco(PLANT, "NES", "SET_UP_CONSOLE", EVENT_DECO_FAMICOM + 0, 0x5c), -- SPRITE_FAMICOM
|
||||
deco(PLANT, "SUPER NES", "SET_UP_CONSOLE", EVENT_DECO_FAMICOM + 1, 0x5b),
|
||||
deco(PLANT, "NINTENDO64", "SET_UP_CONSOLE", EVENT_DECO_FAMICOM + 2, 0x51),
|
||||
deco(PLANT, "VIRTUAL BOY", "SET_UP_CONSOLE", EVENT_DECO_FAMICOM + 3, 0x57),
|
||||
deco(PLANT, "PUT IT AWAY", "PUT_AWAY_BIG_DOLL", TEMP, 0), -- BIG_DOLLS
|
||||
deco(BIGDOLL, "SNORLAX", "SET_UP_BIG_DOLL", EVENT_DECO_BIG_SNORLAX_DOLL + 0, 0x33),
|
||||
deco(BIGDOLL, "ONIX", "SET_UP_BIG_DOLL", EVENT_DECO_BIG_SNORLAX_DOLL + 1, 0x50),
|
||||
deco(BIGDOLL, "LAPRAS", "SET_UP_BIG_DOLL", EVENT_DECO_BIG_SNORLAX_DOLL + 2, 0x47),
|
||||
deco(PLANT, "PUT IT AWAY", "PUT_AWAY_DOLL", TEMP, 0), -- DOLLS
|
||||
deco(DOLL, "PIKACHU", "SET_UP_DOLL", EVENT_DECO_PIKACHU_DOLL + 0, 0x8e),
|
||||
-- The surfing Pikachu doll is a DECO_PLANT too, and for the same reason:
|
||||
-- "SURF PIKACHU DOLL" is one DecorationNames string, not a mon plus " DOLL".
|
||||
deco(PLANT, "SURF PIKACHU DOLL", "SET_UP_DOLL", EVENT_DECO_PIKACHU_DOLL + 1, 0x34),
|
||||
deco(DOLL, "CLEFAIRY", "SET_UP_DOLL", EVENT_DECO_PIKACHU_DOLL + 2, 0x8f),
|
||||
deco(DOLL, "JIGGLYPUFF", "SET_UP_DOLL", EVENT_DECO_PIKACHU_DOLL + 3, 0x94),
|
||||
deco(DOLL, "BULBASAUR", "SET_UP_DOLL", EVENT_DECO_PIKACHU_DOLL + 4, 0x93),
|
||||
deco(DOLL, "CHARMANDER", "SET_UP_DOLL", EVENT_DECO_PIKACHU_DOLL + 5, 0x90),
|
||||
deco(DOLL, "SQUIRTLE", "SET_UP_DOLL", EVENT_DECO_PIKACHU_DOLL + 6, 0x89),
|
||||
deco(DOLL, "POLIWAG", "SET_UP_DOLL", EVENT_DECO_PIKACHU_DOLL + 7, 0x8d),
|
||||
deco(DOLL, "DIGLETT", "SET_UP_DOLL", EVENT_DECO_PIKACHU_DOLL + 8, 0x8c),
|
||||
-- STARYU's doll stands on SPRITE_STARMIE; the cart's own row says so.
|
||||
deco(DOLL, "STARYU", "SET_UP_DOLL", EVENT_DECO_PIKACHU_DOLL + 9, 0x92),
|
||||
deco(DOLL, "MAGIKARP", "SET_UP_DOLL", EVENT_DECO_PIKACHU_DOLL + 10, 0x88),
|
||||
deco(DOLL, "ODDISH", "SET_UP_DOLL", EVENT_DECO_PIKACHU_DOLL + 11, 0x85),
|
||||
deco(DOLL, "GENGAR", "SET_UP_DOLL", EVENT_DECO_PIKACHU_DOLL + 12, 0x86),
|
||||
deco(DOLL, "SHELLDER", "SET_UP_DOLL", EVENT_DECO_PIKACHU_DOLL + 13, 0x84),
|
||||
deco(DOLL, "GRIMER", "SET_UP_DOLL", EVENT_DECO_PIKACHU_DOLL + 14, 0x95),
|
||||
deco(DOLL, "VOLTORB", "SET_UP_DOLL", EVENT_DECO_PIKACHU_DOLL + 15, 0x9b),
|
||||
deco(DOLL, "WEEDLE", "SET_UP_DOLL", EVENT_DECO_PIKACHU_DOLL + 16, 0x83),
|
||||
deco(DOLL, "UNOWN", "SET_UP_DOLL", EVENT_DECO_PIKACHU_DOLL + 17, 0x80),
|
||||
deco(DOLL, "GEODUDE", "SET_UP_DOLL", EVENT_DECO_PIKACHU_DOLL + 18, 0x81),
|
||||
deco(DOLL, "MACHOP", "SET_UP_DOLL", EVENT_DECO_PIKACHU_DOLL + 19, 0x9a),
|
||||
deco(DOLL, "TENTACOOL", "SET_UP_DOLL", EVENT_DECO_PIKACHU_DOLL + 20, 0x98),
|
||||
-- Both trophies are SET_UP_DOLL: a trophy stands in an ornament slot.
|
||||
deco(PLANT, "GOLD TROPHY", "SET_UP_DOLL", EVENT_DECO_GOLD_TROPHY, 0x5e),
|
||||
deco(PLANT, "SILVER TROPHY", "SET_UP_DOLL", EVENT_DECO_SILVER_TROPHY, 0x5f),
|
||||
}
|
||||
Decorations.ATTRIBUTES = ATTRIBUTES
|
||||
|
||||
-- The seven category menus, in _PlayerDecorationMenu's .owned_pointers order.
|
||||
-- `id` is the DECO_* of the category's own PUT_IT_AWAY row, which is exactly
|
||||
-- what FindOwnedDecosInCategory appends to its list, and `members` is that
|
||||
-- routine's own db list -- transcribed rather than derived from a range,
|
||||
-- because the doll list runs past the two trophies and the big dolls do not
|
||||
-- sit next to the small ones.
|
||||
local function range(first, last)
|
||||
local out = {}
|
||||
for id = first, last do out[#out + 1] = id end
|
||||
return out
|
||||
end
|
||||
|
||||
Decorations.CATEGORIES = {
|
||||
{ id = 1, label = "BED", members = range(2, 5) },
|
||||
{ id = 6, label = "CARPET", members = range(7, 10) },
|
||||
{ id = 11, label = "PLANT", members = range(12, 14) },
|
||||
{ id = 15, label = "POSTER", members = range(16, 19) },
|
||||
{ id = 20, label = "GAME CONSOLE", members = range(21, 24) },
|
||||
{ id = 29, label = "ORNAMENT", members = range(30, 52) },
|
||||
{ id = 25, label = "BIG DOLL", members = range(26, 28) },
|
||||
}
|
||||
|
||||
-- data/decorations/decorations.asm DecorationIDs: DECOFLAG_* -> DECO_*. The
|
||||
-- only thing that reads it is GetDecorationID, i.e. the routines that GIVE a
|
||||
-- decoration, which name what they hand over by DECOFLAG.
|
||||
local DECORATION_IDS = {}
|
||||
do
|
||||
local order = {
|
||||
range(2, 5), range(7, 10), range(12, 14), range(16, 19), range(21, 24),
|
||||
range(30, 50), range(26, 28), { 51, 52 },
|
||||
}
|
||||
for _, group in ipairs(order) do
|
||||
for _, id in ipairs(group) do
|
||||
DECORATION_IDS[#DECORATION_IDS + 1] = id
|
||||
end
|
||||
end
|
||||
end
|
||||
-- DECOFLAG_* is a `const_def` block, so it is 0-based: shift the 1-based Lua
|
||||
-- list rather than leaving a caller to guess.
|
||||
function Decorations.idForFlag(decoFlag)
|
||||
return DECORATION_IDS[(decoFlag or 0) + 1]
|
||||
end
|
||||
|
||||
-- constants/deco_constants.asm DECOFLAG_*, for the two callers that name one.
|
||||
Decorations.DECOFLAG_GOLD_TROPHY_DOLL = 43
|
||||
Decorations.DECOFLAG_SILVER_TROPHY_DOLL = 44
|
||||
|
||||
-- DescribeDecoration's five arms and the wDeco* byte each one reads
|
||||
-- (constants/script_constants.asm DECODESC_*, which is what the cache's
|
||||
-- decorationOrder carries). Only the three that share
|
||||
-- DecorationDesc_OrnamentOrConsole put a NAME in wStringBuffer3; the poster
|
||||
-- arm picks a different script instead, and the giant ornament's says the same
|
||||
-- thing whatever is standing there.
|
||||
Decorations.DESC_SLOTS = {
|
||||
DECODESC_POSTER = { slot = "poster" },
|
||||
DECODESC_LEFT_DOLL = { slot = "leftOrnament", named = true },
|
||||
DECODESC_RIGHT_DOLL = { slot = "rightOrnament", named = true },
|
||||
DECODESC_BIG_DOLL = { slot = "bigDoll" },
|
||||
DECODESC_CONSOLE = { slot = "console", named = true },
|
||||
}
|
||||
|
||||
--------------------------------------------------------------------------
|
||||
-- State
|
||||
--------------------------------------------------------------------------
|
||||
|
||||
-- The eight wDeco* bytes, on the save. InitDecorations (called from
|
||||
-- intro_menu.asm at New Game) is the two defaults below: the feathery bed and
|
||||
-- the TOWN MAP poster are set up before the player has chosen anything, which
|
||||
-- is why a new game's room already has a bed in it. Filling them in lazily
|
||||
-- rather than in Save.newGame means an older save gets the same room.
|
||||
function Decorations.state(save)
|
||||
if type(save) ~= "table" then return {} end
|
||||
local state = save.decorations
|
||||
if not state then
|
||||
state = { bed = 2, poster = 16 } -- DECO_FEATHERY_BED, DECO_TOWN_MAP
|
||||
save.decorations = state
|
||||
end
|
||||
return state
|
||||
end
|
||||
|
||||
-- ------------------------------------------------------------ the registry
|
||||
--
|
||||
-- The `decorations` registry (src/mods/Schemas.lua), one of the Gen 2-only
|
||||
-- six: Red's bedroom has no PC decoration menu, so the name is gated under
|
||||
-- Gen 1 and routed to data.gen2Decorations under Gen 2. src/mods/Builtins.lua
|
||||
-- seeds it with the ATTRIBUTES rows above, engine-owned.
|
||||
--
|
||||
-- Ids are "deco:<n>", where n is the attribute row's index -- the DECO_* byte,
|
||||
-- which is what wMenuSelection holds and what every caller passes. The cart's
|
||||
-- decoration constants are a bare const_def block with no name table in the
|
||||
-- ROM behind it, so there is nothing to spell them by; battle_anims addresses
|
||||
-- its unnamed rows the same way ("subanim:<n>").
|
||||
local DECO_ID_PREFIX = "deco:"
|
||||
local registryRows = nil
|
||||
|
||||
function Decorations.idFor(decoId)
|
||||
return DECO_ID_PREFIX .. tostring(decoId)
|
||||
end
|
||||
|
||||
-- One read point for the attribute row, so the merged record reaches every
|
||||
-- caller: name/owns/give/apply/visibility/tiles below all come through here,
|
||||
-- as do src/ui/gen2/DecorationMenu.lua and the two DECO_* screens. Falls back
|
||||
-- to the module's own table, which is what a headless test and a boot with no
|
||||
-- loader get.
|
||||
function Decorations.attributes(decoId)
|
||||
if decoId == nil then return nil end
|
||||
local merged = registryRows and registryRows[DECO_ID_PREFIX .. tostring(decoId)]
|
||||
return merged or ATTRIBUTES[decoId]
|
||||
end
|
||||
|
||||
-- vanilla registrations, engine-owned
|
||||
function Decorations.registerInto(registry, _, owner)
|
||||
local count = 0
|
||||
for decoId, attr in pairs(ATTRIBUTES) do
|
||||
registry:register(Decorations.idFor(decoId), attr, owner)
|
||||
count = count + 1
|
||||
end
|
||||
return count
|
||||
end
|
||||
|
||||
-- the merged table, held by reference; nil forgets it
|
||||
function Decorations.useRegistry(data)
|
||||
registryRows = data and data.gen2Decorations or nil
|
||||
return registryRows ~= nil
|
||||
end
|
||||
|
||||
-- GetDecoName: the display name, built from the type and the name column. The
|
||||
-- four types that name a SPECIES read the mon's name out of the data table,
|
||||
-- which is what `monName` is for; with no resolver the constant is already the
|
||||
-- English name for all twenty-four of them.
|
||||
function Decorations.name(decoId, monName)
|
||||
local attr = Decorations.attributes(decoId)
|
||||
if not attr then return "" end
|
||||
local base = attr.name
|
||||
if attr.type == BED then return base .. " BED" end
|
||||
if attr.type == CARPET then return base .. " CARPET" end
|
||||
local mon = (monName and monName(base)) or base
|
||||
if attr.type == POSTER then return mon .. " POSTER" end
|
||||
if attr.type == DOLL then return mon .. " DOLL" end
|
||||
if attr.type == BIGDOLL then return "BIG " .. mon end
|
||||
return base
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------
|
||||
-- Owning
|
||||
--------------------------------------------------------------------------
|
||||
|
||||
-- DecorationFlagAction CHECK_FLAG. `events` is the src/world/gen2/Events.lua
|
||||
-- bitfield the rest of the port keys by number.
|
||||
function Decorations.owns(events, decoId)
|
||||
local attr = Decorations.attributes(decoId)
|
||||
if not (events and attr and attr.flag) then return false end
|
||||
return events:get(attr.flag) and true or false
|
||||
end
|
||||
|
||||
-- SetSpecificDecorationFlag, i.e. how a decoration is acquired at all: the
|
||||
-- NORMAL_BOX / GORGEOUS_BOX trophies (engine/items/item_effects.asm), Mom's
|
||||
-- doll purchases (engine/events/mom_phone.asm Mom_GiveItemOrDoll) and Mystery
|
||||
-- Gift all end here. Named by DECOFLAG_*, because that is what every caller
|
||||
-- passes.
|
||||
function Decorations.giveFlag(events, decoFlag)
|
||||
return Decorations.give(events, Decorations.idForFlag(decoFlag))
|
||||
end
|
||||
|
||||
function Decorations.give(events, decoId)
|
||||
local attr = Decorations.attributes(decoId)
|
||||
if not (events and attr and attr.flag) then return false end
|
||||
events:set(attr.flag, true)
|
||||
return true
|
||||
end
|
||||
|
||||
-- .FindOwnedDecos: the categories with at least one owned decoration, in the
|
||||
-- .owned_pointers order. EXIT is not in that list -- DecoExitMenu is the
|
||||
-- eighth .category_pointers row and is always on the menu -- so the caller
|
||||
-- appends it, the way .FindCategoriesWithOwnedDecos appends its own 7.
|
||||
function Decorations.ownedCategories(events)
|
||||
local out = {}
|
||||
for _, category in ipairs(Decorations.CATEGORIES) do
|
||||
for _, id in ipairs(category.members) do
|
||||
if Decorations.owns(events, id) then
|
||||
out[#out + 1] = category
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
-- FindOwnedDecosInCategory: every owned decoration in the category, then the
|
||||
-- category's own PUT_IT_AWAY row, then row 0 (CANCEL). An empty category
|
||||
-- answers an empty list and PopulateDecoCategoryMenu prints "There's nothing
|
||||
-- to choose." instead of opening a menu.
|
||||
function Decorations.rows(events, category)
|
||||
local out = {}
|
||||
for _, id in ipairs(category and category.members or {}) do
|
||||
if Decorations.owns(events, id) then out[#out + 1] = id end
|
||||
end
|
||||
if #out == 0 then return out end
|
||||
out[#out + 1] = category.id
|
||||
out[#out + 1] = 0
|
||||
return out
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------
|
||||
-- Placing
|
||||
--------------------------------------------------------------------------
|
||||
|
||||
-- data/text/common_1.asm. Declared up here and formatted at the call site, so
|
||||
-- Strings.source is what registers them.
|
||||
local SET_UP = Strings.source("Set up the\n%s.")
|
||||
local PUT_AWAY = Strings.source("Put away the\n%s.")
|
||||
local NOTHING_TO_PUT_AWAY = Strings.source("There's nothing to\nput away.")
|
||||
local ALREADY_SET_UP = Strings.source("That's already set\nup.")
|
||||
local NOTHING_TO_CHOOSE = Strings.source("There's nothing to\nchoose.")
|
||||
-- _PutAwayAndSetUpText is one text with a `para` in it, so it is two pages.
|
||||
local PUT_AWAY_PAGE = Strings.source("Put away the\n%s")
|
||||
local AND_SET_UP = Strings.source("and set up the\n%s.")
|
||||
|
||||
Decorations.NOTHING_TO_CHOOSE = NOTHING_TO_CHOOSE
|
||||
|
||||
-- DoDecorationAction2 for one menu row. Returns
|
||||
-- changed wChangedDecorations: TRUE only when the room actually changed,
|
||||
-- which is what makes the PC reload the map on the way out
|
||||
-- pages the text to print, in order
|
||||
-- `side` is "left" or "right" and only an ornament row reads it; a nil side on
|
||||
-- an ornament row is DecoAction_AskWhichSide's cancel (`scf`), which changes
|
||||
-- nothing and prints nothing.
|
||||
function Decorations.apply(state, decoId, side, monName)
|
||||
local attr = Decorations.attributes(decoId)
|
||||
if not (state and attr) then return false, {} end
|
||||
local action = attr.action and ACTIONS[attr.action]
|
||||
-- DecoAction_nothing: row 0, the CANCEL row. `scf` and no text.
|
||||
if not action then return false, {} end
|
||||
|
||||
local slot = action.slot
|
||||
if action.ornament then
|
||||
if side ~= "left" and side ~= "right" then return false, {} end
|
||||
slot = (side == "right") and "rightOrnament" or "leftOrnament"
|
||||
end
|
||||
|
||||
local current = state[slot] or 0
|
||||
local name = function(id) return Decorations.name(id, monName) end
|
||||
|
||||
if action.put then
|
||||
-- DecoAction_TryPutItAway clears the slot BEFORE it checks what was in it,
|
||||
-- so putting away an empty slot still writes a 0 over the 0.
|
||||
state[slot] = 0
|
||||
if current == 0 then return false, { Strings(NOTHING_TO_PUT_AWAY) } end
|
||||
-- DecoAction_PutItAway_Ornament names the thing that WAS out, not the row
|
||||
-- the player picked (the row is the PUT IT AWAY row and has no name).
|
||||
return true, { Strings(PUT_AWAY, name(current)) }
|
||||
end
|
||||
|
||||
if current == decoId then
|
||||
-- .alreadythere: carry, so nothing is written and nothing changed.
|
||||
return false, { Strings(ALREADY_SET_UP) }
|
||||
end
|
||||
|
||||
state[slot] = decoId
|
||||
if current == 0 then
|
||||
return true, { Strings(SET_UP, name(decoId)) }
|
||||
end
|
||||
return true, { Strings(PUT_AWAY_PAGE, name(current)),
|
||||
Strings(AND_SET_UP, name(decoId)) }
|
||||
end
|
||||
|
||||
-- DecoAction_SetItUp_Ornament .getwhichside: setting a doll up on one side
|
||||
-- when the SAME doll is already on the other takes it off the other side --
|
||||
-- there is only one of each. Called by the menu right after apply() on an
|
||||
-- ornament row, because the cart does it inside the same action.
|
||||
function Decorations.clearOtherSide(state, decoId, side)
|
||||
if not (state and decoId and decoId ~= 0) then return end
|
||||
local other = (side == "right") and "leftOrnament" or "rightOrnament"
|
||||
if state[other] == decoId then state[other] = 0 end
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------
|
||||
-- Showing: the two map callbacks
|
||||
--------------------------------------------------------------------------
|
||||
|
||||
-- ToggleDecorationsVisibility (the PLAYERS_HOUSE_2F MAPCALLBACK_NEWMAP). One
|
||||
-- row per object slot: an empty slot SETS the object's event flag, which hides
|
||||
-- it, and a filled one clears the flag and writes the decoration's sprite byte
|
||||
-- into wVariableSprites.
|
||||
--
|
||||
-- Answers a plain list so the caller can apply it to a live world or a test
|
||||
-- table; nothing here touches love or the map.
|
||||
function Decorations.visibility(state)
|
||||
local out = {}
|
||||
for _, row in ipairs(Decorations.OBJECT_SLOTS) do
|
||||
local decoId = state and state[row.slot] or 0
|
||||
local attr = Decorations.attributes(decoId)
|
||||
if decoId ~= 0 and attr then
|
||||
out[#out + 1] = { sprite = row.sprite, byte = attr.sprite,
|
||||
flag = row.flag, hidden = false }
|
||||
else
|
||||
out[#out + 1] = { sprite = row.sprite, flag = row.flag, hidden = true }
|
||||
end
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
-- ToggleMaptileDecorations (the MAPCALLBACK_TILES one). Its coordinates "work
|
||||
-- the same way as for changeblock": PadCoords_de adds 4 to each and
|
||||
-- GetBlockLocation halves them, so the pairs in the asm are CELL coordinates
|
||||
-- and the block written is (x / 2, y / 2) -- the same halving
|
||||
-- src/script/gen2/Vm.lua does for `changeblock`.
|
||||
--
|
||||
-- bed cell (0, 4) -> block (0, 2)
|
||||
-- plant cell (7, 4) -> block (3, 2)
|
||||
-- poster cell (6, 0) -> block (3, 0)
|
||||
-- carpet cell (0, 0) -> block (0, 0), and cell (0, 2) -> block row (0, 1)
|
||||
--
|
||||
-- The carpet is the only one that writes more than one block: its top-left
|
||||
-- block is the sprite byte and the row under it is +1, +2, +1. An empty slot
|
||||
-- writes NOTHING (SetDecorationTile's `and a / ret z`), so the map keeps the
|
||||
-- bare block it was loaded with.
|
||||
function Decorations.tiles(state)
|
||||
local out = {}
|
||||
local function put(slot, blockX, blockY)
|
||||
local attr = Decorations.attributes(state and state[slot])
|
||||
if attr and attr.sprite and attr.sprite ~= 0 then
|
||||
out[#out + 1] = { x = blockX, y = blockY, block = attr.sprite }
|
||||
return attr.sprite
|
||||
end
|
||||
return nil
|
||||
end
|
||||
put("bed", 0, 2)
|
||||
put("plant", 3, 2)
|
||||
put("poster", 3, 0)
|
||||
local carpet = put("carpet", 0, 0)
|
||||
if carpet then
|
||||
out[#out + 1] = { x = 0, y = 1, block = carpet + 1 }
|
||||
out[#out + 1] = { x = 1, y = 1, block = carpet + 2 }
|
||||
out[#out + 1] = { x = 2, y = 1, block = carpet + 1 }
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
-- SetPosterVisibility, which rides along inside ToggleMaptileDecorations: the
|
||||
-- bedroom's poster bg_event is BGEVENT_IFSET on EVENT_PLAYERS_ROOM_POSTER, so
|
||||
-- a bare wall must not be readable at all.
|
||||
function Decorations.posterVisible(state)
|
||||
return ((state and state.poster) or 0) ~= 0
|
||||
end
|
||||
|
||||
return Decorations
|
||||
Reference in New Issue
Block a user