mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-04-17 09:33:51 +00:00
Add server info commands: /time, /played, and /who
- Add CMSG_QUERY_TIME (0x1CE) and SMSG_QUERY_TIME_RESPONSE (0x1CF) opcodes - Add CMSG_REQUEST_PLAYED_TIME (0x1CC) and SMSG_PLAYED_TIME (0x1CD) opcodes - Add CMSG_WHO (0x062) and SMSG_WHO (0x063) opcodes - Implement /time command to query and display server time - Implement /played command to show total and level playtime statistics - Implement /who [name] command to list online players with level and guild - Add packet builders: QueryTimePacket, RequestPlayedTimePacket, WhoPacket - Add response parsers for all three server info packet types - Add handlers that format and display responses in chat as system messages - Format played time as "X days, Y hours, Z minutes" for readability - Format server time as "YYYY-MM-DD HH:MM:SS" for readability
This commit is contained in:
parent
8b8e32e716
commit
f9c4cbddee
6 changed files with 287 additions and 0 deletions
|
|
@ -7,6 +7,7 @@
|
|||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cctype>
|
||||
#include <ctime>
|
||||
#include <random>
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
|
|
@ -286,6 +287,24 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
}
|
||||
break;
|
||||
|
||||
case Opcode::SMSG_QUERY_TIME_RESPONSE:
|
||||
if (state == WorldState::IN_WORLD) {
|
||||
handleQueryTimeResponse(packet);
|
||||
}
|
||||
break;
|
||||
|
||||
case Opcode::SMSG_PLAYED_TIME:
|
||||
if (state == WorldState::IN_WORLD) {
|
||||
handlePlayedTime(packet);
|
||||
}
|
||||
break;
|
||||
|
||||
case Opcode::SMSG_WHO:
|
||||
if (state == WorldState::IN_WORLD) {
|
||||
handleWho(packet);
|
||||
}
|
||||
break;
|
||||
|
||||
// ---- Phase 1: Foundation ----
|
||||
case Opcode::SMSG_NAME_QUERY_RESPONSE:
|
||||
handleNameQueryResponse(packet);
|
||||
|
|
@ -1530,6 +1549,39 @@ void GameHandler::inspectTarget() {
|
|||
LOG_INFO("Sent inspect request for player: ", name, " (GUID: 0x", std::hex, targetGuid, std::dec, ")");
|
||||
}
|
||||
|
||||
void GameHandler::queryServerTime() {
|
||||
if (state != WorldState::IN_WORLD || !socket) {
|
||||
LOG_WARNING("Cannot query time: not in world or not connected");
|
||||
return;
|
||||
}
|
||||
|
||||
auto packet = QueryTimePacket::build();
|
||||
socket->send(packet);
|
||||
LOG_INFO("Requested server time");
|
||||
}
|
||||
|
||||
void GameHandler::requestPlayedTime() {
|
||||
if (state != WorldState::IN_WORLD || !socket) {
|
||||
LOG_WARNING("Cannot request played time: not in world or not connected");
|
||||
return;
|
||||
}
|
||||
|
||||
auto packet = RequestPlayedTimePacket::build(true);
|
||||
socket->send(packet);
|
||||
LOG_INFO("Requested played time");
|
||||
}
|
||||
|
||||
void GameHandler::queryWho(const std::string& playerName) {
|
||||
if (state != WorldState::IN_WORLD || !socket) {
|
||||
LOG_WARNING("Cannot query who: not in world or not connected");
|
||||
return;
|
||||
}
|
||||
|
||||
auto packet = WhoPacket::build(0, 0, playerName);
|
||||
socket->send(packet);
|
||||
LOG_INFO("Sent WHO query", playerName.empty() ? "" : " for: " + playerName);
|
||||
}
|
||||
|
||||
void GameHandler::releaseSpirit() {
|
||||
if (!playerDead_) return;
|
||||
if (socket && state == WorldState::IN_WORLD) {
|
||||
|
|
@ -2792,6 +2844,97 @@ void GameHandler::addSystemChatMessage(const std::string& message) {
|
|||
addLocalChatMessage(msg);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Server Info Command Handlers
|
||||
// ============================================================
|
||||
|
||||
void GameHandler::handleQueryTimeResponse(network::Packet& packet) {
|
||||
QueryTimeResponseData data;
|
||||
if (!QueryTimeResponseParser::parse(packet, data)) {
|
||||
LOG_WARNING("Failed to parse SMSG_QUERY_TIME_RESPONSE");
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert Unix timestamp to readable format
|
||||
time_t serverTime = static_cast<time_t>(data.serverTime);
|
||||
struct tm* timeInfo = localtime(&serverTime);
|
||||
char timeStr[64];
|
||||
strftime(timeStr, sizeof(timeStr), "%Y-%m-%d %H:%M:%S", timeInfo);
|
||||
|
||||
std::string msg = "Server time: " + std::string(timeStr);
|
||||
addSystemChatMessage(msg);
|
||||
LOG_INFO("Server time: ", data.serverTime, " (", timeStr, ")");
|
||||
}
|
||||
|
||||
void GameHandler::handlePlayedTime(network::Packet& packet) {
|
||||
PlayedTimeData data;
|
||||
if (!PlayedTimeParser::parse(packet, data)) {
|
||||
LOG_WARNING("Failed to parse SMSG_PLAYED_TIME");
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.triggerMessage) {
|
||||
// Format total time played
|
||||
uint32_t totalDays = data.totalTimePlayed / 86400;
|
||||
uint32_t totalHours = (data.totalTimePlayed % 86400) / 3600;
|
||||
uint32_t totalMinutes = (data.totalTimePlayed % 3600) / 60;
|
||||
|
||||
// Format level time played
|
||||
uint32_t levelDays = data.levelTimePlayed / 86400;
|
||||
uint32_t levelHours = (data.levelTimePlayed % 86400) / 3600;
|
||||
uint32_t levelMinutes = (data.levelTimePlayed % 3600) / 60;
|
||||
|
||||
std::string totalMsg = "Total time played: ";
|
||||
if (totalDays > 0) totalMsg += std::to_string(totalDays) + " days, ";
|
||||
if (totalHours > 0 || totalDays > 0) totalMsg += std::to_string(totalHours) + " hours, ";
|
||||
totalMsg += std::to_string(totalMinutes) + " minutes";
|
||||
|
||||
std::string levelMsg = "Time played this level: ";
|
||||
if (levelDays > 0) levelMsg += std::to_string(levelDays) + " days, ";
|
||||
if (levelHours > 0 || levelDays > 0) levelMsg += std::to_string(levelHours) + " hours, ";
|
||||
levelMsg += std::to_string(levelMinutes) + " minutes";
|
||||
|
||||
addSystemChatMessage(totalMsg);
|
||||
addSystemChatMessage(levelMsg);
|
||||
}
|
||||
|
||||
LOG_INFO("Played time: total=", data.totalTimePlayed, "s, level=", data.levelTimePlayed, "s");
|
||||
}
|
||||
|
||||
void GameHandler::handleWho(network::Packet& packet) {
|
||||
// Parse WHO response
|
||||
uint32_t displayCount = packet.readUInt32();
|
||||
uint32_t onlineCount = packet.readUInt32();
|
||||
|
||||
LOG_INFO("WHO response: ", displayCount, " players displayed, ", onlineCount, " total online");
|
||||
|
||||
if (displayCount == 0) {
|
||||
addSystemChatMessage("No players found.");
|
||||
return;
|
||||
}
|
||||
|
||||
addSystemChatMessage(std::to_string(onlineCount) + " player(s) online:");
|
||||
|
||||
for (uint32_t i = 0; i < displayCount; ++i) {
|
||||
std::string playerName = packet.readString();
|
||||
std::string guildName = packet.readString();
|
||||
uint32_t level = packet.readUInt32();
|
||||
uint32_t classId = packet.readUInt32();
|
||||
uint32_t raceId = packet.readUInt32();
|
||||
packet.readUInt8(); // gender (unused)
|
||||
packet.readUInt32(); // zoneId (unused)
|
||||
|
||||
std::string msg = " " + playerName;
|
||||
if (!guildName.empty()) {
|
||||
msg += " <" + guildName + ">";
|
||||
}
|
||||
msg += " - Level " + std::to_string(level);
|
||||
|
||||
addSystemChatMessage(msg);
|
||||
LOG_INFO(" ", playerName, " (", guildName, ") Lv", level, " Class:", classId, " Race:", raceId);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t GameHandler::generateClientSeed() {
|
||||
// Generate cryptographically random seed
|
||||
std::random_device rd;
|
||||
|
|
|
|||
|
|
@ -1181,6 +1181,55 @@ network::Packet InspectPacket::build(uint64_t targetGuid) {
|
|||
return packet;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Server Info Commands
|
||||
// ============================================================
|
||||
|
||||
network::Packet QueryTimePacket::build() {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_QUERY_TIME));
|
||||
LOG_DEBUG("Built CMSG_QUERY_TIME");
|
||||
return packet;
|
||||
}
|
||||
|
||||
bool QueryTimeResponseParser::parse(network::Packet& packet, QueryTimeResponseData& data) {
|
||||
data.serverTime = packet.readUInt32();
|
||||
data.timeOffset = packet.readUInt32();
|
||||
LOG_DEBUG("Parsed SMSG_QUERY_TIME_RESPONSE: time=", data.serverTime, " offset=", data.timeOffset);
|
||||
return true;
|
||||
}
|
||||
|
||||
network::Packet RequestPlayedTimePacket::build(bool sendToChat) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_REQUEST_PLAYED_TIME));
|
||||
packet.writeUInt8(sendToChat ? 1 : 0);
|
||||
LOG_DEBUG("Built CMSG_REQUEST_PLAYED_TIME: sendToChat=", sendToChat);
|
||||
return packet;
|
||||
}
|
||||
|
||||
bool PlayedTimeParser::parse(network::Packet& packet, PlayedTimeData& data) {
|
||||
data.totalTimePlayed = packet.readUInt32();
|
||||
data.levelTimePlayed = packet.readUInt32();
|
||||
data.triggerMessage = packet.readUInt8() != 0;
|
||||
LOG_DEBUG("Parsed SMSG_PLAYED_TIME: total=", data.totalTimePlayed, " level=", data.levelTimePlayed);
|
||||
return true;
|
||||
}
|
||||
|
||||
network::Packet WhoPacket::build(uint32_t minLevel, uint32_t maxLevel,
|
||||
const std::string& playerName,
|
||||
const std::string& guildName,
|
||||
uint32_t raceMask, uint32_t classMask,
|
||||
uint32_t zones) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_WHO));
|
||||
packet.writeUInt32(minLevel);
|
||||
packet.writeUInt32(maxLevel);
|
||||
packet.writeString(playerName);
|
||||
packet.writeString(guildName);
|
||||
packet.writeUInt32(raceMask);
|
||||
packet.writeUInt32(classMask);
|
||||
packet.writeUInt32(zones); // Number of zones
|
||||
LOG_DEBUG("Built CMSG_WHO: player=", playerName);
|
||||
return packet;
|
||||
}
|
||||
|
||||
network::Packet NameQueryPacket::build(uint64_t playerGuid) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_NAME_QUERY));
|
||||
packet.writeUInt64(playerGuid);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue