From 1b3e4abe823a394b6f6a30ab2fda32d8492bd57a Mon Sep 17 00:00:00 2001 From: sruon Date: Wed, 1 Apr 2026 19:10:43 -0600 Subject: [PATCH] Exdata definitions Chocobo card Chocobo egg Crafting Set Evolith Glowing Lamp Bonanza Marbles MMM Tabulas Co-Authored-By: atom0s --- scripts/commands/givebonanzapearl.lua | 11 +- scripts/enum/item.lua | 4 + scripts/events/mog_bonanza.lua | 35 ++-- scripts/globals/einherjar/lamp.lua | 82 +++------ scripts/specs/core/Exdata.lua | 62 ++++++- scripts/tests/systems/exdata.lua | 164 ++++++++++++++++++ .../Hazhalm_Testing_Grounds/npcs/_260.lua | 35 +++- src/map/items.h | 19 ++ src/map/items/CMakeLists.txt | 14 ++ src/map/items/exdata.cpp | 80 +++++++++ src/map/items/exdata.h | 7 + src/map/items/exdata/chocobo_card.cpp | 124 +++++++++++++ src/map/items/exdata/chocobo_card.h | 67 +++++++ src/map/items/exdata/chocobo_egg.cpp | 51 ++++++ src/map/items/exdata/chocobo_egg.h | 47 +++++ src/map/items/exdata/crafting_set.cpp | 38 ++++ src/map/items/exdata/crafting_set.h | 40 +++++ src/map/items/exdata/evolith.cpp | 44 +++++ src/map/items/exdata/evolith.h | 43 +++++ src/map/items/exdata/glowing_lamp.cpp | 47 +++++ src/map/items/exdata/glowing_lamp.h | 43 +++++ src/map/items/exdata/lottery_ticket.cpp | 34 ++++ src/map/items/exdata/lottery_ticket.h | 39 +++++ src/map/items/exdata/tabula.cpp | 122 +++++++++++++ src/map/items/exdata/tabula.h | 59 +++++++ 25 files changed, 1208 insertions(+), 103 deletions(-) create mode 100644 src/map/items/exdata/chocobo_card.cpp create mode 100644 src/map/items/exdata/chocobo_card.h create mode 100644 src/map/items/exdata/chocobo_egg.cpp create mode 100644 src/map/items/exdata/chocobo_egg.h create mode 100644 src/map/items/exdata/crafting_set.cpp create mode 100644 src/map/items/exdata/crafting_set.h create mode 100644 src/map/items/exdata/evolith.cpp create mode 100644 src/map/items/exdata/evolith.h create mode 100644 src/map/items/exdata/glowing_lamp.cpp create mode 100644 src/map/items/exdata/glowing_lamp.h create mode 100644 src/map/items/exdata/lottery_ticket.cpp create mode 100644 src/map/items/exdata/lottery_ticket.h create mode 100644 src/map/items/exdata/tabula.cpp create mode 100644 src/map/items/exdata/tabula.h diff --git a/scripts/commands/givebonanzapearl.lua b/scripts/commands/givebonanzapearl.lua index e33fe44dd5c..715046bcb80 100644 --- a/scripts/commands/givebonanzapearl.lua +++ b/scripts/commands/givebonanzapearl.lua @@ -31,14 +31,13 @@ commandObj.onTrigger = function(player, target, selectedNum, eventNum) if targ:getFreeSlotsCount() == 0 then player:printToPlayer(string.format('Player \'%s\' does not have free space for that item!', target)) else - targ:addItem({ id = xi.item.BONANZA_PEARL, + targ:addItem({ + id = xi.item.BONANZA_PEARL, exdata = { - [0] = bit.band(selectedNum, 0xFF), - [1] = bit.band(bit.rshift(selectedNum, 8), 0xFF), - [2] = bit.band(bit.rshift(selectedNum, 16), 0xFF), - [3] = bit.band(eventNum, 0xFF), - } + number = selectedNum, + title = eventNum, + }, }) player:printToPlayer(string.format('Gave player \'%s\' Item with ID of \'%u\'', target, xi.item.BONANZA_PEARL)) diff --git a/scripts/enum/item.lua b/scripts/enum/item.lua index 447f805101d..88842002849 100644 --- a/scripts/enum/item.lua +++ b/scripts/enum/item.lua @@ -1696,6 +1696,7 @@ xi.item = DANCERS_TESTIMONY = 2556, SCHOLARS_TESTIMONY = 2557, BLOCK_OF_YAGUDO_GLUE = 2558, + MOG_BONANZA_MARBLE = 2559, PIECE_OF_PIZZA_DOUGH = 2561, ONE_HUNDRED_EIGHT_KNOT_QUIPU = 2562, LUMP_OF_KARUGO_NARUGO_CLAY = 2563, @@ -1866,6 +1867,7 @@ xi.item = ENFEEBLEMENT_KIT_OF_BLINDNESS = 2780, ENFEEBLEMENT_KIT_OF_SLEEP = 2781, ENFEEBLEMENT_KIT_OF_SILENCE = 2782, + EVOLITH = 2783, FLASQUE_OF_CATALYTIC_OIL = 2792, TREMORSTONE = 2796, MOG_KUPON_AW_PAN = 2801, @@ -4052,6 +4054,8 @@ xi.item = KEIS_SCALE = 9305, KYOUS_SCALE = 9306, FUS_SCALE = 9307, + WOODWORKING_SET_25 = 9412, + COOKING_SET_95 = 9483, AMBUSCADE_VOUCHER_WEAPON = 9780, AMBUSCADE_VOUCHER_BACK = 9781, ABDHALJS_NUGGETS = 9782, diff --git a/scripts/events/mog_bonanza.lua b/scripts/events/mog_bonanza.lua index 771eb2d8854..dc27c60b773 100644 --- a/scripts/events/mog_bonanza.lua +++ b/scripts/events/mog_bonanza.lua @@ -15,11 +15,7 @@ local localSettings = MAX_PEARLS = 1, DISABLE_PRIMEVAL_BREW = 1, -- 0 will turn on the prank the moogle shows when purchasing - -- 0x55: New Year's Nomad Mog Bonanza 2021 - -- 0x5A: 20th Vana'versary Nomad Mog Bonanza - -- 0x5C: 21st Vana'versary Nomad Mog Bonanza - -- 0x5E: - BONANZA_ID = 0x5C, + BONANZA_ID = xi.bonanza.eventId.TWENTY_FIRST_VANAVERSARY_NOMAD, -- ALL TIMES JST BUYING_PERIOD_START = { year = 2023, month = 5, day = 17, hour = 1 }, @@ -260,22 +256,17 @@ local giveBonanzaPearl = function(player, number) number > 999 or number < 0 then - print(string.format('giveBonanzaPearl: %s tried to create a pear with invalid number: %d', player:getName(), number)) + print(string.format('giveBonanzaPearl: %s tried to create a pearl with invalid number: %d', player:getName(), number)) return nil end - player:addItem({ id = xi.item.BONANZA_PEARL, + player:addItem({ + id = xi.item.BONANZA_PEARL, exdata = { - [0] = bit.band(number, 0xFF), - [1] = bit.band(bit.rshift(number, 8), 0xFF), - [2] = bit.band(bit.rshift(number, 16), 0xFF), - [3] = bit.band(localSettings.BONANZA_ID, 0xFF), - [4] = 0, -- 0xCE, -- These might not be needed - [5] = 0, -- 0x62, -- These might not be needed - [6] = 0, -- 0x95, -- These might not be needed - [7] = 0, -- 0x23, -- These might not be needed - } + number = number, + title = localSettings.BONANZA_ID, + }, }) end @@ -287,17 +278,11 @@ xi.events.mogBonanza.onBonanzaMoogleTrade = function(player, npc, trade) then local bonanzaPearl = trade:getItem(0) local exData = bonanzaPearl:getExData() - local eventId = exData[3] - - if eventId == localSettings.BONANZA_ID then - local baseCs = csidLookup[player:getZoneID()] - local pearlNumber = 0 - for exIndex = 0, 2 do - pearlNumber = pearlNumber + bit.lshift(exData[exIndex], 8 * exIndex) - end + if exData.title == localSettings.BONANZA_ID then + local baseCs = csidLookup[player:getZoneID()] - player:setLocalVar('prizeRank', getPrizeRank(player, pearlNumber)) + player:setLocalVar('prizeRank', getPrizeRank(player, exData.number)) player:startEvent(baseCs + 2, 0, 0, 0, 0, 0, 0, 0, localSettings.BONANZA_ID) end end diff --git a/scripts/globals/einherjar/lamp.lua b/scripts/globals/einherjar/lamp.lua index 108476d8376..77931fc2a32 100644 --- a/scripts/globals/einherjar/lamp.lua +++ b/scripts/globals/einherjar/lamp.lua @@ -9,16 +9,10 @@ xi.einherjar.makeLamp = function(player, chamberId, startTime, endTime) id = xi.item.GLOWING_LAMP, exdata = { - [ 0] = 0x1D + chamberId, - [ 2] = 0x01, - [ 8] = bit.band(endTime, 0xFF), - [ 9] = bit.band(bit.rshift(endTime, 8), 0xFF), - [10] = bit.band(bit.rshift(endTime, 16), 0xFF), - [11] = bit.band(bit.rshift(endTime, 24), 0xFF), - [12] = bit.band(startTime, 0xFF), - [13] = bit.band(bit.rshift(startTime, 8), 0xFF), - [14] = bit.band(bit.rshift(startTime, 16), 0xFF), - [15] = bit.band(bit.rshift(startTime, 24), 0xFF), + chamberId = chamberId, + flags = 1, + startTime = startTime, + endTime = endTime, }, }) end @@ -28,61 +22,25 @@ end xi.einherjar.voidAllLamps = function(player, chamberId) for _, item in ipairs(player:findItems(xi.item.GLOWING_LAMP)) do local lampData = xi.einherjar.decypherLamp(item) - if lampData.chamber == chamberId then + if lampData.chamberId == chamberId then xi.einherjar.voidLamp(player, item) end end end --- Drop a given glowing lamp and give a new zeroed out one to make it seem like it expired --- The client briefly flashes a new item which is not exactly retail behavior +-- Zero out a given glowing lamp so the client shows it as expired xi.einherjar.voidLamp = function(player, lampObj) - player:delItemAt(xi.item.GLOWING_LAMP, 1, lampObj:getLocationID(), lampObj:getSlotID()) - player:addItem({ - id = xi.item.GLOWING_LAMP, - exdata = - { - [ 0] = 0x0, - [ 2] = 0x0, - [ 8] = 0x0, - [ 9] = 0x0, - [10] = 0x0, - [11] = 0x0, - [12] = 0x0, - [13] = 0x0, - [14] = 0x0, - [15] = 0x0, - }, - }) + lampObj:setExData({ chamberId = 0, flags = 0, startTime = 0, endTime = 0 }) end -- Reads a given Glowing Lamp and returns the chamber, enter time, and exit time xi.einherjar.decypherLamp = function(lampObj) - local exData = lampObj and lampObj:getExData() - if - not exData or - #exData < 16 or - exData[0] == 0 - then - return { chamber = 0, tier = 0, startTime = 0, endTime = 0 } - end - - local chamber = exData[0] - 0x1D - local endTime = bit.bor( - exData[8] or 0, - bit.lshift(exData[9] or 0, 8), - bit.lshift(exData[10] or 0, 16), - bit.lshift(exData[11] or 0, 24) - ) - - local startTime = bit.bor( - exData[12] or 0, - bit.lshift(exData[13] or 0, 8), - bit.lshift(exData[14] or 0, 16), - bit.lshift(exData[15] or 0, 24) - ) - - return { chamber = chamber, startTime = startTime, endTime = endTime } + local ex = lampObj and lampObj:getExData() + if not ex or ex.chamberId == 0 then + return { chamberId = 0, startTime = 0, endTime = 0 } + end + + return { chamberId = ex.chamberId, startTime = ex.startTime, endTime = ex.endTime } end xi.einherjar.isLampExpired = function(lampObj) @@ -97,7 +55,7 @@ xi.einherjar.getMatchingLamps = function(player, chamberId, startTime) for _, item in ipairs(player:findItems(xi.item.GLOWING_LAMP)) do local lampData = xi.einherjar.decypherLamp(item) if - lampData.chamber == chamberId and + lampData.chamberId == chamberId and lampData.startTime == startTime then table.insert(matchingLamps, item) @@ -136,11 +94,11 @@ xi.einherjar.onLampUse = function(player, lampObj) end local lampData = xi.einherjar.decypherLamp(lampObj) - if not lampData or not lampData.chamber then + if not lampData or not lampData.chamberId then return end - local chamberInstance = xi.einherjar.getChamber(lampData.chamber) + local chamberInstance = xi.einherjar.getChamber(lampData.chamberId) if not chamberInstance then xi.einherjar.voidLamp(player, lampObj) return @@ -149,7 +107,7 @@ xi.einherjar.onLampUse = function(player, lampObj) -- Using the lamp consumes it, so we need to make two new ones -- TODO: Figure out if lamp consumption can be blocked for _ = 1, 2 do - xi.einherjar.makeLamp(player, lampData.chamber, lampData.startTime, lampData.endTime) + xi.einherjar.makeLamp(player, lampData.chamberId, lampData.startTime, lampData.endTime) end end @@ -161,11 +119,11 @@ xi.einherjar.onLampDrop = function(player, lampObj) end local lampData = xi.einherjar.decypherLamp(lampObj) - if lampData.chamber == 0 then + if lampData.chamberId == 0 then return end - local chamberData = xi.einherjar.getChamber(lampData.chamber) + local chamberData = xi.einherjar.getChamber(lampData.chamberId) if not chamberData then return end @@ -174,7 +132,7 @@ xi.einherjar.onLampDrop = function(player, lampObj) return end - if #xi.einherjar.getMatchingLamps(player, lampData.chamber, lampData.startTime) == 0 then + if #xi.einherjar.getMatchingLamps(player, lampData.chamberId, lampData.startTime) == 0 then xi.einherjar.onChamberExit(chamberData, player, false) end end diff --git a/scripts/specs/core/Exdata.lua b/scripts/specs/core/Exdata.lua index a757fd41cdf..c5181490dff 100644 --- a/scripts/specs/core/Exdata.lua +++ b/scripts/specs/core/Exdata.lua @@ -37,4 +37,64 @@ ---@field raceId integer # [0-262143] ---@field raceGrade xi.chocoboRacing.raceGrade ----@alias Exdata ExdataLegionPass|ExdataPerpetualHourglass|ExdataBettingSlip|ExdataAssaultLog|ExdataBrennerBook|ExdataMeebleGrimoire|ExdataHoneymoonTicket|ExdataRaceCertificate +---@class ExdataLotteryTicket +---@field number integer # [0-16777215] +---@field title xi.bonanza.eventId + +---@class ExdataTabulaRune +---@field id xi.maze.rune +---@field rotation integer # Rune rotation [0-3] +---@field position integer # Grid index [0-24] on the 5x5 board + +---@class ExdataTabula +---@field voucher xi.maze.voucher +---@field runes ExdataTabulaRune[] # Up to 12 runes +---@field uses integer # Uses count [0-127] (Tabula R only) + +---@class ExdataEvolith +---@field augment integer # Augment ID [0-1023] +---@field shape xi.evolith.shape +---@field element xi.evolith.element +---@field bonus integer # Bonus [0-15] +---@field signature string # 12 characters + +---@class ExdataCraftingSet +---@field quality integer +---@field signature string # 12 characters + +---@class ExdataGlowingLamp +---@field chamberId xi.einherjar.chamber +---@field flags integer # Undocumented flags +---@field startTime integer # Reservation start time +---@field endTime integer # Reservation end time + +---@class ExdataChocoboEgg +---@field dna integer[] # 3 color genes indexed [1-3], each [0-7] +---@field ability xi.chocoboRaising.ability +---@field plan xi.chocoboRaising.honeymoonPlan +---@field isBred boolean # Whether the egg is from breeding + +---@class ExdataChocoboStatByte +---@field trait boolean # Physical trait flag (legs/tail/head) +---@field rp integer +---@field rank xi.chocoboRaising.statRank + +---@class ExdataChocoboStatByteRCP +---@field rp integer +---@field rank xi.chocoboRaising.statRank + +---@class ExdataChocoboCard +---@field strength ExdataChocoboStatByte # Strength (legs trait) +---@field endurance ExdataChocoboStatByte # Endurance (tail trait) +---@field discernment ExdataChocoboStatByte # Discernment (head trait) +---@field receptivity ExdataChocoboStatByteRCP +---@field dna integer[] # 3 color genes indexed [1-3], each [0-7] +---@field abilities xi.chocoboRaising.ability[] +---@field temperament xi.chocoboRaising.temperament +---@field weather xi.chocoboRaising.weather +---@field gender xi.chocoboRaising.gender +---@field color xi.chocoboRaising.color +---@field size xi.chocoboRacing.jockeySize +---@field name string # 12 characters chocobo name + +---@alias Exdata ExdataLegionPass|ExdataPerpetualHourglass|ExdataBettingSlip|ExdataAssaultLog|ExdataBrennerBook|ExdataMeebleGrimoire|ExdataHoneymoonTicket|ExdataRaceCertificate|ExdataLotteryTicket|ExdataTabula|ExdataEvolith|ExdataCraftingSet|ExdataGlowingLamp|ExdataChocoboEgg|ExdataChocoboCard diff --git a/scripts/tests/systems/exdata.lua b/scripts/tests/systems/exdata.lua index a9a2d9caad3..edf49ee1ef0 100644 --- a/scripts/tests/systems/exdata.lua +++ b/scripts/tests/systems/exdata.lua @@ -234,6 +234,170 @@ describe('Exdata', function() assert(ex.plan == xi.chocoboRaising.honeymoonPlan.HIKING) end) + it('can get and set Lottery Ticket exdata', function() + local item = player:addItem({ id = xi.item.BONANZA_PEARL, quantity = 1 }) + assert(item) + + item:setExData( + { + number = 123456, + title = xi.bonanza.eventId.TWENTY_FIRST_VANAVERSARY_NOMAD, + }) + + local ex = item:getExData() + assert(ex.number == 123456) + assert(ex.title == xi.bonanza.eventId.TWENTY_FIRST_VANAVERSARY_NOMAD) + end) + + it('can get and set Tabula exdata', function() + local item = player:addItem({ id = xi.item.MAZE_TABULA_M01, quantity = 1 }) + assert(item) + + item:setExData( + { + voucher = xi.maze.voucher.ACTUALIZATION_TEAM, + runes = + { + { id = xi.maze.rune.AQUAN, rotation = 2, position = 0 }, + { id = xi.maze.rune.DRAGON, rotation = 1, position = 13 }, + }, + uses = 10, + }) + + local ex = item:getExData() + assert(ex.voucher == xi.maze.voucher.ACTUALIZATION_TEAM) + assert(#ex.runes == 2) + assert(ex.runes[1].id == xi.maze.rune.AQUAN) + assert(ex.runes[1].rotation == 2) + assert(ex.runes[1].position == 0) + assert(ex.runes[2].id == xi.maze.rune.DRAGON) + assert(ex.runes[2].rotation == 1) + assert(ex.runes[2].position == 13) + assert(ex.uses == 10) + end) + + it('can get and set Evolith exdata', function() + local item = player:addItem({ id = xi.item.EVOLITH, quantity = 1 }) + assert(item) + + item:setExData( + { + augment = 120, + shape = xi.evolith.shape.DOWN_FILLED, + element = xi.evolith.element.FIRE, + bonus = 3, + signature = 'TestCrafter', + }) + + local ex = item:getExData() + assert(ex.augment == 120) + assert(ex.shape == xi.evolith.shape.DOWN_FILLED) + assert(ex.element == xi.evolith.element.FIRE) + assert(ex.bonus == 3) + assert(ex.signature == 'TestCrafter') + end) + + it('can get and set Crafting Set exdata', function() + local item = player:addItem({ id = xi.item.WOODWORKING_SET_25, quantity = 1 }) + assert(item) + + item:setExData( + { + quality = 100, + signature = 'Test', + }) + + local ex = item:getExData() + assert(ex.quality == 100) + assert(ex.signature == 'Test') + end) + + it('can get and set Glowing Lamp exdata', function() + local item = player:addItem({ id = xi.item.GLOWING_LAMP, quantity = 1 }) + assert(item) + + local now = GetSystemTime() + item:setExData( + { + chamberId = xi.einherjar.chamber.SCHWERTLEITE, + flags = 3, + startTime = now, + endTime = now + 1800, + }) + + local ex = item:getExData() + assert(ex.chamberId == xi.einherjar.chamber.SCHWERTLEITE) + assert(ex.flags == 3) + assert(ex.startTime == now) + assert(ex.endTime == now + 1800) + end) + + it('can get and set Chocobo Egg exdata', function() + local item = player:addItem({ id = xi.item.CHOCOBO_EGG_FAINTLY_WARM, quantity = 1 }) + assert(item) + + item:setExData( + { + dna = { 3, 5, 7 }, + ability = xi.chocoboRaising.ability.TREASURE_FINDER, + plan = xi.chocoboRaising.honeymoonPlan.SPORTS, + isBred = true, + }) + + local ex = item:getExData() + assert(ex.dna[1] == 3) + assert(ex.dna[2] == 5) + assert(ex.dna[3] == 7) + assert(ex.ability == xi.chocoboRaising.ability.TREASURE_FINDER) + assert(ex.plan == xi.chocoboRaising.honeymoonPlan.SPORTS) + assert(ex.isBred == true) + end) + + it('can get and set Chocobo Card exdata', function() + local item = player:addItem({ id = xi.item.VCS_REGISTRATION_FORM, quantity = 1 }) + assert(item) + + item:setExData( + { + strength = { trait = true, rp = 8, rank = xi.chocoboRaising.statRank.IMPRESSIVE }, + endurance = { trait = false, rp = 4, rank = xi.chocoboRaising.statRank.AVERAGE }, + discernment = { trait = true, rp = 12, rank = xi.chocoboRaising.statRank.OUTSTANDING }, + receptivity = { rp = 20, rank = xi.chocoboRaising.statRank.FIRST_CLASS }, + dna = { 2, 4, 6 }, + abilities = { xi.chocoboRaising.ability.GALLOP, xi.chocoboRaising.ability.CANTER }, + temperament = xi.chocoboRaising.temperament.ENIGMATIC, + weather = xi.chocoboRaising.weather.CLOUDY, + gender = xi.chocoboRaising.gender.FEMALE, + color = xi.chocoboRaising.color.RED, + size = xi.chocoboRacing.jockeySize.HUME_F, + name = 'ChocoTest', + }) + + local ex = item:getExData() + assert(ex.strength.trait == true) + assert(ex.strength.rp == 8) + assert(ex.strength.rank == xi.chocoboRaising.statRank.IMPRESSIVE) + assert(ex.endurance.trait == false) + assert(ex.endurance.rp == 4) + assert(ex.endurance.rank == xi.chocoboRaising.statRank.AVERAGE) + assert(ex.discernment.trait == true) + assert(ex.discernment.rp == 12) + assert(ex.discernment.rank == xi.chocoboRaising.statRank.OUTSTANDING) + assert(ex.receptivity.rp == 20) + assert(ex.receptivity.rank == xi.chocoboRaising.statRank.FIRST_CLASS) + assert(ex.dna[1] == 2) + assert(ex.dna[2] == 4) + assert(ex.dna[3] == 6) + assert(ex.abilities[1] == xi.chocoboRaising.ability.GALLOP) + assert(ex.abilities[2] == xi.chocoboRaising.ability.CANTER) + assert(ex.temperament == xi.chocoboRaising.temperament.ENIGMATIC) + assert(ex.weather == xi.chocoboRaising.weather.CLOUDY) + assert(ex.gender == xi.chocoboRaising.gender.FEMALE) + assert(ex.color == xi.chocoboRaising.color.RED) + assert(ex.size == xi.chocoboRacing.jockeySize.HUME_F) + assert(ex.name == 'ChocoTest') + end) + it('unhandled items fall back to raw bytes', function() local item = player:addItem({ id = xi.item.FIRE_CRYSTAL, quantity = 1 }) assert(item) diff --git a/scripts/zones/Hazhalm_Testing_Grounds/npcs/_260.lua b/scripts/zones/Hazhalm_Testing_Grounds/npcs/_260.lua index 63754c928e9..75bf15156ec 100644 --- a/scripts/zones/Hazhalm_Testing_Grounds/npcs/_260.lua +++ b/scripts/zones/Hazhalm_Testing_Grounds/npcs/_260.lua @@ -9,9 +9,20 @@ local entity = {} local function releaseLamp(player) local tradeContainer = player:getTrade() - if tradeContainer then - tradeContainer:clean() + if not tradeContainer then + return + end + + local item = tradeContainer:getItem() + local itemId = item and item:getID() + if + itemId == xi.item.GLOWING_LAMP or + itemId == xi.item.SMOLDERING_LAMP + then + item:setReservedValue(0) end + + tradeContainer:clean() end entity.onTrade = function(player, npc, trade) @@ -43,7 +54,7 @@ entity.onTrade = function(player, npc, trade) local lampData = xi.einherjar.decypherLamp(lampObj) releaseLamp(player) - local chamberData = xi.einherjar.getChamber(lampData.chamber) + local chamberData = xi.einherjar.getChamber(lampData.chamberId) if not chamberData then xi.einherjar.voidLamp(player, lampObj) @@ -51,15 +62,15 @@ entity.onTrade = function(player, npc, trade) return end - if not xi.einherjar.meetsRequirementsForEntry(player, lampData.chamber) then + if not xi.einherjar.meetsRequirementsForEntry(player, lampData.chamberId) then return end - player:setLocalVar('[ein]requestedChamber', lampData.chamber) + player:setLocalVar('[ein]requestedChamber', lampData.chamberId) player:setLocalVar('[ein]requestedStart', lampData.startTime) player:startEvent(3, - 0x1D + lampData.chamber, + 0x1D + lampData.chamberId, xi.besieged.getMercenaryRank(player), xi.einherjar.settings.EINHERJAR_KO_EXPEL_TIME, xi.einherjar.settings.EINHERJAR_REENTRY_TIME, @@ -100,8 +111,8 @@ entity.onEventUpdate = function(player, csid, option, npc) player:updateEvent(0, 10, - xi.settings.main.EINHERJAR_KO_EXPEL_TIME, - xi.settings.main.EINHERJAR_REENTRY_TIME, + xi.einherjar.settings.EINHERJAR_KO_EXPEL_TIME, + xi.einherjar.settings.EINHERJAR_REENTRY_TIME, 0, xi.einherjar.getChambersMenu(player), xi.item.SMOLDERING_LAMP, @@ -147,7 +158,13 @@ entity.onEventFinish = function(player, csid, option) else -- event cancelled releaseLamp(player) end - elseif csid == 3 and option == 1 then -- player requested entry into chamber + elseif csid == 3 then + releaseLamp(player) + + if option ~= 1 then + return + end + local requestedChamber = player:getLocalVar('[ein]requestedChamber') local requestedStart = player:getLocalVar('[ein]requestedStart') player:setLocalVar('[ein]requestedChamber', 0) diff --git a/src/map/items.h b/src/map/items.h index b78311f9a34..d5e6959b352 100644 --- a/src/map/items.h +++ b/src/map/items.h @@ -46,7 +46,16 @@ enum ITEMID : uint16 SAIRUI_RAN = 1188, KODOKU = 1191, SHINOBI_TABI = 1194, + CHOCOBO_EGG_FAINTLY = 2312, + VCS_REGISTRATION_CARD = 2313, + CHOCOBO_EGG_SLIGHTLY = 2314, + CHOCOBO_EGG_A_BIT = 2317, + CHOCOBO_EGG_A_LITTLE = 2318, + CHOCOBO_EGG_SOMEWHAT = 2319, + CHOCOCARD_M = 2339, + CHOCOCARD_F = 2342, VCS_HONEYMOON_TICKET = 2344, + CRA_RACING_FORM = 2402, SOUL_PLATE = 2477, CHOCOBET_TICKET = 2479, RACE_COMPLETION_CERTIFICATE = 2481, @@ -54,9 +63,11 @@ enum ITEMID : uint16 ILRUSI_TRAVEL_LEDGER = 2495, SANJAKU_TENUGUI = 2553, SOSHI = 2555, + MOG_BONANZA_MARBLE = 2559, KABENRO = 2642, JINKO = 2643, RYUNO = 2644, + EVOLITH = 2783, KINDREDS_CREST = 2955, HIGH_KINDREDS_CREST = 2956, MOKUJIN = 2970, @@ -82,6 +93,7 @@ enum ITEMID : uint16 LEGION_PASS = 3528, DILIGENCE_GRIMOIRE = 3875, SANCTITY_GRIMOIRE = 3879, + BONANZA_PEARL = 4089, FIRE_CRYSTAL = 4096, ICE_CRYSTAL = 4097, WIND_CRYSTAL = 4098, @@ -105,6 +117,7 @@ enum ITEMID : uint16 PAGE_OF_THE_BALLISTA_WHITEBOOK = 5303, COPY_OF_THE_BRENNER_BLUEBOOK = 5323, PAGE_OF_THE_BRENNER_BLACKBOOK = 5326, + GLOWING_LAMP = 5414, PYRE_CRYSTAL = 6506, FROST_CRYSTAL = 6507, VORTEX_CRYSTAL = 6508, @@ -115,10 +128,16 @@ enum ITEMID : uint16 SHADOW_CRYSTAL = 6513, RANKA = 8803, FURUSUMI = 8804, + WOODWORKING_SET_25 = 9412, + COOKING_SET_95 = 9483, DREAM_BELL = 18863, DREAM_BELL_P1 = 18864, LADY_BELL = 18868, LADY_BELL_P1 = 18869, MARVELOUS_CHEER = 22283, + MAZE_TABULA_M01 = 28672, + MAZE_TABULA_M03 = 28674, + MAZE_TABULA_R01 = 28704, + MAZE_TABULA_R03 = 28706, GIL = 65535, }; diff --git a/src/map/items/CMakeLists.txt b/src/map/items/CMakeLists.txt index 36c453eccca..d5d776a0383 100644 --- a/src/map/items/CMakeLists.txt +++ b/src/map/items/CMakeLists.txt @@ -8,16 +8,30 @@ set(ITEM_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/exdata/betting_slip.h ${CMAKE_CURRENT_SOURCE_DIR}/exdata/brenner_book.cpp ${CMAKE_CURRENT_SOURCE_DIR}/exdata/brenner_book.h + ${CMAKE_CURRENT_SOURCE_DIR}/exdata/chocobo_card.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/exdata/chocobo_card.h + ${CMAKE_CURRENT_SOURCE_DIR}/exdata/chocobo_egg.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/exdata/chocobo_egg.h + ${CMAKE_CURRENT_SOURCE_DIR}/exdata/crafting_set.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/exdata/crafting_set.h + ${CMAKE_CURRENT_SOURCE_DIR}/exdata/evolith.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/exdata/evolith.h + ${CMAKE_CURRENT_SOURCE_DIR}/exdata/glowing_lamp.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/exdata/glowing_lamp.h ${CMAKE_CURRENT_SOURCE_DIR}/exdata/honeymoon_ticket.cpp ${CMAKE_CURRENT_SOURCE_DIR}/exdata/honeymoon_ticket.h ${CMAKE_CURRENT_SOURCE_DIR}/exdata/legion_pass.cpp ${CMAKE_CURRENT_SOURCE_DIR}/exdata/legion_pass.h + ${CMAKE_CURRENT_SOURCE_DIR}/exdata/lottery_ticket.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/exdata/lottery_ticket.h ${CMAKE_CURRENT_SOURCE_DIR}/exdata/meeble_grimoire.cpp ${CMAKE_CURRENT_SOURCE_DIR}/exdata/meeble_grimoire.h ${CMAKE_CURRENT_SOURCE_DIR}/exdata/perpetual_hourglass.cpp ${CMAKE_CURRENT_SOURCE_DIR}/exdata/perpetual_hourglass.h ${CMAKE_CURRENT_SOURCE_DIR}/exdata/race_certificate.cpp ${CMAKE_CURRENT_SOURCE_DIR}/exdata/race_certificate.h + ${CMAKE_CURRENT_SOURCE_DIR}/exdata/tabula.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/exdata/tabula.h ${CMAKE_CURRENT_SOURCE_DIR}/item_currency.cpp ${CMAKE_CURRENT_SOURCE_DIR}/item_currency.h ${CMAKE_CURRENT_SOURCE_DIR}/item_equipment.cpp diff --git a/src/map/items/exdata.cpp b/src/map/items/exdata.cpp index 4c8e55aa11d..28d1a635eca 100644 --- a/src/map/items/exdata.cpp +++ b/src/map/items/exdata.cpp @@ -82,6 +82,44 @@ auto getType(const CItem* item) -> Type return Type::AssaultLog; } + if (itemId == BONANZA_PEARL || itemId == MOG_BONANZA_MARBLE) + { + return Type::LotteryTicket; + } + + if ((itemId >= MAZE_TABULA_M01 && itemId <= MAZE_TABULA_M03) || + (itemId >= MAZE_TABULA_R01 && itemId <= MAZE_TABULA_R03)) + { + return Type::Tabula; + } + + if (itemId == EVOLITH) + { + return Type::Evolith; + } + + if (itemId >= WOODWORKING_SET_25 && itemId <= COOKING_SET_95) + { + return Type::CraftingSet; + } + + if (itemId == GLOWING_LAMP) + { + return Type::GlowingLamp; + } + + if (itemId == CHOCOBO_EGG_FAINTLY || itemId == CHOCOBO_EGG_SLIGHTLY || + (itemId >= CHOCOBO_EGG_A_BIT && itemId <= CHOCOBO_EGG_SOMEWHAT)) + { + return Type::ChocoboEgg; + } + + if (itemId == VCS_REGISTRATION_CARD || itemId == CHOCOCARD_M || + itemId == CHOCOCARD_F || itemId == CRA_RACING_FORM) + { + return Type::ChocoboCard; + } + return Type::None; } @@ -114,6 +152,27 @@ auto toTable(const CItem* item, sol::table& table) -> bool case Type::RaceCertificate: item->exdata().toTable(table); return true; + case Type::LotteryTicket: + item->exdata().toTable(table); + return true; + case Type::Tabula: + item->exdata().toTable(table); + return true; + case Type::Evolith: + item->exdata().toTable(table); + return true; + case Type::CraftingSet: + item->exdata().toTable(table); + return true; + case Type::GlowingLamp: + item->exdata().toTable(table); + return true; + case Type::ChocoboEgg: + item->exdata().toTable(table); + return true; + case Type::ChocoboCard: + item->exdata().toTable(table); + return true; default: return false; } @@ -148,6 +207,27 @@ auto fromTable(CItem* item, const sol::table& data) -> bool case Type::RaceCertificate: item->exdata().fromTable(data); return true; + case Type::LotteryTicket: + item->exdata().fromTable(data); + return true; + case Type::Tabula: + item->exdata().fromTable(data); + return true; + case Type::Evolith: + item->exdata().fromTable(data); + return true; + case Type::CraftingSet: + item->exdata().fromTable(data); + return true; + case Type::GlowingLamp: + item->exdata().fromTable(data); + return true; + case Type::ChocoboEgg: + item->exdata().fromTable(data); + return true; + case Type::ChocoboCard: + item->exdata().fromTable(data); + return true; default: return false; } diff --git a/src/map/items/exdata.h b/src/map/items/exdata.h index f46b878644b..0a5d9254d73 100644 --- a/src/map/items/exdata.h +++ b/src/map/items/exdata.h @@ -28,11 +28,18 @@ #include "exdata/assault_log.h" #include "exdata/betting_slip.h" #include "exdata/brenner_book.h" +#include "exdata/chocobo_card.h" +#include "exdata/chocobo_egg.h" +#include "exdata/crafting_set.h" +#include "exdata/evolith.h" +#include "exdata/glowing_lamp.h" #include "exdata/honeymoon_ticket.h" #include "exdata/legion_pass.h" +#include "exdata/lottery_ticket.h" #include "exdata/meeble_grimoire.h" #include "exdata/perpetual_hourglass.h" #include "exdata/race_certificate.h" +#include "exdata/tabula.h" class CItem; diff --git a/src/map/items/exdata/chocobo_card.cpp b/src/map/items/exdata/chocobo_card.cpp new file mode 100644 index 00000000000..63b65bcfa6d --- /dev/null +++ b/src/map/items/exdata/chocobo_card.cpp @@ -0,0 +1,124 @@ +/* +=========================================================================== + + Copyright (c) 2026 LandSandBoat Dev Teams + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see http://www.gnu.org/licenses/ + +=========================================================================== +*/ + +#include "chocobo_card.h" + +#include "common/lua.h" + +namespace +{ + +auto statToTable(const Exdata::ChocoboStatByte& stat) -> sol::table +{ + sol::table t = lua.create_table(); + t["trait"] = static_cast(stat.Trait); + t["rp"] = static_cast(stat.RP); + t["rank"] = static_cast(stat.Rank); + return t; +} + +void statFromTable(Exdata::ChocoboStatByte& stat, const sol::table& t) +{ + stat.Trait = Exdata::get_or(t, "trait", stat.Trait) ? 1 : 0; + stat.RP = Exdata::get_or(t, "rp", stat.RP); + stat.Rank = Exdata::get_or(t, "rank", stat.Rank); +} + +} // anonymous namespace + +void Exdata::ChocoboCard::toTable(sol::table& table) const +{ + table["strength"] = statToTable(this->STR); + table["endurance"] = statToTable(this->END); + table["discernment"] = statToTable(this->DSC); + + sol::table rcp = lua.create_table(); + rcp["rp"] = static_cast(this->RCP.RP); + rcp["rank"] = static_cast(this->RCP.Rank); + table["receptivity"] = rcp; + + sol::table dna = lua.create_table(); + dna[1] = static_cast(this->DNA1); + dna[2] = static_cast(this->DNA2); + dna[3] = static_cast(this->DNA3); + table["dna"] = dna; + + sol::table abilities = lua.create_table(); + abilities[1] = static_cast(this->Ability1); + abilities[2] = static_cast(this->Ability2); + table["abilities"] = abilities; + + table["temperament"] = static_cast(this->Temperament); + table["weather"] = static_cast(this->Weather); + table["gender"] = static_cast(this->Gender); + table["color"] = static_cast(this->Color); + table["size"] = static_cast(this->Size); + + table["name"] = Exdata::decodeSignature(this->Signature); +} + +void Exdata::ChocoboCard::fromTable(const sol::table& data) +{ + if (sol::optional strength = data["strength"]) + { + statFromTable(this->STR, *strength); + } + + if (sol::optional endurance = data["endurance"]) + { + statFromTable(this->END, *endurance); + } + + if (sol::optional discernment = data["discernment"]) + { + statFromTable(this->DSC, *discernment); + } + + if (sol::optional receptivity = data["receptivity"]) + { + this->RCP.RP = Exdata::get_or(*receptivity, "rp", this->RCP.RP); + this->RCP.Rank = Exdata::get_or(*receptivity, "rank", this->RCP.Rank); + } + + if (sol::optional dna = data["dna"]) + { + this->DNA1 = Exdata::get_or(*dna, 1, this->DNA1); + this->DNA2 = Exdata::get_or(*dna, 2, this->DNA2); + this->DNA3 = Exdata::get_or(*dna, 3, this->DNA3); + } + + if (sol::optional abilities = data["abilities"]) + { + this->Ability1 = Exdata::get_or(*abilities, 1, this->Ability1); + this->Ability2 = Exdata::get_or(*abilities, 2, this->Ability2); + } + + this->Temperament = Exdata::get_or(data, "temperament", this->Temperament); + this->Weather = Exdata::get_or(data, "weather", this->Weather); + this->Gender = Exdata::get_or(data, "gender", this->Gender); + this->Color = Exdata::get_or(data, "color", this->Color); + this->Size = Exdata::get_or(data, "size", this->Size); + + if (sol::optional name = data["name"]) + { + Exdata::encodeSignature(*name, this->Signature); + } +} diff --git a/src/map/items/exdata/chocobo_card.h b/src/map/items/exdata/chocobo_card.h new file mode 100644 index 00000000000..cec2d349ad6 --- /dev/null +++ b/src/map/items/exdata/chocobo_card.h @@ -0,0 +1,67 @@ +/* +=========================================================================== + + Copyright (c) 2026 LandSandBoat Dev Teams + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see http://www.gnu.org/licenses/ + +=========================================================================== +*/ + +#pragma once + +#include "base.h" + +namespace Exdata +{ +#pragma pack(push, 1) +// Reference: https://github.com/Ivaar/Windower-addons/tree/master/chococard +struct ChocoboStatByte +{ + uint8_t Trait : 1; + uint8_t RP : 4; + uint8_t Rank : 3; +}; + +struct ChocoboStatByteRCP +{ + uint8_t RP : 5; + uint8_t Rank : 3; +}; + +struct ChocoboCard +{ + ChocoboStatByte STR; + ChocoboStatByte END; + ChocoboStatByte DSC; + ChocoboStatByteRCP RCP; + uint32_t DNA1 : 3; + uint32_t DNA2 : 3; + uint32_t DNA3 : 3; + uint32_t Ability1 : 4; + uint32_t Ability2 : 4; + uint32_t Temperament : 3; + uint32_t Weather : 4; + uint32_t Gender : 1; + uint32_t Color : 3; + uint32_t Size : 3; + uint32_t unknown00 : 1; + uint32_t padding00; + uint8_t Signature[12]; + + void toTable(sol::table& table) const; + void fromTable(const sol::table& data); +}; +#pragma pack(pop) +} // namespace Exdata diff --git a/src/map/items/exdata/chocobo_egg.cpp b/src/map/items/exdata/chocobo_egg.cpp new file mode 100644 index 00000000000..f03f14bb48a --- /dev/null +++ b/src/map/items/exdata/chocobo_egg.cpp @@ -0,0 +1,51 @@ +/* +=========================================================================== + + Copyright (c) 2026 LandSandBoat Dev Teams + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see http://www.gnu.org/licenses/ + +=========================================================================== +*/ + +#include "chocobo_egg.h" + +#include "common/lua.h" + +void Exdata::ChocoboEgg::toTable(sol::table& table) const +{ + sol::table dna = lua.create_table(); + dna[1] = static_cast(this->DNA1); + dna[2] = static_cast(this->DNA2); + dna[3] = static_cast(this->DNA3); + table["dna"] = dna; + + table["ability"] = static_cast(this->Ability); + table["plan"] = static_cast(this->Plan); + table["isBred"] = static_cast(this->IsBred); +} + +void Exdata::ChocoboEgg::fromTable(const sol::table& data) +{ + if (sol::optional dna = data["dna"]) + { + this->DNA1 = Exdata::get_or(*dna, 1, this->DNA1); + this->DNA2 = Exdata::get_or(*dna, 2, this->DNA2); + this->DNA3 = Exdata::get_or(*dna, 3, this->DNA3); + } + + this->Ability = Exdata::get_or(data, "ability", this->Ability); + this->Plan = Exdata::get_or(data, "plan", this->Plan); + this->IsBred = Exdata::get_or(data, "isBred", this->IsBred) ? 1 : 0; +} diff --git a/src/map/items/exdata/chocobo_egg.h b/src/map/items/exdata/chocobo_egg.h new file mode 100644 index 00000000000..506c89f668d --- /dev/null +++ b/src/map/items/exdata/chocobo_egg.h @@ -0,0 +1,47 @@ +/* +=========================================================================== + + Copyright (c) 2026 LandSandBoat Dev Teams + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see http://www.gnu.org/licenses/ + +=========================================================================== +*/ + +#pragma once + +#include "base.h" + +namespace Exdata +{ +#pragma pack(push, 1) +// Chocobo Egg exdata (items: 2312, 2314, 2317-2319) +// Reference: https://github.com/Ivaar/Windower-addons/tree/master/chococard +struct ChocoboEgg +{ + uint32_t DNA1 : 3; + uint32_t DNA2 : 3; + uint32_t DNA3 : 3; + uint32_t Ability : 4; + uint32_t unknown00 : 1; + uint32_t Plan : 2; + uint32_t unknown01 : 15; + uint32_t IsBred : 1; + uint8_t padding00[20]; + + void toTable(sol::table& table) const; + void fromTable(const sol::table& data); +}; +#pragma pack(pop) +} // namespace Exdata diff --git a/src/map/items/exdata/crafting_set.cpp b/src/map/items/exdata/crafting_set.cpp new file mode 100644 index 00000000000..887540724be --- /dev/null +++ b/src/map/items/exdata/crafting_set.cpp @@ -0,0 +1,38 @@ +/* +=========================================================================== + + Copyright (c) 2026 LandSandBoat Dev Teams + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see http://www.gnu.org/licenses/ + +=========================================================================== +*/ + +#include "crafting_set.h" + +void Exdata::CraftingSet::toTable(sol::table& table) const +{ + table["quality"] = this->Quality; + table["signature"] = Exdata::decodeSignature(this->Signature); +} + +void Exdata::CraftingSet::fromTable(const sol::table& data) +{ + this->Quality = Exdata::get_or(data, "quality", this->Quality); + + if (sol::optional sig = data["signature"]) + { + Exdata::encodeSignature(*sig, this->Signature); + } +} diff --git a/src/map/items/exdata/crafting_set.h b/src/map/items/exdata/crafting_set.h new file mode 100644 index 00000000000..efa9fe8475b --- /dev/null +++ b/src/map/items/exdata/crafting_set.h @@ -0,0 +1,40 @@ +/* +=========================================================================== + + Copyright (c) 2026 LandSandBoat Dev Teams + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see http://www.gnu.org/licenses/ + +=========================================================================== +*/ + +#pragma once + +#include "base.h" + +namespace Exdata +{ +#pragma pack(push, 1) +struct CraftingSet +{ + uint16_t padding00; + uint16_t Quality; + uint8_t padding01[8]; + uint8_t Signature[12]; + + void toTable(sol::table& table) const; + void fromTable(const sol::table& data); +}; +#pragma pack(pop) +} // namespace Exdata diff --git a/src/map/items/exdata/evolith.cpp b/src/map/items/exdata/evolith.cpp new file mode 100644 index 00000000000..20fc86dbe24 --- /dev/null +++ b/src/map/items/exdata/evolith.cpp @@ -0,0 +1,44 @@ +/* +=========================================================================== + + Copyright (c) 2026 LandSandBoat Dev Teams + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see http://www.gnu.org/licenses/ + +=========================================================================== +*/ + +#include "evolith.h" + +void Exdata::Evolith::toTable(sol::table& table) const +{ + table["augment"] = static_cast(this->Augment); + table["shape"] = static_cast(this->Shape); + table["element"] = static_cast(this->Element); + table["bonus"] = static_cast(this->Bonus); + table["signature"] = Exdata::decodeSignature(this->Signature); +} + +void Exdata::Evolith::fromTable(const sol::table& data) +{ + this->Augment = Exdata::get_or(data, "augment", this->Augment); + this->Shape = Exdata::get_or(data, "shape", this->Shape); + this->Element = Exdata::get_or(data, "element", this->Element); + this->Bonus = Exdata::get_or(data, "bonus", this->Bonus); + + if (sol::optional sig = data["signature"]) + { + Exdata::encodeSignature(*sig, this->Signature); + } +} diff --git a/src/map/items/exdata/evolith.h b/src/map/items/exdata/evolith.h new file mode 100644 index 00000000000..935bf75084a --- /dev/null +++ b/src/map/items/exdata/evolith.h @@ -0,0 +1,43 @@ +/* +=========================================================================== + + Copyright (c) 2026 LandSandBoat Dev Teams + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see http://www.gnu.org/licenses/ + +=========================================================================== +*/ + +#pragma once + +#include "base.h" + +namespace Exdata +{ +#pragma pack(push, 1) +struct Evolith +{ + uint32_t Augment : 10; + uint32_t Shape : 4; + uint32_t Element : 3; + uint32_t Bonus : 4; + uint32_t padding00 : 11; + uint8_t padding01[8]; + uint8_t Signature[12]; + + void toTable(sol::table& table) const; + void fromTable(const sol::table& data); +}; +#pragma pack(pop) +} // namespace Exdata diff --git a/src/map/items/exdata/glowing_lamp.cpp b/src/map/items/exdata/glowing_lamp.cpp new file mode 100644 index 00000000000..a38c258ff19 --- /dev/null +++ b/src/map/items/exdata/glowing_lamp.cpp @@ -0,0 +1,47 @@ +/* +=========================================================================== + + Copyright (c) 2026 LandSandBoat Dev Teams + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see http://www.gnu.org/licenses/ + +=========================================================================== +*/ + +#include "glowing_lamp.h" + +namespace +{ +constexpr uint16_t CHAMBER_ID_OFFSET = 0x1D; +} + +void Exdata::GlowingLamp::toTable(sol::table& table) const +{ + table["chamberId"] = (this->ChamberId >= CHAMBER_ID_OFFSET) ? static_cast(this->ChamberId - CHAMBER_ID_OFFSET) : 0; + table["flags"] = static_cast(this->Flags); + table["startTime"] = this->StartTime; + table["endTime"] = this->EndTime; +} + +void Exdata::GlowingLamp::fromTable(const sol::table& data) +{ + if (sol::optional chamberId = data["chamberId"]) + { + this->ChamberId = (*chamberId == 0) ? 0 : *chamberId + CHAMBER_ID_OFFSET; + } + + this->Flags = Exdata::get_or(data, "flags", this->Flags); + this->StartTime = Exdata::get_or(data, "startTime", this->StartTime); + this->EndTime = Exdata::get_or(data, "endTime", this->EndTime); +} diff --git a/src/map/items/exdata/glowing_lamp.h b/src/map/items/exdata/glowing_lamp.h new file mode 100644 index 00000000000..727a50f3a30 --- /dev/null +++ b/src/map/items/exdata/glowing_lamp.h @@ -0,0 +1,43 @@ +/* +=========================================================================== + + Copyright (c) 2026 LandSandBoat Dev Teams + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see http://www.gnu.org/licenses/ + +=========================================================================== +*/ + +#pragma once + +#include "base.h" + +namespace Exdata +{ +#pragma pack(push, 1) +struct GlowingLamp +{ + uint16_t ChamberId; + uint8_t Flags : 3; + uint8_t padding00 : 5; + uint8_t padding01[5]; + uint32_t StartTime; + uint32_t EndTime; + uint8_t padding02[8]; + + void toTable(sol::table& table) const; + void fromTable(const sol::table& data); +}; +#pragma pack(pop) +} // namespace Exdata diff --git a/src/map/items/exdata/lottery_ticket.cpp b/src/map/items/exdata/lottery_ticket.cpp new file mode 100644 index 00000000000..eda59143a24 --- /dev/null +++ b/src/map/items/exdata/lottery_ticket.cpp @@ -0,0 +1,34 @@ +/* +=========================================================================== + + Copyright (c) 2026 LandSandBoat Dev Teams + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see http://www.gnu.org/licenses/ + +=========================================================================== +*/ + +#include "lottery_ticket.h" + +void Exdata::LotteryTicket::toTable(sol::table& table) const +{ + table["number"] = static_cast(this->Number); + table["title"] = static_cast(this->Title); +} + +void Exdata::LotteryTicket::fromTable(const sol::table& data) +{ + this->Number = Exdata::get_or(data, "number", this->Number); + this->Title = Exdata::get_or(data, "title", this->Title); +} diff --git a/src/map/items/exdata/lottery_ticket.h b/src/map/items/exdata/lottery_ticket.h new file mode 100644 index 00000000000..6c37f9cc883 --- /dev/null +++ b/src/map/items/exdata/lottery_ticket.h @@ -0,0 +1,39 @@ +/* +=========================================================================== + + Copyright (c) 2026 LandSandBoat Dev Teams + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see http://www.gnu.org/licenses/ + +=========================================================================== +*/ + +#pragma once + +#include "base.h" + +namespace Exdata +{ +#pragma pack(push, 1) +struct LotteryTicket +{ + uint32_t Number : 24; + uint32_t Title : 8; + uint8_t padding00[20]; + + void toTable(sol::table& table) const; + void fromTable(const sol::table& data); +}; +#pragma pack(pop) +} // namespace Exdata diff --git a/src/map/items/exdata/tabula.cpp b/src/map/items/exdata/tabula.cpp new file mode 100644 index 00000000000..49a21fc085e --- /dev/null +++ b/src/map/items/exdata/tabula.cpp @@ -0,0 +1,122 @@ +/* +=========================================================================== + + Copyright (c) 2026 LandSandBoat Dev Teams + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see http://www.gnu.org/licenses/ + +=========================================================================== +*/ + +#include "tabula.h" +#include "common/lua.h" + +namespace +{ +constexpr uint8_t GRID_SIZE = 25; // 5x5 board positions +constexpr uint8_t NUM_RUNES = 12; // Max runes per tabula +constexpr uint8_t RUNE_ID_BITS = 9; // Bits per rune ID +constexpr uint8_t RUNE_ROT_BITS = 2; // Bits per rotation +constexpr uint8_t ROT_BIT_OFFSET = NUM_RUNES * RUNE_ID_BITS; // Bit 108: rotations start +constexpr uint8_t USES_BIT_OFFSET = ROT_BIT_OFFSET + NUM_RUNES * RUNE_ROT_BITS; // Bit 132: uses field start +constexpr uint8_t USES_BITS = 7; // Bits for uses count +} // anonymous namespace + +void Exdata::Tabula::toTable(sol::table& table) const +{ + table["voucher"] = this->Voucher; + + sol::table runes = lua.create_table(); + uint8_t slot = 0; + for (uint8_t pos = 0; pos < GRID_SIZE && slot < NUM_RUNES; ++pos) + { + const bool hasAnchor = this->AnchorBits >> (GRID_SIZE - 1 - pos) & 1; + if (!hasAnchor) + { + continue; + } + + const uint8_t idOffset = slot * RUNE_ID_BITS; + const uint8_t rotOffset = ROT_BIT_OFFSET + slot * RUNE_ROT_BITS; + const uint16_t runeId = static_cast(unpackBitsLE(this->RuneStream, 0, idOffset, RUNE_ID_BITS)); + + if (runeId > 0) + { + const uint8_t rotation = static_cast(unpackBitsLE(this->RuneStream, 0, rotOffset, RUNE_ROT_BITS)); + + sol::table rune = lua.create_table(); + rune["id"] = runeId; + rune["rotation"] = rotation; + rune["position"] = pos; + + runes[runes.size() + 1] = rune; + } + + ++slot; + } + + table["runes"] = runes; + table["uses"] = static_cast(unpackBitsLE(this->RuneStream, 0, USES_BIT_OFFSET, USES_BITS)); +} + +void Exdata::Tabula::fromTable(const sol::table& data) +{ + this->Voucher = Exdata::get_or(data, "voucher", this->Voucher); + + if (sol::optional runes = data["runes"]) + { + this->AnchorBits = 0; + std::memset(this->RuneStream, 0, sizeof(this->RuneStream)); + + uint8_t slot = 0; + for (auto& entry : *runes) + { + if (slot >= NUM_RUNES) + { + break; + } + + auto rune = entry.second.as>(); + if (!rune) + { + continue; + } + + if (sol::optional id = (*rune)["id"]) + { + packBitsLE(this->RuneStream, *id, slot * RUNE_ID_BITS, RUNE_ID_BITS); + } + + if (sol::optional rot = (*rune)["rotation"]) + { + packBitsLE(this->RuneStream, *rot, ROT_BIT_OFFSET + slot * RUNE_ROT_BITS, RUNE_ROT_BITS); + } + + if (sol::optional pos = (*rune)["position"]) + { + if (*pos < GRID_SIZE) + { + this->AnchorBits |= 1u << (GRID_SIZE - 1 - *pos); // Mark grid position as occupied + } + } + + ++slot; + } + } + + if (sol::optional uses = data["uses"]) + { + packBitsLE(this->RuneStream, *uses, USES_BIT_OFFSET, USES_BITS); + } +} diff --git a/src/map/items/exdata/tabula.h b/src/map/items/exdata/tabula.h new file mode 100644 index 00000000000..3b81819099d --- /dev/null +++ b/src/map/items/exdata/tabula.h @@ -0,0 +1,59 @@ +/* +=========================================================================== + + Copyright (c) 2026 LandSandBoat Dev Teams + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see http://www.gnu.org/licenses/ + +=========================================================================== +*/ + +#pragma once + +#include "base.h" + +namespace Exdata +{ + +#pragma pack(push, 1) +struct Tabula +{ + uint32_t Voucher : 7; + + // 25-bit mask for the 5x5 grid. Bit 24 = pos 0, bit 0 = pos 24. + // Set bits mark which grid positions have runes. + // Runes in RuneStream are stored in the order the set bits appear (left to right, top to bottom). + // + // +---+---+---+---+---+ + // | | A | | B | | AnchorBits = 01010_00001_01000_00000_00100 + // +---+---+---+---+---+ pos: 1,3,9,11,22 + // | | | | | C | + // +---+---+---+---+---+ RuneStream slot 0 -> A (pos 1) + // | | D | | | | RuneStream slot 1 -> B (pos 3) + // +---+---+---+---+---+ RuneStream slot 2 -> C (pos 9) + // | | | | | | RuneStream slot 3 -> D (pos 11) + // +---+---+---+---+---+ RuneStream slot 4 -> E (pos 22) + // | | | E | | | + // +---+---+---+---+---+ + // + uint32_t AnchorBits : 25; + + // 160 bits: [12x 9-bit rune IDs][12x 2-bit rotations][7-bit uses (R Tabulas only)] + uint8_t RuneStream[20]; + + void toTable(sol::table& table) const; + void fromTable(const sol::table& data); +}; +#pragma pack(pop) +} // namespace Exdata