Commit graph

2754 commits

Author SHA1 Message Date
Kelsi
891b9e5822 fix: show friendly map names on loading screen (Outland not Expansion01)
Add mapDisplayName() with friendly names for continents: "Eastern
Kingdoms", "Kalimdor", "Outland", "Northrend". The loading screen
previously showed WDT directory names like "Expansion01" when
Map.dbc's localized name field was empty or matched the internal name.
2026-03-24 13:20:06 -07:00
Kelsi
9a6a430768 fix: track render pass subpass mode to prevent ImGui secondary violation
When parallel recording is active, the scene pass uses
VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS. Post-processing paths
(FSR/FXAA) end the scene pass and begin a new INLINE render pass for
the swapchain output. ImGui rendering must use the correct mode —
secondary buffers for SECONDARY passes, direct calls for INLINE.

Previously the check used a static condition based on enabled features
(!fsr && !fsr2 && !fxaa && parallel), which could mismatch if a
feature was enabled but initialization failed. Replace with
endFrameInlineMode_ flag that tracks the actual current render pass
mode at runtime, eliminating the validation error
VUID-vkCmdDrawIndexed-commandBuffer-recording that caused intermittent
NVIDIA driver crashes.
2026-03-24 13:05:27 -07:00
Kelsi
a152023e5e fix: add VkSampler cache to prevent sampler exhaustion crash
Validation layers revealed 9965 VkSamplers allocated against a device
limit of 4000 — every VkTexture created its own sampler even when
configurations were identical. This exhausted NVIDIA's sampler pool
and caused intermittent SIGSEGV in vkCmdBeginRenderPass.

Add a thread-safe sampler cache in VkContext that deduplicates samplers
by FNV-1a hash of all 14 VkSamplerCreateInfo fields. All texture,
render target, renderer, water, and loading screen sampler creation
now goes through getOrCreateSampler(). Textures set ownsSampler_=false
so shared samplers aren't double-freed.

Also auto-disable anisotropy in the cache when the physical device
doesn't support the samplerAnisotropy feature, fixing the validation
error VUID-VkSamplerCreateInfo-anisotropyEnable-01070.
2026-03-24 11:44:54 -07:00
Kelsi
1556559211 fix: skip VkPipelineCache on NVIDIA to prevent driver crash
VkPipelineCache causes vkCmdBeginRenderPass to SIGSEGV inside
libnvidia-glcore.so on NVIDIA 590.x drivers. Skip pipeline cache
creation on NVIDIA GPUs — NVIDIA drivers already provide built-in
shader disk caching, so the Vulkan-level cache is redundant.
Pipeline cache still works on AMD and other vendors.
2026-03-24 10:30:25 -07:00
Kelsi Rae Davis
0a32c0fa27
Merge pull request #21 from ldmonster/chore/add-classification-to-render
refactor(rendering): extract M2 classification into pure functions
2026-03-24 10:18:24 -07:00
Kelsi
d44411c304 fix: convert PLAY_OBJECT_SOUND positions to render coords for 3D audio
Entity positions are in canonical WoW coords (X=north, Y=west) but the
audio listener uses render coords (X=west, Y=north) from the camera.
Without conversion, distance attenuation was computed on swapped axes,
making NPC ambient sounds (peasant voices, etc.) play at wrong volumes
regardless of actual distance.
2026-03-24 10:17:47 -07:00
Kelsi
c09a443b18 cleanup: remove temporary PLAY_SOUND diagnostic logging 2026-03-24 10:13:31 -07:00
Kelsi
4fcb92dfdc fix: skip FSR3 frame gen on non-AMD GPUs to prevent NVIDIA driver crash
The AMD FidelityFX FSR3 runtime corrupts Vulkan driver state when
context creation fails on NVIDIA GPUs, causing vkCmdBeginRenderPass
to SIGSEGV inside libnvidia-glcore. Gate FSR3 frame gen initialization
behind isAmdGpu() check — FSR2 upscaling still works on all GPUs.
2026-03-24 10:11:21 -07:00
Kelsi
ceb8006c3d fix: prevent hang on FSR3 upscale context creation failure
When ffxCreateContext for the upscaler fails (e.g. on NVIDIA with the
AMD FidelityFX runtime), the shutdown() path called dlclose() on the
runtime library which could hang — the library's global destructors may
block waiting for GPU operations that never completed.

