diff --git a/cdtweaks/languages/english/dirty_fighting.tra b/cdtweaks/languages/english/dirty_fighting.tra new file mode 100644 index 00000000..fbe84d3f --- /dev/null +++ b/cdtweaks/languages/english/dirty_fighting.tra @@ -0,0 +1,2 @@ +@100 = "Dirty Fighting: the character suffers 5% of its maximum health as unmitigated damage" +@101 = "Unaffected by effects from Dirty Fighting" \ No newline at end of file diff --git a/cdtweaks/languages/english/weidu.tra b/cdtweaks/languages/english/weidu.tra index ee6d1dff..6ad810d3 100644 --- a/cdtweaks/languages/english/weidu.tra +++ b/cdtweaks/languages/english/weidu.tra @@ -823,3 +823,5 @@ Use Baldur.lua options: a7_interval_ini @616000 = "NWN-ish Barbarian Rage [Luke]" @618000 = "Blind Fight innate feat for Berserkers [Luke (EEex)]" + +@619000 = "Dirty Fighting class feat for Chaotic-aligned Rogues [Luke (EEex)]" \ No newline at end of file diff --git a/cdtweaks/languages/italian/dirty_fighting.tra b/cdtweaks/languages/italian/dirty_fighting.tra new file mode 100644 index 00000000..4432ec13 --- /dev/null +++ b/cdtweaks/languages/italian/dirty_fighting.tra @@ -0,0 +1,2 @@ +@100 = "Combattimento Scorretto: il personaggio subisce il 5% della sua salute massima come danno puro" +@101 = "Non soggetto agli effetti di Combattimento Scorretto" \ No newline at end of file diff --git a/cdtweaks/languages/italian/weidu.tra b/cdtweaks/languages/italian/weidu.tra index f7a66576..765f3efd 100644 --- a/cdtweaks/languages/italian/weidu.tra +++ b/cdtweaks/languages/italian/weidu.tra @@ -735,4 +735,6 @@ Usa opzioni di Baldur.lua: a7_interval_ini @616000 = "Ira Barbarica in stile NWN [Luke]" -@618000 = "Aggiungi talento innato Combattere alla Cieca per i Berserker [Luke (EEex)]" \ No newline at end of file +@618000 = "Aggiungi talento innato Combattere alla Cieca per i Berserker [Luke (EEex)]" + +@619000 = "Aggiungi talento di classe Combattimento Scorretto per i Ladri di allineamento Caotico [Luke (EEex)]" diff --git a/cdtweaks/lib/comp_6190.tpa b/cdtweaks/lib/comp_6190.tpa new file mode 100644 index 00000000..2c2e52f1 --- /dev/null +++ b/cdtweaks/lib/comp_6190.tpa @@ -0,0 +1,17 @@ +/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\///// +/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\///// +///// ///// +///// NWN-ish Dirty Fighting \\\\\ +///// \\\\\ +/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\///// +/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\///// + +WITH_SCOPE BEGIN + INCLUDE "cdtweaks\luke\misc.tph" + // + INCLUDE "cdtweaks\lib\dirty_fighting.tph" + // + WITH_TRA "cdtweaks\languages\english\dirty_fighting.tra" "cdtweaks\languages\%LANGUAGE%\dirty_fighting.tra" BEGIN + LAF "DIRTY_FIGHTING" END + END +END \ No newline at end of file diff --git a/cdtweaks/lib/dirty_fighting.tph b/cdtweaks/lib/dirty_fighting.tph new file mode 100644 index 00000000..9418dd31 --- /dev/null +++ b/cdtweaks/lib/dirty_fighting.tph @@ -0,0 +1,30 @@ +DEFINE_ACTION_FUNCTION "DIRTY_FIGHTING" +BEGIN + LAF "GT_ADD_SPELL" + INT_VAR + "type" = 4 + "level" = 4 + STR_VAR + "idsName" = "THIEF_DIRTY_FIGHTING" + RET + "THIEF_DIRTY_FIGHTING" = "resName" + END + // + WITH_SCOPE BEGIN + ACTION_TO_LOWER "THIEF_DIRTY_FIGHTING" + // + CREATE "eff" "%THIEF_DIRTY_FIGHTING%b" + COPY_EXISTING "%THIEF_DIRTY_FIGHTING%b.eff" "override" + WRITE_LONG 0x10 402 // invoke lua + WRITE_ASCII 0x30 "%THIEF_DIRTY_FIGHTING%" #8 // lua func + WRITE_SHORT 0x2C 100 // probability1 + BUT_ONLY + END + // lua + WITH_SCOPE BEGIN + OUTER_SET "feedback_strref_hit" = RESOLVE_STR_REF (@100) + OUTER_SET "feedback_strref_immune" = RESOLVE_STR_REF (@101) + // + LAF "APPEND_LUA_FUNCTION" STR_VAR "description" = "Class/Kit Abilities" "sourceFileSpec" = "cdtweaks\luke\lua\class\dirty_fighting.lua" "destRes" = "m_gtspcl" END + END +END \ No newline at end of file diff --git a/cdtweaks/luke/lua/class/dirty_fighting.lua b/cdtweaks/luke/lua/class/dirty_fighting.lua new file mode 100644 index 00000000..d717a4e9 --- /dev/null +++ b/cdtweaks/luke/lua/class/dirty_fighting.lua @@ -0,0 +1,197 @@ +--[[ ++------------------------------------------------------------------------+ +| cdtweaks, NWN-ish Dirty Fighting class feat for chaotic-aligned rogues | ++------------------------------------------------------------------------+ +--]] + +-- Apply Ability -- + +EEex_Opcode_AddListsResolvedListener(function(sprite) + -- Sanity check + if not EEex_GameObject_IsSprite(sprite) then + return + end + -- internal function that applies the actual feat + local apply = function() + -- Mark the creature as 'feat applied' + sprite:setLocalInt("gtThiefDirtyFighting", 1) + -- + sprite:applyEffect({ + ["effectID"] = 321, -- Remove effects by resource + ["res"] = "%THIEF_DIRTY_FIGHTING%", + ["sourceID"] = sprite.m_id, + ["sourceTarget"] = sprite.m_id, + }) + sprite:applyEffect({ + ["effectID"] = 248, -- Melee hit effect + ["durationType"] = 9, + ["res"] = "%THIEF_DIRTY_FIGHTING%B", -- EFF file + ["m_sourceRes"] = "%THIEF_DIRTY_FIGHTING%", + ["sourceID"] = sprite.m_id, + ["sourceTarget"] = sprite.m_id, + }) + end + -- Check creature's class / flags + 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 + -- Check if rogue class -- single/multi/(complete)dual + local applyAbility = spriteClassStr == "THIEF" or spriteClassStr == "FIGHTER_MAGE_THIEF" + or (spriteClassStr == "FIGHTER_THIEF" and (EEex_IsBitUnset(spriteFlags, 0x6) or spriteLevel1 > spriteLevel2)) + or (spriteClassStr == "MAGE_THIEF" and (EEex_IsBitUnset(spriteFlags, 0x6) or spriteLevel1 > spriteLevel2)) + or (spriteClassStr == "CLERIC_THIEF" and (EEex_IsBitUnset(spriteFlags, 0x6) or spriteLevel1 > spriteLevel2)) + -- Check if chaotic + local alignmentMaskChaotic = EEex_Trigger_ParseConditionalString("Alignment(Myself,MASK_CHAOTIC)") + -- + local applyAbility = applyAbility and alignmentMaskChaotic:evalConditionalAsAIBase(sprite) + -- + if sprite:getLocalInt("gtThiefDirtyFighting") == 0 then + if applyAbility then + apply() + end + else + if applyAbility then + -- do nothing + else + -- Mark the creature as 'feat removed' + sprite:setLocalInt("gtThiefDirtyFighting", 0) + -- + sprite:applyEffect({ + ["effectID"] = 321, -- Remove effects by resource + ["res"] = "%THIEF_DIRTY_FIGHTING%", + ["sourceID"] = sprite.m_id, + ["sourceTarget"] = sprite.m_id, + }) + end + end + -- + alignmentMaskChaotic:free() +end) + +-- Core op402 listener -- + +function %THIEF_DIRTY_FIGHTING%(CGameEffect, CGameSprite) + local sourceSprite = EEex_GameObject_Get(CGameEffect.m_sourceId) + -- + local sourceAux = EEex_GetUDAux(sourceSprite) + -- + local equipment = sourceSprite.m_equipment -- CGameSpriteEquipment + local selectedWeapon = equipment.m_items:get(equipment.m_selectedWeapon) -- CItem + local selectedWeaponHeader = selectedWeapon.pRes.pHeader -- Item_Header_st + local selectedWeaponAbility = EEex_Resource_GetItemAbility(selectedWeaponHeader, equipment.m_selectedWeaponAbility) -- Item_ability_st + -- + local isUsableBySingleClassThief = EEex_IsBitUnset(selectedWeaponHeader.notUsableBy, 22) + -- + if sourceSprite.m_leftAttack == 1 then -- if off-hand attack + 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 not (pHeader.itemType == 0xC) then -- if not shield, then overwrite item ability/usability check... + selectedWeaponAbility = EEex_Resource_GetItemAbility(pHeader, 0) -- Item_ability_st + isUsableBySingleClassThief = EEex_IsBitUnset(pHeader.notUsableBy, 22) + end + end + end + -- + local immunityToDamage = EEex_Trigger_ParseConditionalString("EEex_IsImmuneToOpcode(Myself,12)") + -- + local targetActiveStats = EEex_Sprite_GetActiveStats(CGameSprite) + -- + local resistDamageTypeTable = { + [0x10] = targetActiveStats.m_nResistPiercing, -- piercing + [0x0] = targetActiveStats.m_nResistCrushing, -- crushing + [0x100] = targetActiveStats.m_nResistSlashing, -- slashing + [0x80] = targetActiveStats.m_nResistMissile, -- missile + [0x800] = targetActiveStats.m_nResistCrushing, -- non-lethal + } + 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 sourceAux["gt_ThiefDirtyFighting_FirstAttack"] then + if isUsableBySingleClassThief then + if itmAbilityDamageTypeToIDS[selectedWeaponAbility.damageType] then -- sanity check + if resistDamageTypeTable[itmAbilityDamageTypeToIDS[selectedWeaponAbility.damageType]] < 100 and not immunityToDamage:evalConditionalAsAIBase(CGameSprite) then + -- 5% unmitigated damage + EEex_GameObject_ApplyEffect(CGameSprite, + { + ["effectID"] = 0xC, -- Damage + ["dwFlags"] = itmAbilityDamageTypeToIDS[selectedWeaponAbility.damageType] * 0x10000 + 3, -- mode: reduce by percentage + --["numDice"] = 1, + --["diceSize"] = 4, + ["effectAmount"] = 5, + ["m_sourceRes"] = CGameEffect.m_sourceRes:get(), + ["m_sourceType"] = CGameEffect.m_sourceType, + ["sourceID"] = CGameEffect.m_sourceId, + ["sourceTarget"] = CGameEffect.m_sourceTarget, + }) + -- the percentage mode of op12 does not provide feedback, so we have to manually display it... + EEex_GameObject_ApplyEffect(CGameSprite, + { + ["effectID"] = 139, -- Display string + ["effectAmount"] = %feedback_strref_hit%, + ["m_sourceRes"] = CGameEffect.m_sourceRes:get(), + ["m_sourceType"] = CGameEffect.m_sourceType, + ["sourceID"] = CGameEffect.m_sourceId, + ["sourceTarget"] = CGameEffect.m_sourceTarget, + }) + else + EEex_GameObject_ApplyEffect(CGameSprite, + { + ["effectID"] = 139, -- Immunity to resource and message + ["effectAmount"] = %feedback_strref_immune%, + ["m_sourceRes"] = CGameEffect.m_sourceRes:get(), + ["m_sourceType"] = CGameEffect.m_sourceType, + ["sourceID"] = CGameEffect.m_sourceId, + ["sourceTarget"] = CGameEffect.m_sourceTarget, + }) + end + end + end + end + -- + immunityToDamage:free() +end + +-- Flag first attack in each round -- + +EEex_Opcode_AddListsResolvedListener(function(sprite) + -- Sanity check + if not EEex_GameObject_IsSprite(sprite) then + return + end + -- + local conditionalString = EEex_Trigger_ParseConditionalString('!GlobalTimerNotExpired("gtDirtyFightingTimer","LOCALS")') + local responseString = EEex_Action_ParseResponseString('SetGlobalTimer("gtDirtyFightingTimer","LOCALS",6)') + -- + local spriteAux = EEex_GetUDAux(sprite) + -- + if sprite:getLocalInt("gtThiefDirtyFighting") == 1 then + if sprite.m_startedSwing == 1 then + if conditionalString:evalConditionalAsAIBase(sprite) then + responseString:executeResponseAsAIBaseInstantly(sprite) + spriteAux["gt_ThiefDirtyFighting_FirstAttack"] = true + end + else + if not conditionalString:evalConditionalAsAIBase(sprite) and spriteAux["gt_ThiefDirtyFighting_FirstAttack"] then + spriteAux["gt_ThiefDirtyFighting_FirstAttack"] = false + end + end + end + -- + conditionalString:free() + responseString:free() +end) diff --git a/cdtweaks/readme-cdtweaks.html b/cdtweaks/readme-cdtweaks.html index c5f52be7..121e6565 100644 --- a/cdtweaks/readme-cdtweaks.html +++ b/cdtweaks/readme-cdtweaks.html @@ -1481,6 +1481,7 @@

