diff --git a/TemplePlus/condition.cpp b/TemplePlus/condition.cpp index d536b8999..8335497e2 100644 --- a/TemplePlus/condition.cpp +++ b/TemplePlus/condition.cpp @@ -130,6 +130,16 @@ class GenericCallbacks } genericCallbacks; + +class ItemCallbacks +{ +public: + static int __cdecl SkillBonus(DispatcherCallbackArgs args); + + static int __cdecl UseableItemRadialEntry(DispatcherCallbackArgs args); +} itemCallbacks; + + class ConditionFunctionReplacement : public TempleFix { public: const char* name() override { @@ -205,18 +215,14 @@ class ConditionFunctionReplacement : public TempleFix { // power attack damage bonus replaceFunction(0x100F8540, genericCallbacks.PowerAttackDamageBonus); + + replaceFunction(0x10100840, itemCallbacks.UseableItemRadialEntry); } } condFuncReplacement; -class ItemCallbacks -{ -public: - static int __cdecl SkillBonus(DispatcherCallbackArgs args); -} itemCallbacks; - class ClassAbilityCallbacks { #define FeatFunc(fname) static int __cdecl Feat ## fname ## (DispatcherCallbackArgs args) @@ -1995,7 +2001,7 @@ int* ConditionSystem::CondNodeGetArgPtr(CondNode* condNode, int argIdx) radEntry.minArg = 0; radEntry.type = RadialMenuEntryType::Toggle; radEntry.actualArg = (int)conds.CondNodeGetArgPtr(args.subDispNode->condNode, 0); - radEntry.callback = (void (__cdecl*)(objHndl, RadialMenuEntry*))temple::GetPointer(0x100F0200); + radEntry.callback = (BOOL (__cdecl*)(objHndl, RadialMenuEntry*))temple::GetPointer(0x100F0200); MesLine mesLine; mesLine.key = 5105; //disable AoOs if (!mesFuncs.GetLine(*combatSys.combatMesfileIdx, &mesLine) ) @@ -2154,7 +2160,7 @@ int __cdecl RecklessOffenseRadialMenuInit(DispatcherCallbackArgs args) radEntry.minArg = 0; radEntry.type = RadialMenuEntryType::Toggle; radEntry.actualArg = (int)conds.CondNodeGetArgPtr(args.subDispNode->condNode, 0); - radEntry.callback = (void(__cdecl*)(objHndl, RadialMenuEntry*))temple::GetPointer(0x100F0200); + radEntry.callback = (BOOL(__cdecl*)(objHndl, RadialMenuEntry*))temple::GetPointer(0x100F0200); MesLine mesLine; mesLine.key = 5107; // reckless offense if (!mesFuncs.GetLine(*combatSys.combatMesfileIdx, &mesLine)) @@ -2994,6 +3000,80 @@ int ItemCallbacks::SkillBonus(DispatcherCallbackArgs args) return 0; } +int ItemCallbacks::UseableItemRadialEntry(DispatcherCallbackArgs args) +{ + auto invIdx = args.GetCondArg(2); + 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); + + if (objType != obj_t_food && !inventory.IsIdentified(itemHandle)) + return 0; + + auto charges = itemObj->GetInt32(obj_f_item_spell_charges_idx); + if (charges == 0) + return 0; + + auto itemFlags = itemObj->GetItemFlags(); + + auto spIdx = args.GetCondArg(0); + + 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; + + if (objType == obj_t_scroll && !spellSys.CheckAbilityScoreReqForSpell(args.objHndCaller, spData.spellEnum, -1) && !useMagicDeviceSkillBase) + return 0; + + RadialMenuEntry radEntry; + if (objType == obj_t_food){ + if (inventory.IsMagicItem(itemHandle)) + radEntry.d20ActionType = D20A_USE_POTION; + else + radEntry.d20ActionType = D20A_USE_ITEM; + } + else{ + radEntry.d20ActionType = D20A_USE_ITEM; + } + + radEntry.d20ActionData1 = invIdx; + radEntry.d20SpellData.Set(spData.spellEnum, spData.classCode, spData.spellLevel, invIdx, (MetaMagicData)0); + radEntry.text = const_cast(description.getDisplayName(itemHandle, args.objHndCaller)); + + RadialMenuStandardNode parentType; + switch(objType) + { + case obj_t_scroll: + parentType = RadialMenuStandardNode ::Scrolls; + break; + case obj_t_food: + parentType = RadialMenuStandardNode::Potions; + break; + default: + parentType = args.GetCondArg(1) != 3 ? RadialMenuStandardNode::Items : RadialMenuStandardNode::Wands; + break; + } + + radEntry.helpId = ElfHash::Hash(spellSys.GetSpellEnumTAG(spData.spellEnum)); + + radEntry.AddChildToStandard(args.objHndCaller, parentType); + + // add to Copy Scroll + if (objType == obj_t_scroll && objects.StatLevelGet(args.objHndCaller, stat_level_wizard) >= 1 + && critterSys.HashMatchingClassForSpell(args.objHndCaller, spData.spellEnum) + && spellSys.IsArcaneSpellClass(spData.classCode) + && !spellSys.spellKnownQueryGetData(args.objHndCaller, spData.spellEnum, nullptr, nullptr, nullptr)) + { + radEntry.d20ActionType = D20A_COPY_SCROLL; + radEntry.d20ActionData1 = inventory.GetInventoryLocation(itemHandle); + radEntry.AddChildToStandard(args.objHndCaller, RadialMenuStandardNode::CopyScroll); + } + + return 0; +} #pragma endregion diff --git a/TemplePlus/critter.cpp b/TemplePlus/critter.cpp index a4dd2caef..d35618831 100644 --- a/TemplePlus/critter.cpp +++ b/TemplePlus/critter.cpp @@ -1153,6 +1153,18 @@ int LegacyCritterSystem::PlayCritterVoiceLine(objHndl obj, objHndl fellow, char* return addresses.PlayCritterVoiceLine(obj, fellow, text, soundId); } +bool LegacyCritterSystem::HashMatchingClassForSpell(objHndl handle, uint32_t spellEnum) const +{ + return temple::GetRef(0x10075DA0)(handle, spellEnum); +} + +int LegacyCritterSystem::SkillBaseGet(objHndl handle, SkillEnum skill) +{ + if (!handle) + return 0; + return gameSystems->GetObj().GetObject(handle)->GetInt32Array(obj_f_critter_skill_idx)[ skill] / 2; +} + int LegacyCritterSystem::SpellNumByFieldAndClass(objHndl obj, obj_f field, uint32_t spellClassCode) { auto objBody = gameSystems->GetObj().GetObject(obj); diff --git a/TemplePlus/critter.h b/TemplePlus/critter.h index ba06e089a..ad9fb8a4b 100644 --- a/TemplePlus/critter.h +++ b/TemplePlus/critter.h @@ -5,6 +5,8 @@ #include #include +enum SkillEnum:uint32_t; + namespace gfx { enum class WeaponAnim; class EncodedAnimId; @@ -310,6 +312,8 @@ struct LegacyCritterSystem : temple::AddressTable bool IsWieldingRangedWeapon(objHndl performer); void GetCritterVoiceLine(objHndl obj, objHndl fellow, char *str, int* soundId); int PlayCritterVoiceLine(objHndl obj, objHndl fellow, char* text, int soundId); + bool HashMatchingClassForSpell(objHndl handle, uint32_t spellEnum) const; // checks if obj has a matching spell in their list + static int SkillBaseGet(objHndl handle, SkillEnum skill); static int SpellNumByFieldAndClass(objHndl obj, obj_f field, uint32_t spellClassCode); int DomainSpellNumByField(objHndl obj, obj_f field); static int GetNumFollowers(objHndl obj, int excludeForcedFollowers); diff --git a/TemplePlus/hotkeys.cpp b/TemplePlus/hotkeys.cpp index b056cb25c..ffca9b0cd 100644 --- a/TemplePlus/hotkeys.cpp +++ b/TemplePlus/hotkeys.cpp @@ -5,6 +5,9 @@ #include "tio\tio.h" #include "gamesystems/legacy.h" #include "util/fixes.h" +#include "obj.h" +#include "critter.h" +#include "action_sequence.h" HotkeySystem hotkeys; @@ -50,6 +53,9 @@ class HotkeyReplacements : TempleFix { public: const char* name() override { return "Hotkey Function Replacements";} + + static BOOL HotkeyCompare(RadialMenuEntry& first, RadialMenuEntry & second); + static BOOL HotkeyActivate(objHndl obj); void apply() override { replaceFunction(0x100F3B80, HotkeyInit); @@ -57,9 +63,92 @@ class HotkeyReplacements : TempleFix replaceFunction(0x100F3BD0, SaveHotkeys); replaceFunction(0x100F3C80, LoadHotkeys); replaceFunction(0x100F4030, HotkeyAssignCallback); + replaceFunction(0x100F0380, HotkeyCompare); + replaceFunction(0x100F0B80, HotkeyActivate); } } hotkeyReplacements; +BOOL HotkeyReplacements::HotkeyCompare(RadialMenuEntry& first, RadialMenuEntry& second) +{ + auto actionType = first.d20ActionType; + + if (actionType != second.d20ActionType) { + return FALSE; + } + + if (actionType == D20A_ACTIVATE_DEVICE_FREE + || actionType == D20A_ACTIVATE_DEVICE_STANDARD + || actionType == D20A_ACTIVATE_DEVICE_SPELL) + return first.textHash == second.textHash; + + if (first.d20ActionData1 != second.d20ActionData1) + return FALSE; + + if (first.d20SpellData.spellEnumOrg != second.d20SpellData.spellEnumOrg) + return FALSE; + + if (first.d20SpellData.metaMagicData != second.d20SpellData.metaMagicData) + return FALSE; + + if (first.d20ActionType == D20A_NONE &&first.textHash != second.textHash) + return FALSE; + + return TRUE; +} + +BOOL HotkeyReplacements::HotkeyActivate(objHndl obj) +{ + auto radMenuForHK = temple::GetRef(0x115B2050); + + if (!radMenuForHK) + return FALSE; + + auto radMenuNodeCount = temple::GetRef(0x118676C0); + if (radMenuNodeCount > radMenuForHK->nodeCount) + return FALSE; + + auto& activeRadialMenu = temple::GetRef(0x115B2048); + activeRadialMenu = radialMenus.GetForObj(obj); + + auto& radEntry = radMenuForHK->nodes[radMenuNodeCount -1].entry; + + auto& activeRadialMenuNode = temple::GetRef(0x115B204C); + activeRadialMenuNode = radMenuNodeCount - 1; + + if (radEntry.d20ActionType == D20A_CAST_SPELL) + actSeqSys.ActSeqSpellReset(); + else if (radEntry.d20ActionType == D20A_USE_ITEM && radEntry.d20SpellData.spellEnumOrg != 0){ + actSeqSys.ActSeqSpellReset(); + } + + auto nodeType = radEntry.type; + auto result = FALSE; + if (nodeType == RadialMenuEntryType::Action) + { + if (radEntry.callback){ + result = radEntry.callback(obj, &radEntry); + } + } + else if (nodeType == RadialMenuEntryType::Slider)// will toggle between min/max values + { + temple::GetRef(0x100F05C0)(obj, radEntry); // toggle value to min/max + temple::GetRef(0x100F05F0)(obj, radEntry); // activate / deactivate float line + result = FALSE; + } + else if (nodeType == RadialMenuEntryType::Toggle) + { + if (radEntry.callback) { + result = radEntry.callback(obj, &radEntry); + } + temple::GetRef(0x100F05F0)(obj, radEntry); // activate / deactivate float line + } + + + activeRadialMenu = nullptr; + activeRadialMenuNode = -1; + return result; + +} int HotkeySystem::SaveHotkeys(TioFile* file) { @@ -143,12 +232,16 @@ void HotkeySystem::HotkeyAssignCallback(int cancelFlag) { if (!cancelFlag) { + auto hotkeyTable = addresses.hotkeyTable; + auto hkIdx = *addresses.keyIdxToBind; + int radMenuIdx = addresses.HotkeyTableSearch(*addresses.radMenuEntryToBind); if (radMenuIdx != -1) - addresses.hotkeyTable[radMenuIdx].d20ActionType = D20A_UNASSIGNED; - memcpy(&addresses.hotkeyTable[*addresses.keyIdxToBind], *addresses.radMenuEntryToBind, sizeof(RadialMenuEntry)); - strncpy(&addresses.hotkeyTexts[128 * (*addresses.keyIdxToBind)], (*addresses.radMenuEntryToBind)->text, 127); - addresses.hotkeyTable[*addresses.keyIdxToBind].text = &addresses.hotkeyTexts[128 * (*addresses.keyIdxToBind)]; + hotkeyTable[radMenuIdx].d20ActionType = D20A_UNASSIGNED; + + hotkeyTable[hkIdx] = **addresses.radMenuEntryToBind; + strncpy(&addresses.hotkeyTexts[128 * hkIdx], (*addresses.radMenuEntryToBind)->text, 127); + hotkeyTable[hkIdx].text = &addresses.hotkeyTexts[128 * hkIdx]; auto file = fopen("hotkeys.sco", "wb"); SaveHotkeys(file); diff --git a/TemplePlus/inventory.cpp b/TemplePlus/inventory.cpp index 9fea598b9..562dfe2ba 100644 --- a/TemplePlus/inventory.cpp +++ b/TemplePlus/inventory.cpp @@ -444,6 +444,26 @@ const char* InventorySystem::GetItemErrorString(ItemErrorCode itemErrorCode) return line.value; } +bool InventorySystem::IsMagicItem(objHndl itemHandle) +{ + return gameSystems->GetObj().GetObject(itemHandle)->GetItemFlags() & OIF_IS_MAGICAL; +} + +bool InventorySystem::IsIdentified(objHndl itemHandle) +{ + if (!itemHandle) + return false; + auto obj = gameSystems->GetObj().GetObject(itemHandle); + + if (!obj->IsItem()) + return false; + + if (obj->GetItemFlags() & ItemFlag::OIF_IS_MAGICAL) + return (obj->GetItemFlags() & ItemFlag::OIF_IDENTIFIED); + + return true; +} + bool InventorySystem::IsBuckler(objHndl shield) { if (!shield) diff --git a/TemplePlus/inventory.h b/TemplePlus/inventory.h index 14cb525b6..6dc92204d 100644 --- a/TemplePlus/inventory.h +++ b/TemplePlus/inventory.h @@ -81,6 +81,8 @@ struct InventorySystem : temple::AddressTable bool IsProficientWithArmor(objHndl obj, objHndl armor) const; void GetItemMesLine(MesLine* line); const char* GetItemErrorString(ItemErrorCode itemErrorCode); + static bool IsMagicItem(objHndl itemHandle); + static bool IsIdentified(objHndl itemHandle); static bool IsBuckler(objHndl shield); 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 diff --git a/TemplePlus/radialmenu.cpp b/TemplePlus/radialmenu.cpp index 0e504c5b7..d6c4817c5 100644 --- a/TemplePlus/radialmenu.cpp +++ b/TemplePlus/radialmenu.cpp @@ -50,7 +50,7 @@ static struct RadialMenuAddresses : temple::AddressTable { int(__cdecl *SetSpontaneousCastingAltNode)(objHndl handle, int parentIdx, SpellStoreData * spellData); int(__cdecl *AddSpell)(objHndl handle, SpellStoreData * spellData, int * idxOut, const RadialMenuEntry & entry); //adds a spell to the Radial Menu int(__cdecl * RadialMenuCheckboxSthgSub_100F0200)(objHndl objHnd, RadialMenuEntry* radialMenuEntry); - void(__cdecl * CopyEntryToSelected)(objHndl obj, RadialMenuEntry* entry); + BOOL(__cdecl * CopyEntryToSelected)(objHndl obj, RadialMenuEntry* entry); RadialMenu ** activeRadialMenu; int * activeRadialMenuNode; @@ -235,7 +235,7 @@ int RadialMenus::AddParentChildNode(objHndl objHnd, RadialMenuEntry* radialMenuE node->entry.d20ActionType = D20A_NONE; node->parent = parentIdx; node->childCount = 0; - node->entry.callback = (void ( __cdecl*)(objHndl, RadialMenuEntry*) )return0; + node->entry.callback = (int ( __cdecl*)(objHndl, RadialMenuEntry*) )return0; node->entry.type = RadialMenuEntryType::Parent; node->morphsTo = -1; node->entry.textHash = conds.hashmethods.StringHash(radialMenuEntry->text); @@ -294,7 +294,7 @@ int RadialMenus::AddRootParentNode(objHndl obj, RadialMenuEntry* entry){ node->entry.d20ActionType = D20A_NONE; node->childCount = 0; - node->entry.callback = (void(__cdecl*)(objHndl, RadialMenuEntry*))return0; + node->entry.callback = (int(__cdecl*)(objHndl, RadialMenuEntry*))return0; node->entry.type = RadialMenuEntryType::Parent; node->morphsTo = -1; node->entry.textHash = ElfHash::Hash(entry->text); @@ -463,7 +463,7 @@ RadialMenuEntrySlider::RadialMenuEntrySlider(int combatMesLine, int _minArg, int if (combatMesHeaderText != -1) field4 = reinterpret_cast(combatSys.GetCombatMesLine(combatMesHeaderText)); // the popup title helpId = _helpId; - callback = (void(__cdecl*)(objHndl, RadialMenuEntry*))temple::GetPointer(0x100F0200); + callback = (BOOL(__cdecl*)(objHndl, RadialMenuEntry*))temple::GetPointer(0x100F0200); } RadialMenuEntryAction::RadialMenuEntryAction(int combatMesLine, D20ActionType d20aType, int data1, uint32_t HelpId) : RadialMenuEntry() { @@ -484,7 +484,7 @@ RadialMenuEntryToggle::RadialMenuEntryToggle(int combatMesLine, void* ActualArg, type = RadialMenuEntryType::Toggle; text = combatSys.GetCombatMesLine(combatMesLine); helpId = ElfHash::Hash(HelpId); - callback = temple::GetRef(0x100F0200); + callback = temple::GetRef(0x100F0200); minArg = 0; maxArg = 1; actualArg = reinterpret_cast(ActualArg); diff --git a/TemplePlus/radialmenu.h b/TemplePlus/radialmenu.h index 455076bdd..af8f104a9 100644 --- a/TemplePlus/radialmenu.h +++ b/TemplePlus/radialmenu.h @@ -74,7 +74,7 @@ struct RadialMenuEntry { //int spellEnumOrg; //uint32_t spellMetaMagic; int dispKey; - void (__cdecl *callback)(objHndl a1, RadialMenuEntry *entry); + BOOL (__cdecl *callback)(objHndl a1, RadialMenuEntry *entry); int flags; int helpId; // String hash for the help topic associated with this entry int field44; diff --git a/TemplePlus/spell.cpp b/TemplePlus/spell.cpp index f204f477a..6e7ea4c49 100644 --- a/TemplePlus/spell.cpp +++ b/TemplePlus/spell.cpp @@ -315,6 +315,11 @@ const char* LegacySpellSystem::GetSpellMesline(uint32_t lineNumber) const return mesLine.value; } +bool LegacySpellSystem::CheckAbilityScoreReqForSpell(objHndl handle, uint32_t spellEnum, int statBeingRaised) const +{ + return temple::GetRef(0x10075C60)(handle, spellEnum, statBeingRaised); +} + const char* LegacySpellSystem::GetSpellEnumTAG(uint32_t spellEnum){ MesLine mesline; diff --git a/TemplePlus/spell.h b/TemplePlus/spell.h index 8ffec0c4d..a01cbd3f2 100644 --- a/TemplePlus/spell.h +++ b/TemplePlus/spell.h @@ -155,6 +155,7 @@ struct LegacySpellSystem : temple::AddressTable int ParseSpellSpecString(SpellStoreData* spell, char* spellString); const char* GetSpellMesline(uint32_t line) const; + bool CheckAbilityScoreReqForSpell(objHndl handle, uint32_t spellEnum, int statBeingRaised) const; static const char* GetSpellEnumTAG(uint32_t spellEnum); const char* GetSpellName(uint32_t spellEnum) const; diff --git a/TemplePlus/spell_structs.h b/TemplePlus/spell_structs.h index 1378532d3..86aede7a6 100644 --- a/TemplePlus/spell_structs.h +++ b/TemplePlus/spell_structs.h @@ -56,6 +56,7 @@ struct SpellStoreState }; enum SpontCastType : unsigned char { + spontCastNone = 0, spontCastGoodCleric = 2, spontCastEvilCleric = 4, spontCastDruid = 8 @@ -81,8 +82,19 @@ struct D20SpellData uint8_t itemSpellData; SpontCastType spontCastType : 4; unsigned char spellSlotLevel : 4; + void Set(uint32_t spellEnum, uint32_t spellClassCode, uint32_t spellLevel, uint32_t invIdx, MetaMagicData metaMagicData); }; +inline void D20SpellData::Set(uint32_t spellEnum, uint32_t SpellClassCode, uint32_t SpellLevel, uint32_t invIdx, MetaMagicData mmData) +{ + spellEnumOrg = spellEnum; + metaMagicData = mmData; + spellClassCode = SpellClassCode; + itemSpellData = invIdx; + spellSlotLevel = SpellLevel; + spontCastType = SpontCastType::spontCastNone; +} + const uint32_t TestSizeOfD20SpellData = sizeof(D20SpellData); #pragma pack(push,4) diff --git a/TemplePlus/ui/ui_item_creation.cpp b/TemplePlus/ui/ui_item_creation.cpp index 3bd7a51e2..5b2324dd3 100644 --- a/TemplePlus/ui/ui_item_creation.cpp +++ b/TemplePlus/ui/ui_item_creation.cpp @@ -608,7 +608,7 @@ int CraftWandRadialMenu(DispatcherCallbackArgs args) setWandLevel.field4 = (int)combatSys.GetCombatMesLine(6019); setWandLevel.type = RadialMenuEntryType::Slider; setWandLevel.actualArg = (int)conds.CondNodeGetArgPtr(args.subDispNode->condNode, 0); - setWandLevel.callback = (void (__cdecl*)(objHndl, RadialMenuEntry*))itemCreationAddresses.Sub_100F0200; + setWandLevel.callback = (BOOL (__cdecl*)(objHndl, RadialMenuEntry*))itemCreationAddresses.Sub_100F0200; setWandLevel.text = combatSys.GetCombatMesLine(6017); setWandLevel.helpId = templeFuncs.StringHash("TAG_CRAFT_WAND"); radialMenus.AddChildNode(args.objHndCaller, &setWandLevel, newParent);