Skip dlclose() on context creation failure: just clean up function
pointers and mark as failed. The library stays loaded (harmless) and
the game continues with FSR2 fallback instead of hanging.
2026-03-24 10:06:57 -07:00
Kelsi
d2a396df11 feat: log GPU vendor/name at init, add PLAY_SOUND diagnostics
Log GPU name and vendor ID during VkContext initialization for easier
debugging of GPU-specific issues (FSR3, driver compat, etc.). Add
isAmdGpu()/isNvidiaGpu() accessors.

Temporarily log SMSG_PLAY_SOUND and SMSG_PLAY_OBJECT_SOUND at WARN
level (sound ID, name, file path) to diagnose unidentified ambient
NPC sounds reported by the user.
2026-03-24 09:56:54 -07:00
Paul
cbfe7d5f44 refactor(rendering): extract M2 classification into pure functions 2026-03-24 19:55:24 +03:00
Kelsi
c8c01f8ac0 perf: add Vulkan pipeline cache persistence for faster startup
Create a VkPipelineCache at device init, loaded from disk if available.
All 65 pipeline creation calls across 19 renderer files now use the
shared cache. On shutdown, the cache is serialized to disk so subsequent
launches skip redundant shader compilation.

Cache path: ~/.local/share/wowee/pipeline_cache.bin (Linux),
~/Library/Caches/wowee/ (macOS), %APPDATA%\wowee\ (Windows).
Stale/corrupt caches are handled gracefully (fallback to empty cache).
2026-03-24 09:47:03 -07:00
Kelsi
c18720f0f0 feat: server-synced bag sort, fix world map continent bounds, update docs
Inventory sort: clicking "Sort Bags" now generates CMSG_SWAP_ITEM packets
to move items server-side (one swap per frame to avoid race conditions).
Client-side sort runs immediately for visual preview; server swaps follow.
New Inventory::computeSortSwaps() computes minimal swap sequence using
selection-sort permutation on quality→itemId→stackCount comparator.

World map: fix continent bounds derivation that used intersection (max/min)
instead of union (min/max) of child zone bounds, causing continent views
to display zoomed-in/clipped.

Update README.md and docs/status.md with current features, release info,
and known gaps (v1.8.2-preview, 664 opcode handlers, NPC voices, bag
independence, CharSections auto-detect, quest GO server limitation).
2026-03-24 09:24:09 -07:00
Kelsi
62e99da1c2 fix: remove forced backpack-open from toggleBag for full bag independence
Some checks are pending
Build / Build (arm64) (push) Waiting to run
Build / Build (x86-64) (push) Waiting to run
Build / Build (macOS arm64) (push) Waiting to run
Build / Build (windows-arm64) (push) Waiting to run
Build / Build (windows-x86-64) (push) Waiting to run
Security / CodeQL (C/C++) (push) Waiting to run
Security / Semgrep (push) Waiting to run
Security / Sanitizer Build (ASan/UBSan) (push) Waiting to run
2026-03-24 08:38:06 -07:00
Kelsi
9ab70c7b1f cleanup: remove unused spline diagnostic variables 2026-03-24 08:29:43 -07:00
Kelsi
d083ac11fa fix: suppress false-positive maskBlockCount warnings for VALUES updates
VALUES update blocks don't carry an objectType field (it defaults to 0),
so the sanity check incorrectly used the non-PLAYER threshold (10) for
player character updates that legitimately need 42-46 mask blocks. Allow
up to 55 blocks for VALUES updates (could be any entity type including
PLAYER). Only enforce strict limits on CREATE_OBJECT blocks where the
objectType is known.
2026-03-24 08:23:14 -07:00
Kelsi Davis
2e136e9fdc fix: enable Vulkan portability drivers on macOS for MoltenVK compatibility
Some checks are pending
Build / Build (arm64) (push) Waiting to run
Build / Build (x86-64) (push) Waiting to run
Build / Build (macOS arm64) (push) Waiting to run
Build / Build (windows-arm64) (push) Waiting to run
Build / Build (windows-x86-64) (push) Waiting to run
Security / CodeQL (C/C++) (push) Waiting to run
Security / Semgrep (push) Waiting to run
Security / Sanitizer Build (ASan/UBSan) (push) Waiting to run
Homebrew's vulkan-loader hides portability ICDs (like MoltenVK) from
pre-instance extension enumeration by default, causing SDL2 to fail
with "doesn't implement VK_KHR_surface". Set VK_LOADER_ENABLE_PORTABILITY_DRIVERS
before loading the Vulkan library so the loader includes MoltenVK and
its surface extensions.
2026-03-23 19:16:12 -07:00
Kelsi
5e8d4e76c8 fix: allow closing any bag independently and reset off-screen positions
Remove the forced backpack-open constraint that prevented closing the
backpack while other bags were open. Each bag window is now independently
closable regardless of which others are open.

