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 {}