landsandboat/scripts/globals/instance.lua

598 lines
19 KiB
Lua
Raw Permalink Normal View History

-----------------------------------
-- Instance
-----------------------------------
-- TOAU storyline missions
-- Salvage
-- WOTG storyline missions
-- Certain Campaign Ops
-- Moblin Maze Mongers
-- The Notorious Monster battles that occur when Sandworm sucks in players using Doomvoid
--
-- Instance zones:
-- - ILRUSI_ATOLL = 55,
-- - PERIQIA = 56,
-- - THE_ASHU_TALIF = 60,
-- - LEBROS_CAVERN = 63,
-- - MAMOOL_JA_TRAINING_GROUNDS = 66,
-- - LEUJAOAM_SANCTUM = 69,
-- - ZHAYOLM_REMNANTS = 73,
-- - ARRAPAGO_REMNANTS = 74,
-- - BHAFLAU_REMNANTS = 75,
-- - SILVER_SEA_REMNANTS = 76,
-- - NYZUL_ISLE = 77,
-- - EVERBLOOM_HOLLOW = 86,
-- - RUHOTZ_SILVERMINES = 93,
-- - GHOYUS_REVERIE = 129,
-- - MAQUETTE_ABDHALJS_LEGION_A = 183,
-- - RALA_WATERWAYS_U = 259,
-- - YORCIA_WEALD_U = 264,
-- - CIRDAS_CAVERNS_U = 271,
-- - OUTER_RAKAZNAR_U1 = 275,
-- - MAQUETTE_ABDHALJS_LEGION_B = 287, -- See: ambuscade.lua
-- - DYNAMIS_SAN_DORIA_D = 294,
-- - DYNAMIS_BASTOK_D = 295,
-- - DYNAMIS_WINDURST_D = 296,
-- - DYNAMIS_JEUNO_D = 297,
-----------------------------------
xi = xi or {}
xi.instance = {}
--[[
[zoneId] =
{
{
instanceIdInDatabase,
onTrigger startEvent args (to be unpacked),
onEventFinish valid entry args for registrant (to be unpacked),
event args for joining party members (to be unpacked)
}
},
--]]
xi.instance.lookup =
{
[xi.zone.ILRUSI_ATOLL] =
{
-- Assault: Golden Salvage
-- Assault: Lamia No.13
-- Assault: Extermination
-- Assault: Demolition Duty
-- Assault: Searat Salvation
-- Assault: Apkallu Seizure
-- Assault: Lost and Found
-- Assault: Deserter
-- Assault: Desperately Seeking Cephalopods
-- Assault: Bellerophon's Bliss
},
[xi.zone.PERIQIA] =
{
2021-10-21 20:31:59 -05:00
{ 5600, { 143, 79, -6, 0, 99, 3, 0 }, { 143, 4 }, { 147, 3 } }, -- Shades of Vengeance (TOAU31)
2021-10-24 20:13:06 -05:00
{ 5601, { 143, 31, -4, 0, 70, 0, 1 }, { 143, 4 }, { 147, 0 } }, -- Assault: Seagull Grounded
-- Assault: Requiem
-- Assault: Saving Private Ryaaf
-- Assault: Shooting Down the Baron
-- Assault: Stop the Bloodshed
-- Assault: Defuse the Threat
-- Assault: Operation: Snake Eyes
-- Assault: Wake the Puppet
-- Assault: The Price is Right
},
[xi.zone.THE_ASHU_TALIF] =
{
{ 6000, { 221, 53, -6, 0, 99, 6, 0 }, { 221, 4 }, { 222, 6 } }, -- The Black Coffin (TOAU 15)
{ 6001, { 221, 54, -9, 0, 99, 6, 0 }, { 221, 4 }, { 222, 6 } }, -- Against All Odds
-- Testing the Waters (TOAU 34)
-- Legacy of the Lost (TOAU 35)
-- Assault: Royal Painter Escort
-- Assault: Scouting the Ashu Talif
-- Assault: Targeting the Captain
},
[xi.zone.LEBROS_CAVERN] =
{
2021-10-21 20:31:59 -05:00
{ 6300, { 203, 21, -4, 0, 50, 0, 1 }, { 203, 4 }, { 208, 0 } }, -- Assault: Excavation Duty
-- Assault: Lebros Supplies
-- Assault: Troll Fugitives
-- Assault: Evade and Escape
-- Assault: Siegemaster Assassination
-- Assault: Apkallu Breeding
-- Assault: Wamoura Farm Raid
-- Assault: Egg Conservation
-- Assault: Operation: Black Pearl
-- Assault: Better Than One
},
[xi.zone.MAMOOL_JA_TRAINING_GROUNDS] =
{
2022-09-17 10:50:57 -04:00
{ 6600, { 505, 11, -4, 0, 60, 0, 1 }, { 505, 4 }, { 511, 0 } }, -- Assault: Imperial Agent Rescue
-- Assault: Preemptive Strike
-- Assault: Sagelord Elimination
-- Assault: Breaking Morale
-- Assault: The Double Agent
-- Assault: Imperial Treasure Retrieval
-- Assault: Blitzkrieg
-- Assault: Marids in the Mist
-- Assault: Azure Ailments
-- Assault: The Susanoo Shuffle
},
[xi.zone.LEUJAOAM_SANCTUM] =
{
2022-09-17 10:50:57 -04:00
{ 6900, { 140, 1, -4, 0, 50, 0, 1 }, { 140, 4 }, { 147, 0 } }, -- Assault: Leujaoam Cleansing
-- Assault: Orichalcum Survey
-- Assault: Escort Professor Chanoix
-- Assault: Shanarha Grass Conservation
-- Assault: Counting Sheep
-- Assault: Supplies Recovery
-- Assault: Azure Experiments
-- Assault: Imperial Code
-- Assault: Red Versus Blue
-- Assault: Bloody Rondo
},
[xi.zone.ZHAYOLM_REMNANTS] =
{
2025-02-04 15:45:33 -06:00
{ 7300, { 407, 0, -6, 0, 0, 7 }, { 407, 4 }, { 411, 7 } }, -- Salvage I, Zhayolm Remnants
},
[xi.zone.ARRAPAGO_REMNANTS] =
{
2025-02-04 15:45:33 -06:00
{ 7400, { 408, 0, -6, 0, 0, 8 }, { 408, 4 }, { 411, 8 } }, -- Salvage I, Arrapago Remnants
},
[xi.zone.BHAFLAU_REMNANTS] =
{
2025-02-04 15:45:33 -06:00
{ 7500, { 409, 0, -6, 0, 0, 9 }, { 409, 4 }, { 411, 9 } }, -- Salvage I, Bhaflau Remnants
},
[xi.zone.SILVER_SEA_REMNANTS] =
{
2025-02-04 15:45:33 -06:00
{ 7600, { 410, 0, -6, 0, 0, 10 }, { 410, 4 }, { 411, 10 } }, -- Salvage I, Silver Sea Remnants
},
[xi.zone.NYZUL_ISLE] =
{
{ 7700, { 405, 58, -6, 0, 99, 5, 0 }, { 116, 1 }, { 411, 5 } }, -- Path of Darkness
{ 7701, { 405, 59, -10, 0, 99, 5, 0 }, { 116, 1 }, { 411, 5 } }, -- Nashmeira's Plea
{ 7702, { 405, 60, -34, 0, 99, 5, 1, 0, 14 }, { 116, 1 }, { 411, 5 } }, -- Waking the Colossus/Divine Interference
-- Forging a New Myth
{ 7704, { 405, 51, -4, 0, 75, 5, 1 }, { 116, 2 }, { 411, 5 } }, -- Nyzul Isle Investigation
},
[xi.zone.EVERBLOOM_HOLLOW] =
{
-- Honor Under Fire
-- Bonds That Never Die
-- A Nation on the Brink
-- Dungeons and Dancers
-- Campaign Ops:
-- Brave Dawn I (San d'Oria)
-- Brave Dawn II (San d'Oria)
-- Brave Dawn III (San d'Oria)
-- Granite Rose I (San d'Oria)
-- Granite Rose II (San d'Oria)
-- Granite Rose III (San d'Oria)
-- Pit Spider I (San d'Oria)
-- Pit Spider II (San d'Oria)
-- Pit Spider III (San d'Oria)
-- Doomvoid - King Arthro
-- Doomvoid - Lambton Worm
-- Moblin Maze Mongers
},
[xi.zone.RUHOTZ_SILVERMINES] =
{
{ 9300, { 3, 0, 0, 19 }, { 3, 4 }, { 4, 1 } }, -- Light in the Darkness (WOTG Bastok Quest 3)
{ 9301, { 203, 0, 0, 36 }, { 203, 4 }, { 201, 1 } }, -- Fire in the Hole (WOTG Bastok Quest 6)
-- { 0, { 0, 0, 34 } }, -- Seeing Blood-red (SCH AF3)
-- { 0, { 0, 23, 0 } }, -- Distorter of Time
-- Campaign Ops:
-- Brave Dawn I (Bastok)
-- Brave Dawn II (Bastok)
-- Brave Dawn III (Bastok)
-- By Light of Fire I (Bastok)
-- Granite Rose I (Bastok)
-- Granite Rose II (Bastok)
-- Granite Rose III (Bastok)
-- Pit Spider I (Bastok)
-- Pit Spider II (Bastok)
-- Doomvoid - Lambton Worm
-- Doomvoid - Guivre
-- Moblin Maze Mongers
},
[xi.zone.GHOYUS_REVERIE] =
{
-- A Feast for Gnats
-- A Manifest Problem
-- In a Haze of Glory
-- Sins of the Mothers
-- Campaign Ops:
-- Brave Dawn I (Windurst)
-- Brave Dawn II (Windurst)
-- Brave Dawn III (Windurst)
-- Granite Rose I (Windurst)
-- Granite Rose II (Windurst)
-- Pit Spider I (Windurst)
-- Pit Spider II (Windurst)
-- Doomvoid - Lambton Worm
-- Doomvoid - Serket
},
[xi.zone.MAQUETTE_ABDHALJS_LEGION_A] =
{
-- Hall of An (Leader entry: { 8009, 0, 0, 0, 0, 6, 1 }, Party entry: { 8003, 0, 0, 0, 0, 6, 1 })
-- Hall of Ki
-- Hall of Im
-- Hall of Muru
-- Hall of Mul
},
[xi.zone.RALA_WATERWAYS_U] =
{
-- { 0, 0 }, -- Endeavoring to Awaken
-- { 1, 0 }, -- Endeavoring to Awaken
-- -- Blank
{ 25900, { 5511, 258, 8 }, { 5511, 8 }, { 258, 8 } }, -- Behind the Sluices
-- { 4, 0 }, -- Stonewalled
-- { 5, 0 }, -- The Gates
-- { 6, 0 }, -- Saved by the Bell
-- { 7, 0 }, -- Quiescence
-- { 8, 0 }, -- The Charlatan
-- { 9, 0 }, -- Yggdrasil Beckons
-- { 10, 0 }, -- Yggdrasil Beckons
-- { 11, 0 }, -- Watery Grave
-- { 12, 0 }, -- Mistress of Ceremonies
-- { 13, 0 }, -- A Barrel of Laughs
-- { 14, 0 }, -- Sinister Reign
-- { 15, 0 }, -- The Ygnas Directive 6
-- { 16, 0 }, -- Skirmishes
-- { 17, 0 }, -- Fractures
-- { 18, 0 }, -- Alluvion skirmishes
-- { 19, 0 }, -- The Silent Forest
-- { 20, 0 }, -- Wind of Eternity
},
[xi.zone.YORCIA_WEALD_U] =
{
},
[xi.zone.CIRDAS_CAVERNS_U] =
{
},
[xi.zone.OUTER_RAKAZNAR_U1] =
{
},
[xi.zone.DYNAMIS_SAN_DORIA_D] =
{
},
[xi.zone.DYNAMIS_BASTOK_D] =
{
},
[xi.zone.DYNAMIS_WINDURST_D] =
{
},
[xi.zone.DYNAMIS_JEUNO_D] =
{
},
}
local getInstanceName = function(player, instanceId)
return switch (instanceId) : caseof
{
[7702] = function()
if player:getQuestStatus(xi.questLog.AHT_URHGAN, xi.quest.id.ahtUrhgan.DIVINE_INTERFERENCE) >= xi.questStatus.QUEST_ACCEPTED then
return 1 -- Divine Interference
else
return 0 -- Waking the Colossus
end
end,
}
end
-- Party leader registering
2021-07-08 15:33:49 +03:00
local checkRegistryReqs = function(player, instanceId)
local instanceObj = GetCachedInstanceScript(instanceId)
if type(instanceObj.registryRequirements) == 'function' then
return instanceObj.registryRequirements(player)
else
print('xi.instance: checkReqs: registryRequirements function not set for instance: ' .. instanceId)
return false
end
end
-- Further players joining
2021-07-08 15:33:49 +03:00
local checkEntryReqs = function(player, instanceId)
local instanceObj = GetCachedInstanceScript(instanceId)
if type(instanceObj.entryRequirements) == 'function' then
return instanceObj.entryRequirements(player)
else
print('xi.instance: checkReqs: entryRequirements function not set for instance: ' .. instanceId)
return false
end
end
-- Clear up after possible failed loads
xi.instance.clearInstance = function(player)
player:setLocalVar('INSTANCE_REQUESTED', 0)
local existingInstance = player:getInstance()
if existingInstance then
existingInstance:fail()
end
end
xi.instance.onTrade = function(player, npc, trade)
end
xi.instance.onTrigger = function(player, npc, instanceZoneID)
local zoneLookup = xi.instance.lookup[instanceZoneID]
xi.instance.clearInstance(player)
-- Find the first instance you're valid for
-- TODO: Handle being valid for multiple instances from the same entrance
local chosenEntry
for _, entry in ipairs(zoneLookup) do
2023-08-02 02:11:11 +02:00
local instanceId = entry[1]
2021-07-08 15:33:49 +03:00
local hasValidEntry = checkRegistryReqs(player, instanceId)
2023-08-02 02:11:11 +02:00
if hasValidEntry then
chosenEntry = entry
break
end
end
if chosenEntry == nil then
return false
end
-- Play the cs + args for that instance
2023-08-02 02:11:11 +02:00
local instanceId = chosenEntry[1]
local instanceTriggerArgs = chosenEntry[2]
2023-08-02 02:11:11 +02:00
local hasValidEntry = checkRegistryReqs(player, instanceId)
if hasValidEntry then
player:setLocalVar('INSTANCE_ID', instanceId)
if instanceTriggerArgs[8] ~= nil then
instanceTriggerArgs[8] = getInstanceName(player, instanceId)
end
player:startEvent(unpack(instanceTriggerArgs))
2023-08-02 02:11:11 +02:00
return true
else
return false
end
end
xi.instance.onEventUpdate = function(player, csid, option, npc)
local instanceId = player:getLocalVar('INSTANCE_ID')
2023-08-02 02:11:11 +02:00
local party = player:getParty()
local ID = zones[player:getZoneID()]
if party ~= nil then
for _, v in pairs(party) do
if
v:getID() ~= player:getID() and
v:getZoneID() == player:getZoneID()
then
-- Check entry requirements for party
if not checkEntryReqs(v, instanceId) then
player:messageText(npc, ID.text.MEMBER_NO_REQS, false)
player:instanceEntry(npc, 1)
2023-08-02 02:11:11 +02:00
return false
end
-- Check everyone is in range
if v:checkDistance(player) > 50 then
player:messageText(npc, ID.text.MEMBER_TOO_FAR, false)
player:instanceEntry(npc, 1)
2023-08-02 02:11:11 +02:00
return false
end
end
end
end
if player:getLocalVar('INSTANCE_REQUESTED') == 0 then
player:createInstance(instanceId)
player:setLocalVar('INSTANCE_REQUESTED', 1)
end
if
player:getInstance() ~= nil or
(player:getLocalVar('INSTANCE_REQUESTED') > 0 and
player:getLocalVar('INSTANCE_REQUESTED') < 10)
then
-- return true so we don't immediately call the cancel event update (since instances don't immediately load), but
-- increment variable to eventually return false if instance fails to load and trigger onInstanceCreatedCallback
player:setLocalVar('INSTANCE_REQUESTED', player:getLocalVar('INSTANCE_REQUESTED') + 1)
return true
else
return false
end
end
-- 'Default' behavior. It's up to each instance whether or not they want to use this logic
-- Can pass instance cutscene information directly if accessible from container, otherwise will try and find it from the player's instance
xi.instance.onInstanceCreatedCallback = function(player, instance, entryInfo)
local instanceId = instance:getID()
if entryInfo == nil then
-- Collect cs for party members
for _, entry in ipairs(xi.instance.lookup[instance:getZone():getID()]) do
local entryInstanceId = entry[1]
if instanceId == entryInstanceId then
entryInfo = entry
2023-08-02 02:11:11 +02:00
break
end
end
2021-07-08 15:33:49 +03:00
end
-- If you're in the official entrance zone, try and playout the
-- entrance animation. Otherwise: go straight to the instance
if player:getZoneID() == instance:getEntranceZoneID() then
-- join initiating player as commander
player:setInstance(instance)
-- This packet will trigger the end of the blocking
-- cutscene and xi.instance.onEventFinish will handle
-- the transportation
2021-07-08 15:33:49 +03:00
for _, v in ipairs(player:getParty()) do
if v:getZoneID() == player:getZoneID() then
if v:getID() ~= player:getID() then
-- player will be brought into instance either way
-- this makes the animation trigger reliably
v:release()
v:startEvent(unpack(entryInfo.memberEvent or entryInfo[4]))
v:setInstance(instance)
local npc = player:getEventTarget()
if npc ~= nil then
v:instanceEntry(npc, 4)
end
end
v:timer(35000, function(playerArg)
-- failsafe to bring all players into instance
-- if a player doesn't receive the event packet, the whole party will be stuck in blackscreen
-- timer is destroyed if onEventFinish works properly and player gets zoned
-- a properly-functioning event loop will take 20s to zoning into the instance
-- this _should_ be completely unnecessary due to the looping logic with INSTANCE_REQUESTED, but just in case
local instanceArg = playerArg:getInstance()
if instanceArg then
print(fmt('Player {} failed to cleanly transition into instance event, forcing entry via setPos.', playerArg:getName()))
playerArg:setPos(0, 0, 0, 0, instanceArg:getZone():getID())
end
end)
end
2021-07-08 15:33:49 +03:00
end
-- finally, send commander in
player:startEvent(unpack(entryInfo.memberEvent or entryInfo[4])) -- will fail if previous event is working as it should, otherwise catches secondary event to enter
local npc = player:getEventTarget()
if npc ~= nil then
player:instanceEntry(npc, 4)
end
else
2021-07-08 15:33:49 +03:00
for _, v in ipairs(player:getParty()) do
v:setInstance(instance)
2021-07-08 15:33:49 +03:00
v:setPos(0, 0, 0, 0, instance:getZone():getID())
end
end
end
-- Default instance behavior. Unpacks the csid and option from the lookup table.
-- Can pass instance custcene information directly if accessible from container,
-- otherwise will try and find it from the player's instance and a table lookup.
xi.instance.onEventFinish = function(player, csid, option, npc, instanceInfo)
local instance = player:getInstance()
2023-08-02 02:11:11 +02:00
if instance then
if not instanceInfo then
instanceInfo = xi.instance.lookup[instance:getZone():getID()][1][3]
end
local csidEntry, optionEntry = unpack(instanceInfo)
2023-08-02 02:11:11 +02:00
if csid == csidEntry and option == optionEntry then
2021-07-08 15:33:49 +03:00
for _, v in ipairs(player:getParty()) do
v:setPos(0, 0, 0, 0, instance:getZone():getID())
end
return true
end
end
return false
end
local function setInstanceLastTimeUpdateMessage(instance, players, remainingTimeLimit, text)
2023-08-02 02:11:11 +02:00
local message = 0
local lastTimeUpdate = instance:getLastTimeUpdate()
if lastTimeUpdate == 0 and remainingTimeLimit < 600 then
message = 600
elseif lastTimeUpdate == 600 and remainingTimeLimit < 300 then
message = 300
elseif lastTimeUpdate == 300 and remainingTimeLimit < 60 then
message = 60
elseif lastTimeUpdate == 60 and remainingTimeLimit < 30 then
message = 30
elseif lastTimeUpdate == 30 and remainingTimeLimit < 10 then
message = 10
end
if message ~= 0 then
for i, player in pairs(players) do
2021-02-09 17:02:02 +01:00
if remainingTimeLimit >= 60 then
player:messageSpecial(text.TIME_REMAINING_MINUTES, message / 60)
else
player:messageSpecial(text.TIME_REMAINING_SECONDS, message)
end
end
instance:setLastTimeUpdate(message)
end
end
xi.instance.updateInstanceTime = function(instance, elapsed, text)
2023-08-02 02:11:11 +02:00
local players = instance:getChars()
local remainingTimeLimit = instance:getTimeLimit() * 60 - (elapsed / 1000)
local wipeTime = instance:getWipeTime()
if
remainingTimeLimit < 0 or
2023-08-02 02:11:11 +02:00
(wipeTime ~= 0 and (elapsed - wipeTime) / 1000 > 180
)
then
instance:fail()
2023-08-02 02:11:11 +02:00
return
end
if wipeTime == 0 then
local wipe = true
2023-08-02 02:11:11 +02:00
for i, player in pairs(players) do
if player:getHP() ~= 0 then
wipe = false
break
end
end
2021-02-09 17:02:02 +01:00
if wipe then
for i, player in pairs(players) do
player:messageSpecial(text.PARTY_FALLEN, 3)
end
instance:setWipeTime(elapsed)
end
else
for i, player in pairs(players) do
if player:getHP() ~= 0 then
instance:setWipeTime(0)
break
end
end
end
setInstanceLastTimeUpdateMessage(instance, players, remainingTimeLimit, text)
end