Skip to content

Commit

Permalink
Merge pull request #112 from 4Luke4/dirty_fighting
Browse files Browse the repository at this point in the history
New component: Dirty Fighting
  • Loading branch information
CamDawg authored Jan 10, 2025
2 parents 0c2125a + 240007a commit fa56b36
Show file tree
Hide file tree
Showing 9 changed files with 289 additions and 7 deletions.
2 changes: 2 additions & 0 deletions cdtweaks/languages/english/dirty_fighting.tra
Original file line number Diff line number Diff line change
@@ -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"
2 changes: 2 additions & 0 deletions cdtweaks/languages/english/weidu.tra
Original file line number Diff line number Diff line change
Expand Up @@ -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)]"
2 changes: 2 additions & 0 deletions cdtweaks/languages/italian/dirty_fighting.tra
Original file line number Diff line number Diff line change
@@ -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"
4 changes: 3 additions & 1 deletion cdtweaks/languages/italian/weidu.tra
Original file line number Diff line number Diff line change
Expand Up @@ -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)]"
@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)]"
17 changes: 17 additions & 0 deletions cdtweaks/lib/comp_6190.tpa
Original file line number Diff line number Diff line change
@@ -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
30 changes: 30 additions & 0 deletions cdtweaks/lib/dirty_fighting.tph
Original file line number Diff line number Diff line change
@@ -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
197 changes: 197 additions & 0 deletions cdtweaks/luke/lua/class/dirty_fighting.lua
Original file line number Diff line number Diff line change
@@ -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<CItem*,39>
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)
26 changes: 20 additions & 6 deletions cdtweaks/readme-cdtweaks.html
Original file line number Diff line number Diff line change
Expand Up @@ -1481,6 +1481,7 @@ <h3> <a id="NWN-ish_feats" name="NWN-ish_feats"></a>NWN-ish Feats Collection </h
<div class="section">
<p> Components in this category are inspired from <a href="https://nwn.beamdog.com/">NWN</a> and are aimed at providing new class/kit abilities.</p>


<p> <strong>Spellcraft / Counterspell class feat for spellcasters [Luke]</strong><br />
<em><abbr title="Enhanced Edition">EEex</abbr></em></p>
<p> This component aims at implementing the NWN feat Spellcraft / Counterspell.<br />
Expand Down Expand Up @@ -1561,16 +1562,29 @@ <h3> <a id="NWN-ish_feats" name="NWN-ish_feats"></a>NWN-ish Feats Collection </h
</p>
<p>
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.
</p>
<p>
Notes:
<ul>

<li>Use: automatic.</li>
<li>
Unmitigated damage means that damage resistance is ignored.
<ul>
<li>However, if the target's damage resistance is 100+, then it will be completely immune to the dirty fighting move.</li>
</ul>
</li>
<li>The rogue must wield a melee weapon usable by single-class thieves in order to perform a dirty fighting move.</li>

<li>This feat can only negate the melee THAC0 penalty. If the character is wielding a ranged weapon, only the AC penalty will be negated.</li>

</ul>
</p>

<p> <strong>Dirty Fighting class feat for Chaotic-aligned Rogues [Luke]</strong><br />
<em><abbr title="Enhanced Edition">EEex</abbr></em></p>
<p>
This component aims at implementing the NWN feat Dirty Fighting.
</p>
<p>
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.</p>
</div>


<div class="ribbon_rectangle_h2"> <a id="Contact" name="Contact"></a>
<h2> Contact Information </h2>
</div>
Expand Down
16 changes: 16 additions & 0 deletions cdtweaks/setup-cdtweaks.tp2
Original file line number Diff line number Diff line change
Expand Up @@ -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~

0 comments on commit fa56b36

Please sign in to comment.