mirror of
https://github.com/brues-code/nampower.git
synced 2026-10-10 23:50:02 +00:00
fix item export, allow positioning aoe target during queuing
This commit is contained in:
@@ -156,6 +156,17 @@ namespace game {
|
||||
unsigned __int64 *pos;
|
||||
};
|
||||
|
||||
struct C3Vector {
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
};
|
||||
|
||||
struct CTerrainClickEvent {
|
||||
C3Vector point;
|
||||
unsigned int button;
|
||||
};
|
||||
|
||||
enum ItemStatsFlags {
|
||||
ITEM_FLAG_NO_PICKUP = 1,
|
||||
ITEM_FLAG_CONJURED = 2,
|
||||
@@ -740,6 +751,29 @@ namespace game {
|
||||
TARGET_LOCATION_UNIT_POSITION = 64,
|
||||
};
|
||||
|
||||
// SpellEntry::Targets
|
||||
enum SpellCastTargetFlags
|
||||
{
|
||||
TARGET_FLAG_SELF = 0x00000000,
|
||||
TARGET_FLAG_UNUSED1 = 0x00000001, // not used in any spells (can be set dynamically)
|
||||
TARGET_FLAG_UNIT = 0x00000002, // pguid
|
||||
TARGET_FLAG_UNUSED2 = 0x00000004, // not used in any spells (can be set dynamically)
|
||||
TARGET_FLAG_UNUSED3 = 0x00000008, // not used in any spells (can be set dynamically)
|
||||
TARGET_FLAG_ITEM = 0x00000010, // pguid
|
||||
TARGET_FLAG_SOURCE_LOCATION = 0x00000020, // 3 float
|
||||
TARGET_FLAG_DEST_LOCATION = 0x00000040, // 3 float
|
||||
TARGET_FLAG_OBJECT_UNK = 0x00000080, // used in 7 spells only
|
||||
TARGET_FLAG_UNIT_UNK = 0x00000100, // looks like self target (389 spells)
|
||||
TARGET_FLAG_PVP_CORPSE = 0x00000200, // pguid
|
||||
TARGET_FLAG_UNIT_CORPSE = 0x00000400, // 10 spells (gathering professions)
|
||||
TARGET_FLAG_OBJECT = 0x00000800, // pguid, 0 spells
|
||||
TARGET_FLAG_TRADE_ITEM = 0x00001000, // pguid, 0 spells
|
||||
TARGET_FLAG_STRING = 0x00002000, // string, 0 spells
|
||||
TARGET_FLAG_UNK1 = 0x00004000, // 199 spells, opening object/lock
|
||||
TARGET_FLAG_CORPSE = 0x00008000, // pguid, resurrection spells
|
||||
TARGET_FLAG_UNK2 = 0x00010000, // pguid, not used in any spells (can be set dynamically)
|
||||
};
|
||||
|
||||
enum Events : std::uint32_t {
|
||||
UNIT_NAME_UPDATE = 183,
|
||||
UNIT_PORTRAIT_UPDATE = 184,
|
||||
|
||||
+4
-2
@@ -30,7 +30,8 @@ namespace Nampower {
|
||||
|
||||
const std::string key = normalize(spellName);
|
||||
|
||||
auto const spellNameMatchesAtSlot = [&normalize](const char *requestedLower, uint32_t slot, uint32_t type) -> bool {
|
||||
auto const spellNameMatchesAtSlot = [&normalize](const char *requestedLower, uint32_t slot,
|
||||
uint32_t type) -> bool {
|
||||
if (!requestedLower) {
|
||||
return false;
|
||||
}
|
||||
@@ -169,7 +170,8 @@ namespace Nampower {
|
||||
}
|
||||
|
||||
bool SpellIsTargeting(const game::SpellRec *spell) {
|
||||
return spell->Targets == game::SpellTarget::TARGET_LOCATION_UNIT_POSITION;
|
||||
return spell->Targets & game::SpellCastTargetFlags::TARGET_FLAG_SOURCE_LOCATION ||
|
||||
spell->Targets & game::SpellCastTargetFlags::TARGET_FLAG_DEST_LOCATION;
|
||||
}
|
||||
|
||||
bool SpellIsOnSwing(const game::SpellRec *spell) {
|
||||
|
||||
+104
-58
@@ -10,59 +10,24 @@
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace Nampower {
|
||||
// Global dictionary to store itemId -> ItemStats_C mappings
|
||||
static std::unordered_map<uint32_t, game::ItemStats_C *> itemStatsCache;
|
||||
|
||||
// Track pending async loads (can have multiple at once)
|
||||
static std::unordered_set<uint32_t> pendingItemIds;
|
||||
|
||||
// Export state tracking
|
||||
static bool isExporting = false;
|
||||
static uint32_t currentExportItemId = 0;
|
||||
static const uint32_t MAX_EXPORT_ITEM_ID = 100000;
|
||||
static const uint32_t ITEMS_PER_FRAME = 20;
|
||||
|
||||
auto const getRow = reinterpret_cast<DBCache_ItemCacheDBGetRowT>(Offsets::DBCache_ItemCacheDBGetRow);
|
||||
auto const itemCache = reinterpret_cast<void *>(Offsets::ItemDBCache);
|
||||
|
||||
// Callback function for DBCache
|
||||
void __stdcall ItemStatsCallback(uintptr_t *tooltip, char param_2) {
|
||||
if (tooltip != nullptr) {
|
||||
auto itemId = *reinterpret_cast<uint32_t *>(tooltip + 0xE6);
|
||||
|
||||
int ptr;
|
||||
auto itemStats = getRow(itemCache, nullptr, itemId, &ptr, &ItemStatsCallback, nullptr, 0);
|
||||
if (itemStats) {
|
||||
DEBUG_LOG("Queried itemId " << itemId << " -> " << itemStats);
|
||||
// Store in cache
|
||||
itemStatsCache[itemId] = reinterpret_cast<game::ItemStats_C *>(itemStats);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LoadItem(uint32_t itemId) {
|
||||
// Check if already in cache
|
||||
auto it = itemStatsCache.find(itemId);
|
||||
if (it != itemStatsCache.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!itemCache) {
|
||||
return;
|
||||
}
|
||||
|
||||
int ptr;
|
||||
auto itemStats = getRow(itemCache, nullptr, itemId, &ptr, &ItemStatsCallback, nullptr, 0);
|
||||
if (itemStats) {
|
||||
// Store in cache
|
||||
itemStatsCache[itemId] = reinterpret_cast<game::ItemStats_C *>(itemStats);
|
||||
} else {
|
||||
// wait for .005 sec
|
||||
Sleep(5);
|
||||
}
|
||||
}
|
||||
|
||||
game::ItemStats_C* GetItemStats(uint32_t itemId) {
|
||||
auto it = itemStatsCache.find(itemId);
|
||||
if (it != itemStatsCache.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string escapeJsonString(const char *str) {
|
||||
if (!str) return "null";
|
||||
|
||||
@@ -104,21 +69,77 @@ namespace Nampower {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Register a function if you want to use this
|
||||
// Don't want everyone scraping all items.
|
||||
void ExportAllItems() {
|
||||
DEBUG_LOG("Starting getAllItems - fetching items 1 to 100000");
|
||||
|
||||
// Loop through all item IDs
|
||||
for (uint32_t itemId = 1; itemId <= 100000; ++itemId) {
|
||||
LoadItem(itemId);
|
||||
|
||||
// Log progress every 1000 items
|
||||
if (itemId % 1000 == 0) {
|
||||
DEBUG_LOG("Progress: " << itemId << " / 100000 items processed");
|
||||
// Callback function for DBCache
|
||||
void __fastcall ItemStatsCallback(uint32_t itemId, const uint64_t *ownerGuid, void *userData, bool resultFlag) {
|
||||
if (resultFlag) {
|
||||
uint64_t guid;
|
||||
auto itemStats = getRow(itemCache, itemId, &guid, &ItemStatsCallback, nullptr, false);
|
||||
if (itemStats) {
|
||||
// Store in cache
|
||||
DEBUG_LOG("Added itemId " << itemId);
|
||||
itemStatsCache[itemId] = reinterpret_cast<game::ItemStats_C *>(itemStats);
|
||||
} else {
|
||||
DEBUG_LOG("Still missing " << itemId);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove from pending set
|
||||
pendingItemIds.erase(itemId);
|
||||
}
|
||||
|
||||
bool LoadItem(uint32_t itemId) {
|
||||
// Check if already in cache
|
||||
auto it = itemStatsCache.find(itemId);
|
||||
if (it != itemStatsCache.end()) {
|
||||
return false; // Already loaded, no wait needed
|
||||
}
|
||||
|
||||
uint64_t guid;
|
||||
auto itemStats = getRow(itemCache, itemId, &guid, &ItemStatsCallback, nullptr, true);
|
||||
if (itemStats) {
|
||||
// Item was available immediately, store in cache
|
||||
itemStatsCache[itemId] = reinterpret_cast<game::ItemStats_C *>(itemStats);
|
||||
return false; // Loaded immediately, no wait needed
|
||||
} else {
|
||||
// Item not immediately available, need to wait for callback
|
||||
pendingItemIds.insert(itemId);
|
||||
return true; // Caller should wait for callback
|
||||
}
|
||||
}
|
||||
|
||||
bool ProcessItemExport() {
|
||||
if (!isExporting) {
|
||||
return false; // Not currently exporting
|
||||
}
|
||||
|
||||
// Try to load items until we have 20 pending or run out of items
|
||||
uint32_t itemsRequestedThisFrame = 0;
|
||||
while (pendingItemIds.size() < ITEMS_PER_FRAME && currentExportItemId <= MAX_EXPORT_ITEM_ID) {
|
||||
LoadItem(currentExportItemId);
|
||||
|
||||
// Log progress every 1000 items
|
||||
if (currentExportItemId % 1000 == 0) {
|
||||
DEBUG_LOG("Progress: " << currentExportItemId << " / " << MAX_EXPORT_ITEM_ID
|
||||
<< " items requested, " << pendingItemIds.size() << " pending callbacks");
|
||||
}
|
||||
|
||||
currentExportItemId++;
|
||||
itemsRequestedThisFrame++;
|
||||
|
||||
// Safety check - don't request too many in a single frame
|
||||
if (itemsRequestedThisFrame >= ITEMS_PER_FRAME) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we're done (all items requested and no pending callbacks)
|
||||
if (currentExportItemId > MAX_EXPORT_ITEM_ID && pendingItemIds.empty()) {
|
||||
// Fall through to export completion
|
||||
} else {
|
||||
return true; // Still exporting, more items to process or waiting for callbacks
|
||||
}
|
||||
|
||||
// Export complete, write to file
|
||||
DEBUG_LOG("Finished fetching items, found " << itemStatsCache.size() << " valid items");
|
||||
DEBUG_LOG("Writing items to items.json");
|
||||
|
||||
@@ -127,7 +148,8 @@ namespace Nampower {
|
||||
std::ofstream jsonFile("items.json", std::ios::trunc);
|
||||
if (!jsonFile.is_open()) {
|
||||
DEBUG_LOG("Failed to open items.json for writing");
|
||||
return;
|
||||
isExporting = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
jsonFile << "[\n";
|
||||
@@ -289,5 +311,29 @@ namespace Nampower {
|
||||
} catch (const std::exception &e) {
|
||||
DEBUG_LOG("Exception writing to items.json: " << e.what());
|
||||
}
|
||||
|
||||
isExporting = false;
|
||||
return false; // Export complete
|
||||
}
|
||||
|
||||
game::ItemStats_C *GetItemStats(uint32_t itemId) {
|
||||
auto it = itemStatsCache.find(itemId);
|
||||
if (it != itemStatsCache.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Register a function if you want to use this
|
||||
// Don't want everyone scraping all items.
|
||||
void ExportAllItems() {
|
||||
DEBUG_LOG("Starting item export - will fetch items 1 to " << MAX_EXPORT_ITEM_ID << " with up to " << ITEMS_PER_FRAME << " concurrent requests");
|
||||
|
||||
// Initialize export state
|
||||
isExporting = true;
|
||||
currentExportItemId = 1;
|
||||
pendingItemIds.clear();
|
||||
|
||||
DEBUG_LOG("Export initialized. ProcessItemExport will be called each frame from processQueues.");
|
||||
}
|
||||
}
|
||||
|
||||
+4
-3
@@ -7,11 +7,12 @@
|
||||
#include "main.hpp"
|
||||
|
||||
namespace Nampower {
|
||||
using TooltipItemStatsCallbackT = void (__stdcall *)(uintptr_t *tooltip, char param_2);
|
||||
using TooltipItemStatsCallbackT = void (__fastcall *)(uint32_t itemId, const uint64_t *ownerGuid, void *userData, bool resultFlag);
|
||||
|
||||
using DBCache_ItemCacheDBGetRowT = uint32_t * (__fastcall *)(void *this_ptr, void *dummy_edx, uint32_t itemId, int *param_2, TooltipItemStatsCallbackT callback, int *param_4, char param_5);
|
||||
using DBCache_ItemCacheDBGetRowT = uint32_t * (__thiscall *)(void *this_ptr, uint32_t itemId, uint64_t *guid, TooltipItemStatsCallbackT callback, uintptr_t *userData, bool requestIfMissing);
|
||||
|
||||
void ExportAllItems();
|
||||
void LoadItem(uint32_t itemId);
|
||||
bool LoadItem(uint32_t itemId); // Returns true if item needs async load (caller should wait)
|
||||
bool ProcessItemExport(); // Process one item export per frame, returns true if still exporting
|
||||
game::ItemStats_C* GetItemStats(uint32_t itemId);
|
||||
}
|
||||
|
||||
@@ -94,6 +94,7 @@ namespace Nampower {
|
||||
std::unique_ptr<hadesmem::PatchDetour<SignalEventT>> gSignalEventDetour;
|
||||
std::unique_ptr<hadesmem::PatchDetour<Spell_C_SpellFailedT>> gSpellFailedDetour;
|
||||
std::unique_ptr<hadesmem::PatchDetour<Spell_C_GetSpellModifiersT>> gSpell_C_GetSpellModifiersDetour;
|
||||
std::unique_ptr<hadesmem::PatchDetour<Spell_C_GetSpellRadiusT>> gSpell_C_GetSpellRadiusDetour;
|
||||
std::unique_ptr<hadesmem::PatchRaw> gCastbarPatch;
|
||||
std::unique_ptr<hadesmem::PatchDetour<ISceneEndT>> gIEndSceneDetour;
|
||||
std::unique_ptr<hadesmem::PatchDetour<Spell_C_GetAutoRepeatingSpellT>> gSpell_C_GetAutoRepeatingSpellDetour;
|
||||
@@ -103,6 +104,7 @@ namespace Nampower {
|
||||
std::unique_ptr<hadesmem::PatchDetour<LuaScriptT>> gSpellStopCastingDetour;
|
||||
std::unique_ptr<hadesmem::PatchDetour<OnSpriteRightClickT>> gOnSpriteRightClickDetour;
|
||||
std::unique_ptr<hadesmem::PatchDetour<Spell_C_HandleSpriteClickT>> gSpell_C_HandleSpriteClickDetour;
|
||||
std::unique_ptr<hadesmem::PatchDetour<Spell_C_HandleTerrainClickT>> gSpell_C_HandleTerrainClickDetour;
|
||||
std::unique_ptr<hadesmem::PatchDetour<Spell_C_TargetSpellT>> gSpell_C_TargetSpellDetour;
|
||||
|
||||
std::unique_ptr<hadesmem::PatchDetour<PacketHandlerT>> gSpellCooldownDetour;
|
||||
@@ -328,6 +330,8 @@ namespace Nampower {
|
||||
gCastData.nonGcdSpellQueued = false;
|
||||
gCastData.cooldownNonGcdSpellQueued = false;
|
||||
}
|
||||
gCastData.targetingSpellQueued = false;
|
||||
gCastData.targetingSpellId = 0;
|
||||
}
|
||||
|
||||
void checkForStopChanneling() {
|
||||
@@ -457,6 +461,8 @@ namespace Nampower {
|
||||
<< " due to max time since last cast");
|
||||
TriggerSpellQueuedEvent(NORMAL_QUEUE_POPPED, gLastNormalCastParams.spellId);
|
||||
gCastData.normalSpellQueued = false;
|
||||
gCastData.targetingSpellQueued = false;
|
||||
gCastData.targetingSpellId = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -481,6 +487,9 @@ namespace Nampower {
|
||||
}
|
||||
}
|
||||
|
||||
// Process item export if active (low priority, runs when nothing else is queued)
|
||||
// ProcessItemExport()
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1120,6 +1129,8 @@ namespace Nampower {
|
||||
&Script_SpellStopCastingHook);
|
||||
gSpell_C_TargetSpellDetour = createHook<Spell_C_TargetSpellT>(process, Offsets::Spell_C_TargetSpell,
|
||||
&Spell_C_TargetSpellHook);
|
||||
gSpell_C_HandleTerrainClickDetour = createHook<Spell_C_HandleTerrainClickT>(process, Offsets::Spell_C_HandleTerrainClick,
|
||||
&Spell_C_HandleTerrainClickHook);
|
||||
gOnSpriteRightClickDetour = createHook<OnSpriteRightClickT>(process, Offsets::OnSpriteRightClick,
|
||||
OnSpriteRightClickHook);
|
||||
gIEndSceneDetour = createHook<ISceneEndT>(process, Offsets::ISceneEndPtr, &ISceneEndHook);
|
||||
@@ -1127,6 +1138,8 @@ namespace Nampower {
|
||||
&InvalidFunctionPtrCheckHook);
|
||||
gGetSpellSlotFromLuaDetour = createHook<GetSpellSlotFromLuaT>(process, Offsets::GetSpellSlotFromLua,
|
||||
&GetSpellSlotFromLuaHook);
|
||||
gSpell_C_GetSpellRadiusDetour = createHook<Spell_C_GetSpellRadiusT>(process, Offsets::Spell_C_GetSpellRadius,
|
||||
&Spell_C_GetSpellRadiusHook);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -84,6 +84,7 @@ namespace Nampower {
|
||||
using Spell_C_GetAutoRepeatingSpellT = int (__cdecl *)();
|
||||
using SpellGoT = void (__fastcall *)(uint64_t *, uint64_t *, uint32_t, CDataStore *);
|
||||
using Spell_C_HandleSpriteClickT = bool (__fastcall *)(game::CSpriteClickEvent *event);
|
||||
using Spell_C_HandleTerrainClickT = uint32_t (__fastcall *)(game::CTerrainClickEvent *event);
|
||||
using Spell_C_TargetSpellT = bool (__fastcall *)(
|
||||
uint32_t *player,
|
||||
uint32_t *spellId,
|
||||
@@ -93,6 +94,7 @@ namespace Nampower {
|
||||
using Spell_C_GetSpellCooldownT = int (__fastcall *)(uint32_t spellId, uint32_t isPetSpell,
|
||||
uint32_t *duration, uint64_t *startTime, uint32_t *enable);
|
||||
using Spell_C_IsSpellUsableT = int (__fastcall *)(const game::SpellRec *spellRec, uint32_t *usesManaReturn);
|
||||
using Spell_C_GetSpellRadiusT = float (__fastcall *)();
|
||||
using Spell_C_GetSpellModifiersT = void (__fastcall *)(const game::SpellRec *spellRec, int *returnVal, game::SpellModOp modOp);
|
||||
|
||||
using CGPlayer_C_OnAttackIconPressedT = int (__fastcall *)(uintptr_t *this_ptr, void *dummy_edx, uint64_t guid);
|
||||
|
||||
@@ -47,6 +47,7 @@ enum class Offsets : std::uint32_t {
|
||||
CastingItemIdPtr = 0X00CECAB0,
|
||||
CastingSpellId = 0xCECA88,
|
||||
AutoRepeatingSpellId = 0xCEAC30,
|
||||
SpellNeedsTargets = 0X00CECAC0,
|
||||
VisualSpellId = 0X00CEAC58,
|
||||
GetSpellSlotAndType = 0X004B3950,
|
||||
GetSpellSlotFromLua = 0X004B3EC0,
|
||||
@@ -100,10 +101,12 @@ enum class Offsets : std::uint32_t {
|
||||
Spell_C_CastSpellByID = 0x6E5A90,
|
||||
Spell_C_GetAutoRepeatingSpell = 0x006E9FD0,
|
||||
Spell_C_HandleSpriteClick = 0x006E5B10,
|
||||
Spell_C_HandleTerrainClick = 0x006E60F0,
|
||||
Spell_C_TargetSpell = 0x006E5250,
|
||||
Spell_C_GetSpellCooldown = 0X006E2EA0,
|
||||
Spell_C_IsSpellUsable = 0X006E3D60,
|
||||
Spell_C_GetDuration = 0X006EA000,
|
||||
Spell_C_GetSpellRadius = 0x006E6350,
|
||||
Spell_C_ApplySpellModifiers = 0x006e6af0,
|
||||
Spell_C_GetSpellModifierValues = 0x006e6b30,
|
||||
|
||||
@@ -174,4 +177,7 @@ enum class Offsets : std::uint32_t {
|
||||
DBCache_ItemCacheDBGetRow = 0x0055BA30,
|
||||
|
||||
InvalidFunctionPtrCheck = 0x0042A320,
|
||||
|
||||
CursorSetCursorMode = 0x00523d20,
|
||||
CursorModelSetSequence = 0x00523c20,
|
||||
};
|
||||
|
||||
+132
-73
@@ -10,6 +10,111 @@
|
||||
namespace Nampower {
|
||||
auto const APPLY_BUFFER_TO_GCD = false; // gcd issue seems fixed for now
|
||||
|
||||
void SetReleaseAction(uint32_t input) {
|
||||
uint32_t activeControl = *reinterpret_cast<uint32_t *>(Offsets::CGInputControlGetActive);
|
||||
|
||||
typedef void(__thiscall *SetReleaseActionT)(uint32_t, uint32_t);
|
||||
auto SetReleaseAction = reinterpret_cast<SetReleaseActionT>(Offsets::CGInputControlSetReleaseAction);
|
||||
SetReleaseAction(activeControl, input);
|
||||
}
|
||||
|
||||
void SetControlBit(uint32_t input) {
|
||||
uint32_t activeControl = *reinterpret_cast<uint32_t *>(Offsets::CGInputControlGetActive);
|
||||
auto *LastHardwareAction = reinterpret_cast<uintptr_t *>(Offsets::LastHardwareAction);
|
||||
|
||||
typedef void(__thiscall *SetControlBitT)(uint32_t, uint32_t, uint32_t, uintptr_t *, int);
|
||||
auto SetControlBit = reinterpret_cast<SetControlBitT>(Offsets::CGInputControlSetControlBit);
|
||||
SetControlBit(activeControl, 2, input, LastHardwareAction, 0);
|
||||
}
|
||||
|
||||
void CameraOrSelectOrMoveStart() {
|
||||
SetReleaseAction(1);
|
||||
SetControlBit(1);
|
||||
}
|
||||
|
||||
void CameraOrSelectOrMoveStop() {
|
||||
SetControlBit(0);
|
||||
}
|
||||
|
||||
|
||||
uint32_t Spell_C_HandleTerrainClickHook(hadesmem::PatchDetourBase *detour, game::CTerrainClickEvent *event) {
|
||||
if(!gCastData.targetingSpellQueued){
|
||||
auto const handleTerrainClick = detour->GetTrampolineT<Spell_C_HandleTerrainClickT>();
|
||||
return handleTerrainClick(event);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
float Spell_C_GetSpellRadiusHook(hadesmem::PatchDetourBase *detour) {
|
||||
auto originalSpellId = *reinterpret_cast<uint32_t*>(Offsets::VisualSpellId);
|
||||
if(gCastData.targetingSpellQueued){
|
||||
// switch out visual spellid to the queued spell so that we get the correct radius
|
||||
*reinterpret_cast<uint32_t*>(Offsets::VisualSpellId) = gCastData.targetingSpellId;
|
||||
}
|
||||
|
||||
auto const getSpellRadius = detour->GetTrampolineT<Spell_C_GetSpellRadiusT>();
|
||||
auto radius = getSpellRadius();
|
||||
|
||||
if(gCastData.targetingSpellQueued){
|
||||
// switch back to originalSpellId
|
||||
*reinterpret_cast<uint32_t*>(Offsets::VisualSpellId) = originalSpellId;
|
||||
}
|
||||
|
||||
return radius;
|
||||
}
|
||||
|
||||
void EnableSpellTargeting(uint32_t *casterUnit, const game::SpellRec *spell, uintptr_t *unk, uint64_t guid) {
|
||||
// set s_needtargets 00cecac0
|
||||
auto s_needtargets = reinterpret_cast<uint32_t *>(Offsets::SpellNeedsTargets);
|
||||
*s_needtargets = spell->Targets;
|
||||
|
||||
auto const CursorSetCursorMode = reinterpret_cast<void (__fastcall *)(uint32_t)>(Offsets::CursorSetCursorMode);
|
||||
CursorSetCursorMode(1); // CAST_CURSOR
|
||||
auto const CursorModelSetSequence = reinterpret_cast<void (__fastcall *)(uint32_t)>(Offsets::CursorModelSetSequence);
|
||||
CursorModelSetSequence(22); // CAST_CURSOR
|
||||
}
|
||||
|
||||
bool Spell_C_TargetSpellHook(hadesmem::PatchDetourBase *detour,
|
||||
uint32_t *player,
|
||||
uint32_t *spellId,
|
||||
uint32_t unk3,
|
||||
float unk4) {
|
||||
auto const spellTarget = detour->GetTrampolineT<Spell_C_TargetSpellT>();
|
||||
auto result = spellTarget(player, spellId, unk3, unk4);
|
||||
|
||||
if (!result) {
|
||||
auto const spellName = game::GetSpellName(*spellId);
|
||||
auto const spell = game::GetSpellInfo(*spellId);
|
||||
|
||||
if (spell->Targets == game::SpellCastTargetFlags::TARGET_FLAG_DEST_LOCATION &&
|
||||
spell->Effect[0] != game::SPELL_EFFECT_SUMMON_GUARDIAN) {
|
||||
// if quickcast is on instantly trigger all casts
|
||||
// otherwise if this is a queued cast, trigger it instant cast
|
||||
if (gUserSettings.quickcastTargetingSpells ||
|
||||
(gUserSettings.queueTargetingSpells && gCastData.castingQueuedSpell)) {
|
||||
DEBUG_LOG("Quickcasting terrain spell " << spellName
|
||||
<< " quickcast: "
|
||||
<< gUserSettings.quickcastTargetingSpells
|
||||
<< " queuetrigger: " << gCastData.castingQueuedSpell);
|
||||
|
||||
// store the current target
|
||||
auto const targetGuid = game::GetCurrentTargetGuid();
|
||||
|
||||
CameraOrSelectOrMoveStart();
|
||||
CameraOrSelectOrMoveStop();
|
||||
|
||||
// check if target changed
|
||||
if (targetGuid != game::GetCurrentTargetGuid()) {
|
||||
DEBUG_LOG("Target changed during quick cast, restoring previous target " << targetGuid);
|
||||
auto const targetUnit = reinterpret_cast<CGGameUI_TargetT>(Offsets::CGGameUI_Target);
|
||||
targetUnit(targetGuid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t GetChannelBaseDuration(const game::SpellRec *spell) {
|
||||
auto const duration = game::GetDurationObject(spell->DurationIndex);
|
||||
if (duration == nullptr) {
|
||||
@@ -117,6 +222,8 @@ namespace Nampower {
|
||||
DEBUG_LOG("Triggering queued non gcd cast of " << game::GetSpellName(nonGcdCastParams.spellId));
|
||||
|
||||
gCastData.castingQueuedSpell = true;
|
||||
gCastData.targetingSpellQueued = false; // make sure this is off so SendCast is allowed
|
||||
gCastData.targetingSpellId = 0;
|
||||
gCastData.numRetries = nonGcdCastParams.numRetries;
|
||||
Spell_C_CastSpellHook(castSpellDetour, nonGcdCastParams.casterUnit, nonGcdCastParams.spellId,
|
||||
nonGcdCastParams.item, nonGcdCastParams.guid);
|
||||
@@ -128,6 +235,8 @@ namespace Nampower {
|
||||
TriggerSpellQueuedEvent(NON_GCD_QUEUE_POPPED, nonGcdCastParams.spellId);
|
||||
gCastData.nonGcdSpellQueued = !gNonGcdCastQueue.isEmpty();
|
||||
gCastData.castingQueuedSpell = false;
|
||||
gCastData.targetingSpellQueued = false;
|
||||
gCastData.targetingSpellId = 0;
|
||||
gCastData.numRetries = 0;
|
||||
}
|
||||
}
|
||||
@@ -137,6 +246,8 @@ namespace Nampower {
|
||||
if (gLastNormalCastParams.spellId > 0) {
|
||||
DEBUG_LOG("Triggering queued cast of " << game::GetSpellName(gLastNormalCastParams.spellId));
|
||||
gCastData.castingQueuedSpell = true;
|
||||
gCastData.targetingSpellQueued = false; // make sure this is off so SendCast is allowed
|
||||
gCastData.targetingSpellId = 0;
|
||||
gCastData.numRetries = gLastNormalCastParams.numRetries;
|
||||
Spell_C_CastSpellHook(castSpellDetour, gLastNormalCastParams.casterUnit, gLastNormalCastParams.spellId,
|
||||
gLastNormalCastParams.item, gLastNormalCastParams.guid);
|
||||
@@ -148,6 +259,8 @@ namespace Nampower {
|
||||
TriggerSpellQueuedEvent(NORMAL_QUEUE_POPPED, gLastNormalCastParams.spellId);
|
||||
gCastData.normalSpellQueued = false;
|
||||
gCastData.castingQueuedSpell = false;
|
||||
gCastData.targetingSpellQueued = false;
|
||||
gCastData.targetingSpellId = 0;
|
||||
gCastData.numRetries = 0;
|
||||
}
|
||||
}
|
||||
@@ -377,18 +490,28 @@ namespace Nampower {
|
||||
if (gUserSettings.queueTargetingSpells) {
|
||||
if (castTime > 0 && inSpellQueueWindow) {
|
||||
if (gUserSettings.queueCastTimeSpells) {
|
||||
// call EnableSpellTargeting to set s_needTargets and trigger the targeting indicator
|
||||
EnableSpellTargeting(casterUnit, spell, nullptr, guid);
|
||||
|
||||
DEBUG_LOG("Queuing targeting for after cast/gcd: " << remainingCD << "ms " << spellName);
|
||||
TriggerSpellQueuedEvent(NORMAL_QUEUED, spellId);
|
||||
gCastData.normalSpellQueued = true;
|
||||
gCastData.targetingSpellQueued = true;
|
||||
gCastData.targetingSpellId = spellId;
|
||||
return false;
|
||||
}
|
||||
} else if (inSpellQueueWindow) {
|
||||
if (gUserSettings.queueInstantSpells) {
|
||||
if (spellOnGcd) {
|
||||
// call EnableSpellTargeting to set s_needTargets and trigger the targeting indicator
|
||||
EnableSpellTargeting(casterUnit, spell, nullptr, guid);
|
||||
|
||||
DEBUG_LOG("Queuing instant cast targeting for after cast/gcd: " << remainingCD << "ms "
|
||||
<< spellName);
|
||||
TriggerSpellQueuedEvent(NORMAL_QUEUED, spellId);
|
||||
gCastData.normalSpellQueued = true;
|
||||
gCastData.targetingSpellQueued = true;
|
||||
gCastData.targetingSpellId = spellId;
|
||||
return false;
|
||||
} else if (remainingEffectiveCastTime > 0) {
|
||||
auto castParams = gNonGcdCastQueue.findSpellId(spellId);
|
||||
@@ -397,6 +520,9 @@ namespace Nampower {
|
||||
castParams->guid = guid;
|
||||
return false;
|
||||
} else {
|
||||
// call EnableSpellTargeting to set s_needTargets and trigger the targeting indicator
|
||||
EnableSpellTargeting(casterUnit, spell, nullptr, guid);
|
||||
|
||||
DEBUG_LOG("Queuing instant cast non GCD targeting for after cast/gcd: "
|
||||
<< remainingEffectiveCastTime << "ms " << spellName
|
||||
<< " gcd category "
|
||||
@@ -410,6 +536,8 @@ namespace Nampower {
|
||||
false}, gUserSettings.replaceMatchingNonGcdCategory);
|
||||
TriggerSpellQueuedEvent(NON_GCD_QUEUED, spellId);
|
||||
gCastData.nonGcdSpellQueued = true;
|
||||
gCastData.targetingSpellQueued = true;
|
||||
gCastData.targetingSpellId = spellId;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -640,74 +768,6 @@ namespace Nampower {
|
||||
}
|
||||
}
|
||||
|
||||
void SetReleaseAction(uint32_t input) {
|
||||
uint32_t activeControl = *reinterpret_cast<uint32_t *>(Offsets::CGInputControlGetActive);
|
||||
|
||||
typedef void(__thiscall *SetReleaseActionT)(uint32_t, uint32_t);
|
||||
auto SetReleaseAction = reinterpret_cast<SetReleaseActionT>(Offsets::CGInputControlSetReleaseAction);
|
||||
SetReleaseAction(activeControl, input);
|
||||
}
|
||||
|
||||
void SetControlBit(uint32_t input) {
|
||||
uint32_t activeControl = *reinterpret_cast<uint32_t *>(Offsets::CGInputControlGetActive);
|
||||
auto *LastHardwareAction = reinterpret_cast<uintptr_t *>(Offsets::LastHardwareAction);
|
||||
|
||||
typedef void(__thiscall *SetControlBitT)(uint32_t, uint32_t, uint32_t, uintptr_t *, int);
|
||||
auto SetControlBit = reinterpret_cast<SetControlBitT>(Offsets::CGInputControlSetControlBit);
|
||||
SetControlBit(activeControl, 2, input, LastHardwareAction, 0);
|
||||
}
|
||||
|
||||
|
||||
void CameraOrSelectOrMoveStart() {
|
||||
SetReleaseAction(1);
|
||||
SetControlBit(1);
|
||||
}
|
||||
|
||||
void CameraOrSelectOrMoveStop() {
|
||||
SetControlBit(0);
|
||||
}
|
||||
|
||||
bool Spell_C_TargetSpellHook(hadesmem::PatchDetourBase *detour,
|
||||
uint32_t *player,
|
||||
uint32_t *spellId,
|
||||
uint32_t unk3,
|
||||
float unk4) {
|
||||
auto const spellTarget = detour->GetTrampolineT<Spell_C_TargetSpellT>();
|
||||
auto result = spellTarget(player, spellId, unk3, unk4);
|
||||
|
||||
if (!result) {
|
||||
auto const spellName = game::GetSpellName(*spellId);
|
||||
auto const spell = game::GetSpellInfo(*spellId);
|
||||
|
||||
if (spell->Targets == game::SpellTarget::TARGET_LOCATION_UNIT_POSITION &&
|
||||
spell->Effect[0] != game::SPELL_EFFECT_SUMMON_GUARDIAN) {
|
||||
// if quickcast is on instantly trigger all casts
|
||||
// otherwise if this is a queued cast, trigger it instant cast
|
||||
if (gUserSettings.quickcastTargetingSpells ||
|
||||
(gUserSettings.queueTargetingSpells && gCastData.castingQueuedSpell)) {
|
||||
DEBUG_LOG("Quickcasting terrain spell " << spellName
|
||||
<< " quickcast: "
|
||||
<< gUserSettings.quickcastTargetingSpells
|
||||
<< " queuetrigger: " << gCastData.castingQueuedSpell);
|
||||
|
||||
// store the current target
|
||||
auto const targetGuid = game::GetCurrentTargetGuid();
|
||||
|
||||
CameraOrSelectOrMoveStart();
|
||||
CameraOrSelectOrMoveStop();
|
||||
|
||||
// check if target changed
|
||||
if (targetGuid != game::GetCurrentTargetGuid()) {
|
||||
DEBUG_LOG("Target changed during quick cast, restoring previous target " << targetGuid);
|
||||
auto const targetUnit = reinterpret_cast<CGGameUI_TargetT>(Offsets::CGGameUI_Target);
|
||||
targetUnit(targetGuid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void
|
||||
CancelSpellHook(hadesmem::PatchDetourBase *detour, bool failed, bool notifyServer,
|
||||
game::SpellCastResult reason) {
|
||||
@@ -724,11 +784,10 @@ namespace Nampower {
|
||||
}
|
||||
|
||||
void SendCastHook(hadesmem::PatchDetourBase *detour, game::SpellCast *cast, char unk) {
|
||||
auto const sendCast = detour->GetTrampolineT<SendCastT>();
|
||||
sendCast(cast, unk);
|
||||
auto const sendCast = detour->GetTrampolineT<SendCastT>();
|
||||
sendCast(cast, unk);
|
||||
|
||||
auto const spell = game::GetSpellInfo(cast->spellId);
|
||||
BeginCast(gCastData.attemptedCastTimeMs, spell, cast);
|
||||
auto const spell = game::GetSpellInfo(cast->spellId);
|
||||
BeginCast(gCastData.attemptedCastTimeMs, spell, cast);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -32,5 +32,9 @@ namespace Nampower {
|
||||
uint32_t unk3,
|
||||
float unk4);
|
||||
|
||||
uint32_t Spell_C_HandleTerrainClickHook(hadesmem::PatchDetourBase *detour, game::CTerrainClickEvent *event);
|
||||
|
||||
float Spell_C_GetSpellRadiusHook(hadesmem::PatchDetourBase *detour);
|
||||
|
||||
void SendCastHook(hadesmem::PatchDetourBase *detour, game::SpellCast *cast, char unk);
|
||||
}
|
||||
@@ -120,6 +120,9 @@ struct CastData {
|
||||
|
||||
bool normalSpellQueued;
|
||||
bool nonGcdSpellQueued;
|
||||
bool targetingSpellQueued;
|
||||
|
||||
uint32_t targetingSpellId;
|
||||
|
||||
bool castingQueuedSpell;
|
||||
uint32_t numRetries;
|
||||
|
||||
Reference in New Issue
Block a user