diff --git a/include/game/game_handler.hpp b/include/game/game_handler.hpp index 89c518a6..fb5d4ef5 100644 --- a/include/game/game_handler.hpp +++ b/include/game/game_handler.hpp @@ -417,7 +417,7 @@ public: void castSpell(uint32_t spellId, uint64_t targetGuid = 0); void cancelCast(); void cancelAura(uint32_t spellId); - const std::vector& getKnownSpells() const { return knownSpells; } + const std::unordered_set& getKnownSpells() const { return knownSpells; } bool isCasting() const { return casting; } uint32_t getCurrentCastSpellId() const { return currentCastSpellId; } float getCastProgress() const { return castTimeTotal > 0 ? (castTimeTotal - castTimeRemaining) / castTimeTotal : 0.0f; } @@ -1315,7 +1315,7 @@ private: uint64_t playerTransportStickyGuid_ = 0; // Last transport player was on (temporary retention) float playerTransportStickyTimer_ = 0.0f; // Seconds to keep sticky transport alive after transient clears std::unique_ptr transportManager_; // Transport movement manager - std::vector knownSpells; + std::unordered_set knownSpells; std::unordered_map spellCooldowns; // spellId -> remaining seconds uint8_t castCount = 0; bool casting = false; diff --git a/include/ui/spellbook_screen.hpp b/include/ui/spellbook_screen.hpp index 6fac7ba7..1ae5a1f6 100644 --- a/include/ui/spellbook_screen.hpp +++ b/include/ui/spellbook_screen.hpp @@ -73,7 +73,7 @@ private: void loadSpellDBC(pipeline::AssetManager* assetManager); void loadSpellIconDBC(pipeline::AssetManager* assetManager); void loadSkillLineDBCs(pipeline::AssetManager* assetManager); - void categorizeSpells(const std::vector& knownSpells); + void categorizeSpells(const std::unordered_set& knownSpells); GLuint getSpellIcon(uint32_t iconId, pipeline::AssetManager* assetManager); const SpellInfo* getSpellInfo(uint32_t spellId) const; }; diff --git a/src/audio/audio_engine.cpp b/src/audio/audio_engine.cpp index 88f0d8cb..edd60f59 100644 --- a/src/audio/audio_engine.cpp +++ b/src/audio/audio_engine.cpp @@ -25,9 +25,25 @@ struct DecodedWavCacheEntry { static std::unordered_map gDecodedWavCache; static uint64_t makeWavCacheKey(const std::vector& wavData) { - uint64_t ptr = static_cast(reinterpret_cast(wavData.data())); - uint64_t sz = static_cast(wavData.size()); - return (ptr * 11400714819323198485ull) ^ (sz + 0x9e3779b97f4a7c15ull + (ptr << 6) + (ptr >> 2)); + // FNV-1a over the first 256 bytes + last 256 bytes + total size. + // Full-content hash would be correct but slow for large files; sampling the + // edges catches virtually all distinct files while keeping cost O(1). + constexpr uint64_t FNV_OFFSET = 14695981039346656037ull; + constexpr uint64_t FNV_PRIME = 1099511628211ull; + uint64_t h = FNV_OFFSET; + auto mix = [&](uint8_t b) { h ^= b; h *= FNV_PRIME; }; + + const size_t sz = wavData.size(); + const size_t head = std::min(sz, size_t(256)); + for (size_t i = 0; i < head; ++i) mix(wavData[i]); + if (sz > 256) { + const size_t tail_start = sz > 512 ? sz - 256 : 256; + for (size_t i = tail_start; i < sz; ++i) mix(wavData[i]); + } + // Mix in the total size so files with identical head/tail but different + // lengths still produce different keys. + for (int s = 0; s < 8; ++s) mix(static_cast(sz >> (s * 8))); + return h; } static bool decodeWavCached(const std::vector& wavData, DecodedWavCacheEntry& out) { @@ -80,6 +96,12 @@ static bool decodeWavCached(const std::vector& wavData, DecodedWavCache entry.sampleRate = sampleRate; entry.frames = framesRead; entry.pcmData = pcmData; + // Evict oldest half when cache grows too large (keeps ~128 most-recent sounds) + if (gDecodedWavCache.size() >= 256) { + auto it = gDecodedWavCache.begin(); + for (size_t n = gDecodedWavCache.size() / 2; n > 0; --n, ++it) {} + gDecodedWavCache.erase(gDecodedWavCache.begin(), it); + } gDecodedWavCache.emplace(key, entry); out = entry; return true; diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index dd59bdbc..76e58b70 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -96,8 +96,8 @@ GameHandler::GameHandler() { wardenModuleManager_ = std::make_unique(); // Default spells always available - knownSpells.push_back(6603); // Attack - knownSpells.push_back(8690); // Hearthstone + knownSpells.insert(6603); // Attack + knownSpells.insert(8690); // Hearthstone // Default action bar layout actionBar[0].type = ActionBarSlot::SPELL; @@ -1053,9 +1053,8 @@ void GameHandler::handlePacket(network::Packet& packet) { // Add to known spells immediately for prerequisite re-evaluation // (SMSG_LEARNED_SPELL may come separately, but we need immediate update) - bool alreadyKnown = std::find(knownSpells.begin(), knownSpells.end(), spellId) != knownSpells.end(); - if (!alreadyKnown) { - knownSpells.push_back(spellId); + if (!knownSpells.count(spellId)) { + knownSpells.insert(spellId); LOG_INFO("Added spell ", spellId, " to known spells (trainer purchase)"); } @@ -7010,21 +7009,17 @@ void GameHandler::handleInitialSpells(network::Packet& packet) { InitialSpellsData data; if (!InitialSpellsParser::parse(packet, data)) return; - knownSpells = data.spellIds; + knownSpells = {data.spellIds.begin(), data.spellIds.end()}; // Debug: check if specific spells are in initial spells - bool has527 = std::find(knownSpells.begin(), knownSpells.end(), 527u) != knownSpells.end(); - bool has988 = std::find(knownSpells.begin(), knownSpells.end(), 988u) != knownSpells.end(); - bool has1180 = std::find(knownSpells.begin(), knownSpells.end(), 1180u) != knownSpells.end(); + bool has527 = knownSpells.count(527u); + bool has988 = knownSpells.count(988u); + bool has1180 = knownSpells.count(1180u); LOG_INFO("Initial spells include: 527=", has527, " 988=", has988, " 1180=", has1180); // Ensure Attack (6603) and Hearthstone (8690) are always present - if (std::find(knownSpells.begin(), knownSpells.end(), 6603u) == knownSpells.end()) { - knownSpells.insert(knownSpells.begin(), 6603u); - } - if (std::find(knownSpells.begin(), knownSpells.end(), 8690u) == knownSpells.end()) { - knownSpells.push_back(8690u); - } + knownSpells.insert(6603u); + knownSpells.insert(8690u); // Set initial cooldowns for (const auto& cd : data.cooldowns) { @@ -7172,7 +7167,7 @@ void GameHandler::handleAuraUpdate(network::Packet& packet, bool isAll) { void GameHandler::handleLearnedSpell(network::Packet& packet) { uint32_t spellId = packet.readUInt32(); - knownSpells.push_back(spellId); + knownSpells.insert(spellId); LOG_INFO("Learned spell: ", spellId); // Check if this spell corresponds to a talent rank @@ -7192,9 +7187,7 @@ void GameHandler::handleLearnedSpell(network::Packet& packet) { void GameHandler::handleRemovedSpell(network::Packet& packet) { uint32_t spellId = packet.readUInt32(); - knownSpells.erase( - std::remove(knownSpells.begin(), knownSpells.end(), spellId), - knownSpells.end()); + knownSpells.erase(spellId); LOG_INFO("Removed spell: ", spellId); } @@ -7204,12 +7197,10 @@ void GameHandler::handleSupercededSpell(network::Packet& packet) { uint32_t newSpellId = packet.readUInt32(); // Remove old spell - knownSpells.erase( - std::remove(knownSpells.begin(), knownSpells.end(), oldSpellId), - knownSpells.end()); + knownSpells.erase(oldSpellId); // Add new spell - knownSpells.push_back(newSpellId); + knownSpells.insert(newSpellId); LOG_INFO("Spell superceded: ", oldSpellId, " -> ", newSpellId); @@ -7226,9 +7217,7 @@ void GameHandler::handleUnlearnSpells(network::Packet& packet) { for (uint32_t i = 0; i < spellCount && packet.getSize() - packet.getReadPos() >= 4; ++i) { uint32_t spellId = packet.readUInt32(); - knownSpells.erase( - std::remove(knownSpells.begin(), knownSpells.end(), spellId), - knownSpells.end()); + knownSpells.erase(spellId); LOG_INFO(" Unlearned spell: ", spellId); } @@ -8300,8 +8289,8 @@ void GameHandler::handleTrainerList(network::Packet& packet) { } // Check if specific prerequisite spells are known - bool has527 = std::find(knownSpells.begin(), knownSpells.end(), 527u) != knownSpells.end(); - bool has25312 = std::find(knownSpells.begin(), knownSpells.end(), 25312u) != knownSpells.end(); + bool has527 = knownSpells.count(527u); + bool has25312 = knownSpells.count(25312u); LOG_INFO("Prerequisite check: 527=", has527, " 25312=", has25312); // Debug: log first few trainer spells to see their state diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index e09f5754..881cddd5 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -4860,7 +4860,7 @@ void GameScreen::renderTrainerWindow(game::GameHandler& gameHandler) { auto isKnown = [&](uint32_t id) { if (id == 0) return true; // Check if spell is in knownSpells list - bool found = std::find(knownSpells.begin(), knownSpells.end(), id) != knownSpells.end(); + bool found = knownSpells.count(id); if (found) return true; // Also check if spell is in trainer list with state=2 (explicitly known) diff --git a/src/ui/spellbook_screen.cpp b/src/ui/spellbook_screen.cpp index c1a7b8cd..41ccba86 100644 --- a/src/ui/spellbook_screen.cpp +++ b/src/ui/spellbook_screen.cpp @@ -136,7 +136,7 @@ void SpellbookScreen::loadSkillLineDBCs(pipeline::AssetManager* assetManager) { } } -void SpellbookScreen::categorizeSpells(const std::vector& knownSpells) { +void SpellbookScreen::categorizeSpells(const std::unordered_set& knownSpells) { spellTabs.clear(); // Only SkillLine category 7 ("Class") gets its own tab (the 3 specialties).