diff --git a/cdtweaks/languages/english/parry.tra b/cdtweaks/languages/english/parry.tra
new file mode 100644
index 00000000..17df2365
--- /dev/null
+++ b/cdtweaks/languages/english/parry.tra
@@ -0,0 +1,13 @@
+@0 = "Parry Mode"
+@1 = "Parry Mode
+
+Parry allows the character to block incoming attacks and make spectacular counterattacks.
+A successful parry (save vs. Breath) means that the attack does not damage the parrying character."
+
+@2 = "Parry Mode On"
+
+@3 = "Parry Mode Off"
+
+@4 = "Parry (Success)"
+
+@5 = "Riposte Attack"
\ No newline at end of file
diff --git a/cdtweaks/languages/english/weidu.tra b/cdtweaks/languages/english/weidu.tra
index e0fdc267..6aef6312 100644
--- a/cdtweaks/languages/english/weidu.tra
+++ b/cdtweaks/languages/english/weidu.tra
@@ -801,6 +801,7 @@ Use Baldur.lua options: a7_interval_ini
///// \\\\\
/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\
/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\
+
@603000 = "Spontaneous Casting for Clerics [Luke]"
@604000 = "Weapon Finesse class feat for Thieves [Luke]"
@@ -833,4 +834,6 @@ Use Baldur.lua options: a7_interval_ini
@619000 = "Dirty Fighting class feat for Chaotic-aligned Rogues [Luke (EEex)]"
-@621000 = "Self Concealment class feat for Monks [Luke (EEex)]"
\ No newline at end of file
+@621000 = "Self Concealment class feat for Monks [Luke (EEex)]"
+
+@622000 = "Parry Mode kit feat for Blades e Swashbucklers [Luke (EEex)]"
diff --git a/cdtweaks/languages/italian/parry.tra b/cdtweaks/languages/italian/parry.tra
new file mode 100644
index 00000000..99b4e7c9
--- /dev/null
+++ b/cdtweaks/languages/italian/parry.tra
@@ -0,0 +1,13 @@
+@0 = "Modalità Parata"
+@1 = "Modalità Parata
+
+La Modalità Parata consente al personaggio di bloccare gli attacchi ed effettuare spettacolari controattacchi.
+Se un attacco viene parato con successo (tiro-salvezza contro Soffio), esso non danneggerà il personaggio."
+
+@2 = "Modalità Parata Attivata"
+
+@3 = "Modalità Parata Disattivata"
+
+@4 = "Modalità Parata (Successo)"
+
+@5 = "Controattacco"
\ No newline at end of file
diff --git a/cdtweaks/languages/italian/weidu.tra b/cdtweaks/languages/italian/weidu.tra
index 065a522d..c0361952 100644
--- a/cdtweaks/languages/italian/weidu.tra
+++ b/cdtweaks/languages/italian/weidu.tra
@@ -748,3 +748,5 @@ Usa opzioni di Baldur.lua: a7_interval_ini
@619000 = "Aggiungi talento di classe Combattimento Scorretto per i Ladri di allineamento Caotico [Luke (EEex)]"
@621000 = "Aggiungi talento di classe Auto-Occultamento per i Monaci [Luke (EEex)]"
+
+@622000 = "Aggiungi talento di classe Modalità Parata per Bardi Lama e Rodomonti [Luke (EEex)]"
diff --git a/cdtweaks/lib/comp_6220.tpa b/cdtweaks/lib/comp_6220.tpa
new file mode 100644
index 00000000..70122bb5
--- /dev/null
+++ b/cdtweaks/lib/comp_6220.tpa
@@ -0,0 +1,18 @@
+/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\////\\\\////
+/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\////\\\\////
+///// \\\\\////\\\\////
+///// Parry Mode kit feat for Blades and Swashbucklers \\\\\
+///// \\\\\////\\\\////
+/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\////\\\\////
+/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\////\\\\////
+
+WITH_SCOPE BEGIN
+ INCLUDE "cdtweaks\luke\misc.tph"
+ INCLUDE "cdtweaks\ardanis\functions.tph"
+ //
+ INCLUDE "cdtweaks\lib\parry.tph"
+ //
+ WITH_TRA "cdtweaks\languages\english\parry.tra" "cdtweaks\languages\%LANGUAGE%\parry.tra" BEGIN
+ LAF "PARRY" END
+ END
+END
\ No newline at end of file
diff --git a/cdtweaks/lib/parry.tph b/cdtweaks/lib/parry.tph
new file mode 100644
index 00000000..47a1bcda
--- /dev/null
+++ b/cdtweaks/lib/parry.tph
@@ -0,0 +1,117 @@
+DEFINE_ACTION_FUNCTION "PARRY"
+BEGIN
+ LAF "GT_ADD_SPELL"
+ INT_VAR
+ "type" = 4
+ "level" = 5
+ STR_VAR
+ "idsName" = "BLADE_SWASHBUCKLER_PARRY"
+ RET
+ "BLADE_SWASHBUCKLER_PARRY" = "resName"
+ END
+ //
+ LAF "ADD_EXTENDED_STAT" INT_VAR "max" = 30 STR_VAR "identifier" = "GT_NUMBER_OF_ATTACKS_PARRIED" END
+ //
+ WITH_SCOPE BEGIN
+ ACTION_TO_LOWER "BLADE_SWASHBUCKLER_PARRY"
+ //
+ COPY "cdtweaks\luke\bam\kit\parry\portrait_icon.bam" "override\%BLADE_SWASHBUCKLER_PARRY%d.bam"
+ COPY "cdtweaks\luke\bam\kit\parry\spl_icon.bam" "override\%BLADE_SWASHBUCKLER_PARRY%b.bam"
+ // main
+ CREATE "spl" "%BLADE_SWASHBUCKLER_PARRY%"
+ COPY_EXISTING "%BLADE_SWASHBUCKLER_PARRY%.spl" "override"
+ WRITE_LONG NAME1 RESOLVE_STR_REF (@0)
+ WRITE_LONG UNIDENTIFIED_DESC RESOLVE_STR_REF (@1)
+ WRITE_LONG DESC "-1"
+ WRITE_LONG NAME2 "-1"
+ WRITE_LONG 0x18 (BIT14 BOR BIT25) // ignore dead/wild magic, castable when silenced
+ WRITE_SHORT 0x1C 4 // type: innate
+ WRITE_LONG 0x34 1 // level
+ WRITE_ASCII 0x3A "%DEST_RES%B" #8 // icon
+ //
+ LPF "ADD_SPELL_HEADER" INT_VAR "target" = 5 "range" = 30 STR_VAR "icon" = "%DEST_RES%B" END
+ //
+ LPF "ADD_SPELL_EFFECT" INT_VAR "opcode" = 138 "target" = 1 "parameter2" = 7 END // SEQ_READY
+ LPF "ADD_SPELL_EFFECT" INT_VAR "opcode" = 174 "target" = 1 STR_VAR "resource" = "EFF_M11B" END // play sound
+ LPF "ADD_SPELL_EFFECT" INT_VAR "opcode" = 139 "target" = 1 "parameter1" = RESOLVE_STR_REF (@2) END // feedback string
+ //
+ LPF "ADD_SPELL_EFFECT" INT_VAR "opcode" = 402 "target" = 1 STR_VAR "resource" = "%DEST_RES%" END // invoke lua
+ BUT_ONLY
+ // cancel
+ CREATE "spl" "%BLADE_SWASHBUCKLER_PARRY%b"
+ COPY_EXISTING "%BLADE_SWASHBUCKLER_PARRY%b.spl" "override"
+ WRITE_LONG NAME1 RESOLVE_STR_REF (@3)
+ WRITE_LONG UNIDENTIFIED_DESC "-1"
+ WRITE_LONG DESC "-1"
+ WRITE_LONG NAME2 "-1"
+ WRITE_LONG 0x18 (BIT14 BOR BIT25) // ignore dead/wild magic, castable when silenced
+ WRITE_SHORT 0x1C 4 // type: innate
+ WRITE_LONG 0x34 1 // level
+ WRITE_ASCII 0x3A "%DEST_RES%" #8
+ //
+ LPF "ADD_SPELL_HEADER" INT_VAR "target" = 5 "range" = 30 STR_VAR "icon" = "%DEST_RES%" END
+ //
+ LPF "ADD_SPELL_EFFECT" INT_VAR "opcode" = 402 "target" = 1 "parameter1" = 1 STR_VAR "resource" = "%BLADE_SWASHBUCKLER_PARRY%" END // invoke lua
+ BUT_ONLY
+ // SEQ_ATTACK + feedback string
+ CREATE "spl" "%BLADE_SWASHBUCKLER_PARRY%e"
+ COPY_EXISTING "%BLADE_SWASHBUCKLER_PARRY%e.spl" "override"
+ WRITE_LONG NAME1 "-1"
+ WRITE_LONG UNIDENTIFIED_DESC "-1"
+ WRITE_LONG DESC "-1"
+ WRITE_LONG NAME2 "-1"
+ WRITE_LONG 0x18 (BIT14 BOR BIT25) // ignore dead/wild magic, castable when silenced
+ WRITE_SHORT 0x1C 4 // type: innate
+ WRITE_LONG 0x34 1 // level
+ WRITE_ASCII 0x3A "%DEST_RES%" #8
+ //
+ LPF "ADD_SPELL_HEADER" INT_VAR "range" = 30 STR_VAR "icon" = "%DEST_RES%" END
+ //
+ LPF "ADD_SPELL_EFFECT" INT_VAR "opcode" = 138 "target" = 1 "parameter2" = 0 END // set animation (ATTACK)
+ LPF "ADD_SPELL_EFFECT" INT_VAR "opcode" = 139 "target" = 1 "parameter1" = RESOLVE_STR_REF (@4) END // "Parry (Success)"
+ BUT_ONLY
+ // riposte attack
+ CREATE "spl" "%BLADE_SWASHBUCKLER_PARRY%f"
+ COPY_EXISTING "%BLADE_SWASHBUCKLER_PARRY%f.spl" "override"
+ WRITE_LONG NAME1 RESOLVE_STR_REF (@5)
+ WRITE_LONG UNIDENTIFIED_DESC "-1"
+ WRITE_LONG DESC "-1"
+ WRITE_LONG NAME2 "-1"
+ WRITE_LONG 0x18 (BIT14 BOR BIT25) // ignore dead/wild magic, castable when silenced
+ WRITE_SHORT 0x1C 4 // type: innate
+ WRITE_LONG 0x34 1 // level
+ WRITE_ASCII 0x3A "%BLADE_SWASHBUCKLER_PARRY%B" #8
+ //
+ LPF "ADD_SPELL_HEADER" INT_VAR "range" = 30 STR_VAR "icon" = "%BLADE_SWASHBUCKLER_PARRY%B" END
+ //
+ LPF "ADD_SPELL_EFFECT" INT_VAR "opcode" = 402 "target" = 2 "parameter1" = 2 STR_VAR "resource" = "%BLADE_SWASHBUCKLER_PARRY%" END // invoke lua
+ BUT_ONLY
+ // SEQ_READY
+ CREATE "spl" "%BLADE_SWASHBUCKLER_PARRY%g"
+ COPY_EXISTING "%BLADE_SWASHBUCKLER_PARRY%g.spl" "override"
+ WRITE_LONG NAME1 "-1"
+ WRITE_LONG UNIDENTIFIED_DESC "-1"
+ WRITE_LONG DESC "-1"
+ WRITE_LONG NAME2 "-1"
+ WRITE_LONG 0x18 (BIT14 BOR BIT25) // ignore dead/wild magic, castable when silenced
+ WRITE_SHORT 0x1C 4 // type: innate
+ WRITE_LONG 0x34 1 // level
+ WRITE_ASCII 0x3A "%BLADE_SWASHBUCKLER_PARRY%B" #8
+ //
+ LPF "ADD_SPELL_HEADER" INT_VAR "target" = 5 "range" = 30 STR_VAR "icon" = "%BLADE_SWASHBUCKLER_PARRY%B" END
+ //
+ LPF "ADD_SPELL_EFFECT" INT_VAR "opcode" = 138 "target" = 1 "parameter2" = 7 END // set animation (READY)
+ BUT_ONLY
+ END
+ // lua
+ WITH_SCOPE BEGIN
+ LAF "ADD_STATDESC_ENTRY" INT_VAR "description" = RESOLVE_STR_REF (@0) STR_VAR "bam_file" = "%BLADE_SWASHBUCKLER_PARRY%D" RET "feedback_icon" = "index" END
+ //
+ LAF "APPEND_LUA_FUNCTION" STR_VAR "description" = "Class/Kit Abilities" "sourceFileSpec" = "cdtweaks\luke\lua\kit\parry.lua" "destRes" = "m_gtspcl" END
+ LAF "APPEND_LUA_FUNCTION" STR_VAR "description" = "AI-related stuff" "sourceFileSpec" = "cdtweaks\luke\lua\ai\object_type.lua" "destRes" = "m_gt#ai" END
+ END
+ //
+ ACTION_IF !(FILE_EXISTS_IN_GAME "m_gttbls.lua") BEGIN
+ COPY "cdtweaks\luke\lua\m_gttbls.lua" "override"
+ END
+END
\ No newline at end of file
diff --git a/cdtweaks/luke/bam/kit/parry/portrait_icon.bam b/cdtweaks/luke/bam/kit/parry/portrait_icon.bam
new file mode 100644
index 00000000..f8e33ac2
Binary files /dev/null and b/cdtweaks/luke/bam/kit/parry/portrait_icon.bam differ
diff --git a/cdtweaks/luke/bam/kit/parry/spl_icon.bam b/cdtweaks/luke/bam/kit/parry/spl_icon.bam
new file mode 100644
index 00000000..36ef708c
Binary files /dev/null and b/cdtweaks/luke/bam/kit/parry/spl_icon.bam differ
diff --git a/cdtweaks/luke/lua/kit/parry.lua b/cdtweaks/luke/lua/kit/parry.lua
new file mode 100644
index 00000000..9ddbc1b9
--- /dev/null
+++ b/cdtweaks/luke/lua/kit/parry.lua
@@ -0,0 +1,487 @@
+--[[
++-----------------------------------------------------------+
+| cdtweaks, NWN-ish Parry mode for Blades and Swashbucklers |
++-----------------------------------------------------------+
+--]]
+
+-- Gain ability --
+
+EEex_Opcode_AddListsResolvedListener(function(sprite)
+ -- Sanity check
+ if not EEex_GameObject_IsSprite(sprite) then
+ return
+ end
+ -- internal function that grants the ability
+ local gain = function()
+ -- Mark the creature as 'feat granted'
+ sprite:setLocalInt("gtRogueParry", 1)
+ --
+ local effectCodes = {
+ {["op"] = 172}, -- remove spell
+ {["op"] = 171}, -- give spell
+ }
+ --
+ for _, attributes in ipairs(effectCodes) do
+ sprite:applyEffect({
+ ["effectID"] = attributes["op"] or EEex_Error("opcode number not specified"),
+ ["res"] = "%BLADE_SWASHBUCKLER_PARRY%",
+ ["sourceID"] = sprite.m_id,
+ ["sourceTarget"] = sprite.m_id,
+ })
+ end
+ end
+ -- Check creature's class / kit
+ local spriteClassStr = GT_Resource_IDSToSymbol["class"][sprite.m_typeAI.m_Class]
+ --
+ local spriteFlags = sprite.m_baseStats.m_flags
+ -- since ``EEex_Opcode_AddListsResolvedListener`` is running after the effect lists have been evaluated, ``m_bonusStats`` has already been added to ``m_derivedStats`` by the engine
+ local spriteLevel1 = sprite.m_derivedStats.m_nLevel1
+ local spriteLevel2 = sprite.m_derivedStats.m_nLevel2
+ local spriteKitStr = GT_Resource_IDSToSymbol["kit"][sprite.m_derivedStats.m_nKit]
+ --
+ local gainAbility = (spriteClassStr == "BARD" and spriteKitStr == "BLADE")
+ or (spriteKitStr == "SWASHBUCKLER"
+ and ((spriteClassStr == "MAGE_THIEF" and (EEex_IsBitUnset(spriteFlags, 0x6) or spriteLevel1 > spriteLevel2))
+ or (spriteClassStr == "CLERIC_THIEF" and (EEex_IsBitUnset(spriteFlags, 0x6) or spriteLevel1 > spriteLevel2))
+ or (spriteClassStr == "FIGHTER_THIEF" and (EEex_IsBitUnset(spriteFlags, 0x6) or spriteLevel1 > spriteLevel2))
+ or (spriteClassStr == "THIEF")))
+ --
+ if sprite:getLocalInt("gtRogueParry") == 0 then
+ if gainAbility then
+ gain()
+ end
+ else
+ if gainAbility then
+ -- do nothing
+ else
+ -- Mark the creature as 'feat removed'
+ sprite:setLocalInt("gtRogueParry", 0)
+ --
+ if EEex_Sprite_GetLocalInt(sprite, "gtParryMode") == 1 then
+ sprite:applyEffect({
+ ["effectID"] = 146, -- Cast spell
+ ["dwFlags"] = 1, -- instant/ignore level
+ ["res"] = "%BLADE_SWASHBUCKLER_PARRY%B",
+ ["sourceID"] = sprite.m_id,
+ ["sourceTarget"] = sprite.m_id,
+ })
+ end
+ --
+ sprite:applyEffect({
+ ["effectID"] = 172, -- remove spell
+ ["res"] = "%BLADE_SWASHBUCKLER_PARRY%",
+ ["sourceID"] = sprite.m_id,
+ ["sourceTarget"] = sprite.m_id,
+ })
+ end
+ end
+end)
+
+-- save vs. breath to parry an incoming attack; the higher DEX, the easier is to succeed --
+
+local cdtweaks_ParryMode_AttacksPerRound = {0, 1, 2, 3, 4, 5, .5, 1.5, 2.5, 3.5, 4.5}
+local cdtweaks_ParryMode_AttacksPerRound_Haste = {0, 2, 4, 6, 8, 10, 1, 3, 5, 7, 9}
+
+EEex_Sprite_AddBlockWeaponHitListener(function(args)
+ local toReturn = false
+ --
+ local stats = GT_Resource_SymbolToIDS["stats"]
+ local state = GT_Resource_SymbolToIDS["state"]
+ --
+ local dexmod = GT_Resource_2DA["dexmod"]
+ --
+ local attackingWeapon = args.weapon -- CItem
+ local targetSprite = args.targetSprite -- CGameSprite
+ local attackingSprite = args.attackingSprite -- CGameSprite
+ local attackingWeaponAbility = args.weaponAbility -- Item_ability_st
+ --
+ local targetActiveStats = EEex_Sprite_GetActiveStats(targetSprite)
+ -- you cannot parry weapons with bare hands (only other bare hands)
+ local equipment = targetSprite.m_equipment
+ local targetWeapon = equipment.m_items:get(equipment.m_selectedWeapon) -- CItem
+ local targetWeaponHeader = targetWeapon.pRes.pHeader -- Item_Header_st
+ --
+ local attackingWeaponHeader = attackingWeapon.pRes.pHeader -- Item_Header_st
+ -- get # attacks
+ local targetNumberOfAttacks
+ if EEex_IsBitSet(targetActiveStats.m_generalState, 15) then -- if STATE_HASTED
+ targetNumberOfAttacks = cdtweaks_ParryMode_AttacksPerRound_Haste[EEex_Sprite_GetStat(targetSprite, stats["NUMBEROFATTACKS"]) + 1]
+ else
+ targetNumberOfAttacks = Infinity_RandomNumber(1, 2) == 1 and math.ceil(cdtweaks_ParryMode_AttacksPerRound[EEex_Sprite_GetStat(targetSprite, stats["NUMBEROFATTACKS"]) + 1]) or math.floor(cdtweaks_ParryMode_AttacksPerRound[EEex_Sprite_GetStat(targetSprite, stats["NUMBEROFATTACKS"]) + 1])
+ end
+ --
+ targetSprite:setStoredScriptingTarget("GT_ParryModeTarget", attackingSprite)
+ local conditionalString = EEex_Trigger_ParseConditionalString('OR(2) \n !Allegiance(Myself,GOODCUTOFF) InWeaponRange(EEex_Target("GT_ParryModeTarget") \n OR(2) \n !Allegiance(Myself,EVILCUTOFF) Range(EEex_Target("GT_ParryModeTarget"),4)') -- we intentionally let the AI cheat. In so doing, it can enter the mode without worrying about being in weapon range...
+ --
+ if targetSprite:getLocalInt("gtParryMode") == 1 then -- parry mode ON
+ if targetSprite.m_curAction.m_actionID == 0 and targetSprite.m_nSequence == 7 then -- idle/ready (in particular, you cannot parry while performing a riposte attack)
+ if EEex_BAnd(targetActiveStats.m_generalState, state["CD_STATE_NOTVALID"]) == 0 then -- incapacitated creatures cannot parry
+ if EEex_Sprite_GetStat(targetSprite, stats["GT_NUMBER_OF_ATTACKS_PARRIED"]) < targetNumberOfAttacks then -- you can parry at most X number of attacks per round, where X is the number of attacks of the parrying creature
+ if attackingWeaponAbility.type == 1 and attackingWeaponAbility.range <= 2 then -- only melee attacks can be parried
+ if attackingWeaponHeader.itemType == 28 or targetWeaponHeader.itemType ~= 28 then -- bare hands can only parry bare hands
+ if conditionalString:evalConditionalAsAIBase(targetSprite) then
+ if targetActiveStats.m_nSaveVSBreath - tonumber(dexmod[string.format("%s", targetActiveStats.m_nDEX)]["MISSILE"]) <= targetSprite.m_saveVSBreathRoll then
+ -- increment stats["GT_NUMBER_OF_ATTACKS_PARRIED"] by 1; reset to 0 after one round
+ local effectCodes = {
+ {["op"] = 401, ["p1"] = 1, ["spec"] = stats["GT_NUMBER_OF_ATTACKS_PARRIED"], ["tmg"] = 1, ["effsource"] = "%BLADE_SWASHBUCKLER_PARRY%C"}, -- EEex: Set Extended Stat
+ {["op"] = 321, ["res"] = "%BLADE_SWASHBUCKLER_PARRY%C", ["tmg"] = 4, ["dur"] = 6, ["effsource"] = "%BLADE_SWASHBUCKLER_PARRY%D"}, -- Remove effects by resource
+ {["op"] = 318, ["res"] = "%BLADE_SWASHBUCKLER_PARRY%D", ["dur"] = 6, ["effsource"] = "%BLADE_SWASHBUCKLER_PARRY%D"}, -- Protection from resource
+ }
+ --
+ for _, attributes in ipairs(effectCodes) do
+ targetSprite:applyEffect({
+ ["effectID"] = attributes["op"] or EEex_Error("opcode number not specified"),
+ ["effectAmount"] = attributes["p1"] or 0,
+ ["special"] = attributes["spec"] or 0,
+ ["res"] = attributes["res"] or "",
+ ["durationType"] = attributes["tmg"] or 0,
+ ["duration"] = attributes["dur"] or 0,
+ ["m_sourceRes"] = attributes["effsource"] or "",
+ ["sourceID"] = targetSprite.m_id,
+ ["sourceTarget"] = targetSprite.m_id,
+ })
+ end
+ -- initialize the attack frame counter
+ targetSprite.m_attackFrame = 0
+ -- store attacking ID
+ targetSprite:setLocalInt("gtParryModeAtkID", attackingSprite.m_id)
+ -- cast a dummy spl that performs the attack animation via op138 (p2=0)
+ attackingSprite:applyEffect({
+ ["effectID"] = 146, -- Cast spl
+ ["res"] = "%BLADE_SWASHBUCKLER_PARRY%E",
+ ["sourceID"] = targetSprite.m_id,
+ ["sourceTarget"] = attackingSprite.m_id,
+ })
+ -- block base weapon damage + on-hit effects (if any)
+ toReturn = true
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ --
+ conditionalString:free()
+ --
+ return toReturn
+end)
+
+-- cast a spl (riposte attack) when ``m_attackFrame`` is equal to 6 (that should be approx. the value corresponding to the weapon hit...?) --
+
+EEex_Opcode_AddListsResolvedListener(function(sprite)
+ -- Sanity check
+ if not EEex_GameObject_IsSprite(sprite) then
+ return
+ end
+ -- if the blade / swashbuckler gets hit while performing a riposte attack, the attack will be canceled
+ if sprite:getLocalInt("gtParryMode") == 1 and sprite.m_attackFrame == 6 and sprite.m_nSequence == 0 then
+ local attackingSprite = EEex_GameObject_Get(sprite:getLocalInt("gtParryModeAtkID"))
+ --
+ attackingSprite:applyEffect({
+ ["effectID"] = 146, -- Cast spl
+ ["dwFlags"] = 1, -- mode: instant / permanent
+ ["res"] = "%BLADE_SWASHBUCKLER_PARRY%F",
+ ["sourceID"] = sprite.m_id,
+ ["sourceTarget"] = attackingSprite.m_id,
+ })
+ end
+end)
+
+-- automatically cancel mode if ranged weapon / polymorphed / magically created weapon --
+
+EEex_Opcode_AddListsResolvedListener(function(sprite)
+ -- Sanity check
+ if not EEex_GameObject_IsSprite(sprite) then
+ return
+ end
+ --
+ local conditionalString = EEex_Trigger_ParseConditionalString("OR(2) \n IsWeaponRanged(Myself) HasItemSlot(Myself,SLOT_MISC19)")
+ --
+ if sprite:getLocalInt("gtRogueParry") == 1 then
+ if EEex_Sprite_GetLocalInt(sprite, "gtParryMode") == 1 and (conditionalString:evalConditionalAsAIBase(sprite) or sprite.m_derivedStats.m_bPolymorphed == 1) then
+ sprite:applyEffect({
+ ["effectID"] = 146, -- Cast spell
+ ["dwFlags"] = 1, -- instant/ignore level
+ ["res"] = "%BLADE_SWASHBUCKLER_PARRY%B",
+ ["sourceID"] = sprite.m_id,
+ ["sourceTarget"] = sprite.m_id,
+ })
+ end
+ end
+ --
+ conditionalString:free()
+end)
+
+-- maintain SEQ_READY while in parry mode --
+
+EEex_Opcode_AddListsResolvedListener(function(sprite)
+ -- Sanity check
+ if not EEex_GameObject_IsSprite(sprite) then
+ return
+ end
+ --
+ if sprite:getLocalInt("gtRogueParry") == 1 then
+ if EEex_Sprite_GetLocalInt(sprite, "gtParryMode") == 1 and sprite.m_nSequence == 6 and sprite.m_curAction.m_actionID == 0 then
+ sprite:applyEffect({
+ ["effectID"] = 146, -- Cast spell
+ ["res"] = "%BLADE_SWASHBUCKLER_PARRY%G",
+ ["sourceID"] = sprite.m_id,
+ ["sourceTarget"] = sprite.m_id,
+ })
+ end
+ end
+end)
+
+-- make sure it cannot be disrupted. Cancel mode if no longer idle --
+
+EEex_Action_AddSpriteStartedActionListener(function(sprite, action)
+ if sprite:getLocalInt("gtRogueParry") == 1 then
+ --
+ local toskip = {
+ ["%BLADE_SWASHBUCKLER_PARRY%E"] = true,
+ ["%BLADE_SWASHBUCKLER_PARRY%G"] = true,
+ }
+ --
+ if EEex_Sprite_GetLocalInt(sprite, "gtParryMode") == 0 then
+ if action.m_actionID == 31 and action.m_string1.m_pchData:get() == "%BLADE_SWASHBUCKLER_PARRY%" then
+ action.m_actionID = 113 -- ForceSpell()
+ end
+ else
+ if not (action.m_actionID == 113 and toskip[action.m_string1.m_pchData:get()]) then
+ sprite:applyEffect({
+ ["effectID"] = 146, -- Cast spell
+ ["dwFlags"] = 1, -- instant/ignore level
+ ["res"] = "%BLADE_SWASHBUCKLER_PARRY%B",
+ ["sourceID"] = sprite.m_id,
+ ["sourceTarget"] = sprite.m_id,
+ })
+ end
+ end
+ end
+end)
+
+-- core op402 listener --
+
+function %BLADE_SWASHBUCKLER_PARRY%(CGameEffect, CGameSprite)
+ if CGameEffect.m_effectAmount == 0 then
+ -- we apply effects here due to op232's presence (which for best results requires EFF V2.0)
+ local effectCodes = {
+ {["op"] = 321, ["res"] = "%BLADE_SWASHBUCKLER_PARRY%"}, -- remove effects by resource
+ {["op"] = 232, ["p2"] = 16, ["res"] = "%BLADE_SWASHBUCKLER_PARRY%B", ["tmg"] = 1}, -- cast spl on condition (condition: Die(); target: self)
+ {["op"] = 142, ["p2"] = %feedback_icon%, ["tmg"] = 1}, -- feedback icon
+ }
+ --
+ for _, attributes in ipairs(effectCodes) do
+ CGameSprite:applyEffect({
+ ["effectID"] = attributes["op"] or EEex_Error("opcode number not specified"),
+ ["dwFlags"] = attributes["p2"] or 0,
+ ["res"] = attributes["res"] or "",
+ ["durationType"] = attributes["tmg"] or 0,
+ ["m_sourceRes"] = "%BLADE_SWASHBUCKLER_PARRY%",
+ ["sourceID"] = CGameSprite.m_id,
+ ["sourceTarget"] = CGameSprite.m_id,
+ })
+ end
+ --
+ EEex_Sprite_SetLocalInt(CGameSprite, "gtParryMode", 1)
+ elseif CGameEffect.m_effectAmount == 1 then
+ CGameSprite:applyEffect({
+ ["effectID"] = 321, -- Remove effects by resource
+ ["res"] = "%BLADE_SWASHBUCKLER_PARRY%",
+ ["sourceID"] = CGameSprite.m_id,
+ ["sourceTarget"] = CGameSprite.m_id,
+ })
+ --
+ EEex_Sprite_SetLocalInt(CGameSprite, "gtParryMode", 0)
+ elseif CGameEffect.m_effectAmount == 2 then
+ local itemflag = GT_Resource_SymbolToIDS["itemflag"]
+ --
+ local targetActiveStats = EEex_Sprite_GetActiveStats(CGameSprite)
+ --
+ local sourceSprite = EEex_GameObject_Get(CGameEffect.m_sourceId)
+ local sourceActiveStats = EEex_Sprite_GetActiveStats(sourceSprite)
+ --
+ local strmod = GT_Resource_2DA["strmod"]
+ local strmodex = GT_Resource_2DA["strmodex"]
+ local strBonus = tonumber(strmod[string.format("%s", sourceActiveStats.m_nSTR)]["DAMAGE"] + strmodex[string.format("%s", sourceActiveStats.m_nSTRExtra)]["DAMAGE"])
+ --
+ local equipment = sourceSprite.m_equipment
+ local selectedWeapon = equipment.m_items:get(equipment.m_selectedWeapon) -- CItem
+ local selectedWeaponResRef = selectedWeapon.pRes.resref:get()
+ local selectedWeaponHeader = selectedWeapon.pRes.pHeader -- Item_Header_st
+ --
+ local selectedWeaponAbility = EEex_Resource_GetItemAbility(selectedWeaponHeader, equipment.m_selectedWeaponAbility) -- Item_ability_st
+ --
+ if EEex_BAnd(selectedWeaponHeader.itemFlags, itemflag["TWOHANDED"]) == 0 and Infinity_RandomNumber(1, 2) == 1 then -- if single-handed and 1d2 == 1 (50% chance)
+ local items = sourceSprite.m_equipment.m_items -- Array Parry Mode kit feat for Blades and Swashbucklers [Luke] This component aims at implementing a Parry Mode for Blades and Swashbucklers.
+ Parry allows the character to block incoming attacks and make spectacular counterattacks.
+ A successful parry (save vs. Breath) means that the attack does not damage the parrying character.
+
+ Notes:
+ NWN-ish Feats Collection
+ EEex
+
+
+
+
+
+
+
+ MISSILE
column of dexmod.2da
to get an idea about how the saving throw bonus scales with Dexterity.