-- 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:", 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:"). 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