diff --git a/scripts/enum/item.lua b/scripts/enum/item.lua index c879cf05812..732263e37e2 100644 --- a/scripts/enum/item.lua +++ b/scripts/enum/item.lua @@ -5269,6 +5269,26 @@ xi.item = CALMA_HOSE = 11980, MUSTELA_BRAIS = 11981, MAGAVAN_SLOPS = 11982, + FIGHTERS_TORQUE = 11988, + TEMPLE_TORQUE = 11989, + HEALERS_TORQUE = 11990, + WIZARDS_TORQUE = 11991, + WARLOCKS_TORQUE = 11992, + ROGUES_TORQUE = 11993, + GALLANT_TORQUE = 11994, + CHAOS_TORQUE = 11995, + BEAST_TORQUE = 11996, + CHORAL_TORQUE = 11997, + HUNTERS_TORQUE = 11998, + MYOCHIN_SHUSA = 11999, + NINJA_SHUSA = 12000, + DRACHEN_TORQUE = 12001, + EVOKERS_TORQUE = 12002, + MAGUS_TORQUE = 12003, + CORSAIRS_TORQUE = 12004, + PUPPETRY_TORQUE = 12005, + DANCERS_TORQUE = 12006, + SCHOLARS_TORQUE = 12007, RAVAGERS_MASK = 12008, TANTRA_CROWN = 12009, ORISON_CAP = 12010, diff --git a/scripts/globals/magian.lua b/scripts/globals/magian.lua index 70821f51f51..f5f7e5d1c4f 100644 --- a/scripts/globals/magian.lua +++ b/scripts/globals/magian.lua @@ -13,10 +13,22 @@ xi.magian = xi.magian or {} -- data unless returned via getPlayerTrialData(). xi.magian.playerCache = xi.magian.playerCache or {} +-- Per-moogle configuration. Indices, in order: +-- [1] event: player level is too low (nil to skip the check entirely) +-- [2] event: player does not yet hold this moogle's trial log +-- [3] event: main menu, showing the player's active trials +-- [4] event: an item was traded, offering the trials available for it +-- [5] event: status of a selected trial +-- [6] event: a trial is complete, handing over the reward +-- [7] item type this moogle accepts +-- [8] minimum level required to use this moogle +-- [9] key item acting as this moogle's trial log +-- [10] key item this moogle also accepts, but never hands out 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 }, + ['Magian_Moogle_Blue'] = { nil, 10141, 10142, 10143, 10144, 10148, xi.itemType.ARMOR, 75, xi.ki.MAGIAN_TRIAL_LOG, nil }, + ['Magian_Moogle_Orange'] = { 10121, 10122, 10123, 10124, 10125, 10129, xi.itemType.WEAPON, 75, xi.ki.MAGIAN_TRIAL_LOG, nil }, + ['Magian_Moogle_Green'] = { 10151, 10160, 10152, 10153, 10156, 10158, xi.itemType.ARMOR, 30, xi.ki.MAGIAN_LEARNERS_LOG, xi.ki.MAGIAN_TRIAL_LOG }, } -- Returns a table of data containing the player's currently active trials, and caches this data @@ -352,7 +364,7 @@ xi.magian.magianOnTrade = function(player, npc, trade) local trialData = xi.magian.trials[trialId] if - player:hasKeyItem(xi.ki.MAGIAN_TRIAL_LOG) and + (player:hasKeyItem(moogleData[9]) or (moogleData[10] ~= nil and player:hasKeyItem(moogleData[10]))) and trade:getSlotCount() == 1 and itemObj:isType(moogleData[7]) then @@ -419,15 +431,26 @@ xi.magian.magianOnTrigger = function(player, npc) if moogleData[1] and - player:getMainLvl() < 75 + player:getMainLvl() < moogleData[8] then player:startEvent(moogleData[1]) - elseif not player:hasKeyItem(xi.ki.MAGIAN_TRIAL_LOG) then + elseif + not (player:hasKeyItem(moogleData[9]) or (moogleData[10] ~= nil and player:hasKeyItem(moogleData[10]))) + then player:startEvent(moogleData[2]) else local packedData, numActiveTrials = packActiveTrials(player) + local eligibleJobs = 0 + + -- Trials for jobs the player has not levelled far enough are hidden from the list. + -- The mask marks the entries to omit, so the bit is set for ineligible jobs. + for job = xi.job.WAR, xi.job.SCH do + if player:getJobLevel(job) < moogleData[8] then + eligibleJobs = bit.bor(eligibleJobs, bit.lshift(1, job)) + end + end - player:startEvent(moogleData[3], packedData[1], packedData[2], packedData[3], packedData[4], packedData[5], 0, 0, numActiveTrials) + player:startEvent(moogleData[3], packedData[1], packedData[2], packedData[3], packedData[4], packedData[5], eligibleJobs, 0, numActiveTrials) end end @@ -442,6 +465,7 @@ xi.magian.magianEventUpdate = function(player, csid, option, npc) 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, @@ -503,6 +527,11 @@ xi.magian.magianEventUpdate = function(player, csid, option, npc) if trialSlot then player:updateEvent(0, 0, 0, 0, 0, trialSlot) updatePlayerTrial(player, trialSlot, 0, 0) + + -- Recorded so the event's finish handler knows the trial was retired rather + -- than continued: both finish identically, and the cached trial data is not + -- refreshed in between. + player:setLocalVar('magianRetiredTrial', trialId) end end, @@ -570,6 +599,19 @@ xi.magian.magianEventUpdate = function(player, csid, option, npc) player:updateEvent(0) end end, + + -- Item details shown on the completion event. Unlike the other lookups this payload + -- carries no trial id, so it is taken from the stored trial instead. + [15] = function() + local trialId = player:getLocalVar('storeTrialId') + local trialData = xi.magian.trials[trialId] + + if trialData then + local augParam1, augParam2 = unpack(packAugmentParameters(trialData.requiredItem.itemAugments)) + + player:updateEvent(2, augParam1, augParam2, trialData.requiredItem.itemId, 0, getRequiredTradeItem(trialId), trialData.textOffset) + end + end, } end @@ -581,7 +623,41 @@ xi.magian.magianOnEventFinish = function(player, csid, option, npc) csid == moogleData[2] and option == 1 then - npcUtil.giveKeyItem(player, xi.ki.MAGIAN_TRIAL_LOG) + npcUtil.giveKeyItem(player, moogleData[9]) + elseif + csid == moogleData[3] and + finishType == 7 + then + -- Trial undertaken from the moogle's menu rather than by trading an item in. The + -- moogle supplies the required item itself, already inscribed. The client sends the + -- chosen trial in the same position as the traded-item path. + local trialId = bit.rshift(option, 8) + local trialData = xi.magian.trials[trialId] + + if trialData then + -- Trials tied to a job are only offered for jobs the player has levelled far + -- enough. TODO: retail omits ineligible trials from the list entirely rather + -- than refusing them on selection; replicating that needs capture data for how + -- the list is built, so this refuses at the point of acceptance instead. + if + trialData.requiredJob and + player:getJobLevel(trialData.requiredJob) < moogleData[8] + then + return + end + + -- The moogle supplies the item here rather than handing back one that was traded + -- in, so refuse when there is nowhere to put it. Registering the trial without + -- granting the item would leave it active and uncompletable. + if player:getFreeSlotsCount() == 0 then + player:messageSpecial(ruludeID.text.ITEM_CANNOT_BE_OBTAINED, trialData.requiredItem.itemId) + + return + end + + xi.magian.giveRequiredItem(player, trialId, true) + updatePlayerTrial(player, getAvailableTrialSlot(player), trialId, 0) + end elseif csid == moogleData[4] then -- Trial Item Traded without Trial Inscribed @@ -622,6 +698,22 @@ xi.magian.magianOnEventFinish = function(player, csid, option, npc) xi.magian.giveRequiredItem(player, trialId, true) player:messageSpecial(ruludeID.text.RETURN_MAGIAN_ITEM, trialInfo.requiredItem.itemId) + player:setLocalVar('storeTrialId', 0) + elseif finishType == 5 then + -- Hand the traded item back. Continuing and retiring both finish this way and + -- carry no trial id, so the stored trial is used, and update type 6 flags which + -- of the two happened. The item keeps its inscription only if the trial is being + -- continued. + local trialId = player:getLocalVar('storeTrialId') + local trialInfo = xi.magian.trials[trialId] + local retired = player:getLocalVar('magianRetiredTrial') == trialId + + if trialInfo then + xi.magian.giveRequiredItem(player, trialId, not retired) + player:messageSpecial(ruludeID.text.RETURN_MAGIAN_ITEM, trialInfo.requiredItem.itemId) + end + + player:setLocalVar('magianRetiredTrial', 0) player:setLocalVar('storeTrialId', 0) elseif finishType == 8 or @@ -642,7 +734,10 @@ xi.magian.magianOnEventFinish = function(player, csid, option, npc) end elseif csid == moogleData[6] and - finishType == 0 + ( + finishType == 0 or + finishType == 15 + ) then -- Complete Active Trial local trialId = player:getLocalVar('storeTrialId') diff --git a/scripts/globals/magian_data.lua b/scripts/globals/magian_data.lua index c77e67b35a9..a705d12e01e 100644 --- a/scripts/globals/magian_data.lua +++ b/scripts/globals/magian_data.lua @@ -11783,6 +11783,487 @@ xi.magian.trials = }, }, + -- Trial of the Magians: job emote trials, undertaken from the Green Magian Moogle's + -- menu. The moogle hands out the unaugmented neck piece; trading it back together with + -- the job's item completes the trial and returns the augmented version. + -- TODO: textOffset is unverified for all of these, and reward augments are only filled + -- in for BLU so far. + + [4424] = -- WAR + { + previousTrial = 0, + requiredJob = xi.job.WAR, + requiredItem = + { + itemId = xi.item.FIGHTERS_TORQUE, + }, + + textOffset = 1137, + tradeItem = xi.item.BULB_OF_SHAMAN_GARLIC, + numRequired = 1, + + rewardItem = + { + itemId = xi.item.FIGHTERS_TORQUE, + itemAugments = + { + [1] = { 23, 2 }, -- Accuracy +3 + [2] = { 25, 2 }, -- Attack +3 + }, + }, + }, + + [4425] = -- MNK + { + previousTrial = 0, + requiredJob = xi.job.MNK, + requiredItem = + { + itemId = xi.item.TEMPLE_TORQUE, + }, + + textOffset = 1138, + tradeItem = xi.item.CUP_OF_DHALMEL_SALIVA, + numRequired = 1, + + rewardItem = + { + itemId = xi.item.TEMPLE_TORQUE, + itemAugments = + { + [1] = { 23, 2 }, -- Accuracy +3 + [2] = { 195, 1 }, -- "Subtle Blow" +2 + }, + }, + }, + + [4426] = -- WHM + { + previousTrial = 0, + requiredJob = xi.job.WHM, + requiredItem = + { + itemId = xi.item.HEALERS_TORQUE, + }, + + textOffset = 1139, + tradeItem = xi.item.CATHEDRAL_TAPESTRY, + numRequired = 1, + + rewardItem = + { + itemId = xi.item.HEALERS_TORQUE, + itemAugments = + { + [1] = { 329, 0 }, -- Cure Potency +1% + }, + }, + }, + + [4427] = -- BLM + { + previousTrial = 0, + requiredJob = xi.job.BLM, + requiredItem = + { + itemId = xi.item.WIZARDS_TORQUE, + }, + + textOffset = 1140, + tradeItem = xi.item.MAGICKED_SKULL, + numRequired = 1, + + rewardItem = + { + itemId = xi.item.WIZARDS_TORQUE, + itemAugments = + { + [1] = { 516, 0 }, -- INT +1 + [2] = { 133, 0 }, -- "Magic Atk. Bonus" +1 + }, + }, + }, + + [4428] = -- RDM + { + previousTrial = 0, + requiredJob = xi.job.RDM, + requiredItem = + { + itemId = xi.item.WARLOCKS_TORQUE, + }, + + textOffset = 1141, + tradeItem = xi.item.BLOODY_ROBE, + numRequired = 1, + + rewardItem = + { + itemId = xi.item.WARLOCKS_TORQUE, + itemAugments = + { + [1] = { 517, 0 }, -- MND +1 + [2] = { 35, 0 }, -- Magic Accuracy +1 + }, + }, + }, + + [4429] = -- THF + { + previousTrial = 0, + requiredJob = xi.job.THF, + requiredItem = + { + itemId = xi.item.ROGUES_TORQUE, + }, + + textOffset = 1142, + tradeItem = xi.item.PINCH_OF_YUHTUNGA_SULFUR, + numRequired = 1, + + rewardItem = + { + itemId = xi.item.ROGUES_TORQUE, + itemAugments = + { + [1] = { 23, 2 }, -- Accuracy +3 + [2] = { 31, 2 }, -- Evasion +3 + }, + }, + }, + + [4430] = -- PLD + { + previousTrial = 0, + requiredJob = xi.job.PLD, + requiredItem = + { + itemId = xi.item.GALLANT_TORQUE, + }, + + textOffset = 1143, + tradeItem = xi.item.PINCH_OF_VALKURM_SUNSAND, + numRequired = 1, + + rewardItem = + { + itemId = xi.item.GALLANT_TORQUE, + itemAugments = + { + [1] = { 1, 17 }, -- HP +18 + }, + }, + }, + + [4431] = -- DRK + { + previousTrial = 0, + requiredJob = xi.job.DRK, + requiredItem = + { + itemId = xi.item.CHAOS_TORQUE, + }, + + textOffset = 1144, + tradeItem = xi.item.ODD_POSTCARD, + numRequired = 1, + + rewardItem = + { + itemId = xi.item.CHAOS_TORQUE, + itemAugments = + { + [1] = { 512, 2 }, -- STR +3 + [2] = { 25, 2 }, -- Attack +3 + }, + }, + }, + + [4432] = -- BST + { + previousTrial = 0, + requiredJob = xi.job.BST, + requiredItem = + { + itemId = xi.item.BEAST_TORQUE, + }, + + textOffset = 1145, + tradeItem = xi.item.WILD_RABBIT_TAIL, + numRequired = 1, + + rewardItem = + { + itemId = xi.item.BEAST_TORQUE, + itemAugments = + { + [1] = { 512, 2 }, -- STR +3 + [2] = { 97, 2 }, -- Pet: Attack +3 Ranged Attack +3 + }, + }, + }, + + [4433] = -- BRD + { + previousTrial = 0, + requiredJob = xi.job.BRD, + requiredItem = + { + itemId = xi.item.CHORAL_TORQUE, + }, + + textOffset = 1146, + tradeItem = xi.item.SIRENS_TEAR, + numRequired = 1, + + rewardItem = + { + itemId = xi.item.CHORAL_TORQUE, + itemAugments = + { + [1] = { 296, 0 }, -- Singing skill +1 + }, + }, + }, + + [4434] = -- RNG + { + previousTrial = 0, + requiredJob = xi.job.RNG, + requiredItem = + { + itemId = xi.item.HUNTERS_TORQUE, + }, + + textOffset = 1147, + tradeItem = xi.item.SEEDSPALL_ASTRUM, + numRequired = 1, + + rewardItem = + { + itemId = xi.item.HUNTERS_TORQUE, + itemAugments = + { + [1] = { 515, 2 }, -- AGI +3 + [2] = { 517, 2 }, -- MND +3 + }, + }, + }, + + [4435] = -- SAM + { + previousTrial = 0, + requiredJob = xi.job.SAM, + requiredItem = + { + itemId = xi.item.MYOCHIN_SHUSA, + }, + + textOffset = 1148, + tradeItem = xi.item.CRAB_APRON, + numRequired = 1, + + rewardItem = + { + itemId = xi.item.MYOCHIN_SHUSA, + itemAugments = + { + [1] = { 25, 2 }, -- Attack +3 + [2] = { 142, 0 }, -- "Store TP" +1 + }, + }, + }, + + [4436] = -- NIN + { + previousTrial = 0, + requiredJob = xi.job.NIN, + requiredItem = + { + itemId = xi.item.NINJA_SHUSA, + }, + + textOffset = 1149, + tradeItem = xi.item.PIECE_OF_EASTERN_PAPER, + numRequired = 1, + + rewardItem = + { + itemId = xi.item.NINJA_SHUSA, + itemAugments = + { + [1] = { 513, 2 }, -- DEX +3 + [2] = { 23, 2 }, -- Accuracy +3 + }, + }, + }, + + [4437] = -- DRG + { + previousTrial = 0, + requiredJob = xi.job.DRG, + requiredItem = + { + itemId = xi.item.DRACHEN_TORQUE, + }, + + textOffset = 1150, + tradeItem = xi.item.DAMSELFLY_WORM, + numRequired = 1, + + rewardItem = + { + itemId = xi.item.DRACHEN_TORQUE, + itemAugments = + { + [1] = { 512, 2 }, -- STR +3 + [2] = { 514, 2 }, -- VIT +3 + }, + }, + }, + + [4438] = -- SMN + { + previousTrial = 0, + requiredJob = xi.job.SMN, + requiredItem = + { + itemId = xi.item.EVOKERS_TORQUE, + }, + + textOffset = 1151, + tradeItem = xi.item.DAMP_ENVELOPE, + numRequired = 1, + + rewardItem = + { + itemId = xi.item.EVOKERS_TORQUE, + itemAugments = + { + [1] = { 9, 14 }, -- MP +15 + }, + }, + }, + + [4439] = -- BLU + { + previousTrial = 0, + requiredJob = xi.job.BLU, + requiredItem = + { + itemId = xi.item.MAGUS_TORQUE, + }, + + textOffset = 1152, + tradeItem = xi.item.TWO_LEAF_MANDRAGORA_BUD, + numRequired = 1, + + rewardItem = + { + itemId = xi.item.MAGUS_TORQUE, + itemAugments = + { + [1] = { 9, 9 }, -- MP +10 + [2] = { 35, 0 }, -- Magic Accuracy +1 + }, + }, + }, + + [4440] = -- COR + { + previousTrial = 0, + requiredJob = xi.job.COR, + requiredItem = + { + itemId = xi.item.CORSAIRS_TORQUE, + }, + + textOffset = 1153, + tradeItem = xi.item.HANDFUL_OF_COUNTERFEIT_GIL, + numRequired = 1, + + rewardItem = + { + itemId = xi.item.CORSAIRS_TORQUE, + itemAugments = + { + [1] = { 27, 2 }, -- Ranged Accuracy +3 + [2] = { 29, 2 }, -- Ranged Attack +3 + }, + }, + }, + + [4441] = -- PUP + { + previousTrial = 0, + requiredJob = xi.job.PUP, + requiredItem = + { + itemId = xi.item.PUPPETRY_TORQUE, + }, + + textOffset = 1154, + tradeItem = xi.item.NYUMOMO_DOLL, + numRequired = 1, + + rewardItem = + { + itemId = xi.item.PUPPETRY_TORQUE, + itemAugments = + { + [1] = { 513, 2 }, -- DEX +3 + [2] = { 96, 2 }, -- Pet: Accuracy +3 Ranged Accuracy +3 + }, + }, + }, + + [4442] = -- DNC + { + previousTrial = 0, + requiredJob = xi.job.DNC, + requiredItem = + { + itemId = xi.item.DANCERS_TORQUE, + }, + + textOffset = 1155, + tradeItem = xi.item.TORN_EPISTLE, + numRequired = 1, + + rewardItem = + { + itemId = xi.item.DANCERS_TORQUE, + itemAugments = + { + [1] = { 513, 2 }, -- DEX +3 + [2] = { 518, 2 }, -- CHR +3 + }, + }, + }, + + [4443] = -- SCH + { + previousTrial = 0, + requiredJob = xi.job.SCH, + requiredItem = + { + itemId = xi.item.SCHOLARS_TORQUE, + }, + + textOffset = 1156, + tradeItem = xi.item.COPY_OF_THE_CASTLE_FLOOR_PLANS, + numRequired = 1, + + rewardItem = + { + itemId = xi.item.SCHOLARS_TORQUE, + itemAugments = + { + [1] = { 131, 0 }, -- Magic Accuracy +1 "Magic Atk. Bonus" +1 + }, + }, + }, + [4448] = { previousTrial = 4403, diff --git a/scripts/zones/RuLude_Gardens/npcs/Magian_Moogle_Green.lua b/scripts/zones/RuLude_Gardens/npcs/Magian_Moogle_Green.lua index d5355f3ecec..6780ebfd51f 100644 --- a/scripts/zones/RuLude_Gardens/npcs/Magian_Moogle_Green.lua +++ b/scripts/zones/RuLude_Gardens/npcs/Magian_Moogle_Green.lua @@ -2,60 +2,25 @@ -- Area: Ru'Lude Gardens -- NPC: Magian Moogle (Green Bobble) -- Type: Magian Trials NPC (Job Emotes) --- !pos -4.558 2.451 111.305 64 +-- !pos -4.558 2.451 111.306 243 ----------------------------------- ---@type TNpcEntity local entity = {} entity.onTrade = function(player, npc, trade) - if xi.settings.main.ENABLE_MAGIAN_TRIALS ~= 1 then - return - end - - --[[ TODO - if trade:getItemCount() == 1 then - local itemID = trade:getItemId() - local trialInfo = getEmoteTrialInfo(itemID) - local invalid = 0 - if trialInfo.t1 == 0 and trialInfo.t2 == 0 and trialInfo.t3 == 0 and trialInfo.t4 == 0 then - invalid = 1 - end - player:startEvent(10153, trialInfo.t1, trialInfo.t2, trialInfo.t3, trialInfo.t4, 0, itemID, 0, invalid) - else - -- placeholder for torque+other required item trade. - end - ]] + xi.magian.magianOnTrade(player, npc, trade) end entity.onTrigger = function(player, npc) - if xi.settings.main.ENABLE_MAGIAN_TRIALS ~= 1 then - return - end + xi.magian.magianOnTrigger(player, npc) +end - if player:getMainLvl() < 30 then - player:startEvent(10151) - elseif - player:getCharVar('MetGreenMagianMog') == 0 and - not player:hasKeyItem(xi.ki.MAGIAN_LEARNERS_LOG) - then - if not player:hasKeyItem(xi.ki.MAGIAN_TRIAL_LOG) then - player:startEvent(10160, 0) - else - player:startEvent(10160, 1) - end - else - player:startEvent(10152) -- parameters unknown - end +entity.onEventUpdate = function(player, csid, option, npc) + xi.magian.magianEventUpdate(player, csid, option, npc) end entity.onEventFinish = function(player, csid, option, npc) - if csid == 10160 and option == 1 then - if not player:hasKeyItem(xi.ki.MAGIAN_TRIAL_LOG) then - npcUtil.giveKeyItem(player, xi.ki.MAGIAN_LEARNERS_LOG) - end - - player:setCharVar('MetGreenMagianMog', 1) - end + xi.magian.magianOnEventFinish(player, csid, option, npc) end return entity