mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-04-03 20:03:50 +00:00
fix: inspect (packed GUID), follow (client-side auto-walk); add loot/raid commands
Inspect: CMSG_INSPECT was writing full uint64 GUID instead of packed GUID. Server silently rejected the malformed packet. Fixed both InspectPacket and QueryInspectAchievementsPacket to use writePackedGuid(). Follow: was a no-op (only stored GUID). Added client-side auto-follow system: camera controller walks toward followed entity, faces target, cancels on WASD/mouse input, stops within 3 units, cancels at 40+ units distance. Party commands: - /lootmethod (ffa/roundrobin/master/group/nbg) sends CMSG_LOOT_METHOD - /lootthreshold (0-5 or quality name) sets minimum loot quality - /raidconvert converts party to raid (leader only) Equipment diagnostic logging still active for debugging naked players.
This commit is contained in:
parent
16fc3ebfdf
commit
b366773f29
8 changed files with 264 additions and 8 deletions
|
|
@ -1122,6 +1122,10 @@ public:
|
|||
using CameraShakeCallback = std::function<void(float magnitude, float frequency, float duration)>;
|
||||
void setCameraShakeCallback(CameraShakeCallback cb) { cameraShakeCallback_ = std::move(cb); }
|
||||
|
||||
// Auto-follow callback: pass render-space position pointer to start, nullptr to cancel.
|
||||
using AutoFollowCallback = std::function<void(const glm::vec3* renderPos)>;
|
||||
void setAutoFollowCallback(AutoFollowCallback cb) { autoFollowCallback_ = std::move(cb); }
|
||||
|
||||
// Unstuck callback (resets player Z to floor height)
|
||||
using UnstuckCallback = std::function<void()>;
|
||||
void setUnstuckCallback(UnstuckCallback cb) { unstuckCallback_ = std::move(cb); }
|
||||
|
|
@ -1352,6 +1356,8 @@ public:
|
|||
void acceptGroupInvite();
|
||||
void declineGroupInvite();
|
||||
void leaveGroup();
|
||||
void convertToRaid();
|
||||
void sendSetLootMethod(uint32_t method, uint32_t threshold, uint64_t masterLooterGuid);
|
||||
bool isInGroup() const { return !partyData.isEmpty(); }
|
||||
const GroupListData& getPartyData() const { return partyData; }
|
||||
const std::vector<ContactEntry>& getContacts() const { return contacts_; }
|
||||
|
|
@ -2812,6 +2818,7 @@ private:
|
|||
|
||||
// ---- Follow state ----
|
||||
uint64_t followTargetGuid_ = 0;
|
||||
glm::vec3 followRenderPos_{0.0f}; // Render-space position of followed entity (updated each frame)
|
||||
|
||||
// ---- AFK/DND status ----
|
||||
bool afkStatus_ = false;
|
||||
|
|
@ -2905,6 +2912,7 @@ private:
|
|||
WorldEntryCallback worldEntryCallback_;
|
||||
KnockBackCallback knockBackCallback_;
|
||||
CameraShakeCallback cameraShakeCallback_;
|
||||
AutoFollowCallback autoFollowCallback_;
|
||||
UnstuckCallback unstuckCallback_;
|
||||
UnstuckCallback unstuckGyCallback_;
|
||||
UnstuckCallback unstuckHearthCallback_;
|
||||
|
|
|
|||
|
|
@ -1315,6 +1315,23 @@ public:
|
|||
static network::Packet build();
|
||||
};
|
||||
|
||||
/** CMSG_GROUP_RAID_CONVERT packet builder */
|
||||
class GroupRaidConvertPacket {
|
||||
public:
|
||||
static network::Packet build();
|
||||
};
|
||||
|
||||
/** CMSG_LOOT_METHOD packet builder */
|
||||
class SetLootMethodPacket {
|
||||
public:
|
||||
/**
|
||||
* @param method 0=FFA, 1=RoundRobin, 2=MasterLoot, 3=GroupLoot, 4=NeedBeforeGreed
|
||||
* @param threshold item quality threshold (0-6)
|
||||
* @param masterLooterGuid GUID of master looter (only relevant for method=2)
|
||||
*/
|
||||
static network::Packet build(uint32_t method, uint32_t threshold, uint64_t masterLooterGuid);
|
||||
};
|
||||
|
||||
/** MSG_RAID_TARGET_UPDATE packet builder */
|
||||
class RaidTargetUpdatePacket {
|
||||
public:
|
||||
|
|
|
|||
|
|
@ -96,6 +96,11 @@ public:
|
|||
// while server-sitting), so the caller can send CMSG_STAND_STATE_CHANGE(0).
|
||||
using StandUpCallback = std::function<void()>;
|
||||
void setStandUpCallback(StandUpCallback cb) { standUpCallback_ = std::move(cb); }
|
||||
|
||||
// Callback invoked when auto-follow is cancelled by user movement input.
|
||||
using AutoFollowCancelCallback = std::function<void()>;
|
||||
void setAutoFollowCancelCallback(AutoFollowCancelCallback cb) { autoFollowCancelCallback_ = std::move(cb); }
|
||||
|
||||
void setUseWoWSpeed(bool use) { useWoWSpeed = use; }
|
||||
void setRunSpeedOverride(float speed) { runSpeedOverride_ = speed; }
|
||||
void setWalkSpeedOverride(float speed) { walkSpeedOverride_ = speed; }
|
||||
|
|
@ -121,6 +126,13 @@ public:
|
|||
void clearMovementInputs();
|
||||
void suppressMovementFor(float seconds) { movementSuppressTimer_ = seconds; }
|
||||
|
||||
// Auto-follow: walk toward a target position each frame (WoW /follow).
|
||||
// The caller updates *targetPos every frame with the followed entity's render position.
|
||||
// Stops within FOLLOW_STOP_DIST; cancels on manual WASD input.
|
||||
void setAutoFollow(const glm::vec3* targetPos) { autoFollowTarget_ = targetPos; }
|
||||
void cancelAutoFollow() { autoFollowTarget_ = nullptr; }
|
||||
bool isAutoFollowing() const { return autoFollowTarget_ != nullptr; }
|
||||
|
||||
// Trigger mount jump (applies vertical velocity for physics hop)
|
||||
void triggerMountJump();
|
||||
|
||||
|
|
@ -259,6 +271,11 @@ private:
|
|||
bool autoRunning = false;
|
||||
bool tildeWasDown = false;
|
||||
|
||||
// Auto-follow target position (WoW /follow). Non-null when following.
|
||||
const glm::vec3* autoFollowTarget_ = nullptr;
|
||||
static constexpr float FOLLOW_STOP_DIST = 3.0f; // Stop within 3 units of target
|
||||
static constexpr float FOLLOW_MAX_DIST = 40.0f; // Cancel if > 40 units away
|
||||
|
||||
// Movement state tracking (for sending opcodes on state change)
|
||||
bool wasMovingForward = false;
|
||||
bool wasMovingBackward = false;
|
||||
|
|
@ -278,6 +295,7 @@ private:
|
|||
// Movement callback
|
||||
MovementCallback movementCallback;
|
||||
StandUpCallback standUpCallback_;
|
||||
AutoFollowCancelCallback autoFollowCancelCallback_;
|
||||
|
||||
// Movement speeds
|
||||
bool useWoWSpeed = false;
|
||||
|
|
|
|||
|
|
@ -965,6 +965,11 @@ void Application::setState(AppState newState) {
|
|||
gameHandler->setStandState(0); // CMSG_STAND_STATE_CHANGE(STAND)
|
||||
}
|
||||
});
|
||||
cc->setAutoFollowCancelCallback([this]() {
|
||||
if (gameHandler) {
|
||||
gameHandler->cancelFollow();
|
||||
}
|
||||
});
|
||||
cc->setUseWoWSpeed(true);
|
||||
}
|
||||
if (gameHandler) {
|
||||
|
|
@ -983,6 +988,15 @@ void Application::setState(AppState newState) {
|
|||
renderer->getCameraController()->triggerShake(magnitude, frequency, duration);
|
||||
}
|
||||
});
|
||||
gameHandler->setAutoFollowCallback([this](const glm::vec3* renderPos) {
|
||||
if (renderer && renderer->getCameraController()) {
|
||||
if (renderPos) {
|
||||
renderer->getCameraController()->setAutoFollow(renderPos);
|
||||
} else {
|
||||
renderer->getCameraController()->cancelAutoFollow();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
// Load quest marker models
|
||||
loadQuestMarkerModels();
|
||||
|
|
|
|||
|
|
@ -1379,6 +1379,17 @@ void GameHandler::update(float deltaTime) {
|
|||
clearTarget();
|
||||
}
|
||||
|
||||
// Update auto-follow: refresh render position or cancel if entity disappeared
|
||||
if (followTargetGuid_ != 0) {
|
||||
auto followEnt = entityManager.getEntity(followTargetGuid_);
|
||||
if (followEnt) {
|
||||
followRenderPos_ = core::coords::canonicalToRender(
|
||||
glm::vec3(followEnt->getX(), followEnt->getY(), followEnt->getZ()));
|
||||
} else {
|
||||
cancelFollow();
|
||||
}
|
||||
}
|
||||
|
||||
// Detect combat state transitions → fire PLAYER_REGEN_DISABLED / PLAYER_REGEN_ENABLED
|
||||
{
|
||||
bool combatNow = isInCombat();
|
||||
|
|
@ -13213,6 +13224,14 @@ void GameHandler::followTarget() {
|
|||
// Set follow target
|
||||
followTargetGuid_ = targetGuid;
|
||||
|
||||
// Initialize render-space position from entity's canonical coords
|
||||
followRenderPos_ = core::coords::canonicalToRender(glm::vec3(target->getX(), target->getY(), target->getZ()));
|
||||
|
||||
// Tell camera controller to start auto-following
|
||||
if (autoFollowCallback_) {
|
||||
autoFollowCallback_(&followRenderPos_);
|
||||
}
|
||||
|
||||
// Get target name
|
||||
std::string targetName = "Target";
|
||||
if (target->getType() == ObjectType::PLAYER) {
|
||||
|
|
@ -13232,10 +13251,12 @@ void GameHandler::followTarget() {
|
|||
|
||||
void GameHandler::cancelFollow() {
|
||||
if (followTargetGuid_ == 0) {
|
||||
addSystemChatMessage("You are not following anyone.");
|
||||
return;
|
||||
}
|
||||
followTargetGuid_ = 0;
|
||||
if (autoFollowCallback_) {
|
||||
autoFollowCallback_(nullptr);
|
||||
}
|
||||
addSystemChatMessage("You stop following.");
|
||||
fireAddonEvent("AUTOFOLLOW_END", {});
|
||||
}
|
||||
|
|
@ -19146,6 +19167,32 @@ void GameHandler::leaveGroup() {
|
|||
fireAddonEvent("PARTY_MEMBERS_CHANGED", {});
|
||||
}
|
||||
|
||||
void GameHandler::convertToRaid() {
|
||||
if (!isInWorld()) return;
|
||||
if (!isInGroup()) {
|
||||
addSystemChatMessage("You are not in a group.");
|
||||
return;
|
||||
}
|
||||
if (partyData.leaderGuid != getPlayerGuid()) {
|
||||
addSystemChatMessage("You must be the party leader to convert to raid.");
|
||||
return;
|
||||
}
|
||||
if (partyData.groupType == 1) {
|
||||
addSystemChatMessage("You are already in a raid group.");
|
||||
return;
|
||||
}
|
||||
auto packet = GroupRaidConvertPacket::build();
|
||||
socket->send(packet);
|
||||
LOG_INFO("Sent CMSG_GROUP_RAID_CONVERT");
|
||||
}
|
||||
|
||||
void GameHandler::sendSetLootMethod(uint32_t method, uint32_t threshold, uint64_t masterLooterGuid) {
|
||||
if (!isInWorld()) return;
|
||||
auto packet = SetLootMethodPacket::build(method, threshold, masterLooterGuid);
|
||||
socket->send(packet);
|
||||
LOG_INFO("sendSetLootMethod: method=", method, " threshold=", threshold);
|
||||
}
|
||||
|
||||
void GameHandler::handleGroupInvite(network::Packet& packet) {
|
||||
GroupInviteResponseData data;
|
||||
if (!GroupInviteResponseParser::parse(packet, data)) return;
|
||||
|
|
|
|||
|
|
@ -1767,16 +1767,15 @@ network::Packet SetActiveMoverPacket::build(uint64_t guid) {
|
|||
|
||||
network::Packet InspectPacket::build(uint64_t targetGuid) {
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_INSPECT));
|
||||
packet.writeUInt64(targetGuid);
|
||||
packet.writePackedGuid(targetGuid);
|
||||
LOG_DEBUG("Built CMSG_INSPECT: target=0x", std::hex, targetGuid, std::dec);
|
||||
return packet;
|
||||
}
|
||||
|
||||
network::Packet QueryInspectAchievementsPacket::build(uint64_t targetGuid) {
|
||||
// CMSG_QUERY_INSPECT_ACHIEVEMENTS: uint64 targetGuid + uint8 unk (always 0)
|
||||
// CMSG_QUERY_INSPECT_ACHIEVEMENTS: PackedGuid targetGuid
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_QUERY_INSPECT_ACHIEVEMENTS));
|
||||
packet.writeUInt64(targetGuid);
|
||||
packet.writeUInt8(0); // unk / achievementSlot — always 0 for WotLK
|
||||
packet.writePackedGuid(targetGuid);
|
||||
LOG_DEBUG("Built CMSG_QUERY_INSPECT_ACHIEVEMENTS: target=0x", std::hex, targetGuid, std::dec);
|
||||
return packet;
|
||||
}
|
||||
|
|
@ -2474,6 +2473,22 @@ network::Packet GroupDisbandPacket::build() {
|
|||
return packet;
|
||||
}
|
||||
|
||||
network::Packet GroupRaidConvertPacket::build() {
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_GROUP_RAID_CONVERT));
|
||||
LOG_DEBUG("Built CMSG_GROUP_RAID_CONVERT");
|
||||
return packet;
|
||||
}
|
||||
|
||||
network::Packet SetLootMethodPacket::build(uint32_t method, uint32_t threshold, uint64_t masterLooterGuid) {
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_LOOT_METHOD));
|
||||
packet.writeUInt32(method);
|
||||
packet.writeUInt32(threshold);
|
||||
packet.writeUInt64(masterLooterGuid);
|
||||
LOG_DEBUG("Built CMSG_LOOT_METHOD: method=", method, " threshold=", threshold,
|
||||
" masterLooter=0x", std::hex, masterLooterGuid, std::dec);
|
||||
return packet;
|
||||
}
|
||||
|
||||
network::Packet RaidTargetUpdatePacket::build(uint8_t targetIndex, uint64_t targetGuid) {
|
||||
network::Packet packet(wireOpcode(Opcode::MSG_RAID_TARGET_UPDATE));
|
||||
packet.writeUInt8(targetIndex);
|
||||
|
|
|
|||
|
|
@ -283,17 +283,56 @@ void CameraController::update(float deltaTime) {
|
|||
autoRunning = !autoRunning;
|
||||
}
|
||||
tildeWasDown = tildeDown;
|
||||
// Helper: cancel auto-follow and notify game handler
|
||||
auto doCancelAutoFollow = [&]() {
|
||||
if (autoFollowTarget_) {
|
||||
autoFollowTarget_ = nullptr;
|
||||
if (autoFollowCancelCallback_) autoFollowCancelCallback_();
|
||||
}
|
||||
};
|
||||
|
||||
if (keyW || keyS) {
|
||||
autoRunning = false;
|
||||
doCancelAutoFollow();
|
||||
}
|
||||
|
||||
bool mouseAutorun = !uiWantsKeyboard && !sitting && leftMouseDown && rightMouseDown;
|
||||
if (mouseAutorun) {
|
||||
autoRunning = false;
|
||||
doCancelAutoFollow();
|
||||
}
|
||||
|
||||
// Auto-follow: face target and walk forward when within range
|
||||
bool autoFollowWalk = false;
|
||||
if (autoFollowTarget_ && followTarget && !movementRooted_) {
|
||||
glm::vec3 myPos = *followTarget;
|
||||
glm::vec3 tgtPos = *autoFollowTarget_;
|
||||
float dx = tgtPos.x - myPos.x;
|
||||
float dy = tgtPos.y - myPos.y;
|
||||
float dist2D = std::sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (dist2D > FOLLOW_MAX_DIST) {
|
||||
doCancelAutoFollow();
|
||||
} else if (dist2D > FOLLOW_STOP_DIST) {
|
||||
// Face target (render-space yaw: atan2(-dx, -dy) -> degrees)
|
||||
float targetYawRad = std::atan2(-dx, -dy);
|
||||
float targetYawDeg = targetYawRad * 180.0f / 3.14159265f;
|
||||
facingYaw = targetYawDeg;
|
||||
yaw = targetYawDeg;
|
||||
autoFollowWalk = true;
|
||||
}
|
||||
// else: within stop distance, stay put
|
||||
|
||||
// Cancel on strafe/turn keys
|
||||
if (keyA || keyD || keyQ || keyE) {
|
||||
doCancelAutoFollow();
|
||||
autoFollowWalk = false;
|
||||
}
|
||||
}
|
||||
|
||||
// When the server has rooted the player, suppress all horizontal movement input.
|
||||
const bool movBlocked = movementRooted_;
|
||||
bool nowForward = !movBlocked && (keyW || mouseAutorun || autoRunning);
|
||||
bool nowForward = !movBlocked && (keyW || mouseAutorun || autoRunning || autoFollowWalk);
|
||||
bool nowBackward = !movBlocked && keyS;
|
||||
bool nowStrafeLeft = false;
|
||||
bool nowStrafeRight = false;
|
||||
|
|
|
|||
|
|
@ -2535,12 +2535,13 @@ void GameScreen::renderChatWindow(game::GameHandler& gameHandler) {
|
|||
"/i", "/ignore", "/inspect", "/instance", "/invite",
|
||||
"/j", "/join", "/kick", "/kneel",
|
||||
"/l", "/leave", "/leaveparty", "/loc", "/local", "/logout",
|
||||
"/lootmethod", "/lootthreshold",
|
||||
"/macrohelp", "/mainassist", "/maintank", "/mark", "/me",
|
||||
"/notready",
|
||||
"/p", "/party", "/petaggressive", "/petattack", "/petdefensive",
|
||||
"/petdismiss", "/petfollow", "/pethalt", "/petpassive", "/petstay",
|
||||
"/played", "/pvp",
|
||||
"/r", "/raid", "/raidinfo", "/raidwarning", "/random", "/ready",
|
||||
"/r", "/raid", "/raidconvert", "/raidinfo", "/raidwarning", "/random", "/ready",
|
||||
"/readycheck", "/reload", "/reloadui", "/removefriend",
|
||||
"/reply", "/rl", "/roll", "/run",
|
||||
"/s", "/say", "/score", "/screenshot", "/script", "/setloot",
|
||||
|
|
@ -6306,7 +6307,8 @@ void GameScreen::sendChatMessage(game::GameHandler& gameHandler) {
|
|||
"Chat: /s /y /p /g /raid /rw /o /bg /w <name> /r /join /leave",
|
||||
"Social: /who /friend add/remove /ignore /unignore",
|
||||
"Party: /invite /uninvite /leave /readycheck /mark /roll",
|
||||
" /maintank /mainassist /raidinfo",
|
||||
" /maintank /mainassist /raidconvert /raidinfo",
|
||||
" /lootmethod /lootthreshold",
|
||||
"Guild: /ginvite /gkick /gquit /gpromote /gdemote /gmotd",
|
||||
" /gleader /groster /ginfo /gcreate /gdisband",
|
||||
"Combat: /cast /castsequence /use /startattack /stopattack",
|
||||
|
|
@ -7043,6 +7045,102 @@ void GameScreen::sendChatMessage(game::GameHandler& gameHandler) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (cmdLower == "raidconvert") {
|
||||
gameHandler.convertToRaid();
|
||||
chatInputBuffer[0] = '\0';
|
||||
return;
|
||||
}
|
||||
|
||||
// /lootmethod (or /grouploot, /setloot) — set party/raid loot method
|
||||
if (cmdLower == "lootmethod" || cmdLower == "grouploot" || cmdLower == "setloot") {
|
||||
if (!gameHandler.isInGroup()) {
|
||||
gameHandler.addUIError("You are not in a group.");
|
||||
} else if (spacePos == std::string::npos) {
|
||||
// No argument — show current method and usage
|
||||
static constexpr const char* kMethodNames[] = {
|
||||
"Free for All", "Round Robin", "Master Looter", "Group Loot", "Need Before Greed"
|
||||
};
|
||||
const auto& pd = gameHandler.getPartyData();
|
||||
const char* cur = (pd.lootMethod < 5) ? kMethodNames[pd.lootMethod] : "Unknown";
|
||||
game::MessageChatData msg;
|
||||
msg.type = game::ChatType::SYSTEM;
|
||||
msg.language = game::ChatLanguage::UNIVERSAL;
|
||||
msg.message = std::string("Current loot method: ") + cur;
|
||||
gameHandler.addLocalChatMessage(msg);
|
||||
msg.message = "Usage: /lootmethod ffa|roundrobin|master|group|needbeforegreed";
|
||||
gameHandler.addLocalChatMessage(msg);
|
||||
} else {
|
||||
std::string arg = command.substr(spacePos + 1);
|
||||
// Lowercase the argument
|
||||
for (auto& c : arg) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||
uint32_t method = 0xFFFFFFFF;
|
||||
if (arg == "ffa" || arg == "freeforall") method = 0;
|
||||
else if (arg == "roundrobin" || arg == "rr") method = 1;
|
||||
else if (arg == "master" || arg == "masterloot") method = 2;
|
||||
else if (arg == "group" || arg == "grouploot") method = 3;
|
||||
else if (arg == "needbeforegreed" || arg == "nbg" || arg == "need") method = 4;
|
||||
|
||||
if (method == 0xFFFFFFFF) {
|
||||
gameHandler.addUIError("Unknown loot method. Use: ffa, roundrobin, master, group, needbeforegreed");
|
||||
} else {
|
||||
const auto& pd = gameHandler.getPartyData();
|
||||
// Master loot uses player guid as master looter; otherwise 0
|
||||
uint64_t masterGuid = (method == 2) ? gameHandler.getPlayerGuid() : 0;
|
||||
gameHandler.sendSetLootMethod(method, pd.lootThreshold, masterGuid);
|
||||
}
|
||||
}
|
||||
chatInputBuffer[0] = '\0';
|
||||
return;
|
||||
}
|
||||
|
||||
// /lootthreshold — set minimum item quality for group loot rolls
|
||||
if (cmdLower == "lootthreshold") {
|
||||
if (!gameHandler.isInGroup()) {
|
||||
gameHandler.addUIError("You are not in a group.");
|
||||
} else if (spacePos == std::string::npos) {
|
||||
const auto& pd = gameHandler.getPartyData();
|
||||
static constexpr const char* kQualityNames[] = {
|
||||
"Poor (grey)", "Common (white)", "Uncommon (green)",
|
||||
"Rare (blue)", "Epic (purple)", "Legendary (orange)"
|
||||
};
|
||||
const char* cur = (pd.lootThreshold < 6) ? kQualityNames[pd.lootThreshold] : "Unknown";
|
||||
game::MessageChatData msg;
|
||||
msg.type = game::ChatType::SYSTEM;
|
||||
msg.language = game::ChatLanguage::UNIVERSAL;
|
||||
msg.message = std::string("Current loot threshold: ") + cur;
|
||||
gameHandler.addLocalChatMessage(msg);
|
||||
msg.message = "Usage: /lootthreshold <0-5> (0=Poor, 1=Common, 2=Uncommon, 3=Rare, 4=Epic, 5=Legendary)";
|
||||
gameHandler.addLocalChatMessage(msg);
|
||||
} else {
|
||||
std::string arg = command.substr(spacePos + 1);
|
||||
// Trim whitespace
|
||||
while (!arg.empty() && arg.front() == ' ') arg.erase(arg.begin());
|
||||
uint32_t threshold = 0xFFFFFFFF;
|
||||
if (arg.size() == 1 && arg[0] >= '0' && arg[0] <= '5') {
|
||||
threshold = static_cast<uint32_t>(arg[0] - '0');
|
||||
} else {
|
||||
// Accept quality names
|
||||
for (auto& c : arg) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||
if (arg == "poor" || arg == "grey" || arg == "gray") threshold = 0;
|
||||
else if (arg == "common" || arg == "white") threshold = 1;
|
||||
else if (arg == "uncommon" || arg == "green") threshold = 2;
|
||||
else if (arg == "rare" || arg == "blue") threshold = 3;
|
||||
else if (arg == "epic" || arg == "purple") threshold = 4;
|
||||
else if (arg == "legendary" || arg == "orange") threshold = 5;
|
||||
}
|
||||
|
||||
if (threshold == 0xFFFFFFFF) {
|
||||
gameHandler.addUIError("Invalid threshold. Use 0-5 or: poor, common, uncommon, rare, epic, legendary");
|
||||
} else {
|
||||
const auto& pd = gameHandler.getPartyData();
|
||||
uint64_t masterGuid = (pd.lootMethod == 2) ? gameHandler.getPlayerGuid() : 0;
|
||||
gameHandler.sendSetLootMethod(pd.lootMethod, threshold, masterGuid);
|
||||
}
|
||||
}
|
||||
chatInputBuffer[0] = '\0';
|
||||
return;
|
||||
}
|
||||
|
||||
// /mark [icon] — set or clear a raid target mark on the current target.
|
||||
// Icon names (case-insensitive): star, circle, diamond, triangle, moon, square, cross, skull
|
||||
// /mark clear | /mark 0 — remove all marks (sets icon 0xFF = clear)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue