From 70238ab51ee14ba6f8e336ca25c6a6a9b73745c0 Mon Sep 17 00:00:00 2001 From: 4Luke4 Date: Thu, 28 Nov 2024 11:41:16 +0100 Subject: [PATCH 1/5] Parry Mode --- cdtweaks/languages/english/parry.tra | 13 + cdtweaks/languages/english/weidu.tra | 10 + cdtweaks/languages/italian/parry.tra | 13 + cdtweaks/languages/italian/weidu.tra | 10 + cdtweaks/lib/comp_6220.tpa | 18 + cdtweaks/lib/parry.tph | 98 ++++ cdtweaks/luke/bam/kit/parry/portrait_icon.bam | Bin 0 -> 961 bytes cdtweaks/luke/bam/kit/parry/spl_icon.bam | Bin 0 -> 1252 bytes cdtweaks/luke/lua/kit/parry.lua | 425 ++++++++++++++++++ cdtweaks/luke/lua/m_gttbls.lua | 2 +- cdtweaks/readme-cdtweaks.html | 49 ++ cdtweaks/setup-cdtweaks.tp2 | 23 + 12 files changed, 660 insertions(+), 1 deletion(-) create mode 100644 cdtweaks/languages/english/parry.tra create mode 100644 cdtweaks/languages/italian/parry.tra create mode 100644 cdtweaks/lib/comp_6220.tpa create mode 100644 cdtweaks/lib/parry.tph create mode 100644 cdtweaks/luke/bam/kit/parry/portrait_icon.bam create mode 100644 cdtweaks/luke/bam/kit/parry/spl_icon.bam create mode 100644 cdtweaks/luke/lua/kit/parry.lua diff --git a/cdtweaks/languages/english/parry.tra b/cdtweaks/languages/english/parry.tra new file mode 100644 index 0000000..3a75b90 --- /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 (Success)" + +@4 = "Riposte Attack" + +@5 = "Parry Mode Off" \ No newline at end of file diff --git a/cdtweaks/languages/english/weidu.tra b/cdtweaks/languages/english/weidu.tra index 06daf4e..f369d62 100644 --- a/cdtweaks/languages/english/weidu.tra +++ b/cdtweaks/languages/english/weidu.tra @@ -802,3 +802,13 @@ Use Baldur.lua options: a7_interval_ini @504000 = ~Allow Yeslick to Use Axes~ @505000 = ~Ensure Shar-Teel Doesn't Die in the Original Challenge~ + +/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\ +/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\ +///// \\\\\ +///// NWN-ish feats collection \\\\\ +///// \\\\\ +/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\ +/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\ + +@600220 = "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 0000000..dff78ea --- /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 (Successo)" + +@4 = "Controattacco" + +@5 = "Modalità Parata Disattivata" \ No newline at end of file diff --git a/cdtweaks/languages/italian/weidu.tra b/cdtweaks/languages/italian/weidu.tra index 738ea95..2556b52 100644 --- a/cdtweaks/languages/italian/weidu.tra +++ b/cdtweaks/languages/italian/weidu.tra @@ -718,3 +718,13 @@ Usa opzioni di Baldur.lua: a7_interval_ini @504000 = ~Permettere a Yeslick di usare le asce~ @505000 = ~Assicura che Shar-Teel non muoia nella sfida iniziale~ + +/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\ +/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\ +///// \\\\\ +///// NWN-ish feats collection \\\\\ +///// \\\\\ +/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\ +/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\ + +@600220 = "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 0000000..70122bb --- /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 0000000..883cf31 --- /dev/null +++ b/cdtweaks/lib/parry.tph @@ -0,0 +1,98 @@ +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 + // + 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" + // + 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 + // + CREATE "spl" "%BLADE_SWASHBUCKLER_PARRY%b" + COPY_EXISTING "%BLADE_SWASHBUCKLER_PARRY%b.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 (@3) END // "Parry (Success)" + BUT_ONLY + // + CREATE "spl" "%BLADE_SWASHBUCKLER_PARRY%c" + COPY_EXISTING "%BLADE_SWASHBUCKLER_PARRY%c.spl" "override" + WRITE_LONG NAME1 RESOLVE_STR_REF (@4) + 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 + // + CREATE "spl" "%BLADE_SWASHBUCKLER_PARRY%d" + COPY_EXISTING "%BLADE_SWASHBUCKLER_PARRY%d.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 "target" = 5 "range" = 30 STR_VAR "icon" = "%BLADE_SWASHBUCKLER_PARRY%B" END + // + LPF "ADD_SPELL_EFFECT" INT_VAR "opcode" = 402 "target" = 1 "parameter1" = 1 STR_VAR "resource" = "%BLADE_SWASHBUCKLER_PARRY%" END // invoke lua + 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 + 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 0000000000000000000000000000000000000000..f8e33ac2b865ea92992d9a2fb38097f2c038a986 GIT binary patch literal 961 zcmV;y13vshK}|zeF(4qd1ONbdoNbeDOj}hL#(#y|%*?c8W|y%Mwz>}#*q}J%U)-cJ zgx$?qiV6dqY;~!lG95S-DxYxC4-|CLuw~3Fm~`{cI3-t)fidC&VkC(pglDSl+9sqz7nNr#RkAZ>9X4hkH|y3J#Otn81+R#38>5 z&xo30KWus(HogR_tw?!oNNIan;k?fB?0Pn|ERgbO8*B2Nlz03NwW6ow6VVqym+nHB zvK{@7L4kdQ<<-9m{22^S4Pz`H#9(o;ruJJ_R|b(w-eJ8XOwwjI)`m}6wfC0TZz8wI zPH1QXO2^O__&Iqt4Av{?lB>wvGeLdp2c+6VP$o|#$0dH0{bLJ+j@>+aK1zOL0Oq@AGy4lQwIXP@s6t2}g zf%eT;40XRrUt0rOi;Ou@;*FXtwD4ODwP$hjv)%Zd5?8yHQB{_SYnw#VE;Am7gpvz^ zc8SO-82?hCT7i#yYw^4Sc10$7)lYO1+FB*l*?V~RsDY_#PtthsF>VW*f8K_f8Tk1Z z_pDk`A79104B`i-F13;l}=@W&iN`*Gq9 z=(!P<_;Fm%SVUrK+CW|1G8X2OxD;N?{{0EWZf&HmZv#C&si+Hzz+cCfhgWj(Vwu2j z;cm%eK6Z$X&SDl8ItgED7PzO#%v>jYfT^huiAFDoc!)s2%iy37Rh^`@^;M1?^U~FI zgsQ4aE?*v_qr)TiRAR9hv$L~;?~Le;GCDfS$jAtxP>6wn0l_>mcdh;}iY@vn*_!Q(523 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..36ef708ce93ab921e31cbad2446dd6537903d37b GIT binary patch literal 1252 zcmVDkvNv)1_PRXncT-^j+ z7pRoLIvoksC8WWTF`YE6Bmp4=kLQ#(d0Y3$n|%^=Bs=XQm7=Vt@?=C=V`w#f} z*Vj;g@fy}QU%~p;E2y_$Lc6^Qt!4|F%_cN03$z!nK-+u?#rE&;=$9Q-@4%z>4&>3? zJnD1LcR=6R0Ei}Ic5q$$?GA|)Y)>R2QOG^Ya$5TR+5qYw#2AV>#+MtzD3LXe_> zMhcOg><1s|2S4dU9))TW$p<9Lgf9d>f(F7vfyYaM7iEGV!9~%4Q$85>l0Xq2pgdk6 z2?AvbWiO25B#`(x650ce<1PXSmkSBiI1U(w12H^|pcJ>t}5&fKrvdrA<-}#qsjQi&2xrJLicP%jY*{5?emjl6T z*BAJGeWvuCGdA)R33cey(Wme25u%RaKw z^*>?Q1SfbtzP}_c=d$0jEYHPRUcGeDH23@<6J=N~_K}h^Qsf}dvKgMw=t=s1!`ky_ zzPJ(R1$pA!72&%CS30=F!ehTIn}Sf*hftcTE7^DL*jkB{bLfzJ}osdKcprq_BtTxVJKt~`2* zkmaoGaaXnE#fn+!*$|qK$J0wCzp*koos*VC+5f=Q>xX_|F3NI0sihTS>im0>z{^j+ zE|zu=ztBwh&WHDMLUGyY7$r*5J=v61)9U#nfoRq#$lpm4e)2?0lcW_xHMH`fKgS7< zNkt^4%LS=iA>3u7P&N*aKN4ZN0Z%#QJyXhX8ikLYlX6<6w?7tOqP#pY<(`;Ug}ZVo zg}cA38KzvglzFl1FVj9RUhQXXpE z2bhV*mQ=M`Ycx7vH22tSJT@v;-TG1Mt`Bj!%4)T2Huj%KQzYZ*u2wjBL5yG+Yv$o? z>4c%GNBaLfZuIDN9|85WJ(fd#h#c%g<6u7$9sMwL^kHzw9={=b@`i1h!)#z1v4L#F z1}@BwD$EWi%#I_>4xnLsbcXDy8SF=lqaQAgeylk9f#T>#iQ@<&14j{Yyurud8*B{r OxNz>#aPu!dh)ZPPgJmKB literal 0 HcmV?d00001 diff --git a/cdtweaks/luke/lua/kit/parry.lua b/cdtweaks/luke/lua/kit/parry.lua new file mode 100644 index 0000000..61a1b3e --- /dev/null +++ b/cdtweaks/luke/lua/kit/parry.lua @@ -0,0 +1,425 @@ +--[[ ++-----------------------------------------------------------+ +| 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("cdtweaksParryMode", 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("cdtweaksParryMode") == 0 then + if gainAbility then + gain() + end + else + if gainAbility then + -- do nothing + else + -- Mark the creature as 'feat removed' + sprite:setLocalInt("cdtweaksParryMode", 0) + -- + if EEex_Sprite_GetLocalInt(sprite, "gtParryMode") == 1 then + sprite:applyEffect({ + ["effectID"] = 146, -- Cast spell + ["dwFlags"] = 1, -- instant/ignore level + ["res"] = "%BLADE_SWASHBUCKLER_PARRY%D", + ["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 -- + +EEex_Sprite_AddBlockWeaponHitListener(function(args) + local toReturn = false + -- + local fatigmod = GT_Resource_2DA["fatigmod"] + 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 + -- 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 + -- by default, the character can parry max 2 attacks per round (1 if slowed OR fatigued, 1 per second if hasted) + local time = 3 + if EEex_IsBitSet(targetSprite.m_derivedStats.m_generalState, 16) or tonumber(fatigmod[string.format("%s", targetSprite.m_derivedStats.m_nFatigue)]["LUCK"]) < 0 then + time = 6 + elseif EEex_IsBitSet(targetSprite.m_derivedStats.m_generalState, 15) then + time = 1 + end + local responseString = EEex_Action_ParseResponseString(string.format('SetGlobalTimer("gtParryModeTimer","LOCALS",%d)', time)) + local conditionalString = EEex_Trigger_ParseConditionalString('!GlobalTimerNotExpired("gtParryModeTimer","LOCALS") \n InWeaponRange(EEex_Target("GT_ParryModeTarget"))') + targetSprite:setStoredScriptingTarget("GT_ParryModeTarget", attackingSprite) + -- + if targetSprite:getLocalInt("gtParryMode") == 1 then -- parry mode ON + 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 targetSprite.m_derivedStats.m_nSaveVSBreath - tonumber(dexmod[string.format("%s", targetSprite.m_derivedStats.m_nDEX)]["MISSILE"]) <= targetSprite.m_saveVSBreathRoll then + -- set timer + responseString:executeResponseAsAIBaseInstantly(targetSprite) + -- 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%B", + ["sourceID"] = targetSprite.m_id, + ["sourceTarget"] = attackingSprite.m_id, + }) + -- block base weapon damage + on-hit effects + toReturn = true + end + end + end + end + end + -- + conditionalString:free() + responseString:free() + -- + return toReturn +end) + +-- cast a spl 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%C", + ["sourceID"] = sprite.m_id, + ["sourceTarget"] = attackingSprite.m_id, + }) + end +end) + +-- automatically cancel mode if ranged weapon / polymorphed -- + +EEex_Opcode_AddListsResolvedListener(function(sprite) + -- Sanity check + if not EEex_GameObject_IsSprite(sprite) then + return + end + -- + local isWeaponRanged = EEex_Trigger_ParseConditionalString("IsWeaponRanged(Myself)") + -- + if sprite:getLocalInt("cdtweaksParryMode") == 1 then + if EEex_Sprite_GetLocalInt(sprite, "gtParryMode") == 1 and (isWeaponRanged: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%D", + ["sourceID"] = sprite.m_id, + ["sourceTarget"] = sprite.m_id, + }) + end + end + -- + isWeaponRanged:free() +end) + +-- make sure it cannot be disrupted. Cancel mode if no longer idle -- + +EEex_Action_AddSpriteStartedActionListener(function(sprite, action) + if sprite:getLocalInt("cdtweaksParryMode") == 1 then + 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 action.m_string1.m_pchData:get() == "%BLADE_SWASHBUCKLER_PARRY%B") then + sprite:applyEffect({ + ["effectID"] = 146, -- Cast spell + ["dwFlags"] = 1, -- instant/ignore level + ["res"] = "%BLADE_SWASHBUCKLER_PARRY%D", + ["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%D", ["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 + local offHand = items:get(9) -- CItem + -- + if offHand then -- sanity check + local pHeader = offHand.pRes.pHeader -- Item_Header_st + if pHeader.itemType ~= 0xC then -- if not shield, then overwrite item resref / header / ability... + selectedWeaponResRef = offHand.pRes.resref:get() + selectedWeaponHeader = pHeader -- Item_Header_st + selectedWeaponAbility = EEex_Resource_GetItemAbility(pHeader, 0) -- Item_ability_st + end + end + end + -- collect on-hit effects (if any) + local onHitEffects = {} + do + local currentEffectAddress = EEex_UDToPtr(selectedWeaponHeader) + selectedWeaponHeader.effectsOffset + selectedWeaponAbility.startingEffect * Item_effect_st.sizeof + -- + for idx = 1, selectedWeaponAbility.effectCount do + local pEffect = EEex_PtrToUD(currentEffectAddress, "Item_effect_st") + -- + table.insert(onHitEffects, { + ["effectID"] = pEffect.effectID, + ["targetType"] = pEffect.targetType, + ["spellLevel"] = pEffect.spellLevel, + ["effectAmount"] = pEffect.effectAmount, + ["dwFlags"] = pEffect.dwFlags, + ["durationType"] = EEex_BAnd(pEffect.durationType, 0xFF), + ["m_flags"] = EEex_RShift(pEffect.durationType, 8), + ["duration"] = pEffect.duration, + ["probabilityUpper"] = pEffect.probabilityUpper, + ["probabilityLower"] = pEffect.probabilityLower, + ["res"] = pEffect.res:get(), + ["numDice"] = pEffect.numDice, + ["diceSize"] = pEffect.diceSize, + ["savingThrow"] = pEffect.savingThrow, + ["saveMod"] = pEffect.saveMod, + ["special"] = pEffect.special, + }) + -- + currentEffectAddress = currentEffectAddress + Item_effect_st.sizeof + end + end + -- + local itmAbilityDamageTypeToIDS = { + [0] = 0x0 -- none (crushing) + [1] = 0x10, -- piercing + [2] = 0x0, -- crushing + [3] = 0x100, -- slashing + [4] = 0x80, -- missile + [5] = 0x800, -- non-lethal + [6] = targetActiveStats.m_nResistPiercing > targetActiveStats.m_nResistCrushing and 0x0 or 0x10, -- piercing/crushing (better) + [7] = targetActiveStats.m_nResistPiercing > targetActiveStats.m_nResistSlashing and 0x100 or 0x10, -- piercing/slashing (better) + [8] = targetActiveStats.m_nResistCrushing > targetActiveStats.m_nResistSlashing and 0x0 or 0x100, -- slashing/crushing (worse) + } + -- + if itmAbilityDamageTypeToIDS[selectedWeaponAbility.damageType] then -- sanity check + -- damage type ``NONE`` requires extra care + local mode = 0 -- normal + if selectedWeaponAbility.damageType == 0 and selectedWeaponAbility.damageDiceCount > 0 then + mode = 1 -- set HP to value + end + -- + EEex_GameObject_ApplyEffect(CGameSprite, + { + ["effectID"] = 0xC, -- Damage (12) + ["dwFlags"] = itmAbilityDamageTypeToIDS[selectedWeaponAbility.damageType] * 0x10000 + mode, + ["effectAmount"] = selectedWeaponAbility.damageDiceBonus + strBonus, + ["numDice"] = selectedWeaponAbility.damageDiceCount, + ["diceSize"] = selectedWeaponAbility.damageDice, + --["m_sourceRes"] = CGameEffect.m_sourceRes:get(), + --["m_sourceType"] = CGameEffect.m_sourceType, + ["sourceID"] = CGameEffect.m_sourceId, + ["sourceTarget"] = CGameEffect.m_sourceTarget, + }) + -- apply on-hit effects (if any) + for _, v in ipairs(onHitEffects) do + local array = {} + -- + if v["targetType"] == 1 or v["targetType"] == 9 then -- self / original caster + table.insert(array, sourceSprite) + elseif v["targetType"] == 2 then -- projectile target + table.insert(array, CGameSprite) + elseif v["targetType"] == 3 or (v["targetType"] == 6 and sourceSprite.m_typeAI.m_EnemyAlly == 2) then -- party + for i = 0, 5 do + local partyMember = EEex_Sprite_GetInPortrait(i) -- CGameSprite + if partyMember and EEex_BAnd(partyMember.m_derivedStats.m_generalState, 0x800) == 0 then -- skip if STATE_DEAD + table.insert(array, partyMember) + end + end + elseif v["targetType"] == 4 then -- everyone + local everyone = EEex_Area_GetAllOfTypeInRange(sourceSprite.m_pArea, sourceSprite.m_pos.x, sourceSprite.m_pos.y, GT_AI_ObjectType["ANYONE"], 0x7FFF, false, nil, nil) + -- + for _, sprite in ipairs(everyone) do + if EEex_BAnd(sprite.m_derivedStats.m_generalState, 0x800) == 0 then -- skip if STATE_DEAD + table.insert(array, sprite) + end + end + elseif v["targetType"] == 5 then -- everyone but party + local everyone = EEex_Area_GetAllOfTypeInRange(sourceSprite.m_pArea, sourceSprite.m_pos.x, sourceSprite.m_pos.y, GT_AI_ObjectType["ANYONE"], 0x7FFF, false, nil, nil) + -- + for _, sprite in ipairs(everyone) do + if sprite.m_typeAI.m_EnemyAlly ~= 2 then + if EEex_BAnd(sprite.m_derivedStats.m_generalState, 0x800) == 0 then -- skip if STATE_DEAD + table.insert(array, sprite) + end + end + end + elseif v["targetType"] == 6 and sourceSprite.m_typeAI.m_EnemyAlly ~= 2 then -- caster group + local casterGroup = EEex_Area_GetAllOfTypeStringInRange(sourceSprite.m_pArea, sourceSprite.m_pos.x, sourceSprite.m_pos.y, string.format("[0.0.0.0.%d]", sourceSprite.m_typeAI.m_Specifics), 0x7FFF, false, nil, nil) + -- + for _, sprite in ipairs(casterGroup) do + if EEex_BAnd(sprite.m_derivedStats.m_generalState, 0x800) == 0 then -- skip if STATE_DEAD + table.insert(array, sprite) + end + end + elseif v["targetType"] == 7 then -- target group + local targetGroup = EEex_Area_GetAllOfTypeStringInRange(sourceSprite.m_pArea, sourceSprite.m_pos.x, sourceSprite.m_pos.y, string.format("[0.0.0.0.%d]", CGameSprite.m_typeAI.m_Specifics), 0x7FFF, false, nil, nil) + -- + for _, sprite in ipairs(targetGroup) do + if EEex_BAnd(sprite.m_derivedStats.m_generalState, 0x800) == 0 then -- skip if STATE_DEAD + table.insert(array, sprite) + end + end + elseif v["targetType"] == 8 then -- everyone but self + local everyone = EEex_Area_GetAllOfTypeInRange(sourceSprite.m_pArea, sourceSprite.m_pos.x, sourceSprite.m_pos.y, GT_AI_ObjectType["ANYONE"], 0x7FFF, false, nil, nil) + -- + for _, sprite in ipairs(everyone) do + if sprite.m_id ~= sourceSprite.m_id then + if EEex_BAnd(sprite.m_derivedStats.m_generalState, 0x800) == 0 then -- skip if STATE_DEAD + table.insert(array, sprite) + end + end + end + end + -- + for _, object in ipairs(array) do + EEex_GameObject_ApplyEffect(object, + { + ["effectID"] = v["effectID"], + ["spellLevel"] = v["spellLevel"], + ["effectAmount"] = v["effectAmount"], + ["dwFlags"] = v["dwFlags"], + ["durationType"] = v["durationType"], + ["m_flags"] = v["m_flags"], + ["duration"] = v["duration"], + ["probabilityUpper"] = v["probabilityUpper"], + ["probabilityLower"] = v["probabilityLower"], + ["res"] = v["res"], + ["numDice"] = v["numDice"], + ["diceSize"] = v["diceSize"], + ["savingThrow"] = v["savingThrow"], + ["saveMod"] = v["saveMod"], + ["special"] = v["special"], + -- + ["m_sourceRes"] = selectedWeaponResRef, + ["m_sourceType"] = 2, + ["sourceID"] = CGameEffect.m_sourceId, + ["sourceTarget"] = CGameEffect.m_sourceTarget, + }) + end + end + end + end +end diff --git a/cdtweaks/luke/lua/m_gttbls.lua b/cdtweaks/luke/lua/m_gttbls.lua index 6861a6c..3205302 100644 --- a/cdtweaks/luke/lua/m_gttbls.lua +++ b/cdtweaks/luke/lua/m_gttbls.lua @@ -7,7 +7,7 @@ GT_Resource_SymbolToIDS = {} EEex_GameState_AddInitializedListener(function() -- 2DA EEex_Utility_NewScope(function() - local resources = { "STRMOD", "STRMODEX", "DEXMOD", "STYLBONU", "SNEAKATT" } + local resources = { "STRMOD", "STRMODEX", "DEXMOD", "STYLBONU", "SNEAKATT", "FATIGMOD" } -- for _, v in ipairs(resources) do local data = EEex_Resource_Load2DA(v) diff --git a/cdtweaks/readme-cdtweaks.html b/cdtweaks/readme-cdtweaks.html index 84c933f..05b9253 100644 --- a/cdtweaks/readme-cdtweaks.html +++ b/cdtweaks/readme-cdtweaks.html @@ -1461,6 +1461,55 @@