NWN-ish Feats Collection

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

+

Spellcraft / Counterspell class feat for spellcasters [Luke]
EEex

This component aims at implementing the NWN feat Spellcraft / Counterspell.
@@ -1561,16 +1562,29 @@

NWN-ish Feats Collection

This feat grants the character the ability to fight well, even if blinded. As a result, whenever the character gets blinded, it does not suffer from the 4 THAC0 / AC penalty. -

-

- Notes: -

+ +

Dirty Fighting class feat for Chaotic-aligned Rogues [Luke]
+ EEex

+

+ This component aims at implementing the NWN feat Dirty Fighting. +

+

+ The character knows brutal and effective fighting tactics. During the first attack of each round, the character can elect to perform a dirty fighting move that will deal 5% of the target's maximum health as unmitigated damage.

- -

Contact Information

diff --git a/cdtweaks/setup-cdtweaks.tp2 b/cdtweaks/setup-cdtweaks.tp2 index ab55217a..6d50e05b 100644 --- a/cdtweaks/setup-cdtweaks.tp2 +++ b/cdtweaks/setup-cdtweaks.tp2 @@ -4988,3 +4988,19 @@ 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%/blind_fight.tra~ @7 LABEL ~cd_tweaks_nwn_blind_fight~ + +/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\ +/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\ +///// \\\\\ +///// Dirty Fighting class feat for Rogues \\\\\ +///// \\\\\ +/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\ +/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\ + +BEGIN @619000 DESIGNATED 6190 +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%/dirty_fighting.tra~ @7 +LABEL ~cd_tweaks_nwn_dirty_fighting~ +