From e38a97cc2121b0f1f1312790c1d7cfa6e5a79d15 Mon Sep 17 00:00:00 2001 From: Critical <48370698+CriticalXI@users.noreply.github.com> Date: Sat, 31 Jan 2026 17:05:22 -0700 Subject: [PATCH] [lua] Beastmaster job util cleanup --- scripts/actions/abilities/bestial_loyalty.lua | 4 +- scripts/actions/abilities/call_beast.lua | 4 +- scripts/actions/abilities/charm.lua | 4 +- scripts/actions/abilities/familiar.lua | 6 +- scripts/actions/abilities/feral_howl.lua | 59 +- scripts/actions/abilities/fight.lua | 4 +- scripts/actions/abilities/gauge.lua | 4 +- scripts/actions/abilities/heel.lua | 4 +- scripts/actions/abilities/killer_instinct.lua | 6 +- scripts/actions/abilities/leave.lua | 4 +- scripts/actions/abilities/reward.lua | 4 +- scripts/actions/abilities/run_wild.lua | 4 +- scripts/actions/abilities/sic.lua | 26 +- scripts/actions/abilities/snarl.lua | 4 +- scripts/actions/abilities/spur.lua | 4 +- scripts/actions/abilities/stay.lua | 4 +- scripts/actions/abilities/tame.lua | 4 +- scripts/actions/abilities/unleash.lua | 4 +- scripts/globals/job_utils/beastmaster.lua | 724 ++++++++++-------- 19 files changed, 425 insertions(+), 452 deletions(-) diff --git a/scripts/actions/abilities/bestial_loyalty.lua b/scripts/actions/abilities/bestial_loyalty.lua index 63678c47e95..09efaa65b2c 100644 --- a/scripts/actions/abilities/bestial_loyalty.lua +++ b/scripts/actions/abilities/bestial_loyalty.lua @@ -9,11 +9,11 @@ local abilityObject = {} abilityObject.onAbilityCheck = function(player, target, ability) - return xi.job_utils.beastmaster.onAbilityCheckJug(player, target, ability) + return xi.job_utils.beastmaster.checkBestialLoyalty(player, target, ability) end abilityObject.onUseAbility = function(player, target, ability) - return xi.job_utils.beastmaster.onUseAbilityJug(player, target, ability) + return xi.job_utils.beastmaster.useBestialLoyalty(player, target, ability) end return abilityObject diff --git a/scripts/actions/abilities/call_beast.lua b/scripts/actions/abilities/call_beast.lua index 6db1ac8ac7a..b5470439315 100644 --- a/scripts/actions/abilities/call_beast.lua +++ b/scripts/actions/abilities/call_beast.lua @@ -9,11 +9,11 @@ local abilityObject = {} abilityObject.onAbilityCheck = function(player, target, ability) - return xi.job_utils.beastmaster.onAbilityCheckJug(player, target, ability) + return xi.job_utils.beastmaster.checkCallBeast(player, target, ability) end abilityObject.onUseAbility = function(player, target, ability) - return xi.job_utils.beastmaster.onUseAbilityJug(player, target, ability) + return xi.job_utils.beastmaster.useCallBeast(player, target, ability) end return abilityObject diff --git a/scripts/actions/abilities/charm.lua b/scripts/actions/abilities/charm.lua index 40958a030ac..b84ee5ec7a6 100644 --- a/scripts/actions/abilities/charm.lua +++ b/scripts/actions/abilities/charm.lua @@ -9,11 +9,11 @@ local abilityObject = {} abilityObject.onAbilityCheck = function(player, target, ability) - return xi.job_utils.beastmaster.onAbilityCheckCharm(player, target, ability) + return xi.job_utils.beastmaster.checkCharm(player, target, ability) end abilityObject.onUseAbility = function(player, target, ability) - return xi.job_utils.beastmaster.onUseAbilityCharm(player, target, ability) + return xi.job_utils.beastmaster.useCharm(player, target, ability) end return abilityObject diff --git a/scripts/actions/abilities/familiar.lua b/scripts/actions/abilities/familiar.lua index 1f00c76f1d5..880126c6ee0 100644 --- a/scripts/actions/abilities/familiar.lua +++ b/scripts/actions/abilities/familiar.lua @@ -9,11 +9,11 @@ local abilityObject = {} abilityObject.onAbilityCheck = function(player, target, ability) - return xi.job_utils.beastmaster.onAbilityCheckFamiliar(player, target, ability) + return xi.job_utils.beastmaster.checkFamiliar(player, target, ability) end -abilityObject.onUseAbility = function(player, target, ability) - return xi.job_utils.beastmaster.onUseAbilityFamiliar(player, target, ability) +abilityObject.onUseAbility = function(player, target, ability, action) + return xi.job_utils.beastmaster.useFamiliar(player, target, ability, action) end return abilityObject diff --git a/scripts/actions/abilities/feral_howl.lua b/scripts/actions/abilities/feral_howl.lua index 8ce4c43a3e5..b4f97b94a49 100644 --- a/scripts/actions/abilities/feral_howl.lua +++ b/scripts/actions/abilities/feral_howl.lua @@ -9,64 +9,11 @@ local abilityObject = {} abilityObject.onAbilityCheck = function(player, target, ability) - return 0, 0 + return xi.job_utils.beastmaster.checkFeralHowl(player, target, ability) end -abilityObject.onUseAbility = function(player, target, ability) - local modAcc = player:getMerit(xi.merit.FERAL_HOWL) - local feralHowlMod = player:getMod(xi.mod.FERAL_HOWL_DURATION) - local duration = 10 - - if - target:hasStatusEffect(xi.effect.TERROR) or - target:hasStatusEffect(xi.effect.STUN) - then - -- effect already on, or target stunned, do nothing - -- reserved for miss based on target already having stun or terror effect active - else - -- Calculate duration. - if feralHowlMod >= 1 then - -- http://wiki.ffxiclopedia.org/wiki/Monster_Jackcoat_(Augmented)_%2B2 - -- add 20% duration per merit level if wearing Augmented Monster Jackcoat +2 - duration = duration + (duration * modAcc * 0.04) -- modAcc returns intervals of 5. (0.2 / 5 = 0.04) - end - end - - -- Leaving potency at 1 since I am not sure it applies with this ability. No known way to increase resistance - local potency = 1 - - -- Grabbing variables for terror accuracy. Unknown if ability is stat based. Using Beastmaster's level versus Target's level - local pLvl = player:getMainLvl() - local mLvl = target:getMainLvl() - - -- Checking level difference between the target and the BST - local dLvl = mLvl - pLvl - - -- Determining what level of resistance the target will have to the ability - local resist = 1 -- default level difference to 1 if mob is equal to the Beastmaster's level or less. - dLvl = (10 * dLvl) - modAcc -- merits increase accuracy by 5% per level - - if dLvl > 0 then - resist = math.random(1, (dLvl + 100)) -- calculate chance of missing based on number of levels mob is higher than you. Target gets 10% resist per level over BST - end - - -- Adjusting duration based on resistance. - if resist >= 20 then - if resist / 10 >= duration then - duration = duration - math.random(1, (duration - 2)) - else - duration = duration - math.random(1, (resist / 10)) - end - end - - -- execute ability based off of resistance value space reserved for resist message - if resist <= 90 then -- still experimental. not exactly sure how to calculate hit % - target:addStatusEffect(xi.effect.TERROR, potency, 0, duration) - else - -- reserved for text related to resist - end - - return xi.effect.TERROR +abilityObject.onUseAbility = function(player, target, ability, action) + return xi.job_utils.beastmaster.useFeralHowl(player, target, ability, action) end return abilityObject diff --git a/scripts/actions/abilities/fight.lua b/scripts/actions/abilities/fight.lua index 80af8dd443a..4702b7fa346 100644 --- a/scripts/actions/abilities/fight.lua +++ b/scripts/actions/abilities/fight.lua @@ -9,11 +9,11 @@ local abilityObject = {} abilityObject.onAbilityCheck = function(player, target, ability) - return xi.job_utils.beastmaster.onAbilityCheckFight(player, target, ability) + return xi.job_utils.beastmaster.checkFight(player, target, ability) end abilityObject.onUseAbility = function(player, target, ability) - return xi.job_utils.beastmaster.onUseAbilityFight(player, target, ability) + return xi.job_utils.beastmaster.useFight(player, target, ability) end return abilityObject diff --git a/scripts/actions/abilities/gauge.lua b/scripts/actions/abilities/gauge.lua index e5a1d8b7f41..71b5f01ca06 100644 --- a/scripts/actions/abilities/gauge.lua +++ b/scripts/actions/abilities/gauge.lua @@ -9,11 +9,11 @@ local abilityObject = {} abilityObject.onAbilityCheck = function(player, target, ability) - return xi.job_utils.beastmaster.onAbilityCheckGauge(player, target, ability) + return xi.job_utils.beastmaster.checkGauge(player, target, ability) end abilityObject.onUseAbility = function(player, target, ability) - return xi.job_utils.beastmaster.onUseAbilityGauge(player, target, ability) + return xi.job_utils.beastmaster.useGauge(player, target, ability) end return abilityObject diff --git a/scripts/actions/abilities/heel.lua b/scripts/actions/abilities/heel.lua index 775320a84fc..4cd904b5e79 100644 --- a/scripts/actions/abilities/heel.lua +++ b/scripts/actions/abilities/heel.lua @@ -9,11 +9,11 @@ local abilityObject = {} abilityObject.onAbilityCheck = function(player, target, ability) - return xi.job_utils.beastmaster.onAbilityCheckNilPet(player, target, ability) + return xi.job_utils.beastmaster.checkPetCommand(player, target, ability) end abilityObject.onUseAbility = function(player, target, ability) - return xi.job_utils.beastmaster.onUseAbilityHeel(player, target, ability) + return xi.job_utils.beastmaster.useHeel(player, target, ability) end return abilityObject diff --git a/scripts/actions/abilities/killer_instinct.lua b/scripts/actions/abilities/killer_instinct.lua index 3c2267be6e3..f73c3adc824 100644 --- a/scripts/actions/abilities/killer_instinct.lua +++ b/scripts/actions/abilities/killer_instinct.lua @@ -8,11 +8,11 @@ local abilityObject = {} abilityObject.onAbilityCheck = function(player, target, ability) - return xi.job_utils.beastmaster.onAbilityCheckKillerInstinct(player, target, ability) + return xi.job_utils.beastmaster.checkKillerInstinct(player, target, ability) end -abilityObject.onUseAbility = function(player, target, ability) - return xi.job_utils.beastmaster.onUseAbilityKillerInstinct(player, target, ability) +abilityObject.onUseAbility = function(player, target, ability, action) + return xi.job_utils.beastmaster.useKillerInstinct(player, target, ability, action) end return abilityObject diff --git a/scripts/actions/abilities/leave.lua b/scripts/actions/abilities/leave.lua index cf159d3b879..15a084c8e4f 100644 --- a/scripts/actions/abilities/leave.lua +++ b/scripts/actions/abilities/leave.lua @@ -8,11 +8,11 @@ local abilityObject = {} abilityObject.onAbilityCheck = function(player, target, ability) - return xi.job_utils.beastmaster.onAbilityCheckNilPet(player, target, ability) + return xi.job_utils.beastmaster.checkPetCommand(player, target, ability) end abilityObject.onUseAbility = function(player, target, ability) - return xi.job_utils.beastmaster.onUseAbilityLeave(player, target, ability) + return xi.job_utils.beastmaster.useLeave(player, target, ability) end return abilityObject diff --git a/scripts/actions/abilities/reward.lua b/scripts/actions/abilities/reward.lua index 1fe26040f54..c15b40f188e 100644 --- a/scripts/actions/abilities/reward.lua +++ b/scripts/actions/abilities/reward.lua @@ -9,11 +9,11 @@ local abilityObject = {} abilityObject.onAbilityCheck = function(player, target, ability) - return xi.job_utils.beastmaster.onAbilityCheckReward(player, target, ability) + return xi.job_utils.beastmaster.checkReward(player, target, ability) end abilityObject.onUseAbility = function(player, target, ability, action) - return xi.job_utils.beastmaster.onUseAbilityReward(player, target, ability) + return xi.job_utils.beastmaster.useReward(player, target, ability) end return abilityObject diff --git a/scripts/actions/abilities/run_wild.lua b/scripts/actions/abilities/run_wild.lua index f6aa1716a64..8d0624c4138 100644 --- a/scripts/actions/abilities/run_wild.lua +++ b/scripts/actions/abilities/run_wild.lua @@ -11,11 +11,11 @@ local abilityObject = {} abilityObject.onAbilityCheck = function(player, target, ability) -- same requirements as snarl: pet exists and is attacking a target - return xi.job_utils.beastmaster.onAbilityCheckSnarl(player, target, ability) + return xi.job_utils.beastmaster.checkSnarl(player, target, ability) end abilityObject.onUseAbility = function(player, target, ability, action) - return xi.job_utils.beastmaster.onUseAbilityRunWild(player, target, ability, action) + return xi.job_utils.beastmaster.useRunWild(player, target, ability, action) end return abilityObject diff --git a/scripts/actions/abilities/sic.lua b/scripts/actions/abilities/sic.lua index 353a31d3d89..f29cc3332c9 100644 --- a/scripts/actions/abilities/sic.lua +++ b/scripts/actions/abilities/sic.lua @@ -9,33 +9,11 @@ local abilityObject = {} abilityObject.onAbilityCheck = function(player, target, ability) - if player:getPet() == nil then - return xi.msg.basic.REQUIRES_A_PET, 0 - else - if player:getPet():getHP() == 0 then - return xi.msg.basic.UNABLE_TO_USE_JA, 0 - elseif player:getPet():getTarget() == nil then - return xi.msg.basic.PET_CANNOT_DO_ACTION, 0 - elseif not player:getPet():hasTPMoves() then - return xi.msg.basic.UNABLE_TO_USE_JA, 0 - else - return 0, 0 - end - end + return xi.job_utils.beastmaster.checkSic(player, target, ability) end abilityObject.onUseAbility = function(player, target, ability) - local function doSic(mob) - if mob:getTP() >= 1000 then - mob:useMobAbility() - elseif mob:hasSpellList() then - mob:castSpell() - else - mob:queue(0, doSic) - end - end - - player:getPet():queue(0, doSic) + xi.job_utils.beastmaster.useSic(player, target, ability) end return abilityObject diff --git a/scripts/actions/abilities/snarl.lua b/scripts/actions/abilities/snarl.lua index 401e8413a57..77122315994 100644 --- a/scripts/actions/abilities/snarl.lua +++ b/scripts/actions/abilities/snarl.lua @@ -9,11 +9,11 @@ local abilityObject = {} abilityObject.onAbilityCheck = function(player, target, ability) - return xi.job_utils.beastmaster.onAbilityCheckSnarl(player, target, ability) + return xi.job_utils.beastmaster.checkSnarl(player, target, ability) end abilityObject.onUseAbility = function(player, target, ability) - return xi.job_utils.beastmaster.onUseAbilitySnarl(player, target, ability) + return xi.job_utils.beastmaster.useSnarl(player, target, ability) end return abilityObject diff --git a/scripts/actions/abilities/spur.lua b/scripts/actions/abilities/spur.lua index 0be28b40f73..e3aa5650625 100644 --- a/scripts/actions/abilities/spur.lua +++ b/scripts/actions/abilities/spur.lua @@ -10,11 +10,11 @@ local abilityObject = {} abilityObject.onAbilityCheck = function(player, target, ability) -- same requirements as snarl: pet exists and is attacking a target - return xi.job_utils.beastmaster.onAbilityCheckSnarl(player, target, ability) + return xi.job_utils.beastmaster.checkSnarl(player, target, ability) end abilityObject.onUseAbility = function(player, target, ability, action) - return xi.job_utils.beastmaster.onUseAbilitySpur(player) + return xi.job_utils.beastmaster.useSpur(player) end return abilityObject diff --git a/scripts/actions/abilities/stay.lua b/scripts/actions/abilities/stay.lua index dfd6514e338..61a3bdd54e0 100644 --- a/scripts/actions/abilities/stay.lua +++ b/scripts/actions/abilities/stay.lua @@ -9,11 +9,11 @@ local abilityObject = {} abilityObject.onAbilityCheck = function(player, target, ability) - return xi.job_utils.beastmaster.onAbilityCheckNilPet(player, target, ability) + return xi.job_utils.beastmaster.checkPetCommand(player, target, ability) end abilityObject.onUseAbility = function(player, target, ability, action) - return xi.job_utils.beastmaster.onUseAbilityStay(player, target, ability) + return xi.job_utils.beastmaster.useStay(player, target, ability) end return abilityObject diff --git a/scripts/actions/abilities/tame.lua b/scripts/actions/abilities/tame.lua index 2f901a0d142..e198ed9c266 100644 --- a/scripts/actions/abilities/tame.lua +++ b/scripts/actions/abilities/tame.lua @@ -9,11 +9,11 @@ local abilityObject = {} abilityObject.onAbilityCheck = function(player, target, ability) - return xi.job_utils.beastmaster.onAbilityCheckTame(player, target, ability) + return xi.job_utils.beastmaster.checkTame(player, target, ability) end abilityObject.onUseAbility = function(player, target, ability) - return xi.job_utils.beastmaster.onUseAbilityTame(player, target, ability) + return xi.job_utils.beastmaster.useTame(player, target, ability) end return abilityObject diff --git a/scripts/actions/abilities/unleash.lua b/scripts/actions/abilities/unleash.lua index 039f22251c1..0a5289cc30e 100644 --- a/scripts/actions/abilities/unleash.lua +++ b/scripts/actions/abilities/unleash.lua @@ -9,11 +9,11 @@ local abilityObject = {} abilityObject.onAbilityCheck = function(player, target, ability) - return xi.job_utils.beastmaster.onAbilityCheckUnleash(player, target, ability) + return xi.job_utils.beastmaster.checkUnleash(player, target, ability) end abilityObject.onUseAbility = function(player, target, ability) - return xi.job_utils.beastmaster.onUseAbilityUnleash(player, target, ability) + return xi.job_utils.beastmaster.useUnleash(player, target, ability) end return abilityObject diff --git a/scripts/globals/job_utils/beastmaster.lua b/scripts/globals/job_utils/beastmaster.lua index 412fcedee39..ecf77a59394 100644 --- a/scripts/globals/job_utils/beastmaster.lua +++ b/scripts/globals/job_utils/beastmaster.lua @@ -7,11 +7,59 @@ require('scripts/globals/jobpoints') xi = xi or {} xi.job_utils = xi.job_utils or {} xi.job_utils.beastmaster = xi.job_utils.beastmaster or {} + ----------------------------------- +-- Helper Tables ----------------------------------- --- Jug Levels + +xi.job_utils.beastmaster.petFoodData = +{ + [xi.item.PET_FOOD_ALPHA_BISCUIT] = { minHealing = 50, regen = 1, mndMult = 2, mndThreshold = 10 }, + [xi.item.PET_FOOD_BETA_BISCUIT] = { minHealing = 180, regen = 3, mndMult = 1, mndThreshold = 33 }, + [xi.item.PET_FOOD_GAMMA_BISCUIT] = { minHealing = 300, regen = 5, mndMult = 1, mndThreshold = 35 }, -- TO BE VERIFIED. + [xi.item.PET_FOOD_DELTA_BISCUIT] = { minHealing = 530, regen = 8, mndMult = 2, mndThreshold = 40 }, -- TO BE VERIFIED. + [xi.item.PET_FOOD_EPSILON_BISCUIT] = { minHealing = 750, regen = 11, mndMult = 2, mndThreshold = 45 }, + [xi.item.PET_FOOD_ZETA_BISCUIT] = { minHealing = 900, regen = 14, mndMult = 3, mndThreshold = 45 }, + [xi.item.PET_FOOD_ETA_BISCUIT] = { minHealing = 1200, regen = 17, mndMult = 4, mndThreshold = 50 }, + [xi.item.PET_FOOD_THETA_BISCUIT] = { minHealing = 1600, regen = 20, mndMult = 4, mndThreshold = 55 }, +} + ----------------------------------- -xi = xi or {} +-- Helper Functions +----------------------------------- + +local function getCharmDuration(charmer, target) + local charmDuration = 0 + + -- Calculate base duration (see https://www.bg-wiki.com/ffxi/Charm_Duration) and dLvl + local baseCharmDuration = math.floor(1.25 * charmer:getStat(xi.mod.CHR) + 150) + local dLvl = charmer:getMainLvl() - target:getMainLvl() + + -- Default multiplier for dLvl -6 or lower + local dLvlCharmMult = 1 / 24 + + if dLvl >= -6 and dLvl < 9 then + -- Quintic least squares fitting of duration multiplier as function of dLvl (r^2 > 0.999) + -- Fitting on values from table at https://www.bg-wiki.com/ffxi/Charm_Duration + -- See fitting at https://mycurvefit.com/index.html?action=openshare&id=358a5d99-4499-4a6a-bbfe-0a667739335c + dLvlCharmMult = 0.9997336 + 0.3652882 * dLvl + 0.02097742 * dLvl ^ 2 + - 0.004106429 * dLvl ^ 3 + 0.000007231037 * dLvl ^ 4 + + 0.00005102634 * dLvl ^ 5 + -- Caps at dLvl > 9 + elseif dLvl >= 9 then + dLvlCharmMult = 6 + end + + -- Apply the dLvl multiplier + charmDuration = baseCharmDuration * dLvlCharmMult + + -- Apply charm duration extension from gear + local charmTimeMod = charmer:getMod(xi.mod.CHARM_TIME) + local extraDurationFromMod = charmDuration * (charmTimeMod * 0.5 / 10) -- Assumes 5% per charmTimeMod + charmDuration = charmDuration + extraDurationFromMod + + return math.floor(charmDuration) +end local getValidJugPetID = function(player) -- jug pet reagents are @@ -38,8 +86,106 @@ local getValidJugPetID = function(player) return nil end --- On Ability Check Jug (Call Beast and Bestial Loyalty) -xi.job_utils.beastmaster.onAbilityCheckJug = function(player, target, ability) +xi.job_utils.beastmaster.getCharmChance = function(charmer, target, includeMods) + if + not charmer or -- Invalid charmer + not target or -- Invalid target + not charmer:isPC() or -- Charmer not a player + not target:isMob() or -- Target not a mob + target:getMobMod(xi.mobMod.CHARMABLE) == 0 or -- Not charmable + target:getMaster() ~= nil -- Someone else's pet + then + return 0 + end + + -- Use the players BST level (even if subjob) for charm chance calc + local charmerJobLevel = charmer:getJobLevel(xi.job.BST) + local targetLevel = target:getMainLvl() + local charmres = target:getMod(xi.mod.CHARMRES) + local charmChance = 50 - charmres + -- dLvl only applies when player lvl < mob lvl + -- and varies for different target levels + if charmerJobLevel < targetLevel then + if targetLevel >= 71 then + charmChance = charmChance - 10 * (targetLevel - charmerJobLevel) + elseif targetLevel >= 51 then + charmChance = charmChance - 5 * (targetLevel - charmerJobLevel) + else + charmChance = charmChance - 3 * (targetLevel - charmerJobLevel) + end + end + + -- Another multiplier determined by target light res rank + -- as charm is a light based ability + local rank = target:getMod(xi.mod.LIGHT_RES_RANK) + if rank <= -3 then + charmChance = charmChance * 1.5 + elseif rank <= -2 then + charmChance = charmChance * 1.4 + elseif rank <= -1 then + charmChance = charmChance * 1.2 + elseif rank <= 0 then + charmChance = charmChance + else + charmChance = charmChance / 2 + end + + -- Need a includeMods param because staves (which give CHARM_CHANCE) are not taken into account for Gauge + if includeMods then + charmChance = charmChance + charmer:getMod(xi.mod.CHARM_CHANCE) + end + + -- apply the dCHR component + local dCHR = charmer:getStat(xi.mod.CHR) - target:getStat(xi.mod.CHR) + charmChance = charmChance + dCHR + + return utils.clamp(charmChance, 0, 95) +end + +xi.job_utils.beastmaster.attemptCharm = function(charmer, target) + if + not charmer or -- Invalid charmer + not target or -- Invalid target + not charmer:isPC() or -- Charmer not a player + not (target:isMob() or -- Target not a mob or PC + target:isPC()) + then + return xi.msg.basic.JA_MISS + elseif -- Not charmable so apply bind + target:getMobMod(xi.mobMod.CHARMABLE) == 0 or -- Target is not charmable + target:isPC() or -- Target is a PC (ballista) + target:getMaster() -- Target already has a master + then + local resist = applyResistanceAddEffect(charmer, target, xi.element.ICE, 0) + if not target:hasStatusEffect(xi.effect.BIND) and resist >= 0.5 then + target:addStatusEffect(xi.effect.BIND, 1, 0, math.random(1, 5)) + return xi.msg.basic.JA_ENFEEB_IS + else + return xi.msg.basic.JA_MISS + end + end + + -- Calculate charm chance + local chance = xi.job_utils.beastmaster.getCharmChance(charmer, target, true) + + -- If successful then calculate duration and charm + if chance > math.random(1, 100) then + local duration = getCharmDuration(charmer, target) + + if duration > 0 then + charmer:charm(target, duration) + return xi.msg.basic.CHARM_SUCCESS + end + end + + return xi.msg.basic.CHARM_FAIL +end + +----------------------------------- +-- Ability Check Functions +----------------------------------- + +xi.job_utils.beastmaster.checkCallBeast = function(player, target, ability) local petId = getValidJugPetID(player) if player:getPet() ~= nil then @@ -53,29 +199,21 @@ xi.job_utils.beastmaster.onAbilityCheckJug = function(player, target, ability) return 0, 0 end --- On Ability Use Jug (Call Beast and Bestial Loyalty) -xi.job_utils.beastmaster.onUseAbilityJug = function(player, target, ability) +xi.job_utils.beastmaster.checkBestialLoyalty = function(player, target, ability) local petId = getValidJugPetID(player) - if not petId then - -- should not be possible but just in case - return - end - - xi.pet.spawnPet(player, petId) - if ability:getID() == xi.jobAbility.CALL_BEAST then - player:removeAmmo(1) + if player:getPet() ~= nil then + return xi.msg.basic.ALREADY_HAS_A_PET, 0 + elseif not petId then + return xi.msg.basic.NO_JUG_PET_ITEM, 0 + elseif not player:canUseMisc(xi.zoneMisc.PET) then + return xi.msg.basic.CANT_BE_USED_IN_AREA, 0 end - -- Briefly put the recastId for READY/SIC (102) into a recast state to - -- toggle charges accumulating. 102 is the shared recast id for all jug - -- pet abilities and for SIC when using a charmed mob. - -- see sql/abilities_charges and sql_abilities - player:addRecast(xi.recast.ABILITY, 102, 1) + return 0, 0 end --- On Ability Check Familiar -xi.job_utils.beastmaster.onAbilityCheckFamiliar = function(player, target, ability) +xi.job_utils.beastmaster.checkFamiliar = function(player, target, ability) local pet = player:getPet() if not pet then @@ -92,31 +230,186 @@ xi.job_utils.beastmaster.onAbilityCheckFamiliar = function(player, target, abili return 0, 0 end --- On Ability Use Familiar -xi.job_utils.beastmaster.onUseAbilityFamiliar = function(player, target, ability) - xi.pet.applyFamiliarBuffs(player, player:getPet()) +xi.job_utils.beastmaster.checkCharm = function(player, target, ability) + if player:getPet() ~= nil then + return xi.msg.basic.ALREADY_HAS_A_PET, 0 + elseif + target:getMaster() ~= nil and + target:getMaster():isPC() + then + return xi.msg.basic.THAT_SOMEONES_PET, 0 + end - ability:setMsg(xi.msg.basic.FAMILIAR_PC) + return 0, 0 +end - return 0 +xi.job_utils.beastmaster.checkGauge = function(player, target, ability) + if player:getPet() ~= nil then + return xi.msg.basic.ALREADY_HAS_A_PET, 0 + end + + return 0, 0 end --- On Ability Check Charm -xi.job_utils.beastmaster.onAbilityCheckCharm = function(player, target, ability) +xi.job_utils.beastmaster.checkTame = function(player, target, ability) if player:getPet() ~= nil then return xi.msg.basic.ALREADY_HAS_A_PET, 0 + end + + return 0, 0 +end + +xi.job_utils.beastmaster.checkReward = function(player, target, ability) + local pet = player:getPet() + + if not pet then + return xi.msg.basic.REQUIRES_A_PET, 0 --TODO this currently will not hit this function. Returns You cannot attack that target. Targetfind.cpp line 564 elseif - target:getMaster() ~= nil and - target:getMaster():isPC() + not player:hasJugPet() and + pet:getObjType() ~= xi.objType.MOB then - return xi.msg.basic.THAT_SOMEONES_PET, 0 + return xi.msg.basic.NO_EFFECT_ON_PET, 0 + else + local id = player:getEquipID(xi.slot.AMMO) + if + id >= xi.item.PET_FOOD_ALPHA_BISCUIT and + id <= xi.item.PET_FOOD_THETA_BISCUIT + then + return 0, 0 + else + return xi.msg.basic.MUST_HAVE_FOOD, 0 + end end +end + +xi.job_utils.beastmaster.checkUnleash = function(player, target, ability) + ability:setRecast(math.max(0, ability:getRecast() - player:getMod(xi.mod.ONE_HOUR_RECAST) * 60)) + + return 0, 0 +end + +-- On Ability Check For Leave, Heel and Stay. +xi.job_utils.beastmaster.checkPetCommand = function(player, target, ability) + local pet = player:getPet() + + if + player:hasJugPet() or + pet:getObjType() == xi.objType.MOB + then + if player:getPet() == nil then + return xi.msg.basic.REQUIRES_A_PET, 0 + end + end + + return 0, 0 +end + +xi.job_utils.beastmaster.checkFight = function(player, target, ability) + if player:getPet() == nil then + return xi.msg.basic.REQUIRES_A_PET, 0 + elseif + target:getID() == player:getPet():getID() or + (target:getMaster() ~= nil and target:getMaster():isPC()) + then + return xi.msg.basic.CANNOT_ATTACK_TARGET, 0 + end + + return 0, 0 +end + +xi.job_utils.beastmaster.checkKillerInstinct = function(player, target, ability) + local pet = player:getPet() + + if + pet == nil or -- No pet currently spawned + (not player:hasJugPet() and pet:getObjType() ~= xi.objType.MOB) -- The pet spawned is not a jug pet or charmed mob + then + return xi.msg.basic.REQUIRES_A_PET, 0 + end + + return 0, 0 +end + +xi.job_utils.beastmaster.checkSnarl = function(player, target, ability) + if player:getPet() == nil then + return xi.msg.basic.REQUIRES_A_PET, 0 + else + if + player:getPet():getTarget() ~= nil and + player:hasJugPet() + then + return 0, 0 + else + return xi.msg.basic.PET_CANNOT_DO_ACTION, 0 + end + end +end + +xi.job_utils.beastmaster.checkSic = function(player, target, ability) + local pet = player:getPet() + + if pet == nil then + return xi.msg.basic.REQUIRES_A_PET, 0 + elseif pet:getHP() == 0 then + return xi.msg.basic.UNABLE_TO_USE_JA, 0 + elseif pet:getTarget() == nil then + return xi.msg.basic.PET_CANNOT_DO_ACTION, 0 + elseif not pet:hasTPMoves() then + return xi.msg.basic.UNABLE_TO_USE_JA, 0 + end + + return 0, 0 +end + +xi.job_utils.beastmaster.checkFeralHowl = function(player, target, ability) + return 0, 0 +end + +----------------------------------- +-- Ability Use Functions +----------------------------------- + +xi.job_utils.beastmaster.useCallBeast = function(player, target, ability) + local petId = getValidJugPetID(player) + if not petId then + return + end + + xi.pet.spawnPet(player, petId) + player:removeAmmo(1) + + -- Briefly put the recastId for READY/SIC (102) into a recast state to + -- toggle charges accumulating. 102 is the shared recast id for all jug + -- pet abilities and for SIC when using a charmed mob. + -- see sql/abilities_charges and sql_abilities + player:addRecast(xi.recast.ABILITY, 102, 1) +end + +xi.job_utils.beastmaster.useBestialLoyalty = function(player, target, ability) + local petId = getValidJugPetID(player) + if not petId then + return + end + + xi.pet.spawnPet(player, petId) + + player:addRecast(xi.recast.ABILITY, 102, 1) +end + +xi.job_utils.beastmaster.useFamiliar = function(player, target, ability, action) + local pet = player:getPet() - return 0, 0 + xi.pet.applyFamiliarBuffs(player, pet) + + -- Redirect animation from player to pet + action:ID(player:getID(), pet:getID()) + + ability:setMsg(xi.msg.basic.FAMILIAR_PC) + + return 0 end --- On Ability Use Charm -xi.job_utils.beastmaster.onUseAbilityCharm = function(player, target, ability) +xi.job_utils.beastmaster.useCharm = function(player, target, ability) local isTamed = false if player:getLocalVar('Tamed_Mob') == target:getID() then @@ -139,17 +432,7 @@ xi.job_utils.beastmaster.onUseAbilityCharm = function(player, target, ability) end end --- On Ability Check Gauge -xi.job_utils.beastmaster.onAbilityCheckGauge = function(player, target, ability) - if player:getPet() ~= nil then - return xi.msg.basic.ALREADY_HAS_A_PET, 0 - end - - return 0, 0 -end - --- On Ability Use Gauge -xi.job_utils.beastmaster.onUseAbilityGauge = function(player, target, ability) +xi.job_utils.beastmaster.useGauge = function(player, target, ability) local charmChance = xi.job_utils.beastmaster.getCharmChance(player, target, false) if charmChance >= 75 then @@ -165,14 +448,8 @@ xi.job_utils.beastmaster.onUseAbilityGauge = function(player, target, ability) end end --- On Ability Check Tame -xi.job_utils.beastmaster.onAbilityCheckTame = function(player, target, ability) - return 0, 0 -end - --- On Ability Use Tame -- **NOTE** Use of Battlemod may remove message -xi.job_utils.beastmaster.onUseAbilityTame = function(player, target, ability) +xi.job_utils.beastmaster.useTame = function(player, target, ability) if player:getPet() ~= nil then ability:setMsg(xi.msg.basic.JA_NO_EFFECT) target:addEnmity(player, 1, 0) @@ -223,32 +500,7 @@ xi.job_utils.beastmaster.onUseAbilityTame = function(player, target, ability) end end --- On Ability Check Reward -xi.job_utils.beastmaster.onAbilityCheckReward = function(player, target, ability) - local pet = player:getPet() - - if not pet then - return xi.msg.basic.REQUIRES_A_PET, 0 --TODO this currently will not hit this function. Returns You cannot attack that target. Targetfind.cpp line 564 - elseif - not player:hasJugPet() and - pet:getObjType() ~= xi.objType.MOB - then - return xi.msg.basic.NO_EFFECT_ON_PET, 0 - else - local id = player:getEquipID(xi.slot.AMMO) - if - id >= xi.item.PET_FOOD_ALPHA_BISCUIT and - id <= xi.item.PET_FOOD_THETA_BISCUIT - then - return 0, 0 - else - return xi.msg.basic.MUST_HAVE_FOOD, 0 - end - end -end - --- On Ability Use Reward -xi.job_utils.beastmaster.onUseAbilityReward = function(player, target, ability) +xi.job_utils.beastmaster.useReward = function(player, target, ability) -- 1st need to get the pet food is equipped in the range slot. local rangeObj = player:getEquipID(xi.slot.AMMO) local minimumHealing = 0 @@ -265,57 +517,12 @@ xi.job_utils.beastmaster.onUseAbilityReward = function(player, target, ability) -- Please note that I used this as base for the calculations: -- http://wiki.ffxiclopedia.org/wiki/Reward - -- TODO: Create lookup table for these switches - switch(rangeObj):caseof - { - [xi.item.PET_FOOD_ALPHA_BISCUIT] = function() -- pet food alpha biscuit - minimumHealing = 50 - regenAmount = 1 - totalHealing = math.floor(minimumHealing + 2 * (playerMnd - 10)) - end, - - [xi.item.PET_FOOD_BETA_BISCUIT] = function() -- pet food beta biscuit - minimumHealing = 180 - regenAmount = 3 - totalHealing = math.floor(minimumHealing + 1 * (playerMnd - 33)) - end, - - [xi.item.PET_FOOD_GAMMA_BISCUIT] = function() -- pet food gamma biscuit - minimumHealing = 300 - regenAmount = 5 - totalHealing = math.floor(minimumHealing + 1 * (playerMnd - 35)) -- TO BE VERIFIED. - end, - - [xi.item.PET_FOOD_DELTA_BISCUIT] = function() -- pet food delta biscuit - minimumHealing = 530 - regenAmount = 8 - totalHealing = math.floor(minimumHealing + 2 * (playerMnd - 40)) -- TO BE VERIFIED. - end, - - [xi.item.PET_FOOD_EPSILON_BISCUIT] = function() -- pet food epsilon biscuit - minimumHealing = 750 - regenAmount = 11 - totalHealing = math.floor(minimumHealing + 2 * (playerMnd - 45)) - end, - - [xi.item.PET_FOOD_ZETA_BISCUIT] = function() -- pet food zeta biscuit - minimumHealing = 900 - regenAmount = 14 - totalHealing = math.floor(minimumHealing + 3 * (playerMnd - 45)) - end, - - [xi.item.PET_FOOD_ETA_BISCUIT] = function() -- pet food eta biscuit - minimumHealing = 1200 - regenAmount = 17 - totalHealing = math.floor(minimumHealing + 4 * (playerMnd - 50)) - end, - - [xi.item.PET_FOOD_THETA_BISCUIT] = function() -- pet food theta biscuit - minimumHealing = 1600 - regenAmount = 20 - totalHealing = math.floor(minimumHealing + 4 * (playerMnd - 55)) - end, - } + local foodData = xi.job_utils.beastmaster.petFoodData[rangeObj] + if foodData then + minimumHealing = foodData.minHealing + regenAmount = foodData.regen + totalHealing = math.floor(minimumHealing + foodData.mndMult * (playerMnd - foodData.mndThreshold)) + end -- Now calculating the bonus based on gear. switch(player:getEquipID(xi.slot.BODY)):caseof @@ -384,38 +591,13 @@ xi.job_utils.beastmaster.onUseAbilityReward = function(player, target, ability) return totalHealing end --- On Ability Check Unleash -xi.job_utils.beastmaster.onAbilityCheckUnleash = function(player, target, ability) - ability:setRecast(math.max(0, ability:getRecast() - player:getMod(xi.mod.ONE_HOUR_RECAST) * 60)) - - return 0, 0 -end - --- On Ability Use Unleash -xi.job_utils.beastmaster.onUseAbilityUnleash = function(player, target, ability) +xi.job_utils.beastmaster.useUnleash = function(player, target, ability) player:addStatusEffect(xi.effect.UNLEASH, 9, 0, 60) return xi.effect.UNLEASH end --- On Ability Check For Leave, Heel and Stay. -xi.job_utils.beastmaster.onAbilityCheckNilPet = function(player, target, ability) - local pet = player:getPet() - - if - player:hasJugPet() or - pet:getObjType() == xi.objType.MOB - then - if player:getPet() == nil then - return xi.msg.basic.REQUIRES_A_PET, 0 - end - end - - return 0, 0 -end - --- On Ability Use Leave -xi.job_utils.beastmaster.onUseAbilityLeave = function(player, target, ability) +xi.job_utils.beastmaster.useLeave = function(player, target, ability) local pet = target:getPet() if @@ -428,29 +610,25 @@ xi.job_utils.beastmaster.onUseAbilityLeave = function(player, target, ability) target:despawnPet() end --- On Ability Check Snarl -xi.job_utils.beastmaster.onAbilityCheckSnarl = function(player, target, ability) - if player:getPet() == nil then - return xi.msg.basic.REQUIRES_A_PET, 0 - else - if - player:getPet():getTarget() ~= nil and - player:hasJugPet() - then - return 0, 0 +xi.job_utils.beastmaster.useSnarl = function(player, target, ability) + player:transferEnmity(player:getPet(), 99, 11.5) +end + +xi.job_utils.beastmaster.useSic = function(player, target, ability) + local function doSic(mob) + if mob:getTP() >= 1000 then + mob:useMobAbility() + elseif mob:hasSpellList() then + mob:castSpell() else - return xi.msg.basic.PET_CANNOT_DO_ACTION, 0 + mob:queue(0, doSic) end end -end --- On Ability Use Snarl -xi.job_utils.beastmaster.onUseAbilitySnarl = function(player, target, ability) - player:transferEnmity(player:getPet(), 99, 11.5) + player:getPet():queue(0, doSic) end --- On Ability Use Heel -xi.job_utils.beastmaster.onUseAbilityHeel = function(player, target, ability) +xi.job_utils.beastmaster.useHeel = function(player, target, ability) local pet = player:getPet() if pet:hasStatusEffect(xi.effect.HEALING) then @@ -460,8 +638,7 @@ xi.job_utils.beastmaster.onUseAbilityHeel = function(player, target, ability) player:petRetreat() end --- On Ability Use Stay -xi.job_utils.beastmaster.onUseAbilityStay = function(player, target, ability) +xi.job_utils.beastmaster.useStay = function(player, target, ability) local pet = player:getPet() if not pet:hasPreventActionEffect() then @@ -482,22 +659,7 @@ xi.job_utils.beastmaster.onUseAbilityStay = function(player, target, ability) end end --- On Ability Check Fight -xi.job_utils.beastmaster.onAbilityCheckFight = function(player, target, ability) - if player:getPet() == nil then - return xi.msg.basic.REQUIRES_A_PET, 0 - elseif - target:getID() == player:getPet():getID() or - (target:getMaster() ~= nil and target:getMaster():isPC()) - then - return xi.msg.basic.CANNOT_ATTACK_TARGET, 0 - end - - return 0, 0 -end - --- On Ability Use Fight -xi.job_utils.beastmaster.onUseAbilityFight = function(player, target, ability) +xi.job_utils.beastmaster.useFight = function(player, target, ability) local pet = player:getPet() if player:checkDistance(pet) <= 25 then @@ -509,163 +671,19 @@ xi.job_utils.beastmaster.onUseAbilityFight = function(player, target, ability) end end --- On Ability Check Killer Instinct -xi.job_utils.beastmaster.onAbilityCheckKillerInstinct = function(player, target, ability) - local pet = player:getPet() - - if - pet == nil or -- No pet currently spawned - (not player:hasJugPet() and pet:getObjType() ~= xi.objType.MOB) -- The pet spawned is not a jug pet or charmed mob - then - return xi.msg.basic.REQUIRES_A_PET, 0 - end - - return 0, 0 -end - --- On Ability Use Killer Instinct -xi.job_utils.beastmaster.onUseAbilityKillerInstinct = function(player, target, ability) +xi.job_utils.beastmaster.useKillerInstinct = function(player, target, ability, action) -- Notes: Pet ecosystem is assigned to the subPower, then mapped to the correct killer mod in the effect script. local pet = player:getPet() local petEcosystem = pet:getEcosystem() local power = 10 local duration = 180 + (player:getMerit(xi.merit.KILLER_INSTINCT) - 10) - -- TODO: Is there gear/mods that enhance power/duration? target:addStatusEffect(xi.effect.KILLER_INSTINCT, power, 0, duration, 0, petEcosystem) return xi.effect.KILLER_INSTINCT end -local function getCharmDuration(charmer, target) - local charmDuration = 0 - - -- Calculate base duration (see https://www.bg-wiki.com/ffxi/Charm_Duration) and dLvl - local baseCharmDuration = math.floor(1.25 * charmer:getStat(xi.mod.CHR) + 150) - local dLvl = charmer:getMainLvl() - target:getMainLvl() - - -- Default multiplier for dLvl -6 or lower - local dLvlCharmMult = 1 / 24 - - if dLvl >= -6 and dLvl < 9 then - -- Quintic least squares fitting of duration multiplier as function of dLvl (r^2 > 0.999) - -- Fitting on values from table at https://www.bg-wiki.com/ffxi/Charm_Duration - -- See fitting at https://mycurvefit.com/index.html?action=openshare&id=358a5d99-4499-4a6a-bbfe-0a667739335c - dLvlCharmMult = 0.9997336 + 0.3652882 * dLvl + 0.02097742 * dLvl ^ 2 - - 0.004106429 * dLvl ^ 3 + 0.000007231037 * dLvl ^ 4 - + 0.00005102634 * dLvl ^ 5 - -- Caps at dLvl > 9 - elseif dLvl >= 9 then - dLvlCharmMult = 6 - end - - -- Apply the dLvl multiplier - charmDuration = baseCharmDuration * dLvlCharmMult - - -- Apply charm duration extension from gear - local charmTimeMod = charmer:getMod(xi.mod.CHARM_TIME) - local extraDurationFromMod = charmDuration * (charmTimeMod * 0.5 / 10) -- Assumes 5% per charmTimeMod - charmDuration = charmDuration + extraDurationFromMod - - return math.floor(charmDuration) -end - -xi.job_utils.beastmaster.getCharmChance = function(charmer, target, includeMods) - if - not charmer or -- Invalid charmer - not target or -- Invalid target - not charmer:isPC() or -- Charmer not a player - not target:isMob() or -- Target not a mob - target:getMobMod(xi.mobMod.CHARMABLE) == 0 or -- Not charmable - target:getMaster() ~= nil -- Someone else's pet - then - return 0 - end - - -- Use the players BST level (even if subjob) for charm chance calc - local charmerJobLevel = charmer:getJobLevel(xi.job.BST) - local targetLevel = target:getMainLvl() - local charmres = target:getMod(xi.mod.CHARMRES) - local charmChance = 50 - charmres - -- dLvl only applies when player lvl < mob lvl - -- and varies for different target levels - if charmerJobLevel < targetLevel then - if targetLevel >= 71 then - charmChance = charmChance - 10 * (targetLevel - charmerJobLevel) - elseif targetLevel >= 51 then - charmChance = charmChance - 5 * (targetLevel - charmerJobLevel) - else - charmChance = charmChance - 3 * (targetLevel - charmerJobLevel) - end - end - - -- Another multiplier determined by target light res rank - -- as charm is a light based ability - local rank = target:getMod(xi.mod.LIGHT_RES_RANK) - if rank <= -3 then - charmChance = charmChance * 1.5 - elseif rank <= -2 then - charmChance = charmChance * 1.4 - elseif rank <= -1 then - charmChance = charmChance * 1.2 - elseif rank <= 0 then - charmChance = charmChance - else - charmChance = charmChance / 2 - end - - -- Need a includeMods param because staves (which give CHARM_CHANCE) are not taken into account for Gauge - if includeMods then - charmChance = charmChance + charmer:getMod(xi.mod.CHARM_CHANCE) - end - - -- apply the dCHR component - local dCHR = charmer:getStat(xi.mod.CHR) - target:getStat(xi.mod.CHR) - charmChance = charmChance + dCHR - - return utils.clamp(charmChance, 0, 95) -end - -xi.job_utils.beastmaster.attemptCharm = function(charmer, target) - if - not charmer or -- Invalid charmer - not target or -- Invalid target - not charmer:isPC() or -- Charmer not a player - not (target:isMob() or -- Target not a mob or PC - target:isPC()) - then - return xi.msg.basic.JA_MISS - elseif -- Not charmable so apply bind - target:getMobMod(xi.mobMod.CHARMABLE) == 0 or -- Target is not charmable - target:isPC() or -- Target is a PC (ballista) - target:getMaster() -- Target already has a master - then - local resist = applyResistanceAddEffect(charmer, target, xi.element.ICE, 0) - if not target:hasStatusEffect(xi.effect.BIND) and resist >= 0.5 then - target:addStatusEffect(xi.effect.BIND, 1, 0, math.random(1, 5)) - return xi.msg.basic.JA_ENFEEB_IS - else - return xi.msg.basic.JA_MISS - end - end - - -- Calculate charm chance - local chance = xi.job_utils.beastmaster.getCharmChance(charmer, target, true) - - -- If successful then calculate duration and charm - if chance > math.random(1, 100) then - local duration = getCharmDuration(charmer, target) - - if duration > 0 then - charmer:charm(target, duration) - return xi.msg.basic.CHARM_SUCCESS - end - end - - return xi.msg.basic.CHARM_FAIL -end - -xi.job_utils.beastmaster.onUseAbilitySpur = function(player) +xi.job_utils.beastmaster.useSpur = function(player) local power = 20 + player:getMod(xi.mod.ENHANCES_SPUR) -- bonus STORETP local subpower = player:getJobPointLevel(xi.jp.SPUR_EFFECT) * 3 -- bonus attack local pet = player:getPet() @@ -674,7 +692,7 @@ xi.job_utils.beastmaster.onUseAbilitySpur = function(player) end end -xi.job_utils.beastmaster.onUseAbilityRunWild = function(player, target, ability, action) +xi.job_utils.beastmaster.useRunWild = function(player, target, ability, action) -- all but regen are a 25% bonus local power = 25 local pet = player:getPet() @@ -699,3 +717,33 @@ xi.job_utils.beastmaster.onUseAbilityRunWild = function(player, target, ability, return ability:getID() end + +xi.job_utils.beastmaster.useFeralHowl = function(player, target, ability, action) + local modAcc = player:getMerit(xi.merit.FERAL_HOWL) + local feralHowlMod = player:getMod(xi.mod.FERAL_HOWL_DURATION) + local duration = 10 + + -- Calculate duration bonus from gear + if feralHowlMod >= 1 then + -- https://ffxiclopedia.fandom.com/wiki/Monster_Jackcoat_%2B2 + -- Add 1 second duration per merit level if wearing Monster Jackcoat +2 + duration = duration + (modAcc / 5) + end + + if + xi.data.statusEffect.isTargetImmune(target, xi.effect.TERROR, xi.element.DARK) or + xi.data.statusEffect.isTargetResistant(player, target, xi.effect.TERROR) or + xi.data.statusEffect.isEffectNullified(target, xi.effect.TERROR, 0) + then + ability:setMsg(xi.msg.basic.JA_MISS_2) + else + -- modAcc returns 5 per merit level (5, 10, 15, 20, 25), providing 5% accuracy bonus per merit + local resistanceRate = xi.combat.magicHitRate.calculateResistRate(player, target, 0, 0, xi.skillRank.B_MINUS, xi.element.DARK, xi.mod.CHR, xi.effect.TERROR, modAcc) + + if xi.data.statusEffect.isResistRateSuccessfull(xi.effect.TERROR, resistanceRate, 0) then + target:addStatusEffect(xi.effect.TERROR, 1, 0, duration * resistanceRate) + end + end + + return xi.effect.TERROR +end