chore: remove dead functions left behind by handler extractions

685 lines of unused code duplicated into extracted handler files
(entity_controller, spell_handler, quest_handler, warden_handler,
social_handler, action_bar_panel, chat_panel, window_manager)
during PRs #33-#38. Build is now warning-free.
This commit is contained in:
Kelsi 2026-04-02 14:47:04 -07:00
parent 0a4cc9ab96
commit fe1c4c622b
5 changed files with 0 additions and 685 deletions

View file

@ -94,13 +94,6 @@ bool isAuthCharPipelineOpcode(LogicalOpcode op) {
namespace {
bool envFlagEnabled(const char* key, bool defaultValue = false) {
const char* raw = std::getenv(key);
if (!raw || !*raw) return defaultValue;
return !(raw[0] == '0' || raw[0] == 'f' || raw[0] == 'F' ||
raw[0] == 'n' || raw[0] == 'N');
}
int parseEnvIntClamped(const char* key, int defaultValue, int minValue, int maxValue) {
const char* raw = std::getenv(key);
if (!raw || !*raw) return defaultValue;
@ -126,47 +119,14 @@ float incomingPacketBudgetMs(WorldState state) {
return static_cast<float>(state == WorldState::IN_WORLD ? inWorldBudgetMs : loginBudgetMs);
}
int updateObjectBlocksBudgetPerUpdate(WorldState state) {
static const int inWorldBudget =
parseEnvIntClamped("WOWEE_NET_MAX_UPDATE_OBJECT_BLOCKS", 24, 1, 2048);
static const int loginBudget =
parseEnvIntClamped("WOWEE_NET_MAX_UPDATE_OBJECT_BLOCKS_LOGIN", 128, 1, 4096);
return state == WorldState::IN_WORLD ? inWorldBudget : loginBudget;
}
float slowPacketLogThresholdMs() {
static const int thresholdMs =
parseEnvIntClamped("WOWEE_NET_SLOW_PACKET_LOG_MS", 10, 1, 60000);
return static_cast<float>(thresholdMs);
}
float slowUpdateObjectBlockLogThresholdMs() {
static const int thresholdMs =
parseEnvIntClamped("WOWEE_NET_SLOW_UPDATE_BLOCK_LOG_MS", 10, 1, 60000);
return static_cast<float>(thresholdMs);
}
constexpr size_t kMaxQueuedInboundPackets = 4096;
CombatTextEntry::Type combatTextTypeFromSpellMissInfo(uint8_t missInfo) {
switch (missInfo) {
case 0: return CombatTextEntry::MISS;
case 1: return CombatTextEntry::DODGE;
case 2: return CombatTextEntry::PARRY;
case 3: return CombatTextEntry::BLOCK;
case 4: return CombatTextEntry::EVADE;
case 5: return CombatTextEntry::IMMUNE;
case 6: return CombatTextEntry::DEFLECT;
case 7: return CombatTextEntry::ABSORB;
case 8: return CombatTextEntry::RESIST;
case 9: // Some cores encode SPELL_MISS_IMMUNE2 as 9.
case 10: // Others encode SPELL_MISS_IMMUNE2 as 10.
return CombatTextEntry::IMMUNE;
case 11: return CombatTextEntry::REFLECT;
default: return CombatTextEntry::MISS;
}
}
} // end anonymous namespace
std::string formatCopperAmount(uint32_t amount) {
@ -192,412 +152,6 @@ std::string formatCopperAmount(uint32_t amount) {
return oss.str();
}
namespace {
std::string displaySpellName(GameHandler& handler, uint32_t spellId) {
if (spellId == 0) return {};
const std::string& name = handler.getSpellName(spellId);
if (!name.empty()) return name;
return "spell " + std::to_string(spellId);
}
std::string formatSpellNameList(GameHandler& handler,
const std::vector<uint32_t>& spellIds,
size_t maxShown = 3) {
if (spellIds.empty()) return {};
const size_t shownCount = std::min(spellIds.size(), maxShown);
std::ostringstream oss;
for (size_t i = 0; i < shownCount; ++i) {
if (i > 0) {
if (shownCount == 2) {
oss << " and ";
} else if (i == shownCount - 1) {
oss << ", and ";
} else {
oss << ", ";
}
}
oss << displaySpellName(handler, spellIds[i]);
}
if (spellIds.size() > shownCount) {
oss << ", and " << (spellIds.size() - shownCount) << " more";
}
return oss.str();
}
bool readCStringAt(const std::vector<uint8_t>& data, size_t start, std::string& out, size_t& nextPos) {
out.clear();
if (start >= data.size()) return false;
size_t i = start;
while (i < data.size()) {
uint8_t b = data[i++];
if (b == 0) {
nextPos = i;
return true;
}
out.push_back(static_cast<char>(b));
}
return false;
}
std::string asciiLower(std::string s) {
std::transform(s.begin(), s.end(), s.begin(),
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
return s;
}
std::vector<std::string> splitWowPath(const std::string& wowPath) {
std::vector<std::string> out;
std::string cur;
for (char c : wowPath) {
if (c == '\\' || c == '/') {
if (!cur.empty()) {
out.push_back(cur);
cur.clear();
}
continue;
}
cur.push_back(c);
}
if (!cur.empty()) out.push_back(cur);
return out;
}
int pathCaseScore(const std::string& name) {
int score = 0;
for (unsigned char c : name) {
if (std::islower(c)) score += 2;
else if (std::isupper(c)) score -= 1;
}
return score;
}
std::string resolveCaseInsensitiveDataPath(const std::string& dataRoot, const std::string& wowPath) {
if (dataRoot.empty() || wowPath.empty()) return std::string();
std::filesystem::path cur(dataRoot);
std::error_code ec;
if (!std::filesystem::exists(cur, ec) || !std::filesystem::is_directory(cur, ec)) {
return std::string();
}
for (const std::string& segment : splitWowPath(wowPath)) {
std::string wanted = asciiLower(segment);
std::filesystem::path bestPath;
int bestScore = std::numeric_limits<int>::min();
bool found = false;
for (const auto& entry : std::filesystem::directory_iterator(cur, ec)) {
if (ec) break;
std::string name = entry.path().filename().string();
if (asciiLower(name) != wanted) continue;
int score = pathCaseScore(name);
if (!found || score > bestScore) {
found = true;
bestScore = score;
bestPath = entry.path();
}
}
if (!found) return std::string();
cur = bestPath;
}
if (!std::filesystem::exists(cur, ec) || std::filesystem::is_directory(cur, ec)) {
return std::string();
}
return cur.string();
}
std::vector<uint8_t> readFileBinary(const std::string& fsPath) {
std::ifstream in(fsPath, std::ios::binary);
if (!in) return {};
in.seekg(0, std::ios::end);
std::streamoff size = in.tellg();
if (size <= 0) return {};
in.seekg(0, std::ios::beg);
std::vector<uint8_t> data(static_cast<size_t>(size));
in.read(reinterpret_cast<char*>(data.data()), size);
if (!in) return {};
return data;
}
bool hmacSha1Matches(const uint8_t seedBytes[4], const std::string& text, const uint8_t expected[20]) {
uint8_t out[SHA_DIGEST_LENGTH];
unsigned int outLen = 0;
HMAC(EVP_sha1(),
seedBytes, 4,
reinterpret_cast<const uint8_t*>(text.data()),
static_cast<int>(text.size()),
out, &outLen);
return outLen == SHA_DIGEST_LENGTH && std::memcmp(out, expected, SHA_DIGEST_LENGTH) == 0;
}
const std::unordered_map<std::string, std::array<uint8_t, 20>>& knownDoorHashes() {
static const std::unordered_map<std::string, std::array<uint8_t, 20>> k = {
{"world\\lordaeron\\stratholme\\activedoodads\\doors\\nox_door_plague.m2",
{0xB4,0x45,0x2B,0x6D,0x95,0xC9,0x8B,0x18,0x6A,0x70,0xB0,0x08,0xFA,0x07,0xBB,0xAE,0xF3,0x0D,0xF7,0xA2}},
{"world\\kalimdor\\onyxiaslair\\doors\\onyxiasgate01.m2",
{0x75,0x19,0x5E,0x4A,0xED,0xA0,0xBC,0xAF,0x04,0x8C,0xA0,0xE3,0x4D,0x95,0xA7,0x0D,0x4F,0x53,0xC7,0x46}},
{"world\\generic\\human\\activedoodads\\doors\\deadminedoor02.m2",
{0x3D,0xFF,0x01,0x1B,0x9A,0xB1,0x34,0xF3,0x7F,0x88,0x50,0x97,0xE6,0x95,0x35,0x1B,0x91,0x95,0x35,0x64}},
{"world\\kalimdor\\silithus\\activedoodads\\ahnqirajdoor\\ahnqirajdoor02.m2",
{0xDB,0xD4,0xF4,0x07,0xC4,0x68,0xCC,0x36,0x13,0x4E,0x62,0x1D,0x16,0x01,0x78,0xFD,0xA4,0xD0,0xD2,0x49}},
{"world\\kalimdor\\diremaul\\activedoodads\\doors\\diremaulsmallinstancedoor.m2",
{0x0D,0xC8,0xDB,0x46,0xC8,0x55,0x49,0xC0,0xFF,0x1A,0x60,0x0F,0x6C,0x23,0x63,0x57,0xC3,0x05,0x78,0x1A}},
};
return k;
}
bool isReadableQuestText(const std::string& s, size_t minLen, size_t maxLen) {
if (s.size() < minLen || s.size() > maxLen) return false;
bool hasAlpha = false;
for (unsigned char c : s) {
if (c < 0x20 || c > 0x7E) return false;
if (std::isalpha(c)) hasAlpha = true;
}
return hasAlpha;
}
bool isPlaceholderQuestTitle(const std::string& s) {
return s.rfind("Quest #", 0) == 0;
}
float mergeCooldownSeconds(float current, float incoming) {
constexpr float kEpsilon = 0.05f;
if (incoming <= 0.0f) return 0.0f;
if (current <= 0.0f) return incoming;
// Cooldowns should normally tick down. If a duplicate/late packet reports a
// larger value, keep the local remaining time to avoid visible timer resets.
if (incoming > current + kEpsilon) return current;
return incoming;
}
bool looksLikeQuestDescriptionText(const std::string& s) {
int spaces = 0;
int commas = 0;
for (unsigned char c : s) {
if (c == ' ') spaces++;
if (c == ',') commas++;
}
const int words = spaces + 1;
if (words > 8) return true;
if (commas > 0 && words > 5) return true;
if (s.find(". ") != std::string::npos) return true;
if (s.find(':') != std::string::npos && words > 5) return true;
return false;
}
bool isStrongQuestTitle(const std::string& s) {
if (!isReadableQuestText(s, 6, 72)) return false;
if (looksLikeQuestDescriptionText(s)) return false;
unsigned char first = static_cast<unsigned char>(s.front());
return std::isupper(first) != 0;
}
int scoreQuestTitle(const std::string& s) {
if (!isReadableQuestText(s, 4, 72)) return -1000;
if (looksLikeQuestDescriptionText(s)) return -1000;
int score = 0;
score += static_cast<int>(std::min<size_t>(s.size(), 32));
unsigned char first = static_cast<unsigned char>(s.front());
if (std::isupper(first)) score += 20;
if (std::islower(first)) score -= 20;
if (s.find(' ') != std::string::npos) score += 8;
if (s.find('.') != std::string::npos) score -= 18;
if (s.find('!') != std::string::npos || s.find('?') != std::string::npos) score -= 6;
return score;
}
struct QuestQueryTextCandidate {
std::string title;
std::string objectives;
int score = -1000;
};
QuestQueryTextCandidate pickBestQuestQueryTexts(const std::vector<uint8_t>& data, bool classicHint) {
QuestQueryTextCandidate best;
if (data.size() <= 9) return best;
std::vector<size_t> seedOffsets;
const size_t base = 8;
const size_t classicOffset = base + 40u * 4u;
const size_t wotlkOffset = base + 55u * 4u;
if (classicHint) {
seedOffsets.push_back(classicOffset);
seedOffsets.push_back(wotlkOffset);
} else {
seedOffsets.push_back(wotlkOffset);
seedOffsets.push_back(classicOffset);
}
for (size_t off : seedOffsets) {
if (off < data.size()) {
std::string title;
size_t next = off;
if (readCStringAt(data, off, title, next)) {
QuestQueryTextCandidate c;
c.title = title;
c.score = scoreQuestTitle(title) + 20; // Prefer expected struct offsets
std::string s2;
size_t n2 = next;
if (readCStringAt(data, next, s2, n2) && isReadableQuestText(s2, 8, 600)) {
c.objectives = s2;
}
if (c.score > best.score) best = c;
}
}
}
// Fallback: scan packet for best printable C-string title candidate.
for (size_t start = 8; start < data.size(); ++start) {
std::string title;
size_t next = start;
if (!readCStringAt(data, start, title, next)) continue;
QuestQueryTextCandidate c;
c.title = title;
c.score = scoreQuestTitle(title);
if (c.score < 0) continue;
std::string s2, s3;
size_t n2 = next, n3 = next;
if (readCStringAt(data, next, s2, n2)) {
if (isReadableQuestText(s2, 8, 600)) c.objectives = s2;
else if (readCStringAt(data, n2, s3, n3) && isReadableQuestText(s3, 8, 600)) c.objectives = s3;
}
if (c.score > best.score) best = c;
}
return best;
}
// Parse kill/item objectives from SMSG_QUEST_QUERY_RESPONSE raw data.
// Returns true if the objective block was found and at least one entry read.
//
// Format after the fixed integer header (40*4 Classic or 55*4 WotLK bytes post questId+questMethod):
// N strings (title, objectives, details, endText; + completedText for WotLK)
// 4x { int32 npcOrGoId, uint32 count } -- entity (kill/interact) objectives
// 6x { uint32 itemId, uint32 count } -- item collect objectives
// 4x cstring -- per-objective display text
//
// We use the same fixed-offset heuristic as pickBestQuestQueryTexts and then scan past
// the string section to reach the objective data.
struct QuestQueryObjectives {
struct Kill { int32_t npcOrGoId; uint32_t required; };
struct Item { uint32_t itemId; uint32_t required; };
std::array<Kill, 4> kills{};
std::array<Item, 6> items{};
bool valid = false;
};
static uint32_t readU32At(const std::vector<uint8_t>& d, size_t pos) {
return static_cast<uint32_t>(d[pos])
| (static_cast<uint32_t>(d[pos + 1]) << 8)
| (static_cast<uint32_t>(d[pos + 2]) << 16)
| (static_cast<uint32_t>(d[pos + 3]) << 24);
}
// Try to parse objective block starting at `startPos` with `nStrings` strings before it.
// Returns a valid QuestQueryObjectives if the data looks plausible, otherwise invalid.
static QuestQueryObjectives tryParseQuestObjectivesAt(const std::vector<uint8_t>& data,
size_t startPos, int nStrings) {
QuestQueryObjectives out;
size_t pos = startPos;
// Scan past each string (null-terminated).
for (int si = 0; si < nStrings; ++si) {
while (pos < data.size() && data[pos] != 0) ++pos;
if (pos >= data.size()) return out; // truncated
++pos; // consume null terminator
}
// Read 4 entity objectives: int32 npcOrGoId + uint32 count each.
for (int i = 0; i < 4; ++i) {
if (pos + 8 > data.size()) return out;
out.kills[i].npcOrGoId = static_cast<int32_t>(readU32At(data, pos)); pos += 4;
out.kills[i].required = readU32At(data, pos); pos += 4;
}
// Read 6 item objectives: uint32 itemId + uint32 count each.
for (int i = 0; i < 6; ++i) {
if (pos + 8 > data.size()) break;
out.items[i].itemId = readU32At(data, pos); pos += 4;
out.items[i].required = readU32At(data, pos); pos += 4;
}
out.valid = true;
return out;
}
QuestQueryObjectives extractQuestQueryObjectives(const std::vector<uint8_t>& data, bool classicHint) {
if (data.size() < 16) return {};
// questId(4) + questMethod(4) prefix before the fixed integer header.
const size_t base = 8;
// Classic/TBC: 40 fixed uint32 fields + 4 strings before objectives.
// WotLK: 55 fixed uint32 fields + 5 strings before objectives.
const size_t classicStart = base + 40u * 4u;
const size_t wotlkStart = base + 55u * 4u;
// Try the expected layout first, then fall back to the other.
if (classicHint) {
auto r = tryParseQuestObjectivesAt(data, classicStart, 4);
if (r.valid) return r;
return tryParseQuestObjectivesAt(data, wotlkStart, 5);
} else {
auto r = tryParseQuestObjectivesAt(data, wotlkStart, 5);
if (r.valid) return r;
return tryParseQuestObjectivesAt(data, classicStart, 4);
}
}
// Parse quest reward fields from SMSG_QUEST_QUERY_RESPONSE fixed header.
// Classic/TBC: 40 fixed fields; WotLK: 55 fixed fields.
struct QuestQueryRewards {
int32_t rewardMoney = 0;
std::array<uint32_t, 4> itemId{};
std::array<uint32_t, 4> itemCount{};
std::array<uint32_t, 6> choiceItemId{};
std::array<uint32_t, 6> choiceItemCount{};
bool valid = false;
};
static QuestQueryRewards tryParseQuestRewards(const std::vector<uint8_t>& data,
bool classicLayout) {
const size_t base = 8; // after questId(4) + questMethod(4)
const size_t fieldCount = classicLayout ? 40u : 55u;
const size_t headerEnd = base + fieldCount * 4u;
if (data.size() < headerEnd) return {};
// Field indices (0-based) for each expansion:
// Classic/TBC: rewardMoney=[14], rewardItemId[4]=[20..23], rewardItemCount[4]=[24..27],
// rewardChoiceItemId[6]=[28..33], rewardChoiceItemCount[6]=[34..39]
// WotLK: rewardMoney=[17], rewardItemId[4]=[30..33], rewardItemCount[4]=[34..37],
// rewardChoiceItemId[6]=[38..43], rewardChoiceItemCount[6]=[44..49]
const size_t moneyField = classicLayout ? 14u : 17u;
const size_t itemIdField = classicLayout ? 20u : 30u;
const size_t itemCountField = classicLayout ? 24u : 34u;
const size_t choiceIdField = classicLayout ? 28u : 38u;
const size_t choiceCntField = classicLayout ? 34u : 44u;
QuestQueryRewards out;
out.rewardMoney = static_cast<int32_t>(readU32At(data, base + moneyField * 4u));
for (size_t i = 0; i < 4; ++i) {
out.itemId[i] = readU32At(data, base + (itemIdField + i) * 4u);
out.itemCount[i] = readU32At(data, base + (itemCountField + i) * 4u);
}
for (size_t i = 0; i < 6; ++i) {
out.choiceItemId[i] = readU32At(data, base + (choiceIdField + i) * 4u);
out.choiceItemCount[i] = readU32At(data, base + (choiceCntField + i) * 4u);
}
out.valid = true;
return out;
}
} // namespace
template<typename ManagerGetter, typename Callback>
void GameHandler::withSoundManager(ManagerGetter getter, Callback cb) {
if (auto* ac = services_.audioCoordinator) {
@ -5751,40 +5305,6 @@ void GameHandler::acceptBattlefield(uint32_t queueSlot) {
// LFG / Dungeon Finder handlers (WotLK 3.3.5a)
// ---------------------------------------------------------------------------
static const char* lfgJoinResultString(uint8_t result) {
switch (result) {
case 0: return nullptr; // success
case 1: return "Role check failed.";
case 2: return "No LFG slots available for your group.";
case 3: return "No LFG object found.";
case 4: return "No slots available (player).";
case 5: return "No slots available (party).";
case 6: return "Dungeon requirements not met by all members.";
case 7: return "Party members are from different realms.";
case 8: return "Not all members are present.";
case 9: return "Get info timeout.";
case 10: return "Invalid dungeon slot.";
case 11: return "You are marked as a deserter.";
case 12: return "A party member is marked as a deserter.";
case 13: return "You are on a random dungeon cooldown.";
case 14: return "A party member is on a random dungeon cooldown.";
case 16: return "No spec/role available.";
default: return "Cannot join dungeon finder.";
}
}
static const char* lfgTeleportDeniedString(uint8_t reason) {
switch (reason) {
case 0: return "You are not in a LFG group.";
case 1: return "You are not in the dungeon.";
case 2: return "You have a summon pending.";
case 3: return "You are dead.";
case 4: return "You have Deserter.";
case 5: return "You do not meet the requirements.";
default: return "Teleport to dungeon denied.";
}
}
// ---------------------------------------------------------------------------
// LFG outgoing packets
// ---------------------------------------------------------------------------
@ -5922,16 +5442,6 @@ float GameHandler::getSpellCooldown(uint32_t spellId) const {
return 0;
}
static audio::SpellSoundManager::MagicSchool schoolMaskToMagicSchool(uint32_t mask) {
if (mask & 0x04) return audio::SpellSoundManager::MagicSchool::FIRE;
if (mask & 0x10) return audio::SpellSoundManager::MagicSchool::FROST;
if (mask & 0x02) return audio::SpellSoundManager::MagicSchool::HOLY;
if (mask & 0x08) return audio::SpellSoundManager::MagicSchool::NATURE;
if (mask & 0x20) return audio::SpellSoundManager::MagicSchool::SHADOW;
if (mask & 0x40) return audio::SpellSoundManager::MagicSchool::ARCANE;
return audio::SpellSoundManager::MagicSchool::ARCANE;
}
// ============================================================
// Talents
// ============================================================

View file

@ -53,23 +53,6 @@ namespace {
return s.substr(first, last - first + 1);
}
// Format a duration in seconds as compact text: "2h", "3:05", "42"
void fmtDurationCompact(char* buf, size_t sz, int secs) {
if (secs >= 3600) snprintf(buf, sz, "%dh", secs / 3600);
else if (secs >= 60) snprintf(buf, sz, "%d:%02d", secs / 60, secs % 60);
else snprintf(buf, sz, "%d", secs);
}
// Render "Remaining: Xs" or "Remaining: Xm Ys" in a tooltip (light gray)
void renderAuraRemaining(int remainMs) {
if (remainMs <= 0) return;
int s = remainMs / 1000;
char buf[32];
if (s < 60) snprintf(buf, sizeof(buf), "Remaining: %ds", s);
else snprintf(buf, sizeof(buf), "Remaining: %dm %ds", s / 60, s % 60);
ImGui::TextColored(kLightGray, "%s", buf);
}
std::string toLower(std::string s) {
std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) {
return static_cast<char>(std::tolower(c));
@ -187,12 +170,10 @@ bool ChatPanel::shouldShowMessage(const game::MessageChatData& msg, int tabIndex
// Forward declaration — defined below
static std::string firstMacroCommand(const std::string& macroText);
static std::vector<std::string> allMacroCommands(const std::string& macroText);
static std::string evaluateMacroConditionals(const std::string& rawArg,
game::GameHandler& gameHandler,
uint64_t& targetOverride);
static std::string getMacroShowtooltipArg(const std::string& macroText);
void ChatPanel::render(game::GameHandler& gameHandler,
InventoryScreen& inventoryScreen,
@ -1708,22 +1689,6 @@ void ChatPanel::render(game::GameHandler& gameHandler,
}
static std::string firstMacroCommand(const std::string& macroText) {
size_t pos = 0;
while (pos <= macroText.size()) {
size_t nl = macroText.find('\n', pos);
std::string line = (nl != std::string::npos) ? macroText.substr(pos, nl - pos) : macroText.substr(pos);
if (!line.empty() && line.back() == '\r') line.pop_back();
size_t start = line.find_first_not_of(" \t");
if (start != std::string::npos) line = line.substr(start);
if (!line.empty() && line.front() != '#')
return line;
if (nl == std::string::npos) break;
pos = nl + 1;
}
return {};
}
// Collect all non-comment, non-empty lines from a macro body.
static std::vector<std::string> allMacroCommands(const std::string& macroText) {
std::vector<std::string> cmds;
@ -1742,36 +1707,6 @@ static std::vector<std::string> allMacroCommands(const std::string& macroText) {
return cmds;
}
// Returns the #showtooltip argument from a macro body:
// "#showtooltip Spell" → "Spell"
// "#showtooltip" → "__auto__" (derive from first /cast)
// (none) → ""
static std::string getMacroShowtooltipArg(const std::string& macroText) {
size_t pos = 0;
while (pos <= macroText.size()) {
size_t nl = macroText.find('\n', pos);
std::string line = (nl != std::string::npos) ? macroText.substr(pos, nl - pos) : macroText.substr(pos);
if (!line.empty() && line.back() == '\r') line.pop_back();
size_t fs = line.find_first_not_of(" \t");
if (fs != std::string::npos) line = line.substr(fs);
if (line.rfind("#showtooltip", 0) == 0 || line.rfind("#show", 0) == 0) {
size_t sp = line.find(' ');
if (sp != std::string::npos) {
std::string arg = line.substr(sp + 1);
size_t as = arg.find_first_not_of(" \t");
if (as != std::string::npos) arg = arg.substr(as);
size_t ae = arg.find_last_not_of(" \t");
if (ae != std::string::npos) arg.resize(ae + 1);
if (!arg.empty()) return arg;
}
return "__auto__";
}
if (nl == std::string::npos) break;
pos = nl + 1;
}
return {};
}
// ---------------------------------------------------------------------------
// WoW macro conditional evaluator
// Parses: [cond1,cond2] Spell1; [cond3] Spell2; DefaultSpell

View file

@ -32,13 +32,6 @@ namespace {
constexpr auto& kColorBrightGreen = kBrightGreen;
constexpr auto& kColorYellow = kYellow;
// Format a duration in seconds as compact text: "2h", "3:05", "42"
void fmtDurationCompact(char* buf, size_t sz, int secs) {
if (secs >= 3600) snprintf(buf, sz, "%dh", secs / 3600);
else if (secs >= 60) snprintf(buf, sz, "%d:%02d", secs / 60, secs % 60);
else snprintf(buf, sz, "%d", secs);
}
// Render "Remaining: Xs" or "Remaining: Xm Ys" in a tooltip (light gray)
void renderAuraRemaining(int remainMs) {
if (remainMs <= 0) return;
@ -269,7 +262,6 @@ void CombatUI::renderRaidWarningOverlay(game::GameHandler& gameHandler) {
// Walk only the new messages (deque — iterate from back by skipping old ones)
size_t toScan = newCount - raidWarnChatSeenCount_;
size_t startIdx = newCount > toScan ? newCount - toScan : 0;
auto* renderer = services_.renderer;
for (size_t i = startIdx; i < newCount; ++i) {
const auto& msg = chatHistory[i];
if (msg.type == game::ChatType::RAID_WARNING ||

View file

@ -76,24 +76,6 @@ namespace {
// Common ImGui window flags for popup dialogs
const ImGuiWindowFlags kDialogFlags = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize;
// Build a WoW-format item link string for chat insertion.
// Format: |cff<qualHex>|Hitem:<itemId>:0:0:0:0:0:0:0:0|h[<name>]|h|r
std::string buildItemChatLink(uint32_t itemId, uint8_t quality, const std::string& name) {
static constexpr const char* kQualHex[] = {"9d9d9d","ffffff","1eff00","0070dd","a335ee","ff8000","e6cc80","e6cc80"};
uint8_t qi = quality < 8 ? quality : 1;
char buf[512];
snprintf(buf, sizeof(buf), "|cff%s|Hitem:%u:0:0:0:0:0:0:0:0|h[%s]|h|r",
kQualHex[qi], itemId, name.c_str());
return buf;
}
std::string trim(const std::string& s) {
size_t first = s.find_first_not_of(" \t\r\n");
if (first == std::string::npos) return "";
size_t last = s.find_last_not_of(" \t\r\n");
return s.substr(first, last - first + 1);
}
// Format a duration in seconds as compact text: "2h", "3:05", "42"
void fmtDurationCompact(char* buf, size_t sz, int secs) {
if (secs >= 3600) snprintf(buf, sz, "%dh", secs / 3600);
@ -111,13 +93,6 @@ namespace {
ImGui::TextColored(kLightGray, "%s", buf);
}
std::string toLower(std::string s) {
std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) {
return static_cast<char>(std::tolower(c));
});
return s;
}
// Render gold/silver/copper amounts in WoW-canonical colors on the current ImGui line.
// Skips zero-value denominations (except copper, which is always shown when gold=silver=0).
// Aliases for shared class color helpers (wowee::ui namespace)
@ -138,40 +113,6 @@ namespace {
return wowee::game::getClassName(static_cast<wowee::game::Class>(classId));
}
bool isPortBotTarget(const std::string& target) {
std::string t = toLower(trim(target));
return t == "portbot" || t == "gmbot" || t == "telebot";
}
std::string buildPortBotCommand(const std::string& rawInput) {
std::string input = trim(rawInput);
if (input.empty()) return "";
std::string lower = toLower(input);
if (lower == "help" || lower == "?") {
return "__help__";
}
if (lower.rfind(".tele ", 0) == 0 || lower.rfind(".go ", 0) == 0) {
return input;
}
if (lower.rfind("xyz ", 0) == 0) {
return ".go " + input;
}
if (lower == "sw" || lower == "stormwind") return ".tele stormwind";
if (lower == "if" || lower == "ironforge") return ".tele ironforge";
if (lower == "darn" || lower == "darnassus") return ".tele darnassus";
if (lower == "org" || lower == "orgrimmar") return ".tele orgrimmar";
if (lower == "tb" || lower == "thunderbluff") return ".tele thunderbluff";
if (lower == "uc" || lower == "undercity") return ".tele undercity";
if (lower == "shatt" || lower == "shattrath") return ".tele shattrath";
if (lower == "dal" || lower == "dalaran") return ".tele dalaran";
return ".tele " + input;
}
bool raySphereIntersect(const wowee::rendering::Ray& ray, const glm::vec3& center, float radius, float& tOut) {
glm::vec3 oc = ray.origin - center;
float b = glm::dot(oc, ray.direction);
@ -199,50 +140,6 @@ namespace {
return "Unknown";
}
// Collect all non-comment, non-empty lines from a macro body.
std::vector<std::string> allMacroCommands(const std::string& macroText) {
std::vector<std::string> cmds;
size_t pos = 0;
while (pos <= macroText.size()) {
size_t nl = macroText.find('\n', pos);
std::string line = (nl != std::string::npos) ? macroText.substr(pos, nl - pos) : macroText.substr(pos);
if (!line.empty() && line.back() == '\r') line.pop_back();
size_t start = line.find_first_not_of(" \t");
if (start != std::string::npos) line = line.substr(start);
if (!line.empty() && line.front() != '#')
cmds.push_back(std::move(line));
if (nl == std::string::npos) break;
pos = nl + 1;
}
return cmds;
}
// Returns the #showtooltip argument from a macro body.
std::string getMacroShowtooltipArg(const std::string& macroText) {
size_t pos = 0;
while (pos <= macroText.size()) {
size_t nl = macroText.find('\n', pos);
std::string line = (nl != std::string::npos) ? macroText.substr(pos, nl - pos) : macroText.substr(pos);
if (!line.empty() && line.back() == '\r') line.pop_back();
size_t fs = line.find_first_not_of(" \t");
if (fs != std::string::npos) line = line.substr(fs);
if (line.rfind("#showtooltip", 0) == 0 || line.rfind("#show", 0) == 0) {
size_t sp = line.find(' ');
if (sp != std::string::npos) {
std::string arg = line.substr(sp + 1);
size_t as = arg.find_first_not_of(" \t");
if (as != std::string::npos) arg = arg.substr(as);
size_t ae = arg.find_last_not_of(" \t");
if (ae != std::string::npos) arg.resize(ae + 1);
if (!arg.empty()) return arg;
}
return "__auto__";
}
if (nl == std::string::npos) break;
pos = nl + 1;
}
return {};
}
}
namespace wowee { namespace ui {

View file

@ -43,25 +43,6 @@ namespace {
constexpr auto& kColorGray = kGray;
constexpr auto& kColorDarkGray = kDarkGray;
// Render gold/silver/copper amounts in WoW-canonical colors
void renderGoldText(uint32_t totalCopper) {
uint32_t gold = totalCopper / 10000;
uint32_t silver = (totalCopper / 100) % 100;
uint32_t copper = totalCopper % 100;
bool shown = false;
if (gold > 0) {
ImGui::TextColored(ImVec4(1.0f, 0.84f, 0.0f, 1.0f), "%ug", gold);
shown = true;
}
if (silver > 0 || shown) {
if (shown) { ImGui::SameLine(0, 2); }
ImGui::TextColored(ImVec4(0.75f, 0.75f, 0.75f, 1.0f), "%us", silver);
shown = true;
}
if (shown) { ImGui::SameLine(0, 2); }
ImGui::TextColored(ImVec4(0.72f, 0.45f, 0.2f, 1.0f), "%uc", copper);
}
// Common ImGui window flags for popup dialogs
const ImGuiWindowFlags kDialogFlags = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize;