Add Tier 8 commands: advanced targeting system

Targeting Commands:
- /cleartarget - Clear current target selection
- /targetenemy - Cycle to next hostile target (Tab equivalent)
- /targetfriend - Cycle to next friendly player
- /targetlasttarget, /targetlast - Switch to previous target
- /targetlastenemy - Cycle to previous hostile target
- /targetlastfriend - Cycle to previous friendly player
- /focus - Set current target as focus (client-side)
- /clearfocus - Clear focus target

Implementation:
- Added focusGuid and lastTargetGuid to GameHandler for client-side tracking
- setTarget() now automatically saves previous target to lastTargetGuid
- setFocus() and clearFocus() manage focus target with user feedback
- targetLastTarget() swaps current and previous targets
- targetEnemy() cycles through hostile entities (Units)
- targetFriend() cycles through friendly entities (Players)
- Both targetEnemy/targetFriend support reverse parameter for backwards cycling

Features:
- Focus targeting is client-side (no server opcode in 3.3.5a)
- Last target tracking happens automatically on every target change
- Enemy/friend cycling iterates through visible entities
- Provides user feedback when no targets available
- Tab-like cycling behavior with wraparound

All commands work entirely client-side for responsive targeting.
This commit is contained in:
kelsi davis 2026-02-07 13:44:36 -08:00
parent bca3f64af6
commit dfc4008ec7
3 changed files with 206 additions and 0 deletions

View file

@ -202,6 +202,18 @@ public:
bool hasTarget() const { return targetGuid != 0; }
void tabTarget(float playerX, float playerY, float playerZ);
// Focus targeting
void setFocus(uint64_t guid);
void clearFocus();
uint64_t getFocusGuid() const { return focusGuid; }
std::shared_ptr<Entity> getFocus() const;
bool hasFocus() const { return focusGuid != 0; }
// Advanced targeting
void targetLastTarget();
void targetEnemy(bool reverse = false);
void targetFriend(bool reverse = false);
// Inspection
void inspectTarget();
@ -646,6 +658,8 @@ private:
// Targeting
uint64_t targetGuid = 0;
uint64_t focusGuid = 0; // Focus target
uint64_t lastTargetGuid = 0; // Previous target
std::vector<uint64_t> tabCycleList;
int tabCycleIndex = -1;
bool tabCycleStale = true;

View file

