Commit graph

3082 commits

Author SHA1 Message Date
Kelsi
298974ebc2 refactor: extract markPlayerDead to deduplicate death/corpse caching
Both the health==0 and dynFlags UNIT_DYNFLAG_DEAD paths duplicated the
same corpse-position caching and death-state logic with a subtle
asymmetry (only health path called stopAutoAttack). Extracted into
markPlayerDead() so coordinate swapping and state changes happen in one
place. stopAutoAttack remains at the health==0 call site since the
dynFlags path doesn't need it.
2026-03-29 17:59:44 -07:00
Kelsi
dc500fede9 refactor: consolidate buildItemLink into game_utils.hpp
Three identical copies (game_handler.cpp, spell_handler.cpp,
quest_handler.cpp) plus two forward declarations (inventory_handler.cpp,
social_handler.cpp) replaced with a single inline definition in
game_utils.hpp. All affected files already include this header, so
quality color table changes now propagate from one source of truth.
2026-03-29 17:57:05 -07:00
Kelsi
0aff4b155c fix: dismount cleared all indefinite auras instead of just mount aura
The dismount path wiped every aura with maxDurationMs < 0, which
includes racial passives, tracking, and zone buffs — not just the mount
spell. Now only clears the specific mountAuraSpellId_ so the buff bar
stays accurate without waiting for a server aura resync.
2026-03-29 17:56:59 -07:00
Kelsi
8993b8329e fix: isReadableQuestText rejected all non-ASCII UTF-8 text
The range check (c > 0x7E) rejected UTF-8 multi-byte sequences, so quest
titles on localized servers (French, German, Russian, etc.) were treated
as unreadable binary and replaced with 'Quest #ID' placeholders. Now
allows bytes >= 0x80 while still requiring at least one ASCII letter to
distinguish real text from binary garbage.
2026-03-29 17:56:52 -07:00
Kelsi
b3abf04dbb fix: misleading indentation on PLAYER_ALIVE/PLAYER_UNGHOST event emits
The emit calls were indented at a level suggesting they were outside the
if/else blocks, but braces placed them inside. Fixed to match the actual
control flow, preventing a future maintainer from "correcting" the
indentation and accidentally changing the logic.
2026-03-29 17:52:56 -07:00
Kelsi
ec24bcd910 fix: Warrior Charge sent 3x SET_FACING by falling through to generic facing
Charge already computed facing and sent SET_FACING, but then fell through
to both the melee-ability facing block and the generic targeted-spell
facing block — sending up to 3 SET_FACING + 1 HEARTBEAT per cast. Added
facingHandled flag so only one block sends facing, reducing redundant
network traffic that could trigger server-side movement validation.
2026-03-29 17:52:51 -07:00
Kelsi
b9ecc26f50 fix: misplaced brace included book handlers inside LOOT_CLEAR_MONEY loop
The for-loop over {SMSG_LOOT_CLEAR_MONEY} was missing its closing brace,
so SMSG_READ_ITEM_OK and SMSG_READ_ITEM_FAILED registrations were inside
the loop body. Works by accident (single iteration) but fragile and
misleading — future additions to the loop would re-register book handlers.
2026-03-29 17:52:43 -07:00
Kelsi
020e016853 fix: quest reward items stuck as 'Item #ID' due to stale pending queries
Two fixes for item name resolution:

1. Clear entry from pendingItemQueries_ even when response parsing fails.
   Previously a malformed response left the entry stuck in pending forever,
   blocking all retries so the UI permanently showed "Item 12345".

2. Add 5-second periodic cleanup of pendingItemQueries_ so lost/dropped
   responses don't permanently block item info resolution.
2026-03-29 17:44:46 -07:00
Kelsi
51da88b120 fix: SMSG_ITEM_PUSH_RESULT read extra byte causing wrong item count
The handler read an extra uint8 (bag) after bagSlot, shifting all
subsequent fields by 1 byte. This caused count to straddle the count
and countInInventory fields — e.g. count=1 read as 0x03000000 (50M).

