From 9da3305204fd91c5034720df53e56b5922be31b8 Mon Sep 17 00:00:00 2001 From: sruon Date: Tue, 11 Mar 2025 01:28:50 -0600 Subject: [PATCH] Einherjar smoldering lamp interactions - Kilusha update - Einherjar settings - Einherjar enum - Chambers basic info - Gate basic interactions --- scripts/enum/einherjar.lua | 30 ++++ scripts/enum/item.lua | 14 ++ scripts/globals/einherjar/chambers.lua | 154 ++++++++++++++++++ scripts/globals/einherjar/settings.lua | 38 +++++ .../npcs/Armoury_Crate.lua | 24 +++ .../Hazhalm_Testing_Grounds/npcs/_260.lua | 90 ++++++++++ scripts/zones/Nashmau/npcs/Kilusha.lua | 49 +++--- 7 files changed, 376 insertions(+), 23 deletions(-) create mode 100644 scripts/enum/einherjar.lua create mode 100644 scripts/globals/einherjar/chambers.lua create mode 100644 scripts/globals/einherjar/settings.lua create mode 100644 scripts/zones/Hazhalm_Testing_Grounds/npcs/Armoury_Crate.lua create mode 100644 scripts/zones/Hazhalm_Testing_Grounds/npcs/_260.lua diff --git a/scripts/enum/einherjar.lua b/scripts/enum/einherjar.lua new file mode 100644 index 00000000000..3ad01d4350c --- /dev/null +++ b/scripts/enum/einherjar.lua @@ -0,0 +1,30 @@ +----------------------------------- +-- Einherjar chambers +----------------------------------- +xi = xi or {} +xi.einherjar = xi.einherjar or {} + +---@enum xi.einherjar.chamber +xi.einherjar.chamber = +{ + ROSSWEISS = 1, + GRIMGERDE = 2, + SIEGRUNE = 3, + WALTRAUTE = 4, + HELMWIGE = 5, + SCHWERTLEITE = 6, + ORTLINDE = 7, + GERHILDE = 8, + BRUNNHILDE = 9, + ODIN = 10, + ODIN_II = 11, +} + +---@enum xi.einherjar.wing +xi.einherjar.wing = +{ + WING_1 = 1, + WING_2 = 2, + WING_3 = 3, + ODIN = 4, +} diff --git a/scripts/enum/item.lua b/scripts/enum/item.lua index ea4c3674d29..cb73e892f22 100644 --- a/scripts/enum/item.lua +++ b/scripts/enum/item.lua @@ -257,6 +257,7 @@ xi.item = CHUNK_OF_ALUMINUM_ORE = 678, ADAMAN_CHAIN = 683, CHUNK_OF_KHROMA_ORE = 685, + IMPERIAL_WOOTZ_INGOT = 686, SCARLETITE_INGOT = 687, ARROWWOOD_LOG = 688, LAUAN_LOG = 689, @@ -883,6 +884,7 @@ xi.item = SOMBER_MEMORY = 1611, RADIANT_MEMORY = 1612, MALEVOLENT_MEMORY = 1613, + CORSE_BRACELET = 1614, BUFFALO_HORN = 1615, ANTLION_JAW = 1616, EFT_SKIN = 1623, @@ -897,6 +899,7 @@ xi.item = SQUARE_OF_MOBLINWEAVE = 1636, SQUARE_OF_BUGARD_LEATHER = 1637, MOBLIN_MASK = 1638, + CORSE_ROBE = 1639, CHUNK_OF_KOPPARNICKEL_ORE = 1650, BOTTLE_OF_RICE_VINEGAR = 1652, IGNEOUS_ROCK = 1654, @@ -1172,6 +1175,7 @@ xi.item = MARID_TUSK = 2147, PUK_WING = 2148, COLIBRI_FEATHER = 2150, + MARID_HIDE = 2151, SQUARE_OF_MARID_LEATHER = 2152, QIQIRN_SANDBAG = 2153, OROBON_LURE = 2154, @@ -1183,7 +1187,9 @@ xi.item = IMP_WING = 2163, PEPHREDO_HIVE_CHIP = 2164, QUTRUB_GORGET = 2165, + LOCK_OF_MARID_HAIR = 2166, LAMIAN_ARMLET = 2167, + CERBERUS_CLAW = 2168, COLIBRI_BEAK = 2171, WAMOURA_COCOON = 2173, CHUNK_OF_FLAN_MEAT = 2175, @@ -1235,6 +1241,7 @@ xi.item = BAG_OF_WILDGRASS_SEEDS = 2235, BAG_OF_SIMSIM = 2236, BAG_OF_COFFEE_CHERRIES = 2270, + SCINTILLANT_INGOT = 2275, UNAPPRAISED_HEADPIECE = 2276, UNAPPRAISED_EARRING = 2277, UNAPPRAISED_RING = 2278, @@ -1262,10 +1269,12 @@ xi.item = CHOCOBO_EGG_A_BIT_WARM = 2317, CHOCOBO_EGG_A_LITTLE_WARM = 2318, CHOCOBO_EGG_SOMEWHAT_WARM = 2319, + YOICHIS_SASH = 2330, BLUE_MAGES_TESTIMONY = 2331, CORSAIRS_TESTIMONY = 2332, PUPPETMASTERS_TESTIMONY = 2333, POROGGO_HAT = 2334, + SOULFLAYER_TENTACLE = 2335, SOULFLAYER_STAFF = 2336, CHOCOCARD_M = 2339, SQUARE_OF_IMPERIAL_SILK_CLOTH = 2340, @@ -1276,7 +1285,10 @@ xi.item = HI_CHOCOLIXIR = 2345, TORNADO_SALAD = 2346, STAR_SAPPHIRE = 2359, + AMERETAT_VINE = 2361, PIECE_OF_YELLOW_GINSENG = 2364, + KHIMAIRA_HORN = 2371, + KHIMAIRA_MANE = 2372, BHAFLAU_CARD = 2377, SILVER_SEA_CARD = 2378, SMOKE_FILLED_FLASK = 2384, @@ -1289,6 +1301,8 @@ xi.item = CHUNK_OF_AHT_URHGAN_BRASS = 2417, AHT_URHGAN_BRASS_INGOT = 2418, WIVRE_HORN = 2426, + WIVRE_MAUL = 2427, + WIVRE_HIDE = 2428, PHANTASMAL_ABJURATION_HEAD = 2429, PHANTASMAL_ABJURATION_BODY = 2430, PHANTASMAL_ABJURATION_HANDS = 2431, diff --git a/scripts/globals/einherjar/chambers.lua b/scripts/globals/einherjar/chambers.lua new file mode 100644 index 00000000000..3def1bc624f --- /dev/null +++ b/scripts/globals/einherjar/chambers.lua @@ -0,0 +1,154 @@ +----------------------------------- +-- Einherjar: Chambers management +----------------------------------- +xi = xi or {} +xi.einherjar = xi.einherjar or {} + +local chambersByTier = { + [xi.einherjar.wing.WING_1] = { + { + id = xi.einherjar.chamber.ROSSWEISS, + ki = xi.ki.ROSSWEISSES_FEATHER, + menu = 0x2, + center = { 401.1, -216, 40.6, 28 }, + ichor = 960, + }, + { + id = xi.einherjar.chamber.GRIMGERDE, + ki = xi.ki.GRIMGERDES_FEATHER, + menu = 0x4, + center = { 159.6, -196, -41.4, 220 }, + ichor = 960, + }, + { + id = xi.einherjar.chamber.SIEGRUNE, + ki = xi.ki.SIEGRUNES_FEATHER, + menu = 0x8, + center = { 78.5, -176, -281, 221 }, + ichor = 960, + }, + }, + [xi.einherjar.wing.WING_2] = { + { + id = xi.einherjar.chamber.WALTRAUTE, + ki = xi.ki.WALTRAUTES_FEATHER, + menu = 0x10, + center = { -197.32, -146, -439.5, 218 }, + ichor = 1440, + }, + { + id = xi.einherjar.chamber.HELMWIGE, + ki = xi.ki.HELMWIGES_FEATHER, + menu = 0x20, + center = { -437.3986, -126.0, -281.89, 34 }, + ichor = 1440, + }, + { + id = xi.einherjar.chamber.SCHWERTLEITE, + ki = xi.ki.SCHWERTLEITES_FEATHER, + menu = 0x40, + center = { -678, -106, -120, 28 }, + ichor = 1440, + }, + }, + [xi.einherjar.wing.WING_3] = { + { + id = xi.einherjar.chamber.ORTLINDE, + ki = xi.ki.ORTLINDES_FEATHER, + menu = 0x80, + center = { -519.36, -66, 158.96, 56 }, + ichor = 1920, + }, + { + id = xi.einherjar.chamber.GERHILDE, + ki = xi.ki.GERHILDES_FEATHER, + menu = 0x100, + center = { -360.81, -46, 398.3684, 96 }, + ichor = 1920, + }, + { + id = xi.einherjar.chamber.BRUNNHILDE, + ki = xi.ki.BRUNHILDES_FEATHER, + menu = 0x200, + center = { -82.26, -6, 242.05, 159 }, + ichor = 1920, + }, + }, + [xi.einherjar.wing.ODIN] = { + { + id = xi.einherjar.chamber.ODIN, + ki = 0, + menu = 0x400, + center = { -277.4, 34, -38.15, 222 }, + ichor = 2880, + }, + { + id = xi.einherjar.chamber.ODIN_II, + ki = 0, + menu = 0x1000, + center = { -277.4, 34, -38.15, 222 }, + ichor = 3600, + }, + }, +} + +xi.einherjar.chambers = { + [xi.einherjar.chamber.ROSSWEISS] = chambersByTier[xi.einherjar.wing.WING_1][1], + [xi.einherjar.chamber.GRIMGERDE] = chambersByTier[xi.einherjar.wing.WING_1][2], + [xi.einherjar.chamber.SIEGRUNE] = chambersByTier[xi.einherjar.wing.WING_1][3], + [xi.einherjar.chamber.WALTRAUTE] = chambersByTier[xi.einherjar.wing.WING_2][1], + [xi.einherjar.chamber.HELMWIGE] = chambersByTier[xi.einherjar.wing.WING_2][2], + [xi.einherjar.chamber.SCHWERTLEITE] = chambersByTier[xi.einherjar.wing.WING_2][3], + [xi.einherjar.chamber.ORTLINDE] = chambersByTier[xi.einherjar.wing.WING_3][1], + [xi.einherjar.chamber.GERHILDE] = chambersByTier[xi.einherjar.wing.WING_3][2], + [xi.einherjar.chamber.BRUNNHILDE] = chambersByTier[xi.einherjar.wing.WING_3][3], + [xi.einherjar.chamber.ODIN] = chambersByTier[xi.einherjar.wing.ODIN][1], +} + +xi.einherjar.isLockedOut = function(player) + local lockout = player:getCharVar('[ein]lockout') + if lockout == 0 then + return 0 + end + + return math.ceil((lockout - os.time()) / 2088) -- Vanadiel days +end + +-- Bitmask of chambers the player has access to +-- Player must own all key items from previous tier to access the next tier +-- Wing 1 is always accessible +xi.einherjar.getChambersMenu = function(player) + local mask = 0xFF0 + local wings = { + xi.einherjar.wing.WING_1, + xi.einherjar.wing.WING_2, + xi.einherjar.wing.WING_3, + xi.einherjar.wing.ODIN + } + + for i = 1, #wings do + local tierChambers = chambersByTier[wings[i]] + local nextTierChambers = chambersByTier[wings[i + 1]] + + -- Check if player owns all key items in the current tier + local ownsAllCurrent = true + for _, chamber in ipairs(tierChambers) do + if not player:hasKeyItem(chamber.ki) then + ownsAllCurrent = false + break + end + end + + if not ownsAllCurrent then + break + end + + if nextTierChambers then + for _, chamber in ipairs(nextTierChambers) do + mask = bit.band(mask, bit.bnot(bit.lshift(1, chamber.id))) + end + end + end + + return mask +end diff --git a/scripts/globals/einherjar/settings.lua b/scripts/globals/einherjar/settings.lua new file mode 100644 index 00000000000..534838aab81 --- /dev/null +++ b/scripts/globals/einherjar/settings.lua @@ -0,0 +1,38 @@ +----------------------------------- +-- Einherjar: Settings +----------------------------------- +xi = xi or {} +xi.einherjar = xi.einherjar or {} + +-- Retail defaults +xi.einherjar.settings = { + -- Should Einherjar be enabled? Entry gate interactions disabled if false + EINHERJAR_ENABLED = xi.settings.main.EINHERJAR_ENABLED or false, + + -- Minimum level required to reserve and enter Einherjar + EINHERJAR_LEVEL_MIN = xi.settings.main.EINHERJAR_LEVEL_MIN or 60, + + -- Smoldering lamp cost from Kilusha. Cost with ROV KI is fixed at 1000g. + SMOLDERING_LAMP_BASE_COST = xi.settings.main.SMOLDERING_LAMP_BASE_COST or 60000, + + -- How long before a player can reenter Einherjar, in hours. Reentry with ROV KI is fixed at 1 hour. + EINHERJAR_REENTRY_TIME = xi.settings.main.EINHERJAR_REENTRY_TIME or 20, + + -- How long before all players are expelled from the chamber when wiped + EINHERJAR_KO_EXPEL_TIME = xi.settings.main.EINHERJAR_KO_EXPEL_TIME or 3, + + -- How long a chamber reservation is valid for, in minutes. + EINHERJAR_TIME_LIMIT = xi.settings.main.EINHERJAR_TIME_LIMIT or 30, + + -- Multiplier for awarded Therion Ichor. + EINHERJAR_ICHOR_RATE = xi.settings.main.EINHERJAR_ICHOR_RATE or 1.0, + + -- How long until a reservation is released if the player does not enter, in minutes. + EINHERJAR_RESERVATION_TIMEOUT = xi.settings.main.EINHERJAR_RESERVATION_TIMEOUT or 3, + + -- How many players can enter a chamber before it is considered full + EINHERJAR_MAX_PLAYERS_PER_CHAMBER = xi.settings.main.EINHERJAR_MAX_PLAYERS_PER_CHAMBER or 36, + + -- How long before the chamber kicks everyone out after the chest has been opened. + EINHERJAR_CLEAR_EXTRA_TIME = xi.settings.main.EINHERJAR_CLEAR_EXTRA_TIME or 5, +} diff --git a/scripts/zones/Hazhalm_Testing_Grounds/npcs/Armoury_Crate.lua b/scripts/zones/Hazhalm_Testing_Grounds/npcs/Armoury_Crate.lua new file mode 100644 index 00000000000..34f385a527b --- /dev/null +++ b/scripts/zones/Hazhalm_Testing_Grounds/npcs/Armoury_Crate.lua @@ -0,0 +1,24 @@ +----------------------------------- +-- Area: Hazhalm Testing Grounds +-- NPC: Armoury Crate +----------------------------------- +---@type TMobEntity +local entity = {} + +entity.onMobInitialize = function(mob) + mob:setMobMod(xi.mobMod.NO_REST, 1) + mob:setMobMod(xi.mobMod.NO_MOVE, 1) + mob:setMobMod(xi.mobMod.NO_DESPAWN, 1) + mob:setStatus(xi.status.NORMAL) +end + +entity.onMobSpawn = function(mob) +end + +entity.onMobDeath = function(mob, player, optParams) +end + +entity.onMobDespawn = function(mob) +end + +return entity diff --git a/scripts/zones/Hazhalm_Testing_Grounds/npcs/_260.lua b/scripts/zones/Hazhalm_Testing_Grounds/npcs/_260.lua new file mode 100644 index 00000000000..d88e32910a0 --- /dev/null +++ b/scripts/zones/Hazhalm_Testing_Grounds/npcs/_260.lua @@ -0,0 +1,90 @@ +----------------------------------- +-- Area: Hazhalm_Testing_Grounds +-- NPC: Entry Gate (_260) +----------------------------------- +local ID = zones[xi.zone.HAZHALM_TESTING_GROUNDS] +----------------------------------- +---@type TNpcEntity +local entity = {} + +entity.onTrade = function(player, npc, trade) + if not xi.einherjar.settings.EINHERJAR_ENABLED then + return + end + + if npcUtil.tradeHasExactly(trade, { xi.item.SMOLDERING_LAMP }) then + -- TODO: Check requirements + player:startEvent(2, + 0, + xi.besieged.getMercenaryRank(player), + xi.einherjar.settings.EINHERJAR_KO_EXPEL_TIME, + xi.einherjar.settings.EINHERJAR_REENTRY_TIME, + 0, -- Unknown + xi.einherjar.getChambersMenu(player), + xi.item.SMOLDERING_LAMP, + xi.item.GLOWING_LAMP + ) + -- Continued in onEventFinish 2, 1 + end + + if npcUtil.tradeHasExactly(trade, { xi.item.GLOWING_LAMP }) then + -- TODO: Player requests entry into chamber + -- Continued in onEventFinish 3, 1 + end +end + +entity.onTrigger = function(player, npc) + -- TODO: Entry point for The Rider Cometh + -- If The Rider Cometh is flagged, no lockout message will show + -- but the battlefield selection menu will show up + local lockout = xi.einherjar.isLockedOut(player) + if lockout ~= 0 then + player:messageSpecial(ID.text.ENTRY_PROHIBITED, lockout) + return + end + + player:messageSpecial(ID.text.GATE_FIRMLY_CLOSED) +end + +entity.onEventUpdate = function(player, csid, option, npc) + if csid == 2 and (option >= 1 and option <= 10) then + local mask = xi.einherjar.getChambersMenu(player) + local chamberEntry = xi.einherjar.chambers[option] + + if chamberEntry and bit.band(mask, chamberEntry.menu) ~= 0 then + -- Player attempted to reserve a chamber they don't have access to + player:instanceEntry(npc, 3) + return + end + + player:updateEvent(0, + 10, + xi.settings.main.EINHERJAR_KO_EXPEL_TIME, + xi.settings.main.EINHERJAR_REENTRY_TIME, + 0, + xi.einherjar.getChambersMenu(player), + xi.item.SMOLDERING_LAMP, + xi.item.GLOWING_LAMP) + if player:getFreeSlotsCount() ~= 0 then + -- TODO: Reserve chamber, generate waves, and create lamp + player:instanceEntry(npc, 4) + else + player:messageSpecial(ID.text.ITEM_CANNOT_BE_OBTAINED, xi.item.GLOWING_LAMP) + player:instanceEntry(npc, 3) + end + end +end + +entity.onEventFinish = function(player, csid, option) + -- Player has registered their lamp + if csid == 2 and option >= 65 and option <= 74 then -- > Rossweisse's Chamber < to < Odin's Chamber + player:messageSpecial(ID.text.GLOWING_LAMP_OBTAINED, xi.item.GLOWING_LAMP) + player:messageSpecial(ID.text.CLAIM_RELINQUISH, xi.item.GLOWING_LAMP, xi.einherjar.settings.EINHERJAR_RESERVATION_TIMEOUT) + player:messageSpecial(ID.text.ITEM_OBTAINED, xi.item.GLOWING_LAMP) + elseif csid == 3 and option == 1 then -- player requested entry into chamber + local _ = player:getTrade():getItem() + -- TODO: Read lamp and warp player in + end +end + +return entity diff --git a/scripts/zones/Nashmau/npcs/Kilusha.lua b/scripts/zones/Nashmau/npcs/Kilusha.lua index 2eae35d33bb..d8b18abaa4c 100644 --- a/scripts/zones/Nashmau/npcs/Kilusha.lua +++ b/scripts/zones/Nashmau/npcs/Kilusha.lua @@ -10,7 +10,7 @@ local ID = zones[xi.zone.NASHMAU] local entity = {} entity.onTrade = function(player, npc, trade) - local lampCost = 60000 -- base cost without RHAPSODY_IN_AZURE key item + local lampCost = xi.einherjar.settings.SMOLDERING_LAMP_BASE_COST -- base cost without RHAPSODY_IN_AZURE key item if player:hasKeyItem(xi.ki.RHAPSODY_IN_AZURE) then lampCost = 1000 @@ -20,20 +20,23 @@ entity.onTrade = function(player, npc, trade) npcUtil.tradeHasExactly(trade, { { 'gil', lampCost } }) and player:getCharVar('EinherjarIntro') ~= 1 then - if npcUtil.giveItem(player, xi.item.SMOLDERING_LAMP) then - player:tradeComplete() - player:startEvent(25) - else - player:messageSpecial(ID.text.ITEM_CANNOT_BE_OBTAINED, xi.item.SMOLDERING_LAMP) + if + player:getFreeSlotsCount() == 0 or + player:hasItem(xi.item.SMOLDERING_LAMP) + then + player:messageSpecial(ID.text.ITEM_CANNOT_BE_OBTAINEDX, xi.item.SMOLDERING_LAMP) + return end + + player:startEvent(25) -- Lamp given in onEventFinish end end entity.onTrigger = function(player, npc) local ichor = player:getCurrency('therion_ichor') local allowValkyrieBuying = 29360128 -- set this to 0 if you wish to allow players to buy feather key items without KI - local lampCost = 60000 -- base cost without RHAPSODY_IN_AZURE key item - local reentryTime = 20 -- in hours + local lampCost = xi.einherjar.settings.SMOLDERING_LAMP_BASE_COST -- base cost without RHAPSODY_IN_AZURE key item + local reentryTime = xi.einherjar.settings.EINHERJAR_REENTRY_TIME -- in hours local toau = player:hasCompletedMission(xi.mission.log_id.TOAU, xi.mission.id.toau.IMMORTAL_SENTRIES) if player:hasKeyItem(xi.ki.RHAPSODY_IN_AZURE) then @@ -45,10 +48,13 @@ entity.onTrigger = function(player, npc) allowValkyrieBuying = 0 end - if player:getMainLvl() <= 59 or not toau then + if + player:getMainLvl() < xi.einherjar.settings.EINHERJAR_LEVEL_MIN or + not toau + then player:startEvent(22) -- worthless CS elseif - (player:getMainLvl() >= 60 or toau) and + (player:getMainLvl() >= xi.einherjar.settings.EINHERJAR_LEVEL_MIN or toau) and player:getCharVar('EinherjarIntro') == 1 then player:startEvent(23, lampCost, 856, 3, 616, 10, 172, 172, 0) -- Einherjar introduction @@ -67,11 +73,12 @@ end entity.onEventFinish = function(player, csid, option, npc) if csid == 23 then player:setCharVar('EinherjarIntro', 0) -- deletes CharVar set at character creation - elseif - csid == 24 and - option ~= utils.EVENT_CANCELLED_OPTION and - option ~= 0 - then + elseif csid == 25 then + -- Give players their purchased lamp + if npcUtil.giveItem(player, xi.item.SMOLDERING_LAMP) then + player:confirmTrade() + end + elseif csid == 24 and option ~= utils.EVENT_CANCELLED_OPTION and option ~= 0 then local kilushaItems = { [1] = { item = xi.item.ANIMATOR_P1, cost = 15000 }, @@ -102,14 +109,10 @@ entity.onEventFinish = function(player, csid, option, npc) local row = kilushaItems[option] - if - player:getFreeSlotsCount() ~= 0 and - player:getCurrency('therion_ichor') >= row.cost - then - npcUtil.giveItem(player, row.item) - player:delCurrency('therion_ichor', row.cost) - else - player:messageSpecial(ID.text.ITEM_CANNOT_BE_OBTAINED, row.item) + if player:getCurrency('therion_ichor') >= row.cost then + if npcUtil.giveItem(player, row.item) then + player:delCurrency('therion_ichor', row.cost) + end end end end