Kelsidavis-WoWee/src/ui/chat/commands/target_commands.cpp
Pavel Okhlopkov ada019e0d4 refactor(chat): extract ItemTooltipRenderer, slim render(), consolidate utils
- Extract renderItemTooltip() (510 LOC) from ChatMarkupRenderer into
  dedicated ItemTooltipRenderer class; chat_markup_renderer.cpp 766→192 LOC
- Extract formatChatMessage(), detectChannelPrefix(), inputTextCallback()
  from render(); render() 711→376 LOC
- Consolidate replaceGenderPlaceholders() from 3 copies into
  chat_utils::replaceGenderPlaceholders(); remove 118 LOC duplicate from
  quest_log_screen.cpp, update 8 call sites in window_manager.cpp
- Delete chat_panel_commands.cpp (359 LOC) — absorb sendChatMessage,
  executeMacroText, PortBot helpers into chat_panel.cpp; move
  evaluateMacroConditionals to macro_eval_convenience.cpp
- Delete chat_panel_utils.cpp (229 LOC) — absorb small utilities into
  chat_panel.cpp
- Replace 3 forward declarations of evaluateMacroConditionals with
  #include "ui/chat/macro_evaluator.hpp"

Signed-off-by: Pavel Okhlopkov <pavel.okhlopkov@flant.com>
2026-04-12 15:46:03 +03:00

248 lines
9.7 KiB
C++