Also removes cast bar diagnostic overlay and demotes debug logs.
2026-03-29 17:30:44 -07:00
Kelsi
309fd11a7b fix: cast bar invisible due to stale ImGui saved window position
The cast bar window used ImGuiCond_FirstUseEver for positioning, so
ImGui's .ini state restored a stale off-screen position from a prior
session. Switch to ImGuiCond_Always and add NoSavedSettings flag so
the bar always renders centered near the bottom of the screen.

Also demotes remaining diagnostic logs to LOG_DEBUG.
2026-03-29 17:20:02 -07:00
Kelsi
209c257745 fix: wire SpellHandler::updateTimers and remove stale cast state members
SpellHandler::updateTimers() was never called after PR #23 extraction,
so cast bar timers, spell cooldowns, and unit cast state timers never
ticked. Also removes duplicate cast/queue/spell members left in
GameHandler that shadowed the SpellHandler versions, and fixes
MovementHandler writing to those stale members on world portal.

Demotes SMSG_SPELL_START/CAST_RESULT debug logs to LOG_DEBUG.
2026-03-29 16:49:17 -07:00
Kelsi
d32b35c583 fix: restore Classic aura flag normalization and clean up EntityController
- Restore 0x02→0x80 Classic harmful-to-WotLK debuff bit mapping in
  syncClassicAurasFromFields so downstream checks work across expansions
- Extract handleDisplayIdChange helper to deduplicate identical logic
  in onValuesUpdateUnit and onValuesUpdatePlayer
- Remove unused newItemCreated parameter from handleValuesUpdate
- Fix indentation on PLAYER_DEAD/PLAYER_ALIVE/PLAYER_UNGHOST emit calls
2026-03-29 16:29:56 -07:00
Kelsi Rae Davis
1988b53619
Merge pull request #29 from ldmonster/chore/entity-controller-refactoring
[chore] EntityController refactoring (SOLID decomposition)
2026-03-29 16:25:50 -07:00
Paul
b0a07c2472 refactor(game): apply SOLID phases 2-6 to EntityController
- split applyUpdateObjectBlock into handleCreateObject,
  handleValuesUpdate, handleMovementUpdate
-  extract concern helpers — createEntityFromBlock,
  applyPlayerTransportState, applyUnitFieldsOnCreate/OnUpdate,
  applyPlayerStatFields, dispatchEntitySpawn, trackItemOnCreate,
  updateItemOnValuesUpdate, syncClassicAurasFromFields,
  detectPlayerMountChange, updateNonPlayerTransportAttachment
- UnitFieldIndices, PlayerFieldIndices, UnitFieldUpdateResult
  structs with static resolve() — eliminate repeated fieldIndex() calls
- IObjectTypeHandler strategy interface; concrete handlers
  UnitTypeHandler, PlayerTypeHandler, GameObjectTypeHandler,
  ItemTypeHandler, CorpseTypeHandler registered in typeHandlers_ map;
  handleCreateObject and handleValuesUpdate now dispatch via
  getTypeHandler() — adding a new object type requires zero changes
  to existing handler methods
- PendingEvents member bus; all 27 inline owner_.fireAddonEvent()
  calls in the update path replaced with pendingEvents_.emit(); events
  flushed via flushPendingEvents() at the end of each handler, decoupling
  field-parse logic from the addon callback system

entity_controller.cpp: 1520-line monolith → longest method ~200 lines,
cyclomatic complexity ~180 → ~5; zero duplicated CREATE/VALUES blocks
2026-03-29 14:42:38 +03:00
Paul
f5757aca83 refactor(game): extract EntityController from GameHandler (step 1.3)
Moves entity lifecycle, name/creature/game-object caches, transport GUID
tracking, and the entire update-object pipeline out of GameHandler into a
new EntityController class (friend-class pattern, same as CombatHandler
et al.).

What moved:
- applyUpdateObjectBlock() — 1,520-line core of all entity creation,
  field updates, and movement application
- processOutOfRangeObjects() / finalizeUpdateObjectBatch()
- handleUpdateObject() / handleCompressedUpdateObject() / handleDestroyObject()
- handleNameQueryResponse() / handleCreatureQueryResponse()
- handleGameObjectQueryResponse() / handleGameObjectPageText()
- handlePageTextQueryResponse()
- enqueueUpdateObjectWork() / processPendingUpdateObjectWork()
- playerNameCache, playerClassRaceCache_, pendingNameQueries
- creatureInfoCache, pendingCreatureQueries
- gameObjectInfoCache_, pendingGameObjectQueries_
- transportGuids_, serverUpdatedTransportGuids_
- EntityManager (accessed by other handlers via getEntityManager())

