Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New component: Dirty Fighting #112

Merged
merged 7 commits into from
Jan 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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~