[core][lua][magic] Unify Spell Interrupt into Lua#10204
Open
JerokeXI wants to merge 1 commit into
Open
Conversation
Xaver-DaRed
reviewed
Jun 1, 2026
| @@ -1,4 +1,5 @@ | |||
| require('scripts/globals/combat/magic_hit_rate') | |||
| require('scripts/globals/combat/magic_interrupt') | |||
Xaver-DaRed
reviewed
Jun 1, 2026
| end | ||
|
|
||
| return false | ||
| end |
Contributor
There was a problem hiding this comment.
Proposal:
xi.combat.magic.shouldInterruptSpell = function(attacker, defender, spell)
-- Early return: Manafont prevents casting interruption.
if defender:hasStatusEffect(xi.effect.MANAFONT) then
return false
end
-- Early return: Trusts can't be interrupted.
local entityType = defender:getObjType()
if entityType == xi.objType.TRUST then
return false
end
-- Early return: Songs can't be interrupted.
local skillType = spell:getSkillType()
if skillType == xi.skill.SINGING then
return false
end
-- Llevel factor.
local baseInterruptionRate = entityType == xi.objType.MOB and 5 or 50
local levelRatio = utils.clamp((baseInterruptionRate + attacker:getMainLvl() - defender:getMainLvl()) / 100, 0.01, 1.02)
-- Skill factor.
local skillRatio = 1
if entityType == xi.objType.PC then
local skillCap = defender:getMaxSkillLevel(defender:getMainLvl(), defender:getMainJob(), skillType)
local skillLevel = defender:getSkillLevel(skillType)
-- If skill cap is 0, player may be using a spell from their subjob.
if skillCap == 0 then
skillCap = defender:getMaxSkillLevel(defender:getMainLvl(), defender:getSubJob(), skillType)
end
-- If skill level is 0, set ratio to 10.
if skillLevel <= 0 then
skillRatio = 10
else
skillRatio = skillCap / skillLevel
end
end
-- SIRD reduces the interrupt after all the calculations are done -- as evidenced by the infamous "102% SIRD" builds.
-- Anything less than 102% interrupt results in the ability to be interrupted.
-- Note: the 102% is probably an x/256 x/1024 nonsense -- sometimes 101% works.
local sirdRatio = (100 - defender:getMerit(xi.merit.SPELL_INTERUPTION_RATE) - defender:getMod(xi.mod.SPELLINTERRUPT)) / 100
-- levelRatio : 0.01 to infinity.
-- skillRatio : 1 to infinity.
-- SIRDRatio : No limits. Can be negative. A negative value will guarantee NOT being interrupted.
local finalRatio = levelRatio * skillRatio * sirdRatio -- TL;DR Higher = Worse = More chances to get interrupted.
-- Early return: Caster doesn't get interrupted.
if math.random() >= finalRatio then
return false
end
-- Early return: Caster can't prevent interruption via aquaveil.
if not defender:hasStatusEffect(xi.effect.AQUAVEIL) then
return true
end
-- Handle aquaveil and prevent interruption.
local aquaveilPower = defender:getStatusEffect(xi.effect.AQUAVEIL):getPower() - 1
if aquaveilPower == 0 then
defender:delStatusEffect(xi.effect.AQUAVEIL)
else
defender:getStatusEffect(xi.effect.AQUAVEIL):setPower(aquaveilPower)
end
return false
end
Contributor
There was a problem hiding this comment.
For the sake of this being a learning experience, some notes here:
- Lua and C++ don't work the same. You don't need .0 floats in there. They don't hurt, but they also don't provide extra precision in Lua. So they are extra characters that aren't needed.
- If you find yourself fetching entity type, or other kinds of info, more than once, it may be wroth just storing them in a variable.
- Long operations can be divided in multiple steps, for the sake of readability. I'm refering concretely when fetching the base interruption rate, which varies between players and other entities.
- I didn't do it in my proposal, but, defender could be named
casterto make it crystal clear which entity is the one that can be interrupted. Minor, but everything helps for the sake of making the code as easy to follow as possible. - And this is just a personal preference, but I find that using an "early return" structure helps streamlining logic and makes understanding the flow of the code easier.
Contributor
There was a problem hiding this comment.
Having said all of this, we have to decide if we really want this in lua, now, or in the future, or if a setting is the better approach.
But nontheless, its a good job.
sruon
reviewed
Jun 3, 2026
| xi.combat.magic.shouldInterruptSpell = function(attacker, defender, spell) | ||
| -- Exceptions. | ||
| if | ||
| defender:getObjType() == xi.objType.TRUST or -- Caster is a trust. |
Contributor
Contributor
There was a problem hiding this comment.
I'm pretty sure the only non interrupted casting action is with movement and no action to prevent casting.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

I affirm:
What does this pull request do?
Changes Spell Interrupt from CPP to LUA
Steps to test these changes