Joinable +

NWN-ish Feats Collection

+ +
+
+
+

Components in this category are inspired from NWN and are aimed at providing new class/kit abilities.

+ +

Parry Mode kit feat for Blades and Swashbucklers [Luke]
+ EEex

+

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: +

    +
  • Use: selected (unlimited uses per day).
  • +
  • Parry is a combat mode, so the character must remain idle while parrying attacks. Any other action will cancel the mode.
  • +
  • Parry applies only in melee combat; it does not function if either the attacker or defender is using a ranged weapon.
  • +
  • + By default, the defender can parry a maximum of 2 attacks per round. +
      +
    • If slowed or fatigued, it can only parry a maximum of 1 attack per round.
    • +
    • If hasted, it can parry a maximum of 1 attack per second.
    • +
    +
  • +
  • + Riposte attacks automatically hit (the attacker must be within weapon range of the defender). +
      +
    • Bonus due to high Strength and on-hit effects apply as normal, but riposte attacks are not eligible for critical hit / miss.
    • +
    +
  • +
  • If the parrying character gets hit while performing a riposte attack, the riposte attack will be canceled.
  • +
  • + The higher Dexterity, the easier is to parry an incoming attack. +
      +
    • See the MISSILE column of dexmod.2da to get an idea about how the saving throw bonus scales with Dexterity.
    • +
    +
  • +
  • This combat mode cannot be used while polymorphed.
  • +
  • If the defender is equipped with fists, it can only parry other fists.
  • +
  • If the parrying character is dual-wielding, the riposte attack will be made with the mainhand / offhand (50% chance each).
  • +