8 opcodes re-registered by EntityController::registerOpcodes():
  SMSG_UPDATE_OBJECT, SMSG_COMPRESSED_UPDATE_OBJECT, SMSG_DESTROY_OBJECT,
  SMSG_NAME_QUERY_RESPONSE, SMSG_CREATURE_QUERY_RESPONSE,
  SMSG_GAMEOBJECT_QUERY_RESPONSE, SMSG_GAMEOBJECT_PAGETEXT,
  SMSG_PAGE_TEXT_QUERY_RESPONSE

Other handler files (combat, movement, social, spell, inventory, quest,
chat) updated to access EntityManager via getEntityManager() and the
name cache via getPlayerNameCache() — no logic changes.

Also included:
- .clang-tidy: add modernize-use-nodiscard,
  modernize-use-designated-initializers; set -std=c++20 in ExtraArgs
- test.sh: prepend clang's own resource include dir before GCC's to
  silence xmmintrin.h / ia32intrin.h conflicts during clang-tidy runs

Line counts:
  entity_controller.hpp  147 lines  (new)
  entity_controller.cpp  2172 lines (new)
  game_handler.cpp       8095 lines (was 10143, −2048)

Build: 0 errors, 0 warnings.
2026-03-29 08:21:27 +03:00
Kelsi
4f2a4e5520 debug: log SMSG_SPELL_START to diagnose missing cast bar
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-28 17:00:24 -07:00
Kelsi
71cf3ab737 debug: log CMSG_CAST_SPELL packet size to verify format 2026-03-28 16:56:15 -07:00
Kelsi
d68bb5a831 fix: spell facing used atan2(dy,dx) but canonical convention is atan2(-dy,dx)
The canonical yaw convention (documented in coordinates.hpp) is
atan2(-dy, dx) where X=north, Y=west. North=0, East=+PI/2.

The spell facing code used atan2(dy, dx) (no negation on dy), producing
a yaw ~77° off from the correct server orientation. The server rejected
every cast with "unit not in front" because the sent orientation pointed
in the wrong direction.

Fixed in all 3 locations: charge facing, melee facing, and general
pre-cast facing.
2026-03-28 16:51:23 -07:00
Kelsi
7f3c7379b5 debug: log pre-cast facing orientation details 2026-03-28 16:45:57 -07:00
Kelsi
4ff59c6f76 debug: log castSpell calls and SMSG_CAST_RESULT at WARNING level 2026-03-28 16:41:15 -07:00
Kelsi
1aec1c6cf1 fix: send heartbeat after SET_FACING before cast to ensure server has orientation 2026-03-28 16:27:26 -07:00
Kelsi
9cb6c596d5 fix: face target before casting any targeted spell (not just melee)
Only melee abilities sent MSG_MOVE_SET_FACING before the cast packet.
Ranged spells like Smite used whatever orientation was in movementInfo
from the last movement, causing "target not in front" server rejection.

Now sends a facing update toward the target entity before ANY targeted
spell cast. The server checks a ~180° frontal arc for most spells.
2026-03-28 16:23:27 -07:00
Kelsi
c58537e2b8 fix: load binary DBCs from Data/db/ fallback path
CreatureDisplayInfo.dbc (691KB, 24K+ entries) exists at Data/db/ but
the loader only checked DBFilesClient\ (MPQ manifest) and expansion CSV.
The CSV had only 13248 entries (malformed export), so TBC+ creatures
(Mana Wyrms, Blood Elf area) had no display data and were invisible.

Now checks Data/db/ as a fallback for binary DBCs. This path contains
pre-extracted DBCs shared across expansions. Binary DBCs have complete
record data including proper IDs.
2026-03-28 16:17:59 -07:00
Kelsi
d8c768701d fix: visible item field base 284→283 (confirmed by raw field dump)
RAW FIELDS dump shows item entries at odd indices: 283, 285, 287, 289...
With base=283, stride=2: 17 of 19 slots have valid item IDs (14200,
12020, 14378, etc). Slots 12-13 (trinkets) correctly empty.

