Skip to content

Commit

Permalink
Merge pull request #760 from doug1234/ShieldStuff
Browse files Browse the repository at this point in the history
Added shield feats from Sagenlicht
  • Loading branch information
doug1234 authored Feb 22, 2024
2 parents 9c8487d + c44d823 commit 9875d36
Show file tree
Hide file tree
Showing 18 changed files with 344 additions and 37 deletions.
61 changes: 39 additions & 22 deletions TemplePlus/condition.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1935,14 +1935,28 @@ int __cdecl GlobalToHitBonus(DispatcherCallbackArgs args)
}
if (dualWielding)
{
if (d20Sys.UsingSecondaryWeapon(args.objHndCaller, attackCode))
bonusSys.bonusAddToBonusList(&dispIo->bonlist, -10, 26, 121); // penalty for dualwield on offhand attack
else
bonusSys.bonusAddToBonusList(&dispIo->bonlist, -6, 27, 122); // penalty for dualwield on primary attack

if (critterSys.OffhandIsLight(args.objHndCaller))
{
bonusSys.bonusAddToBonusList(&dispIo->bonlist, 2, 0, 167); // Light Off-hand Weapon
//See if python is going to handle the penalty
const bool overridePenalty = d20Sys.D20QueryPython(args.objHndCaller, "Override Two Weapon Penalty", dualWielding ? 1 :0);
if (overridePenalty) {
auto penalty = d20Sys.D20QueryPython(args.objHndCaller, "Get Two Weapon Penalty", dualWielding ? 1 : 0);
if (d20Sys.UsingSecondaryWeapon(args.objHndCaller, attackCode)) {
bonusSys.bonusAddToBonusList(&dispIo->bonlist, penalty, 26, 121);
}
else {
bonusSys.bonusAddToBonusList(&dispIo->bonlist, penalty, 27, 122);
}
}
else {
if (d20Sys.UsingSecondaryWeapon(args.objHndCaller, attackCode))
bonusSys.bonusAddToBonusList(&dispIo->bonlist, -10, 26, 121); // penalty for dualwield on offhand attack
else
bonusSys.bonusAddToBonusList(&dispIo->bonlist, -6, 27, 122); // penalty for dualwield on primary attack

if (critterSys.OffhandIsLight(args.objHndCaller))
{
bonusSys.bonusAddToBonusList(&dispIo->bonlist, 2, 0, 167); // Light Off-hand Weapon
}
}
}
}
Expand Down Expand Up @@ -3533,22 +3547,25 @@ int __cdecl GreaterTWFRanger(DispatcherCallbackArgs args)

