diff --git a/Configurator/HouseRulesWindow.xaml b/Configurator/HouseRulesWindow.xaml
index 149132d67..dff5710e4 100644
--- a/Configurator/HouseRulesWindow.xaml
+++ b/Configurator/HouseRulesWindow.xaml
@@ -23,7 +23,7 @@
-
+
diff --git a/TemplePlus/condition.cpp b/TemplePlus/condition.cpp
index 4bb76fbde..268806fad 100644
--- a/TemplePlus/condition.cpp
+++ b/TemplePlus/condition.cpp
@@ -164,6 +164,8 @@ class ItemCallbacks
static int __cdecl SkillBonus(DispatcherCallbackArgs args);
static int __cdecl UseableItemRadialEntry(DispatcherCallbackArgs args);
+ static int __cdecl UseableItemActionCheck(DispatcherCallbackArgs args);
+
static int __cdecl BucklerToHitPenalty(DispatcherCallbackArgs args);
static int __cdecl WeaponMerciful(DispatcherCallbackArgs);
static int __cdecl WeaponSeekingAttackerConcealmentMissChance(DispatcherCallbackArgs args);
@@ -324,6 +326,8 @@ class ConditionFunctionReplacement : public TempleFix {
// power attack damage bonus
replaceFunction(0x100F8540, genericCallbacks.PowerAttackDamageBonus);
+ // Use Item ActionCheck & Radial
+ replaceFunction(0x10100B60, itemCallbacks.UseableItemActionCheck);
replaceFunction(0x10100840, itemCallbacks.UseableItemRadialEntry);
// couraged aura
@@ -336,6 +340,7 @@ class ConditionFunctionReplacement : public TempleFix {
replaceFunction(0x10104DA0, itemCallbacks.BucklerToHitPenalty);
+
// Druid wild shape
replaceFunction(0x100FBDB0, classAbilityCallbacks.DruidWildShapeReset);
replaceFunction(0x100FBB20, classAbilityCallbacks.DruidWildShapeRadialMenu);
@@ -3450,9 +3455,28 @@ int ItemCallbacks::UseableItemRadialEntry(DispatcherCallbackArgs args){
auto spData = itemObj->GetSpell(obj_f_item_spell_idx, spIdx);
- if ( (objType == obj_t_scroll || (itemFlags & OIF_NEEDS_SPELL) && (itemObj->type == obj_t_generic || itemObj->type == obj_t_weapon) )
- && !useMagicDeviceSkillBase && !critterSys.HashMatchingClassForSpell(args.objHndCaller, spData.spellEnum))
- return 0;
+ auto handle = args.objHndCaller;
+ auto obj = objSystem->GetObject(handle);
+
+
+
+ if ( objType == obj_t_scroll || (itemFlags & OIF_NEEDS_SPELL) && (itemObj->type == obj_t_generic || itemObj->type == obj_t_weapon) ){
+ auto isOk = false;
+
+ if (useMagicDeviceSkillBase || critterSys.HashMatchingClassForSpell(args.objHndCaller, spData.spellEnum))
+ isOk = true;
+
+ // clerics with magic domain
+ else if (spellSys.IsArcaneSpellClass(spData.classCode)) {
+ auto clrLvl = objects.StatLevelGet(handle, stat_level_cleric);
+ if (clrLvl > 0 && max(1, clrLvl / 2) >= (int)spData.spellLevel && critterSys.HasDomain(handle, Domain_Magic))
+ isOk = true;
+ }
+
+ if (!isOk)
+ return 0;
+
+ }
if (objType == obj_t_scroll && !spellSys.CheckAbilityScoreReqForSpell(args.objHndCaller, spData.spellEnum, -1) && !useMagicDeviceSkillBase)
return 0;
@@ -3504,6 +3528,72 @@ int ItemCallbacks::UseableItemRadialEntry(DispatcherCallbackArgs args){
return 0;
}
+int ItemCallbacks::UseableItemActionCheck(DispatcherCallbackArgs args){
+ GET_DISPIO(dispIOTypeD20ActionTurnBased, DispIoD20ActionTurnBased);
+
+ auto invIdx = args.GetCondArg(2);
+
+ // check if this is the referenced item
+ if (dispIo->d20a->data1 != invIdx)
+ return 0;
+
+
+ auto itemHandle = inventory.GetItemAtInvIdx(args.objHndCaller, invIdx);
+ auto itemObj = gameSystems->GetObj().GetObject(itemHandle);
+ auto objType = itemObj->type;
+ int useMagicDeviceSkillBase = critterSys.SkillBaseGet(args.objHndCaller, skill_use_magic_device);
+
+ // ensure is identified
+ if (objType != obj_t_food && !inventory.IsIdentified(itemHandle)){
+ dispIo->returnVal = AEC_INVALID_ACTION;
+ return IEC_Cannot_Wield_Magical;
+ }
+
+ // check item charges
+ auto charges = itemObj->GetInt32(obj_f_item_spell_charges_idx);
+ if (charges == 0){
+ dispIo->returnVal = AEC_OUT_OF_CHARGES;
+ return IEC_Cannot_Wield_Magical;
+ }
+
+
+ // check if caster needs and has spell/class
+ auto itemFlags = itemObj->GetItemFlags();
+
+ auto spIdx = args.GetCondArg(0);
+ auto spData = itemObj->GetSpell(obj_f_item_spell_idx, spIdx);
+
+ auto handle = args.objHndCaller;
+ auto obj = objSystem->GetObject(handle);
+
+ if (objType == obj_t_scroll || (itemFlags & OIF_NEEDS_SPELL) && (itemObj->type == obj_t_generic || itemObj->type == obj_t_weapon)) {
+ auto isOk = false;
+
+
+ if (useMagicDeviceSkillBase || critterSys.HashMatchingClassForSpell(args.objHndCaller, spData.spellEnum))
+ isOk = true;
+
+ // clerics with magic domain
+ else if (spellSys.IsArcaneSpellClass(spData.classCode)) {
+ auto clrLvl = objects.StatLevelGet(handle, stat_level_cleric);
+ if (clrLvl > 0 && max(1, clrLvl / 2) >= (int)spData.spellLevel && critterSys.HasDomain(handle, Domain_Magic))
+ isOk = true;
+ }
+
+ if (!isOk){
+ dispIo->returnVal = AEC_INVALID_ACTION;
+ return IEC_Cannot_Wield_Magical;
+ }
+ }
+
+ if (objType == obj_t_scroll && !spellSys.CheckAbilityScoreReqForSpell(args.objHndCaller, spData.spellEnum, -1) && !useMagicDeviceSkillBase){
+ dispIo->returnVal = AEC_INVALID_ACTION;
+ return IEC_Cannot_Wield_Magical;
+ }
+
+ return 0;
+}
+
int ItemCallbacks::BucklerToHitPenalty(DispatcherCallbackArgs args)
{
auto invIdx = args.GetCondArg(2);
diff --git a/TemplePlus/critter.cpp b/TemplePlus/critter.cpp
index fe2ed1743..a9def3c65 100644
--- a/TemplePlus/critter.cpp
+++ b/TemplePlus/critter.cpp
@@ -1275,7 +1275,34 @@ int LegacyCritterSystem::PlayCritterVoiceLine(objHndl obj, objHndl fellow, char*
bool LegacyCritterSystem::HashMatchingClassForSpell(objHndl handle, uint32_t spellEnum) const
{
- return temple::GetRef(0x10075DA0)(handle, spellEnum) == TRUE;
+ //return temple::GetRef(0x10075DA0)(handle, spellEnum) == TRUE;
+ SpellEntry spEntry(spellEnum);
+ for (auto i=0u; iGetObject(handle);
+
+ // if is Cleric or NPC and the spell spec is Domain Special
+ if (objects.StatLevelGet(handle, stat_level_cleric) > 0
+ || (obj->IsNPC() && lvlSpec.classCode == Domain_Special)){
+
+ if (objects.StatLevelGet(handle, stat_domain_1) == lvlSpec.classCode
+ || objects.StatLevelGet(handle, stat_domain_2) == lvlSpec.classCode)
+ return true;
+ }
+
+ }
+ // normal spell
+ else{
+ if (objects.StatLevelGet(handle, spellSys.GetCastingClass(lvlSpec.classCode)) > 0)
+ return true;
+ }
+ }
+
+ return false;
}
int LegacyCritterSystem::GetArmorClass(objHndl obj, DispIoAttackBonus* dispIo){
@@ -1318,6 +1345,13 @@ int LegacyCritterSystem::DomainSpellNumByField(objHndl obj, obj_f field)
return numSpells;
}
+bool LegacyCritterSystem::HasDomain(objHndl handle, uint32_t domainType){
+ if (objects.StatLevelGet(handle, stat_domain_1) == domainType
+ || objects.StatLevelGet(handle, stat_domain_2) == domainType)
+ return true;
+ return false;
+}
+
int LegacyCritterSystem::GetNumFollowers(objHndl obj, int excludeForcedFollowers)
{
auto objBod = objSystem->GetObject(obj);
diff --git a/TemplePlus/critter.h b/TemplePlus/critter.h
index c176cabd4..72981166c 100644
--- a/TemplePlus/critter.h
+++ b/TemplePlus/critter.h
@@ -331,8 +331,9 @@ struct LegacyCritterSystem : temple::AddressTable
int GetSpellListLevelExtension(objHndl handle, Stat classCode); // modifies the effective character level for the purpose of fetching spell lists
bool IsCaster(objHndl obj);
static int SpellNumByFieldAndClass(objHndl obj, obj_f field, uint32_t spellClassCode);
- bool HashMatchingClassForSpell(objHndl handle, uint32_t spellEnum) const; // checks if obj has a matching spell in their list
+ bool HashMatchingClassForSpell(objHndl handle, uint32_t spellEnum) const; // checks if obj has a matching spell in their list (does not regard level)
int DomainSpellNumByField(objHndl obj, obj_f field);
+ bool HasDomain(objHndl handle, uint32_t domainType);
#pragma endregion
bool IsWarded(objHndl obj); // checks if creature is warded from melee attacks (by stuff like Meld Into Stone, Tree Shape, Otiluke's Resislient Sphere)
diff --git a/TemplePlus/d20.cpp b/TemplePlus/d20.cpp
index 59cbd8a1e..64e3c4187 100644
--- a/TemplePlus/d20.cpp
+++ b/TemplePlus/d20.cpp
@@ -2046,6 +2046,8 @@ ActionErrorCode D20ActionCallbacks::PerformCastItemSpell(D20Actn* d20a){
if (d20a->d20ActType == D20A_ACTIVATE_DEVICE_SPELL || !(item))
return AEC_OK;
+ auto caster = d20a->d20APerformer;
+
auto useMagicDeviceBase = critterSys.SkillBaseGet(d20a->d20APerformer, SkillEnum::skill_use_magic_device);
int resultDeltaFromDc;
if (!inventory.IsIdentified(item) && itemObj->type != obj_t_food){ // blind use of magic item
@@ -2063,31 +2065,48 @@ ActionErrorCode D20ActionCallbacks::PerformCastItemSpell(D20Actn* d20a){
}
-
- if ( (itemObj->type == obj_t_scroll || (itemFlags & OIF_NEEDS_SPELL ) && (itemObj->type == obj_t_generic || itemObj->type == obj_t_weapon))
- && !critterSys.HashMatchingClassForSpell(d20a->d20APerformer, spellEnum) ){
- if (!useMagicDeviceBase)
- return AEC_CANNOT_CAST_SPELLS;
-
- if (itemObj->type == obj_t_scroll){
-
- auto umdRoll = skillSys.SkillRoll(d20a->d20APerformer, SkillEnum::skill_use_magic_device, 20, &resultDeltaFromDc, 1);
- if (!umdRoll){
- skillSys.FloatError(d20a->d20APerformer, 7); // Use Scroll Failed
- if (*actSeqSys.actSeqCur){
- (*actSeqSys.actSeqCur)->spellPktBody.Reset();
- }
+ // check if item requires knowing the spell
+ if (itemObj->type == obj_t_scroll || (itemFlags & OIF_NEEDS_SPELL ) && (itemObj->type == obj_t_generic || itemObj->type == obj_t_weapon)){
+
+ auto isOk = false;
+
+ if (critterSys.HashMatchingClassForSpell(d20a->d20APerformer, spellEnum)){
+ isOk = true;
+ }
+ else if (spellSys.IsArcaneSpellClass(spellClass) ){
+ auto clrLvl = objects.StatLevelGet(caster, stat_level_cleric);
+ if (clrLvl > 0 && critterSys.HasDomain(caster, Domain_Magic)
+ && spellLvl <= max(1,clrLvl/2) )
+ isOk = true;
+ }
+
+ if (!isOk){
+
+ // do Use Magic Device roll
+ if (!useMagicDeviceBase)
return AEC_CANNOT_CAST_SPELLS;
+
+ if (itemObj->type == obj_t_scroll) {
+
+ auto umdRoll = skillSys.SkillRoll(d20a->d20APerformer, SkillEnum::skill_use_magic_device, 20, &resultDeltaFromDc, 1);
+ if (!umdRoll) {
+ skillSys.FloatError(d20a->d20APerformer, 7); // Use Scroll Failed
+ if (*actSeqSys.actSeqCur) {
+ (*actSeqSys.actSeqCur)->spellPktBody.Reset();
+ }
+ return AEC_CANNOT_CAST_SPELLS;
+ }
}
- }
- else{
- auto umdRoll = skillSys.SkillRoll(d20a->d20APerformer, SkillEnum::skill_use_magic_device, 20, &resultDeltaFromDc, 1);
- if (!umdRoll) {
- skillSys.FloatError(d20a->d20APerformer, 8); // Use Wand Failed
- return AEC_CANNOT_CAST_SPELLS;
+ else {
+ auto umdRoll = skillSys.SkillRoll(d20a->d20APerformer, SkillEnum::skill_use_magic_device, 20, &resultDeltaFromDc, 1);
+ if (!umdRoll) {
+ skillSys.FloatError(d20a->d20APerformer, 8); // Use Wand Failed
+ return AEC_CANNOT_CAST_SPELLS;
+ }
}
}
}
+
if (itemObj->type == obj_t_scroll){
if (!spellSys.CheckAbilityScoreReqForSpell(d20a->d20APerformer, spellEnum, -1)) {
diff --git a/TemplePlus/feat.cpp b/TemplePlus/feat.cpp
index 083ff479b..4091f29dc 100644
--- a/TemplePlus/feat.cpp
+++ b/TemplePlus/feat.cpp
@@ -309,7 +309,8 @@ char* LegacyFeatSystem::GetFeatPrereqDescription(feat_enums feat)
if (feat >= FEAT_NONE
|| feat == FEAT_IMPROVED_DISARM
|| feat == FEAT_GREATER_WEAPON_SPECIALIZATION
- || feat == FEAT_IMPROVED_SUNDER)
+ || feat == FEAT_IMPROVED_SUNDER
+ || feat == FEAT_STUNNING_FIST)
mesHnd = &feats.featMesNew;
mesLineNone.key = 9998;
diff --git a/TemplePlus/ui/ui_char_editor.cpp b/TemplePlus/ui/ui_char_editor.cpp
index f9aa35884..a6bc2f290 100644
--- a/TemplePlus/ui/ui_char_editor.cpp
+++ b/TemplePlus/ui/ui_char_editor.cpp
@@ -1514,7 +1514,7 @@ BOOL UiCharEditor::ClassNextBtnMsg(int widId, TigMsg * msg){
}
if (_msg->widgetEventType == TigMsgWidgetEvent::Entered) {
- auto textboxText = fmt::format("Prestige Classes\n\n Currently supported:\n Dwarven Defender\n Duelist\n Eldritch Knight\n Mystic Theurge");
+ auto textboxText = fmt::format("Prestige Classes\n\n Currently supported:\n Arcane Trickster\n Dwarven Defender\n Duelist\n Eldritch Knight\n Mystic Theurge");
if (textboxText.size() >= 1024)
textboxText[1023] = 0;
strcpy(temple::GetRef(0x10C80CC0), &textboxText[0]);
diff --git a/tpdata/tpmes/feat.mes b/tpdata/tpmes/feat.mes
index 4c6a29a04..e729ac1d3 100644
--- a/tpdata/tpmes/feat.mes
+++ b/tpdata/tpmes/feat.mes
@@ -201,6 +201,7 @@
// prerequisites descriptions
{10137} Greater Weapon Specialization {Weapon Specialization + Greater Weapon Focus w/ selected weapon, fighter level 12th}
{10211} Improved Disarm {Int 13, Combat Expertise.}
+{10339} Stunning Fist {Dex 13, Wis 13, Improved Unarmed Strike, base attack bonus +8.}
{10340} Improved Sunder {Str 13, Power Attack.}
{10658} Diamond Body {}
{10659} Abundant Step {}