From 8964c53d6689294e192e3db25e19711d22a43cfd Mon Sep 17 00:00:00 2001 From: MrSent Date: Sun, 15 Mar 2026 16:14:25 +0000 Subject: [PATCH 1/4] [trust] AAEV gambits, Amchuchu gambit ONE_FOR_ALL ai.r.MA->ai.r.JA fix --- scripts/actions/spells/trust/aaev.lua | 78 +++++++++++++++++++++++ scripts/actions/spells/trust/amchuchu.lua | 2 +- src/map/ai/helpers/gambits_container.cpp | 23 +++++-- 3 files changed, 98 insertions(+), 5 deletions(-) diff --git a/scripts/actions/spells/trust/aaev.lua b/scripts/actions/spells/trust/aaev.lua index 742fe1138c7..8482bf4c6af 100644 --- a/scripts/actions/spells/trust/aaev.lua +++ b/scripts/actions/spells/trust/aaev.lua @@ -14,6 +14,84 @@ end spellObject.onMobSpawn = function(mob) xi.trust.message(mob, xi.trust.messageOffset.SPAWN) + + mob:addMod(xi.mod.FASTCAST, 30) + mob:addMod(xi.mod.CURE_POTENCY, 50) + mob:addMod(xi.mod.DMG, -10) + mob:addMod(xi.mod.HPP, 20) + mob:addMod(xi.mod.ABSORB_PHYSDMG_TO_MP, 5) + + local lastSynergyBonus = 0 + + -- Dynamic modifier that checks party member list on tick to apply + mob:addListener('COMBAT_TICK', 'AAEV_CTICK', function(mobArg) + local synergyMembers = + { + xi.magic.spell.AAHM, + xi.magic.spell.AAMR, + xi.magic.spell.AATT, + xi.magic.spell.AAGK + } + + local synergyCount = 0 + local party = mobArg:getMaster():getPartyWithTrusts() + + for _, member in pairs(party) do + if member:getObjType() == xi.objType.TRUST then + local trustId = member:getTrustID() + for _, sId in ipairs(synergyMembers) do + if trustId == sId then + synergyCount = synergyCount + 1 + break + end + end + end + end + + -- Determine what the bonus should be + local targetBonus = (synergyCount == #synergyMembers) and 50 or 0 + + -- Only update if the state has changed + if targetBonus ~= lastSynergyBonus then + -- AAEV already has a MEVA value so we just manipulate it + -- Subtract exactly what we added last time + mobArg:delMod(xi.mod.MEVA, lastSynergyBonus) + -- Add the new bonus + mobArg:addMod(xi.mod.MEVA, targetBonus) + + -- Update lastSynergyBonus + lastSynergyBonus = targetBonus + end + end) + + ----------------------------------- + -- Gambits + ----------------------------------- + -- 1 condition + mob:addGambit(ai.t.TARGET, { ai.c.NOT_STATUS, xi.effect.FLASH }, { ai.r.MA, ai.s.SPECIFIC, xi.magic.spell.FLASH }) + mob:addGambit(ai.t.PARTY, { ai.c.HPP_LT, 50 }, { ai.r.MA, ai.s.HIGHEST, xi.magic.spellFamily.CURE }) + mob:addGambit(ai.t.TARGET, { ai.c.CASTING_ELE_MA_AOE, 0 }, { ai.r.JA, ai.s.SPECIFIC, xi.ja.SHIELD_STRIKE }) + mob:addGambit(ai.t.TARGET, { ai.c.STATUS, xi.effect.MANAFONT }, { ai.r.JA, ai.s.SPECIFIC, xi.ja.RAMPART }) + mob:addGambit(ai.t.TARGET, { ai.c.STATUS, xi.effect.CHAINSPELL }, { ai.r.JA, ai.s.SPECIFIC, xi.ja.RAMPART }) + mob:addGambit(ai.t.TARGET, { ai.c.STATUS, xi.effect.ASTRAL_FLOW }, { ai.r.JA, ai.s.SPECIFIC, xi.ja.RAMPART }) + mob:addGambit(ai.t.SELF, { ai.c.NOT_STATUS, xi.effect.PHALANX }, { ai.r.MA, ai.s.SPECIFIC, xi.magic.spell.PHALANX }) + mob:addGambit(ai.t.SELF, { ai.c.NOT_STATUS, xi.effect.REPRISAL }, { ai.r.MA, ai.s.SPECIFIC, xi.magic.spell.REPRISAL }) + mob:addGambit(ai.t.SELF, { ai.c.NOT_STATUS, xi.effect.SENTINEL }, { ai.r.JA, ai.s.SPECIFIC, xi.ja.SENTINEL }) + mob:addGambit(ai.t.SELF, { ai.c.NOT_STATUS, xi.effect.ENLIGHT }, { ai.r.MA, ai.s.SPECIFIC, xi.magic.spell.ENLIGHT }) + mob:addGambit(ai.t.SELF, { ai.c.NOT_STATUS, xi.effect.DIVINE_EMBLEM }, { ai.r.JA, ai.s.SPECIFIC, xi.ja.DIVINE_EMBLEM }) + mob:addGambit(ai.t.SELF, { ai.c.NOT_STATUS, xi.effect.PALISADE }, { ai.r.MA, ai.s.SPECIFIC, xi.magic.spell.PALISADE }) + + -- 2 conditions + mob:addGambit(ai.t.SELF, { { ai.c.MPP_LT, 25 }, { ai.c.TP_GTE, 1000 }, }, { ai.r.JA, ai.s.SPECIFIC, xi.ja.CHIVALRY }) + + mob:setTrustTPSkillSettings(ai.tp.CLOSER_UNTIL_TP, ai.s.HIGHEST, 2000) + + mob:addListener('WEAPONSKILL_USE', 'AAEV_WEAPONSKILL_USE', function(mobArg, target, wsid, tp, action) + --if wsid == 61 then -- + -- + xi.trust.message(mobArg, xi.trust.messageOffset.SPECIAL_MOVE_1) + --end + end) end spellObject.onMobDespawn = function(mob) diff --git a/scripts/actions/spells/trust/amchuchu.lua b/scripts/actions/spells/trust/amchuchu.lua index 610bb3c39bc..bece2965ecd 100644 --- a/scripts/actions/spells/trust/amchuchu.lua +++ b/scripts/actions/spells/trust/amchuchu.lua @@ -22,7 +22,7 @@ spellObject.onMobSpawn = function(mob) ----------------------------------- -- 1 condition mob:addGambit(ai.t.SELF, { ai.c.NOT_HAS_TOP_ENMITY, 0 }, { ai.r.JA, ai.s.SPECIFIC, xi.ja.PROVOKE }) - mob:addGambit(ai.t.TARGET, { ai.c.CASTING_ELE_MA_AOE, 0 }, { ai.r.MA, ai.s.SPECIFIC, xi.ja.ONE_FOR_ALL }) + mob:addGambit(ai.t.TARGET, { ai.c.CASTING_ELE_MA_AOE, 0 }, { ai.r.JA, ai.s.SPECIFIC, xi.ja.ONE_FOR_ALL }) mob:addGambit(ai.t.TARGET, { ai.c.CASTING_ELE_MA_AOE, 0 }, { ai.r.JA, ai.s.SPECIFIC, xi.ja.VALIANCE }) mob:addGambit(ai.t.TARGET, { ai.c.NOT_STATUS, xi.effect.FLASH }, { ai.r.MA, ai.s.SPECIFIC, xi.magic.spell.FLASH }) mob:addGambit(ai.t.SELF, { ai.c.NOT_STATUS, xi.effect.PHALANX }, { ai.r.MA, ai.s.SPECIFIC, xi.magic.spell.PHALANX }) diff --git a/src/map/ai/helpers/gambits_container.cpp b/src/map/ai/helpers/gambits_container.cpp index 741c0b22953..1388dfb3c98 100644 --- a/src/map/ai/helpers/gambits_container.cpp +++ b/src/map/ai/helpers/gambits_container.cpp @@ -951,7 +951,9 @@ bool CGambitsContainer::CheckTrigger(const CBattleEntity* triggerTarget, Predica if (triggerTarget->PAI->IsCurrentState()) { auto spellFamily = static_cast(triggerTarget->PAI->GetCurrentState())->GetSpell()->getSpellFamily(); - if (spellFamily >= SPELLFAMILY::SPELLFAMILY_FIRAGA && spellFamily <= SPELLFAMILY::SPELLFAMILY_WATERGA) + if ((spellFamily >= SPELLFAMILY::SPELLFAMILY_FIRAGA && spellFamily <= SPELLFAMILY::SPELLFAMILY_WATERGA) || + (spellFamily >= SPELLFAMILY::SPELLFAMILY_FIRA && spellFamily <= SPELLFAMILY::SPELLFAMILY_WATERA) || + spellFamily == SPELLFAMILY::SPELLFAMILY_JA) { isAOE = true; } @@ -965,7 +967,10 @@ bool CGambitsContainer::CheckTrigger(const CBattleEntity* triggerTarget, Predica if (triggerTarget->PAI->IsCurrentState()) { auto spellFamily = static_cast(triggerTarget->PAI->GetCurrentState())->GetSpell()->getSpellFamily(); - isElementalMA = spellFamily >= SPELLFAMILY::SPELLFAMILY_FIRE && spellFamily <= SPELLFAMILY::SPELLFAMILY_FLOOD; + isElementalMA = ((spellFamily >= SPELLFAMILY::SPELLFAMILY_FIRE && spellFamily <= SPELLFAMILY::SPELLFAMILY_FLOOD) || + (spellFamily >= SPELLFAMILY::SPELLFAMILY_FIRA && spellFamily <= SPELLFAMILY::SPELLFAMILY_WATERA) || + spellFamily == SPELLFAMILY::SPELLFAMILY_JA); + } predicateResults.push_back(isElementalMA); continue; @@ -977,7 +982,9 @@ bool CGambitsContainer::CheckTrigger(const CBattleEntity* triggerTarget, Predica { auto spellFamily = static_cast(triggerTarget->PAI->GetCurrentState())->GetSpell()->getSpellFamily(); auto targetID = static_cast(triggerTarget->PAI->GetCurrentState())->GetTarget()->id; - bool isElementalMA = spellFamily >= SPELLFAMILY::SPELLFAMILY_FIRE && spellFamily <= SPELLFAMILY::SPELLFAMILY_FLOOD; + bool isElementalMA = ((spellFamily >= SPELLFAMILY::SPELLFAMILY_FIRE && spellFamily <= SPELLFAMILY::SPELLFAMILY_FLOOD) || + (spellFamily >= SPELLFAMILY::SPELLFAMILY_FIRA && spellFamily <= SPELLFAMILY::SPELLFAMILY_WATERA) || + spellFamily == SPELLFAMILY::SPELLFAMILY_JA); if (targetID == POwner->id && isElementalMA) { isElementalMAOnSelf = true; @@ -994,37 +1001,45 @@ bool CGambitsContainer::CheckTrigger(const CBattleEntity* triggerTarget, Predica auto spellFamily = static_cast(triggerTarget->PAI->GetCurrentState())->GetSpell()->getSpellFamily(); auto targetID = static_cast(triggerTarget->PAI->GetCurrentState())->GetTarget()->id; uint32 element = 0; - if (targetID == POwner->id && (spellFamily >= SPELLFAMILY::SPELLFAMILY_FIRE && spellFamily <= SPELLFAMILY::SPELLFAMILY_FLOOD)) + if (targetID == POwner->id && ((spellFamily >= SPELLFAMILY::SPELLFAMILY_FIRE && spellFamily <= SPELLFAMILY::SPELLFAMILY_FLOOD) || + (spellFamily >= SPELLFAMILY::SPELLFAMILY_FIRA && spellFamily <= SPELLFAMILY::SPELLFAMILY_WATERA) || + spellFamily == SPELLFAMILY::SPELLFAMILY_JA)) { switch (spellFamily) { case SPELLFAMILY::SPELLFAMILY_FIRE: case SPELLFAMILY::SPELLFAMILY_FLARE: + case SPELLFAMILY::SPELLFAMILY_FIRA: needBarEffect = !POwner->StatusEffectContainer->HasStatusEffect(EFFECT_BARFIRE); element = ELEMENT_FIRE; break; case SPELLFAMILY::SPELLFAMILY_BLIZZARD: case SPELLFAMILY::SPELLFAMILY_FREEZE: + case SPELLFAMILY::SPELLFAMILY_BLIZZARA: needBarEffect = !POwner->StatusEffectContainer->HasStatusEffect(EFFECT_BARBLIZZARD); element = ELEMENT_ICE; break; case SPELLFAMILY::SPELLFAMILY_AERO: case SPELLFAMILY::SPELLFAMILY_TORNADO: + case SPELLFAMILY::SPELLFAMILY_AERORA: needBarEffect = !POwner->StatusEffectContainer->HasStatusEffect(EFFECT_BARAERO); element = ELEMENT_WIND; break; case SPELLFAMILY::SPELLFAMILY_STONE: case SPELLFAMILY::SPELLFAMILY_QUAKE: + case SPELLFAMILY::SPELLFAMILY_STONERA: needBarEffect = !POwner->StatusEffectContainer->HasStatusEffect(EFFECT_BARSTONE); element = ELEMENT_EARTH; break; case SPELLFAMILY::SPELLFAMILY_THUNDER: case SPELLFAMILY::SPELLFAMILY_BURST: + case SPELLFAMILY::SPELLFAMILY_THUNDARA: needBarEffect = !POwner->StatusEffectContainer->HasStatusEffect(EFFECT_BARTHUNDER); element = ELEMENT_THUNDER; break; case SPELLFAMILY::SPELLFAMILY_WATER: case SPELLFAMILY::SPELLFAMILY_FLOOD: + case SPELLFAMILY::SPELLFAMILY_WATERA: needBarEffect = !POwner->StatusEffectContainer->HasStatusEffect(EFFECT_BARWATER); element = ELEMENT_WATER; break; From ad3008f909b6b34a6c2a54ca7943332bfd1dc6ed Mon Sep 17 00:00:00 2001 From: MrSent Date: Sun, 15 Mar 2026 22:06:06 +0000 Subject: [PATCH 2/4] [sql,lua,core] Make gambit RANDOM skillchain-aware; add AAEV mobskills --- .../actions/mobskills/arrogance_incarnate.lua | 46 ++++++ scripts/actions/mobskills/chant_du_cygne.lua | 27 ++++ scripts/actions/spells/trust/aaev.lua | 29 ++-- sql/mob_skills.sql | 10 +- src/map/ai/helpers/gambits_container.cpp | 136 +++++++++--------- 5 files changed, 165 insertions(+), 83 deletions(-) create mode 100644 scripts/actions/mobskills/arrogance_incarnate.lua create mode 100644 scripts/actions/mobskills/chant_du_cygne.lua diff --git a/scripts/actions/mobskills/arrogance_incarnate.lua b/scripts/actions/mobskills/arrogance_incarnate.lua new file mode 100644 index 00000000000..8ca8b598189 --- /dev/null +++ b/scripts/actions/mobskills/arrogance_incarnate.lua @@ -0,0 +1,46 @@ +----------------------------------- +-- Arrogance Incarnate +-- Description: Delivers an unavoidable area attack. Damage varies with HP and TP. +-- Type: Breath +-- Element: None +-- Stat Modifier: 80% DEX +----------------------------------- +---@type TMobSkill +local mobskillObject = {} + +mobskillObject.onMobSkillCheck = function(target, mob, skill) + return 0 +end + +mobskillObject.onMobWeaponSkill = function(mob, target, skill, action) + local tp = skill:getTP() + local hp = mob:getHP() + local dmg = math.floor(hp * (math.floor(0.016 * tp) + 16) / 256) + + if tp > 2000 then -- 2001 - 3000 + dmg = math.floor(hp * (math.floor(0.072 * tp) - 96) / 256) + end + + dmg = math.floor(dmg * 2.5) + dmg = math.floor(dmg * xi.combat.damage.calculateDamageAdjustment(target, false, false, false, true)) + dmg = math.floor(dmg * xi.spells.damage.calculateAbsorption(target, xi.element.NONE, false)) + dmg = math.floor(dmg * xi.spells.damage.calculateNullification(target, xi.element.NONE, false, true)) + dmg = math.floor(target:handleSevereDamage(dmg, false)) + dmg = utils.handlePhalanx(target, dmg) + + if dmg < 0 then + return 0 + end + + dmg = utils.handleStoneskin(target, dmg) + + if dmg > 0 then + target:wakeUp() + target:updateEnmityFromDamage(mob, dmg) + end + + target:takeDamage(dmg, mob, xi.attackType.BREATH, xi.damageType.ELEMENTAL) + return dmg +end + +return mobskillObject diff --git a/scripts/actions/mobskills/chant_du_cygne.lua b/scripts/actions/mobskills/chant_du_cygne.lua new file mode 100644 index 00000000000..bcf64d577c1 --- /dev/null +++ b/scripts/actions/mobskills/chant_du_cygne.lua @@ -0,0 +1,27 @@ +----------------------------------- +-- Chant du Cygne +-- Description: Delivers a threefold attack. Chance of critical hit varies with TP. +-- Type: Physical +-- Element: None +-- Stat Modifier: 80% DEX +----------------------------------- +---@type TMobSkill +local mobskillObject = {} + +mobskillObject.onMobSkillCheck = function(target, mob, skill) + return 0 +end + +mobskillObject.onMobWeaponSkill = function(mob, target, skill, action) + local numhits = 3 + local accmod = 1 + local ftp = 1.6328125 + + local info = xi.mobskills.mobPhysicalMove(mob, target, skill, numhits, accmod, ftp, xi.mobskills.physicalTpBonus.CRIT_VARIES, 0.15, 0.25, 0.40) + local dmg = xi.mobskills.mobFinalAdjustments(info, mob, skill, target, xi.attackType.PHYSICAL, xi.damageType.SLASHING, info.hitslanded) + + target:takeDamage(dmg, mob, xi.attackType.PHYSICAL, xi.damageType.SLASHING) + return dmg +end + +return mobskillObject diff --git a/scripts/actions/spells/trust/aaev.lua b/scripts/actions/spells/trust/aaev.lua index 8482bf4c6af..fead763027a 100644 --- a/scripts/actions/spells/trust/aaev.lua +++ b/scripts/actions/spells/trust/aaev.lua @@ -20,17 +20,16 @@ spellObject.onMobSpawn = function(mob) mob:addMod(xi.mod.DMG, -10) mob:addMod(xi.mod.HPP, 20) mob:addMod(xi.mod.ABSORB_PHYSDMG_TO_MP, 5) - local lastSynergyBonus = 0 -- Dynamic modifier that checks party member list on tick to apply mob:addListener('COMBAT_TICK', 'AAEV_CTICK', function(mobArg) - local synergyMembers = - { - xi.magic.spell.AAHM, - xi.magic.spell.AAMR, - xi.magic.spell.AATT, - xi.magic.spell.AAGK + local synergyMembers = + { + xi.magic.spell.AAHM, + xi.magic.spell.AAMR, + xi.magic.spell.AATT, + xi.magic.spell.AAGK } local synergyCount = 0 @@ -58,7 +57,7 @@ spellObject.onMobSpawn = function(mob) mobArg:delMod(xi.mod.MEVA, lastSynergyBonus) -- Add the new bonus mobArg:addMod(xi.mod.MEVA, targetBonus) - + -- Update lastSynergyBonus lastSynergyBonus = targetBonus end @@ -69,8 +68,9 @@ spellObject.onMobSpawn = function(mob) ----------------------------------- -- 1 condition mob:addGambit(ai.t.TARGET, { ai.c.NOT_STATUS, xi.effect.FLASH }, { ai.r.MA, ai.s.SPECIFIC, xi.magic.spell.FLASH }) + mob:addGambit(ai.t.SELF, { ai.c.HPP_LT, 75 }, { ai.r.MA, ai.s.HIGHEST, xi.magic.spellFamily.CURE }) mob:addGambit(ai.t.PARTY, { ai.c.HPP_LT, 50 }, { ai.r.MA, ai.s.HIGHEST, xi.magic.spellFamily.CURE }) - mob:addGambit(ai.t.TARGET, { ai.c.CASTING_ELE_MA_AOE, 0 }, { ai.r.JA, ai.s.SPECIFIC, xi.ja.SHIELD_STRIKE }) + mob:addGambit(ai.t.TARGET, { ai.c.CASTING_ELE_MA_AOE, 0 }, { ai.r.MS, ai.s.SPECIFIC, 3714 }) -- Shield Strike mob:addGambit(ai.t.TARGET, { ai.c.STATUS, xi.effect.MANAFONT }, { ai.r.JA, ai.s.SPECIFIC, xi.ja.RAMPART }) mob:addGambit(ai.t.TARGET, { ai.c.STATUS, xi.effect.CHAINSPELL }, { ai.r.JA, ai.s.SPECIFIC, xi.ja.RAMPART }) mob:addGambit(ai.t.TARGET, { ai.c.STATUS, xi.effect.ASTRAL_FLOW }, { ai.r.JA, ai.s.SPECIFIC, xi.ja.RAMPART }) @@ -80,17 +80,18 @@ spellObject.onMobSpawn = function(mob) mob:addGambit(ai.t.SELF, { ai.c.NOT_STATUS, xi.effect.ENLIGHT }, { ai.r.MA, ai.s.SPECIFIC, xi.magic.spell.ENLIGHT }) mob:addGambit(ai.t.SELF, { ai.c.NOT_STATUS, xi.effect.DIVINE_EMBLEM }, { ai.r.JA, ai.s.SPECIFIC, xi.ja.DIVINE_EMBLEM }) mob:addGambit(ai.t.SELF, { ai.c.NOT_STATUS, xi.effect.PALISADE }, { ai.r.MA, ai.s.SPECIFIC, xi.magic.spell.PALISADE }) - + -- 2 conditions mob:addGambit(ai.t.SELF, { { ai.c.MPP_LT, 25 }, { ai.c.TP_GTE, 1000 }, }, { ai.r.JA, ai.s.SPECIFIC, xi.ja.CHIVALRY }) - mob:setTrustTPSkillSettings(ai.tp.CLOSER_UNTIL_TP, ai.s.HIGHEST, 2000) + mob:setTrustTPSkillSettings(ai.tp.CLOSER_UNTIL_TP, ai.s.RANDOM, 2000) mob:addListener('WEAPONSKILL_USE', 'AAEV_WEAPONSKILL_USE', function(mobArg, target, wsid, tp, action) - --if wsid == 61 then -- - -- + if wsid == 3710 then xi.trust.message(mobArg, xi.trust.messageOffset.SPECIAL_MOVE_1) - --end + elseif wsid == 3712 then + xi.trust.message(mobArg, xi.trust.messageOffset.SPECIAL_MOVE_2) + end end) end diff --git a/sql/mob_skills.sql b/sql/mob_skills.sql index ce139604d8b..51698502bfc 100644 --- a/sql/mob_skills.sql +++ b/sql/mob_skills.sql @@ -3737,11 +3737,11 @@ INSERT INTO `mob_skills` VALUES (3706,633,'cross_reaver',4,0.0,7.0,2000,1500,4,0 INSERT INTO `mob_skills` VALUES (3708,641,'swift_blade',0,0.0,7.0,2000,1500,4,0,0,0,9,0,0); INSERT INTO `mob_skills` VALUES (3709,2283,'chant_du_cygne',0,0.0,7.0,2000,1500,4,0,0,0,13,10,0); -- Capture shows 235. Adding 2048 for it to work properly. -- Trust: AAEV -INSERT INTO `mob_skills` VALUES (3710,2285,'arrogance_incarnate',1,0.0,7.0,2000,1500,4,0,0,0,0,0,0); -- Capture shows 237. Adding 2048 for it to work properly. -INSERT INTO `mob_skills` VALUES (3711,643,'vorpal_blade',0,0.0,7.0,2000,1500,4,0,0,0,4,8,0); -INSERT INTO `mob_skills` VALUES (3712,635,'dominion_slash',0,0.0,7.0,2000,1500,4,0,0,0,0,0,0); -INSERT INTO `mob_skills` VALUES (3713,2284,'chant_du_cygne',0,0.0,7.0,2000,1500,4,0,0,0,13,10,0); -- Capture shows 236. Adding 2048 for it to work properly. -INSERT INTO `mob_skills` VALUES (3714,638,'shield_strike',0,0.0,7.0,2000,1500,4,0,0,0,0,0,0); +INSERT INTO `mob_skills` VALUES (3710,2285,'arrogance_incarnate',1,0.0,7.0,2000,1000,4,0,0,0,0,0,0); -- Capture shows 237. Adding 2048 for it to work properly. +INSERT INTO `mob_skills` VALUES (3711,643,'vorpal_blade',0,0.0,7.0,2000,1000,4,0,0,0,4,8,0); +INSERT INTO `mob_skills` VALUES (3712,635,'dominion_slash',0,0.0,7.0,2000,1000,4,0,0,0,0,0,0); +INSERT INTO `mob_skills` VALUES (3713,2284,'chant_du_cygne',0,0.0,7.0,2000,1000,4,0,0,0,13,10,0); -- Capture shows 236. Adding 2048 for it to work properly. +INSERT INTO `mob_skills` VALUES (3714,638,'shield_strike',0,0.0,7.0,2000,0,4,0,0,0,0,0,0); INSERT INTO `mob_skills` VALUES (3715,644,'rampage',0,0.0,7.0,2000,1500,4,0,0,0,4,0,0); -- AAMR INSERT INTO `mob_skills` VALUES (3716,3460,'calamity',0,0.0,7.0,2000,1500,4,0,0,0,4,8,0); -- AAMR INSERT INTO `mob_skills` VALUES (3717,634,'havoc_spiral',1,0.0,7.0,2000,1500,4,0,0,0,0,0,0); -- AAMR diff --git a/src/map/ai/helpers/gambits_container.cpp b/src/map/ai/helpers/gambits_container.cpp index 1388dfb3c98..e3a95350b89 100644 --- a/src/map/ai/helpers/gambits_container.cpp +++ b/src/map/ai/helpers/gambits_container.cpp @@ -708,6 +708,11 @@ bool CGambitsContainer::CheckTrigger(const CBattleEntity* triggerTarget, Predica { TracyZoneScoped; + if (triggerTarget == nullptr) + { + return false; + } + auto* controller = static_cast(POwner->PAI->GetController()); std::vector predicateResults; @@ -950,10 +955,10 @@ bool CGambitsContainer::CheckTrigger(const CBattleEntity* triggerTarget, Predica bool isAOE = false; if (triggerTarget->PAI->IsCurrentState()) { - auto spellFamily = static_cast(triggerTarget->PAI->GetCurrentState())->GetSpell()->getSpellFamily(); - if ((spellFamily >= SPELLFAMILY::SPELLFAMILY_FIRAGA && spellFamily <= SPELLFAMILY::SPELLFAMILY_WATERGA) || - (spellFamily >= SPELLFAMILY::SPELLFAMILY_FIRA && spellFamily <= SPELLFAMILY::SPELLFAMILY_WATERA) || - spellFamily == SPELLFAMILY::SPELLFAMILY_JA) + auto spellElement = static_cast(triggerTarget->PAI->GetCurrentState())->GetSpell()->getElement(); + auto isElementalMA = spellElement >= ELEMENT_FIRE && spellElement <= ELEMENT_WATER; + auto spellAOEType = static_cast(triggerTarget->PAI->GetCurrentState())->GetSpell()->getAOE(); + if (isElementalMA && spellAOEType == SPELLAOE_RADIAL) { isAOE = true; } @@ -966,11 +971,8 @@ bool CGambitsContainer::CheckTrigger(const CBattleEntity* triggerTarget, Predica bool isElementalMA = false; if (triggerTarget->PAI->IsCurrentState()) { - auto spellFamily = static_cast(triggerTarget->PAI->GetCurrentState())->GetSpell()->getSpellFamily(); - isElementalMA = ((spellFamily >= SPELLFAMILY::SPELLFAMILY_FIRE && spellFamily <= SPELLFAMILY::SPELLFAMILY_FLOOD) || - (spellFamily >= SPELLFAMILY::SPELLFAMILY_FIRA && spellFamily <= SPELLFAMILY::SPELLFAMILY_WATERA) || - spellFamily == SPELLFAMILY::SPELLFAMILY_JA); - + auto spellElement = static_cast(triggerTarget->PAI->GetCurrentState())->GetSpell()->getElement(); + isElementalMA = spellElement >= ELEMENT_FIRE && spellElement <= ELEMENT_WATER; } predicateResults.push_back(isElementalMA); continue; @@ -980,11 +982,9 @@ bool CGambitsContainer::CheckTrigger(const CBattleEntity* triggerTarget, Predica bool isElementalMAOnSelf = false; if (triggerTarget->PAI->IsCurrentState()) { - auto spellFamily = static_cast(triggerTarget->PAI->GetCurrentState())->GetSpell()->getSpellFamily(); + auto spellElement = static_cast(triggerTarget->PAI->GetCurrentState())->GetSpell()->getElement(); auto targetID = static_cast(triggerTarget->PAI->GetCurrentState())->GetTarget()->id; - bool isElementalMA = ((spellFamily >= SPELLFAMILY::SPELLFAMILY_FIRE && spellFamily <= SPELLFAMILY::SPELLFAMILY_FLOOD) || - (spellFamily >= SPELLFAMILY::SPELLFAMILY_FIRA && spellFamily <= SPELLFAMILY::SPELLFAMILY_WATERA) || - spellFamily == SPELLFAMILY::SPELLFAMILY_JA); + bool isElementalMA = spellElement >= ELEMENT_FIRE && spellElement <= ELEMENT_WATER; if (targetID == POwner->id && isElementalMA) { isElementalMAOnSelf = true; @@ -998,58 +998,34 @@ bool CGambitsContainer::CheckTrigger(const CBattleEntity* triggerTarget, Predica bool needBarEffect = false; if (triggerTarget->PAI->IsCurrentState()) { - auto spellFamily = static_cast(triggerTarget->PAI->GetCurrentState())->GetSpell()->getSpellFamily(); - auto targetID = static_cast(triggerTarget->PAI->GetCurrentState())->GetTarget()->id; - uint32 element = 0; - if (targetID == POwner->id && ((spellFamily >= SPELLFAMILY::SPELLFAMILY_FIRE && spellFamily <= SPELLFAMILY::SPELLFAMILY_FLOOD) || - (spellFamily >= SPELLFAMILY::SPELLFAMILY_FIRA && spellFamily <= SPELLFAMILY::SPELLFAMILY_WATERA) || - spellFamily == SPELLFAMILY::SPELLFAMILY_JA)) + auto spellElement = static_cast(triggerTarget->PAI->GetCurrentState())->GetSpell()->getElement(); + + switch (spellElement) { - switch (spellFamily) - { - case SPELLFAMILY::SPELLFAMILY_FIRE: - case SPELLFAMILY::SPELLFAMILY_FLARE: - case SPELLFAMILY::SPELLFAMILY_FIRA: - needBarEffect = !POwner->StatusEffectContainer->HasStatusEffect(EFFECT_BARFIRE); - element = ELEMENT_FIRE; - break; - case SPELLFAMILY::SPELLFAMILY_BLIZZARD: - case SPELLFAMILY::SPELLFAMILY_FREEZE: - case SPELLFAMILY::SPELLFAMILY_BLIZZARA: - needBarEffect = !POwner->StatusEffectContainer->HasStatusEffect(EFFECT_BARBLIZZARD); - element = ELEMENT_ICE; - break; - case SPELLFAMILY::SPELLFAMILY_AERO: - case SPELLFAMILY::SPELLFAMILY_TORNADO: - case SPELLFAMILY::SPELLFAMILY_AERORA: - needBarEffect = !POwner->StatusEffectContainer->HasStatusEffect(EFFECT_BARAERO); - element = ELEMENT_WIND; - break; - case SPELLFAMILY::SPELLFAMILY_STONE: - case SPELLFAMILY::SPELLFAMILY_QUAKE: - case SPELLFAMILY::SPELLFAMILY_STONERA: - needBarEffect = !POwner->StatusEffectContainer->HasStatusEffect(EFFECT_BARSTONE); - element = ELEMENT_EARTH; - break; - case SPELLFAMILY::SPELLFAMILY_THUNDER: - case SPELLFAMILY::SPELLFAMILY_BURST: - case SPELLFAMILY::SPELLFAMILY_THUNDARA: - needBarEffect = !POwner->StatusEffectContainer->HasStatusEffect(EFFECT_BARTHUNDER); - element = ELEMENT_THUNDER; - break; - case SPELLFAMILY::SPELLFAMILY_WATER: - case SPELLFAMILY::SPELLFAMILY_FLOOD: - case SPELLFAMILY::SPELLFAMILY_WATERA: - needBarEffect = !POwner->StatusEffectContainer->HasStatusEffect(EFFECT_BARWATER); - element = ELEMENT_WATER; - break; - default: - needBarEffect = false; - element = battleutils::GetDayElement(); - break; - } - POwner->SetLocalVar("[Gambit]CastElement", element); + case ELEMENT_FIRE: + needBarEffect = !POwner->StatusEffectContainer->HasStatusEffect(EFFECT_BARFIRE); + break; + case ELEMENT_ICE: + needBarEffect = !POwner->StatusEffectContainer->HasStatusEffect(EFFECT_BARBLIZZARD); + break; + case ELEMENT_WIND: + needBarEffect = !POwner->StatusEffectContainer->HasStatusEffect(EFFECT_BARAERO); + break; + case ELEMENT_EARTH: + needBarEffect = !POwner->StatusEffectContainer->HasStatusEffect(EFFECT_BARSTONE); + break; + case ELEMENT_THUNDER: + needBarEffect = !POwner->StatusEffectContainer->HasStatusEffect(EFFECT_BARTHUNDER); + break; + case ELEMENT_WATER: + needBarEffect = !POwner->StatusEffectContainer->HasStatusEffect(EFFECT_BARWATER); + break; + default: + needBarEffect = false; + spellElement = (uint16)battleutils::GetDayElement(); + break; } + POwner->SetLocalVar("[Gambit]CastElement", spellElement); } predicateResults.push_back(needBarEffect); continue; @@ -1184,7 +1160,39 @@ bool CGambitsContainer::TryTrustSkill() { case G_SELECT::RANDOM: { - chosen_skill = xirand::GetRandomElement(tp_skills); + auto* PSCEffect = target->StatusEffectContainer->GetStatusEffect(EFFECT_SKILLCHAIN); + + if (!PSCEffect) // Opener, if no skillchain available select a random ws + { + chosen_skill = xirand::GetRandomElement(tp_skills); + break; + } + + // Closer, if a skillchain is available select a random ws that can close it, if multiple are available select the one that creates the best skillchain + for (auto& skill : tp_skills) + { + std::list resonanceProperties; + if (uint16 power = PSCEffect->GetPower()) + { + resonanceProperties.emplace_back((SKILLCHAIN_ELEMENT)(power & 0xF)); + resonanceProperties.emplace_back((SKILLCHAIN_ELEMENT)(power >> 4 & 0xF)); + resonanceProperties.emplace_back((SKILLCHAIN_ELEMENT)(power >> 8)); + } + + std::list skillProperties; + skillProperties.emplace_back((SKILLCHAIN_ELEMENT)skill.primary); + skillProperties.emplace_back((SKILLCHAIN_ELEMENT)skill.secondary); + skillProperties.emplace_back((SKILLCHAIN_ELEMENT)skill.tertiary); + if (SKILLCHAIN_ELEMENT possible_skillchain = battleutils::FormSkillchain(resonanceProperties, skillProperties); + possible_skillchain != SC_NONE) + { + if (possible_skillchain >= chosen_skillchain) + { + chosen_skill = skill; + chosen_skillchain = possible_skillchain; + } + } + } break; } case G_SELECT::HIGHEST: // Form the best possible skillchain From 3430df2dadc6a93db4683427098b8a13c52a1938 Mon Sep 17 00:00:00 2001 From: MrSent Date: Thu, 19 Mar 2026 13:16:04 +0000 Subject: [PATCH 3/4] [trust] AAEV fixed palisade using MA not JA --- scripts/actions/spells/trust/aaev.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/actions/spells/trust/aaev.lua b/scripts/actions/spells/trust/aaev.lua index fead763027a..1670411cd61 100644 --- a/scripts/actions/spells/trust/aaev.lua +++ b/scripts/actions/spells/trust/aaev.lua @@ -79,7 +79,7 @@ spellObject.onMobSpawn = function(mob) mob:addGambit(ai.t.SELF, { ai.c.NOT_STATUS, xi.effect.SENTINEL }, { ai.r.JA, ai.s.SPECIFIC, xi.ja.SENTINEL }) mob:addGambit(ai.t.SELF, { ai.c.NOT_STATUS, xi.effect.ENLIGHT }, { ai.r.MA, ai.s.SPECIFIC, xi.magic.spell.ENLIGHT }) mob:addGambit(ai.t.SELF, { ai.c.NOT_STATUS, xi.effect.DIVINE_EMBLEM }, { ai.r.JA, ai.s.SPECIFIC, xi.ja.DIVINE_EMBLEM }) - mob:addGambit(ai.t.SELF, { ai.c.NOT_STATUS, xi.effect.PALISADE }, { ai.r.MA, ai.s.SPECIFIC, xi.magic.spell.PALISADE }) + mob:addGambit(ai.t.SELF, { ai.c.NOT_STATUS, xi.effect.PALISADE }, { ai.r.JA, ai.s.SPECIFIC, xi.ja.PALISADE }) -- 2 conditions mob:addGambit(ai.t.SELF, { { ai.c.MPP_LT, 25 }, { ai.c.TP_GTE, 1000 }, }, { ai.r.JA, ai.s.SPECIFIC, xi.ja.CHIVALRY }) From ea57bf8dfc24ca5d7c4c811857b1889eb7ef0fea Mon Sep 17 00:00:00 2001 From: MrSent Date: Thu, 19 Mar 2026 15:59:17 +0000 Subject: [PATCH 4/4] [sql,lua] AAEV shield mastery power lvl range, reprisal spell --- scripts/actions/spells/trust/aaev.lua | 17 +++++++++++++++++ sql/mob_spell_lists.sql | 1 + 2 files changed, 18 insertions(+) diff --git a/scripts/actions/spells/trust/aaev.lua b/scripts/actions/spells/trust/aaev.lua index 1670411cd61..325b8953f0f 100644 --- a/scripts/actions/spells/trust/aaev.lua +++ b/scripts/actions/spells/trust/aaev.lua @@ -15,6 +15,23 @@ end spellObject.onMobSpawn = function(mob) xi.trust.message(mob, xi.trust.messageOffset.SPAWN) + mob:setMobMod(xi.mobMod.CAN_SHIELD_BLOCK, 1) + + local lvl = mob:getMainLvl() + local shieldMasteryPower = 0 + + if lvl >= 96 then + shieldMasteryPower = 40 + elseif lvl >= 75 then + shieldMasteryPower = 30 + elseif lvl >= 50 then + shieldMasteryPower = 20 + elseif lvl >= 25 then + shieldMasteryPower = 10 + end + + mob:setMod(xi.mod.SHIELD_MASTERY_TP, shieldMasteryPower) + mob:setMod(xi.mod.SHIELDBLOCKRATE, 45) -- 45% base block rate mob:addMod(xi.mod.FASTCAST, 30) mob:addMod(xi.mod.CURE_POTENCY, 50) mob:addMod(xi.mod.DMG, -10) diff --git a/sql/mob_spell_lists.sql b/sql/mob_spell_lists.sql index 0c0d78213d8..3287aee686f 100644 --- a/sql/mob_spell_lists.sql +++ b/sql/mob_spell_lists.sql @@ -4104,6 +4104,7 @@ INSERT INTO `mob_spell_lists` VALUES ('TRUST_AAEV',406,3,30,255); -- cure_iii INSERT INTO `mob_spell_lists` VALUES ('TRUST_AAEV',406,4,55,255); -- cure_iv (55~255) INSERT INTO `mob_spell_lists` VALUES ('TRUST_AAEV',406,21,55,255); -- holy (55~255) INSERT INTO `mob_spell_lists` VALUES ('TRUST_AAEV',406,22,99,255); -- holy_ii (99~255) +INSERT INTO `mob_spell_lists` VALUES ('TRUST_AAEV',406,97,61,255); -- reprisal (61~255) INSERT INTO `mob_spell_lists` VALUES ('TRUST_AAEV',406,106,77,255); -- phalanx (77~255) INSERT INTO `mob_spell_lists` VALUES ('TRUST_AAEV',406,112,37,255); -- flash (37~255) INSERT INTO `mob_spell_lists` VALUES ('TRUST_AAEV',406,310,85,255); -- enlight (85~255)