// Target commands: /target, /cleartarget, /targetenemy, /targetfriend,
// /targetlasttarget, /targetlastenemy, /targetlastfriend,
// /focus, /clearfocus
// Moved from ChatPanel::sendChatMessage() if/else chain (Phase 3).
#include "ui/chat/i_chat_command.hpp"
#include "ui/chat/macro_evaluator.hpp"
#include "ui/chat_panel.hpp"
#include "game/game_handler.hpp"
#include "game/entity.hpp"
#include <algorithm>
#include <cctype>
#include <limits>
#include <memory>
namespace wowee { namespace ui {
namespace {
// Trim leading/trailing whitespace.
inline void trimInPlace(std::string& s) {
while (!s.empty() && s.front() == ' ') s.erase(s.begin());
while (!s.empty() && s.back() == ' ') s.pop_back();
}
// Search nearby visible entities by name (case-insensitive prefix match).
// Returns the GUID of the nearest matching unit, or 0 if none found.
uint64_t findNearestByName(game::GameHandler& gh, const std::string& targetArgLower) {
uint64_t bestGuid = 0;
float bestDist = std::numeric_limits<float>::max();
const auto& pmi = gh.getMovementInfo();
for (const auto& [guid, entity] : gh.getEntityManager().getEntities()) {
if (!entity || entity->getType() == game::ObjectType::OBJECT) continue;
std::string name;
if (entity->getType() == game::ObjectType::PLAYER ||
entity->getType() == game::ObjectType::UNIT) {
auto unit = std::static_pointer_cast<game::Unit>(entity);
name = unit->getName();
}
if (name.empty()) continue;
std::string nameLower = name;
for (char& c : nameLower) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
if (nameLower.find(targetArgLower) == 0) {
float dx = entity->getX() - pmi.x;
float dy = entity->getY() - pmi.y;
float dz = entity->getZ() - pmi.z;
float dist = dx*dx + dy*dy + dz*dz;
if (dist < bestDist) {
bestDist = dist;
bestGuid = guid;
}
}
}
return bestGuid;
}
} // anon namespace
// --- /target ---
class TargetCommand : public IChatCommand {
public:
ChatCommandResult execute(ChatCommandContext& ctx) override {
if (ctx.args.empty()) return {false, false};
std::string targetArg = ctx.args;
// Evaluate conditionals if present
uint64_t targetCmdOverride = static_cast<uint64_t>(-1);
if (!targetArg.empty() && targetArg.front() == '[') {
targetArg = evaluateMacroConditionals(targetArg, ctx.gameHandler, targetCmdOverride);
if (targetArg.empty() && targetCmdOverride == static_cast<uint64_t>(-1)) return {};
trimInPlace(targetArg);
}
// If conditionals resolved to a specific GUID, target it directly
if (targetCmdOverride != static_cast<uint64_t>(-1) && targetCmdOverride != 0) {
ctx.gameHandler.setTarget(targetCmdOverride);
return {};
}
if (targetArg.empty()) return {};
std::string targetArgLower = targetArg;
for (char& c : targetArgLower) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
uint64_t bestGuid = findNearestByName(ctx.gameHandler, targetArgLower);
if (bestGuid) {
ctx.gameHandler.setTarget(bestGuid);
} else {
game::MessageChatData sysMsg;
sysMsg.type = game::ChatType::SYSTEM;
sysMsg.language = game::ChatLanguage::UNIVERSAL;
sysMsg.message = "No target matching '" + targetArg + "' found.";
ctx.gameHandler.addLocalChatMessage(sysMsg);
}
return {};
}
std::vector<std::string> aliases() const override { return {"target"}; }
std::string helpText() const override { return "Target unit by name"; }
};
// --- /cleartarget ---
class ClearTargetCommand : public IChatCommand {
public:
ChatCommandResult execute(ChatCommandContext& ctx) override {
bool condPass = true;
if (!ctx.args.empty()) {
std::string ctArg = ctx.args;
trimInPlace(ctArg);
if (!ctArg.empty() && ctArg.front() == '[') {
uint64_t ctOver = static_cast<uint64_t>(-1);
std::string res = evaluateMacroConditionals(ctArg, ctx.gameHandler, ctOver);
condPass = !(res.empty() && ctOver == static_cast<uint64_t>(-1));
}
}
if (condPass) ctx.gameHandler.clearTarget();
return {};
}
std::vector<std::string> aliases() const override { return {"cleartarget"}; }
std::string helpText() const override { return "Clear current target"; }
};
// --- /targetenemy ---
class TargetEnemyCommand : public IChatCommand {
public:
ChatCommandResult execute(ChatCommandContext& ctx) override {
ctx.gameHandler.targetEnemy(false);
return {};
}
std::vector<std::string> aliases() const override { return {"targetenemy"}; }
std::string helpText() const override { return "Cycle to next enemy"; }
};
// --- /targetfriend ---
class TargetFriendCommand : public IChatCommand {
public:
ChatCommandResult execute(ChatCommandContext& ctx) override {
ctx.gameHandler.targetFriend(false);
return {};
}
std::vector<std::string> aliases() const override { return {"targetfriend"}; }
std::string helpText() const override { return "Cycle to next friendly unit"; }
};
// --- /targetlasttarget, /targetlast ---
class TargetLastTargetCommand : public IChatCommand {
public:
ChatCommandResult execute(ChatCommandContext& ctx) override {
ctx.gameHandler.targetLastTarget();
return {};
}
std::vector<std::string> aliases() const override { return {"targetlasttarget", "targetlast"}; }
std::string helpText() const override { return "Target previous target"; }
};
// --- /targetlastenemy ---
class TargetLastEnemyCommand : public IChatCommand {
public:
ChatCommandResult execute(ChatCommandContext& ctx) override {
ctx.gameHandler.targetEnemy(true);
return {};
}
std::vector<std::string> aliases() const override { return {"targetlastenemy"}; }
std::string helpText() const override { return "Cycle to previous enemy"; }
};
// --- /targetlastfriend ---
class TargetLastFriendCommand : public IChatCommand {
public:
ChatCommandResult execute(ChatCommandContext& ctx) override {
ctx.gameHandler.targetFriend(true);
return {};
}
std::vector<std::string> aliases() const override { return {"targetlastfriend"}; }
std::string helpText() const override { return "Cycle to previous friendly unit"; }
};
// --- /focus ---
class FocusCommand : public IChatCommand {
public:
ChatCommandResult execute(ChatCommandContext& ctx) override {
if (!ctx.args.empty()) {
std::string focusArg = ctx.args;
// Evaluate conditionals if present
uint64_t focusCmdOverride = static_cast<uint64_t>(-1);
if (!focusArg.empty() && focusArg.front() == '[') {
focusArg = evaluateMacroConditionals(focusArg, ctx.gameHandler, focusCmdOverride);
if (focusArg.empty() && focusCmdOverride == static_cast<uint64_t>(-1)) return {};
trimInPlace(focusArg);
}
if (focusCmdOverride != static_cast<uint64_t>(-1) && focusCmdOverride != 0) {
ctx.gameHandler.setFocus(focusCmdOverride);
} else if (!focusArg.empty()) {
std::string focusArgLower = focusArg;
for (char& c : focusArgLower) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
uint64_t bestGuid = findNearestByName(ctx.gameHandler, focusArgLower);
if (bestGuid) {
ctx.gameHandler.setFocus(bestGuid);
} else {
game::MessageChatData msg;
msg.type = game::ChatType::SYSTEM;
msg.language = game::ChatLanguage::UNIVERSAL;
msg.message = "No unit matching '" + focusArg + "' found.";
ctx.gameHandler.addLocalChatMessage(msg);
}
}
} else if (ctx.gameHandler.hasTarget()) {
ctx.gameHandler.setFocus(ctx.gameHandler.getTargetGuid());
} else {
game::MessageChatData msg;
msg.type = game::ChatType::SYSTEM;
msg.language = game::ChatLanguage::UNIVERSAL;
msg.message = "You must target a unit to set as focus.";
ctx.gameHandler.addLocalChatMessage(msg);
}
return {};
}
std::vector<std::string> aliases() const override { return {"focus"}; }
std::string helpText() const override { return "Set focus target"; }
};
// --- /clearfocus ---
class ClearFocusCommand : public IChatCommand {
public:
ChatCommandResult execute(ChatCommandContext& ctx) override {
ctx.gameHandler.clearFocus();
return {};
}
std::vector<std::string> aliases() const override { return {"clearfocus"}; }
std::string helpText() const override { return "Clear focus target"; }
};
// --- Registration ---
void registerTargetCommands(ChatCommandRegistry& reg) {
reg.registerCommand(std::make_unique<TargetCommand>());
reg.registerCommand(std::make_unique<ClearTargetCommand>());
reg.registerCommand(std::make_unique<TargetEnemyCommand>());
reg.registerCommand(std::make_unique<TargetFriendCommand>());
reg.registerCommand(std::make_unique<TargetLastTargetCommand>());
reg.registerCommand(std::make_unique<TargetLastEnemyCommand>());
reg.registerCommand(std::make_unique<TargetLastFriendCommand>());
reg.registerCommand(std::make_unique<FocusCommand>());
reg.registerCommand(std::make_unique<ClearFocusCommand>());
}
} // namespace ui
} // namespace wowee