Add off-screen position reset to individual bag windows (renderBagWindow)
so bags saved at positions outside the current resolution snap back to
their default stack position.
2026-03-23 18:16:23 -07:00
Kelsi
b10c8b7aea fix: send GAMEOBJ_USE+LOOT together for chests, reset off-screen bag pos
Chest-type GOs now send CMSG_GAMEOBJ_USE immediately followed by
CMSG_LOOT in the same frame. The USE handler opens the chest, then the
LOOT handler reads the contents — both processed sequentially by the
server. Previously only CMSG_LOOT was sent (no USE), which failed on
AzerothCore because the chest wasn't activated first.

Reset the Bags window position to bottom-right if the saved position
is outside the current screen resolution (e.g. after a resolution
change or moving between monitors).
2026-03-23 18:10:16 -07:00
Kelsi
8a617e842b fix: direct CMSG_LOOT for chest GOs and increase M2 descriptor pools
Chest-type game objects (quest pickups, treasure chests) now send
CMSG_LOOT directly with the GO GUID instead of CMSG_GAMEOBJ_USE +
delayed CMSG_LOOT. The server's loot handler activates the GO and
sends SMSG_LOOT_RESPONSE in one step. The old approach failed because
CMSG_GAMEOBJ_USE opened+despawned the GO before CMSG_LOOT arrived.

Double M2 bone and material descriptor pool sizes (8192 → 16384) to
handle the increased NPC count from the spline parsing fix — patrolling
NPCs that were previously invisible now spawn correctly, exhausting
the old pool limits.
2026-03-23 17:41:42 -07:00
Kelsi
98cc282e7e fix: stop sending CMSG_LOOT after CMSG_GAMEOBJ_USE (releases server loot)
AzerothCore handles loot automatically in the CMSG_GAMEOBJ_USE handler
(calls SendLoot internally). Sending a redundant CMSG_LOOT 200ms later
triggers DoLootRelease() on the server, which closes the loot the server
just opened — before SMSG_LOOT_RESPONSE ever reaches the client. This
broke quest GO interactions (Bundle of Wood, etc.) because the loot
window never appeared and quest items were never granted.

Also remove temporary diagnostic logging from GO interaction path.
2026-03-23 17:23:49 -07:00
Kelsi
a3934807af fix: restore WMO wall collision threshold to cos(50°) ≈ 0.65
Some checks are pending
Build / Build (arm64) (push) Waiting to run
Build / Build (x86-64) (push) Waiting to run
Build / Build (macOS arm64) (push) Waiting to run
Build / Build (windows-arm64) (push) Waiting to run
Build / Build (windows-x86-64) (push) Waiting to run
Security / CodeQL (C/C++) (push) Waiting to run
Security / Semgrep (push) Waiting to run
Security / Sanitizer Build (ASan/UBSan) (push) Waiting to run
The wall/floor classification threshold was lowered from 0.65 to 0.35
in a prior optimization commit, causing surfaces at 35-65° from
horizontal (steep walls, angled building geometry) to be classified as
floors and skipped during wall collision. This allowed the player to
clip through angled WMO walls.

