From d62518a18ae4f5e4fba5890a6fee6a2992628a8e Mon Sep 17 00:00:00 2001 From: sruon Date: Sat, 28 Mar 2026 14:13:26 -0600 Subject: [PATCH] Exdata definitions Assault Log Meeble Grimoires Honeymoon Tickets Race Completion certificates Chocobet Brenner Book Co-Authored-By: atom0s --- scripts/enum/item.lua | 12 ++ scripts/specs/core/CItem.lua | 4 +- scripts/specs/core/Exdata.lua | 42 ++++- scripts/tests/systems/exdata.lua | 187 +++++++++++++++++---- scripts/zones/Upper_Jeuno/npcs/Finbarr.lua | 6 +- src/map/items.h | 177 ++++++++++--------- src/map/items/CMakeLists.txt | 12 ++ src/map/items/exdata.cpp | 68 ++++++++ src/map/items/exdata.h | 6 + src/map/items/exdata/assault_log.cpp | 58 +++++++ src/map/items/exdata/assault_log.h | 48 ++++++ src/map/items/exdata/betting_slip.cpp | 40 +++++ src/map/items/exdata/betting_slip.h | 43 +++++ src/map/items/exdata/brenner_book.cpp | 35 ++++ src/map/items/exdata/brenner_book.h | 43 +++++ src/map/items/exdata/honeymoon_ticket.cpp | 40 +++++ src/map/items/exdata/honeymoon_ticket.h | 39 +++++ src/map/items/exdata/meeble_grimoire.cpp | 75 +++++++++ src/map/items/exdata/meeble_grimoire.h | 43 +++++ src/map/items/exdata/race_certificate.cpp | 34 ++++ src/map/items/exdata/race_certificate.h | 40 +++++ 21 files changed, 924 insertions(+), 128 deletions(-) create mode 100644 src/map/items/exdata/assault_log.cpp create mode 100644 src/map/items/exdata/assault_log.h create mode 100644 src/map/items/exdata/betting_slip.cpp create mode 100644 src/map/items/exdata/betting_slip.h create mode 100644 src/map/items/exdata/brenner_book.cpp create mode 100644 src/map/items/exdata/brenner_book.h create mode 100644 src/map/items/exdata/honeymoon_ticket.cpp create mode 100644 src/map/items/exdata/honeymoon_ticket.h create mode 100644 src/map/items/exdata/meeble_grimoire.cpp create mode 100644 src/map/items/exdata/meeble_grimoire.h create mode 100644 src/map/items/exdata/race_certificate.cpp create mode 100644 src/map/items/exdata/race_certificate.h diff --git a/scripts/enum/item.lua b/scripts/enum/item.lua index 033756ab5ab..447f805101d 100644 --- a/scripts/enum/item.lua +++ b/scripts/enum/item.lua @@ -633,6 +633,8 @@ xi.item = CARBUNCLES_RUBY = 1125, BEASTMENS_SEAL = 1126, KINDREDS_SEAL = 1127, + COPY_OF_THE_WYVERN_CODEX = 1128, + COPY_OF_THE_GRIFFON_CODEX = 1129, MOON_ORB = 1130, STAR_ORB = 1131, SQUARE_OF_RAXA = 1132, @@ -1655,9 +1657,13 @@ xi.item = CHUNK_OF_KAOLIN = 2475, SPOOL_OF_PLATINUM_SILK_THREAD = 2476, SOUL_PLATE = 2477, + CHOCOBET_TICKET = 2479, + RACE_COMPLETION_CERTIFICATE = 2481, MERCENARY_CAMP_ENTRY_SLIP = 2487, ALEXANDRITE = 2488, FORBIDDEN_KEY = 2490, + LEUJAOAM_OBSERVATION_LOG = 2491, + ILRUSI_TRAVEL_LEDGER = 2495, BLACK_PUPPET_TURBAN = 2501, WHITE_PUPPET_TURBAN = 2502, HANDFUL_OF_ALMONDS = 2503, @@ -2357,6 +2363,8 @@ xi.item = VALKYRIES_TEAR = 3856, VALKYRIES_WING = 3867, VALKYRIES_SOUL = 3868, + DILIGENCE_GRIMOIRE = 3875, + SANCTITY_GRIMOIRE = 3879, COPY_OF_MELODIOUS_PLANS = 3885, TIMBRE_CASE_KIT = 3886, MUSICHINERY_KIT = 3887, @@ -3230,6 +3238,8 @@ xi.item = CLUSTER_OF_RADIANT_MEMORIES = 5292, CLUSTER_OF_MALEVOLENT_MEMORIES = 5293, DISH_OF_SALSA = 5299, + COPY_OF_THE_BALLISTA_REDBOOK = 5300, + PAGE_OF_THE_BALLISTA_WHITEBOOK = 5303, TOOLBAG_UCHITAKE = 5308, TOOLBAG_TSURARA = 5309, TOOLBAG_KAWAHORI_OGI = 5310, @@ -3243,6 +3253,8 @@ xi.item = TOOLBAG_KODOKU = 5318, TOOLBAG_SHINOBI_TABI = 5319, FLASK_OF_HEALING_POWDER = 5322, + COPY_OF_THE_BRENNER_BLUEBOOK = 5323, + PAGE_OF_THE_BRENNER_BLACKBOOK = 5326, TARUTARU_SNARE = 5329, MITHRA_SNARE = 5330, QIQIRN_MINE = 5331, diff --git a/scripts/specs/core/CItem.lua b/scripts/specs/core/CItem.lua index 565497bf315..0f183a2caf3 100644 --- a/scripts/specs/core/CItem.lua +++ b/scripts/specs/core/CItem.lua @@ -195,11 +195,11 @@ function CItem:getSoulPlateData() end ---@nodiscard ----@return ExdataLegionPass|ExdataPerpetualHourglass +---@return Exdata function CItem:getExData() end ----@param data ExdataLegionPass|ExdataPerpetualHourglass +---@param data Exdata function CItem:setExData(data) end diff --git a/scripts/specs/core/Exdata.lua b/scripts/specs/core/Exdata.lua index 385d1f03596..a757fd41cdf 100644 --- a/scripts/specs/core/Exdata.lua +++ b/scripts/specs/core/Exdata.lua @@ -1,12 +1,40 @@ ---@meta ---@class ExdataLegionPass ----@field timestamp integer # How long until the pass expires. Usually 5 minutes from creation. ----@field title xi.legion.title # Legion chamber ----@field signature string # 12 characters pass owner name +---@field timestamp integer # How long until the pass expires. Usually 5 minutes from creation. +---@field title xi.legion.title # Legion chamber +---@field signature string # 12 characters pass owner name ---@class ExdataPerpetualHourglass ----@field flags integer # Undocumented flags. Changes Hourglass text color to denote status. ----@field startTime integer # Reservation start time ----@field endTime integer # Reservation end time ----@field zoneId xi.zone # Zone reserved by Hourglass +---@field flags integer # Undocumented flags. Changes Hourglass text color to denote status. +---@field startTime integer # Reservation start time +---@field endTime integer # Reservation end time +---@field zoneId xi.zone # Zone reserved by Hourglass + +---@class ExdataBettingSlip +---@field raceId integer # Chocobo race ID [0-262143] +---@field raceGrade xi.chocoboRacing.raceGrade +---@field racePairingL integer # [0-7], 0-indexed chocobo entry +---@field racePairingR integer # [0-7], 0-indexed chocobo entry +---@field quills integer # [0-999] + +---@class ExdataAssaultLog +---@field flags boolean[] # 10 flags indexed [1-10] representing completed assaults. + +---@class ExdataBrennerBook +---@field timeValue integer # Seconds since epoch 1009929600 (Jan 2, 2002 00:00 UTC) +---@field level xi.brenner.levelCap + +---@class ExdataMeebleGrimoire +---@field clears table # clears[expeditionType][level] = count (0-7) +---@field count integer # Distinct expedition count +---@field zone xi.meeble.zone + +---@class ExdataHoneymoonTicket +---@field plan xi.chocoboRaising.honeymoonPlan + +---@class ExdataRaceCertificate +---@field raceId integer # [0-262143] +---@field raceGrade xi.chocoboRacing.raceGrade + +---@alias Exdata ExdataLegionPass|ExdataPerpetualHourglass|ExdataBettingSlip|ExdataAssaultLog|ExdataBrennerBook|ExdataMeebleGrimoire|ExdataHoneymoonTicket|ExdataRaceCertificate diff --git a/scripts/tests/systems/exdata.lua b/scripts/tests/systems/exdata.lua index d04fb53ff97..a9a2d9caad3 100644 --- a/scripts/tests/systems/exdata.lua +++ b/scripts/tests/systems/exdata.lua @@ -4,9 +4,9 @@ describe('Exdata', function() before_each(function() player = xi.test.world:spawnPlayer( - { - zone = xi.zone.SOUTHERN_SAN_DORIA, - }) + { + zone = xi.zone.SOUTHERN_SAN_DORIA, + }) end) it('can get and set Legion Pass exdata', function() @@ -15,11 +15,11 @@ describe('Exdata', function() local now = GetSystemTime() item:setExData( - { - timestamp = now + 300, - title = xi.legion.title.HALL_OF_AN_36, - signature = player:getName(), - }) + { + timestamp = now + 300, + title = xi.legion.title.HALL_OF_AN_36, + signature = player:getName(), + }) local ex = item:getExData() assert(ex.timestamp == now + 300) @@ -33,12 +33,12 @@ describe('Exdata', function() local now = GetSystemTime() item:setExData( - { - flags = 0x01, - startTime = now, - endTime = now + 1800, - zoneId = xi.zone.DYNAMIS_SAN_DORIA, - }) + { + flags = 0x01, + startTime = now, + endTime = now + 1800, + zoneId = xi.zone.DYNAMIS_SAN_DORIA, + }) local ex = item:getExData() assert(ex.flags == 0x01) @@ -52,11 +52,11 @@ describe('Exdata', function() assert(item) item:setExData( - { - timestamp = 1000, - title = xi.legion.title.HALL_OF_KI_18, - signature = 'TestPlayer', - }) + { + timestamp = 1000, + title = xi.legion.title.HALL_OF_KI_18, + signature = 'TestPlayer', + }) local ex = item:getExData() ex.title = xi.legion.title.HALL_OF_MURU_36 @@ -71,16 +71,16 @@ describe('Exdata', function() it('addItem accepts exdata table', function() local now = GetSystemTime() local item = player:addItem( - { - id = xi.item.LEGION_PASS, - quantity = 1, - exdata = { - timestamp = now + 300, - title = xi.legion.title.HALL_OF_AN_36, - signature = 'AddItemTest', - }, - }) + id = xi.item.LEGION_PASS, + quantity = 1, + exdata = + { + timestamp = now + 300, + title = xi.legion.title.HALL_OF_AN_36, + signature = 'AddItemTest', + }, + }) assert(item) local ex = item:getExData() @@ -91,11 +91,11 @@ describe('Exdata', function() it('addItem accepts raw exdata bytes', function() local item = player:addItem( - { - id = xi.item.FIRE_CRYSTAL, - quantity = 1, - exdata = { [0] = 0x42, [5] = 0xCD }, - }) + { + id = xi.item.FIRE_CRYSTAL, + quantity = 1, + exdata = { [0] = 0x42, [5] = 0xCD }, + }) assert(item) local ex = item:getExDataRaw() @@ -113,6 +113,127 @@ describe('Exdata', function() assert(ex.timestamp == 500) end) + it('can get and set Betting Slip exdata', function() + local item = player:addItem({ id = xi.item.CHOCOBET_TICKET, quantity = 1 }) + assert(item) + + item:setExData( + { + raceId = 12345, + raceGrade = xi.chocoboRacing.raceGrade.C1, + racePairingL = 3, + racePairingR = 5, + quills = 500, + }) + + local ex = item:getExData() + assert(ex.raceId == 12345) + assert(ex.raceGrade == xi.chocoboRacing.raceGrade.C1) + assert(ex.racePairingL == 3) + assert(ex.racePairingR == 5) + assert(ex.quills == 500) + end) + + it('can get and set Race Certificate exdata', function() + local item = player:addItem({ id = xi.item.RACE_COMPLETION_CERTIFICATE, quantity = 1 }) + assert(item) + + item:setExData( + { + raceId = 99999, + raceGrade = xi.chocoboRacing.raceGrade.C3, + }) + + local ex = item:getExData() + assert(ex.raceId == 99999) + assert(ex.raceGrade == xi.chocoboRacing.raceGrade.C3) + end) + + it('can get and set Assault Log exdata', function() + local item = player:addItem({ id = xi.item.LEUJAOAM_OBSERVATION_LOG, quantity = 1 }) + assert(item) + + item:setExData( + { + flags = + { + [1] = true, + [2] = false, + [3] = true, + [4] = false, + [5] = true, + [6] = false, + [7] = true, + [8] = false, + [9] = true, + [10] = false, + }, + }) + + local ex = item:getExData() + assert(ex.flags[1] == true) + assert(ex.flags[2] == false) + assert(ex.flags[3] == true) + assert(ex.flags[7] == true) + assert(ex.flags[10] == false) + end) + + it('can get and set Brenner Book exdata', function() + local item = player:addItem({ id = xi.item.COPY_OF_THE_BRENNER_BLUEBOOK, quantity = 1 }) + assert(item) + + item:setExData( + { + timeValue = 1000000, + level = xi.brenner.levelCap.LV50, + }) + + local ex = item:getExData() + assert(ex.timeValue == 1000000) + assert(ex.level == xi.brenner.levelCap.LV50) + end) + + it('can get and set Meeble Grimoire exdata', function() + local item = player:addItem({ id = xi.item.DILIGENCE_GRIMOIRE, quantity = 1 }) + assert(item) + + item:setExData( + { + clears = + { + [xi.meeble.expeditionType.ADJUNCT] = { [1] = 3, [2] = 2, [3] = 1, [4] = 0 }, + [xi.meeble.expeditionType.ASSISTANT] = { [1] = 1, [2] = 0, [3] = 0, [4] = 0 }, + [xi.meeble.expeditionType.INSTRUCTOR] = { [1] = 0, [2] = 0, [3] = 0, [4] = 0 }, + [xi.meeble.expeditionType.ASC_RESEARCHER] = { [1] = 0, [2] = 0, [3] = 0, [4] = 0 }, + [xi.meeble.expeditionType.RESEARCHER] = { [1] = 0, [2] = 0, [3] = 0, [4] = 0 }, + }, + count = 5, + zone = xi.meeble.zone.SAUROMUGUE_CHAMPAIGN, + }) + + local ex = item:getExData() + assert(ex.clears[xi.meeble.expeditionType.ADJUNCT][1] == 3) + assert(ex.clears[xi.meeble.expeditionType.ADJUNCT][2] == 2) + assert(ex.clears[xi.meeble.expeditionType.ADJUNCT][3] == 1) + assert(ex.clears[xi.meeble.expeditionType.ADJUNCT][4] == 0) + assert(ex.clears[xi.meeble.expeditionType.ASSISTANT][1] == 1) + assert(ex.count == 5) + assert(ex.zone == xi.meeble.zone.SAUROMUGUE_CHAMPAIGN) + end) + + it('can get and set Honeymoon Ticket exdata', function() + local item = player:addItem({ id = xi.item.VCS_HONEYMOON_TICKET, quantity = 1 }) + assert(item) + + item:setExData( + { + plan = xi.chocoboRaising.honeymoonPlan.HIKING, + }) + + local ex = item:getExData() + assert(ex.plan == xi.chocoboRaising.honeymoonPlan.HIKING) + 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/Upper_Jeuno/npcs/Finbarr.lua b/scripts/zones/Upper_Jeuno/npcs/Finbarr.lua index 06cc34bbff0..59395d5606d 100644 --- a/scripts/zones/Upper_Jeuno/npcs/Finbarr.lua +++ b/scripts/zones/Upper_Jeuno/npcs/Finbarr.lua @@ -70,11 +70,9 @@ entity.onEventFinish = function(player, csid, option, npc) not player:hasItem(xi.item.VCS_HONEYMOON_TICKET) then player:delGil(3500) - local signatures = { 'PlanA', 'PlanB', 'PlanC', 'PlanD' } player:addItem({ - id = xi.item.VCS_HONEYMOON_TICKET, - exdata = { [0] = option }, - signature = signatures[option] + id = xi.item.VCS_HONEYMOON_TICKET, + exdata = { plan = option }, }) player:messageSpecial(ID.text.YOU_OBTAIN_A_X, xi.item.VCS_HONEYMOON_TICKET) else diff --git a/src/map/items.h b/src/map/items.h index f1f3c205902..b78311f9a34 100644 --- a/src/map/items.h +++ b/src/map/items.h @@ -26,86 +26,99 @@ enum ITEMID : uint16 { - NEW_LINKSHELL = 512, - LINKSHELL = 513, - PEARLSACK = 514, - LINKPEARL = 515, - BEASTMENS_SEAL = 1126, - KINDREDS_SEAL = 1127, - UCHITAKE = 1161, - TSURARA = 1164, - KAWAHORI_OGI = 1167, - MAKIBISHI = 1170, - HIRAISHIN = 1173, - MIZU_DEPPO = 1176, - SHIHEI = 1179, - JUSATSU = 1182, - KAGINAWA = 1185, - SAIRUI_RAN = 1188, - KODOKU = 1191, - SHINOBI_TABI = 1194, - SOUL_PLATE = 2477, - SANJAKU_TENUGUI = 2553, - SOSHI = 2555, - KABENRO = 2642, - JINKO = 2643, - RYUNO = 2644, - KINDREDS_CREST = 2955, - HIGH_KINDREDS_CREST = 2956, - MOKUJIN = 2970, - INOSHISHINOFUDA = 2971, - SHIKANOFUDA = 2972, - CHONOFUDA = 2973, - FLAME_GEODE = 3297, - SNOW_GEODE = 3298, - BREEZE_GEODE = 3299, - SOIL_GEODE = 3300, - THUNDER_GEODE = 3301, - AQUA_GEODE = 3302, - LIGHT_GEODE = 3303, - SHADOW_GEODE = 3304, - IFRITITE = 3520, - SHIVITE = 3521, - GARUDITE = 3522, - TITANITE = 3523, - RAMUITE = 3524, - LEVIATITE = 3525, - CARBITE = 3526, - FENRITE = 3527, - LEGION_PASS = 3528, - FIRE_CRYSTAL = 4096, - ICE_CRYSTAL = 4097, - WIND_CRYSTAL = 4098, - EARTH_CRYSTAL = 4099, - LIGHTNING_CRYSTAL = 4100, - WATER_CRYSTAL = 4101, - LIGHT_CRYSTAL = 4102, - DARK_CRYSTAL = 4103, - DARK_CLUSTER = 4111, - PERPETUAL_HOURGLASS = 4237, - INFERNO_CRYSTAL = 4238, - GLACIER_CRYSTAL = 4239, - CYCLONE_CRYSTAL = 4240, - TERRA_CRYSTAL = 4241, - PLASMA_CRYSTAL = 4242, - TORRENT_CRYSTAL = 4243, - AURORA_CRYSTAL = 4244, - TWILIGHT_CRYSTAL = 4245, - GYSAHL_GREENS = 4545, - PYRE_CRYSTAL = 6506, - FROST_CRYSTAL = 6507, - VORTEX_CRYSTAL = 6508, - GEO_CRYSTAL = 6509, - BOLT_CRYSTAL = 6510, - FLUID_CRYSTAL = 6511, - GLIMMER_CRYSTAL = 6512, - SHADOW_CRYSTAL = 6513, - RANKA = 8803, - FURUSUMI = 8804, - DREAM_BELL = 18863, - DREAM_BELL_P1 = 18864, - LADY_BELL = 18868, - LADY_BELL_P1 = 18869, - MARVELOUS_CHEER = 22283, - GIL = 65535, + NEW_LINKSHELL = 512, + LINKSHELL = 513, + PEARLSACK = 514, + LINKPEARL = 515, + BEASTMENS_SEAL = 1126, + KINDREDS_SEAL = 1127, + COPY_OF_THE_WYVERN_CODEX = 1128, + COPY_OF_THE_GRIFFON_CODEX = 1129, + UCHITAKE = 1161, + TSURARA = 1164, + KAWAHORI_OGI = 1167, + MAKIBISHI = 1170, + HIRAISHIN = 1173, + MIZU_DEPPO = 1176, + SHIHEI = 1179, + JUSATSU = 1182, + KAGINAWA = 1185, + SAIRUI_RAN = 1188, + KODOKU = 1191, + SHINOBI_TABI = 1194, + VCS_HONEYMOON_TICKET = 2344, + SOUL_PLATE = 2477, + CHOCOBET_TICKET = 2479, + RACE_COMPLETION_CERTIFICATE = 2481, + LEUJAOAM_OBSERVATION_LOG = 2491, + ILRUSI_TRAVEL_LEDGER = 2495, + SANJAKU_TENUGUI = 2553, + SOSHI = 2555, + KABENRO = 2642, + JINKO = 2643, + RYUNO = 2644, + KINDREDS_CREST = 2955, + HIGH_KINDREDS_CREST = 2956, + MOKUJIN = 2970, + INOSHISHINOFUDA = 2971, + SHIKANOFUDA = 2972, + CHONOFUDA = 2973, + FLAME_GEODE = 3297, + SNOW_GEODE = 3298, + BREEZE_GEODE = 3299, + SOIL_GEODE = 3300, + THUNDER_GEODE = 3301, + AQUA_GEODE = 3302, + LIGHT_GEODE = 3303, + SHADOW_GEODE = 3304, + IFRITITE = 3520, + SHIVITE = 3521, + GARUDITE = 3522, + TITANITE = 3523, + RAMUITE = 3524, + LEVIATITE = 3525, + CARBITE = 3526, + FENRITE = 3527, + LEGION_PASS = 3528, + DILIGENCE_GRIMOIRE = 3875, + SANCTITY_GRIMOIRE = 3879, + FIRE_CRYSTAL = 4096, + ICE_CRYSTAL = 4097, + WIND_CRYSTAL = 4098, + EARTH_CRYSTAL = 4099, + LIGHTNING_CRYSTAL = 4100, + WATER_CRYSTAL = 4101, + LIGHT_CRYSTAL = 4102, + DARK_CRYSTAL = 4103, + DARK_CLUSTER = 4111, + PERPETUAL_HOURGLASS = 4237, + INFERNO_CRYSTAL = 4238, + GLACIER_CRYSTAL = 4239, + CYCLONE_CRYSTAL = 4240, + TERRA_CRYSTAL = 4241, + PLASMA_CRYSTAL = 4242, + TORRENT_CRYSTAL = 4243, + AURORA_CRYSTAL = 4244, + TWILIGHT_CRYSTAL = 4245, + GYSAHL_GREENS = 4545, + COPY_OF_THE_BALLISTA_REDBOOK = 5300, + PAGE_OF_THE_BALLISTA_WHITEBOOK = 5303, + COPY_OF_THE_BRENNER_BLUEBOOK = 5323, + PAGE_OF_THE_BRENNER_BLACKBOOK = 5326, + PYRE_CRYSTAL = 6506, + FROST_CRYSTAL = 6507, + VORTEX_CRYSTAL = 6508, + GEO_CRYSTAL = 6509, + BOLT_CRYSTAL = 6510, + FLUID_CRYSTAL = 6511, + GLIMMER_CRYSTAL = 6512, + SHADOW_CRYSTAL = 6513, + RANKA = 8803, + FURUSUMI = 8804, + DREAM_BELL = 18863, + DREAM_BELL_P1 = 18864, + LADY_BELL = 18868, + LADY_BELL_P1 = 18869, + MARVELOUS_CHEER = 22283, + GIL = 65535, }; diff --git a/src/map/items/CMakeLists.txt b/src/map/items/CMakeLists.txt index abc17ffdb2b..36c453eccca 100644 --- a/src/map/items/CMakeLists.txt +++ b/src/map/items/CMakeLists.txt @@ -1,11 +1,23 @@ set(ITEM_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/exdata.cpp ${CMAKE_CURRENT_SOURCE_DIR}/exdata.h + ${CMAKE_CURRENT_SOURCE_DIR}/exdata/assault_log.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/exdata/assault_log.h ${CMAKE_CURRENT_SOURCE_DIR}/exdata/base.h + ${CMAKE_CURRENT_SOURCE_DIR}/exdata/betting_slip.cpp + ${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/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/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}/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 3b8e2a5ddcc..4c8e55aa11d 100644 --- a/src/map/items/exdata.cpp +++ b/src/map/items/exdata.cpp @@ -50,6 +50,38 @@ auto getType(const CItem* item) -> Type return Type::PerpetualHourglass; } + if (itemId == COPY_OF_THE_WYVERN_CODEX || itemId == COPY_OF_THE_GRIFFON_CODEX || + (itemId >= COPY_OF_THE_BALLISTA_REDBOOK && itemId <= PAGE_OF_THE_BALLISTA_WHITEBOOK) || + (itemId >= COPY_OF_THE_BRENNER_BLUEBOOK && itemId <= PAGE_OF_THE_BRENNER_BLACKBOOK)) + { + return Type::BrennerBook; + } + + if (itemId == CHOCOBET_TICKET) + { + return Type::BettingSlip; + } + + if (itemId == RACE_COMPLETION_CERTIFICATE) + { + return Type::RaceCertificate; + } + + if (itemId == VCS_HONEYMOON_TICKET) + { + return Type::HoneymoonTicket; + } + + if (itemId >= DILIGENCE_GRIMOIRE && itemId <= SANCTITY_GRIMOIRE) + { + return Type::MeebleGrimoire; + } + + if (itemId >= LEUJAOAM_OBSERVATION_LOG && itemId <= ILRUSI_TRAVEL_LEDGER) + { + return Type::AssaultLog; + } + return Type::None; } @@ -64,6 +96,24 @@ auto toTable(const CItem* item, sol::table& table) -> bool case Type::PerpetualHourglass: item->exdata().toTable(table); return true; + case Type::BettingSlip: + item->exdata().toTable(table); + return true; + case Type::AssaultLog: + item->exdata().toTable(table); + return true; + case Type::BrennerBook: + item->exdata().toTable(table); + return true; + case Type::MeebleGrimoire: + item->exdata().toTable(table); + return true; + case Type::HoneymoonTicket: + item->exdata().toTable(table); + return true; + case Type::RaceCertificate: + item->exdata().toTable(table); + return true; default: return false; } @@ -80,6 +130,24 @@ auto fromTable(CItem* item, const sol::table& data) -> bool case Type::PerpetualHourglass: item->exdata().fromTable(data); return true; + case Type::BettingSlip: + item->exdata().fromTable(data); + return true; + case Type::AssaultLog: + item->exdata().fromTable(data); + return true; + case Type::BrennerBook: + item->exdata().fromTable(data); + return true; + case Type::MeebleGrimoire: + item->exdata().fromTable(data); + return true; + case Type::HoneymoonTicket: + item->exdata().fromTable(data); + return true; + case Type::RaceCertificate: + item->exdata().fromTable(data); + return true; default: return false; } diff --git a/src/map/items/exdata.h b/src/map/items/exdata.h index f71038cd949..f46b878644b 100644 --- a/src/map/items/exdata.h +++ b/src/map/items/exdata.h @@ -25,8 +25,14 @@ #include "exdata/base.h" +#include "exdata/assault_log.h" +#include "exdata/betting_slip.h" +#include "exdata/brenner_book.h" +#include "exdata/honeymoon_ticket.h" #include "exdata/legion_pass.h" +#include "exdata/meeble_grimoire.h" #include "exdata/perpetual_hourglass.h" +#include "exdata/race_certificate.h" class CItem; diff --git a/src/map/items/exdata/assault_log.cpp b/src/map/items/exdata/assault_log.cpp new file mode 100644 index 00000000000..4c52ab0c208 --- /dev/null +++ b/src/map/items/exdata/assault_log.cpp @@ -0,0 +1,58 @@ +/* +=========================================================================== + + 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 "assault_log.h" + +#include "common/lua.h" + +void Exdata::AssaultLog::toTable(sol::table& table) const +{ + sol::table flags = lua.create_table(); + flags[1] = static_cast(this->Flag1); + flags[2] = static_cast(this->Flag2); + flags[3] = static_cast(this->Flag3); + flags[4] = static_cast(this->Flag4); + flags[5] = static_cast(this->Flag5); + flags[6] = static_cast(this->Flag6); + flags[7] = static_cast(this->Flag7); + flags[8] = static_cast(this->Flag8); + flags[9] = static_cast(this->Flag9); + flags[10] = static_cast(this->Flag10); + table["flags"] = flags; +} + +void Exdata::AssaultLog::fromTable(const sol::table& data) +{ + if (sol::optional flags = data["flags"]) + { + auto& f = *flags; + this->Flag1 = Exdata::get_or(f[1], this->Flag1); + this->Flag2 = Exdata::get_or(f[2], this->Flag2); + this->Flag3 = Exdata::get_or(f[3], this->Flag3); + this->Flag4 = Exdata::get_or(f[4], this->Flag4); + this->Flag5 = Exdata::get_or(f[5], this->Flag5); + this->Flag6 = Exdata::get_or(f[6], this->Flag6); + this->Flag7 = Exdata::get_or(f[7], this->Flag7); + this->Flag8 = Exdata::get_or(f[8], this->Flag8); + this->Flag9 = Exdata::get_or(f[9], this->Flag9); + this->Flag10 = Exdata::get_or(f[10], this->Flag10); + } +} diff --git a/src/map/items/exdata/assault_log.h b/src/map/items/exdata/assault_log.h new file mode 100644 index 00000000000..e75fa95f6f2 --- /dev/null +++ b/src/map/items/exdata/assault_log.h @@ -0,0 +1,48 @@ +/* +=========================================================================== + + 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 AssaultLog +{ + uint16_t Flag1 : 1; + uint16_t Flag2 : 1; + uint16_t Flag3 : 1; + uint16_t Flag4 : 1; + uint16_t Flag5 : 1; + uint16_t Flag6 : 1; + uint16_t Flag7 : 1; + uint16_t Flag8 : 1; + uint16_t Flag9 : 1; + uint16_t Flag10 : 1; + uint16_t padding00 : 6; + uint8_t padding01[22]; + + void toTable(sol::table& table) const; + void fromTable(const sol::table& data); +}; +#pragma pack(pop) +} // namespace Exdata diff --git a/src/map/items/exdata/betting_slip.cpp b/src/map/items/exdata/betting_slip.cpp new file mode 100644 index 00000000000..2b2ea04b3e7 --- /dev/null +++ b/src/map/items/exdata/betting_slip.cpp @@ -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/ + +=========================================================================== +*/ + +#include "betting_slip.h" + +void Exdata::BettingSlip::toTable(sol::table& table) const +{ + table["raceId"] = this->RaceId; + table["raceGrade"] = this->RaceGrade; + table["racePairingL"] = this->RacePairingL; + table["racePairingR"] = this->RacePairingR; + table["quills"] = this->Quills; +} + +void Exdata::BettingSlip::fromTable(const sol::table& data) +{ + this->RaceId = Exdata::get_or(data, "raceId", this->RaceId); + this->RaceGrade = Exdata::get_or(data, "raceGrade", this->RaceGrade); + this->RacePairingL = Exdata::get_or(data, "racePairingL", this->RacePairingL); + this->RacePairingR = Exdata::get_or(data, "racePairingR", this->RacePairingR); + this->Quills = Exdata::get_or(data, "quills", this->Quills); +} diff --git a/src/map/items/exdata/betting_slip.h b/src/map/items/exdata/betting_slip.h new file mode 100644 index 00000000000..389313b1089 --- /dev/null +++ b/src/map/items/exdata/betting_slip.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 BettingSlip +{ + uint32_t RaceId : 18; + uint32_t RaceGrade : 6; + uint32_t RacePairingL : 4; + uint32_t RacePairingR : 4; + uint16_t Quills : 10; + uint16_t padding00 : 6; + uint8_t padding01[18]; + + void toTable(sol::table& table) const; + void fromTable(const sol::table& data); +}; +#pragma pack(pop) +} // namespace Exdata diff --git a/src/map/items/exdata/brenner_book.cpp b/src/map/items/exdata/brenner_book.cpp new file mode 100644 index 00000000000..cd2b38f0715 --- /dev/null +++ b/src/map/items/exdata/brenner_book.cpp @@ -0,0 +1,35 @@ +/* +=========================================================================== + + 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 "brenner_book.h" + +void Exdata::BrennerBook::toTable(sol::table& table) const +{ + table["timeValue"] = this->TimeValue; + table["level"] = this->Level; +} + +void Exdata::BrennerBook::fromTable(const sol::table& data) +{ + this->Mode = 1; // Always Mode 1 + this->TimeValue = Exdata::get_or(data, "timeValue", this->TimeValue); + this->Level = Exdata::get_or(data, "level", this->Level); +} diff --git a/src/map/items/exdata/brenner_book.h b/src/map/items/exdata/brenner_book.h new file mode 100644 index 00000000000..5c457bdb99c --- /dev/null +++ b/src/map/items/exdata/brenner_book.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 +{ +// NOTE: Mode 0 used to exist packing both TimeValue and Level in a single uint32_t (29 + 3) but only Mode 1 is used nowadays. +// Only Mode 1 is implemented here. +#pragma pack(push, 1) +struct BrennerBook +{ + uint32_t TimeValue; // Seconds since epoch 1009929600 (Jan 2, 2002 00:00 UTC) + uint32_t Level; + uint8_t padding00[3]; + uint8_t Mode; + uint8_t padding01[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/honeymoon_ticket.cpp b/src/map/items/exdata/honeymoon_ticket.cpp new file mode 100644 index 00000000000..c2231f82084 --- /dev/null +++ b/src/map/items/exdata/honeymoon_ticket.cpp @@ -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/ + +=========================================================================== +*/ + +#include "honeymoon_ticket.h" + +#include + +void Exdata::HoneymoonTicket::toTable(sol::table& table) const +{ + table["plan"] = this->Plan; +} + +void Exdata::HoneymoonTicket::fromTable(const sol::table& data) +{ + this->Plan = Exdata::get_or(data, "plan", this->Plan); + + if (this->Plan >= 1 && this->Plan <= 4) + { + // "PlanA" + Exdata::encodeSignature(std::format("Plan{}", static_cast('A' + this->Plan - 1)), this->Signature); + } +} diff --git a/src/map/items/exdata/honeymoon_ticket.h b/src/map/items/exdata/honeymoon_ticket.h new file mode 100644 index 00000000000..043762af37f --- /dev/null +++ b/src/map/items/exdata/honeymoon_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 HoneymoonTicket +{ + uint8_t Plan; + uint8_t padding00[11]; + 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/meeble_grimoire.cpp b/src/map/items/exdata/meeble_grimoire.cpp new file mode 100644 index 00000000000..d4498cc3fe9 --- /dev/null +++ b/src/map/items/exdata/meeble_grimoire.cpp @@ -0,0 +1,75 @@ +/* +=========================================================================== + + 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 "meeble_grimoire.h" + +#include "common/lua.h" + +namespace +{ +constexpr uint8_t NUM_TYPES = 5; +constexpr uint8_t NUM_LEVELS = 4; +constexpr uint8_t BITS_PER_LEVEL = 3; +} // namespace + +void Exdata::MeebleGrimoire::toTable(sol::table& table) const +{ + sol::table clearsTable = lua.create_table(); + for (uint8_t type = 0; type < NUM_TYPES; ++type) + { + sol::table levelsTable = lua.create_table(); + for (uint8_t level = 0; level < NUM_LEVELS; ++level) + { + int32 bitOffset = (type * NUM_LEVELS + level) * BITS_PER_LEVEL; + levelsTable[level + 1] = static_cast(unpackBitsLE(this->Clears, 0, bitOffset, BITS_PER_LEVEL)); + } + clearsTable[type + 1] = levelsTable; + } + + table["clears"] = clearsTable; + table["count"] = this->Count; + table["zone"] = this->Zone; +} + +void Exdata::MeebleGrimoire::fromTable(const sol::table& data) +{ + if (sol::optional clearsOpt = data["clears"]) + { + // Walk clears[type][level] and pack each 3-bit count back into the bitstream. + // Missing entries keep whatever was already there. + for (uint8_t type = 0; type < NUM_TYPES; ++type) + { + if (sol::optional levelsOpt = (*clearsOpt)[type + 1]) + { + for (uint8_t level = 0; level < NUM_LEVELS; ++level) + { + const int32 bitOffset = (type * NUM_LEVELS + level) * BITS_PER_LEVEL; + const uint8_t clearCount = static_cast(unpackBitsLE(this->Clears, 0, bitOffset, BITS_PER_LEVEL)); + uint8_t newCount = Exdata::get_or((*levelsOpt)[level + 1], clearCount); + packBitsLE(this->Clears, std::min(newCount, 7), 0, bitOffset, BITS_PER_LEVEL); + } + } + } + } + + this->Count = Exdata::get_or(data, "count", this->Count); + this->Zone = Exdata::get_or(data, "zone", this->Zone); +} diff --git a/src/map/items/exdata/meeble_grimoire.h b/src/map/items/exdata/meeble_grimoire.h new file mode 100644 index 00000000000..3d755216315 --- /dev/null +++ b/src/map/items/exdata/meeble_grimoire.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 +{ +// Clears are packed as 3-bit counts in bytes 0-7: 5 expedition types x 4 levels = 60 bits used. +// Grimoires are zone-locked (Sauromugue or Batallia). +#pragma pack(push, 1) +struct MeebleGrimoire +{ + uint8_t Clears[8]; // 3-bit-per-level bitstream: 5 types x 4 levels = 60 bits + uint8_t Count; // Distinct expedition count + uint8_t padding00[3]; + uint8_t Zone; // 0=Sauromugue, 1=Batallia + uint8_t padding01[11]; + + void toTable(sol::table& table) const; + void fromTable(const sol::table& data); +}; +#pragma pack(pop) +} // namespace Exdata diff --git a/src/map/items/exdata/race_certificate.cpp b/src/map/items/exdata/race_certificate.cpp new file mode 100644 index 00000000000..9589cfb1ba2 --- /dev/null +++ b/src/map/items/exdata/race_certificate.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 "race_certificate.h" + +void Exdata::RaceCertificate::toTable(sol::table& table) const +{ + table["raceId"] = static_cast(this->RaceId); + table["raceGrade"] = static_cast(this->RaceGrade); +} + +void Exdata::RaceCertificate::fromTable(const sol::table& data) +{ + this->RaceId = Exdata::get_or(data, "raceId", this->RaceId); + this->RaceGrade = Exdata::get_or(data, "raceGrade", this->RaceGrade); +} diff --git a/src/map/items/exdata/race_certificate.h b/src/map/items/exdata/race_certificate.h new file mode 100644 index 00000000000..d22bf5bc090 --- /dev/null +++ b/src/map/items/exdata/race_certificate.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 RaceCertificate +{ + uint32_t RaceId : 18; + uint32_t RaceGrade : 6; + uint32_t padding00 : 8; + uint8_t padding01[20]; + + void toTable(sol::table& table) const; + void fromTable(const sol::table& data); +}; +#pragma pack(pop) +} // namespace Exdata