+

+
+

Contact Information

diff --git a/cdtweaks/setup-cdtweaks.tp2 b/cdtweaks/setup-cdtweaks.tp2 index 5e97fb6..938d24a 100644 --- a/cdtweaks/setup-cdtweaks.tp2 +++ b/cdtweaks/setup-cdtweaks.tp2 @@ -4903,3 +4903,26 @@ GROUP @0 REQUIRE_PREDICATE GAME_IS ~bgee bg2ee eet~ @25 REQUIRE_PREDICATE MOD_IS_INSTALLED "EEex.tp2" 0 @29 LABEL ~cd_tweaks_dorns_sword~ + +/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\ +/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\ +///// \\\\\ +///// NWN-ish feats collection \\\\\ +///// \\\\\ +/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\ +/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\ + +/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\////\\\\//// +/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\////\\\\//// +///// \\\\\////\\\\//// +///// Parry Mode kit feat for Blades and Swashbucklers \\\\\ +///// \\\\\////\\\\//// +/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\////\\\\//// +/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\////\\\\//// + +BEGIN @600220 DESIGNATED 6220 +GROUP @30 +REQUIRE_PREDICATE GAME_IS ~bgee bg2ee eet iwdee~ @25 +REQUIRE_PREDICATE MOD_IS_INSTALLED "EEex.tp2" 0 @29 +REQUIRE_PREDICATE FILE_EXISTS ~cdtweaks/languages/%LANGUAGE%/parry.tra~ @7 +LABEL ~cd_tweaks_nwn_parry~ \ No newline at end of file From 09df91b8f1f4cde53d129e9eca9b1a3c72f3d239 Mon Sep 17 00:00:00 2001 From: 4Luke4 Date: Sat, 30 Nov 2024 17:32:10 +0100 Subject: [PATCH 2/5] Slightly edited implementation --- cdtweaks/languages/english/parry.tra | 6 +- cdtweaks/languages/italian/parry.tra | 6 +- cdtweaks/lib/parry.tph | 42 +++++--- cdtweaks/luke/lua/kit/parry.lua | 138 +++++++++++++++++++-------- cdtweaks/luke/lua/m_gttbls.lua | 2 +- cdtweaks/readme-cdtweaks.html | 8 +- 6 files changed, 138 insertions(+), 64 deletions(-) diff --git a/cdtweaks/languages/english/parry.tra b/cdtweaks/languages/english/parry.tra index 3a75b90..17df236 100644 --- a/cdtweaks/languages/english/parry.tra +++ b/cdtweaks/languages/english/parry.tra @@ -6,8 +6,8 @@ A successful parry (save vs. Breath) means that the attack does not damage the p @2 = "Parry Mode On" -@3 = "Parry (Success)" +@3 = "Parry Mode Off" -@4 = "Riposte Attack" +@4 = "Parry (Success)" -@5 = "Parry Mode Off" \ No newline at end of file +@5 = "Riposte Attack" \ No newline at end of file diff --git a/cdtweaks/languages/italian/parry.tra b/cdtweaks/languages/italian/parry.tra index dff78ea..99b4e7c 100644 --- a/cdtweaks/languages/italian/parry.tra +++ b/cdtweaks/languages/italian/parry.tra @@ -6,8 +6,8 @@ Se un attacco viene parato con successo (tiro-salvezza contro Soffio), esso non @2 = "Modalità Parata Attivata" -@3 = "Modalità Parata (Successo)" +@3 = "Modalità Parata Disattivata" -@4 = "Controattacco" +@4 = "Modalità Parata (Successo)" -@5 = "Modalità Parata Disattivata" \ No newline at end of file +@5 = "Controattacco" \ No newline at end of file diff --git a/cdtweaks/lib/parry.tph b/cdtweaks/lib/parry.tph index 883cf31..63b204c 100644 --- a/cdtweaks/lib/parry.tph +++ b/cdtweaks/lib/parry.tph @@ -10,12 +10,14 @@ BEGIN "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) @@ -35,9 +37,25 @@ BEGIN // 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" @@ -50,12 +68,12 @@ BEGIN 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 (@3) END // "Parry (Success)" + LPF "ADD_SPELL_EFFECT" INT_VAR "opcode" = 139 "target" = 1 "parameter1" = RESOLVE_STR_REF (@4) END // "Parry (Success)" BUT_ONLY - // - CREATE "spl" "%BLADE_SWASHBUCKLER_PARRY%c" - COPY_EXISTING "%BLADE_SWASHBUCKLER_PARRY%c.spl" "override" - WRITE_LONG NAME1 RESOLVE_STR_REF (@4) + // 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" @@ -68,10 +86,10 @@ BEGIN // LPF "ADD_SPELL_EFFECT" INT_VAR "opcode" = 402 "target" = 2 "parameter1" = 2 STR_VAR "resource" = "%BLADE_SWASHBUCKLER_PARRY%" END // invoke lua BUT_ONLY - // - CREATE "spl" "%BLADE_SWASHBUCKLER_PARRY%d" - COPY_EXISTING "%BLADE_SWASHBUCKLER_PARRY%d.spl" "override" - WRITE_LONG NAME1 RESOLVE_STR_REF (@5) + // 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" @@ -82,7 +100,7 @@ BEGIN // 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" = 402 "target" = 1 "parameter1" = 1 STR_VAR "resource" = "%BLADE_SWASHBUCKLER_PARRY%" END // invoke lua + LPF "ADD_SPELL_EFFECT" INT_VAR "opcode" = 138 "target" = 1 "parameter2" = 7 END // set animation (READY) BUT_ONLY END // lua diff --git a/cdtweaks/luke/lua/kit/parry.lua b/cdtweaks/luke/lua/kit/parry.lua index 61a1b3e..3b634a5 100644 --- a/cdtweaks/luke/lua/kit/parry.lua +++ b/cdtweaks/luke/lua/kit/parry.lua @@ -61,7 +61,7 @@ EEex_Opcode_AddListsResolvedListener(function(sprite) sprite:applyEffect({ ["effectID"] = 146, -- Cast spell ["dwFlags"] = 1, -- instant/ignore level - ["res"] = "%BLADE_SWASHBUCKLER_PARRY%D", + ["res"] = "%BLADE_SWASHBUCKLER_PARRY%B", ["sourceID"] = sprite.m_id, ["sourceTarget"] = sprite.m_id, }) @@ -79,10 +79,15 @@ 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 fatigmod = GT_Resource_2DA["fatigmod"] + local stats = GT_Resource_SymbolToIDS["stats"] + local state = GT_Resource_SymbolToIDS["state"] + -- local dexmod = GT_Resource_2DA["dexmod"] -- local attackingWeapon = args.weapon -- CItem @@ -95,37 +100,63 @@ EEex_Sprite_AddBlockWeaponHitListener(function(args) local targetWeaponHeader = targetWeapon.pRes.pHeader -- Item_Header_st -- local attackingWeaponHeader = attackingWeapon.pRes.pHeader -- Item_Header_st - -- by default, the character can parry max 2 attacks per round (1 if slowed OR fatigued, 1 per second if hasted) - local time = 3 - if EEex_IsBitSet(targetSprite.m_derivedStats.m_generalState, 16) or tonumber(fatigmod[string.format("%s", targetSprite.m_derivedStats.m_nFatigue)]["LUCK"]) < 0 then - time = 6 - elseif EEex_IsBitSet(targetSprite.m_derivedStats.m_generalState, 15) then - time = 1 + -- 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 - local responseString = EEex_Action_ParseResponseString(string.format('SetGlobalTimer("gtParryModeTimer","LOCALS",%d)', time)) - local conditionalString = EEex_Trigger_ParseConditionalString('!GlobalTimerNotExpired("gtParryModeTimer","LOCALS") \n InWeaponRange(EEex_Target("GT_ParryModeTarget"))') + -- + local targetActiveStats = EEex_Sprite_GetActiveStats(targetSprite) + -- + 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... targetSprite:setStoredScriptingTarget("GT_ParryModeTarget", attackingSprite) -- if targetSprite:getLocalInt("gtParryMode") == 1 then -- parry mode ON - 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 targetSprite.m_derivedStats.m_nSaveVSBreath - tonumber(dexmod[string.format("%s", targetSprite.m_derivedStats.m_nDEX)]["MISSILE"]) <= targetSprite.m_saveVSBreathRoll then - -- set timer - responseString:executeResponseAsAIBaseInstantly(targetSprite) - -- 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%B", - ["sourceID"] = targetSprite.m_id, - ["sourceTarget"] = attackingSprite.m_id, - }) - -- block base weapon damage + on-hit effects - toReturn = true + 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 + toReturn = true + end + end + end end end end @@ -133,12 +164,11 @@ EEex_Sprite_AddBlockWeaponHitListener(function(args) end -- conditionalString:free() - responseString:free() -- return toReturn end) --- cast a spl when ``m_attackFrame`` is equal to 6 (that should be approx. the value corresponding to the weapon hit...?) -- +-- 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 @@ -152,14 +182,14 @@ EEex_Opcode_AddListsResolvedListener(function(sprite) attackingSprite:applyEffect({ ["effectID"] = 146, -- Cast spl ["dwFlags"] = 1, -- mode: instant / permanent - ["res"] = "%BLADE_SWASHBUCKLER_PARRY%C", + ["res"] = "%BLADE_SWASHBUCKLER_PARRY%F", ["sourceID"] = sprite.m_id, ["sourceTarget"] = attackingSprite.m_id, }) end end) --- automatically cancel mode if ranged weapon / polymorphed -- +-- automatically cancel mode if ranged weapon / polymorphed / magically created weapon -- EEex_Opcode_AddListsResolvedListener(function(sprite) -- Sanity check @@ -167,37 +197,63 @@ EEex_Opcode_AddListsResolvedListener(function(sprite) return end -- - local isWeaponRanged = EEex_Trigger_ParseConditionalString("IsWeaponRanged(Myself)") + local conditionalString = EEex_Trigger_ParseConditionalString("OR(2) \n IsWeaponRanged(Myself) HasItemSlot(Myself,SLOT_MISC19)") -- if sprite:getLocalInt("cdtweaksParryMode") == 1 then - if EEex_Sprite_GetLocalInt(sprite, "gtParryMode") == 1 and (isWeaponRanged:evalConditionalAsAIBase(sprite) or sprite.m_derivedStats.m_bPolymorphed == 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%D", + ["res"] = "%BLADE_SWASHBUCKLER_PARRY%B", ["sourceID"] = sprite.m_id, ["sourceTarget"] = sprite.m_id, }) end end -- - isWeaponRanged:free() + 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("cdtweaksParryMode") == 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("cdtweaksParryMode") == 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 action.m_string1.m_pchData:get() == "%BLADE_SWASHBUCKLER_PARRY%B") then + 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%D", + ["res"] = "%BLADE_SWASHBUCKLER_PARRY%B", ["sourceID"] = sprite.m_id, ["sourceTarget"] = sprite.m_id, }) @@ -213,7 +269,7 @@ function %BLADE_SWASHBUCKLER_PARRY%(CGameEffect, CGameSprite) -- 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%D", ["tmg"] = 1}, -- cast spl on condition (condition: Die(); target: self) + {["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 } -- @@ -366,7 +422,7 @@ function %BLADE_SWASHBUCKLER_PARRY%(CGameEffect, CGameSprite) end end end - elseif v["targetType"] == 6 and sourceSprite.m_typeAI.m_EnemyAlly ~= 2 then -- caster group + elseif v["targetType"] == 6 then -- caster group local casterGroup = EEex_Area_GetAllOfTypeStringInRange(sourceSprite.m_pArea, sourceSprite.m_pos.x, sourceSprite.m_pos.y, string.format("[0.0.0.0.%d]", sourceSprite.m_typeAI.m_Specifics), 0x7FFF, false, nil, nil) -- for _, sprite in ipairs(casterGroup) do diff --git a/cdtweaks/luke/lua/m_gttbls.lua b/cdtweaks/luke/lua/m_gttbls.lua index 3205302..6861a6c 100644 --- a/cdtweaks/luke/lua/m_gttbls.lua +++ b/cdtweaks/luke/lua/m_gttbls.lua @@ -7,7 +7,7 @@ GT_Resource_SymbolToIDS = {} EEex_GameState_AddInitializedListener(function() -- 2DA EEex_Utility_NewScope(function() - local resources = { "STRMOD", "STRMODEX", "DEXMOD", "STYLBONU", "SNEAKATT", "FATIGMOD" } + local resources = { "STRMOD", "STRMODEX", "DEXMOD", "STYLBONU", "SNEAKATT" } -- for _, v in ipairs(resources) do local data = EEex_Resource_Load2DA(v) diff --git a/cdtweaks/readme-cdtweaks.html b/cdtweaks/readme-cdtweaks.html index 05b9253..b8dc6d8 100644 --- a/cdtweaks/readme-cdtweaks.html +++ b/cdtweaks/readme-cdtweaks.html @@ -1484,10 +1484,9 @@