Restore the threshold to 0.65 (cos 50°) in both the collision grid
builder and the runtime checkWallCollision skip, matching the
MAX_WALK_SLOPE limit used for slope-slide physics.
2026-03-23 16:43:15 -07:00
Kelsi
2c3bd06898 fix: read unconditional parabolic fields in WotLK spline parsing
AzerothCore/ChromieCraft always writes verticalAcceleration(float) +
effectStartTime(uint32) after durationMod in the spline movement block,
regardless of whether the PARABOLIC spline flag (0x800) is set. The
parser only read these 8 bytes when PARABOLIC was flagged, causing it
to read the wrong offset as pointCount (0 instead of e.g. 11). This
made every patrolling NPC fail to parse — invisible with no displayId.

Also fix splineStart calculation (was off by 4 bytes) and remove
temporary diagnostic logging.
2026-03-23 16:32:59 -07:00
Kelsi
1a3146395a fix: validate splineMode in Classic spline parse to prevent desync
When a WotLK NPC has durationMod=0.0, the Classic-first spline parser
reads it as pointCount=0 and "succeeds", then consumes garbage bytes as
splineMode and endPoint. This desynchronizes the read position for all
subsequent update blocks in the packet, causing cascading failures
(truncated update mask, unknown update type) that leave NPCs without
displayIds — making them invisible.

Fix: after reading splineMode, reject the Classic parse if splineMode > 3
(valid values are 0-3) and fall through to the WotLK format parser.
2026-03-23 11:09:15 -07:00
Kelsi
503f9ed650 fix: auto-detect CharSections.dbc layout and add Blood Elf/Draenei NPC voices
CharSections.dbc has different field layouts between stock WotLK (textures
at field 4-6) and Classic/TBC/Turtle/HD-textured WotLK (VariationIndex at
field 4). Add detectCharSectionsFields() that probes field-4 values at
runtime to determine the correct layout, so both stock and modded clients
work without JSON changes.

Also add BLOODELF_MALE/FEMALE and DRAENEI_MALE/FEMALE voice types to the
NPC voice system — previously all Blood Elf and Draenei NPCs fell through
to GENERIC (random dwarf/gnome/night elf/orc mix).
2026-03-23 11:00:49 -07:00
Kelsi
d873f27070 feat: add GetNetStats (latency) and AcceptBattlefieldPort (BG queue)
Some checks are pending
Build / Build (arm64) (push) Waiting to run
Build / Build (x86-64) (push) Waiting to run
Build / Build (macOS arm64) (push) Waiting to run
Build / Build (windows-arm64) (push) Waiting to run
Build / Build (windows-x86-64) (push) Waiting to run
Security / CodeQL (C/C++) (push) Waiting to run
Security / Semgrep (push) Waiting to run
Security / Sanitizer Build (ASan/UBSan) (push) Waiting to run
GetNetStats() returns bandwidthIn, bandwidthOut, latencyHome,
latencyWorld — with real latency from getLatencyMs(). Used by
latency display addons and the default UI's network indicator.

AcceptBattlefieldPort(index, accept) accepts or declines a
battleground queue invitation. Backed by existing acceptBattlefield
and declineBattlefield methods.
2026-03-23 04:17:25 -07:00
Kelsi
a53342e23f feat: add taxi/flight path API (NumTaxiNodes, TaxiNodeName, TakeTaxiNode)
NumTaxiNodes() returns the count of taxi nodes available at the
current flight master. TaxiNodeName(index) returns the node name.
TaxiNodeGetType(index) returns whether the node is known/reachable.
TakeTaxiNode(index) activates the flight to the selected node.

Uses existing taxiNodes_ data and activateTaxi() method.
Enables flight path addons and taxi map overlay addons.
2026-03-23 04:07:34 -07:00
Kelsi
285c4caf24 feat: add quest interaction (accept, decline, complete, abandon, rewards)
AcceptQuest() / DeclineQuest() respond to quest offer dialogs.
CompleteQuest() sends quest completion to server.
AbandonQuest(questId) abandons a quest from the quest log.
GetNumQuestRewards() / GetNumQuestChoices() return reward counts
for the selected quest log entry.