With base=284: only 5 entries, and values are enchant IDs (913, 905, 904)
— these are the field AFTER each entry, confirming base was off by 1.
2026-03-28 16:12:31 -07:00
Kelsi
fdca990209 debug: dump raw fields for first 3 players (lowered threshold to size>20) 2026-03-28 16:06:03 -07:00
Kelsi
f74b79f1f8 debug: log outgoing heartbeat coords and chat, fix BG filter for SAY type
Heartbeat: log canonical + wire coords every 30th heartbeat to detect
if we're sending wrong position (causing server to teleport us).

Chat: log outgoing messages at WARNING level to confirm packets are sent.

BG filter: announcer uses SAY (type=0) with color codes, not SYSTEM.
Match "BG Queue Announcer" in message body regardless of chat type.
2026-03-28 16:02:36 -07:00
Kelsi
91c6eef967 fix: suspend gravity for 10s after world entry to prevent WMO fall-through
Stormwind WMO collision takes 25+ seconds to fully load. The warmup
ground check couldn't detect the WMO floor because collision data
wasn't finalized yet. Player spawned and immediately fell through
the unloaded WMO floor into the terrain below (Dun Morogh).

New approach: suspendGravityFor(10s) after world entry. Gravity is
disabled (Z position frozen) until either:
1. A floor is detected by the collision system (gravity resumes instantly)
2. The 10-second timer expires (gravity resumes as fallback)

This handles the case where WMO collision loads during the first few
seconds of gameplay — the player hovers at spawn Z until the floor
appears, then lands normally.

Also fixes faction language for chat (ORCISH for Horde, COMMON for
Alliance) and adds SMSG_MESSAGECHAT diagnostic logging.
2026-03-28 15:50:13 -07:00
Kelsi
b1e2b8866d fix: use faction-correct language for outgoing chat (COMMON vs ORCISH)
Chat was always sent with COMMON (7) language. For Horde players,
AzerothCore rejects COMMON and silently drops the message. Alliance
players nearby also couldn't see Horde messages.

Now detects player race and sends ORCISH (1) for Horde races, COMMON (7)
for Alliance. This matches what the real WoW client sends.
2026-03-28 15:42:01 -07:00
Kelsi
2f96bda6fa fix: far same-map teleport blocked packet handler for 41 seconds
loadOnlineWorldTerrain() was called directly from the worldEntryCallback
inside the packet handler, running the 20s warmup loop synchronously.
This blocked ALL packet processing and froze the game for 20-41 seconds.

Now defers the world reload to pendingWorldEntry_ which is processed on
the next frame, outside the packet handler. Position and camera snap
immediately so the player doesn't drift at the old location.

The /y respawn report was actually a server-initiated teleport (possibly
anti-spam or area trigger) that hit this 41-second blocking path.
2026-03-28 15:38:44 -07:00
Kelsi
9666b871f8 fix: BG announcer filter was suppressing ALL chat messages
The filter matched ALL chat types for patterns like "[H:" + "A:" which
are common in normal messages. Any SAY/WHISPER/GUILD message containing
both substrings was silently dropped. This broke all incoming chat.

Now only filters SYSTEM messages and only matches specific BG announcer
keywords: "Queue status", "BG Queue", "BGAnnouncer".
2026-03-28 15:31:16 -07:00
Kelsi
6edcad421b fix: group invite popup never showing (hasPendingGroupInvite stale getter)
hasPendingGroupInvite() and getPendingInviterName() were inline getters
reading GameHandler's stale copies. SocialHandler owns the canonical
pendingGroupInvite/pendingInviterName state. Players were auto-added to
groups without seeing the accept/decline popup.

Now delegates to socialHandler_.
2026-03-28 15:29:19 -07:00
Kelsi
1af1c66b04 fix: SMSG_TRADE_STATUS_EXTENDED format (whichPlayer is uint32, add missing fields)
WotLK trade packet format was wrong in multiple ways:
- whichPlayer was read as uint8, actually uint32
- Missing tradeId field (we read tradeId as tradeCount)
- Per-slot size was 52 bytes, actually 64 (missing suffixFactor,
  randomPropertyId, lockId = 12 bytes)