NWN-ish Feats Collection Parry is a combat mode, so the character must remain idle while parrying attacks. Any other action will cancel the mode.
  • Parry applies only in melee combat; it does not function if either the attacker or defender is using a ranged weapon.
  • - By default, the defender can parry a maximum of 2 attacks per round. + A character may only parry a number of attacks per round equal to the number of attacks per round available to the character.
      -
    • If slowed or fatigued, it can only parry a maximum of 1 attack per round.
    • -
    • If hasted, it can parry a maximum of 1 attack per second.
    • +
    • 1/2, 3/2, etc. are rounded down / up (50% chance each). So if the parrying character has, say, 3/2 attacks per round, then it can parry a maximum of 1 / 2 attack(s) per round (50% chance each).
  • @@ -1503,7 +1502,8 @@

    NWN-ish Feats Collection See the MISSILE column of dexmod.2da to get an idea about how the saving throw bonus scales with Dexterity.

  • -
  • This combat mode cannot be used while polymorphed.
  • +
  • This combat mode cannot be used while polymorphed / while wielding a magically created weapon.
  • +
  • Incapacitated characters cannot parry / perform riposte attacks.
  • If the defender is equipped with fists, it can only parry other fists.
  • If the parrying character is dual-wielding, the riposte attack will be made with the mainhand / offhand (50% chance each).
  • From ece9dc4a6342247b0485117699ed28d06be5ca2a Mon Sep 17 00:00:00 2001 From: 4Luke4 Date: Sat, 30 Nov 2024 18:02:49 +0100 Subject: [PATCH 3/5] Update parry.tph --- cdtweaks/lib/parry.tph | 1 + 1 file changed, 1 insertion(+) diff --git a/cdtweaks/lib/parry.tph b/cdtweaks/lib/parry.tph index 63b204c..47a1bcd 100644 --- a/cdtweaks/lib/parry.tph +++ b/cdtweaks/lib/parry.tph @@ -108,6 +108,7 @@ 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 From df47a5e5258df8cfc2b6306d3d2edfa278a95725 Mon Sep 17 00:00:00 2001 From: 4Luke4 Date: Thu, 5 Dec 2024 15:51:26 +0100 Subject: [PATCH 4/5] Update parry.lua --- cdtweaks/luke/lua/kit/parry.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cdtweaks/luke/lua/kit/parry.lua b/cdtweaks/luke/lua/kit/parry.lua index 3b634a5..2f84d7f 100644 --- a/cdtweaks/luke/lua/kit/parry.lua +++ b/cdtweaks/luke/lua/kit/parry.lua @@ -469,6 +469,9 @@ function %BLADE_SWASHBUCKLER_PARRY%(CGameEffect, CGameSprite) ["saveMod"] = v["saveMod"], ["special"] = v["special"], -- + ["m_school"] = selectedWeaponAbility.school, + ["m_secondaryType"] = selectedWeaponAbility.secondaryType, + -- ["m_sourceRes"] = selectedWeaponResRef, ["m_sourceType"] = 2, ["sourceID"] = CGameEffect.m_sourceId, From a5d1cde78b8864686032f1632e169549dead88c8 Mon Sep 17 00:00:00 2001 From: 4Luke4 Date: Thu, 12 Dec 2024 12:11:57 +0100 Subject: [PATCH 5/5] Update parry.lua --- cdtweaks/luke/lua/kit/parry.lua | 42 ++++++++++++++++----------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/cdtweaks/luke/lua/kit/parry.lua b/cdtweaks/luke/lua/kit/parry.lua index 2f84d7f..384acfa 100644 --- a/cdtweaks/luke/lua/kit/parry.lua +++ b/cdtweaks/luke/lua/kit/parry.lua @@ -94,6 +94,8 @@ EEex_Sprite_AddBlockWeaponHitListener(function(args) 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 @@ -108,10 +110,8 @@ EEex_Sprite_AddBlockWeaponHitListener(function(args) 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 -- - local targetActiveStats = EEex_Sprite_GetActiveStats(targetSprite) - -- - 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... 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) @@ -400,51 +400,51 @@ function %BLADE_SWASHBUCKLER_PARRY%(CGameEffect, CGameSprite) elseif v["targetType"] == 3 or (v["targetType"] == 6 and sourceSprite.m_typeAI.m_EnemyAlly == 2) then -- party for i = 0, 5 do local partyMember = EEex_Sprite_GetInPortrait(i) -- CGameSprite - if partyMember and EEex_BAnd(partyMember.m_derivedStats.m_generalState, 0x800) == 0 then -- skip if STATE_DEAD + if partyMember and EEex_BAnd(partyMember:getActiveStats().m_generalState, 0x800) == 0 then -- skip if STATE_DEAD table.insert(array, partyMember) end end elseif v["targetType"] == 4 then -- everyone local everyone = EEex_Area_GetAllOfTypeInRange(sourceSprite.m_pArea, sourceSprite.m_pos.x, sourceSprite.m_pos.y, GT_AI_ObjectType["ANYONE"], 0x7FFF, false, nil, nil) -- - for _, sprite in ipairs(everyone) do - if EEex_BAnd(sprite.m_derivedStats.m_generalState, 0x800) == 0 then -- skip if STATE_DEAD - table.insert(array, sprite) + for _, itrSprite in ipairs(everyone) do + if EEex_BAnd(itrSprite:getActiveStats().m_generalState, 0x800) == 0 then -- skip if STATE_DEAD + table.insert(array, itrSprite) end end elseif v["targetType"] == 5 then -- everyone but party local everyone = EEex_Area_GetAllOfTypeInRange(sourceSprite.m_pArea, sourceSprite.m_pos.x, sourceSprite.m_pos.y, GT_AI_ObjectType["ANYONE"], 0x7FFF, false, nil, nil) -- - for _, sprite in ipairs(everyone) do - if sprite.m_typeAI.m_EnemyAlly ~= 2 then - if EEex_BAnd(sprite.m_derivedStats.m_generalState, 0x800) == 0 then -- skip if STATE_DEAD - table.insert(array, sprite) + for _, itrSprite in ipairs(everyone) do + if itrSprite.m_typeAI.m_EnemyAlly ~= 2 then + if EEex_BAnd(itrSprite:getActiveStats().m_generalState, 0x800) == 0 then -- skip if STATE_DEAD + table.insert(array, itrSprite) end end end elseif v["targetType"] == 6 then -- caster group local casterGroup = EEex_Area_GetAllOfTypeStringInRange(sourceSprite.m_pArea, sourceSprite.m_pos.x, sourceSprite.m_pos.y, string.format("[0.0.0.0.%d]", sourceSprite.m_typeAI.m_Specifics), 0x7FFF, false, nil, nil) -- - for _, sprite in ipairs(casterGroup) do - if EEex_BAnd(sprite.m_derivedStats.m_generalState, 0x800) == 0 then -- skip if STATE_DEAD - table.insert(array, sprite) + for _, itrSprite in ipairs(casterGroup) do + if EEex_BAnd(itrSprite:getActiveStats().m_generalState, 0x800) == 0 then -- skip if STATE_DEAD + table.insert(array, itrSprite) end end elseif v["targetType"] == 7 then -- target group local targetGroup = EEex_Area_GetAllOfTypeStringInRange(sourceSprite.m_pArea, sourceSprite.m_pos.x, sourceSprite.m_pos.y, string.format("[0.0.0.0.%d]", CGameSprite.m_typeAI.m_Specifics), 0x7FFF, false, nil, nil) -- - for _, sprite in ipairs(targetGroup) do - if EEex_BAnd(sprite.m_derivedStats.m_generalState, 0x800) == 0 then -- skip if STATE_DEAD - table.insert(array, sprite) + for _, itrSprite in ipairs(targetGroup) do + if EEex_BAnd(itrSprite:getActiveStats().m_generalState, 0x800) == 0 then -- skip if STATE_DEAD + table.insert(array, itrSprite) end end elseif v["targetType"] == 8 then -- everyone but self local everyone = EEex_Area_GetAllOfTypeInRange(sourceSprite.m_pArea, sourceSprite.m_pos.x, sourceSprite.m_pos.y, GT_AI_ObjectType["ANYONE"], 0x7FFF, false, nil, nil) -- - for _, sprite in ipairs(everyone) do - if sprite.m_id ~= sourceSprite.m_id then - if EEex_BAnd(sprite.m_derivedStats.m_generalState, 0x800) == 0 then -- skip if STATE_DEAD - table.insert(array, sprite) + for _, itrSprite in ipairs(everyone) do + if itrSprite.m_id ~= sourceSprite.m_id then + if EEex_BAnd(itrSprite:getActiveStats().m_generalState, 0x800) == 0 then -- skip if STATE_DEAD + table.insert(array, itrSprite) end end end