|
| 1 | +/* |
| 2 | + * GM chat commands for the server-side debug visualization toolkit: |
| 3 | + * .debug vis cells|los|path|collision|height |
| 4 | + * Each draws a one-shot, auto-despawning set of markers (engine-style debug draw). |
| 5 | + */ |
| 6 | + |
| 7 | +#include "Chat.h" |
| 8 | +#include "DebugVis.h" |
| 9 | +#include "PerformanceMonitor.h" |
| 10 | +#include "Player.h" |
| 11 | +#include "Map.h" |
| 12 | +#include "World.h" |
| 13 | +#include "GridDefines.h" |
| 14 | +#include "PathFinder.h" |
| 15 | + |
| 16 | +#include <cmath> |
| 17 | +#include <cstdlib> |
| 18 | +#include <cstdio> |
| 19 | + |
| 20 | +bool ChatHandler::HandleDebugVisCellsCommand(char* args) |
| 21 | +{ |
| 22 | + Player* player = m_session ? m_session->GetPlayer() : NULL; |
| 23 | + if (!player) |
| 24 | + return false; |
| 25 | + |
| 26 | + int radius = 2; |
| 27 | + if (args && *args) |
| 28 | + radius = atoi(args); |
| 29 | + if (radius < 1) radius = 1; |
| 30 | + if (radius > 5) radius = 5; // (2r+1)^2 markers cap |
| 31 | + |
| 32 | + Map* map = player->GetMap(); |
| 33 | + const float cell = SIZE_OF_GRID_CELL; |
| 34 | + // Snap to the cell lattice so markers line up with cell spacing. |
| 35 | + float baseX = floor(player->GetPositionX() / cell) * cell; |
| 36 | + float baseY = floor(player->GetPositionY() / cell) * cell; |
| 37 | + |
| 38 | + uint32 placed = 0; |
| 39 | + for (int i = -radius; i <= radius; ++i) |
| 40 | + { |
| 41 | + for (int j = -radius; j <= radius; ++j) |
| 42 | + { |
| 43 | + float wx = baseX + i * cell; |
| 44 | + float wy = baseY + j * cell; |
| 45 | + // Search ground from well above the player's elevation so distant cells |
| 46 | + // snap to real terrain; skip cells with no ground beneath (was placing |
| 47 | + // markers at the player's Z, which left them floating in mid-air). |
| 48 | + float wz = map->GetHeight(wx, wy, player->GetPositionZ() + 50.0f); |
| 49 | + if (wz < -50000.0f) |
| 50 | + continue; |
| 51 | + char lbl[160]; |
| 52 | + snprintf(lbl, sizeof(lbl), "DebugVis CELL [%+d,%+d]\n(%.1f, %.1f, %.1f) ground Z=%.1f", |
| 53 | + i, j, wx, wy, wz, wz); |
| 54 | + if (DebugVis::Marker(player, DebugVis::DV_CELL, wx, wy, wz, lbl)) |
| 55 | + ++placed; |
| 56 | + } |
| 57 | + } |
| 58 | + PSendSysMessage("DebugVis: drew %u cell-grid markers (cell=%.1f yd, radius=%d). Despawn in %us.", |
| 59 | + placed, cell, radius, DebugVis::DespawnSeconds()); |
| 60 | + return true; |
| 61 | +} |
| 62 | + |
| 63 | +bool ChatHandler::HandleDebugVisLosCommand(char* /*args*/) |
| 64 | +{ |
| 65 | + Player* player = m_session ? m_session->GetPlayer() : NULL; |
| 66 | + if (!player) |
| 67 | + return false; |
| 68 | + |
| 69 | + Unit* target = getSelectedUnit(); |
| 70 | + if (!target) |
| 71 | + { |
| 72 | + SendSysMessage(".debug vis los FAILED: no target selected. It draws the line of sight " |
| 73 | + "from you to a unit (green = clear, red = blocked, with the hit point marked). " |
| 74 | + "Select/target a creature or player, then run .debug vis los again."); |
| 75 | + SetSentErrorMessage(true); |
| 76 | + return false; |
| 77 | + } |
| 78 | + |
| 79 | + Map* map = player->GetMap(); |
| 80 | + float x1 = player->GetPositionX(), y1 = player->GetPositionY(), z1 = player->GetPositionZ() + 2.0f; |
| 81 | + float x2 = target->GetPositionX(), y2 = target->GetPositionY(), z2 = target->GetPositionZ() + 2.0f; |
| 82 | + |
| 83 | + float total = sqrtf((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1) + (z2 - z1) * (z2 - z1)); |
| 84 | + if (map->IsInLineOfSight(x1, y1, z1, x2, y2, z2)) |
| 85 | + { |
| 86 | + char lbl[160]; |
| 87 | + snprintf(lbl, sizeof(lbl), "DebugVis LoS: CLEAR\nto %s, %.1f yd", target->GetName(), total); |
| 88 | + DebugVis::Line(player, DebugVis::DV_LOS_OK, x1, y1, z1, x2, y2, z2, 2.0f, lbl); |
| 89 | + SendSysMessage("DebugVis: line of sight is CLEAR (green)."); |
| 90 | + } |
| 91 | + else |
| 92 | + { |
| 93 | + float hx = x2, hy = y2, hz = z2; |
| 94 | + map->GetHitPosition(x1, y1, z1, hx, hy, hz, -0.5f); |
| 95 | + float hitDist = sqrtf((hx - x1) * (hx - x1) + (hy - y1) * (hy - y1) + (hz - z1) * (hz - z1)); |
| 96 | + DebugVis::Line(player, DebugVis::DV_LOS_BLOCK, x1, y1, z1, hx, hy, hz, 2.0f, "DebugVis LoS: BLOCKED"); |
| 97 | + char lbl[192]; |
| 98 | + snprintf(lbl, sizeof(lbl), |
| 99 | + "DebugVis LoS BLOCKED\nhit (%.1f, %.1f, %.1f)\n%.1f yd from you (target %.1f yd)", |
| 100 | + hx, hy, hz, hitDist, total); |
| 101 | + DebugVis::Marker(player, DebugVis::DV_HITPOINT, hx, hy, hz, lbl); |
| 102 | + PSendSysMessage("DebugVis: line of sight BLOCKED (red); hit at (%.1f, %.1f, %.1f).", hx, hy, hz); |
| 103 | + } |
| 104 | + return true; |
| 105 | +} |
| 106 | + |
| 107 | +bool ChatHandler::HandleDebugVisPathCommand(char* /*args*/) |
| 108 | +{ |
| 109 | + Player* player = m_session ? m_session->GetPlayer() : NULL; |
| 110 | + if (!player) |
| 111 | + return false; |
| 112 | + |
| 113 | + Unit* target = getSelectedUnit(); |
| 114 | + if (!target) |
| 115 | + { |
| 116 | + SendSysMessage(".debug vis path FAILED: no target selected. It draws the navmesh path " |
| 117 | + "from you to a unit (green = valid path, red = incomplete/none). " |
| 118 | + "Select/target a creature or player, then run .debug vis path again."); |
| 119 | + SetSentErrorMessage(true); |
| 120 | + return false; |
| 121 | + } |
| 122 | + |
| 123 | + PathFinder pf(player); |
| 124 | + pf.calculate(target->GetPositionX(), target->GetPositionY(), target->GetPositionZ()); |
| 125 | + PathType type = pf.getPathType(); |
| 126 | + PointsArray& pts = pf.getPath(); |
| 127 | + |
| 128 | + DebugVis::Category cat = (type & (PATHFIND_NOPATH | PATHFIND_INCOMPLETE | PATHFIND_NOT_USING_PATH)) |
| 129 | + ? DebugVis::DV_PATH_BAD : DebugVis::DV_PATH; |
| 130 | + |
| 131 | + uint32 total = uint32(pts.size()); |
| 132 | + uint32 placed = 0; |
| 133 | + uint32 idx = 0; |
| 134 | + for (PointsArray::const_iterator it = pts.begin(); it != pts.end(); ++it, ++idx) |
| 135 | + { |
| 136 | + // Skip the path point on/next to the player — markers are solid and would |
| 137 | + // trap the caster inside the object. |
| 138 | + float pdx = it->x - player->GetPositionX(); |
| 139 | + float pdy = it->y - player->GetPositionY(); |
| 140 | + if (pdx * pdx + pdy * pdy < 9.0f) // within ~3 yd |
| 141 | + continue; |
| 142 | + char lbl[176]; |
| 143 | + snprintf(lbl, sizeof(lbl), |
| 144 | + "DebugVis PATH pt %u/%u\n(%.1f, %.1f, %.1f)\ntype=0x%X (%s)", |
| 145 | + idx + 1, total, it->x, it->y, it->z, uint32(type), |
| 146 | + cat == DebugVis::DV_PATH ? "ok" : "incomplete/none"); |
| 147 | + if (DebugVis::Marker(player, cat, it->x, it->y, it->z, lbl)) |
| 148 | + ++placed; |
| 149 | + } |
| 150 | + |
| 151 | + PSendSysMessage("DebugVis: path type=0x%X, %u points (%s).", |
| 152 | + uint32(type), placed, cat == DebugVis::DV_PATH ? "green=ok" : "red=incomplete/none"); |
| 153 | + return true; |
| 154 | +} |
| 155 | + |
| 156 | +bool ChatHandler::HandleDebugVisCollisionCommand(char* args) |
| 157 | +{ |
| 158 | + Player* player = m_session ? m_session->GetPlayer() : NULL; |
| 159 | + if (!player) |
| 160 | + return false; |
| 161 | + |
| 162 | + float dist = 40.0f; |
| 163 | + if (args && *args) |
| 164 | + { |
| 165 | + float v = (float)atof(args); |
| 166 | + if (v > 1.0f && v < 300.0f) |
| 167 | + dist = v; |
| 168 | + } |
| 169 | + |
| 170 | + Map* map = player->GetMap(); |
| 171 | + float o = player->GetOrientation(); |
| 172 | + float x1 = player->GetPositionX(), y1 = player->GetPositionY(), z1 = player->GetPositionZ() + 2.0f; |
| 173 | + float x2 = x1 + cos(o) * dist, y2 = y1 + sin(o) * dist, z2 = z1; |
| 174 | + |
| 175 | + float hx = x2, hy = y2, hz = z2; |
| 176 | + if (map->GetHitPosition(x1, y1, z1, hx, hy, hz, -0.5f)) |
| 177 | + { |
| 178 | + float hitDist = sqrtf((hx - x1) * (hx - x1) + (hy - y1) * (hy - y1)); |
| 179 | + DebugVis::Line(player, DebugVis::DV_COLLISION, x1, y1, z1, hx, hy, hz, 2.0f, "DebugVis: collision ray"); |
| 180 | + char lbl[176]; |
| 181 | + snprintf(lbl, sizeof(lbl), "DebugVis COLLISION\nhit (%.1f, %.1f, %.1f)\n%.1f yd ahead", |
| 182 | + hx, hy, hz, hitDist); |
| 183 | + DebugVis::Marker(player, DebugVis::DV_HITPOINT, hx, hy, hz, lbl); |
| 184 | + PSendSysMessage("DebugVis: collision at (%.1f, %.1f, %.1f), %.1f yd ahead.", hx, hy, hz, hitDist); |
| 185 | + } |
| 186 | + else |
| 187 | + { |
| 188 | + char lbl[96]; |
| 189 | + snprintf(lbl, sizeof(lbl), "DebugVis: no collision\nwithin %.0f yd ahead", dist); |
| 190 | + DebugVis::Line(player, DebugVis::DV_COLLISION, x1, y1, z1, x2, y2, z2, 2.0f, lbl); |
| 191 | + PSendSysMessage("DebugVis: no collision within %.0f yd ahead.", dist); |
| 192 | + } |
| 193 | + return true; |
| 194 | +} |
| 195 | + |
| 196 | +bool ChatHandler::HandleDebugVisualCommand(char* args) |
| 197 | +{ |
| 198 | + Player* player = m_session ? m_session->GetPlayer() : NULL; |
| 199 | + if (!player) |
| 200 | + return false; |
| 201 | + if (!args || !*args) |
| 202 | + { |
| 203 | + // NOTE: SMSG_PLAY_SPELL_VISUAL takes a SpellVisualKit.dbc id (not SpellVisual). |
| 204 | + // 179 is confirmed working (trainer learn-spell sparkle). Valid kit ids in the |
| 205 | + // 1.12.1 client run 1..6757; good ones to try below. |
| 206 | + SendSysMessage("Usage: .debug visual <SpellVisualKit id> (1-6757). Confirmed working: 179. " |
| 207 | + "Try also 224, 300, 451, 686, 1027, 5670."); |
| 208 | + SetSentErrorMessage(true); |
| 209 | + return false; |
| 210 | + } |
| 211 | + uint32 id = uint32(atoi(args)); |
| 212 | + if (!id) |
| 213 | + { |
| 214 | + SendSysMessage("DebugVis: invalid visual id."); |
| 215 | + SetSentErrorMessage(true); |
| 216 | + return false; |
| 217 | + } |
| 218 | + player->PlaySpellVisual(id); |
| 219 | + PSendSysMessage("DebugVis: played spell visual kit %u on you (visible to nearby players).", id); |
| 220 | + return true; |
| 221 | +} |
| 222 | + |
| 223 | +bool ChatHandler::HandleDebugPerfCommand(char* args) |
| 224 | +{ |
| 225 | + if (args && *args && (*args == 'r' || *args == 'R')) |
| 226 | + { |
| 227 | + PerformanceMonitor::Reset(); |
| 228 | + SendSysMessage("PerformanceMonitor: stats reset."); |
| 229 | + return true; |
| 230 | + } |
| 231 | + |
| 232 | + uint32 ticks, avgMs, maxMs, lastMs; |
| 233 | + PerformanceMonitor::GetStats(ticks, avgMs, maxMs, lastMs); |
| 234 | + PSendSysMessage("Server perf: world ticks=%u avg=%ums max=%ums last=%ums (.debug perf r to reset)", |
| 235 | + ticks, avgMs, maxMs, lastMs); |
| 236 | + return true; |
| 237 | +} |
| 238 | + |
| 239 | +bool ChatHandler::HandleDebugVisHeightCommand(char* /*args*/) |
| 240 | +{ |
| 241 | + Player* player = m_session ? m_session->GetPlayer() : NULL; |
| 242 | + if (!player) |
| 243 | + return false; |
| 244 | + |
| 245 | + Map* map = player->GetMap(); |
| 246 | + float px = player->GetPositionX(), py = player->GetPositionY(), pz = player->GetPositionZ(); |
| 247 | + float groundZ = map->GetHeight(px, py, pz); |
| 248 | + |
| 249 | + if (groundZ < -50000.0f) |
| 250 | + { |
| 251 | + SendSysMessage("DebugVis: no terrain height data at this position."); |
| 252 | + return true; |
| 253 | + } |
| 254 | + |
| 255 | + char lbl[160]; |
| 256 | + snprintf(lbl, sizeof(lbl), "DebugVis HEIGHT\nground Z=%.2f\nyou Z=%.2f (delta %.2f)", |
| 257 | + groundZ, pz, pz - groundZ); |
| 258 | + DebugVis::Marker(player, DebugVis::DV_HEIGHT, px, py, groundZ, lbl); |
| 259 | + PSendSysMessage("DebugVis: ground Z=%.2f, you Z=%.2f (delta %.2f).", groundZ, pz, pz - groundZ); |
| 260 | + return true; |
| 261 | +} |
| 262 | + |
| 263 | +bool ChatHandler::HandleDebugVisClearCommand(char* /*args*/) |
| 264 | +{ |
| 265 | + Player* player = m_session ? m_session->GetPlayer() : NULL; |
| 266 | + if (!player) |
| 267 | + return false; |
| 268 | + |
| 269 | + uint32 removed = DebugVis::Clear(player); |
| 270 | + PSendSysMessage("DebugVis: cleared %u of your markers (the rest had already auto-despawned). " |
| 271 | + "Markers also auto-clear after %u seconds.", removed, DebugVis::DespawnSeconds()); |
| 272 | + return true; |
| 273 | +} |
0 commit comments