landsandboat/scripts/globals/magian.lua
2026-05-17 20:58:21 -04:00

1036 lines
36 KiB
Lua

-----------------------------------
-- Magian Trial Global
-----------------------------------
require('scripts/globals/magian_data')
require('scripts/globals/npc_util')
-----------------------------------
local ruludeID = zones[xi.zone.RULUDE_GARDENS]
-----------------------------------
xi = xi or {}
xi.magian = xi.magian or {}
-- NOTE: This table should never be accessed directly, and is not guaranteed to contain
-- data unless returned via getPlayerTrialData().
xi.magian.playerCache = xi.magian.playerCache or {}
local magianMoogleInfo =
{
['Magian_Moogle_Blue'] = { nil, 10141, 10142, 10143, 10144, 10148, xi.itemType.ARMOR },
['Magian_Moogle_Orange'] = { 10121, 10122, 10123, 10124, 10125, 10129, xi.itemType.WEAPON },
}
-- Returns a table of data containing the player's currently active trials, and caches this data
-- keyed by player ID into xi.magian.playerCache.
local function getPlayerTrialData(player)
local playerId = player:getID()
local trialData = xi.magian.playerCache[playerId]
-- NOTE: This cached data may vary across processes, therefore as a safety measure, refresh
-- the cache any time the player has zoned.
if
trialData and
player:getLocalVar('magianZoned') == 1
then
return trialData
end
trialData = {}
local slotLookup = {}
for trialSlot = 1, 10 do
local packedTrial = player:getCharVar('[trial]' .. trialSlot)
local trialId = bit.rshift(packedTrial, 16)
local trialProgress = bit.band(packedTrial, 0xFFFF)
local objectiveTotal = xi.magian.trials[trialId] and xi.magian.trials[trialId].numRequired or 0
trialData[trialSlot] =
{
trialId = trialId,
progress = trialProgress,
objectiveTotal = objectiveTotal,
}
if trialId > 0 then
slotLookup[trialId] = trialSlot
end
end
xi.magian.playerCache[playerId] =
{
trialData = trialData,
slotLookup = slotLookup,
}
player:setLocalVar('magianZoned', 1)
return xi.magian.playerCache[playerId]
end
local function getTrialProgress(player, trialId)
local playerTrials = getPlayerTrialData(player)
local trialSlot = playerTrials.slotLookup[trialId]
if trialSlot then
return playerTrials.trialData[trialSlot].progress
end
return nil
end
-- Returns the Index of first available Trial Slot
local function getAvailableTrialSlot(player)
local playerTrials = getPlayerTrialData(player)
for trialSlot, trialData in ipairs(playerTrials.trialData) do
if trialData.trialId == 0 then
return trialSlot
end
end
end
-- Returns the slot in which a specific trial for a player is stored, or
-- returns nil if not active.
-- NOTE: This function relies on cached player data, though all functions that
-- use this will have cached data.
local function getTrialSlot(player, trialId)
return xi.magian.playerCache[player:getID()].slotLookup[trialId]
end
-- Updates Player Trial Data in Lua cache and database
local function updatePlayerTrial(player, trialSlot, trialId, progress)
local playerTrialData = getPlayerTrialData(player)
if
trialId == playerTrialData.trialData[trialSlot].trialId and
progress == playerTrialData.trialData[trialSlot].progress
then
return
end
-- If clearing out a trial, ensure that slotLookup data is also removed
if trialId == 0 then
local previousTrialId = playerTrialData.trialData[trialSlot].trialId
playerTrialData.slotLookup[previousTrialId] = nil
else
playerTrialData.slotLookup[trialId] = trialSlot
end
playerTrialData.trialData[trialSlot].trialId = trialId
playerTrialData.trialData[trialSlot].progress = progress or 0
playerTrialData.trialData[trialSlot].objectiveTotal = xi.magian.trials[trialId] and xi.magian.trials[trialId].numRequired or 0
local packedData = bit.lshift(trialId, 16) + progress
player:setCharVar('[trial]' .. trialSlot, packedData)
end
local function progressPlayerTrial(player, trialId, progressAmt)
local activeTrials = getPlayerTrialData(player)
local trialSlot = activeTrials.slotLookup[trialId]
if
trialSlot and
activeTrials.trialData[trialSlot].progress < activeTrials.trialData[trialSlot].objectiveTotal
then
updatePlayerTrial(player, trialSlot, trialId, activeTrials.trialData[trialSlot].progress + progressAmt)
local remainingObjectives = activeTrials.trialData[trialSlot].objectiveTotal - activeTrials.trialData[trialSlot].progress
if remainingObjectives == 0 then
player:messageBasic(xi.msg.basic.MAGIAN_TRIAL_COMPLETE, trialId)
else
player:messageBasic(xi.msg.basic.MAGIAN_TRIAL_COMPLETE - 1, trialId, remainingObjectives)
end
end
end
-- Returns a packed table of active trials, two per index, along with total count of active trials. Active trials
-- are determined by a non-zero value for trialId in the cached data.
local function packActiveTrials(player)
local activeTrials = {}
local playerTrials = getPlayerTrialData(player)
for _, trialSlot in pairs(playerTrials.trialData) do
if trialSlot.trialId > 0 then
table.insert(activeTrials, trialSlot.trialId)
end
end
local packedData = { 0, 0, 0, 0, 0 }
for paramIndex = 1, #activeTrials, 2 do
packedData[(paramIndex + 1) / 2] = activeTrials[paramIndex] + bit.lshift((activeTrials[paramIndex + 1] or 0), 16)
end
return packedData, #activeTrials
end
-- Returns a packed 5 bit value and 11 bit identifier of a single augment. Expected input is a table in the format
-- of { augmentId, augmentValue }
local function packAugment(augmentTable)
return bit.lshift(augmentTable[2], 11) + augmentTable[1]
end
-- Returns a table containing two packed 32bit parameters corresponding to { Augment1+Augment2, Augment3+Augment4 }
local function packAugmentParameters(augmentTable)
local packedAugments = { 0, 0 }
if augmentTable then
for augIndex, augData in pairs(augmentTable) do
local packedIndex = math.ceil(augIndex / 2)
local shiftAmount = augIndex % 2 == 1 and 16 or 0
packedAugments[packedIndex] = packedAugments[packedIndex] + bit.lshift(packAugment(augData), shiftAmount)
end
end
return packedAugments
end
local function getRequiredTradeItem(trialId)
local tradeItem = xi.magian.trials[trialId].tradeItem
return tradeItem and tradeItem or 0
end
-- Tables are always passed by reference. We're going to exploit this a bit here
-- in order to safely insert a value into a nested table structure.
local function insertOrCreate(targetTable, tableIndices, value)
local subTable = targetTable
for tableIndex = 1, #tableIndices do
local indexValue = tableIndices[tableIndex]
if not subTable[indexValue] then
subTable[indexValue] = {}
end
if tableIndex == #tableIndices then
table.insert(subTable[indexValue], value)
end
subTable = subTable[indexValue]
end
end
local function getNestedValue(targetTable, tableIndices)
local subTable = targetTable
for tableIndex = 1, #tableIndices do
local indexValue = tableIndices[tableIndex]
if not subTable[indexValue] then
return nil
end
if tableIndex == #tableIndices then
return subTable[indexValue]
end
subTable = subTable[indexValue]
end
end
-- Build table keyed by parent containing all child trials.
local function buildMagianLookupTables()
local relationTable = {}
local requiredItemTable = {}
for trialId, trialData in pairs(xi.magian.trials) do
-- Build Parent-Child Table
if not relationTable[trialData.previousTrial] then
relationTable[trialData.previousTrial] = {}
end
table.insert(relationTable[trialData.previousTrial], trialId)
-- Build Item + Augment to Trial Table
local itemId = trialData.requiredItem.itemId
local lookupKeys = { itemId, 0, 0, 0, 0 }
if trialData.requiredItem.itemAugments then
for augmentPos = 1, #trialData.requiredItem.itemAugments do
lookupKeys[augmentPos + 1] = packAugment(trialData.requiredItem.itemAugments[augmentPos])
end
end
insertOrCreate(requiredItemTable, lookupKeys, trialId)
end
return relationTable, requiredItemTable
end
-- Set onEquip and onUnequip functions for trial items that require
-- a listener. This avoids having to create an item file for every magian item,
-- since onItemEquip/unEquip functions only exist for two items.
-- NOTE: This function isn't the most efficient, but is only executed on server
-- start, or magian reload.
xi.magian.registerTrialListeners = function()
xi.items = xi.items or {}
for trialId, magianData in pairs(xi.magian.trials) do
if
magianData.defeatMob or
magianData.gainExp or
magianData.useWeaponskill
then
for itemKey, itemId in pairs(xi.item) do
if magianData.requiredItem.itemId == itemId then
local itemName = string.lower(itemKey)
xi.items[itemName] = xi.items[itemName] or {}
xi.items[itemName]['onItemEquip'] = xi.magian.onItemEquip
xi.items[itemName]['onItemUnequip'] = xi.magian.onItemUnequip
break
end
end
end
end
end
xi.magian.trialChildren, xi.magian.requiredItemsToTrial = buildMagianLookupTables()
-- Given Item ID, if exists in xi.magian.requiredItemsToTrial, return its table
local function getAvailableTrials(itemObj)
local itemId = itemObj:getID()
local lookupKeys = { itemId, 0, 0, 0, 0 }
-- TODO: Create binding to return table of augments
for augSlot = 0, 3 do
lookupKeys[augSlot + 2] = packAugment(itemObj:getAugment(augSlot))
end
return getNestedValue(xi.magian.requiredItemsToTrial, lookupKeys) or {}
end
-----------------------------------
-- Global NPC Functions
-----------------------------------
local function giveMagianItem(player, itemData, inscribeTrialId)
local exdata =
{
augmentKind = xi.augment.kind.HAS_AUGMENTS,
augmentSubKind = xi.augment.subKind.STANDARD,
}
if itemData.itemAugments then
exdata.augments = {}
for _, augData in pairs(itemData.itemAugments) do
table.insert(exdata.augments, { id = augData[1], value = augData[2] })
end
end
if inscribeTrialId and inscribeTrialId ~= 0 then
exdata.augmentSubKind = exdata.augmentSubKind + xi.augment.subKind.TRIAL
exdata.trial = { id = inscribeTrialId, completed = false }
end
player:addItem({ id = itemData.itemId, exdata = exdata })
end
xi.magian.giveRequiredItem = function(player, trialId, inscribeTrialId)
giveMagianItem(player, xi.magian.trials[trialId].requiredItem, inscribeTrialId == true and trialId or 0)
end
xi.magian.giveRewardItem = function(player, trialId)
giveMagianItem(player, xi.magian.trials[trialId].rewardItem, false)
end
xi.magian.magianOnTrade = function(player, npc, trade)
if xi.settings.main.ENABLE_MAGIAN_TRIALS ~= 1 then
return
end
local moogleData = magianMoogleInfo[npc:getName()]
local itemObj = trade:getItem()
local itemId = trade:getItemId()
local availableTrials = getAvailableTrials(itemObj)
local _, numActiveTrials = packActiveTrials(player)
local trialId = itemObj:getTrialNumber()
local trialData = xi.magian.trials[trialId]
if
player:hasKeyItem(xi.ki.MAGIAN_TRIAL_LOG) and
trade:getSlotCount() == 1 and
itemObj:isType(moogleData[7])
then
if #availableTrials == 0 then
-- Invalid/Unsupported Item was Traded
player:startEvent(moogleData[4], 0, 0, 0, 0, 0, 0, 0, utils.MAX_UINT32)
return
elseif
numActiveTrials >= 10 and
trialId == 0
then
-- Player cannot undertake more than 10 trials, and has attempted
-- to start another.
player:startEvent(moogleData[4], 0, 0, 0, 0, 0, 0, 0, utils.MAX_UINT32 - 254)
return
elseif trialId ~= 0 then
local playerTrialCache = getPlayerTrialData(player)
for _, slotData in pairs(playerTrialCache.trialData) do
if slotData.trialId == trialId then
player:setLocalVar('storeTrialId', trialId)
player:tradeComplete()
if slotData.progress >= slotData.objectiveTotal then
-- Trial has been completed
player:startEvent(moogleData[6], 0, 0, 0, trialData.rewardItem, 0, 0, 0, itemId)
else
-- Display status of selected trial
-- TODO: v was always a table which would be 0 from bindings, determine what value is expected here.
-- Defaulting to 0 and leaving the variable to mark position for future research.
local v = 0 -- Test
player:startEvent(moogleData[5], trialId, itemId, 0, 0, v, 0, 0, utils.MAX_UINT32 - 1)
end
return
end
end
-- Item has a trial, but no data is stored on the player
player:setLocalVar('storeTrialId', trialId)
player:startEvent(moogleData[5], trialId, trialData.reqItem, 0, 0, 0, 0, 0, utils.MAX_UINT32 - 2)
player:tradeComplete()
return
elseif #availableTrials > 0 then
player:setLocalVar('storeTrialId', availableTrials[1])
player:tradeComplete()
player:startEvent(moogleData[4], availableTrials[1], availableTrials[2], availableTrials[3], availableTrials[4], 0, itemId)
return
end
end
end
xi.magian.magianOnTrigger = function(player, npc)
if xi.settings.main.ENABLE_MAGIAN_TRIALS ~= 1 then
return
end
local moogleData = magianMoogleInfo[npc:getName()]
if
moogleData[1] and
player:getMainLvl() < 75
then
player:startEvent(moogleData[1])
elseif not player:hasKeyItem(xi.ki.MAGIAN_TRIAL_LOG) then
player:startEvent(moogleData[2])
else
local packedData, numActiveTrials = packActiveTrials(player)
player:startEvent(moogleData[3], packedData[1], packedData[2], packedData[3], packedData[4], packedData[5], 0, 0, numActiveTrials)
end
end
xi.magian.magianEventUpdate = function(player, csid, option, npc)
local updateType = bit.band(option, 0xFF)
switch (updateType): caseof
{
[1] = function()
local trialId = bit.rshift(option, 16)
local trialData = xi.magian.trials[trialId]
local augParam1, augParam2 = unpack(packAugmentParameters(trialData.requiredItem.itemAugments))
local tradeItem = getRequiredTradeItem(trialId)
player:updateEvent(2, augParam1, augParam2, trialData.requiredItem.itemId, 0, tradeItem, trialData.textOffset)
end,
[2] = function()
local trialId = bit.rshift(option, 16)
local trialData = xi.magian.trials[trialId]
local progress = getTrialProgress(player, trialId)
local requiredElement = trialData.requiredElement and trialData.requiredElement or 0
local optParam = 0
-- NOTE: optParam value is required for certain messages, and even though
-- this system can support more complex combinations, it may not display correctly
-- if customizing.
if trialData.tradeItem then
optParam = getRequiredTradeItem(trialId)
elseif trialData.minDamage then
optParam = trialData.minDamage
elseif trialData.dayWeather then
optParam = trialData.dayWeather
end
player:updateEvent(trialData.numRequired, 0, progress, 0, 0, optParam, requiredElement)
end,
-- Send information regarding the Reward Item
[3] = function()
local trialId = bit.rshift(option, 16)
local trialData = xi.magian.trials[trialId]
local augParam1, augParam2 = unpack(packAugmentParameters(trialData.rewardItem.itemAugments))
local tradeItem = getRequiredTradeItem(trialId)
player:updateEvent(2, augParam1, augParam2, trialData.rewardItem.itemId, 0, tradeItem)
end,
-- Display Available Trials for the provided Item
[4] = function()
local trialId = bit.rshift(option, 16)
local trialData = xi.magian.trials[trialId]
local nextTrials = xi.magian.trialChildren[trialId] or { 0, 0, 0, 0 }
local tradeItem = getRequiredTradeItem(trialId)
player:updateEvent(nextTrials[1], nextTrials[2], nextTrials[3], nextTrials[4], trialData.previousTrial, tradeItem)
end,
-- Lists Trials to Abandon
[5] = function()
local packedTrials, trialCount = packActiveTrials(player)
player:updateEvent(packedTrials[1], packedTrials[2], packedTrials[3], packedTrials[4], packedTrials[5], 0, 0, trialCount)
end,
-- Abandon Selected Trial
[6] = function()
local trialId = bit.rshift(option, 8)
local trialSlot = getTrialSlot(player, trialId)
if trialSlot then
player:updateEvent(0, 0, 0, 0, 0, trialSlot)
updatePlayerTrial(player, trialSlot, 0, 0)
end
end,
-- Checks if Trial is already in progress
[7] = function()
local trialId = bit.rshift(option, 8)
local trialData = xi.magian.trials[trialId]
local augParam1, augParam2 = unpack(packAugmentParameters(trialData.requiredItem.itemAugments))
local trialSlot = getTrialSlot(player, trialId)
if trialSlot then
player:updateEvent(0, 0, 0, 0, 0, 0, 0, utils.MAX_UINT32)
return
end
player:updateEvent(2, augParam1, augParam2, trialData.requiredItem.itemId)
end,
-- Abandon Trial through Trade (Options 8 and 11)
[8] = function()
local trialId = bit.rshift(option, 8)
local trialData = xi.magian.trials[trialId]
player:updateEvent(0, 0, 0, trialData.requiredItem.itemId)
end,
[11] = function()
local trialId = bit.rshift(option, 8)
local trialData = xi.magian.trials[trialId]
player:updateEvent(0, 0, 0, trialData.requiredItem.itemId)
end,
-- Checks if Item's Level will increase
[13] = function()
local trialId = bit.rshift(option, 8)
local trialData = xi.magian.trials[trialId]
local requiredItem = GetReadOnlyItem(trialData.requiredItem.itemId)
local rewardItem = GetReadOnlyItem(trialData.rewardItem.itemId)
if
requiredItem and
rewardItem and
requiredItem:getReqLvl() < rewardItem:getReqLvl()
then
player:updateEvent(1)
else
player:updateEvent(0)
end
end,
-- Checks if player already has Reward Item, and Item is Rare
[14] = function()
local trialId = bit.rshift(option, 8)
local trialData = xi.magian.trials[trialId]
local rewardObj = player:findItem(trialData.rewardItem.itemId)
if
rewardObj and
bit.band(rewardObj:getFlag(), 0x8000) == 1
then
player:updateEvent(1)
else
player:updateEvent(0)
end
end,
}
end
xi.magian.magianOnEventFinish = function(player, csid, option, npc)
local moogleData = magianMoogleInfo[npc:getName()]
local finishType = bit.band(option, 0xFF)
if
csid == moogleData[2] and
option == 1
then
npcUtil.giveKeyItem(player, xi.ki.MAGIAN_TRIAL_LOG)
elseif csid == moogleData[4] then
-- Trial Item Traded without Trial Inscribed
if finishType == 7 then
-- Start a new trial for an Item
local trialId = bit.rshift(option, 8)
local trialInfo = xi.magian.trials[trialId]
xi.magian.giveRequiredItem(player, trialId, true)
player:messageSpecial(ruludeID.text.RETURN_MAGIAN_ITEM, trialInfo.requiredItem.itemId)
updatePlayerTrial(player, getAvailableTrialSlot(player), trialId, 0)
player:setLocalVar('storeTrialId', 0)
elseif
finishType == 0 or
finishType == 255
then
local trialId = player:getLocalVar('storeTrialId')
local trialInfo = xi.magian.trials[trialId]
xi.magian.giveRequiredItem(player, trialId, false)
player:messageSpecial(ruludeID.text.RETURN_MAGIAN_ITEM, trialInfo.requiredItem.itemId)
player:setLocalVar('storeTrialId', 0)
end
elseif csid == moogleData[5] then
-- Trial Item Traded with a Trial Inscribed
if
finishType == 0 or
finishType == 4
then
-- Return item to the player
local trialId = player:getLocalVar('storeTrialId')
local trialInfo = xi.magian.trials[trialId]
xi.magian.giveRequiredItem(player, trialId, true)
player:messageSpecial(ruludeID.text.RETURN_MAGIAN_ITEM, trialInfo.requiredItem.itemId)
player:setLocalVar('storeTrialId', 0)
elseif
finishType == 8 or
finishType == 11
then
-- Remove Trial ID and return item to the player
local trialId = bit.rshift(option, 8)
local trialInfo = xi.magian.trials[trialId]
local activeSlot = getTrialSlot(player, trialId)
if activeSlot then
updatePlayerTrial(player, activeSlot, 0, 0)
end
xi.magian.giveRequiredItem(player, trialId, false)
player:messageSpecial(ruludeID.text.RETURN_MAGIAN_ITEM, trialInfo.requiredItem.itemId)
end
elseif
csid == moogleData[6] and
finishType == 0
then
-- Complete Active Trial
local trialId = player:getLocalVar('storeTrialId')
local trialInfo = xi.magian.trials[trialId]
local activeSlot = getTrialSlot(player, trialId)
if activeSlot then
updatePlayerTrial(player, activeSlot, 0, 0)
end
xi.magian.giveRewardItem(player, trialId)
player:messageSpecial(ruludeID.text.ITEM_OBTAINED, trialInfo.rewardItem.itemId)
player:setLocalVar('storeTrialId', 0)
end
end
-----------------------------------
-- Delivery Crate
-----------------------------------
-- Get the active trials which requires the traded item in parameter
local function getPlayerTrialsByTradeItemId(player, itemId)
local activeTrials = getPlayerTrialData(player)
local resultTrials = {}
for trialId, trialSlot in pairs(activeTrials.slotLookup) do
if
itemId == xi.magian.trials[trialId].tradeItem and
activeTrials.trialData[trialSlot].progress < activeTrials.trialData[trialSlot].objectiveTotal
then
table.insert(resultTrials, activeTrials.trialData[trialSlot])
end
end
return resultTrials
end
xi.magian.deliveryCrateOnTrade = function(player, npc, trade)
local trialId = 0
local trialSlotId = nil
local tradeItems = {}
-- Find the trial item in any slot and set trialId
for tradeSlot = 0, 7 do
local itemObj = trade:getItem(tradeSlot)
if itemObj then
local itemTrialId = itemObj:getTrialNumber()
if
itemTrialId ~= 0 and
trialId == 0 and
xi.magian.trials[itemTrialId] and
xi.magian.trials[itemTrialId].tradeItem
then
-- NOTE: First in Wins, and we ignore any other item with a trial
trialId = itemTrialId
trialSlotId = tradeSlot
end
end
end
-- With trialId known sum only the required trade item across all other slots
if trialId ~= 0 then
local requiredItemId = xi.magian.trials[trialId].tradeItem
for tradeSlot = 0, 7 do
-- Skip the slot that contained the trial weapon/armor
if tradeSlot ~= trialSlotId then
local itemObj = trade:getItem(tradeSlot)
if itemObj then
local itemId = itemObj:getID()
local itemTrialId = itemObj:getTrialNumber()
-- Only sum the required trade items never count items with a trial number
if itemTrialId == 0 and itemId == requiredItemId then
local qty = trade:getSlotQty(tradeSlot)
if qty > 0 then
tradeItems[requiredItemId] = (tradeItems[requiredItemId] or 0) + qty
end
end
end
end
end
end
local trialInfo = xi.magian.trials[trialId]
local playerTrialProgress = getTrialProgress(player, trialId)
if
trialId ~= 0 and
playerTrialProgress and
trialInfo.tradeItem and
tradeItems[trialInfo.tradeItem]
then
local numRelevantTrials = #getPlayerTrialsByTradeItemId(player, trialInfo.tradeItem)
local numRemainingItems = trialInfo.numRequired - playerTrialProgress
local numItemsTraded = math.min(tradeItems[trialInfo.tradeItem], numRemainingItems)
trade:confirmItem(trialInfo.tradeItem, numItemsTraded)
player:setLocalVar('storeTrialId', trialId)
player:setLocalVar('tradedItemId', trialInfo.tradeItem)
player:setLocalVar('tradedItemQty', numItemsTraded)
player:startEvent(10134, trialInfo.tradeItem, numItemsTraded, numRelevantTrials, trialId, 0, 0, 0, 0)
end
end
xi.magian.deliveryCrateOnEventUpdate = function(player, csid, option, npc)
local optionMod = bit.band(option, 0xFF)
local tradedItemId = player:getLocalVar('tradedItemId')
local numRelevantTrials = #getPlayerTrialsByTradeItemId(player, tradedItemId)
local maxNumber = 31
if csid == 10134 then
if optionMod == 101 then
player:updateEvent(tradedItemId, 0, 0, 0, 0, 0, maxNumber, 0)
elseif optionMod == 103 then
local places = bit.rshift(maxNumber, numRelevantTrials)
local activeTradeTrials = getPlayerTrialsByTradeItemId(player, tradedItemId)
local trialParams = {}
for i = 5, 1, -1 do
local currentTrial = activeTradeTrials[i]
if currentTrial and currentTrial.trialId ~= 0 then
local remainingObjectives = currentTrial.objectiveTotal - currentTrial.progress
table.insert(trialParams, bit.lshift(remainingObjectives, 16) + currentTrial.trialId)
else
table.insert(trialParams, 0)
end
end
player:updateEvent(trialParams[1], trialParams[2], trialParams[3], trialParams[4], trialParams[5], numRelevantTrials, places, 0)
end
end
end
xi.magian.deliveryCrateOnEventFinish = function(player, csid, option, npc)
local optionMod = bit.band(option, 0xFF)
local trialId = bit.rshift(option, 8)
local tradedItemId = player:getLocalVar('tradedItemId')
local tradedItemQty = player:getLocalVar('tradedItemQty')
if csid == 10134 then
if optionMod == 0 then
if tradedItemQty > 1 then
player:messageSpecial(ruludeID.text.RETURN_ITEMS, tradedItemId, tradedItemQty)
else
player:messageSpecial(ruludeID.text.RETURN_ITEM, tradedItemId)
end
elseif optionMod == 102 then
player:confirmTrade()
progressPlayerTrial(player, trialId, tradedItemQty)
end
if
optionMod == 0 or
optionMod == 102
then
player:setLocalVar('storeTrialId', 0)
player:setLocalVar('tradedItemId', 0)
player:setLocalVar('tradedItemQty', 0)
end
end
end
local elementData =
{
[xi.magianElement.FIRE ] = { xi.element.FIRE },
[xi.magianElement.ICE ] = { xi.element.ICE },
[xi.magianElement.WIND ] = { xi.element.WIND },
[xi.magianElement.EARTH ] = { xi.element.EARTH },
[xi.magianElement.THUNDER ] = { xi.element.THUNDER },
[xi.magianElement.WATER ] = { xi.element.WATER },
[xi.magianElement.LIGHT ] = { xi.element.LIGHT },
[xi.magianElement.DARK ] = { xi.element.DARK },
[xi.magianElement.ANY_LIGHT] =
{
xi.element.FIRE,
xi.element.WIND,
xi.element.THUNDER,
xi.element.LIGHT,
},
[xi.magianElement.ANY_DARK] =
{
xi.element.ICE,
xi.element.EARTH,
xi.element.WATER,
xi.element.DARK,
},
[xi.magianElement.ANY] =
{
xi.element.FIRE,
xi.element.ICE,
xi.element.WIND,
xi.element.EARTH,
xi.element.THUNDER,
xi.element.WATER,
xi.element.LIGHT,
xi.element.DARK,
},
}
local trialConditions =
{
-- NOTE: This condition is a bit of an outlier, as it is both a condition and
-- can have a higher reward value as opposed to a credit of 1. Light and Dark are special exceptions
-- that should be handled in magian trial data, if it is the case where multiple elements are allowed.
['dayWeather'] = function(trialData, player, mob, paramTable)
if trialData.dayWeather then
local dayWeatherResult = 0
local dayWeatherTable = elementData[trialData.dayWeather]
local currentDay = VanadielDayOfTheWeek()
local currentWeather = player:getWeather(true)
-- For each element in that table (may not be all elements)
for _, elementId in ipairs(dayWeatherTable) do
-- Check current day element against element checked.
if xi.data.element.getDayElement(currentDay) == elementId then
dayWeatherResult = dayWeatherResult + 1
end
-- Check current weather element against element checked.
if xi.data.element.getWeatherElement(currentWeather) == elementId then
dayWeatherResult = dayWeatherResult + 5
end
end
return dayWeatherResult > 0 and dayWeatherResult or false
end
return true
end,
['minDamage'] = function(trialData, player, mob, paramTable)
return not trialData.minDamage or paramTable.weaponskillDamage >= trialData.minDamage
end,
['mobEcosystem'] = function(trialData, player, mob, paramTable)
return not trialData.mobEcosystem or mob:getEcosystem() == trialData.mobEcosystem
end,
['mobSpecies'] = function(trialData, player, mob, paramTable)
return not trialData.mobSpecies or trialData.mobSpecies[mob:getSpecies()]
end,
['mobFamily'] = function(trialData, player, mob, paramTable)
return not trialData.mobFamily or trialData.mobFamily[mob:getFamily()]
end,
['useWeaponskill'] = function(trialData, player, mob, paramTable)
local weaponskillTable = type(trialData.useWeaponskill) == 'table' and trialData.useWeaponskill or set{ trialData.useWeaponskill }
return not trialData.useWeaponskill or weaponskillTable[paramTable.weaponskillUsed]
end,
['zoneId'] = function(trialData, player, mob, paramTable)
return not trialData.zoneId or trialData.zoneId[player:getZoneID()]
end,
}
local function checkConditions(trialData, player, mob, paramTable)
local creditReward = 1
for conditionName, conditionFunc in pairs(trialConditions) do
local conditionResult = conditionFunc(trialData, player, mob, paramTable)
if not conditionResult then
return false
elseif type(conditionResult) == 'number' then
creditReward = conditionResult
end
end
return creditReward
end
-----------------------------------
-- Item Globals/Callbacks
-----------------------------------
xi.magian.onItemEquip = function(player, itemObj)
local itemTrialId = itemObj:getTrialNumber()
-- If the item has no active trial, or the player has
-- abandoned the trial, bail out.
if
itemTrialId == 0 or
not getTrialProgress(player, itemTrialId)
then
return
end
local trialData = xi.magian.trials[itemTrialId]
if not trialData then
return
end
-- A valid trial exists, so apply the appropriate listener for it. Defeating a mob is always the highest
-- priority listener, and if it doesn't apply, only then fall back. All magian trials with a listener
-- require the player to both be alive and the mob be experience-granting (NMs are handled separately)
-- TODO:
-- Elemental Damage (Anyone counts!)
-- Pet kill
-- Avatar Kill (Summon must be out, but another of the same type can cause credit)
-- While under specific enfeeble
-- Proc additional effect
if trialData.defeatMob then
player:addListener('DEFEATED_MOB', 'TRIAL_' .. itemTrialId, function(mobObj, playerObj, optParams)
if not playerObj:isDead() and playerObj:checkKillCredit(mobObj) then
local conditionResult = checkConditions(trialData, playerObj, mobObj, optParams)
if conditionResult then
progressPlayerTrial(playerObj, itemTrialId, conditionResult)
end
end
end)
elseif trialData.useWeaponskill then
player:addListener('WEAPONSKILL_USE', 'TRIAL_' .. itemTrialId, function(playerObj, mobObj, skill, tp, action, damage)
if not playerObj:isDead() and playerObj:checkKillCredit(mobObj) then
local conditionResult = checkConditions(trialData, playerObj, mobObj, { weaponskillUsed = skill:getID(), weaponskillDamage = damage })
if conditionResult then
progressPlayerTrial(playerObj, itemTrialId, conditionResult)
end
end
end)
elseif trialData.gainExp then
player:addListener('EXPERIENCE_POINTS', 'TRIAL_' .. itemTrialId, function(playerObj, mobObj, expGained)
if not playerObj:isDead() and playerObj:checkKillCredit(mobObj) then
if checkConditions(trialData, playerObj, mobObj) then
progressPlayerTrial(playerObj, itemTrialId, expGained)
end
end
end)
end
end
xi.magian.onItemUnequip = function(player, itemObj)
local itemTrialId = itemObj:getTrialNumber()
if
itemTrialId ~= 0 and
getTrialProgress(player, itemTrialId)
then
player:removeListener('TRIAL_' .. itemTrialId)
end
end
xi.magian.onMobDeath = function(mob, player, optParams, trialTable)
local relevantTrials = {}
if not player then
return
end
if not trialTable then
return
end
for equipSlot = xi.slot.MAIN, xi.slot.FEET do
local itemObj = player:getEquippedItem(equipSlot)
if itemObj then
local trialId = itemObj:getTrialNumber()
if
trialId > 0 and
trialTable[trialId]
then
table.insert(relevantTrials, trialId)
end
end
end
for _, trialId in ipairs(relevantTrials) do
progressPlayerTrial(player, trialId, 1)
end
end