2022-11-28 13:50:51 +01:00
-----------------------------------
2023-06-20 17:09:33 +02:00
-- Global file for magic based skills magic hit rate.
-----------------------------------
2022-11-28 13:50:51 +01:00
xi = xi or { }
2023-02-28 00:30:41 +01:00
xi.combat = xi.combat or { }
xi.combat . magicHitRate = xi.combat . magicHitRate or { }
2026-07-06 13:32:27 +02:00
-----------------------------------
2022-11-28 13:50:51 +01:00
2026-02-06 03:51:18 +01:00
-----------------------------------
-- Calculate Target Resistance Rank
-----------------------------------
local function calculateTargetResistanceRank ( actor , target , params )
-- Early return: Players don't use resistance ranks.
if target : isPC ( ) then
return 0
end
-- Calculate what modifier to use.
local resistanceRankMod = 0 -- Modifier ID of the resistance rank to use.
if params.effectId > 0 then -- Check if it's an effect.
resistanceRankMod = xi.data . statusEffect.getAssociatedResistanceRankModifier ( params.effectId , params.magicalElement )
end
if resistanceRankMod == 0 then -- If it's an effect and this is 0, try with element.
resistanceRankMod = xi.data . element.getElementalResistanceRankModifier ( params.magicalElement )
end
-- Fetch resistance rank.
local resistanceRank = target : getMod ( resistanceRankMod )
-- Apply possible resistance rank modifications.
if params.effectId > 0 then
resistanceRank = resistanceRank - target : getMod ( xi.data . statusEffect.getAssociatedImmunobreakModifier ( params.effectId ) ) -- Apply immunobreak modification.
end
return utils.clamp ( resistanceRank , - 3 , 11 )
end
2023-09-07 08:18:40 -04:00
-----------------------------------
2023-07-08 11:35:36 +02:00
-- Calculate Actor Magic Accuracy
2023-09-07 08:18:40 -04:00
-----------------------------------
2023-07-08 11:35:36 +02:00
-- Magic Accuracy from spell's skill.
2026-02-06 03:51:18 +01:00
local function magicAccuracyFromSkill ( actor , params )
2023-07-08 11:35:36 +02:00
local magicAcc = 0
2025-01-14 10:26:36 +01:00
-- For known skills.
2026-02-06 03:51:18 +01:00
if params.skillType > 0 then
magicAcc = actor : getSkillLevel ( params.skillType )
2025-01-14 10:26:36 +01:00
2026-02-08 20:11:02 -07:00
-- If a mob is casting something its main/sub jobs cannot (i.e. JoL casting black magic) this is _exceptional_
-- So give them A+ rank base magic accuracy
if magicAcc == 0 and actor : isMob ( ) then
magicAcc = xi.data . skillLevel.getSkillCap ( actor : getMainLvl ( ) , xi.skillRank . A_PLUS )
end
2026-02-06 03:51:18 +01:00
if params.skillType == xi.skill . SINGING then
2025-01-14 10:26:36 +01:00
if actor : isPC ( ) then
2026-07-05 21:58:10 +02:00
-- String instruments sacrifice accuracy and amplify the AoE in exchange.
local rangedSkill = actor : getWeaponSkillType ( xi.slot . RANGED )
if rangedSkill == xi.skill . WIND_INSTRUMENT then
magicAcc = magicAcc + math.floor ( actor : getSkillLevel ( rangedSkill ) / 2 )
2025-01-14 10:26:36 +01:00
end
else
2026-07-05 21:58:10 +02:00
magicAcc = magicAcc + math.floor ( magicAcc / 2 )
2025-01-14 10:26:36 +01:00
end
end
-- Made for bolts. Will probably see other uses.
2026-02-06 03:51:18 +01:00
elseif params.skillRank > 0 then
magicAcc = xi.data . skillLevel.getSkillCap ( actor : getMainLvl ( ) , params.skillRank )
2025-01-14 10:26:36 +01:00
-- For mob skills / additional effects which don't have a skill.
2022-11-28 13:50:51 +01:00
else
2025-09-19 13:42:49 +02:00
magicAcc = xi.data . skillLevel.getSkillCap ( actor : getMainLvl ( ) , xi.skillRank . A_PLUS )
2022-11-28 13:50:51 +01:00
end
2023-07-08 11:35:36 +02:00
return magicAcc
end
2023-06-14 12:44:26 +02:00
2023-07-08 11:35:36 +02:00
-- Magic Accuracy from spell's element.
2026-02-06 03:51:18 +01:00
local function magicAccuracyFromElement ( actor , params )
-- Early return: No element.
if params.magicalElement <= xi.element . NONE then
return 0
2022-11-28 13:50:51 +01:00
end
2026-02-06 03:51:18 +01:00
return actor : getMod ( xi.data . element.getElementalMACCModifier ( params.magicalElement ) ) + actor : getMod ( xi.data . element.getElementalStaffModifier ( params.magicalElement ) ) * 10
2023-07-08 11:35:36 +02:00
end
-- Magic Accuracy from Stat Difference between caster and target.
2026-02-06 03:51:18 +01:00
local function magicAccuracyFromStatDifference ( actor , target , params )
if params.actorStat == 0 then
2024-10-03 01:42:47 +02:00
return 0
end
2023-07-08 11:35:36 +02:00
local magicAcc = 0
2026-02-06 03:51:18 +01:00
local statDiff = actor : getStat ( params.actorStat ) - target : getStat ( params.targetStat )
2023-05-24 23:42:07 -04:00
if statDiff <= - 31 then
2023-07-08 11:35:36 +02:00
magicAcc = - 20 + ( statDiff + 30 ) / 4
2023-05-24 23:42:07 -04:00
elseif statDiff <= - 11 then
2023-07-08 11:35:36 +02:00
magicAcc = - 10 + ( statDiff + 10 ) / 2
2023-05-24 23:42:07 -04:00
elseif statDiff < 11 then -- Between -11 and 11
2023-07-08 11:35:36 +02:00
magicAcc = statDiff
2023-05-24 23:42:07 -04:00
elseif statDiff >= 31 then
2023-07-08 11:35:36 +02:00
magicAcc = 20 + ( statDiff - 30 ) / 4
2023-05-24 23:42:07 -04:00
elseif statDiff >= 11 then
2023-07-08 11:35:36 +02:00
magicAcc = 10 + ( statDiff - 10 ) / 2
2022-11-28 13:50:51 +01:00
end
2023-07-08 11:35:36 +02:00
return utils.clamp ( magicAcc , - 30 , 30 )
end
-- Magic Accuracy from Status Effects.
2026-02-06 03:51:18 +01:00
local function magicAccuracyFromStatusEffects ( actor , params )
2023-07-08 11:35:36 +02:00
local magicAcc = 0
local actorJob = actor : getMainJob ( )
local actorWeather = actor : getWeather ( )
2023-05-24 23:42:07 -04:00
2022-11-28 13:50:51 +01:00
-- Altruism
if
2023-02-28 00:30:41 +01:00
actor : hasStatusEffect ( xi.effect . ALTRUISM ) and
2026-02-06 03:51:18 +01:00
params.spellGroup == xi.magic . spellGroup.WHITE
2022-11-28 13:50:51 +01:00
then
2023-07-08 11:35:36 +02:00
magicAcc = actor : getStatusEffect ( xi.effect . ALTRUISM ) : getPower ( )
2022-11-28 13:50:51 +01:00
end
-- Focalization
if
2023-02-28 00:30:41 +01:00
actor : hasStatusEffect ( xi.effect . FOCALIZATION ) and
2026-02-06 03:51:18 +01:00
params.spellGroup == xi.magic . spellGroup.BLACK
2022-11-28 13:50:51 +01:00
then
2023-02-28 00:30:41 +01:00
magicAcc = magicAcc + actor : getStatusEffect ( xi.effect . FOCALIZATION ) : getPower ( )
2022-11-28 13:50:51 +01:00
end
2023-07-08 11:35:36 +02:00
--Klimaform
2022-11-28 13:50:51 +01:00
if
2023-02-28 00:30:41 +01:00
actor : hasStatusEffect ( xi.effect . KLIMAFORM ) and
2026-02-06 03:51:18 +01:00
params.magicalElement > 0 and
( actorWeather == xi.data . element.getAssociatedSingleWeather ( params.magicalElement ) or actorWeather == xi.data . element.getAssociatedDoubleWeather ( params.magicalElement ) )
2022-11-28 13:50:51 +01:00
then
magicAcc = magicAcc + 15
end
-- Apply Divine Emblem to Banish and Holy families
if
2023-02-28 00:30:41 +01:00
actor : hasStatusEffect ( xi.effect . DIVINE_EMBLEM ) and
actorJob == xi.job . PLD and
2026-02-06 03:51:18 +01:00
params.skillType == xi.skill . DIVINE_MAGIC
2022-11-28 13:50:51 +01:00
then
2023-07-08 11:35:36 +02:00
magicAcc = magicAcc + 256
2022-11-28 13:50:51 +01:00
end
2022-12-10 06:08:33 +01:00
-- Elemental seal
if
2023-02-28 00:30:41 +01:00
actor : hasStatusEffect ( xi.effect . ELEMENTAL_SEAL ) and
2026-02-06 03:51:18 +01:00
params.skillType ~= xi.skill . DARK_MAGIC and
params.skillType ~= xi.skill . DIVINE_MAGIC and
params.magicalElement > 0
2022-12-10 06:08:33 +01:00
then
magicAcc = magicAcc + 256
end
2022-11-28 13:50:51 +01:00
-- Dark Seal
if
2023-02-28 00:30:41 +01:00
actor : hasStatusEffect ( xi.effect . DARK_SEAL ) and
2026-02-06 03:51:18 +01:00
params.skillType == xi.skill . DARK_MAGIC
2022-11-28 13:50:51 +01:00
then
2022-12-10 06:08:33 +01:00
magicAcc = magicAcc + 256
2022-11-28 13:50:51 +01:00
end
2023-07-08 11:35:36 +02:00
return magicAcc
end
-- Magic Accuracy from Merits.
2026-02-06 03:51:18 +01:00
local function magicAccuracyFromMerits ( actor , params )
2023-07-08 11:35:36 +02:00
local magicAcc = 0
local actorJob = actor : getMainJob ( )
2023-02-28 00:30:41 +01:00
switch ( actorJob ) : caseof
2022-11-28 13:50:51 +01:00
{
[ xi.job . BLM ] = function ( )
2026-02-06 03:51:18 +01:00
if params.skillType == xi.skill . ELEMENTAL_MAGIC then
2023-07-08 11:35:36 +02:00
magicAcc = actor : getMerit ( xi.merit . ELEMENTAL_MAGIC_ACCURACY )
end
2022-11-28 13:50:51 +01:00
end ,
[ xi.job . RDM ] = function ( )
2023-07-08 11:35:36 +02:00
-- Category 1
2022-11-28 13:50:51 +01:00
if
2026-02-06 03:51:18 +01:00
params.magicalElement >= xi.element . FIRE and
params.magicalElement <= xi.element . WATER
2022-11-28 13:50:51 +01:00
then
2026-02-06 03:51:18 +01:00
magicAcc = actor : getMerit ( xi.data . element.getElementalAccuracyMerit ( params.magicalElement ) )
2022-11-28 13:50:51 +01:00
end
2023-07-08 11:35:36 +02:00
-- Category 2
magicAcc = magicAcc + actor : getMerit ( xi.merit . MAGIC_ACCURACY )
2022-11-28 13:50:51 +01:00
end ,
2025-01-15 10:31:53 +01:00
[ xi.job . BRD ] = function ( )
if
2026-02-06 03:51:18 +01:00
params.skillType == xi.skill . SINGING and
2025-01-15 10:31:53 +01:00
actor : hasStatusEffect ( xi.effect . TROUBADOUR )
then
magicAcc = 64 * ( actor : getMerit ( xi.merit . TROUBADOUR ) / 25 - 1 )
end
end ,
2022-11-28 13:50:51 +01:00
[ xi.job . NIN ] = function ( )
2026-02-06 03:51:18 +01:00
if params.skillType == xi.skill . NINJUTSU then
2023-07-08 11:35:36 +02:00
magicAcc = actor : getMerit ( xi.merit . NIN_MAGIC_ACCURACY )
2022-11-28 13:50:51 +01:00
end
end ,
2023-07-08 11:35:36 +02:00
[ xi.job . BLU ] = function ( )
2026-02-06 03:51:18 +01:00
if params.skillType == xi.skill . BLUE_MAGIC then
2023-07-08 11:35:36 +02:00
magicAcc = actor : getMerit ( xi.merit . MAGICAL_ACCURACY )
2022-11-28 13:50:51 +01:00
end
end ,
}
2023-07-08 11:35:36 +02:00
return magicAcc
end
-- Magic Accuracy from Job Points.
2026-02-06 03:51:18 +01:00
local function magicAccuracyFromJobPoints ( actor , params )
2023-07-08 11:35:36 +02:00
local magicAcc = 0
local actorJob = actor : getMainJob ( )
2023-02-28 00:30:41 +01:00
switch ( actorJob ) : caseof
2022-11-28 13:50:51 +01:00
{
2023-07-08 11:35:36 +02:00
[ xi.job . WHM ] = function ( )
magicAcc = actor : getJobPointLevel ( xi.jp . WHM_MAGIC_ACC_BONUS )
end ,
2022-11-28 13:50:51 +01:00
[ xi.job . BLM ] = function ( )
2023-07-08 11:35:36 +02:00
magicAcc = actor : getJobPointLevel ( xi.jp . BLM_MAGIC_ACC_BONUS )
2022-11-28 13:50:51 +01:00
end ,
[ xi.job . RDM ] = function ( )
2023-07-08 11:35:36 +02:00
-- RDM Job Point: During saboteur, Enfeebling MACC +2
2022-11-28 13:50:51 +01:00
if
2026-02-06 03:51:18 +01:00
params.skillType == xi.skill . ENFEEBLING_MAGIC and
2023-07-08 11:35:36 +02:00
actor : hasStatusEffect ( xi.effect . SABOTEUR )
2022-11-28 13:50:51 +01:00
then
2023-07-08 11:35:36 +02:00
magicAcc = actor : getJobPointLevel ( xi.jp . SABOTEUR_EFFECT ) * 2
2022-11-28 13:50:51 +01:00
end
2023-07-08 11:35:36 +02:00
-- RDM Job Point: Magic Accuracy Bonus, All MACC + 1
magicAcc = magicAcc + actor : getJobPointLevel ( xi.jp . RDM_MAGIC_ACC_BONUS )
2022-11-28 13:50:51 +01:00
end ,
2025-01-15 10:31:53 +01:00
[ xi.job . BRD ] = function ( )
2026-02-06 03:51:18 +01:00
if params.skillType == xi.skill . SINGING then
2025-01-15 10:31:53 +01:00
magicAcc = actor : getJobPointLevel ( xi.jp . SONG_ACC_BONUS )
end
end ,
2022-11-28 13:50:51 +01:00
[ xi.job . NIN ] = function ( )
2026-02-06 03:51:18 +01:00
if params.skillType == xi.skill . NINJUTSU then
2023-07-08 11:35:36 +02:00
magicAcc = actor : getJobPointLevel ( xi.jp . NINJITSU_ACC_BONUS )
2022-11-28 13:50:51 +01:00
end
end ,
2023-07-08 11:35:36 +02:00
[ xi.job . SCH ] = function ( )
if
2026-02-06 03:51:18 +01:00
( params.spellGroup == xi.magic . spellGroup.WHITE and actor : hasStatusEffect ( xi.effect . PARSIMONY ) ) or
( params.spellGroup == xi.magic . spellGroup.BLACK and actor : hasStatusEffect ( xi.effect . PENURY ) )
2023-07-08 11:35:36 +02:00
then
magicAcc = actor : getJobPointLevel ( xi.jp . STRATEGEM_EFFECT_I )
2022-11-28 13:50:51 +01:00
end
end ,
}
2023-07-08 11:35:36 +02:00
return magicAcc
end
2022-11-28 13:50:51 +01:00
2023-07-08 11:35:36 +02:00
-- Magic Accuracy from Magic Burst.
2026-02-06 03:51:18 +01:00
local function magicAccuracyFromMagicBurst ( target , params )
2026-07-26 04:41:25 +02:00
if params.magicalElement == xi.element . NONE then
2024-10-03 01:42:47 +02:00
return 0
end
2026-07-06 13:32:27 +02:00
if params.magicBurstTier <= 0 then
2026-01-30 05:03:36 +01:00
return 0
2023-07-08 11:35:36 +02:00
end
2026-01-30 05:03:36 +01:00
return 100
2023-07-08 11:35:36 +02:00
end
2026-07-05 21:58:10 +02:00
-- Magic Accuracy from Weather Element. Note: https://wiki.ffo.jp/html/1796.html claims day does not affect macc.
local function magicAccuracyFromWeatherElement ( actor , params )
2023-07-08 11:35:36 +02:00
local magicAcc = 0
2025-06-30 16:13:48 +02:00
-- Early return: Invalid element.
2026-02-06 03:51:18 +01:00
if params.magicalElement <= xi.element . NONE then
2025-06-30 16:13:48 +02:00
return magicAcc
2022-12-11 17:29:59 +01:00
end
2025-06-30 16:13:48 +02:00
-- Define what to apply.
local applyBonuses = false
local applyPenalties = false
2023-07-08 09:26:10 +02:00
if
2026-07-05 14:55:22 +01:00
math.randomInt ( 1 , 100 ) <= 33 or -- Random. Applies to both bonuses and penalties.
2025-06-30 16:13:48 +02:00
actor : getMod ( xi.mod . FORCE_DW_BONUS_PENALTY ) >= 1 -- Hachirin-no-Obi forces both bonuses and penalties.
2023-07-08 09:26:10 +02:00
then
2025-06-30 16:13:48 +02:00
applyBonuses = true
applyPenalties = true
2026-02-06 03:51:18 +01:00
elseif actor : getMod ( xi.data . element.getForcedDayOrWeatherBonusModifier ( params.magicalElement ) ) >= 1 then -- Elemental Obis only force bonuses, not penalties.
2025-06-30 16:13:48 +02:00
applyBonuses = true
end
-- Calculate bonuses/penalties.
local actorWeather = actor : getWeather ( )
2023-07-08 11:35:36 +02:00
2025-06-30 16:13:48 +02:00
if applyBonuses then
2026-02-06 03:51:18 +01:00
if actorWeather == xi.data . element.getAssociatedSingleWeather ( params.magicalElement ) then
2025-06-30 16:13:48 +02:00
magicAcc = magicAcc + 5 + actor : getMod ( xi.mod . IRIDESCENCE ) * 5
2026-02-06 03:51:18 +01:00
elseif actorWeather == xi.data . element.getAssociatedDoubleWeather ( params.magicalElement ) then
2025-06-30 16:13:48 +02:00
magicAcc = magicAcc + 10 + actor : getMod ( xi.mod . IRIDESCENCE ) * 5
end
end
2023-07-08 09:26:10 +02:00
2025-06-30 16:13:48 +02:00
if applyPenalties then
2026-02-06 03:51:18 +01:00
if actorWeather == xi.data . element.getOppositeSingleWeather ( params.magicalElement ) then
2025-06-30 16:13:48 +02:00
magicAcc = magicAcc - 5 - actor : getMod ( xi.mod . IRIDESCENCE ) * 5
2026-02-06 03:51:18 +01:00
elseif actorWeather == xi.data . element.getOppositeDoubleWeather ( params.magicalElement ) then
2025-06-30 16:13:48 +02:00
magicAcc = magicAcc - 10 - actor : getMod ( xi.mod . IRIDESCENCE ) * 5
end
2023-07-08 09:26:10 +02:00
end
2023-07-08 11:35:36 +02:00
return magicAcc
end
2023-07-08 09:26:10 +02:00
2025-04-28 23:08:40 -05:00
-- Magic Accuracy from Tandem Strike (BST trait).
local function magicAccuracyFromTandemStrike ( actor )
2026-02-06 03:51:18 +01:00
-- Early return: Can't apply Tandem Strike.
if not actor : isTandemActive ( ) then
return 0
end
2025-04-28 23:08:40 -05:00
2026-02-06 03:51:18 +01:00
-- Actor is a pet, with a master. Fetch master modifier.
local master = actor : getMaster ( )
if master and master : isPC ( ) then
return master : getMod ( xi.mod . TANDEM_STRIKE_POWER )
2025-04-28 23:08:40 -05:00
end
2026-02-06 03:51:18 +01:00
-- Actor is the master.
return actor : getMod ( xi.mod . TANDEM_STRIKE_POWER )
2025-04-28 23:08:40 -05:00
end
2023-07-08 11:35:36 +02:00
-- Magic Accuracy from Food.
2024-10-03 01:42:47 +02:00
local function magicAccuracyFromFoodMultiplier ( actor )
2024-10-23 14:53:05 +02:00
local foodMagicAccBonus = actor : getMod ( xi.mod . FOOD_MACCP ) / 100
local foodMagicAccCap = actor : getMod ( xi.mod . FOOD_MACC_CAP ) / 100
2023-07-08 11:35:36 +02:00
2024-10-23 14:53:05 +02:00
if foodMagicAccCap > 0 then
foodMagicAccBonus = utils.clamp ( foodMagicAccBonus , 0 , foodMagicAccCap )
2023-07-08 09:26:10 +02:00
end
2026-02-06 03:51:18 +01:00
return 1 + foodMagicAccBonus
2022-11-28 13:50:51 +01:00
end
2022-12-10 06:08:33 +01:00
2026-02-06 03:51:18 +01:00
local function magicAccuracyFromSoulVoiceMultiplier ( actor , params )
2025-01-23 02:45:50 +01:00
local effectTable =
set {
xi.effect . SLEEP_I , -- Lullabies
xi.effect . NONE , -- Magic Finale
xi.effect . CHARM_I -- Maiden's Virellai
}
if
2026-02-06 03:51:18 +01:00
effectTable [ params.effectId ] and
params.skillType == xi.skill . SINGING
2025-01-23 02:45:50 +01:00
then
if actor : hasStatusEffect ( xi.effect . SOUL_VOICE ) then
return 2
elseif actor : hasStatusEffect ( xi.effect . MARCATO ) then
return 1.5
end
end
return 1
end
2023-07-08 11:35:36 +02:00
-- Global function to calculate total magicc accuracy.
2026-02-06 03:51:18 +01:00
local function calculateActorMagicAccuracy ( actor , target , params )
2023-07-08 11:35:36 +02:00
local finalMagicAcc = 0
2026-07-05 21:58:10 +02:00
local magicAccBase = actor : getMod ( xi.mod . MACC ) + actor : getILvlMacc ( xi.slot . MAIN ) + params.bonusMacc
local magicAccSkill = magicAccuracyFromSkill ( actor , params )
local magicAccElement = magicAccuracyFromElement ( actor , params )
local magicAccStatDiff = magicAccuracyFromStatDifference ( actor , target , params )
local magicAccEffects = magicAccuracyFromStatusEffects ( actor , params )
local magicAccMerits = magicAccuracyFromMerits ( actor , params )
local magicAccJobPoints = magicAccuracyFromJobPoints ( actor , params )
local magicAccBurst = magicAccuracyFromMagicBurst ( target , params )
local magicAccWeather = magicAccuracyFromWeatherElement ( actor , params )
local magicAccTandem = magicAccuracyFromTandemStrike ( actor )
2023-07-08 11:35:36 +02:00
2024-10-03 01:42:47 +02:00
-- Multipliers
2025-01-23 02:45:50 +01:00
local magicAccFoodFactor = magicAccuracyFromFoodMultiplier ( actor )
2026-02-06 03:51:18 +01:00
local magicAccSoulVoiceFactor = magicAccuracyFromSoulVoiceMultiplier ( actor , params )
2023-07-08 11:35:36 +02:00
-- Add up food magic accuracy.
2026-07-05 21:58:10 +02:00
finalMagicAcc = magicAccBase + magicAccSkill + magicAccElement + magicAccStatDiff + magicAccEffects + magicAccMerits + magicAccJobPoints + magicAccBurst + magicAccWeather + magicAccTandem
2025-01-23 02:45:50 +01:00
finalMagicAcc = math.floor ( finalMagicAcc * magicAccFoodFactor * magicAccSoulVoiceFactor )
2023-07-08 11:35:36 +02:00
return finalMagicAcc
end
2023-09-07 08:18:40 -04:00
-----------------------------------
2023-07-08 11:35:36 +02:00
-- Calculate Target Magic Evasion
2023-09-07 08:18:40 -04:00
-----------------------------------
2023-07-23 11:46:04 +02:00
local resistRankMultiplier =
{
-- [Rank] = Magic Evasion multiplier.
[ - 3 ] = 0.95 ,
[ - 2 ] = 0.96019 ,
[ - 1 ] = 0.98 ,
[ 0 ] = 1 ,
[ 1 ] = 1.023 ,
[ 2 ] = 1.049 ,
[ 3 ] = 1.0905 ,
[ 4 ] = 1.126 ,
[ 5 ] = 1.2075 ,
[ 6 ] = 1.3475 ,
[ 7 ] = 1.70065 ,
[ 8 ] = 2.141 ,
[ 9 ] = 2.2 ,
2023-08-28 21:02:28 -04:00
[ 10 ] = 2.275 , -- Impossible to test since 'Magic Hit Rate' is floored to 5% at this point.
[ 11 ] = 2.35 , -- Impossible to test since 'Magic Hit Rate' is floored to 5% at this point.
2023-07-23 11:46:04 +02:00
}
2023-07-08 11:35:36 +02:00
2026-02-06 03:51:18 +01:00
local function calculateTargetMagicEvasion ( actor , target , params )
2025-09-11 01:59:08 +02:00
local magicEva = target : getMod ( xi.mod . MEVA ) -- Base MACC.
2022-12-10 06:08:33 +01:00
2025-09-11 01:59:08 +02:00
-- Elemental magic evasion. All actions and effects have an associated element.
2026-02-06 03:51:18 +01:00
if params.magicalElement ~= xi.element . NONE then
magicEva = magicEva + target : getMod ( xi.data . element.getElementalMEVAModifier ( params.magicalElement ) )
2022-12-10 06:08:33 +01:00
end
2022-12-11 17:29:59 +01:00
-- Magic evasion against specific status effects.
2026-02-06 03:51:18 +01:00
if params.effectId > 0 then
magicEva = magicEva + target : getMod ( xi.data . statusEffect.getAssociatedMagicEvasionModifier ( params.effectId ) ) + target : getMod ( xi.mod . STATUS_MEVA )
2022-12-11 17:29:59 +01:00
end
2023-06-14 12:45:25 +02:00
-- Level correction. Target gets a bonus the higher the level if it's a mob. Never a penalty.
if
2025-01-18 12:34:24 +01:00
not target : isPC ( ) and
2025-09-19 13:42:49 +02:00
xi.data . levelCorrection.isLevelCorrectedZone ( actor )
2023-06-14 12:45:25 +02:00
then
2025-01-18 12:34:24 +01:00
magicEva = magicEva + utils.clamp ( target : getMainLvl ( ) - actor : getMainLvl ( ) , 0 , 100 ) * 4
2022-12-10 06:08:33 +01:00
end
2025-09-11 01:59:08 +02:00
-- Apply resistance rank multiplier.
2026-02-06 03:51:18 +01:00
magicEva = math.floor ( magicEva * resistRankMultiplier [ params.resistanceRank ] )
2025-09-11 01:59:08 +02:00
2022-12-10 06:08:33 +01:00
return magicEva
end
2023-09-07 08:18:40 -04:00
-----------------------------------
2022-12-12 14:16:00 +01:00
-- Magic Hit Rate. The function gets fed the result of both functions above.
2023-09-07 08:18:40 -04:00
-----------------------------------
2026-02-06 03:51:18 +01:00
local function calculateMagicHitRate ( params )
local magicHitRate = params.actorMagicAccuracy - params.targetMagicEvasion
2022-12-10 06:08:33 +01:00
2025-01-17 01:15:57 +01:00
if magicHitRate < 0 then
magicHitRate = math.floor ( magicHitRate / 2 )
2022-12-10 06:08:33 +01:00
end
2025-01-17 01:15:57 +01:00
magicHitRate = utils.clamp ( ( 50 + magicHitRate ) / 100 , 0.05 , 0.95 )
2022-12-10 06:08:33 +01:00
return magicHitRate
end
2022-12-12 14:16:00 +01:00
2023-09-07 08:18:40 -04:00
-----------------------------------
2026-02-06 03:51:18 +01:00
-- Calculate resist rate.
2023-09-07 08:18:40 -04:00
-----------------------------------
2026-02-06 03:51:18 +01:00
local function calculateResistanceFactor ( actor , target , params )
2025-01-17 01:15:57 +01:00
-- Calculate max allowed resist tier.
local maxResistTier = 3
2022-12-12 14:16:00 +01:00
2025-01-17 01:15:57 +01:00
-- Players: Affected by element shown in equipment screen.
if target : isPC ( ) then
2026-02-06 03:51:18 +01:00
local playerElementalEvasion = target : getMod ( xi.data . element.getElementalMEVAModifier ( params.magicalElement ) ) or 0
2022-12-12 14:16:00 +01:00
2025-01-17 01:15:57 +01:00
if playerElementalEvasion < 0 then
maxResistTier = 1
elseif playerElementalEvasion == 0 then
maxResistTier = 2
end
2022-12-12 14:16:00 +01:00
end
2026-02-06 03:51:18 +01:00
-- Calculate first 3 resist tiers.
-- Notes: https://wiki-ffo-jp.translate.goog/html/795.html?_x_tr_sl=ja&_x_tr_tl=en&_x_tr_hl=en&_x_tr_pto=sc
local resistTier = 0
for i = 1 , maxResistTier do
2026-07-05 14:55:22 +01:00
if math.randomFloat ( 0 , 1 ) > params.magicHitRate then
2026-02-06 03:51:18 +01:00
resistTier = resistTier + 1
2026-03-12 06:27:37 +01:00
else
break
2026-02-06 03:51:18 +01:00
end
2025-01-17 01:15:57 +01:00
end
2026-02-06 03:51:18 +01:00
return 1 / ( 2 ^ resistTier )
2022-12-12 14:16:00 +01:00
end
2024-10-03 01:42:47 +02:00
-----------------------------------
-- Resist rate helper function.
-----------------------------------
2026-02-06 03:51:18 +01:00
local function validateParameters ( actor , target , fedData )
local params = { }
-- Action information.
params.effectId = fedData.effectId or 0
params.magicalElement = fedData.magicalElement or xi.element . NONE
params.bonusMacc = fedData.bonusMacc or 0
2026-07-06 13:32:27 +02:00
params.magicBurstTier = fedData.magicBurstTier or 0
2026-02-06 03:51:18 +01:00
params.actorStat = fedData.actorStat or 0
params.targetStat = fedData.targetStat or params.actorStat
params.skillType = fedData.skillType or 0
params.skillRank = fedData.skillRank or 0
params.spellGroup = fedData.spellGroup or 0
-- Initialize future parameters.
params.resistanceRank = 0
params.actorMagicAccuracy = 0
params.targetMagicEvasion = 0
params.magicHitRate = 0.05
return params
end
2024-10-03 01:42:47 +02:00
2026-07-26 04:41:25 +02:00
xi.combat . magicHitRate.calculateResistRate = function ( actor , target , fedData )
2026-02-06 03:51:18 +01:00
-- Validate fed parameters.
local params = validateParameters ( actor , target , fedData )
2024-10-03 01:42:47 +02:00
2026-07-06 13:32:27 +02:00
-- Early return: Magic shield.
if target : hasStatusEffect ( xi.effect . MAGIC_SHIELD , 0 ) then
return 0
end
-- Early return: Cannot resist non-elemental magic actions.
if params.magicalElement == xi.element . NONE then
return 1
end
-- Calculate and table resistance rank. TODO: Feed it to the function directly.
2026-02-06 03:51:18 +01:00
params.resistanceRank = calculateTargetResistanceRank ( actor , target , params )
2025-09-11 01:59:08 +02:00
2026-02-06 03:51:18 +01:00
-- Early return: Auto-resist.
if params.resistanceRank >= 11 then
if params.effectId > 0 then
return 0 -- Status Effects
else
return 0.25 -- Nukes
2025-09-11 01:59:08 +02:00
end
2026-02-06 03:51:18 +01:00
end
2025-09-12 16:39:37 +02:00
2026-02-06 03:51:18 +01:00
-- Early return: MHR is floored to 0.05. Skip calculating it.
if params.resistanceRank >= 10 then
return calculateResistanceFactor ( actor , target , params )
2024-10-03 01:42:47 +02:00
end
2026-02-06 03:51:18 +01:00
-- Calculate and table MACC, MEVA and MHR.
params.actorMagicAccuracy = calculateActorMagicAccuracy ( actor , target , params )
params.targetMagicEvasion = calculateTargetMagicEvasion ( actor , target , params )
params.magicHitRate = calculateMagicHitRate ( params )
2024-10-03 01:42:47 +02:00
2026-02-06 03:51:18 +01:00
return calculateResistanceFactor ( actor , target , params )
2024-10-03 01:42:47 +02:00
end