Backed by existing GameHandler methods. Enables quest helper addons
to accept/decline/complete quests programmatically.
2026-03-23 03:57:53 -07:00
Kelsi
d9c12c733d feat: add gossip/NPC dialog API for quest and vendor addons
GetNumGossipOptions() returns option count for current NPC dialog.
GetGossipOptions() returns pairs of (text, type) for each option
where type is "gossip", "vendor", "taxi", "trainer", etc.
SelectGossipOption(index) selects a dialog option.
GetNumGossipAvailableQuests() / GetNumGossipActiveQuests() return
quest counts in the gossip dialog.
CloseGossip() closes the NPC dialog.

Uses existing GossipMessageData from SMSG_GOSSIP_MESSAGE handler.
Enables gossip addons and quest helper dialog interaction.
2026-03-23 03:53:54 -07:00
Kelsi
93dabe83e4 feat: add connection, equipment, focus, and realm queries
IsConnectedToServer() checks if connected to the game server.
UnequipItemSlot(slot) moves an equipped item to the backpack.
HasFocus() checks if the player has a focus target set.
GetRealmName() / GetNormalizedRealmName() return realm name.

All backed by existing GameHandler methods.
2026-03-23 03:42:26 -07:00
Kelsi
c874ffc6b6 feat: add player commands (helm/cloak toggle, PvP, minimap ping, /played)
ShowHelm() / ShowCloak() toggle helm/cloak visibility.
TogglePVP() toggles PvP flag.
Minimap_Ping(x, y) sends a minimap ping to the group.
RequestTimePlayed() requests /played data from server.

All backed by existing GameHandler methods.
2026-03-23 03:37:18 -07:00
Kelsi
9a47def27c feat: add chat channel management (Join, Leave, GetChannelName)
JoinChannelByName(name, password) joins a chat channel.
LeaveChannelByName(name) leaves a chat channel.
GetChannelName(index) returns channel info (name, header, collapsed,
channelNumber, count, active, category) — 7-field signature.

Backed by existing joinChannel/leaveChannel/getChannelByIndex methods.
Enables chat channel management addons and channel autojoining.
2026-03-23 03:33:09 -07:00
Kelsi
2b582f8a20 feat: add Logout, CancelLogout, RandomRoll, FollowUnit
Logout() sends logout request to server.
CancelLogout() cancels a pending logout.
RandomRoll(min, max) sends /roll (defaults 1-100).
FollowUnit() is a stub (requires movement system).

Backed by existing requestLogout, cancelLogout, randomRoll methods.
2026-03-23 03:27:45 -07:00
Kelsi
6c873622dc feat: add party management (InviteUnit, UninviteUnit, LeaveParty)
InviteUnit(name) invites a player to the group.
UninviteUnit(name) removes a player from the group.
LeaveParty() leaves the current party/raid.

Backed by existing inviteToGroup, uninvitePlayer, leaveGroup methods.
2026-03-23 03:22:30 -07:00
Kelsi
7927d98e79 feat: add guild management (invite, kick, promote, demote, leave, notes)
GuildInvite(name) invites a player to the guild.
GuildUninvite(name) kicks a member (uses kickGuildMember).
GuildPromote(name) / GuildDemote(name) change rank.
GuildLeave() leaves the guild.
GuildSetPublicNote(name, note) sets a member's public note.

All backed by existing GameHandler methods that send the appropriate
guild management CMSG packets.
2026-03-23 03:18:03 -07:00
Kelsi
2f68282afc feat: add DoEmote for /dance /wave /bow and 30+ emote commands
DoEmote(token) maps emote token strings to TextEmote DBC IDs and
sends them via sendTextEmote. Supports 30+ common emotes: WAVE,
BOW, DANCE, CHEER, CHICKEN, CRY, EAT, FLEX, KISS, LAUGH, POINT,
ROAR, RUDE, SALUTE, SHY, SILLY, SIT, SLEEP, SPIT, THANK, CLAP,
KNEEL, LAY, NO, YES, BEG, ANGRY, FAREWELL, HELLO, WELCOME, etc.