int __cdecl TwoWeaponFightingBonus(DispatcherCallbackArgs args)
{
DispIoAttackBonus *dispIo = dispatch.DispIoCheckIoType5((DispIoAttackBonus*)args.dispIO);
char *featName;
DispIoAttackBonus* dispIo = dispatch.DispIoCheckIoType5((DispIoAttackBonus*)args.dispIO);

feat_enums feat = (feat_enums)conds.CondNodeGetArg(args.subDispNode->condNode, 0);
int attackCode = dispIo->attackPacket.dispKey;
int dualWielding = 0;
int attackNumber = 1;
if (d20Sys.UsingSecondaryWeapon(args.objHndCaller, attackCode))
{
featName = feats.GetFeatName(feat);
bonusSys.bonusAddToBonusListWithDescr(&dispIo->bonlist, 6, 0, 114, featName);
}
else if ( d20Sys.ExtractAttackNumber(args.objHndCaller, attackCode, &attackNumber, &dualWielding), dualWielding != 0)
{
featName = feats.GetFeatName(feat);
bonusSys.bonusAddToBonusListWithDescr(&dispIo->bonlist, 2, 0, 114, featName);
//Disable when using agile shield fighter for examle
if (d20Sys.D20QueryPython(args.objHndCaller, "Disable Two Weapon Fighting Bonus") == 0) {
char* featName;
feat_enums feat = (feat_enums)conds.CondNodeGetArg(args.subDispNode->condNode, 0);
int attackCode = dispIo->attackPacket.dispKey;
int dualWielding = 0;
int attackNumber = 1;
if (d20Sys.UsingSecondaryWeapon(args.objHndCaller, attackCode))
{
featName = feats.GetFeatName(feat);
bonusSys.bonusAddToBonusListWithDescr(&dispIo->bonlist, 6, 0, 114, featName);
}
else if (d20Sys.ExtractAttackNumber(args.objHndCaller, attackCode, &attackNumber, &dualWielding), dualWielding != 0)
{
featName = feats.GetFeatName(feat);
bonusSys.bonusAddToBonusListWithDescr(&dispIo->bonlist, 2, 0, 114, featName);
}
}
return 0;
}
Expand Down
24 changes: 14 additions & 10 deletions tpdatasrc/tpgamefiles/mes/help/new_feats_help.tab

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions tpdatasrc/tpgamefiles/rules/feats/active shield defense.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
name: Active Shield Defense
flags: 12582928
prereqs:
description: Your expert use of your shield allows you to strike at vulnerable foes even when you forgo your own attacks in favor of defense.
prereq descr: Proficiency with shields, Shield Specialization.
5 changes: 5 additions & 0 deletions tpdatasrc/tpgamefiles/rules/feats/agile shield fighter.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
name: Agile Shield Fighter
flags: 12582928
prereqs:
description: When making a shield bash and armed strike attack as part of a full attack action, you take a -2 penalty on each attack. These penalties replace the normal ones you incur for fighting with two weapons.
prereq descr: Improved Shield Bash, Shield Specialization and Shield Specialization.
5 changes: 5 additions & 0 deletions tpdatasrc/tpgamefiles/rules/feats/shield charge.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
name: Shield Charge
flags: 12582928
prereqs: 266 3 1216 1
description: If you hit an opponent with your shield as part of a charge action, in addition to dealing damage normally, you may make a trip attack without provoking an attack of opportunity. If you lose, the defender does not get to try to trip you in return.
prereq descr: Proficiency with shields, Improved Shield Bash, Base Attack Bonus +3
5 changes: 5 additions & 0 deletions tpdatasrc/tpgamefiles/rules/feats/shield specialization.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
name: Shield Specialization
flags: 12582928
prereqs:
description: You are skilled in using a shield, allowing you to gain greater defensive benefits from it.
prereq descr: Proficiency with shields.
5 changes: 5 additions & 0 deletions tpdatasrc/tpgamefiles/rules/feats/shield ward.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
name: Shield Ward
flags: 12582928
prereqs:
description: You use your shield like a wall of steel and wood. When an opponent attempts to draw in close to you, your shield forces him away or ruins his attacks.
prereq descr: Proficiency with shields, Shield Specialization.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from toee import *
import char_editor

def CheckPrereq(attachee, classLevelled, abilityScoreRaised):
if not char_editor.has_feat(feat_shield_proficiency):
return 0
elif not char_editor.has_feat("Shield Specialization"):
return 0
return 1
11 changes: 11 additions & 0 deletions tpdatasrc/tpgamefiles/scr/feats/feat - Agile Shield Fighter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from toee import *
import char_editor

def CheckPrereq(attachee, classLevelled, abilityScoreRaised):
if not char_editor.has_feat(feat_shield_proficiency):
return 0
if not char_editor.has_feat(feat_improved_shield_bash):
return 0
if not char_editor.has_feat("Shield Specialization"):
return 0
return 1
8 changes: 8 additions & 0 deletions tpdatasrc/tpgamefiles/scr/feats/feat - Shield Charge.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from toee import *
import char_editor

def CheckPrereq(attachee, classLevelled, abilityScoreRaised):
#Prerequisite of Base Attack Bonus of 3 is handled by Engine
if not char_editor.has_feat(feat_shield_proficiency):
return 0
return 1
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from toee import *
import char_editor

def CheckPrereq(attachee, classLevelled, abilityScoreRaised):
if char_editor.has_feat(feat_shield_proficiency):
return 1
return 0
9 changes: 9 additions & 0 deletions tpdatasrc/tpgamefiles/scr/feats/feat - Shield Ward.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from toee import *
import char_editor

def CheckPrereq(attachee, classLevelled, abilityScoreRaised):
if not char_editor.has_feat(feat_shield_proficiency):
return 0
elif not char_editor.has_feat("Shield Specialization"):
return 0
return 1
40 changes: 40 additions & 0 deletions tpdatasrc/tpgamefiles/scr/tpModifiers/active_shield_defense.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from templeplus.pymod import PythonModifier
from toee import *
import tpdp

# Active Shield Defense: PHB II, p. 71

def getFeatName():
return "Active Shield Defense"

print "Registering {}".format(getFeatName)

def getFeatTag(featName):
return "TAG_{}".format(featName.upper().replace(" ", "_"))

def nullifyToHitPenalty(attachee, args, evt_obj):
flags = evt_obj.attack_packet.get_flags()
if not flags & D20CAF_ATTACK_OF_OPPORTUNITY:
return 0
elif not attachee.d20_query("Fighting Defensively Checked"):
return 0
elif attachee.item_worn_at(item_wear_shield) == OBJ_HANDLE_NULL:
return 0
elif attachee.d20_query("PQ_Total_Defense_Activated"):
return 0
else:
featName = getFeatName()
featTag = getFeatTag(featName)
#Fighting Defensively has a vanilla bonus type of 0, which means it can't be negated
#So I add the penalty as a bonus again
bonusValue = 4
bonusType = 0 #ID 0 = Untyped (stacking!)
evt_obj.bonus_list.add(bonusValue, bonusType, "Feat: ~{}~[{}]".format(featName, featTag))
return 0

# Allowing AoO's while in Total Defense is directly handled in
# total_defense.py (an already existing PythonModifier Extender)

activeShieldDefenseFeat = PythonModifier(getFeatName(), 2) #featEnum, empty
activeShieldDefenseFeat.MapToFeat(getFeatName(), feat_cond_arg2 = 0)
activeShieldDefenseFeat.AddHook(ET_OnToHitBonus2, EK_NONE, nullifyToHitPenalty, ())
42 changes: 42 additions & 0 deletions tpdatasrc/tpgamefiles/scr/tpModifiers/agile_shield_fighter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from templeplus.pymod import PythonModifier
from toee import *
import tpdp

# Agile Shield Fighter: PHB II, p. 74

def getFeatName():
return "Agile Shield Fighter"

print "Registering {}".format(getFeatName)

def AgileShieldFighterDisableTwoWeaponFightingBonus(attachee, args, evt_obj):
shield = attachee.item_worn_at(item_wear_shield)
if shield == OBJ_HANDLE_NULL:
return 0

evt_obj.return_val = 1 #This feat takes over two weapon fighting
return 0

def AgileShieldFighterOverrideTwoWeaponPenalty(attachee, args, evt_obj):
shield = attachee.item_worn_at(item_wear_shield)
if shield == OBJ_HANDLE_NULL:
return 0

evt_obj.return_val = 1
return 0

def AgileShieldFighterGetTwoWeaponPenalty(attachee, args, evt_obj):
shield = attachee.item_worn_at(item_wear_shield)
if shield == OBJ_HANDLE_NULL:
return 0

evt_obj.return_val = -2
return 0

#Need to block two weapon fighting

agileShieldFighter = PythonModifier(getFeatName(), 2) #Spare, Spare
agileShieldFighter.MapToFeat(getFeatName())
agileShieldFighter.AddHook(ET_OnD20PythonQuery, "Disable Two Weapon Fighting Bonus", AgileShieldFighterDisableTwoWeaponFightingBonus, ())
agileShieldFighter.AddHook(ET_OnD20PythonQuery, "Get Two Weapon Penalty", AgileShieldFighterGetTwoWeaponPenalty, ())
agileShieldFighter.AddHook(ET_OnD20PythonQuery, "Override Two Weapon Penalty", AgileShieldFighterOverrideTwoWeaponPenalty, ())
41 changes: 41 additions & 0 deletions tpdatasrc/tpgamefiles/scr/tpModifiers/shield_charge.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from templeplus.pymod import PythonModifier
from toee import *
import tpdp

# Shield Charge: Complete Warrior, p. 105

def getFeatName():
return "Shield Charge"

print "Registering {}".format(getFeatName)

def getFeatTag(featName):
return "TAG_{}".format(featName.upper().replace(" ", "_"))

def radialEntry(attachee, args, evt_obj):
featName = getFeatName()
featTag = getFeatTag(featName)
toggleRadialId = tpdp.RadialMenuEntryToggle(featName, featTag)
toggleRadialId.link_to_args(args, 1)
toggleRadialId.add_child_to_standard(attachee, tpdp.RadialMenuStandardNode.Options)
return 0

def tripAttempt(attachee, args, evt_obj):
enabledFlag = args.get_arg(1)
if enabledFlag and attachee.d20_query("Charging"):
if attachee.item_worn_at(item_wear_shield) != OBJ_HANDLE_NULL:
target = evt_obj.attack_packet.target
if attachee.trip_check(target):
combatMesId = 104 # ID 104 = Tripped!
target.float_mesfile_line('mes\\combat.mes', combatMesId)
target.condition_add_with_args("Prone")
target.fall_down()
else:
combatMesId = 144 #ID 144 = Attempt Fails
attachee.float_mesfile_line('mes\\combat.mes', combatMesId)
return 0

shieldChargeFeat = PythonModifier(getFeatName(), 3) #featEnum, enabledFlag, empty
shieldChargeFeat.MapToFeat(getFeatName(), feat_cond_arg2 = 1)
shieldChargeFeat.AddHook(ET_OnBuildRadialMenuEntry, EK_NONE, radialEntry, ())
shieldChargeFeat.AddHook(ET_OnDealingDamage2, EK_NONE, tripAttempt, ())
34 changes: 34 additions & 0 deletions tpdatasrc/tpgamefiles/scr/tpModifiers/shield_specialization.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from templeplus.pymod import PythonModifier
from toee import *
import tpdp

# Shield Specialization: PHB II, p. 82

def getFeatName():
return "Shield Specialization"

print "Registering {}".format(getFeatName)

def getFeatTag(featName):
return "TAG_{}".format(featName.upper().replace(" ", "_"))

#def getSubFeatList():
# return {0:"Buckler", 1:"Heavy", 2:"Light"}

def applyBonus(attachee, args, evt_obj):
flags = evt_obj.attack_packet.get_flags()
if attachee.item_worn_at(item_wear_shield) == OBJ_HANDLE_NULL:
return 0
elif flags & D20CAF_TOUCH_ATTACK:
return 0
else:
featName = getFeatName()
featTag = getFeatTag(featName)
bonusValue = 1
bonusType = 0 #ID 0 = Untyped (stacking!)
evt_obj.bonus_list.add(bonusValue, bonusType, "Feat: ~{}~[{}]".format(featName, featTag))
return 0

shieldSpecializationFeat = PythonModifier(getFeatName(), 2) #featEnum, empty
shieldSpecializationFeat.MapToFeat(getFeatName(), feat_cond_arg2 = 0)
shieldSpecializationFeat.AddHook(ET_OnGetAC, EK_NONE, applyBonus, ())
52 changes: 52 additions & 0 deletions tpdatasrc/tpgamefiles/scr/tpModifiers/shield_ward.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from templeplus.pymod import PythonModifier
from toee import *
import tpactions

# Shield Ward: PHB II, p. 82

def getFeatName():
return "Shield Ward"

print "Registering {}".format(getFeatName)

def getFeatTag(featName):
return "TAG_{}".format(featName.upper().replace(" ", "_"))

def getShieldBonus(wornShield):
bonusValue = wornShield.item_d20_query(Q_Armor_Get_AC_Bonus)
bonusValue += 1 #Add Shield Specialization Bonus as well; SP is a prereq for this feat
return bonusValue

def applyBonus(attachee, args, evt_obj):
flags = evt_obj.attack_packet.get_flags()
wornShield = attachee.item_worn_at(item_wear_shield)
if wornShield == OBJ_HANDLE_NULL:
return 0
elif not flags & D20CAF_TOUCH_ATTACK:
return 0
else:
featName = getFeatName()
featTag = getFeatTag(featName)
bonusValue = getShieldBonus(wornShield)
bonusType = 0 #ID 0 = Untyped (stacking!)
evt_obj.bonus_list.add(bonusValue, bonusType, "Feat: ~{}~[{}]".format(featName, featTag))
return 0

def addBonusAgainstSpecialAttacks(attachee, args, evt_obj):
currentSequence = tpactions.get_cur_seq()
performer = currentSequence.performer
if performer != attachee:
wornShield = attachee.item_worn_at(item_wear_shield)
flags = evt_obj.flags
if wornShield != OBJ_HANDLE_NULL and flags == 3:
featName = getFeatName()
featTag = getFeatTag(featName)
bonusValue = getShieldBonus(wornShield)
bonusType = 0 #ID 0 = Untyped (stacking!)
evt_obj.bonus_list.add(bonusValue, bonusType, "Feat: ~{}~[{}]".format(featName, featTag))
return 0

shieldWardFeat = PythonModifier(getFeatName(), 2) #featEnum, empty
shieldWardFeat.MapToFeat(getFeatName(), feat_cond_arg2 = 0)
shieldWardFeat.AddHook(ET_OnGetAC, EK_NONE, applyBonus, ())
shieldWardFeat.AddHook(ET_OnGetAbilityCheckModifier, EK_NONE, addBonusAgainstSpecialAttacks, ())
Loading

0 comments on commit 9875d36

Please sign in to comment.