@ -1521,6 +1521,12 @@ void GameHandler::handleMessageChat(network::Packet& packet) {
void GameHandler::setTarget(uint64_t guid) {
if (guid == targetGuid) return;
// Save previous target
if (targetGuid != 0) {
lastTargetGuid = targetGuid;
}
targetGuid = guid;
// Inform server of target selection (Phase 1)
@ -1548,6 +1554,135 @@ std::shared_ptr<Entity> GameHandler::getTarget() const {
return entityManager.getEntity(targetGuid);
}
void GameHandler::setFocus(uint64_t guid) {
focusGuid = guid;
if (guid != 0) {
auto entity = entityManager.getEntity(guid);
if (entity) {
std::string name = "Unknown";
if (entity->getType() == ObjectType::PLAYER) {
auto player = std::dynamic_pointer_cast<Player>(entity);
if (player && !player->getName().empty()) {
name = player->getName();
}
}
addSystemChatMessage("Focus set: " + name);
LOG_INFO("Focus set: 0x", std::hex, guid, std::dec);
}
}
}
void GameHandler::clearFocus() {
if (focusGuid != 0) {
addSystemChatMessage("Focus cleared.");
LOG_INFO("Focus cleared");
}
focusGuid = 0;
}
std::shared_ptr<Entity> GameHandler::getFocus() const {
if (focusGuid == 0) return nullptr;
return entityManager.getEntity(focusGuid);
}
void GameHandler::targetLastTarget() {
if (lastTargetGuid == 0) {
addSystemChatMessage("No previous target.");
return;
}
// Swap current and last target
uint64_t temp = targetGuid;
setTarget(lastTargetGuid);
lastTargetGuid = temp;
}
void GameHandler::targetEnemy(bool reverse) {
// Get list of hostile entities
std::vector<uint64_t> hostiles;
auto& entities = entityManager.getEntities();
for (const auto& [guid, entity] : entities) {
if (entity->getType() == ObjectType::UNIT) {
// Check if hostile (this is simplified - would need faction checking)
auto unit = std::dynamic_pointer_cast<Unit>(entity);
if (unit && guid != playerGuid) {
hostiles.push_back(guid);
}
}
}
if (hostiles.empty()) {
addSystemChatMessage("No enemies in range.");
return;
}
// Find current target in list
auto it = std::find(hostiles.begin(), hostiles.end(), targetGuid);
if (it == hostiles.end()) {
// Not currently targeting a hostile, target first one
setTarget(reverse ? hostiles.back() : hostiles.front());
} else {
// Cycle to next/previous
if (reverse) {
if (it == hostiles.begin()) {
setTarget(hostiles.back());
} else {
setTarget(*(--it));
}
} else {
++it;
if (it == hostiles.end()) {
setTarget(hostiles.front());
} else {
setTarget(*it);
}
}
}
}
void GameHandler::targetFriend(bool reverse) {
// Get list of friendly entities (players)
std::vector<uint64_t> friendlies;
auto& entities = entityManager.getEntities();
for (const auto& [guid, entity] : entities) {
if (entity->getType() == ObjectType::PLAYER && guid != playerGuid) {
friendlies.push_back(guid);
}
}
if (friendlies.empty()) {
addSystemChatMessage("No friendly targets in range.");
return;
}
// Find current target in list
auto it = std::find(friendlies.begin(), friendlies.end(), targetGuid);
if (it == friendlies.end()) {
// Not currently targeting a friend, target first one
setTarget(reverse ? friendlies.back() : friendlies.front());
} else {
// Cycle to next/previous
if (reverse) {
if (it == friendlies.begin()) {
setTarget(friendlies.back());
} else {
setTarget(*(--it));
}
} else {
++it;
if (it == friendlies.end()) {
setTarget(friendlies.front());
} else {
setTarget(*it);
}
}
}
}
void GameHandler::inspectTarget() {
if (state != WorldState::IN_WORLD || !socket) {
LOG_WARNING("Cannot inspect: not in world or not connected");

View file

@ -1460,6 +1460,63 @@ void GameScreen::sendChatMessage(game::GameHandler& gameHandler) {
return;
}
// Targeting commands
if (cmdLower == "cleartarget") {
gameHandler.clearTarget();
chatInputBuffer[0] = '\0';
return;
}
if (cmdLower == "targetenemy") {
gameHandler.targetEnemy(false);
chatInputBuffer[0] = '\0';
return;
}
if (cmdLower == "targetfriend") {
gameHandler.targetFriend(false);
chatInputBuffer[0] = '\0';
return;
}
if (cmdLower == "targetlasttarget" || cmdLower == "targetlast") {
gameHandler.targetLastTarget();
chatInputBuffer[0] = '\0';
return;
}
if (cmdLower == "targetlastenemy") {
gameHandler.targetEnemy(true); // Reverse direction
chatInputBuffer[0] = '\0';
return;
}
if (cmdLower == "targetlastfriend") {
gameHandler.targetFriend(true); // Reverse direction
chatInputBuffer[0] = '\0';
return;
}
if (cmdLower == "focus") {
if (gameHandler.hasTarget()) {
gameHandler.setFocus(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.";
gameHandler.addLocalChatMessage(msg);
}
chatInputBuffer[0] = '\0';
return;
}
if (cmdLower == "clearfocus") {
gameHandler.clearFocus();
chatInputBuffer[0] = '\0';
return;
}
// Chat channel slash commands
bool isChannelCommand = false;
if (cmdLower == "s" || cmdLower == "say") {