Targets the current target if one exists.
2026-03-23 03:12:30 -07:00
Kelsi
a503d09d9b feat: add friend/ignore management (AddFriend, RemoveFriend, AddIgnore, DelIgnore)
AddFriend(name, note) and RemoveFriend(name) manage the friends list.
AddIgnore(name) and DelIgnore(name) manage the ignore list.
ShowFriends() is a stub (friends panel is ImGui-rendered).

All backed by existing GameHandler methods that send the appropriate
CMSG_ADD_FRIEND, CMSG_DEL_FRIEND, CMSG_ADD_IGNORE, CMSG_DEL_IGNORE
packets to the server.
2026-03-23 03:07:24 -07:00
Kelsi
75db30c91e feat: add Who system API for player search addons
GetNumWhoResults() returns result count and total online players.
GetWhoInfo(index) returns name, guild, level, race, class, zone,
classFileName — the standard 7-field /who result signature.

SendWho(query) sends a /who search to the server.
SetWhoToUI() is a stub for addon compatibility.

Uses existing whoResults_ from SMSG_WHO handler and queryWho().
Enables /who replacement addons and social panel search.
2026-03-23 03:03:01 -07:00
Kelsi
da5b464cf6 feat: add IsPlayerSpell, IsCurrentSpell, and spell state queries
IsPlayerSpell(spellId) checks if the spell is in the player's known
spells set. Used by action bar addons to distinguish permanent spells
from temporary proc/buff-granted abilities.

IsCurrentSpell(spellId) checks if the spell is currently being cast.
IsSpellOverlayed() and IsAutoRepeatSpell() are stubs for addon compat.
2026-03-23 02:53:34 -07:00
Kelsi
9cd2cfa46e feat: add title API (GetCurrentTitle, GetTitleName, SetCurrentTitle)
GetCurrentTitle() returns the player's chosen title bit index.
GetTitleName(bit) returns the formatted title string from
CharTitles.dbc (e.g., "Commander %s", "%s the Explorer").
SetCurrentTitle() is a stub for title switching.

Used by title display addons and the character panel title selector.
2026-03-23 02:47:30 -07:00
Kelsi
0dc3e52d32 feat: add inspect and clear stubs for inspection addons
GetInspectSpecialization() returns the inspected player's active
talent group from the cached InspectResult data.

NotifyInspect() and ClearInspectPlayer() are stubs — inspection is
auto-triggered by the C++ side when targeting players. These prevent
nil errors in inspection addons that call them.
2026-03-23 02:38:18 -07:00
Kelsi
4f28187661 feat: add honor/arena currency, played time, and bind location
GetHonorCurrency() returns honor points from update fields.
GetArenaCurrency() returns arena points.
GetTimePlayed() returns total time played and level time played
in seconds (populated from SMSG_PLAYED_TIME).
GetBindLocation() returns the hearthstone bind zone name.

Used by currency displays, /played addons, and hearthstone tooltip.
2026-03-23 02:33:32 -07:00
Kelsi
a20984ada2 feat: add instance lockout API for raid reset tracking
GetNumSavedInstances() returns count of saved instance lockouts.
GetSavedInstanceInfo(index) returns 9-field WoW signature: name,
mapId, resetTimeRemaining, difficulty, locked, extended,
instanceIDMostSig, isRaid, maxPlayers.

Uses existing instanceLockouts_ from SMSG_RAID_INSTANCE_INFO.
Enables SavedInstances and lockout tracking addons to display
which raids/dungeons the player is locked to and when they reset.
2026-03-23 02:28:38 -07:00
Kelsi
2e9dd01d12 feat: add battleground scoreboard API for PvP addons
GetNumBattlefieldScores() returns player count in the BG scoreboard.
GetBattlefieldScore(index) returns 12-field WoW API signature: name,
killingBlows, honorableKills, deaths, honorGained, faction, rank,
race, class, classToken, damageDone, healingDone.

