From 3e707ee0fa4e6aa3f514b77e6530b68edbf4bf22 Mon Sep 17 00:00:00 2001 From: dolio Date: Mon, 5 Feb 2024 22:04:34 -0500 Subject: [PATCH] Topic/double weapons (#752) * See what happens when GTWF adds an extra attack to quarterstaff * Mistaken about what ItemWornAt reports for quarterstaves * Experiment: treat quarterstaff as two weapon in FullAttackCostCalculate * Factor out fighting style check * Attempt to fix errors * More error fixes * FightingStyle operators * FightingStyle & operator and mask * Various fixes. * Avoid FightingStyle conditional * Factor out light offhand check Also handles double weapons properly * Bad variable reference * Didn't qualify OffhandIsLight * Factor out double weapon test, export to python * Tweak buckler fighting style logic * Add 2-weapon and shield bash query infrastructure * Error fixes * Too many semis * Shield bash critical multiplier * bonList not bonusList * bonlist not bonList * Incorrect access operator * Query 'Shield Bash' for wield type * Default arguments * Mask dice out of shield bash query * Try a different shield bash mask * Have to use new logic for GetWieldType light shields * Shield bash should be armed * Improved/greater TWF for bash/double Logic is simplified due to being factored out. The relevant function just adds 1 if the feat exist and the character is two weapon fighting, so I replaced the ITWF with the GTWF function. * Use switch in GTWF query * GTWF bugs Capitalization on FightingStyle Accidentally deleted an important line previously * More GetAttackWeapon logic for correct AoOs * Proper d20Query call * Fix up two weapon toggle radials * Fix and query twf toggle * Try out some alternate animations based on TWF * Fix some d20Query calls * Fix alternate animation code * Add logic for left handed single weapon fighting Also add some constant query codes that were missing * Fix errors * Use toggle to choose attack anim * d20Query args * Fix backwards OffhandIsLight logic * Animation swapping was too localized * Audit GetWieldType and remove call to original function * Shield bash work Help text Make shield bashing remove AC contribution from the shield Mesline addition for the above * Fix errors in shield AC logic * Pointer mistake * getDisplayName capitalization * Missing return value * More shield bashing logic Make 'Shield Bash' a 3-argument condition since it's an item condition. Use arg2 to find the relevant shield. Assorted shield bash AC penalty logic tweaking Reset bash AC penalty on begin round * Shield bash proficiency and help updates * Proficiency packet weapon fix * Twiddle bash proficiency logic Seems to always being applied for reasons I don't understand. * Martial feat check needs to be by class * Power attack damage logic Some of wielding logic was copied directly in, and it wasn't correct with respect to double weapons and such, so I've changed it to delegate to factored out functions. * dispIo capitalization * Tweak power attack logic, was missing some light weapons * Two weapon toggle fill out Move some functions out of the common class. Implement stickiness of toggles. * Try hiding two weapon toggles on attack * Attempt to fix shield AC bonus and query * Error fixes * Capitalization * Extend AC stacking fix to armor, rework a bit * ArmorAcBonus declaration * Add Two Weapon Toggles to initial conditions * Try defaulting TWF toggles based on equipment events * Tracing mistake * Use info instead of trace for TWF logging * Remove TWF tracing; auto-set for double weapon equip * Strict rules logic for Dwarven Waraxe wield * Replace original GetWieldType for fixed logic * Add strict logic for weapon proficiencies * Bad variable reference * Weapon animation tweaks; gnome hammer * Fix 2-handed strength bonus logic * Bad variable reference * Try to avoid duplicate Two Weapon Toggles * Add python combat logic for TWF toggles I'd been testing this in an override until now * See what polearm anim looks like on staves * Give bucklers the AC calculation treatment * Try adding to co8infra help_extensions * Update kotb help_extensions * See what happens if we enable weapon fields for armor I expect nothing good. * Roll back * Un-nest ParseType logic + trailing whitespace * Expand Shield Bash condition and try to parse from weapon info * Address errors * Explicit damage type casts * Different DamageTypeFromString wrapper strategy * Try to inspect new shield parsing, maybe fix a problem * Use info logger * A bit more logging * Try hooking into the generic number parser * GenericNumber fixes * Tweak armor weapon hooks, add crit multiplier * Undeclared variable * Determine shield wield type via size Means some sizes need to be fixed up, since many shields seem to be erroneously size large. * Add weapon stats for shields in co8 protos_override.tab * Damage type in armor dice query Change shield bash query back to just return 1, because apparently we don't need to have a separate damage type query. * DamageType cast * Avoid putting shield bash on all armor * Bad variable reference * Fix some bugs Armor flags are not set yet when weapon information is parsed. Need to use a different mechanism when other armor-based weapons are added, if ever. DamageTypeFromString wasn't parsing the strings I thought. Make a separate function for D20DT strings. * Fix some errors * Variable naming bug * Remove logging, guard armor weapons behind weapon type shield * Switch animation for cutlass (slashing shortsword) * Error fixes * Missed an error * Might need an explicit cast * 1 handed cutlass override * Roll back staff polearm animation * Remove some debugging in python --- TemplePlus/action_sequence.cpp | 23 +- TemplePlus/condition.cpp | 485 +++++++++++++----- TemplePlus/critter.cpp | 141 +++++ TemplePlus/critter.h | 22 + TemplePlus/d20.cpp | 20 +- TemplePlus/d20_status.cpp | 1 + TemplePlus/dispatcher.cpp | 7 + TemplePlus/dispatcher.h | 1 + TemplePlus/feat.cpp | 21 +- TemplePlus/inventory.cpp | 77 ++- TemplePlus/inventory.h | 5 +- TemplePlus/protos.cpp | 261 ++++++++-- TemplePlus/python/python_object.cpp | 10 + TemplePlus/temple_enums.h | 3 + TemplePlus/ui/ui_char.cpp | 3 +- tpdata/templeplus/lib/templeplus/constants.py | 12 + tpdata/tpmes/bonus.mes | 3 +- tpdata/tpmes/combat.mes | 2 + tpdata/tpmes/help_extensions.tab | 4 + tpdatasrc/co8fixes/rules/protos_override.tab | 32 ++ tpdatasrc/co8infra/tpmes/help_extensions.tab | 4 + tpdatasrc/kotbfixes/tpmes/help_extensions.tab | 4 + .../tpgamefiles/rules/d20_combat/common.py | 61 +++ .../rules/d20_combat/damage_critter.py | 15 +- .../rules/d20_combat/to_hit_processing.py | 24 +- 25 files changed, 1010 insertions(+), 231 deletions(-) create mode 100644 tpdatasrc/tpgamefiles/rules/d20_combat/common.py diff --git a/TemplePlus/action_sequence.cpp b/TemplePlus/action_sequence.cpp index 8cde7489f..ca0110cba 100644 --- a/TemplePlus/action_sequence.cpp +++ b/TemplePlus/action_sequence.cpp @@ -3414,23 +3414,16 @@ void ActionSequenceSystem::FullAttackCostCalculate(D20Actn* d20a, TurnBasedStatu auto mainWeapon = inventory.ItemWornAt(performer, EquipSlot::WeaponPrimary); auto offhand = inventory.ItemWornAt(performer, EquipSlot::WeaponSecondary); - if (offhand){ - if (mainWeapon){ - if (objects.GetType(offhand) != obj_t_armor) { - _attackTypeCodeHigh = ATTACK_CODE_OFFHAND + 1; // originally 5 - _attackTypeCodeLow = ATTACK_CODE_OFFHAND; // originally 4 - usingOffhand = 1; - } - } - else { - mainWeapon = offhand; - } + auto style = critterSys.GetFightingStyle(performer); + if ((style & FightingStyle::Mask) == FightingStyle::TwoWeapon) { + _attackTypeCodeHigh = ATTACK_CODE_OFFHAND + 1; // originally 5 + _attackTypeCodeLow = ATTACK_CODE_OFFHAND; // originally 4 + usingOffhand = 1; } - if (mainWeapon){ - int weapFlags = objects.getInt32(mainWeapon, obj_f_weapon_flags); - if (weapFlags & OWF_RANGED_WEAPON) - d20a->d20Caf |= D20CAF_RANGED; + + if ((style & FightingStyle::Ranged) == FightingStyle::Ranged) { + d20a->d20Caf |= D20CAF_RANGED; } // if unarmed check natural attacks (for monsters) diff --git a/TemplePlus/condition.cpp b/TemplePlus/condition.cpp index dac122fed..267b2a04f 100644 --- a/TemplePlus/condition.cpp +++ b/TemplePlus/condition.cpp @@ -145,6 +145,7 @@ class GenericCallbacks static int QuerySetReturnVal1(DispatcherCallbackArgs args); static int QuerySetReturnVal0(DispatcherCallbackArgs); static int ActionInvalidQueryTrue(DispatcherCallbackArgs); + static int NoOp(DispatcherCallbackArgs); static int EffectTooltipDuration(DispatcherCallbackArgs args); // SubDispDef data1 denotes the effect type idx, data2 denotes combat.mes line; appends duration static int EffectTooltipGeneral(DispatcherCallbackArgs args); @@ -206,9 +207,15 @@ class ItemCallbacks static int __cdecl ArmorCheckPenalty(objHndl armor); static int __cdecl MaxDexBonus(objHndl armor); + static int __cdecl ArmorAcBonus(DispatcherCallbackArgs args); static int __cdecl ArmorBonusAcBonusCapValue(DispatcherCallbackArgs args); static int __cdecl BucklerToHitPenalty(DispatcherCallbackArgs args); static int __cdecl BucklerAcPenalty(DispatcherCallbackArgs args); + static int __cdecl BucklerAcBonus(DispatcherCallbackArgs args); + static int __cdecl ShieldAcPenalty(DispatcherCallbackArgs args); + static int __cdecl ShieldAcBonus(DispatcherCallbackArgs args); + static int __cdecl BaseAcQuery(DispatcherCallbackArgs args); + static int __cdecl EnhAcQuery(DispatcherCallbackArgs args); static int __cdecl WeaponMerciful(DispatcherCallbackArgs); static int __cdecl WeaponSeekingAttackerConcealmentMissChance(DispatcherCallbackArgs args); static int __cdecl WeaponSpeed(DispatcherCallbackArgs args); @@ -462,7 +469,8 @@ class ConditionFunctionReplacement : public TempleFix { replaceFunction(0x100F8F70, DealNormalDamageCallback); replaceFunction(0x100F9040, DealNormalDamageAttackPenalty); - + // ImprovedTWF extra attack; logic is identical to greater twf + replaceFunction(0x100FD1C0, GreaterTwoWeaponFighting); replaceFunction(0x10101150, ItemSkillBonusCallback); @@ -502,6 +510,15 @@ class ConditionFunctionReplacement : public TempleFix { // buckler AC penalty replaceFunction(0x10104E40, itemCallbacks.BucklerAcPenalty); + // shield AC bonus; add shield bash behavior; fix stacking + replaceFunction(0x10100370, itemCallbacks.ShieldAcBonus); + + // buckler AC bonus; fix stacking + replaceFunction(0x10104EE0, itemCallbacks.BucklerAcBonus); + + // armor AC bonus; fix stacking + replaceFunction(0x101001D0, itemCallbacks.ArmorAcBonus); + // Armor AC Bonus Cap - disregard cap >= 100 (so as to not clog the buffer) replaceFunction(0x10100720, itemCallbacks.ArmorBonusAcBonusCapValue); @@ -1056,6 +1073,10 @@ int GenericCallbacks::ActionInvalidQueryTrue(DispatcherCallbackArgs args){ return 0; } +int GenericCallbacks::NoOp(DispatcherCallbackArgs args) { + return 0; +} + int GenericCallbacks::AddEtherealDamageImmunity(DispatcherCallbackArgs args){ auto dispIo = dispatch.DispIoCheckIoType4(args.dispIO); dispIo->damage.AddEtherealImmunity(); @@ -1153,116 +1174,42 @@ int GenericCallbacks::PowerAttackDamageBonus(DispatcherCallbackArgs args) // get wield type auto weaponUsed = dispIo->attackPacket.GetWeaponUsed(); auto wieldType = inventory.GetWieldType(args.objHndCaller, weaponUsed, true); - auto wieldTypeWeaponModified = inventory.GetWieldType(args.objHndCaller, weaponUsed, false); // the wield type if the weapon is not enlarged along with the critter - auto modifiedByEnlarge = wieldType != wieldTypeWeaponModified; - // check offhand - auto offhandWeapon = inventory.ItemWornAt(args.objHndCaller, EquipSlot::WeaponSecondary); - auto shield = inventory.ItemWornAt(args.objHndCaller, EquipSlot::Shield); - auto regardOffhand = (offhandWeapon || shield && !inventory.IsBuckler(shield)) ?true:false; // is there an offhand item (weapon/non-buckler shield) + switch (critterSys.GetFightingStyle(args.objHndCaller)) + { + case FightingStyle::TwoHanded: + dispIo->damage.bonuses.AddBonusFromFeat(2*powerAttackAmt, 0, 114, FEAT_POWER_ATTACK); + return 0; + case FightingStyle::OneHanded: + case FightingStyle::TwoWeapon: + break; + default: + // shouldn't actually get here, ranged has been taken care of + return 0; + } - // case 1 + // one handed/two weapon cases switch (wieldType) { - case 0: // light weapon - switch (wieldTypeWeaponModified) - { - case 0: - dispIo->damage.bonuses.ZeroBonusSetMeslineNum(305); - return 0; - case 1: // benefitting from enlargement of weapon - if (regardOffhand) - dispIo->damage.bonuses.AddBonusFromFeat(powerAttackAmt, 0, 114, FEAT_POWER_ATTACK); - else - dispIo->damage.bonuses.AddBonusFromFeat(2 * powerAttackAmt, 0, 114, FEAT_POWER_ATTACK); - return 0; - case 2: - if (regardOffhand) - dispIo->damage.bonuses.AddBonusFromFeat(powerAttackAmt, 0, 114, FEAT_POWER_ATTACK); - else - dispIo->damage.bonuses.AddBonusFromFeat(2 * powerAttackAmt, 0, 114, FEAT_POWER_ATTACK); - return 0; - default: - dispIo->damage.bonuses.AddBonusFromFeat(powerAttackAmt, 0, 114, FEAT_POWER_ATTACK); - return 0; - } - case 1: // single handed wield if weapon is unaffected - switch (wieldTypeWeaponModified) - { - case 0: // only in reduce person; going to assume the "beneficial" case that the reduction was made voluntarily and hence you let the weapon stay larger - case 1: - if (regardOffhand) - dispIo->damage.bonuses.AddBonusFromFeat(powerAttackAmt, 0, 114, FEAT_POWER_ATTACK); - else - dispIo->damage.bonuses.AddBonusFromFeat(2 * powerAttackAmt, 0, 114, FEAT_POWER_ATTACK); - return 0; - case 2: - if (regardOffhand) - dispIo->damage.bonuses.AddBonusFromFeat(powerAttackAmt, 0, 114, FEAT_POWER_ATTACK); - else - dispIo->damage.bonuses.AddBonusFromFeat(2 * powerAttackAmt, 0, 114, FEAT_POWER_ATTACK); - return 0; - default: - dispIo->damage.bonuses.AddBonusFromFeat(powerAttackAmt, 0, 114, FEAT_POWER_ATTACK); - return 0; - } - case 2: // two handed wield if weapon is unaffected - switch (wieldTypeWeaponModified) - { - case 0: - case 1: // only in reduce person; going to assume the "beneficial" case that the reduction was made voluntarily and hence you let the weapon stay larger - if (regardOffhand) // has offhand item, so assume the weapon stayed the old size - dispIo->damage.bonuses.AddBonusFromFeat(powerAttackAmt, 0, 114, FEAT_POWER_ATTACK); - else - dispIo->damage.bonuses.AddBonusFromFeat(2 * powerAttackAmt, 0, 114, FEAT_POWER_ATTACK); - return 0; - case 2: - if (regardOffhand) // shouldn't really be possible... maybe if player is cheating - { - dispIo->damage.bonuses.AddBonusFromFeat(powerAttackAmt, 0, 114, FEAT_POWER_ATTACK); - logger->warn("Illegally wielding weapon along withoffhand!"); - } - else - dispIo->damage.bonuses.AddBonusFromFeat(2 * powerAttackAmt, 0, 114, FEAT_POWER_ATTACK); - return 0; - default: - dispIo->damage.bonuses.AddBonusFromFeat(powerAttackAmt, 0, 114, FEAT_POWER_ATTACK); - return 0; - } - case 3: - dispIo->damage.bonuses.AddBonusFromFeat(powerAttackAmt, 0, 114, FEAT_POWER_ATTACK); + // light + case 0: + dispIo->damage.bonuses.ZeroBonusSetMeslineNum(305); return 0; + // 1-handed + case 1: + // unarmed case 4: - default: dispIo->damage.bonuses.AddBonusFromFeat(powerAttackAmt, 0, 114, FEAT_POWER_ATTACK); return 0; + // must be double weapon; should be impossible for OneHanded to be 2 or 3 + default: + if (dispIo->attackPacket.flags & D20CAF_SECONDARY_WEAPON) + dispIo->damage.bonuses.ZeroBonusSetMeslineNum(305); + else + dispIo->damage.bonuses.AddBonusFromFeat(powerAttackAmt, 0, 114, FEAT_POWER_ATTACK); } - - //if (modifiedByEnlarge){ - // // if using an offhand and also wielding two handed - // if (regardOffhand && wieldTypeWeaponModified == 2) - // wieldType = wieldTypeWeaponModified; - // else if (!wieldType && wieldTypeWeaponModified) - // wieldType = wieldTypeWeaponModified; - //} - - //if(!wieldType && !wieldTypeWeaponModified){ - // dispIo->damage.bonuses.ZeroBonusSetMeslineNum(305); - // return 0; - //} - - //auto bonAmt = 2*powerAttackAmt; - //if (wieldType == 4) - // bonAmt = powerAttackAmt; - //else if (wieldType != 2){ - // if ( regardOffhand) - // bonAmt = powerAttackAmt; - //} - // - //dispIo->damage.bonuses.AddBonusFromFeat(bonAmt, 0, 114, FEAT_POWER_ATTACK); - // - //return 0; + return 0; } int GenericCallbacks::PowerAttackToHitPenalty(DispatcherCallbackArgs args) @@ -1746,6 +1693,144 @@ int DisarmedRetrieveQuery(DispatcherCallbackArgs args) return 0; }; +int TwoWeaponQuery(DispatcherCallbackArgs args) +{ + GET_DISPIO(dispIOTypeQuery, DispIoD20Query); + auto isCurrentlyOn = args.GetCondArg(0); + + // offset by 1 so that we can tell if the critter has the condition + // at all, and default to the old behavior if not. + dispIo->return_val = isCurrentlyOn+1; + + return 0; +} + +int LeftPrimaryQuery(DispatcherCallbackArgs args) +{ + GET_DISPIO(dispIOTypeQuery, DispIoD20Query); + auto isCurrentlyOn = args.GetCondArg(1); + + dispIo->return_val = isCurrentlyOn; + + return 0; +} + +int TwoWeaponRadialMenu(DispatcherCallbackArgs args) +{ + if (!critterSys.CanTwoWeaponFight(args.objHndCaller)) + return 0; + + // hide radial if an attack has already been made + if (args.GetCondArg(2)) return 0; + + RadialMenuEntryToggle radEntry(5125, args.GetCondArgPtr(0), "TAG_RADIAL_MENU_TWO_WEAPON_FIGHTING"); + radEntry.AddChildToStandard(args.objHndCaller, RadialMenuStandardNode::Options); + return 0; +} + +int DefaultSetTWF(DispatcherCallbackArgs args) +{ + auto dispIo = dispatch.DispIoCheckIoType6(args.dispIO); + if (!dispIo) return 0; + + objHndl uitem = objHndl::FromUpperAndLower(dispIo->data2, dispIo->data1); + if (!uitem) return 0; + + objHndl right = inventory.ItemWornAt(args.objHndCaller, EquipSlot::WeaponPrimary); + objHndl left = inventory.ItemWornAt(args.objHndCaller, EquipSlot::WeaponSecondary); + objHndl shield = inventory.ItemWornAt(args.objHndCaller, EquipSlot::Shield); + + if (uitem == left) { // off hand equipped, default two TWF on + args.SetCondArg(0, 1); + } else if (uitem == shield) { // shield equipped, default to TWF off + args.SetCondArg(0, 0); + args.SetCondArg(1, 0); + } else if (uitem == right) { // right hand equipped + if (!left) { + args.SetCondArg(0, 0); + args.SetCondArg(1, 0); + } + } + + return 0; +} + +int LeftPrimaryRadialMenu(DispatcherCallbackArgs args) +{ + if (!critterSys.CanTwoWeaponFight(args.objHndCaller)) + return 0; + + // hide radial if an attack has already been made + if (args.GetCondArg(2)) return 0; + + RadialMenuEntryToggle radEntry(5126, args.GetCondArgPtr(1), "TAG_RADIAL_MENU_LEFT_PRIMARY"); + radEntry.AddChildToStandard(args.objHndCaller, RadialMenuStandardNode::Options); + return 0; +} + +// Functions for armor that can be used as a weapon via a condition +int ArmorWeaponDice(DispatcherCallbackArgs args) +{ + DispIoAttackDice * dispIo = dispatch.DispIoCheckIoType20(args.dispIO); + + auto invIdx = args.GetCondArg(2); + auto armor = inventory.GetItemAtInvIdx(args.objHndCaller, invIdx); + + if (dispIo->weapon == armor) { + dispIo->dicePacked = conds.CondNodeGetArg(args.subDispNode->condNode, 1); + dispIo->attackDamageType = (DamageType)conds.CondNodeGetArg(args.subDispNode->condNode, 0); + } + + return 0; +} + +int ArmorCritRange(DispatcherCallbackArgs args) +{ + DispIoAttackBonus * dispIo = dispatch.DispIoCheckIoType5(args.dispIO); + + auto invIdx = args.GetCondArg(2); + auto armor = inventory.GetItemAtInvIdx(args.objHndCaller, invIdx); + + if (dispIo->attackPacket.weaponUsed == armor) { + auto mult = conds.CondNodeGetArg(args.subDispNode->condNode, 3); + dispIo->bonlist.AddBonus(mult, 0, 110); + } + + return 0; +} + +int ArmorCritMultiplier(DispatcherCallbackArgs args) +{ + DispIoAttackBonus * dispIo = dispatch.DispIoCheckIoType5(args.dispIO); + + auto invIdx = args.GetCondArg(2); + auto armor = inventory.GetItemAtInvIdx(args.objHndCaller, invIdx); + + if (dispIo->attackPacket.weaponUsed == armor) { + auto mult = conds.CondNodeGetArg(args.subDispNode->condNode, 4); + dispIo->bonlist.AddBonus(mult, 0, 110); + } + + return 0; +} + +int ShieldBashProficiencyPenalty(DispatcherCallbackArgs args) +{ + auto dispIo = dispatch.DispIoCheckIoType5(args.dispIO); + auto invIdx = args.GetCondArg(2); + auto attacker = dispIo->attackPacket.attacker; + auto shield = inventory.GetItemAtInvIdx(attacker, invIdx); + + if (dispIo->attackPacket.weaponUsed != shield) return 0; + + // Future option: create an individual proficiency. + // Probably a waste of time, since no one would take it. + if (!feats.HasFeatCountByClass(attacker, FEAT_MARTIAL_WEAPON_PROFICIENCY_ALL)) + dispIo->bonlist.AddBonus(-4, 37, 138); + + return 0; +} + int __cdecl CondNodeSetArgFromSubDispDef(DispatcherCallbackArgs args) { // sets arg[data1] from data2 @@ -1845,11 +1930,9 @@ int __cdecl GlobalToHitBonus(DispatcherCallbackArgs args) else bonusSys.bonusAddToBonusList(&dispIo->bonlist, -6, 27, 122); // penalty for dualwield on primary attack - auto offhand = inventory.ItemWornAt(args.objHndCaller, 4); - if (offhand) + if (critterSys.OffhandIsLight(args.objHndCaller)) { - if (inventory.GetWieldType(dispIo->attackPacket.attacker, offhand) == 0) - bonusSys.bonusAddToBonusList(&dispIo->bonlist, 2, 0, 167); // Light Off-hand Weapon + bonusSys.bonusAddToBonusList(&dispIo->bonlist, 2, 0, 167); // Light Off-hand Weapon } } } @@ -2454,11 +2537,9 @@ int __cdecl GlobalOnDamage(DispatcherCallbackArgs args) strMod /= 2; } - } else if (d20Sys.d20QueryWithData(args.objHndCaller, DK_QUE_WieldedTwoHanded, (int)dispIo, 0) - && strMod > 0 - && inventory.GetWieldType(args.objHndCaller, weapon, true) ) + } else if (critterSys.GetFightingStyle(args.objHndCaller) == FightingStyle::TwoHanded) { - strMod += strMod / 2; + strMod += std::max(0, strMod) / 2; } if (attackCode > ATTACK_CODE_NATURAL_ATTACK && strMod > 0 && critterSys.GetDamageIdx(args.objHndCaller, attackCode - (ATTACK_CODE_NATURAL_ATTACK + 1)) > 0) { @@ -3099,6 +3180,30 @@ void ConditionSystem::RegisterNewConditions() condAttrEnhBonus.AddHook(enum_disp_type::dispTypeStatBaseGet, D20DispatcherKey::DK_NONE, itemCallbacks.AttributeBaseBonus); } + { + static CondStructNew condShieldBonus; + condShieldBonus.ExtendExisting("Shield Bonus"); + condShieldBonus.AddHook(dispTypeBucklerAcPenalty, DK_NONE, itemCallbacks.ShieldAcPenalty); + // reset shield bash penalty on begin round + condShieldBonus.AddHook(dispTypeBeginRound, DK_NONE, CondNodeSetArgFromSubDispDef, 1, 0); + + // replace Q_Armor_Get_AC_Bonus callbacks to fix stacking behavior + condShieldBonus.subDispDefs[0].dispCallback = itemCallbacks.BaseAcQuery; + static CondStructNew condShieldEnhBonus; + condShieldEnhBonus.ExtendExisting("Shield Enhancement Bonus"); + condShieldEnhBonus.subDispDefs[0].dispCallback = itemCallbacks.EnhAcQuery; + // replace OnGetAC handler for new calculation methodology + condShieldEnhBonus.subDispDefs[1].dispCallback = genericCallbacks.NoOp; + + // as above, but for armor + static CondStructNew condArmorBonus; + condArmorBonus.ExtendExisting("Armor Bonus"); + condArmorBonus.subDispDefs[0].dispCallback = itemCallbacks.BaseAcQuery; + static CondStructNew condArmorEnhBonus; + condArmorEnhBonus.ExtendExisting("Armor Enhancement Bonus"); + condArmorEnhBonus.subDispDefs[0].dispCallback = itemCallbacks.EnhAcQuery; + condArmorEnhBonus.subDispDefs[1].dispCallback = genericCallbacks.NoOp; + } #pragma endregion @@ -3106,6 +3211,23 @@ void ConditionSystem::RegisterNewConditions() preferOneHanded.AddHook(dispTypeD20Query, DK_QUE_Is_Preferring_One_Handed_Wield, genericCallbacks.PreferOneHandedWieldQuery); preferOneHanded.AddHook(dispTypeRadialMenuEntry, DK_NONE, genericCallbacks.PreferOneHandedWieldRadialMenu); + static CondStructNew twoWeapToggles("Two Weapon Toggles", 6, true); + twoWeapToggles.AddHook(dispTypeD20Query, DK_QUE_Is_Two_Weapon_Fighting, TwoWeaponQuery); + twoWeapToggles.AddHook(dispTypeD20Query, DK_QUE_Left_Is_Primary, LeftPrimaryQuery); + twoWeapToggles.AddHook(dispTypeRadialMenuEntry, DK_NONE, TwoWeaponRadialMenu); + twoWeapToggles.AddHook(dispTypeRadialMenuEntry, DK_NONE, LeftPrimaryRadialMenu); + twoWeapToggles.AddHook(dispTypeBeginRound, DK_NONE, CondNodeSetArgFromSubDispDef, 2, 0); + twoWeapToggles.AddHook(dispTypeD20Signal, DK_SIG_Attack_Made, CondNodeSetArgFromSubDispDef, 2, 1); + twoWeapToggles.AddHook(dispTypeD20Signal, DK_SIG_Inventory_Update, DefaultSetTWF); + + // damage type, damage, inventory index, crit range, crit mult + static CondStructNew shieldBash("Shield Bash", 5); + shieldBash.AddHook(dispTypeD20Query, DK_QUE_Can_Shield_Bash, genericCallbacks.QuerySetReturnVal1); + shieldBash.AddHook(dispTypeGetAttackDice, DK_NONE, ArmorWeaponDice); + shieldBash.AddHook(dispTypeGetCriticalHitExtraDice, DK_NONE, ArmorCritMultiplier); + shieldBash.AddHook(dispTypeGetCriticalHitRange, DK_NONE, ArmorCritRange); + shieldBash.AddHook(dispTypeToHitBonus2, DK_NONE, ShieldBashProficiencyPenalty); + { //mCondCraftWandLevelSet = static CondStructNew craftWandSetLev("Craft Wand Level Set", 2); @@ -3356,22 +3478,17 @@ int __cdecl AoODisableQueryAoOPossible(DispatcherCallbackArgs args) int __cdecl GreaterTwoWeaponFighting(DispatcherCallbackArgs args) { - DispIoD20ActionTurnBased *dispIo = dispatch.DispIoCheckIoType12((DispIoD20ActionTurnBased*)args.dispIO); - objHndl mainWeapon = inventory.ItemWornAt(args.objHndCaller, 3); - objHndl offhand = inventory.ItemWornAt(args.objHndCaller, 4); - - if (mainWeapon != offhand) + DispIoD20ActionTurnBased *dispIo = dispatch.DispIoCheckIoType12(args.dispIO); + + switch (critterSys.GetFightingStyle(args.objHndCaller)) { - if (mainWeapon) - { - if (offhand) - { - int weapFlags = objects.getInt32(mainWeapon, obj_f_weapon_flags); - if (!(weapFlags & (4<<8)) && objects.getInt32(offhand, obj_f_type) != obj_t_armor) - ++dispIo->returnVal; - } - } + case FightingStyle::TwoWeapon: + case FightingStyle::TwoWeaponRanged: + ++dispIo->returnVal; + default: + break; } + return 0; } @@ -5667,6 +5784,128 @@ int __cdecl ItemCallbacks::BucklerAcPenalty(DispatcherCallbackArgs args) return 0; } +int __cdecl ItemCallbacks::ShieldAcPenalty(DispatcherCallbackArgs args) +{ + DispIoAttackBonus * dispIo = dispatch.DispIoCheckIoType5(args.dispIO); + objHndl attacker = dispIo->attackPacket.attacker; + + if (!attacker) return 0; + + if (feats.HasFeatCount(attacker, FEAT_IMPROVED_SHIELD_BASH)) + return 0; + + auto invIdx = args.GetCondArg(2); + objHndl source = inventory.GetItemAtInvIdx(attacker, invIdx); + + if (dispIo->attackPacket.GetWeaponUsed() == source) + // shield bashing, disable AC bonus + args.SetCondArg(1, 1); + + return 0; +} + +int __cdecl ItemCallbacks::BucklerAcBonus(DispatcherCallbackArgs args) +{ + auto dispIo = dispatch.DispIoCheckIoType5(args.dispIO); + if (!dispIo) return 0; + + // touch attacks bypass shields; test first to avoid bonus list spam + if (dispIo->attackPacket.flags & D20CAF_TOUCH_ATTACK) + return 0; + + if (args.GetCondArg(1)) { // bonus disabled due to second hand attack + dispIo->bonlist.ZeroBonusSetMeslineNum(326); + return 0; + } + + auto invIdx = args.GetCondArg(2); + auto source = inventory.GetItemAtInvIdx(args.objHndCaller, invIdx); + auto name = description.getDisplayName(source); + auto packedBonus = dispatch.DispatchItemQuery(source, DK_QUE_Armor_Get_AC_Bonus); + auto base = packedBonus & 0xff; + auto enh = (packedBonus & 0xff00) >> 8; + + dispIo->bonlist.AddBonusWithDesc(base + enh, 29, 125, name); + + return 0; +} + +int __cdecl ItemCallbacks::ShieldAcBonus(DispatcherCallbackArgs args) +{ + auto dispIo = dispatch.DispIoCheckIoType5(args.dispIO); + if (!dispIo) return 0; + + // touch attacks bypass shields; test first to avoid bonus list spam + if (dispIo->attackPacket.flags & D20CAF_TOUCH_ATTACK) + return 0; + + if (args.GetCondArg(1)) { // bonus disabled due to shield bash + dispIo->bonlist.ZeroBonusSetMeslineNum(351); + return 0; + } + + auto invIdx = args.GetCondArg(2); + auto source = inventory.GetItemAtInvIdx(args.objHndCaller, invIdx); + auto name = description.getDisplayName(source); + auto packedBonus = dispatch.DispatchItemQuery(source, DK_QUE_Armor_Get_AC_Bonus); + auto base = packedBonus & 0xff; + auto enh = (packedBonus & 0xff00) >> 8; + + dispIo->bonlist.AddBonusWithDesc(base + enh, 29, 125, name); + + return 0; +} + +int __cdecl ItemCallbacks::ArmorAcBonus(DispatcherCallbackArgs args) +{ + auto dispIo = dispatch.DispIoCheckIoType5(args.dispIO); + if (!dispIo) return 0; + + // touch attacks bypass armor; test first to avoid bonus list spam + if (dispIo->attackPacket.flags & D20CAF_TOUCH_ATTACK) + return 0; + + auto invIdx = args.GetCondArg(2); + auto source = inventory.GetItemAtInvIdx(args.objHndCaller, invIdx); + auto name = description.getDisplayName(source); + auto packedBonus = dispatch.DispatchItemQuery(source, DK_QUE_Armor_Get_AC_Bonus); + auto base = packedBonus & 0xff; + auto enh = (packedBonus & 0xff00) >> 8; + + dispIo->bonlist.AddBonusWithDesc(base + enh, 28, 124, name); + + return 0; +} + +int __cdecl ItemCallbacks::BaseAcQuery(DispatcherCallbackArgs args) +{ + auto dispIo = dispatch.DispIoCheckIoType7(args.dispIO); + if (!dispIo) return 0; + + auto base = dispIo->return_val & 0xff; + auto rest = dispIo->return_val & 0xffffff00; + + base = std::max(base, args.GetCondArg(0)); + + dispIo->return_val = rest | base; + + return 0; +} + +int __cdecl ItemCallbacks::EnhAcQuery(DispatcherCallbackArgs args) +{ + auto dispIo = dispatch.DispIoCheckIoType7(args.dispIO); + if (!dispIo) return 0; + + auto enh = dispIo->return_val & 0xff00; + auto rest = dispIo->return_val & 0xffff00ff; + + enh = std::max(enh, args.GetCondArg(0) << 8); + + dispIo->return_val = rest | enh; + + return 0; +} int ItemCallbacks::WeaponMerciful(DispatcherCallbackArgs args){ GET_DISPIO(dispIOTypeDamage, DispIoDamage); diff --git a/TemplePlus/critter.cpp b/TemplePlus/critter.cpp index 490b84ba6..c8622a763 100644 --- a/TemplePlus/critter.cpp +++ b/TemplePlus/critter.cpp @@ -2082,6 +2082,147 @@ int LegacyCritterSystem::GetRacialSavingThrowBonus(objHndl handle, SavingThrowTy return 0; } +FightingStyle operator|(FightingStyle l, FightingStyle r) +{ + return (FightingStyle)((uint32_t)l | (uint32_t)r); +} + +FightingStyle operator&(FightingStyle l, FightingStyle r) +{ + return (FightingStyle)((uint32_t)l & (uint32_t)r); +} + +bool LegacyCritterSystem::CanTwoWeaponFight(objHndl critter) +{ + auto weapr = inventory.ItemWornAt(critter, EquipSlot::WeaponPrimary); + auto weapl = inventory.ItemWornAt(critter, EquipSlot::WeaponSecondary); + + bool result = !!weapl; + result |= d20Sys.d20Query(critter, DK_QUE_Can_Shield_Bash); + result |= inventory.IsDoubleWeapon(weapr); + + return result; +} + +objHndl LegacyCritterSystem::GetRightWield(objHndl critter) +{ + return inventory.ItemWornAt(critter, EquipSlot::WeaponPrimary); +} + +objHndl LegacyCritterSystem::GetLeftWield(objHndl critter) +{ + auto weapl = inventory.ItemWornAt(critter, EquipSlot::WeaponSecondary); + + if (!weapl && d20Sys.d20Query(critter, DK_QUE_Can_Shield_Bash)) { + weapl = inventory.ItemWornAt(critter, EquipSlot::Shield); + } else { + auto weapr = inventory.ItemWornAt(critter, EquipSlot::WeaponPrimary); + if (inventory.IsDoubleWeapon(weapr)) + weapl = weapr; + } + + return weapl; +} + +objHndl LegacyCritterSystem::GetPrimaryWield(objHndl critter) +{ + auto weapr = GetRightWield(critter); + auto weapl = GetLeftWield(critter); + + if (weapl && d20Sys.d20Query(critter, DK_QUE_Left_Is_Primary)) + return weapl; + else + return weapr; +} + +objHndl LegacyCritterSystem::GetSecondaryWield(objHndl critter) +{ + auto weapr = GetRightWield(critter); + auto weapl = GetLeftWield(critter); + + if (weapl && d20Sys.d20Query(critter, DK_QUE_Left_Is_Primary)) + return weapr; + else + return weapl; +} + +FightingStyle LegacyCritterSystem::GetFightingStyle(objHndl critter) +{ + if (!critter || !objSystem->IsValidHandle(critter)) + return FightingStyle::Unknown; + + auto weapp = GetPrimaryWield(critter); + auto weaps = GetSecondaryWield(critter); + bool twfEnabled = false; + + // query behavior: 0 - no toggle, 1 - toggle off, 2 - toggle on + switch (d20Sys.d20Query(critter, DK_QUE_Is_Two_Weapon_Fighting)) + { + case 0: + // if the toggle is missing we need to preserve the old behavior: + // twf is enabled if you are wielding two different _weapons_. + if (!weapp || !weaps) break; + if (weapp == weaps) break; + + twfEnabled = objects.GetType(weaps) == obj_t_weapon; + break; + case 2: + twfEnabled = true; + break; + default: + break; + } + + FightingStyle style = FightingStyle::OneHanded; + + if (weapp) { + auto rangep = OWF_RANGED_WEAPON & objects.getInt32(weapp, obj_f_weapon_flags); + if (weaps && twfEnabled) { + // Note: I'm not sure there's actually any way to dual wield + // with a ranged weapon. I don't think throwing daggers count, + // and they might be the only candidate that doesn't occupy both + // hands. + auto ranges = OWF_RANGED_WEAPON & objects.getInt32(weaps, obj_f_weapon_flags); + if (rangep == ranges) { + style = FightingStyle::TwoWeapon; + } else { + style = FightingStyle::OneHanded; + } + } else if (inventory.IsWieldedTwoHanded(weapp, critter)) { + style = FightingStyle::TwoHanded; + } else { + style = FightingStyle::OneHanded; + } + + // Ranged status follows primary weapon; either the secondary + // matches, or the above logic will forbid two weapon fighting. + if (rangep) + style = style | FightingStyle::Ranged; + } + // if no primary weapon, we're in one handed unarmed mode + + return style; +} + +bool LegacyCritterSystem::OffhandIsLight(objHndl critter) +{ + if (!critter || !objSystem->IsValidHandle(critter)) + return false; + + auto weapp = GetPrimaryWield(critter); + auto weaps = GetSecondaryWield(critter); + + if (weaps && weapp != weaps) { + // if we're actually wielding two weapons, return whether the + // secondary is light + return inventory.GetWieldType(critter, weaps, true) == 0; + } + + // Otherwise, we're wielding a double weapon, or the offhand is + // null, either of which count as light. + return true; +} + int LegacyCritterSystem::SkillBaseGet(objHndl handle, SkillEnum skill){ if (!handle) return 0; diff --git a/TemplePlus/critter.h b/TemplePlus/critter.h index 68317adf0..2a20efbcf 100644 --- a/TemplePlus/critter.h +++ b/TemplePlus/critter.h @@ -91,6 +91,21 @@ enum WaypointFlag : uint32_t { Animate = 4 }; +enum class FightingStyle : uint32_t { + Unknown = 0, + OneHanded = 1, + TwoHanded = 2, + TwoWeapon = 3, + Mask = 0xf, + Ranged = 0x10, + OneHandedRanged = 0x11, + TwoHandedRanged = 0x12, + TwoWeaponRanged = 0x13 +}; + +FightingStyle operator|(FightingStyle l, FightingStyle r); +FightingStyle operator&(FightingStyle l, FightingStyle r); + #pragma pack(push, 1) struct Waypoint { uint32_t flags = 0; @@ -408,6 +423,13 @@ struct LegacyCritterSystem : temple::AddressTable int GetBaseAttackBonus(const objHndl& handle, Stat classBeingLeveld = Stat::stat_strength); int GetArmorClass(objHndl obj, DispIoAttackBonus *dispIo = nullptr); int GetRacialSavingThrowBonus(objHndl handle, SavingThrowType saveType); + objHndl GetRightWield(objHndl hndl); + objHndl GetLeftWield(objHndl hndl); + objHndl GetPrimaryWield(objHndl hndl); + objHndl GetSecondaryWield(objHndl hndl); + bool CanTwoWeaponFight(objHndl hndl); + FightingStyle GetFightingStyle(objHndl hndl); + bool OffhandIsLight(objHndl hndl); #pragma endregion #pragma region Spellcasting diff --git a/TemplePlus/d20.cpp b/TemplePlus/d20.cpp index 83590eb5c..a1d2b7985 100644 --- a/TemplePlus/d20.cpp +++ b/TemplePlus/d20.cpp @@ -1247,16 +1247,23 @@ objHndl LegacyD20System::GetAttackWeapon(objHndl obj, int attackCode, D20CAF fla return objHndl::null; } - if (flags & D20CAF_SECONDARY_WEAPON) - return inventory.ItemWornAt(obj, EquipSlot::WeaponSecondary); + auto weapr = inventory.ItemWornAt(obj, EquipSlot::WeaponPrimary); + auto weapl = inventory.ItemWornAt(obj, EquipSlot::WeaponSecondary); - if (UsingSecondaryWeapon(obj, attackCode)) - return inventory.ItemWornAt(obj, EquipSlot::WeaponSecondary); + if (!weapl && d20Sys.d20Query(obj, DK_QUE_Can_Shield_Bash)) + weapl = inventory.ItemWornAt(obj, EquipSlot::Shield); + + if (!weapl && inventory.IsDoubleWeapon(weapr)) + weapl = weapr; + + if (flags & D20CAF_SECONDARY_WEAPON) return weapl; + + if (UsingSecondaryWeapon(obj, attackCode)) return weapl; if (attackCode > ATTACK_CODE_NATURAL_ATTACK) return objHndl::null; - return inventory.ItemWornAt(obj, EquipSlot::WeaponPrimary); + return weapr; } ActionErrorCode D20ActionCallbacks::PerformStandardAttack(D20Actn* d20a) @@ -1277,6 +1284,9 @@ ActionErrorCode D20ActionCallbacks::PerformStandardAttack(D20Actn* d20a) hitAnimIdx = (d20a->data1 - (ATTACK_CODE_NATURAL_ATTACK + 1)) % 3; } + if (d20Sys.d20Query(d20a->d20APerformer, DK_QUE_Left_Is_Primary)) + useSecondaryAnim = !useSecondaryAnim; + combatSys.ToHitProcessing(*d20a); int caflags = d20a->d20Caf; diff --git a/TemplePlus/d20_status.cpp b/TemplePlus/d20_status.cpp index bcfdf1fe6..67766983f 100644 --- a/TemplePlus/d20_status.cpp +++ b/TemplePlus/d20_status.cpp @@ -264,6 +264,7 @@ void D20StatusSystem::initFeats(objHndl objHnd) _ConditionAddToAttribs_NumArgs0(dispatcher, (CondStruct*)&conds.mCondDisarm); _ConditionAddToAttribs_NumArgs0(dispatcher, (CondStruct*)conds.mCondAidAnother); _ConditionAddToAttribs_NumArgs0(dispatcher, conds.GetByName("Prefer One Handed Wield")); + _ConditionAddToAttribs_NumArgs0(dispatcher, conds.GetByName("Two Weapon Toggles")); //addToDispatcher("Trip Attack Of Opportunity"); // decided to incorporate this in Improved Trip to prevent AoOs on AoOs } diff --git a/TemplePlus/dispatcher.cpp b/TemplePlus/dispatcher.cpp index 50e21858f..61aa83fa6 100644 --- a/TemplePlus/dispatcher.cpp +++ b/TemplePlus/dispatcher.cpp @@ -524,6 +524,13 @@ DispIoObjEvent* DispatcherSystem::DispIoCheckIoType17(DispIO* dispIo) return static_cast(dispIo); } +DispIoAttackDice* DispatcherSystem::DispIoCheckIoType20(DispIO* dispIo) +{ + if (dispIo->dispIOType != dispIOType20) + return nullptr; + return static_cast(dispIo); +} + DispIoImmunity* DispatcherSystem::DispIoCheckIoType23(DispIoImmunity* dispIo) { if (dispIo->dispIOType != dispIOTypeImmunityHandler) return nullptr; diff --git a/TemplePlus/dispatcher.h b/TemplePlus/dispatcher.h index e3fd8c5b6..c891e5789 100644 --- a/TemplePlus/dispatcher.h +++ b/TemplePlus/dispatcher.h @@ -101,6 +101,7 @@ struct DispatcherSystem : temple::AddressTable DispIoMoveSpeed * DispIOCheckIoType13(DispIoMoveSpeed* dispIo); DispIoMoveSpeed * DispIOCheckIoType13(DispIO* dispIo); static DispIoObjEvent* DispIoCheckIoType17(DispIO* dispIo); + DispIoAttackDice* DispIoCheckIoType20(DispIO* dispIo); DispIoImmunity* DispIoCheckIoType23(DispIoImmunity* dispIo); DispIoImmunity* DispIoCheckIoType23(DispIO* dispIo); DispIoEffectTooltip* DispIoCheckIoType24(DispIoEffectTooltip* dispIo); diff --git a/TemplePlus/feat.cpp b/TemplePlus/feat.cpp index 935b7818d..bf5e7277b 100644 --- a/TemplePlus/feat.cpp +++ b/TemplePlus/feat.cpp @@ -1673,18 +1673,30 @@ uint32_t _WeaponFeatCheck(objHndl objHnd, feat_enums * featArray, uint32_t featA } } + auto mprof = FEAT_MARTIAL_WEAPON_PROFICIENCY_ALL; if (wpnType == wt_orc_double_axe) { - if (critterSys.GetRace(objHnd) == race_halforc) - { + auto martial = feats.HasFeatCountByClass(objHnd, mprof, classBeingLeveled, 0); + + if (config.stricterRulesEnforcement) { + // orcs treat this as a martial weapon + if (critterSys.GetSubcategoryFlags(objHnd) & mc_subtype_orc) { + return martial; + } + } else if (critterSys.GetRace(objHnd) == race_halforc) { + // half-orcs don't actually get proficiency in this for some reason, + // so only give it if strict rules are off return 1; } return 0; } else if (wpnType == wt_gnome_hooked_hammer) { + // Gnomes treat hooked hammers as martial, not automatic proficiency if (critterSys.GetRace(objHnd) == race_gnome) { + if (config.stricterRulesEnforcement) + return feats.HasFeatCountByClass(objHnd, mprof, classBeingLeveled, 0); return 1; } return 0; @@ -1693,7 +1705,10 @@ uint32_t _WeaponFeatCheck(objHndl objHnd, feat_enums * featArray, uint32_t featA { if (critterSys.GetRace(objHnd) == race_dwarf) { - return 1; + // Dwarves treat 1 handing waraxes as martial, not automatic proficiency + // no need for martial check because waraxes are always martial for + // proficiency, exotic is only for one handing. + return !config.stricterRulesEnforcement; } return 0; } else if (wpnType == wt_grenade) diff --git a/TemplePlus/inventory.cpp b/TemplePlus/inventory.cpp index da44b506d..8b115b4fe 100644 --- a/TemplePlus/inventory.cpp +++ b/TemplePlus/inventory.cpp @@ -203,6 +203,11 @@ static class InventoryHooks : public TempleFix { replaceFunction(0x100698E0, [](objHndl item, objHndl parent, objHndl appraiser, int skillIdx) { return inventory.GetAppraisedTransactionSum(item, parent, appraiser, (SkillEnum)skillIdx); }); + + // GetWieldType + replaceFunction(0x10066580, [](objHndl wielder, objHndl item) { + return inventory.GetWieldType(wielder, item, false); + }); }; @@ -1525,9 +1530,6 @@ int32_t InventorySystem::GetCoinWorth(int32_t coinType){ // 4: null weapon int InventorySystem::GetWieldType(objHndl wielder, objHndl item, bool regardEnlargement) const { - if (!regardEnlargement) - return _GetWieldType(wielder, item); - if (!item) return 4; @@ -1537,11 +1539,19 @@ int InventorySystem::GetWieldType(objHndl wielder, objHndl item, bool regardEnla if (itemType == obj_t_armor){ auto armorFlags = itemObj->GetInt32(obj_f_armor_flags); auto armorType = inventory.GetArmorType(armorFlags); - if (armorType == ArmorType::ARMOR_TYPE_SHIELD || armorType == ArmorType::ARMOR_TYPE_NONE) - return ( itemObj->GetInt32(obj_f_item_wear_flags) & OIF_WEAR::OIF_WEAR_BUCKLER ) != OIF_WEAR::OIF_WEAR_BUCKLER; + if (armorType == ArmorType::ARMOR_TYPE_SHIELD) { + // if we can shield bash, fall through to use size based + if (!d20Sys.d20Query(wielder, DK_QUE_Can_Shield_Bash)) { + if (IsBuckler(item)) return 0; + return 3; + } + } else if (armorType == ArmorType::ARMOR_TYPE_NONE) { + // uncertain what this case is, but it was in the original + return !IsBuckler(item); + } } - + auto wielderSize = critterSys.GetSize(wielder); auto wielderSizeBase = regardEnlargement ? gameSystems->GetObj().GetObject(wielder)->GetInt32(obj_f_size) : wielderSize; auto itemSize = itemObj->GetInt32(obj_f_size); @@ -1560,7 +1570,7 @@ int InventorySystem::GetWieldType(objHndl wielder, objHndl item, bool regardEnla { wieldType = 2; } - else if (itemSize == wielderSize + 1 ) + else if (itemSize == wielderSize + 1) { wieldType = 2; } @@ -1591,7 +1601,14 @@ int InventorySystem::GetWieldType(objHndl wielder, objHndl item, bool regardEnla return wieldType; } - if (feats.HasFeatCountByClass(wielder, FEAT_EXOTIC_WEAPON_PROFICIENCY_DWARVEN_WARAXE) || objects.StatLevelGet(wielder, stat_race) == race_dwarf) + auto hasExotic = feats.HasFeatCountByClass(wielder, FEAT_EXOTIC_WEAPON_PROFICIENCY_DWARVEN_WARAXE); + auto isDwarf = objects.StatLevelGet(wielder, stat_race) == race_dwarf; + // TODO: make a specific martial feat that no one will ever take? + auto hasMartial = feats.HasFeatCountByClass(wielder, FEAT_MARTIAL_WEAPON_PROFICIENCY_ALL); + // only require martial proficiency if strict enforcement is on + hasMartial |= !config.stricterRulesEnforcement; + + if (hasExotic || (isDwarf && hasMartial)) { return wieldType; } @@ -1685,18 +1702,36 @@ bool InventorySystem::IsWieldedTwoHanded(objHndl weapon, objHndl wielder, bool s gfx::WeaponAnimType InventorySystem::GetWeaponAnimId(objHndl item, objHndl wielder, bool special){ + auto weap = objSystem->GetObject(item); auto wieldType = GetWieldType(wielder, item, false); - WeaponTypes wtype = (WeaponTypes)objects.getInt32(item, obj_f_weapon_type); + WeaponTypes wtype = (WeaponTypes)weap->GetInt32(obj_f_weapon_type); if (wieldType == 4) return gfx::WeaponAnimType::Unarmed; if (IsWieldedTwoHanded(item, wielder, special)) { switch (wtype) { + case WeaponTypes::wt_short_sword: + // cutlass is a slashing shortsword + if (weap->GetInt32(obj_f_weapon_attacktype) == (int32_t)DamageType::Slashing) + return gfx::WeaponAnimType::Greatsword; + else + return gfx::WeaponAnimType::Spear; // piercing weapons case WeaponTypes::wt_dagger: - case WeaponTypes::wt_short_sword: case WeaponTypes::wt_rapier: return gfx::WeaponAnimType::Spear; + case WeaponTypes::wt_orc_double_axe: + case WeaponTypes::wt_dwarven_urgrosh: + if (d20Sys.d20Query(wielder, DK_QUE_Is_Two_Weapon_Fighting) > 1) + return gfx::WeaponAnimType::Staff; + else + return gfx::WeaponAnimType::Greataxe; + case WeaponTypes::wt_quarterstaff: + case WeaponTypes::wt_gnome_hooked_hammer: + if (d20Sys.d20Query(wielder, DK_QUE_Is_Two_Weapon_Fighting) > 1) + return gfx::WeaponAnimType::Staff; + else + return gfx::WeaponAnimType::Greathammer; default: // original two handed anim array return (gfx::WeaponAnimType)temple::GetRef(0x102BE668)[wtype]; @@ -1706,6 +1741,9 @@ gfx::WeaponAnimType InventorySystem::GetWeaponAnimId(objHndl item, objHndl wield { // piercing weapons case WeaponTypes::wt_short_sword: // was sword + // cutlass is a slashing shortsword + if (weap->GetInt32(obj_f_weapon_attacktype) == (int32_t)DamageType::Slashing) + return gfx::WeaponAnimType::Sword; case WeaponTypes::wt_rapier: // was sword return gfx::WeaponAnimType::Dagger; // slashing weapons @@ -1965,6 +2003,25 @@ bool InventorySystem::IsBuckler(objHndl shield) return (gameSystems->GetObj().GetObject(shield)->GetInt32(obj_f_item_wear_flags) & OIF_WEAR::OIF_WEAR_BUCKLER) ? true: false; } +bool InventorySystem::IsDoubleWeapon(objHndl weapon) +{ + if (!weapon) return false; + + switch (objects.GetWeaponType(weapon)) + { + case wt_quarterstaff: + case wt_gnome_hooked_hammer: + case wt_orc_double_axe: + case wt_dire_flail: + case wt_two_bladed_sword: + case wt_dwarven_urgrosh: + return true; + + default: + return false; + } +} + void InventorySystem::ItemRemove(objHndl item) { auto parent = GetParent(item); diff --git a/TemplePlus/inventory.h b/TemplePlus/inventory.h index a7b7d74f5..f2f0af44d 100644 --- a/TemplePlus/inventory.h +++ b/TemplePlus/inventory.h @@ -179,6 +179,7 @@ struct InventorySystem : temple::AddressTable static bool IsIdentified(objHndl itemHandle); void ItemSpellChargeConsume(const objHndl& item, int chargesUsedUp = 1); static bool IsBuckler(objHndl shield); + static bool IsDoubleWeapon(objHndl weapon); void(__cdecl*_ForceRemove)(objHndl, objHndl); void ItemRemove(objHndl item); // pretty much same as ForceRemove, but also send a d20 signal for inventory update, and checks for parent first BOOL ItemGetAdvanced(objHndl item, objHndl parent, int slotIdx, int flags); @@ -246,7 +247,7 @@ struct InventorySystem : temple::AddressTable rebase(GetSubstituteInventory, 0x1007F5B0); //rebase(GetItemAtInvIdx, 0x100651B0); rebase(_ItemWornAt, 0x10065010); - rebase(_GetWieldType, 0x10066580); + //rebase(_GetWieldType, 0x10066580); //rebase(FindMatchingStackableItem, 0x10067DF0); @@ -273,7 +274,7 @@ struct InventorySystem : temple::AddressTable int(__cdecl*_ItemRemove)(objHndl item); int(__cdecl*_ItemDrop)(objHndl item); objHndl(__cdecl *_ItemWornAt)(objHndl, EquipSlot nItemSlot); - int(__cdecl *_GetWieldType)(objHndl wielder, objHndl item); + //int(__cdecl *_GetWieldType)(objHndl wielder, objHndl item); int InvIdxForSlot(EquipSlot slot); // converts EquipSlot to inventory index int InvIdxForSlot(int slot); // converts EquipSlot to inventory index diff --git a/TemplePlus/protos.cpp b/TemplePlus/protos.cpp index 7bcfb0bec..aaf30b0a5 100644 --- a/TemplePlus/protos.cpp +++ b/TemplePlus/protos.cpp @@ -14,7 +14,7 @@ #include "d20_race.h" class ProtosHooks : public TempleFix{ -public: +public: // static int StdParamParserFunc(int colIdx, objHndl handle, char* content, obj_f fieldId, int arrayLen, char** stringArray, int fieldSubIdx); // for future reference static int ParseCondition(int colIdx, objHndl handle, char* content, int condIdx, int stage, int unused, int unused2); @@ -22,8 +22,18 @@ class ProtosHooks : public TempleFix{ static int ParseType(int colIdx, objHndl handle, char* content, obj_f field, int arrayLen, char** strings); static int ParseRace(int colIdx, objHndl handle, char* content, obj_f field, int arrayLen, char** strings); static int ParseSpell(int colIdx, objHndl handle, char* content, obj_f field); + + static int ParseWeaponDamageType(int colIdx, objHndl handle, char* content, obj_f field); + static int ParseWeaponCritRange(int colIdx, objHndl handle, char* content, obj_f field); + static int ParseDice(int colIdx, objHndl handle, char* content, obj_f field); + static int GenericNumber(int colIdx, objHndl handle, char* content, obj_f field); + static int SetCritterAttacks(objHndl handle); - void apply() override + + static int DamageTypeFromString(char* str); + static int DamageTypeFromD20String(char* str); + + void apply() override { // protos.tab line parser; replaced for supporting extensions @@ -50,7 +60,6 @@ class ProtosHooks : public TempleFix{ }); - // Fix for protos condition parser replaceFunction(0x10039E80, ParseCondition); @@ -64,6 +73,12 @@ class ProtosHooks : public TempleFix{ replaceFunction(0x1003A8C0, ParseSpell); + // Hook into weapon parsing for e.g. shield bash stats + replaceFunction(0x100397D0, ParseWeaponDamageType); + replaceFunction(0x10039CE0, ParseWeaponCritRange); + replaceFunction(0x10039B80, ParseDice); + replaceFunction(0x10039380, GenericNumber); + //SetCritterAttacks replaceFunction(0x1003AAC0, SetCritterAttacks); } @@ -141,7 +156,7 @@ int ProtosHooks::ParseCondition(int colIdx, objHndl handle, char * content, int } else { - isParsingCond = 0; + isParsingCond = 0; } return 1; } @@ -178,8 +193,7 @@ int ProtosHooks::ParseCondition(int colIdx, objHndl handle, char * content, int return 1; } - auto damTypeFromString = temple::GetRef(0x100E0AF0); - protoParseParam1 = damTypeFromString(content); + protoParseParam1 = DamageTypeFromString(content); if (protoParseParam1 != -1) { return 1; @@ -210,7 +224,7 @@ int ProtosHooks::ParseCondition(int colIdx, objHndl handle, char * content, int protoParseParam1 = ElfHash::Hash(txtBuf); return 1; - } + } else { protoParseParam1 = atol(content); @@ -240,8 +254,7 @@ int ProtosHooks::ParseCondition(int colIdx, objHndl handle, char * content, int } if (!stage2Ok){ - auto damTypeFromString = temple::GetRef(0x100E0AF0); - protoParseParam2 = damTypeFromString(content); + protoParseParam2 = DamageTypeFromString(content); if (protoParseParam2 != -1) { stage2Ok = true; @@ -335,51 +348,109 @@ int ProtosHooks::ParseMonsterSubcategory(int colIdx, objHndl handle, char * cont return 1; } +static bool armorWeaponStats = false; +static int armorWeaponStage = 0; +static int armorWeaponDamageType = (int)DamageType::Bludgeoning; +static int armorWeaponDice = 0; +static int armorWeaponCritRange = 20; +static int armorWeaponCritMult = 2; + +// armor weapon condition initialization and reset parsing +void ArmorWeaponFinalize(GameObjectBody* obj) { + if (armorWeaponStats) { + // shield bash is the only one right now + auto condId = ElfHash::Hash("Shield Bash"); + + auto condField = obj_f_item_pad_wielder_condition_array; + auto condArgField = obj_f_item_pad_wielder_argument_array; + auto condArray = obj->GetInt32Array(condField); + auto condArgArray = obj->GetInt32Array(condArgField); + + obj->SetInt32(condField, condArray.GetSize(), condId); + + obj->SetInt32(condArgField, condArgArray.GetSize(), armorWeaponDamageType); + obj->SetInt32(condArgField, condArgArray.GetSize(), armorWeaponDice); + obj->SetInt32(condArgField, condArgArray.GetSize(), 0); + obj->SetInt32(condArgField, condArgArray.GetSize(), armorWeaponCritRange); + obj->SetInt32(condArgField, condArgArray.GetSize(), armorWeaponCritMult); + } + + armorWeaponStats = false; + armorWeaponStage = 0; + armorWeaponDice = 0; + armorWeaponCritRange = 20; + armorWeaponCritMult = 2; + armorWeaponDamageType = (int)DamageType::Bludgeoning; +} + int ProtosHooks::ParseType(int colIdx, objHndl handle, char * content, obj_f field, int arrayLen, char ** strings){ auto foundType = false; auto val = 0; - if (content && *content){ - if (arrayLen <= 0) - return 0; - - for (auto i=0; i< arrayLen; i++){ - if ( strings[i] && !_strcmpi(content, strings[i])){ - - val = i; - objSystem->GetObject(handle)->SetInt32(field, val); - foundType = true; - break; - } - } + if (!content || !*content) return FALSE; - if (field == obj_f_weapon_type){ - if (!foundType && !_strcmpi(content, "wt_mindblade")){ - val = wt_mindblade; - foundType = true; - objSystem->GetObject(handle)->SetInt32(field, val); - - } + if (arrayLen <= 0) return 0; - auto weapType = (WeaponTypes)val; - auto damType = weapons.wpnProps[weapType].damType; + for (auto i=0; i< arrayLen; i++){ + if ( strings[i] && !_strcmpi(content, strings[i])){ - if (damType == DamageType::Unspecified){ - weapons.wpnProps[weapType].damType = (DamageType)objSystem->GetObject(handle)->GetInt32(obj_f_weapon_attacktype); - } - //else if (damType != (DamageType)objSystem->GetObject(handle)->GetInt32(obj_f_weapon_attacktype)){ // for debug - // auto d = description.getDisplayName(handle); - // auto strangeWeaponDamType = (DamageType)objSystem->GetObject(handle)->GetInt32(obj_f_weapon_attacktype); - // auto dummy = 1; - //} + val = i; + objSystem->GetObject(handle)->SetInt32(field, val); + foundType = true; + break; } } + auto obj = objSystem->GetObject(handle); + + if (field == obj_f_weapon_type){ + if (!foundType && !_strcmpi(content, "shield")) { + armorWeaponStats = true; + return TRUE; + } + + if (!foundType && !_strcmpi(content, "wt_mindblade")){ + val = wt_mindblade; + foundType = true; + obj->SetInt32(field, val); + } + + auto weapType = (WeaponTypes)val; + auto damType = weapons.wpnProps[weapType].damType; + + if (damType == DamageType::Unspecified){ + weapons.wpnProps[weapType].damType = (DamageType)obj->GetInt32(obj_f_weapon_attacktype); + } + //else if (damType != (DamageType)objSystem->GetObject(handle)->GetInt32(obj_f_weapon_attacktype)){ // for debug + // auto d = description.getDisplayName(handle); + // auto strangeWeaponDamType = (DamageType)objSystem->GetObject(handle)->GetInt32(obj_f_weapon_attacktype); + // auto dummy = 1; + //} + } return foundType ? TRUE : FALSE; } +int ProtosHooks::GenericNumber(int colIdx, objHndl handle, char *content, obj_f field) { + auto obj = objSystem->GetObject(handle); + + bool armorWeapon = obj_t_armor == obj->type && obj_f_weapon_crit_hit_chart == field; + + if (content && *content) { + if (armorWeapon) { // override critical hit multiplier for armor + armorWeaponCritMult = atol(content); + } else { + obj->SetInt32(field, atol(content)); + } + } + + if (armorWeapon && ++armorWeaponStage >= 4) + ArmorWeaponFinalize(obj); + + return 1; +} + int ProtosHooks::ParseRace(int colIdx, objHndl handle, char * content, obj_f field, int arrayLen, char ** strings) { if (content && *content){ @@ -436,6 +507,78 @@ int ProtosHooks::ParseSpell(int colIdx, objHndl handle, char* content, obj_f fie return 1; } +int ProtosHooks::ParseWeaponDamageType(int colIdx, objHndl handle, char* content, obj_f field) { + auto obj = objSystem->GetObject(handle); + bool armorWeapon = obj->type == obj_t_armor && field == obj_f_weapon_attacktype; + + auto dmgType = DamageTypeFromD20String(content); + if (-1 == dmgType && _strcmpi(content, "D20DT_UNSPECIFIED")) { + dmgType = atol(content); + } + + if (content && *content) { + if (armorWeapon) { + armorWeaponDamageType = dmgType; + } else { + // original behavior + obj->SetInt32(field, dmgType); + } + } + + if (armorWeapon && ++armorWeaponStage >= 4) + ArmorWeaponFinalize(obj); + + return 1; +} + +int ProtosHooks::ParseWeaponCritRange(int colIdx, objHndl handle, char* content, obj_f field) { + auto obj = objSystem->GetObject(handle); + bool armorWeapon = obj->type == obj_t_armor && obj_f_weapon_crit_range == field; + + if (content && *content) { + // Crit ranges in the proto are given as the lowest crit roll, + // but they are stored as how many sides of the die are critical hits. + auto range = 21 - atol(content); + + if (armorWeapon) { + armorWeaponCritRange = range; + } else { + obj->SetInt32(field, range); + } + } + + if (armorWeapon && ++armorWeaponStage >= 4) + ArmorWeaponFinalize(obj); + + return 1; +} + +int ProtosHooks::ParseDice(int colIdx, objHndl handle, char* content, obj_f field) { + auto obj = objSystem->GetObject(handle); + bool armorWeapon = obj->type == obj_t_armor && obj_f_weapon_damage_dice == field; + int result = 1; + + if (content && *content) { + int diceCount, diceType, diceBonus; + if (!Dice::Parse(content, diceCount, diceType, diceBonus)) { + result = 0; + } else { + Dice dice(diceCount, diceType, diceBonus); + + if (armorWeapon) { + armorWeaponDice = dice.ToPacked(); + } else { + obj->SetInt32(field, dice.ToPacked()); + } + } + } + + if (armorWeapon && ++armorWeaponStage >= 4) + ArmorWeaponFinalize(obj); + + return result; +} + int ProtosHooks::SetCritterAttacks(objHndl handle) { auto obj = objSystem->GetObject(handle); @@ -468,8 +611,46 @@ int ProtosHooks::SetCritterAttacks(objHndl handle) auto dexMod = objects.GetModFromStatLevel(obj->GetInt32(obj_f_critter_abilities_idx, 1)); auto attackBonusOld = obj->GetInt32(obj_f_attack_bonus_idx, 3); - obj->SetInt32(obj_f_attack_bonus_idx, 3, attackBonusOld - dexMod - sizeMod); + obj->SetInt32(obj_f_attack_bonus_idx, 3, attackBonusOld - dexMod - sizeMod); } } return 0; } + +int ProtosHooks::DamageTypeFromString(char* str) { + static auto original = temple::GetRef(0x100E0AF0); + return original(str); +} + +int ProtosHooks::DamageTypeFromD20String(char* content) { + static std::unordered_map damageTypeEnumTable({ + {"d20dt_unspecified", DamageType::Unspecified}, + {"d20dt_bludgeoning", DamageType::Bludgeoning}, + {"d20dt_piercing", DamageType::Piercing}, + {"d20dt_slashing", DamageType::Slashing}, + {"d20dt_bludgeoning_and_piercing", DamageType::BludgeoningAndPiercing}, + {"d20dt_piercing_and_slashing", DamageType::PiercingAndSlashing}, + {"d20dt_slashing_and_bludgeoning", DamageType::SlashingAndBludgeoning}, + {"d20dt_slashing_and_bludgeoning_and_piercing", DamageType::SlashingAndBludgeoningAndPiercing}, + {"d20dt_acid", DamageType::Acid}, + {"d20dt_cold", DamageType::Cold}, + {"d20dt_electricity", DamageType::Electricity}, + {"d20dt_fire", DamageType::Fire}, + {"d20dt_sonic", DamageType::Sonic}, + {"d20dt_negative_energy", DamageType::NegativeEnergy}, + {"d20dt_subdual", DamageType::Subdual}, + {"d20dt_poison", DamageType::Poison}, + {"d20dt_positive_energy", DamageType::PositiveEnergy}, + {"d20dt_force", DamageType::Force}, + {"d20dt_blood_loss", DamageType::BloodLoss}, + {"d20dt_magic", DamageType::Magic} + }); + + auto findDT = damageTypeEnumTable.find(tolower(content)); + if (findDT != damageTypeEnumTable.end()){ + return (int)findDT->second; + } + + // default to unspecified + return -1; +} diff --git a/TemplePlus/python/python_object.cpp b/TemplePlus/python/python_object.cpp index e99f00088..233ff7090 100644 --- a/TemplePlus/python/python_object.cpp +++ b/TemplePlus/python/python_object.cpp @@ -4420,6 +4420,15 @@ static PyObject* PyObjHandle_IsBuckler(PyObject* obj, PyObject* args) { return PyInt_FromLong(result); } +static PyObject* PyObjHandle_IsDoubleWeapon(PyObject* obj, PyObject* args) { + auto self = GetSelf(obj); + if (!self->handle) { + return PyInt_FromLong(0); + } + auto result = inventory.IsDoubleWeapon(self->handle); + return PyInt_FromLong(result); +} + static PyObject* PyObjHandle_IsThrowingWeapon(PyObject* obj, PyObject* args) { auto self = GetSelf(obj); if (!self->handle) { @@ -4661,6 +4670,7 @@ static PyMethodDef PyObjHandleMethods[] = { { "is_active_combatant", PyObjHandle_IsActiveCombatant, METH_VARARGS, NULL }, { "is_arcane_spell_class", PyObjHandle_IsArcaneSpellClass, METH_VARARGS, NULL }, { "is_buckler", PyObjHandle_IsBuckler, METH_VARARGS, NULL }, + { "is_double_weapon", PyObjHandle_IsDoubleWeapon, METH_VARARGS, NULL }, { "is_category_type", PyObjHandle_IsCategoryType, METH_VARARGS, NULL }, { "is_category_subtype", PyObjHandle_IsCategorySubtype, METH_VARARGS, NULL }, { "is_critter", PyObjHandle_IsCritter, METH_VARARGS, NULL}, diff --git a/TemplePlus/temple_enums.h b/TemplePlus/temple_enums.h index d0cdedd6f..571204b03 100644 --- a/TemplePlus/temple_enums.h +++ b/TemplePlus/temple_enums.h @@ -1741,6 +1741,9 @@ enum D20DispatcherKey : uint32_t { DK_QUE_Is_Preferring_One_Handed_Wield = 0x14A, // e.g. a character with a Buckler can opt to wield a sword one handed so as to not take the -1 to hit penalty DK_QUE_Scribe_Scroll_Spell_Level = 0x14B, DK_QUE_Critter_Is_Immune_Paralysis = 0x14C, + DK_QUE_Is_Two_Weapon_Fighting = 0x14D, + DK_QUE_Left_Is_Primary = 0x14E, + DK_QUE_Can_Shield_Bash = 0x14F, DK_LVL_Stats_Activate = 100, DK_LVL_Stats_Check_Complete = 101, diff --git a/TemplePlus/ui/ui_char.cpp b/TemplePlus/ui/ui_char.cpp index 9b2952ed7..78fc1bba5 100644 --- a/TemplePlus/ui/ui_char.cpp +++ b/TemplePlus/ui/ui_char.cpp @@ -2282,7 +2282,8 @@ void UiCharHooks::apply(){ auto desc = fmt::format("{}\n\n{}: {}", objects.GetDisplayName(item, observer), uiAssets->GetTooltipString(100) /* Weight*/, itemObj->GetInt32(obj_f_item_weight)); - auto acBonus = dispatch.DispatchItemQuery(item, DK_QUE_Armor_Get_AC_Bonus); + auto packedAcBonus = dispatch.DispatchItemQuery(item, DK_QUE_Armor_Get_AC_Bonus); + auto acBonus = ((packedAcBonus & 0xff00) >> 8) + (packedAcBonus & 0xff); auto dexBonus = dispatch.DispatchItemQuery(item, DK_QUE_Armor_Get_Max_DEX_Bonus); auto maxSpeed = dispatch.DispatchItemQuery(item, DK_QUE_Armor_Get_Max_Speed); auto dexBonusString = dexBonus == 100 ? fmt::format(" - ") : fmt::format("{:+d}", dexBonus); diff --git a/tpdata/templeplus/lib/templeplus/constants.py b/tpdata/templeplus/lib/templeplus/constants.py index 8e514ff38..6196ef30b 100644 --- a/tpdata/templeplus/lib/templeplus/constants.py +++ b/tpdata/templeplus/lib/templeplus/constants.py @@ -852,6 +852,15 @@ Q_Craft_Wand_Spell_Level = 117 Q_Is_Ethereal = 118 Q_Empty_Body_Num_Rounds = 119 +Q_Quivering_Palm_Can_Perform = 120 +Q_Trip_AOO = 121 +Q_Get_Arcane_Spell_Failure = 122 +Q_Is_Preferring_One_Handed_Wield = 123 +Q_Scribe_Scroll_Level = 124 +Q_Critter_Is_Immune_Paralysis = 125 +Q_Is_Two_Weapon_Fighting = 126 +Q_Left_Is_Primary = 127 +Q_Can_Shield_Bash = 128 RADIAL_MENU_PARAM_MIN_SETTING = 1 RADIAL_MENU_PARAM_MAX_SETTING = 2 @@ -4184,6 +4193,9 @@ EK_Q_Is_Preferring_One_Handed_Wield = 0x14A # gets arcane spell failure for (class_enum, equip_slot) combo EK_Q_Scribe_Scroll_Spell_Level = 0x14B EK_Q_Critter_Is_Immune_Paralysis = 0x14C +EK_Q_Is_Two_Weapon_Fighting = 0x14D +EK_Q_Left_Is_Primary = 0x14E +EK_Q_Can_Shield_Bash = 0x14F EK_LVL_Stats_Activate = 100 EK_LVL_Stats_Check_Complete = 101 diff --git a/tpdata/tpmes/bonus.mes b/tpdata/tpmes/bonus.mes index 8e0f06ec8..18b56df1b 100644 --- a/tpdata/tpmes/bonus.mes +++ b/tpdata/tpmes/bonus.mes @@ -13,4 +13,5 @@ {347}{~Seeking Weapon~[TAG_WEAPON_SEEKING]} {348}{Bardic Music - Inspire Heroics} {349}{Hiding in Combat} -{350}{Tower Shield} \ No newline at end of file +{350}{Tower Shield} +{351}{~Shield~[TAG_SHIELD_BONUS] bonus lost: ~shield bashed~[TAG_SHIELD_BASH] this round} diff --git a/tpdata/tpmes/combat.mes b/tpdata/tpmes/combat.mes index 408ef11ec..88105e623 100644 --- a/tpdata/tpmes/combat.mes +++ b/tpdata/tpmes/combat.mes @@ -38,6 +38,8 @@ {5122}{Heroism} // from bardic effect {5123}{Hide Check} {5124}{Prefer One-Handed Wield} +{5125}{Enable Two Weapon Fighting} +{5126}{Left Hand Primary} {6017}{Set Caster Level} {6018}{Craft} diff --git a/tpdata/tpmes/help_extensions.tab b/tpdata/tpmes/help_extensions.tab index bd3803914..f52a37112 100644 --- a/tpdata/tpmes/help_extensions.tab +++ b/tpdata/tpmes/help_extensions.tab @@ -2,8 +2,11 @@ TEST Title Beware the mischievous terminator character! Please verify via a h TAG_HIT_DICE Hit Dice This line gives the creature's number and type of Hit Dice (the die rolled to generate hit points), and lists any bonus hit points. A parenthetical note gives the average hit points for a creature of the indicated number of Hit Dice. A creature's Hit Dice total is also treated as its level for determining how spells affect the creature, its rate of natural healing, and its maximum ranks in a skill. TAG_RADIAL_MENU_DISABLE_AOOS TAG_INGAME Disable Attacks of Opportunity Sometimes, you want to hold off on Attacks of Opportunity - for instance if you have an invisible Rogue whose sneak attack you wish to conserve for the right time. This will make your character skip their AoOs. TAG_RADIAL_MENU_PREFER_ONE_HANDED_WIELD TAG_INGAME Prefer One Handed Wield When holding a buckler, you may wish to prefer wielding your weapon one-handed so as to not take the -1 to hit penalty. This option allows you to choose to wield single-handed weapons in such a way. Note that if your off-hand is free, it will still be used to hold the weapon (if possible), since there is no downside in that case. +TAG_RADIAL_MENU_TWO_WEAPON_FIGHTING TAG_INGAME Two Weapon Fighting When wielding two weapons, it may not always be advantageous to fight with both. This toggle allows choosing between one and two weapon fighting styles. When fighting with one weapon, you do not incur penalties on attack rolls, even if you are holding a weapon in the opposite hand. This toggle also allows two weapon fighting with a double weapon (e.g. quarterstaff), and using a shield to bash as a second weapon. +TAG_RADIAL_MENU_LEFT_PRIMARY TAG_INGAME Left Handed Primary Attacks When wielding two weapons or a weapon with two ends, either can be designated as the primary weapon in a given round. Doing so swaps which weapon is used for the primary and off hand attacks in the round, including relevant bonuses if two weapon fighting. This option needn't be used in conjunction with two weapon fighting. You can make attacks exclusively with your left hand in a round, incurring no penalties to hit. This includes using the 'left' end of a double weapon, and shield bashing. TAG_CRAFT_WAND TAG_FEATS_DES TAG_ITEM_CREATION Craft Wand You can create wands, which hold spells. Prerequisite: ~Caster Level~[TAG_CASTER_LEVEL] 5th Benefit: You can create a wand of any 4th-level or lower spell that you know. You may also create a few special wands of higher level spells. The base price of a wand is: its caster level x the spell level x 750 gp To craft a wand, you must spend 1/25 of this base price in ~XP~[TAG_EXPERIENCE_AND_LEVELS] and use up raw materials costing one-half of this base price. A newly created wand has 50 charges. Any wand that stores a spell with a costly material component or an XP cost also carries a commensurate cost. In addition to the cost derived from the base price, you must expend fifty copies of the material component or pay fifty times the XP cost. TAG_DIVINE_MIGHT TAG_FEATS_DES Divine Might As a ~free action~[TAG_FREE_ACTION], spend one of your ~turn or rebuke undead~[TAG_TURN] attempts to add your ~Charisma~[TAG_CHARISMA] bonus to your weapon damage for 1 full ~round~[TAG_COMBAT]. +TAG_IMPROVED_SHIELD_BASH TAG_FEATS_DES TAG_CLASS_FEATURES_FIGHTER_BONUS_FEATS Improved Shield Bash You can bash with a shield while retaining its bonus to ~armor class~[TAG_ARMOR_CLASS] Prerequisites: ~Shield Proficiency~[TAG_SHIELD_PROF] Benefit: you may perform ~shield bash~[TAG_SHIELD_BASH] attacks while retaining the shield's contribution to your ~armor class~[TAG_ARMOR_CLASS] Normal: performing a ~shield bash~[TAG_SHIELD_BASH] causes the shield's contribution to your ~armor class~[TAG_ARMOR_CLASS] to be omitted until your next turn Special: ~fighters~[TAG_FIGHTERS] may select this feat as one of their ~fighter bonus feats~[TAG_CLASS_FEATURES_FIGHTER_BONUS_FEATS] TAG_HEZROU_STENCH Hezrou Stench A Hezrou's skin produces a foul-smelling, toxic liquid whenever it fights. Any living creature (except other demons) within 10 feet must succeed on a DC 24 Fortitude save or be nauseated for as long as it remains within the affected area and for 1d4 rounds afterward. Creatures that successfully save are sickened for as long as they remain in the area. A creature that successfully saves cannot be affected again by the same Hezrou's stench for 24 hours. A delay poison or neutralize poison spell removes either condition from one creature. Creatures that have immunity to poison are unaffected, and creatures resistant to poison receive their normal bonus on their saving throws. The save DC is Constitution-based. TAG_CLASS_FEATURES_MONK_QUIVERING_PALM TAG_MONKS Quivering Palm Starting at 15th level, a monk can set up vibrations within the body of another creature that can thereafter be fatal if the monk so desires. She can use this quivering palm attack once a week, and she must announce her intent before making her ~attack roll~[TAG_ATTACK_ROLL]. Constructs, oozes, plants, undead, incorporeal creatures, and creatures immune to ~critical hits~[TAG_CRITICAL_HIT] cannot be affected. Otherwise, if the monk strikes successfully and the target takes damage from the blow, the quivering palm attack succeeds. Thereafter the monk can try to slay the victim at any later time, as long as the attempt is made within a number of days equal to her monk level. To make such an attempt, the monk merely wills the target to die (a ~free action~[TAG_FREE_ACTION]), and unless the target makes a ~Fortitude~[TAG_FORTITUDE] ~saving throw~[TAG_SAVING_THROW_DESC] (DC 10 + 1/2 the monk's level + the monk's ~Wis~[TAG_WISDOM] modifier), it dies. If the saving throw is successful, the target is no longer in danger from that particular quivering palm attack, but it may still be affected by another one at a later time. TAG_CLASS_FEATURES_MONK_EMPTY_BODY TAG_MONKS Empty Body At 19th level, a monk gains the ability to assume an ethereal state for 1 round per monk level per day. She may go ethereal on a number of different occasions during any single day, as long as the total number of rounds spent in an ethereal state does not exceed her monk level. @@ -18,6 +21,7 @@ TAG_GORE TAG_WEAPONS Gore Attack The creature spears the opponent with an antl TAG_SLAM TAG_WEAPONS Slam Attack The creature batters opponents with an appendage, dealing bludgeoning damage. TAG_STING TAG_WEAPONS Sting Attack The creature stabs with a stinger, dealing piercing damage. Sting attacks usually deal damage from poison in addition to hit point damage. TAG_TENTACLE TAG_WEAPONS Tentacle Attack The creature flails at opponents with a powerful tentacle, dealing bludgeoning (and sometimes slashing) damage. +TAG_SHIELD_BASH TAG_SPECIAL_ATTACKS Shield Bash When wearing a light or heavy shield, you may use it to bludgeon an opponent. Light shields of the appropriate size are treated as light martial weapons, while heavy shields count as one handed martial weapons. Shield bash attacks may either be used as part of ~two weapon fighting~[TAG_TWO_WEAPONS], or by designating the shield hand as the ~primary attack~[TAG_RADIAL_MENU_LEFT_PRIMARY]. Making a shield bash attack forfeits the shield's ~armor class~[TAG_ARMOR_CLASS] bonus for the round unless the attacker has the ~Improved Shield Bash~[TAG_IMPROVED_SHIELD_BASH] feat. Medium sized light shields deal 1d3 bludgeoning damage; small sized shields deal 1d2. Medium sized heavy shields deal 1d4 bludgeoning damage; small sized shields deal 1d3. In game, large shields are heavy for medium creatures, while small shields are light. Bucklers and tower shields cannot be used to shield bash. TAG_CLASS_FEATURES_BARD_SUGGESTION TAG_BARDS Suggestion A bard of 6th level or higher with 9 or more ranks in a ~Perform~[TAG_PERFORM] skill can make a suggestion to a creature that he has already ~fascinated~[TAG_CLASS_FEATURES_BARD_FASCINATE]. In ToEE, the suggestion will instill fear in the target, causing it to flee. Using this ability does not break the bard's concentration on the fascinate effect, nor does it allow a second ~saving throw~[TAG_SAVING_THROW_DESC] against the fascinate effect. Making a suggestion doesn't count against a bard's daily limit on bardic music performances. A Will saving throw (DC 10 + 1/2 bard's level + bard's Cha modifier) negates the effect. This ability affects only a single creature (until the Bard gains the ~Mass Suggestion~[TAG_CLASS_FEATURES_BARD_SUGGESTION_MASS] ability). Suggestion is an enchantment (compulsion), mind-affecting, language dependent ability. TAG_CLASS_FEATURES_BARD_SUGGESTION_MASS TAG_BARDS Mass Suggestion This ability functions like ~suggestion~[TAG_CLASS_FEATURES_BARD_SUGGESTION], except that a bard of 18th level or higher with 21 or more ranks in a ~Perform~[TAG_PERFORM] skill can make the suggestion simultaneously to any number of creatures that he has already ~fascinated~[TAG_CLASS_FEATURES_BARD_FASCINATE]. Mass suggestion is an enchantment (compulsion), mind-affecting, language-dependent ability. TAG_CLASS_FEATURES_BARD_SONG_OF_FREEDOM TAG_BARDS Song of Freedom A bard of 12th level or higher with 15 or more ranks in a ~Perform~[TAG_PERFORM] skill can use music or poetics to create an effect equivalent to the ~break enchantment~[TAG_SPELLS_BREAK_ENCHANTMENT] spell (caster level equals the character's bard level). Using this ability requires 1 minute of uninterrupted concentration and music, and it functions on a single target within 30 feet. A bard can't use song of freedom on himself. diff --git a/tpdatasrc/co8fixes/rules/protos_override.tab b/tpdatasrc/co8fixes/rules/protos_override.tab index 23c9f9701..3081615c3 100644 --- a/tpdatasrc/co8fixes/rules/protos_override.tab +++ b/tpdatasrc/co8fixes/rules/protos_override.tab @@ -4,12 +4,44 @@ 4704 obj_t_weapon 15 4704 4704 size_small mat_flesh 12 10070 OIF_NO_NPC_PICKUP OIF_DRAW_WHEN_PARENTED 2 30500 183 OIF_WEAR_WEAPON_PRIMARY OIF_WEAR_WEAPON_SECONDARY 18500 2 D20DT_BLUDGEONING_AND_PIERCING 1d4 spiked_gauntlet 19 Weapon Masterwork 0 0 4705 obj_t_weapon 15 4705 4705 size_small mat_flesh 12 10070 OIF_NO_NPC_PICKUP OIF_DRAW_WHEN_PARENTED 2 500 183 OIF_WEAR_WEAPON_PRIMARY OIF_WEAR_WEAPON_SECONDARY 18500 2 D20DT_BLUDGEONING_AND_PIERCING 1d4 spiked_gauntlet 19 6031 obj_t_armor 6031 6031 size_small mat_glass 8 12039 OIF_NO_NPC_PICKUP 0 5000 24 OIF_WEAR_HELMET 100 ARMOR_TYPE_LIGHT HELM_TYPE_SMALL Armor Bonus 0 0 Special Equipment Skill Bonus skill_appraise 2 +6050 obj_t_armor 6050 6050 size_medium mat_metal 13 12076 OIF_IS_MAGICAL OIF_DRAW_WHEN_PARENTED OIF_NO_NPC_PICKUP 15 117000 132 20143 OIF_WEAR_WEAPON_SECONDARY OIF_WEAR_ARMOR 2 D20DT_BLUDGEONING 1d4 shield 20 100 15 -2 ARMOR_TYPE_SHIELD Shield Bonus 2 0 Shield Enhancement Bonus 1 0 +6051 obj_t_armor 6051 6051 size_medium mat_metal 13 12005 OIF_IS_MAGICAL OIF_DRAW_WHEN_PARENTED OIF_NO_NPC_PICKUP 15 417000 375 20143 OIF_WEAR_WEAPON_SECONDARY OIF_WEAR_ARMOR 2 D20DT_BLUDGEONING 1d4 shield 20 100 15 -2 ARMOR_TYPE_SHIELD Shield Bonus 2 0 Shield Enhancement Bonus 2 0 +6052 obj_t_armor 6052 6052 size_medium mat_metal 13 12003 OIF_IS_MAGICAL OIF_DRAW_WHEN_PARENTED OIF_NO_NPC_PICKUP 15 917000 376 20143 OIF_WEAR_WEAPON_SECONDARY OIF_WEAR_ARMOR 2 D20DT_BLUDGEONING 1d4 shield 20 100 15 -2 ARMOR_TYPE_SHIELD Shield Bonus 2 0 Shield Enhancement Bonus 3 0 +6053 obj_t_armor 6053 6053 size_medium mat_metal 13 12074 OIF_IS_MAGICAL OIF_DRAW_WHEN_PARENTED OIF_NO_NPC_PICKUP 10 1615700 130 20143 OIF_WEAR_WEAPON_SECONDARY OIF_WEAR_ARMOR 2 D20DT_BLUDGEONING 1d4 shield 20 100 15 -2 ARMOR_TYPE_SHIELD Shield Bonus 2 0 Shield Enhancement Bonus 4 0 +6054 obj_t_armor 6054 6054 size_medium mat_metal 13 12073 OIF_IS_MAGICAL OIF_DRAW_WHEN_PARENTED OIF_NO_NPC_PICKUP 15 2517000 129 20143 OIF_WEAR_WEAPON_SECONDARY OIF_WEAR_ARMOR 2 D20DT_BLUDGEONING 1d4 shield 20 100 15 -2 ARMOR_TYPE_SHIELD Shield Bonus 2 0 Shield Enhancement Bonus 5 0 +6060 obj_t_armor 6060 6060 size_medium mat_metal 13 12067 OIF_DRAW_WHEN_PARENTED OIF_NO_NPC_PICKUP 15 2000 123 OIF_WEAR_WEAPON_SECONDARY OIF_WEAR_ARMOR 2 D20DT_BLUDGEONING 1d4 shield 20 100 15 -2 ARMOR_TYPE_SHIELD Shield Bonus 2 0 +6061 obj_t_armor 6061 6061 size_medium mat_metal 13 12068 OIF_DRAW_WHEN_PARENTED OIF_NO_NPC_PICKUP 15 2000 124 OIF_WEAR_WEAPON_SECONDARY OIF_WEAR_ARMOR 2 D20DT_BLUDGEONING 1d4 shield 20 100 15 0 ARMOR_TYPE_SHIELD Shield Bonus 2 0 +6062 obj_t_armor 6062 6062 size_medium mat_metal 13 12069 OIF_DRAW_WHEN_PARENTED OIF_NO_NPC_PICKUP 15 2000 125 OIF_WEAR_WEAPON_SECONDARY OIF_WEAR_ARMOR 2 D20DT_BLUDGEONING 1d4 shield 20 100 15 -2 ARMOR_TYPE_SHIELD Shield Bonus 2 0 +6063 obj_t_armor 6063 6063 size_medium mat_metal 13 12070 OIF_DRAW_WHEN_PARENTED OIF_NO_NPC_PICKUP 15 17000 126 OIF_WEAR_WEAPON_SECONDARY OIF_WEAR_ARMOR 2 D20DT_BLUDGEONING 1d4 shield 20 100 15 -2 ARMOR_TYPE_SHIELD Shield Bonus 2 0 Armor Masterwork +6064 obj_t_armor 6064 6064 size_medium mat_wood 13 12071 OIF_DRAW_WHEN_PARENTED OIF_NO_NPC_PICKUP 10 700 127 OIF_WEAR_WEAPON_SECONDARY OIF_WEAR_ARMOR 2 D20DT_BLUDGEONING 1d4 shield 20 100 15 -2 ARMOR_TYPE_SHIELD Shield Bonus 2 0 +6065 obj_t_armor 6065 6065 size_medium mat_wood 13 12072 OIF_DRAW_WHEN_PARENTED OIF_NO_NPC_PICKUP 10 15700 128 OIF_WEAR_WEAPON_SECONDARY OIF_WEAR_ARMOR 2 D20DT_BLUDGEONING 1d4 shield 20 100 15 -2 ARMOR_TYPE_SHIELD Shield Bonus 2 0 Armor Masterwork +6066 obj_t_armor 6066 6066 size_medium mat_metal 13 12073 OIF_DRAW_WHEN_PARENTED OIF_NO_NPC_PICKUP 15 2000 129 OIF_WEAR_WEAPON_SECONDARY 100 15 -2 ARMOR_TYPE_SHIELD Shield Bonus 2 0 +6067 obj_t_armor 6067 6067 size_medium mat_wood 13 12074 OIF_DRAW_WHEN_PARENTED OIF_NO_NPC_PICKUP 10 700 130 OIF_WEAR_WEAPON_SECONDARY OIF_WEAR_ARMOR 2 D20DT_BLUDGEONING 1d4 shield 20 100 15 -2 ARMOR_TYPE_SHIELD Shield Bonus 2 0 +6068 obj_t_armor 6068 6068 size_medium mat_metal 13 12075 OIF_DRAW_WHEN_PARENTED OIF_NO_NPC_PICKUP 15 2000 131 OIF_WEAR_WEAPON_SECONDARY OIF_WEAR_ARMOR 2 D20DT_BLUDGEONING 1d4 shield 20 100 15 -2 ARMOR_TYPE_SHIELD Shield Bonus 2 0 +6069 obj_t_armor 6069 6069 size_medium mat_metal 13 12076 OIF_DRAW_WHEN_PARENTED OIF_NO_NPC_PICKUP 15 2000 132 OIF_WEAR_WEAPON_SECONDARY OIF_WEAR_ARMOR 2 D20DT_BLUDGEONING 1d4 shield 20 100 15 -2 ARMOR_TYPE_SHIELD Shield Bonus 2 0 +6070 obj_t_armor 6070 6070 size_medium mat_wood 13 12077 OIF_DRAW_WHEN_PARENTED OIF_NO_NPC_PICKUP 10 700 133 OIF_WEAR_WEAPON_SECONDARY OIF_WEAR_ARMOR 2 D20DT_BLUDGEONING 1d4 shield 20 100 15 -2 ARMOR_TYPE_SHIELD Shield Bonus 2 0 +6071 obj_t_armor 6071 6071 size_medium mat_wood 13 12078 OIF_DRAW_WHEN_PARENTED OIF_NO_NPC_PICKUP 10 700 134 OIF_WEAR_WEAPON_SECONDARY OIF_WEAR_ARMOR 2 D20DT_BLUDGEONING 1d4 shield 20 100 15 -2 ARMOR_TYPE_SHIELD Shield Bonus 2 0 +6072 obj_t_armor 6072 6072 size_medium mat_wood 13 12079 OIF_DRAW_WHEN_PARENTED OIF_NO_NPC_PICKUP 10 700 135 OIF_WEAR_WEAPON_SECONDARY OIF_WEAR_ARMOR 2 D20DT_BLUDGEONING 1d4 shield 20 100 15 -2 ARMOR_TYPE_SHIELD Shield Bonus 2 0 +6073 obj_t_armor 6073 6073 size_small mat_wood 13 12080 OIF_DRAW_WHEN_PARENTED OIF_NO_NPC_PICKUP 6 10000 136 OIF_WEAR_WEAPON_SECONDARY OIF_WEAR_ARMOR 2 D20DT_BLUDGEONING 1d4 shield 20 100 0 0 ARMOR_TYPE_SHIELD Shield Bonus 2 0 Armor Masterwork Skill Circumstance Bonus skill_hide -1 Skill Circumstance Bonus skill_move_silently -1 Skill Circumstance Bonus skill_pick_pocket -1 Skill Circumstance Bonus skill_use_magic_device -1 +6074 obj_t_armor 6074 6074 size_small mat_metal 13 12081 OIF_DRAW_WHEN_PARENTED OIF_NO_NPC_PICKUP 6 900 137 OIF_WEAR_WEAPON_SECONDARY OIF_WEAR_ARMOR 2 D20DT_BLUDGEONING 1d3 shield 20 100 5 -1 ARMOR_TYPE_SHIELD Shield Bonus 1 0 +6075 obj_t_armor 6075 6075 size_small mat_metal 13 12082 OIF_DRAW_WHEN_PARENTED OIF_NO_NPC_PICKUP 6 15900 138 OIF_WEAR_WEAPON_SECONDARY OIF_WEAR_ARMOR 2 D20DT_BLUDGEONING 1d3 shield 20 100 5 -1 ARMOR_TYPE_SHIELD Shield Bonus 1 0 Armor Masterwork +6076 obj_t_armor 6076 6076 size_small mat_wood 13 12083 OIF_DRAW_WHEN_PARENTED OIF_NO_NPC_PICKUP 5 300 139 OIF_WEAR_WEAPON_SECONDARY OIF_WEAR_ARMOR 2 D20DT_BLUDGEONING 1d3 shield 20 100 5 -1 ARMOR_TYPE_SHIELD Shield Bonus 1 0 +6077 obj_t_armor 6077 6077 size_small mat_metal 13 12084 OIF_DRAW_WHEN_PARENTED OIF_NO_NPC_PICKUP 6 900 140 OIF_WEAR_WEAPON_SECONDARY OIF_WEAR_ARMOR 2 D20DT_BLUDGEONING 1d3 shield 20 100 5 -1 ARMOR_TYPE_SHIELD Shield Bonus 1 0 6107 obj_t_armor 6107 6107 size_diminutive mat_metal 10 16033 OIF_IS_MAGICAL OIF_NO_NPC_PICKUP 0 1900000 228 20164 OIF_WEAR_RING_PRIMARY OIF_WEAR_RING_SECONDARY OIF_WEAR_RING_PRIMARY OIF_WEAR_RING_SECONDARY OIF_WEAR_NECKLACE 100 Armor Bonus 0 0 Necklace of Adaptation 6127 obj_t_armor 6127 size_diminutive mat_metal 10 16033 OIF_IS_MAGICAL 0 1900000 319 20164 100 Armor Bonus 0 0 Prayer Beads 0 0 +6135 obj_t_armor 6135 6135 size_medium mat_metal 13 12094 OIF_DRAW_WHEN_PARENTED OIF_IS_MAGICAL OIF_NO_NPC_PICKUP 15 917000 173 20143 OIF_WEAR_WEAPON_SECONDARY OIF_WEAR_ARMOR 2 D20DT_BLUDGEONING 1d4 shield 20 100 15 -2 ARMOR_TYPE_SHIELD Shield Bonus 2 0 Shield Enhancement Bonus 3 0 Weapon Unholy +6136 obj_t_armor 6136 6136 size_medium mat_wood 13 12071 OIF_DRAW_WHEN_PARENTED 10 700 127 OIF_WEAR_WEAPON_SECONDARY OIF_WEAR_ARMOR 2 D20DT_PIERCING 1d6 shield 20 100 15 -2 ARMOR_TYPE_SHIELD Shield Bonus 2 0 +6232 obj_t_armor 6232 6232 size_small mat_wood 13 12083 OIF_DRAW_WHEN_PARENTED OIF_NO_NPC_PICKUP 5 15300 139 OIF_WEAR_WEAPON_SECONDARY OIF_WEAR_ARMOR 2 D20DT_BLUDGEONING 1d3 shield 20 100 5 -1 ARMOR_TYPE_SHIELD Shield Bonus 1 0 Armor Masterwork 6268 obj_t_armor 6268 6268 size_small mat_metal 15 16040 OIF_IS_MAGICAL OIF_NO_NPC_PICKUP 1 500000 488 20166 OIF_WEAR_BRACERS 100 Armor Bonus 0 0 Bracers of Archery 1 0 +6312 obj_t_armor 6312 6312 size_medium mat_wood 13 12079 OIF_DRAW_WHEN_PARENTED OIF_NO_NPC_PICKUP 5 25700 135 20143 OIF_WEAR_WEAPON_SECONDARY OIF_WEAR_ARMOR 2 D20DT_BLUDGEONING 1d4 shield 20 100 15 0 ARMOR_TYPE_SHIELD Shield Bonus 2 0 Armor Masterwork Skill Circumstance Bonus skill_hide -1 Skill Circumstance Bonus skill_move_silently -1 Skill Circumstance Bonus skill_pick_pocket -1 Skill Circumstance Bonus skill_use_magic_device -1 +6313 obj_t_armor 6313 6313 size_large mat_metal 13 12075 OIF_DRAW_WHEN_PARENTED OIF_NO_NPC_PICKUP 5 102000 131 20143 OIF_WEAR_WEAPON_SECONDARY OIF_WEAR_ARMOR 2 D20DT_BLUDGEONING 1d4 shield 20 100 5 0 ARMOR_TYPE_SHIELD Shield Bonus 2 0 Armor Masterwork Skill Circumstance Bonus skill_hide -1 Skill Circumstance Bonus skill_move_silently -1 Skill Circumstance Bonus skill_pick_pocket -1 Skill Circumstance Bonus skill_use_magic_device -1 6347 obj_t_armor 6347 6347 size_huge mat_metal 13 12085 OIF_DRAW_WHEN_PARENTED OIF_NO_NPC_PICKUP 15 150000 141 OIF_WEAR_WEAPON_SECONDARY OIF_WEAR_ARMOR 2 50 -10 ARMOR_TYPE_SHIELD Shield Bonus 4 0 Shield Enhancement Bonus 1 0 Armor Masterwork 6400 obj_t_armor 6400 6400 size_diminutive mat_metal 1 16027 OIF_IS_MAGICAL OIF_NO_DROP OIF_NO_PICKPOCKET 0 0 414 6410 obj_t_armor 6410 6410 size_medium mat_cloth 2 12224 OIF_IS_MAGICAL OIF_NO_NPC_PICKUP 1 9000000 543 20276 -1 OIF_WEAR_ROBES 18200 100 0 0 Useable Item X Times Per Day 0 1 Skill Circumstance Bonus skill_search 10 Skill Circumstance Bonus skill_spot 10 'True Seeing' class_cleric 5 +6483 obj_t_armor 6483 6483 size_medium mat_metal 13 12073 OIF_DRAW_WHEN_PARENTED OIF_NO_NPC_PICKUP OIF_IS_MAGICAL 15 1917000 129 20143 OIF_WEAR_WEAPON_SECONDARY 2 D20DT_BLUDGEONING 1d4 shield 20 100 15 -2 ARMOR_TYPE_SHIELD Shield Bonus 2 0 Elemental Resistance Electricity 15 Shield Enhancement Bonus 1 0 Armor Masterwork +6485 obj_t_armor 6485 6485 size_medium mat_metal 13 12073 OIF_DRAW_WHEN_PARENTED OIF_NO_NPC_PICKUP OIF_IS_MAGICAL 15 117000 129 20143 OIF_WEAR_WEAPON_SECONDARY 2 D20DT_BLUDGEONING 1d4 shield 20 100 15 -2 ARMOR_TYPE_SHIELD Shield Bonus 2 0 Shield Enhancement Bonus 1 0 Armor Masterwork +6498 obj_t_armor 6498 6498 size_medium mat_metal 13 12187 OIF_DRAW_WHEN_PARENTED OIF_NO_NPC_PICKUP 15 17000 425 OIF_WEAR_WEAPON_SECONDARY OIF_WEAR_ARMOR 2 D20DT_BLUDGEONING 1d4 shield 20 100 15 -2 ARMOR_TYPE_SHIELD Shield Bonus 2 0 Armor Masterwork +6499 obj_t_armor 6499 6499 size_medium mat_metal 13 12187 OIF_DRAW_WHEN_PARENTED OIF_NO_NPC_PICKUP 15 2000 425 OIF_WEAR_WEAPON_SECONDARY OIF_WEAR_ARMOR 2 D20DT_BLUDGEONING 1d4 shield 20 100 15 -2 ARMOR_TYPE_SHIELD Shield Bonus 2 0 9200 obj_t_scroll 4002 9199 size_tiny mat_paper 1 16052 OIF_IS_MAGICAL OIF_EXPIRES_AFTER_USE OIF_NO_NPC_PICKUP 2500 00381 20176 1 UseableItem 0 0 'Goodberry' class_druid 1 12675 obj_t_generic 50 12675 12675 size_tiny mat_metal 10165 OIF_NO_NPC_PICKUP 1 5000 647 Special Equipment Skill Bonus skill_appraise 2 285 0 0 0 0 'Fog Cloud' class_sorcerer 2 14050 obj_t_npc 110 14050 14050 size_medium mat_flesh 5 1.5 1006 22.000000 OCF_ANIMAL OCF_MUTE OCF_NO_FLEE 13 15 15 2 12 6 male align_true_neutral 3730 5 1 1d6+1 Bite 3 ONF_NO_EQUIP 1 3 3 0 2 2d8 mc_type_animal Monster Stable Tripping Bite Spot 4 Listen 4 Hide 1 Move Silently 2 Survival 8 Track 2 0 0 0 0 diff --git a/tpdatasrc/co8infra/tpmes/help_extensions.tab b/tpdatasrc/co8infra/tpmes/help_extensions.tab index 81b762a15..93a94c18c 100644 --- a/tpdatasrc/co8infra/tpmes/help_extensions.tab +++ b/tpdatasrc/co8infra/tpmes/help_extensions.tab @@ -1,8 +1,11 @@ TEST Title Beware the mischievous terminator character! Please verify via a hex editor that the file ends in 0D0A. TAG_HIT_DICE Hit Dice This line gives the creature's number and type of Hit Dice (the die rolled to generate hit points), and lists any bonus hit points. A parenthetical note gives the average hit points for a creature of the indicated number of Hit Dice. A creature's Hit Dice total is also treated as its level for determining how spells affect the creature, its rate of natural healing, and its maximum ranks in a skill. TAG_RADIAL_MENU_DISABLE_AOOS TAG_INGAME Disable Attacks of Opportunity Sometimes, you want to hold off on Attacks of Opportunity - for instance if you have an invisible Rogue whose sneak attack you wish to conserve for the right time. This will make your character skip their AoOs. +TAG_RADIAL_MENU_TWO_WEAPON_FIGHTING TAG_INGAME Two Weapon Fighting When wielding two weapons, it may not always be advantageous to fight with both. This toggle allows choosing between one and two weapon fighting styles. When fighting with one weapon, you do not incur penalties on attack rolls, even if you are holding a weapon in the opposite hand. This toggle also allows two weapon fighting with a double weapon (e.g. quarterstaff), and using a shield to bash as a second weapon. +TAG_RADIAL_MENU_LEFT_PRIMARY TAG_INGAME Left Handed Primary Attacks When wielding two weapons or a weapon with two ends, either can be designated as the primary weapon in a given round. Doing so swaps which weapon is used for the primary and off hand attacks in the round, including relevant bonuses if two weapon fighting. This option needn't be used in conjunction with two weapon fighting. You can make attacks exclusively with your left hand in a round, incurring no penalties to hit. This includes using the 'left' end of a double weapon, and shield bashing. TAG_CRAFT_WAND TAG_FEATS_DES TAG_ITEM_CREATION Craft Wand You can create wands, which hold spells. Prerequisite: ~Caster Level~[TAG_CASTER_LEVEL] 5th Benefit: You can create a wand of any 4th-level or lower spell that you know. You may also create a few special wands of higher level spells. The base price of a wand is: its caster level x the spell level x 750 gp To craft a wand, you must spend 1/25 of this base price in ~XP~[TAG_EXPERIENCE_AND_LEVELS] and use up raw materials costing one-half of this base price. A newly created wand has 50 charges. Any wand that stores a spell with a costly material component or an XP cost also carries a commensurate cost. In addition to the cost derived from the base price, you must expend fifty copies of the material component or pay fifty times the XP cost. TAG_DIVINE_MIGHT TAG_FEATS_DES Divine Might As a ~free action~[TAG_FREE_ACTION], spend one of your ~turn or rebuke undead~[TAG_TURN] attempts to add your ~Charisma~[TAG_CHARISMA] bonus to your weapon damage for 1 full ~round~[TAG_COMBAT]. +TAG_IMPROVED_SHIELD_BASH TAG_FEATS_DES TAG_CLASS_FEATURES_FIGHTER_BONUS_FEATS Improved Shield Bash You can bash with a shield while retaining its bonus to ~armor class~[TAG_ARMOR_CLASS] Prerequisites: ~Shield Proficiency~[TAG_SHIELD_PROF] Benefit: you may perform ~shield bash~[TAG_SHIELD_BASH] attacks while retaining the shield's contribution to your ~armor class~[TAG_ARMOR_CLASS] Normal: performing a ~shield bash~[TAG_SHIELD_BASH] causes the shield's contribution to your ~armor class~[TAG_ARMOR_CLASS] to be omitted until your next turn Special: ~fighters~[TAG_FIGHTERS] may select this feat as one of their ~fighter bonus feats~[TAG_CLASS_FEATURES_FIGHTER_BONUS_FEATS] TAG_HEZROU_STENCH Hezrou Stench A Hezrou's skin produces a foul-smelling, toxic liquid whenever it fights. Any living creature (except other demons) within 10 feet must succeed on a DC 24 Fortitude save or be nauseated for as long as it remains within the affected area and for 1d4 rounds afterward. Creatures that successfully save are sickened for as long as they remain in the area. A creature that successfully saves cannot be affected again by the same Hezrou's stench for 24 hours. A delay poison or neutralize poison spell removes either condition from one creature. Creatures that have immunity to poison are unaffected, and creatures resistant to poison receive their normal bonus on their saving throws. The save DC is Constitution-based. TAG_CLASS_FEATURES_MONK_QUIVERING_PALM TAG_MONKS Quivering Palm Starting at 15th level, a monk can set up vibrations within the body of another creature that can thereafter be fatal if the monk so desires. She can use this quivering palm attack once a week, and she must announce her intent before making her ~attack roll~[TAG_ATTACK_ROLL]. Constructs, oozes, plants, undead, incorporeal creatures, and creatures immune to ~critical hits~[TAG_CRITICAL_HIT] cannot be affected. Otherwise, if the monk strikes successfully and the target takes damage from the blow, the quivering palm attack succeeds. Thereafter the monk can try to slay the victim at any later time, as long as the attempt is made within a number of days equal to her monk level. To make such an attempt, the monk merely wills the target to die (a ~free action~[TAG_FREE_ACTION]), and unless the target makes a ~Fortitude~[TAG_FORTITUDE] ~saving throw~[TAG_SAVING_THROW_DESC] (DC 10 + 1/2 the monk's level + the monk's ~Wis~[TAG_WISDOM] modifier), it dies. If the saving throw is successful, the target is no longer in danger from that particular quivering palm attack, but it may still be affected by another one at a later time. TAG_CLASS_FEATURES_MONK_EMPTY_BODY TAG_MONKS Empty Body At 19th level, a monk gains the ability to assume an ethereal state for 1 round per monk level per day. She may go ethereal on a number of different occasions during any single day, as long as the total number of rounds spent in an ethereal state does not exceed her monk level. @@ -18,6 +21,7 @@ TAG_GORE TAG_WEAPONS Gore Attack The creature spears the opponent with an antl TAG_SLAM TAG_WEAPONS Slam Attack The creature batters opponents with an appendage, dealing bludgeoning damage. TAG_STING TAG_WEAPONS Sting Attack The creature stabs with a stinger, dealing piercing damage. Sting attacks usually deal damage from poison in addition to hit point damage. TAG_TENTACLE TAG_WEAPONS Tentacle Attack The creature flails at opponents with a powerful tentacle, dealing bludgeoning (and sometimes slashing) damage. +TAG_SHIELD_BASH TAG_SPECIAL_ATTACKS Shield Bash When wearing a light or heavy shield, you may use it to bludgeon an opponent. Light shields of the appropriate size are treated as light martial weapons, while heavy shields count as one handed martial weapons. Shield bash attacks may either be used as part of ~two weapon fighting~[TAG_TWO_WEAPONS], or by designating the shield hand as the ~primary attack~[TAG_RADIAL_MENU_LEFT_PRIMARY]. Making a shield bash attack forfeits the shield's ~armor class~[TAG_ARMOR_CLASS] bonus for the round unless the attacker has the ~Improved Shield Bash~[TAG_IMPROVED_SHIELD_BASH] feat. Medium sized light shields deal 1d3 bludgeoning damage; small sized shields deal 1d2. Medium sized heavy shields deal 1d4 bludgeoning damage; small sized shields deal 1d3. In game, large shields are heavy for medium creatures, while small shields are light. Bucklers and tower shields cannot be used to shield bash. TAG_CLASS_FEATURES_BARD_BARDIC_MUSIC TAG_BARDS Bardic Music Once per day per bard level, a bard can use her song or poetics to produce magical effects on those around her (including herself, if desired). While these abilities fall under the category of bardic music and include descriptions of singing or playing instruments, they can all also be activated by reciting poetry, chanting, singing lyrics or melodies, etc. Each ability requires both a minimum bard level and a minimum number of ranks in the ~Perform~[TAG_PERFORM] skill to qualify. Starting a bardic music effect is a ~standard action~[TAG_STANDARD_ACTION]. Some bardic music abilities require concentration, which means the bard must take a standard action each round to maintain the ability. While using bardic music, a bard can fight but cannot cast spells, activate ~magic items~[TAG_MAGIC_ITEMS] by spell completion (such as scrolls), or activate magic items by magic word (such as wands). All are mind-affecting abilities. See ~Countersong~[TAG_CLASS_FEATURES_BARD_COUNTERSONG], ~Fascinate~[TAG_CLASS_FEATURES_BARD_FASCINATE], ~Inspire Courage~[TAG_CLASS_FEATURES_BARD_INSPIRE_COURAGE], ~Inspire Competence~[TAG_CLASS_FEATURES_BARD_INSPIRE_COMPETENCE], ~Suggestion~[TAG_CLASS_FEATURES_BARD_SUGGESTION], ~Inspire Greatness~[TAG_CLASS_FEATURES_BARD_INSPIRE_GREATNESS], ~Song of Freedom~[TAG_CLASS_FEATURES_BARD_SONG_OF_FREEDOM], ~Inspire Heroics~[TAG_CLASS_FEATURES_BARD_INSPIRE_HEROICS] and ~Mass Suggestion~[TAG_CLASS_FEATURES_BARD_SUGGESTION_MASS] for descriptions of the different types of bardic musical abilities. TAG_CLASS_FEATURES_BARD_SUGGESTION TAG_BARDS Suggestion A bard of 6th level or higher with 9 or more ranks in a ~Perform~[TAG_PERFORM] skill can make a suggestion to a creature that he has already ~fascinated~[TAG_CLASS_FEATURES_BARD_FASCINATE]. In ToEE, the suggestion will instill fear in the target, causing it to flee. Using this ability does not break the bard's concentration on the fascinate effect, nor does it allow a second ~saving throw~[TAG_SAVING_THROW_DESC] against the fascinate effect. Making a suggestion doesn't count against a bard's daily limit on bardic music performances. A Will saving throw (DC 10 + 1/2 bard's level + bard's Cha modifier) negates the effect. This ability affects only a single creature (until the Bard gains the ~Mass Suggestion~[TAG_CLASS_FEATURES_BARD_SUGGESTION_MASS] ability). Suggestion is an enchantment (compulsion), mind-affecting, language dependent ability. TAG_CLASS_FEATURES_BARD_SUGGESTION_MASS TAG_BARDS Mass Suggestion This ability functions like ~suggestion~[TAG_CLASS_FEATURES_BARD_SUGGESTION], except that a bard of 18th level or higher with 21 or more ranks in a ~Perform~[TAG_PERFORM] skill can make the suggestion simultaneously to any number of creatures that he has already ~fascinated~[TAG_CLASS_FEATURES_BARD_FASCINATE]. Mass suggestion is an enchantment (compulsion), mind-affecting, language-dependent ability. diff --git a/tpdatasrc/kotbfixes/tpmes/help_extensions.tab b/tpdatasrc/kotbfixes/tpmes/help_extensions.tab index 81b762a15..93a94c18c 100644 --- a/tpdatasrc/kotbfixes/tpmes/help_extensions.tab +++ b/tpdatasrc/kotbfixes/tpmes/help_extensions.tab @@ -1,8 +1,11 @@ TEST Title Beware the mischievous terminator character! Please verify via a hex editor that the file ends in 0D0A. TAG_HIT_DICE Hit Dice This line gives the creature's number and type of Hit Dice (the die rolled to generate hit points), and lists any bonus hit points. A parenthetical note gives the average hit points for a creature of the indicated number of Hit Dice. A creature's Hit Dice total is also treated as its level for determining how spells affect the creature, its rate of natural healing, and its maximum ranks in a skill. TAG_RADIAL_MENU_DISABLE_AOOS TAG_INGAME Disable Attacks of Opportunity Sometimes, you want to hold off on Attacks of Opportunity - for instance if you have an invisible Rogue whose sneak attack you wish to conserve for the right time. This will make your character skip their AoOs. +TAG_RADIAL_MENU_TWO_WEAPON_FIGHTING TAG_INGAME Two Weapon Fighting When wielding two weapons, it may not always be advantageous to fight with both. This toggle allows choosing between one and two weapon fighting styles. When fighting with one weapon, you do not incur penalties on attack rolls, even if you are holding a weapon in the opposite hand. This toggle also allows two weapon fighting with a double weapon (e.g. quarterstaff), and using a shield to bash as a second weapon. +TAG_RADIAL_MENU_LEFT_PRIMARY TAG_INGAME Left Handed Primary Attacks When wielding two weapons or a weapon with two ends, either can be designated as the primary weapon in a given round. Doing so swaps which weapon is used for the primary and off hand attacks in the round, including relevant bonuses if two weapon fighting. This option needn't be used in conjunction with two weapon fighting. You can make attacks exclusively with your left hand in a round, incurring no penalties to hit. This includes using the 'left' end of a double weapon, and shield bashing. TAG_CRAFT_WAND TAG_FEATS_DES TAG_ITEM_CREATION Craft Wand You can create wands, which hold spells. Prerequisite: ~Caster Level~[TAG_CASTER_LEVEL] 5th Benefit: You can create a wand of any 4th-level or lower spell that you know. You may also create a few special wands of higher level spells. The base price of a wand is: its caster level x the spell level x 750 gp To craft a wand, you must spend 1/25 of this base price in ~XP~[TAG_EXPERIENCE_AND_LEVELS] and use up raw materials costing one-half of this base price. A newly created wand has 50 charges. Any wand that stores a spell with a costly material component or an XP cost also carries a commensurate cost. In addition to the cost derived from the base price, you must expend fifty copies of the material component or pay fifty times the XP cost. TAG_DIVINE_MIGHT TAG_FEATS_DES Divine Might As a ~free action~[TAG_FREE_ACTION], spend one of your ~turn or rebuke undead~[TAG_TURN] attempts to add your ~Charisma~[TAG_CHARISMA] bonus to your weapon damage for 1 full ~round~[TAG_COMBAT]. +TAG_IMPROVED_SHIELD_BASH TAG_FEATS_DES TAG_CLASS_FEATURES_FIGHTER_BONUS_FEATS Improved Shield Bash You can bash with a shield while retaining its bonus to ~armor class~[TAG_ARMOR_CLASS] Prerequisites: ~Shield Proficiency~[TAG_SHIELD_PROF] Benefit: you may perform ~shield bash~[TAG_SHIELD_BASH] attacks while retaining the shield's contribution to your ~armor class~[TAG_ARMOR_CLASS] Normal: performing a ~shield bash~[TAG_SHIELD_BASH] causes the shield's contribution to your ~armor class~[TAG_ARMOR_CLASS] to be omitted until your next turn Special: ~fighters~[TAG_FIGHTERS] may select this feat as one of their ~fighter bonus feats~[TAG_CLASS_FEATURES_FIGHTER_BONUS_FEATS] TAG_HEZROU_STENCH Hezrou Stench A Hezrou's skin produces a foul-smelling, toxic liquid whenever it fights. Any living creature (except other demons) within 10 feet must succeed on a DC 24 Fortitude save or be nauseated for as long as it remains within the affected area and for 1d4 rounds afterward. Creatures that successfully save are sickened for as long as they remain in the area. A creature that successfully saves cannot be affected again by the same Hezrou's stench for 24 hours. A delay poison or neutralize poison spell removes either condition from one creature. Creatures that have immunity to poison are unaffected, and creatures resistant to poison receive their normal bonus on their saving throws. The save DC is Constitution-based. TAG_CLASS_FEATURES_MONK_QUIVERING_PALM TAG_MONKS Quivering Palm Starting at 15th level, a monk can set up vibrations within the body of another creature that can thereafter be fatal if the monk so desires. She can use this quivering palm attack once a week, and she must announce her intent before making her ~attack roll~[TAG_ATTACK_ROLL]. Constructs, oozes, plants, undead, incorporeal creatures, and creatures immune to ~critical hits~[TAG_CRITICAL_HIT] cannot be affected. Otherwise, if the monk strikes successfully and the target takes damage from the blow, the quivering palm attack succeeds. Thereafter the monk can try to slay the victim at any later time, as long as the attempt is made within a number of days equal to her monk level. To make such an attempt, the monk merely wills the target to die (a ~free action~[TAG_FREE_ACTION]), and unless the target makes a ~Fortitude~[TAG_FORTITUDE] ~saving throw~[TAG_SAVING_THROW_DESC] (DC 10 + 1/2 the monk's level + the monk's ~Wis~[TAG_WISDOM] modifier), it dies. If the saving throw is successful, the target is no longer in danger from that particular quivering palm attack, but it may still be affected by another one at a later time. TAG_CLASS_FEATURES_MONK_EMPTY_BODY TAG_MONKS Empty Body At 19th level, a monk gains the ability to assume an ethereal state for 1 round per monk level per day. She may go ethereal on a number of different occasions during any single day, as long as the total number of rounds spent in an ethereal state does not exceed her monk level. @@ -18,6 +21,7 @@ TAG_GORE TAG_WEAPONS Gore Attack The creature spears the opponent with an antl TAG_SLAM TAG_WEAPONS Slam Attack The creature batters opponents with an appendage, dealing bludgeoning damage. TAG_STING TAG_WEAPONS Sting Attack The creature stabs with a stinger, dealing piercing damage. Sting attacks usually deal damage from poison in addition to hit point damage. TAG_TENTACLE TAG_WEAPONS Tentacle Attack The creature flails at opponents with a powerful tentacle, dealing bludgeoning (and sometimes slashing) damage. +TAG_SHIELD_BASH TAG_SPECIAL_ATTACKS Shield Bash When wearing a light or heavy shield, you may use it to bludgeon an opponent. Light shields of the appropriate size are treated as light martial weapons, while heavy shields count as one handed martial weapons. Shield bash attacks may either be used as part of ~two weapon fighting~[TAG_TWO_WEAPONS], or by designating the shield hand as the ~primary attack~[TAG_RADIAL_MENU_LEFT_PRIMARY]. Making a shield bash attack forfeits the shield's ~armor class~[TAG_ARMOR_CLASS] bonus for the round unless the attacker has the ~Improved Shield Bash~[TAG_IMPROVED_SHIELD_BASH] feat. Medium sized light shields deal 1d3 bludgeoning damage; small sized shields deal 1d2. Medium sized heavy shields deal 1d4 bludgeoning damage; small sized shields deal 1d3. In game, large shields are heavy for medium creatures, while small shields are light. Bucklers and tower shields cannot be used to shield bash. TAG_CLASS_FEATURES_BARD_BARDIC_MUSIC TAG_BARDS Bardic Music Once per day per bard level, a bard can use her song or poetics to produce magical effects on those around her (including herself, if desired). While these abilities fall under the category of bardic music and include descriptions of singing or playing instruments, they can all also be activated by reciting poetry, chanting, singing lyrics or melodies, etc. Each ability requires both a minimum bard level and a minimum number of ranks in the ~Perform~[TAG_PERFORM] skill to qualify. Starting a bardic music effect is a ~standard action~[TAG_STANDARD_ACTION]. Some bardic music abilities require concentration, which means the bard must take a standard action each round to maintain the ability. While using bardic music, a bard can fight but cannot cast spells, activate ~magic items~[TAG_MAGIC_ITEMS] by spell completion (such as scrolls), or activate magic items by magic word (such as wands). All are mind-affecting abilities. See ~Countersong~[TAG_CLASS_FEATURES_BARD_COUNTERSONG], ~Fascinate~[TAG_CLASS_FEATURES_BARD_FASCINATE], ~Inspire Courage~[TAG_CLASS_FEATURES_BARD_INSPIRE_COURAGE], ~Inspire Competence~[TAG_CLASS_FEATURES_BARD_INSPIRE_COMPETENCE], ~Suggestion~[TAG_CLASS_FEATURES_BARD_SUGGESTION], ~Inspire Greatness~[TAG_CLASS_FEATURES_BARD_INSPIRE_GREATNESS], ~Song of Freedom~[TAG_CLASS_FEATURES_BARD_SONG_OF_FREEDOM], ~Inspire Heroics~[TAG_CLASS_FEATURES_BARD_INSPIRE_HEROICS] and ~Mass Suggestion~[TAG_CLASS_FEATURES_BARD_SUGGESTION_MASS] for descriptions of the different types of bardic musical abilities. TAG_CLASS_FEATURES_BARD_SUGGESTION TAG_BARDS Suggestion A bard of 6th level or higher with 9 or more ranks in a ~Perform~[TAG_PERFORM] skill can make a suggestion to a creature that he has already ~fascinated~[TAG_CLASS_FEATURES_BARD_FASCINATE]. In ToEE, the suggestion will instill fear in the target, causing it to flee. Using this ability does not break the bard's concentration on the fascinate effect, nor does it allow a second ~saving throw~[TAG_SAVING_THROW_DESC] against the fascinate effect. Making a suggestion doesn't count against a bard's daily limit on bardic music performances. A Will saving throw (DC 10 + 1/2 bard's level + bard's Cha modifier) negates the effect. This ability affects only a single creature (until the Bard gains the ~Mass Suggestion~[TAG_CLASS_FEATURES_BARD_SUGGESTION_MASS] ability). Suggestion is an enchantment (compulsion), mind-affecting, language dependent ability. TAG_CLASS_FEATURES_BARD_SUGGESTION_MASS TAG_BARDS Mass Suggestion This ability functions like ~suggestion~[TAG_CLASS_FEATURES_BARD_SUGGESTION], except that a bard of 18th level or higher with 21 or more ranks in a ~Perform~[TAG_PERFORM] skill can make the suggestion simultaneously to any number of creatures that he has already ~fascinated~[TAG_CLASS_FEATURES_BARD_FASCINATE]. Mass suggestion is an enchantment (compulsion), mind-affecting, language-dependent ability. diff --git a/tpdatasrc/tpgamefiles/rules/d20_combat/common.py b/tpdatasrc/tpgamefiles/rules/d20_combat/common.py new file mode 100644 index 000000000..a9de6bfac --- /dev/null +++ b/tpdatasrc/tpgamefiles/rules/d20_combat/common.py @@ -0,0 +1,61 @@ +from toee import * + +# Gets the weapon in the left hand. +# This can be one of the following: +# +# 1. A weapon in the 'secondary' slot +# 2. A shield that allows bashing +# 3. A double wepaon in the 'primary' slot +def getLeftWeapon(attacker): + left = attacker.item_worn_at(item_wear_weapon_secondary) + + if left == OBJ_HANDLE_NULL and attacker.d20_query(128): + left = attacker.item_worn_at(item_wear_shield) + + if left == OBJ_HANDLE_NULL: + right = attacker.item_worn_at(item_wear_weapon_primary) + if right.is_double_weapon(): + left = right + + return left + +# Gets the actual primary weapon, respecting the ability to select +# either of two weapons held to be designated as primary +def getPrimaryWeapon(attacker): + weapl = getLeftWeapon(attacker) + + # if we're preferring left weapon, and we have something equipped + # there, use it. + if attacker.d20_query(127): + if not weapl == OBJ_HANDLE_NULL: + return weapl + + # otherwise default to the right hand + return attacker.item_worn_at(item_wear_weapon_primary) + +# Gets the secondary weapon, respecting the ability to select either +# of two weapons held to be designated as primary +def getSecondaryWeapon(attacker): + weapr = attacker.item_worn_at(item_wear_weapon_primary) + weapl = getLeftWeapon(attacker) + + # if preferring left weapon, and there actually is a left weapon, + # then right hand is secondary. + if attacker.d20_query(127): + if not weapl == OBJ_HANDLE_NULL: + return weapr + + # otherwise left hand is secondary + return weapl + +def getUsedWeapon(flags, attacker): + print "getUsedWeapon" + unarmed = OBJ_HANDLE_NULL + #if flags & D20CAF_TOUCH_ATTACK: + # return unarmed # this fucks up thrown grenades + if flags & D20CAF_SECONDARY_WEAPON: + return getSecondaryWeapon(attacker) + else: + return getPrimaryWeapon(attacker) + return unarmed + diff --git a/tpdatasrc/tpgamefiles/rules/d20_combat/damage_critter.py b/tpdatasrc/tpgamefiles/rules/d20_combat/damage_critter.py index bbe4113b9..e6509f090 100644 --- a/tpdatasrc/tpgamefiles/rules/d20_combat/damage_critter.py +++ b/tpdatasrc/tpgamefiles/rules/d20_combat/damage_critter.py @@ -1,6 +1,7 @@ import tpdp import roll_history import logbook +from common import * debug_enabled = True @@ -30,20 +31,6 @@ def floatFriendlyFire(attacker, tgt): # debug_print("Debug: Friendly Fire NOT triggered", "Debug attacker.allegiance_shared(tgt): {}".format(attacker.allegiance_shared(tgt)), "Debug tgt in game.party: {}".format(tgt in game.party), "Debug attacker in game.party: {}".format(attacker in game.party)) return -def getUsedWeapon(flags, attacker): - unarmed = OBJ_HANDLE_NULL - #if flags & D20CAF_TOUCH_ATTACK: - # return unarmed # this fucks up thrown grenades - if flags & D20CAF_SECONDARY_WEAPON: - offhandItem = attacker.item_worn_at(item_wear_weapon_secondary) - if offhandItem != OBJ_HANDLE_NULL and offhandItem.type == obj_t_weapon: - return offhandItem - else: - mainhandItem = attacker.item_worn_at(item_wear_weapon_primary) - if mainhandItem != OBJ_HANDLE_NULL and mainhandItem.type == obj_t_weapon: - return mainhandItem - return unarmed - def playSoundEffect(weaponUsed, attacker, tgt, sound_event): sound_id = attacker.soundmap_item(weaponUsed, tgt, sound_event) game.sound_local_obj(sound_id, attacker) diff --git a/tpdatasrc/tpgamefiles/rules/d20_combat/to_hit_processing.py b/tpdatasrc/tpgamefiles/rules/d20_combat/to_hit_processing.py index 1b8bb3517..69f9f0dfa 100644 --- a/tpdatasrc/tpgamefiles/rules/d20_combat/to_hit_processing.py +++ b/tpdatasrc/tpgamefiles/rules/d20_combat/to_hit_processing.py @@ -3,6 +3,7 @@ import tpdp import logbook import roll_history +from common import * debug_enabled = False @@ -191,31 +192,20 @@ def to_hit_processing(d20a): roll_id = roll_history.add_percent_chance_roll(performer, target, concealmentMissChance, 61, miss_chance_roll, 194, 193) d20a.roll_id_2 = roll_id + + flags = d20a.flags + #ToHitBonus Actions debug_print("To Hit") to_hit_eo = tpdp.EventObjAttack() - to_hit_eo.attack_packet.set_flags(d20a.flags) + to_hit_eo.attack_packet.set_flags(flags) to_hit_eo.attack_packet.target = target to_hit_eo.attack_packet.action_type = d20a.action_type #dispIoToHitBon.attackPacket.d20ActnType = d20a.action_type to_hit_eo.attack_packet.attacker = performer to_hit_eo.attack_packet.event_key = d20Data #dispIoToHitBon.attackPacket.dispKey = d20Data - unarmed = OBJ_HANDLE_NULL - if to_hit_eo.attack_packet.get_flags() & D20CAF_TOUCH_ATTACK: - to_hit_eo.attack_packet.set_weapon_used(unarmed) - elif to_hit_eo.attack_packet.get_flags() & D20CAF_SECONDARY_WEAPON: - offhandItem = performer.item_worn_at(item_wear_weapon_secondary) - if offhandItem == OBJ_HANDLE_NULL or offhandItem.type != obj_t_weapon: - to_hit_eo.attack_packet.set_weapon_used(unarmed) - else: - to_hit_eo.attack_packet.set_weapon_used(offhandItem) - else: - mainhandItem = performer.item_worn_at(item_wear_weapon_primary) - if mainhandItem == OBJ_HANDLE_NULL or mainhandItem.type != obj_t_weapon: - to_hit_eo.attack_packet.set_weapon_used(unarmed) - else: - to_hit_eo.attack_packet.set_weapon_used(mainhandItem) + to_hit_eo.attack_packet.set_weapon_used(getUsedWeapon(flags, performer)) + to_hit_eo.attack_packet.ammo_item = performer.get_ammo_used() - flags = to_hit_eo.attack_packet.get_flags() flags |= D20CAF_FINAL_ATTACK_ROLL to_hit_eo.attack_packet.set_flags(flags) to_hit_eo.dispatch(performer, OBJ_HANDLE_NULL, ET_OnGetBucklerAcPenalty , EK_NONE)