- tradeCount is 8 (7 trade + 1 "will not be traded"), not capped at 7

Verified: header(4+4=8) + 8×(1+64=65) + gold(4) = 532 bytes matches
the observed packet size exactly.

Note: Classic trade format differs and will need its own parser.
2026-03-28 15:24:26 -07:00
Kelsi
df9dad952d fix: handle TRADE_STATUS_UNACCEPT (status=8), revert to Open state 2026-03-28 15:21:32 -07:00
Kelsi
ce54b196e7 fix: trade COMPLETE resets state before EXTENDED can populate items/gold
SMSG_TRADE_STATUS(COMPLETE) and SMSG_TRADE_STATUS_EXTENDED arrive in the
same packet batch. COMPLETE was calling resetTradeState() which cleared
all trade slots and gold BEFORE EXTENDED could write the final data.
The trade window showed "7c" (garbage gold) because the gold field read
from the wrong offset (slot size was also wrong: 60→52 bytes).

Now COMPLETE just sets status to None without full reset, preserving
trade state for EXTENDED to populate. The TRADE_CLOSED addon event
still fires correctly.
2026-03-28 15:18:33 -07:00
Kelsi
ed7cbccceb fix: trade slot size check 60→52 bytes, add trade diagnostic logging 2026-03-28 15:14:53 -07:00
Kelsi
615db79819 fix: skip all-zero equipment emit, broaden BG announcer filter
Equipment: the first emitOtherPlayerEquipment call fired before any item
queries returned, sending all-zero displayIds that stripped players naked.
Now skips the callback when resolved=0 (waiting for queries). Equipment
only applies once at least one item resolves, preventing the naked flash.

BG announcer: broadened filter to match ALL chat types (not just SYSTEM),
and added more patterns: "BGAnnouncer", "[H: N, A: N]" with spaces.

Also added diagnostic logging in setOnlinePlayerEquipment to trace
displayId counts reaching the renderer.
2026-03-28 15:09:52 -07:00
Kelsi
12f5aaf286 fix: filter BG queue announcer spam from system chat
ChromieCraft/AzerothCore BG queue announcer module floods chat with
SYSTEM messages like "Queue status for Alterac Valley [H: 12/40, A: 15/40]".
Now filtered by detecting common patterns: "Queue status", "BG Queue",
"Announcer]", and "[H:...A:..." format.

Equipment status: resolved items ARE rendering (head, shoulders, chest,
legs confirmed with displayIds). Remaining unresolved slots (weapons)
are item queries the server hasn't responded to yet — timing issue,
not a client bug. Items trickle in over ~5 seconds as queries return.
2026-03-28 15:01:25 -07:00
Kelsi
11571c582b fix: hearthstone from action bar, far teleport loading screen
Action bar hearthstone: the slot was type SPELL (spell 8690) not ITEM.
castSpell sends CMSG_CAST_SPELL which the server rejects for item-use
spells. Now detects item-use spells via getItemIdForSpell() and routes
through useItemById() instead, sending CMSG_USE_ITEM correctly.

Far same-map teleport: hearthstone on the same continent (e.g., Westfall
→ Stormwind on Azeroth) skipped the loading screen, so the player fell
through unloaded terrain. Now triggers a full world reload with loading
screen for teleports > 500 units, with the warmup ground check ensuring
WMO floors are loaded before spawning.
2026-03-28 14:55:58 -07:00
Kelsi
4e709692f1 fix: filter officer chat for non-officers (server sends to all guild members)
Some private servers (AzerothCore/ChromieCraft) send OFFICER chat type
to all guild members regardless of rank. The real WoW client checks the
GR_RIGHT_OFFCHATLISTEN (0x80) guild rank permission before displaying.