GetBattlefieldWinner() returns winning faction (0=Horde, 1=Alliance)
or nil if BG is still in progress.

RequestBattlefieldScoreData() sends MSG_PVP_LOG_DATA to refresh the
scoreboard from the server.

Uses existing BgScoreboardData from MSG_PVP_LOG_DATA handler.
Enables BG scoreboard addons and PvP tracking.
2026-03-23 02:17:16 -07:00
Kelsi
47e317debf feat: add UnitIsPVP, UnitIsPVPFreeForAll, and GetBattlefieldStatus
UnitIsPVP(unit) checks UNIT_FLAG_PVP (0x1000) on the unit's flags
field. Used by unit frame addons to show PvP status indicators.

UnitIsPVPFreeForAll(unit) checks for FFA PvP flag.

GetBattlefieldStatus() returns stub ("none") for addons that check
BG queue state on login. Full BG scoreboard data exists in
GameHandler but is rendered via ImGui.
2026-03-23 01:42:57 -07:00
Kelsi
d0743c5fee feat: show Unique-Equipped on items with that flag
Items with the Unique-Equipped flag (itemFlags & 0x1000000) now
display "Unique-Equipped" in the tooltip header. This is distinct
from "Unique" (maxCount=1) — Unique-Equipped means you can carry
multiple but only equip one (e.g., trinkets, rings with the flag).
2026-03-23 01:32:37 -07:00
Kelsi
757fc857cd feat: show 'This Item Begins a Quest' on quest-starting item tooltips
Items with a startQuestId now display "This Item Begins a Quest" in
gold text at the bottom of the tooltip, matching WoW's behavior.
Helps players identify quest-starting drops in their inventory.

Passes startsQuest flag through _GetItemTooltipData from the
startQuestId field in ItemQueryResponseData.
2026-03-23 01:28:28 -07:00
Kelsi
b8c33c7d9b feat: resolve spell \$o1 periodic totals from base points × ticks
Spell descriptions now substitute \$o1/\$o2/\$o3 with the total
periodic damage/healing: base_per_tick × (duration / 3sec).

Example: SW:Pain with base=4 (5 per tick), duration=18sec (6 ticks):
  Before: "Causes X Shadow damage over 18 sec"
  After:  "Causes 30 Shadow damage over 18 sec"

Combined with \$s1 (per-tick/instant) and \$d (duration), the three
most common spell template variables are now fully resolved. This
covers the vast majority of spell tooltips.
2026-03-23 01:02:31 -07:00
Kelsi
11ecc475c8 feat: resolve spell \$d duration to real seconds from SpellDuration.dbc
Spell descriptions now substitute \$d with actual duration values:
  Before: "X damage over X sec"
  After:  "30 damage over 18 sec"

Implementation:
- DurationIndex field (40) added to all expansion Spell.dbc layouts
- SpellDuration.dbc loaded during cache build: maps index → base ms
- cleanSpellDescription substitutes \$d with resolved seconds/minutes
- getSpellDuration() accessor on GameHandler

Combined with \$s1/\$s2/\$s3 from the previous commit, most common
spell description templates are now fully resolved with real values.
2026-03-23 01:00:18 -07:00
Kelsi
a5aa1faf7a feat: resolve spell \$s1/\$s2/\$s3 to real DBC damage/heal values
Spell descriptions now substitute \$s1/\$s2/\$s3 template variables
with actual effect base points from Spell.dbc (field 80/81/82).
For example: "causes \$s1 Fire Damage" → "causes 562 Fire Damage".

Implementation:
- Added EffectBasePoints0/1/2 to all 4 expansion DBC layouts
- SpellNameEntry now stores effectBasePoints[3]
- loadSpellNameCache reads base points during DBC iteration
- cleanSpellDescription substitutes \$s1→abs(base)+1 when available
- getSpellEffectBasePoints() accessor on GameHandler

Values are DBC base points (before spell power scaling). Still uses
"X" placeholder for unresolved variables (\$d, \$o1, etc.).
2026-03-23 00:51:19 -07:00