Now checks the player's guild rank rights from the roster data and
suppresses officer chat if the permission bit is not set.
2026-03-28 14:45:51 -07:00
Kelsi
21fb2aa11c fix: backpack window jumps position when selling items (missing ##id in title) 2026-03-28 14:42:21 -07:00
Kelsi
504d112625 fix: gossip/vendor windows not closing when opening mailbox/trainer/taxi
Domain handlers were setting `owner_.gossipWindowOpen = false` directly
on GameHandler's stale member, but isGossipWindowOpen() delegates to
QuestHandler's copy. The gossip window stayed open because the
delegating getter never saw the close.

Fix: use owner_.closeGossip() / owner_.closeVendor() which properly
delegate to QuestHandler/InventoryHandler to close the canonical state.

Affected: InventoryHandler (3 sites: mail, trainer, bank opening),
MovementHandler (1 site: taxi opening), QuestHandler (2 sites: gossip
opening closes vendor).
2026-03-28 14:36:14 -07:00
Kelsi
e5959dceb5 fix: add bare-points spline fallback for flying/falling splines (0x10000) 2026-03-28 12:47:37 -07:00
Kelsi
47bea0d233 fix: use delegating getters for vendor buyback refresh (stale member read) 2026-03-28 12:45:59 -07:00
Kelsi
b81c616785 fix: delegate gossip/quest detail getters to QuestHandler (NPC dialog broken)
4 more stale getters from PR #23 split:
- isGossipWindowOpen() — QuestHandler owns gossipWindowOpen_
- getCurrentGossip() — QuestHandler owns currentGossip_
- isQuestDetailsOpen() — QuestHandler owns questDetailsOpen_
- getQuestDetails() — QuestHandler owns currentQuestDetails_

Also fix GameHandler::update() distance-close checks to use delegating
getters instead of stale member variables for vendor/gossip/taxi/trainer.

Map state (currentMapId_, worldStateZoneId_, exploredZones_) confirmed
NOT stale — domain handlers write via owner_. reference to GameHandler's
members. Those getters are correct as-is.
2026-03-28 12:43:44 -07:00
Kelsi
ee02faa183 fix: delegate all 113 stale GameHandler getters to domain handlers
PR #23 split GameHandler into 8 domain handlers but left 113 inline
getters reading stale duplicate member variables. Every feature that
relied on these getters was silently broken (showing empty/stale data):

InventoryHandler (32): bank, mail, auction house, guild bank, trainer,
  loot rolls, vendor, buyback, item text, master loot candidates
SocialHandler (43): guild roster, battlegrounds, LFG, duels, petitions,
  arena teams, instance lockouts, ready check, who results, played time
SpellHandler (10): talents, craft queue, GCD, pet unlearn, queued spell
QuestHandler (13): quest log, gossip POIs, quest offer/request windows,
  tracked quests, shared quests, NPC quest statuses
MovementHandler (15): all 8 server speeds, taxi state, taxi nodes/data

All converted from inline `{ return member_; }` to out-of-line
delegations: `return handler_ ? handler_->getter() : fallback;`
2026-03-28 12:18:14 -07:00
Kelsi
d6b387ae35 fix: increase tile-count fallback from 10s to 20s to prevent premature spawn 2026-03-28 12:04:54 -07:00
Kelsi
2633a490eb fix: remove reinterpret_cast UB in trade slot delegation
The TradeSlot structs differ between GameHandler (has bag/slot fields)
and InventoryHandler (no bag/slot). The reinterpret_cast was undefined
behavior that corrupted memory, potentially causing the teleport bug.

Now properly copies fields between the two struct layouts.

NOTE: 113 stale getters remain in GameHandler that read duplicate member
variables never updated by domain handlers. These need systematic fixing.
2026-03-28 12:02:08 -07:00
Kelsi
f37994cc1b fix: trade accept dialog not showing (stale state from domain handler split)
GameHandler::hasPendingTradeRequest() and all trade getters were reading
GameHandler's own tradeStatus_/tradeSlots_ which are never written after
the PR #23 split. InventoryHandler owns the canonical trade state.

Delegate all trade getters to InventoryHandler:
- getTradeStatus, hasPendingTradeRequest, isTradeOpen, getTradePeerName
- getMyTradeSlots, getPeerTradeSlots, getMyTradeGold, getPeerTradeGold

Also fix InventoryHandler::isTradeOpen() to include Accepted state.
2026-03-28 11:58:02 -07:00
Kelsi
99ac31987f fix: add missing <algorithm> include for std::clamp (Windows build) 2026-03-28 11:51:34 -07:00
Kelsi
b9ac3de498 tweak: camera defaults stiffness=30, pivot height=1.6 2026-03-28 11:45:21 -07:00