mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
Compare commits
36 commits
5ee2b55f4b
...
3103662528
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3103662528 | ||
|
|
42222e4095 | ||
|
|
a4c8fd621d | ||
|
|
afc5266acf | ||
|
|
c836b421fc | ||
|
|
42b776dbf8 | ||
|
|
d575c06bc1 | ||
|
|
be841fb3e1 | ||
|
|
1f6865afce | ||
|
|
1fd220de29 | ||
|
|
1988e778c7 | ||
|
|
24e2069225 | ||
|
|
d8c0820c76 | ||
|
|
964437cdf4 | ||
|
|
5d6376f3f1 | ||
|
|
a4ff315c81 | ||
|
|
3ad917bd95 | ||
|
|
9b2f100387 | ||
|
|
c97898712b | ||
|
|
8dca33e5cc | ||
|
|
b1171327cb | ||
|
|
4364fa7bbe | ||
|
|
9267aec0b0 | ||
|
|
ac9214c03f | ||
|
|
f580fd7e6b | ||
|
|
dcd78f4f28 | ||
|
|
4af9838ab4 | ||
|
|
aebc905261 | ||
|
|
57ccee2c28 | ||
|
|
586e9e74ff | ||
|
|
82990f5891 | ||
|
|
e7be60c624 | ||
|
|
2c6a345e32 | ||
|
|
a4d54e83bc | ||
|
|
a73c680190 | ||
|
|
2da0883544 |
5 changed files with 527 additions and 22 deletions
|
|
@ -1300,7 +1300,7 @@ public:
|
|||
|
||||
// Barber shop
|
||||
bool isBarberShopOpen() const { return barberShopOpen_; }
|
||||
void closeBarberShop() { barberShopOpen_ = false; }
|
||||
void closeBarberShop() { barberShopOpen_ = false; if (addonEventCallback_) addonEventCallback_("BARBER_SHOP_CLOSE", {}); }
|
||||
void sendAlterAppearance(uint32_t hairStyle, uint32_t hairColor, uint32_t facialHair);
|
||||
|
||||
// Instance difficulty (0=5N, 1=5H, 2=25N, 3=25H for WotLK)
|
||||
|
|
|
|||
|
|
@ -268,7 +268,17 @@ static int lua_UnitExists(lua_State* L) {
|
|||
static int lua_UnitIsDead(lua_State* L) {
|
||||
const char* uid = luaL_optstring(L, 1, "player");
|
||||
auto* unit = resolveUnit(L, uid);
|
||||
lua_pushboolean(L, unit && unit->getHealth() == 0);
|
||||
if (unit) {
|
||||
lua_pushboolean(L, unit->getHealth() == 0);
|
||||
} else {
|
||||
// Fallback: party member stats for out-of-range members
|
||||
auto* gh = getGameHandler(L);
|
||||
std::string uidStr(uid);
|
||||
for (char& c : uidStr) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||
uint64_t guid = gh ? resolveUnitGuid(gh, uidStr) : 0;
|
||||
const auto* pm = findPartyMember(gh, guid);
|
||||
lua_pushboolean(L, pm ? (pm->curHealth == 0 && pm->maxHealth > 0) : 0);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
|
@ -413,6 +423,324 @@ static int lua_UnitPlayerControlled(lua_State* L) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
// UnitIsTapped(unit) — true if mob is tapped (tagged by any player)
|
||||
static int lua_UnitIsTapped(lua_State* L) {
|
||||
const char* uid = luaL_optstring(L, 1, "target");
|
||||
auto* unit = resolveUnit(L, uid);
|
||||
if (!unit) { lua_pushboolean(L, 0); return 1; }
|
||||
lua_pushboolean(L, (unit->getDynamicFlags() & 0x0004) != 0); // UNIT_DYNFLAG_TAPPED_BY_PLAYER
|
||||
return 1;
|
||||
}
|
||||
|
||||
// UnitIsTappedByPlayer(unit) — true if tapped by the local player (can loot)
|
||||
static int lua_UnitIsTappedByPlayer(lua_State* L) {
|
||||
const char* uid = luaL_optstring(L, 1, "target");
|
||||
auto* unit = resolveUnit(L, uid);
|
||||
if (!unit) { lua_pushboolean(L, 0); return 1; }
|
||||
uint32_t df = unit->getDynamicFlags();
|
||||
// Tapped by player: has TAPPED flag but also LOOTABLE or TAPPED_BY_ALL
|
||||
bool tapped = (df & 0x0004) != 0;
|
||||
bool lootable = (df & 0x0001) != 0;
|
||||
bool sharedTag = (df & 0x0008) != 0;
|
||||
lua_pushboolean(L, tapped && (lootable || sharedTag));
|
||||
return 1;
|
||||
}
|
||||
|
||||
// UnitIsTappedByAllThreatList(unit) — true if shared-tag mob
|
||||
static int lua_UnitIsTappedByAllThreatList(lua_State* L) {
|
||||
const char* uid = luaL_optstring(L, 1, "target");
|
||||
auto* unit = resolveUnit(L, uid);
|
||||
if (!unit) { lua_pushboolean(L, 0); return 1; }
|
||||
lua_pushboolean(L, (unit->getDynamicFlags() & 0x0008) != 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// UnitThreatSituation(unit, mobUnit) → 0=not tanking, 1=not tanking but threat, 2=insecurely tanking, 3=securely tanking
|
||||
static int lua_UnitThreatSituation(lua_State* L) {
|
||||
auto* gh = getGameHandler(L);
|
||||
if (!gh) { lua_pushnumber(L, 0); return 1; }
|
||||
const char* uid = luaL_optstring(L, 1, "player");
|
||||
const char* mobUid = luaL_optstring(L, 2, nullptr);
|
||||
std::string uidStr(uid);
|
||||
for (char& c : uidStr) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||
uint64_t playerUnitGuid = resolveUnitGuid(gh, uidStr);
|
||||
if (playerUnitGuid == 0) { lua_pushnumber(L, 0); return 1; }
|
||||
// If no mob specified, check general combat threat against current target
|
||||
uint64_t mobGuid = 0;
|
||||
if (mobUid && *mobUid) {
|
||||
std::string mStr(mobUid);
|
||||
for (char& c : mStr) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||
mobGuid = resolveUnitGuid(gh, mStr);
|
||||
}
|
||||
// Approximate threat: check if the mob is targeting this unit
|
||||
if (mobGuid != 0) {
|
||||
auto mobEntity = gh->getEntityManager().getEntity(mobGuid);
|
||||
if (mobEntity) {
|
||||
const auto& fields = mobEntity->getFields();
|
||||
auto loIt = fields.find(game::fieldIndex(game::UF::UNIT_FIELD_TARGET_LO));
|
||||
if (loIt != fields.end()) {
|
||||
uint64_t mobTarget = loIt->second;
|
||||
auto hiIt = fields.find(game::fieldIndex(game::UF::UNIT_FIELD_TARGET_HI));
|
||||
if (hiIt != fields.end())
|
||||
mobTarget |= (static_cast<uint64_t>(hiIt->second) << 32);
|
||||
if (mobTarget == playerUnitGuid) {
|
||||
lua_pushnumber(L, 3); // securely tanking
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check if player is in combat (basic threat indicator)
|
||||
if (playerUnitGuid == gh->getPlayerGuid() && gh->isInCombat()) {
|
||||
lua_pushnumber(L, 1); // in combat but not tanking
|
||||
return 1;
|
||||
}
|
||||
lua_pushnumber(L, 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// UnitDetailedThreatSituation(unit, mobUnit) → isTanking, status, threatPct, rawThreatPct, threatValue
|
||||
static int lua_UnitDetailedThreatSituation(lua_State* L) {
|
||||
// Use UnitThreatSituation logic for the basics
|
||||
auto* gh = getGameHandler(L);
|
||||
if (!gh) {
|
||||
lua_pushboolean(L, 0); lua_pushnumber(L, 0); lua_pushnumber(L, 0); lua_pushnumber(L, 0); lua_pushnumber(L, 0);
|
||||
return 5;
|
||||
}
|
||||
const char* uid = luaL_optstring(L, 1, "player");
|
||||
const char* mobUid = luaL_optstring(L, 2, nullptr);
|
||||
std::string uidStr(uid);
|
||||
for (char& c : uidStr) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||
uint64_t unitGuid = resolveUnitGuid(gh, uidStr);
|
||||
bool isTanking = false;
|
||||
int status = 0;
|
||||
if (unitGuid != 0 && mobUid && *mobUid) {
|
||||
std::string mStr(mobUid);
|
||||
for (char& c : mStr) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||
uint64_t mobGuid = resolveUnitGuid(gh, mStr);
|
||||
if (mobGuid != 0) {
|
||||
auto mobEnt = gh->getEntityManager().getEntity(mobGuid);
|
||||
if (mobEnt) {
|
||||
const auto& f = mobEnt->getFields();
|
||||
auto lo = f.find(game::fieldIndex(game::UF::UNIT_FIELD_TARGET_LO));
|
||||
if (lo != f.end()) {
|
||||
uint64_t mt = lo->second;
|
||||
auto hi = f.find(game::fieldIndex(game::UF::UNIT_FIELD_TARGET_HI));
|
||||
if (hi != f.end()) mt |= (static_cast<uint64_t>(hi->second) << 32);
|
||||
if (mt == unitGuid) { isTanking = true; status = 3; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
lua_pushboolean(L, isTanking);
|
||||
lua_pushnumber(L, status);
|
||||
lua_pushnumber(L, isTanking ? 100.0 : 0.0); // threatPct
|
||||
lua_pushnumber(L, isTanking ? 100.0 : 0.0); // rawThreatPct
|
||||
lua_pushnumber(L, 0); // threatValue (not available without server threat data)
|
||||
return 5;
|
||||
}
|
||||
|
||||
// UnitDistanceSquared(unit) → distSq, canCalculate
|
||||
static int lua_UnitDistanceSquared(lua_State* L) {
|
||||
auto* gh = getGameHandler(L);
|
||||
if (!gh) { lua_pushnumber(L, 0); lua_pushboolean(L, 0); return 2; }
|
||||
const char* uid = luaL_checkstring(L, 1);
|
||||
std::string uidStr(uid);
|
||||
for (char& c : uidStr) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||
uint64_t guid = resolveUnitGuid(gh, uidStr);
|
||||
if (guid == 0 || guid == gh->getPlayerGuid()) { lua_pushnumber(L, 0); lua_pushboolean(L, 0); return 2; }
|
||||
auto targetEnt = gh->getEntityManager().getEntity(guid);
|
||||
auto playerEnt = gh->getEntityManager().getEntity(gh->getPlayerGuid());
|
||||
if (!targetEnt || !playerEnt) { lua_pushnumber(L, 0); lua_pushboolean(L, 0); return 2; }
|
||||
float dx = playerEnt->getX() - targetEnt->getX();
|
||||
float dy = playerEnt->getY() - targetEnt->getY();
|
||||
float dz = playerEnt->getZ() - targetEnt->getZ();
|
||||
lua_pushnumber(L, dx*dx + dy*dy + dz*dz);
|
||||
lua_pushboolean(L, 1);
|
||||
return 2;
|
||||
}
|
||||
|
||||
// CheckInteractDistance(unit, distIndex) → boolean
|
||||
// distIndex: 1=inspect(28yd), 2=trade(11yd), 3=duel(10yd), 4=follow(28yd)
|
||||
static int lua_CheckInteractDistance(lua_State* L) {
|
||||
auto* gh = getGameHandler(L);
|
||||
if (!gh) { lua_pushboolean(L, 0); return 1; }
|
||||
const char* uid = luaL_checkstring(L, 1);
|
||||
int distIdx = static_cast<int>(luaL_optnumber(L, 2, 4));
|
||||
std::string uidStr(uid);
|
||||
for (char& c : uidStr) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||
uint64_t guid = resolveUnitGuid(gh, uidStr);
|
||||
if (guid == 0) { lua_pushboolean(L, 0); return 1; }
|
||||
auto targetEnt = gh->getEntityManager().getEntity(guid);
|
||||
auto playerEnt = gh->getEntityManager().getEntity(gh->getPlayerGuid());
|
||||
if (!targetEnt || !playerEnt) { lua_pushboolean(L, 0); return 1; }
|
||||
float dx = playerEnt->getX() - targetEnt->getX();
|
||||
float dy = playerEnt->getY() - targetEnt->getY();
|
||||
float dz = playerEnt->getZ() - targetEnt->getZ();
|
||||
float dist = std::sqrt(dx*dx + dy*dy + dz*dz);
|
||||
float maxDist = 28.0f; // default: follow/inspect range
|
||||
switch (distIdx) {
|
||||
case 1: maxDist = 28.0f; break; // inspect
|
||||
case 2: maxDist = 11.11f; break; // trade
|
||||
case 3: maxDist = 9.9f; break; // duel
|
||||
case 4: maxDist = 28.0f; break; // follow
|
||||
}
|
||||
lua_pushboolean(L, dist <= maxDist);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// IsSpellInRange(spellName, unit) → 0 or 1 (nil if can't determine)
|
||||
static int lua_IsSpellInRange(lua_State* L) {
|
||||
auto* gh = getGameHandler(L);
|
||||
if (!gh) { lua_pushnil(L); return 1; }
|
||||
const char* spellNameOrId = luaL_checkstring(L, 1);
|
||||
const char* uid = luaL_optstring(L, 2, "target");
|
||||
|
||||
// Resolve spell ID
|
||||
uint32_t spellId = 0;
|
||||
if (spellNameOrId[0] >= '0' && spellNameOrId[0] <= '9') {
|
||||
spellId = static_cast<uint32_t>(strtoul(spellNameOrId, nullptr, 10));
|
||||
} else {
|
||||
std::string nameLow(spellNameOrId);
|
||||
for (char& c : nameLow) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||
for (uint32_t sid : gh->getKnownSpells()) {
|
||||
std::string sn = gh->getSpellName(sid);
|
||||
for (char& c : sn) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||
if (sn == nameLow) { spellId = sid; break; }
|
||||
}
|
||||
}
|
||||
if (spellId == 0) { lua_pushnil(L); return 1; }
|
||||
|
||||
// Get spell max range from DBC
|
||||
auto data = gh->getSpellData(spellId);
|
||||
if (data.maxRange <= 0.0f) { lua_pushnil(L); return 1; }
|
||||
|
||||
// Resolve target position
|
||||
std::string uidStr(uid);
|
||||
for (char& c : uidStr) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||
uint64_t guid = resolveUnitGuid(gh, uidStr);
|
||||
if (guid == 0) { lua_pushnil(L); return 1; }
|
||||
auto targetEnt = gh->getEntityManager().getEntity(guid);
|
||||
auto playerEnt = gh->getEntityManager().getEntity(gh->getPlayerGuid());
|
||||
if (!targetEnt || !playerEnt) { lua_pushnil(L); return 1; }
|
||||
|
||||
float dx = playerEnt->getX() - targetEnt->getX();
|
||||
float dy = playerEnt->getY() - targetEnt->getY();
|
||||
float dz = playerEnt->getZ() - targetEnt->getZ();
|
||||
float dist = std::sqrt(dx*dx + dy*dy + dz*dz);
|
||||
lua_pushnumber(L, dist <= data.maxRange ? 1 : 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// UnitIsVisible(unit) → boolean (entity exists in the client's entity manager)
|
||||
static int lua_UnitIsVisible(lua_State* L) {
|
||||
const char* uid = luaL_optstring(L, 1, "target");
|
||||
auto* unit = resolveUnit(L, uid);
|
||||
lua_pushboolean(L, unit != nullptr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// UnitGroupRolesAssigned(unit) → "TANK", "HEALER", "DAMAGER", or "NONE"
|
||||
static int lua_UnitGroupRolesAssigned(lua_State* L) {
|
||||
auto* gh = getGameHandler(L);
|
||||
if (!gh) { lua_pushstring(L, "NONE"); return 1; }
|
||||
const char* uid = luaL_optstring(L, 1, "player");
|
||||
std::string uidStr(uid);
|
||||
for (char& c : uidStr) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||
uint64_t guid = resolveUnitGuid(gh, uidStr);
|
||||
if (guid == 0) { lua_pushstring(L, "NONE"); return 1; }
|
||||
const auto& pd = gh->getPartyData();
|
||||
for (const auto& m : pd.members) {
|
||||
if (m.guid == guid) {
|
||||
// WotLK roles bitmask: 0x02=Tank, 0x04=Healer, 0x08=DPS
|
||||
if (m.roles & 0x02) { lua_pushstring(L, "TANK"); return 1; }
|
||||
if (m.roles & 0x04) { lua_pushstring(L, "HEALER"); return 1; }
|
||||
if (m.roles & 0x08) { lua_pushstring(L, "DAMAGER"); return 1; }
|
||||
break;
|
||||
}
|
||||
}
|
||||
lua_pushstring(L, "NONE");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// UnitCanAttack(unit, otherUnit) → boolean
|
||||
static int lua_UnitCanAttack(lua_State* L) {
|
||||
auto* gh = getGameHandler(L);
|
||||
if (!gh) { lua_pushboolean(L, 0); return 1; }
|
||||
const char* uid1 = luaL_checkstring(L, 1);
|
||||
const char* uid2 = luaL_checkstring(L, 2);
|
||||
std::string u1(uid1), u2(uid2);
|
||||
for (char& c : u1) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||
for (char& c : u2) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||
uint64_t g1 = resolveUnitGuid(gh, u1);
|
||||
uint64_t g2 = resolveUnitGuid(gh, u2);
|
||||
if (g1 == 0 || g2 == 0 || g1 == g2) { lua_pushboolean(L, 0); return 1; }
|
||||
// Check if unit2 is hostile to unit1
|
||||
auto* unit2 = resolveUnit(L, uid2);
|
||||
if (unit2 && unit2->isHostile()) {
|
||||
lua_pushboolean(L, 1);
|
||||
} else {
|
||||
lua_pushboolean(L, 0);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
// UnitCanCooperate(unit, otherUnit) → boolean
|
||||
static int lua_UnitCanCooperate(lua_State* L) {
|
||||
auto* gh = getGameHandler(L);
|
||||
if (!gh) { lua_pushboolean(L, 0); return 1; }
|
||||
(void)luaL_checkstring(L, 1); // unit1 (unused — cooperation is based on unit2's hostility)
|
||||
const char* uid2 = luaL_checkstring(L, 2);
|
||||
auto* unit2 = resolveUnit(L, uid2);
|
||||
if (!unit2) { lua_pushboolean(L, 0); return 1; }
|
||||
lua_pushboolean(L, !unit2->isHostile());
|
||||
return 1;
|
||||
}
|
||||
|
||||
// UnitCreatureFamily(unit) → familyName or nil
|
||||
static int lua_UnitCreatureFamily(lua_State* L) {
|
||||
auto* gh = getGameHandler(L);
|
||||
if (!gh) { lua_pushnil(L); return 1; }
|
||||
const char* uid = luaL_optstring(L, 1, "target");
|
||||
std::string uidStr(uid);
|
||||
for (char& c : uidStr) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||
uint64_t guid = resolveUnitGuid(gh, uidStr);
|
||||
if (guid == 0) { lua_pushnil(L); return 1; }
|
||||
auto entity = gh->getEntityManager().getEntity(guid);
|
||||
if (!entity || entity->getType() == game::ObjectType::PLAYER) { lua_pushnil(L); return 1; }
|
||||
auto unit = std::dynamic_pointer_cast<game::Unit>(entity);
|
||||
if (!unit) { lua_pushnil(L); return 1; }
|
||||
uint32_t family = gh->getCreatureFamily(unit->getEntry());
|
||||
if (family == 0) { lua_pushnil(L); return 1; }
|
||||
static const char* kFamilies[] = {
|
||||
"", "Wolf", "Cat", "Spider", "Bear", "Boar", "Crocolisk", "Carrion Bird",
|
||||
"Crab", "Gorilla", "Raptor", "", "Tallstrider", "", "", "Felhunter",
|
||||
"Voidwalker", "Succubus", "", "Doomguard", "Scorpid", "Turtle", "",
|
||||
"Imp", "Bat", "Hyena", "Bird of Prey", "Wind Serpent", "", "Dragonhawk",
|
||||
"Ravager", "Warp Stalker", "Sporebat", "Nether Ray", "Serpent", "Moth",
|
||||
"Chimaera", "Devilsaur", "Ghoul", "Silithid", "Worm", "Rhino", "Wasp",
|
||||
"Core Hound", "Spirit Beast"
|
||||
};
|
||||
lua_pushstring(L, (family < sizeof(kFamilies)/sizeof(kFamilies[0]) && kFamilies[family][0])
|
||||
? kFamilies[family] : "Beast");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// UnitOnTaxi(unit) → boolean (true if on a flight path)
|
||||
static int lua_UnitOnTaxi(lua_State* L) {
|
||||
const char* uid = luaL_optstring(L, 1, "player");
|
||||
auto* gh = getGameHandler(L);
|
||||
if (!gh) { lua_pushboolean(L, 0); return 1; }
|
||||
std::string uidStr(uid);
|
||||
for (char& c : uidStr) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||
if (uidStr == "player") {
|
||||
lua_pushboolean(L, gh->isOnTaxiFlight());
|
||||
} else {
|
||||
lua_pushboolean(L, 0); // Can't determine for other units
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
// UnitSex(unit) → 1=unknown, 2=male, 3=female
|
||||
static int lua_UnitSex(lua_State* L) {
|
||||
const char* uid = luaL_optstring(L, 1, "player");
|
||||
|
|
@ -502,10 +830,22 @@ static int lua_UnitRace(lua_State* L) {
|
|||
static int lua_UnitPowerType(lua_State* L) {
|
||||
const char* uid = luaL_optstring(L, 1, "player");
|
||||
auto* unit = resolveUnit(L, uid);
|
||||
if (unit) {
|
||||
lua_pushnumber(L, unit->getPowerType());
|
||||
static const char* kPowerNames[] = {"MANA","RAGE","FOCUS","ENERGY","HAPPINESS","","RUNIC_POWER"};
|
||||
if (unit) {
|
||||
uint8_t pt = unit->getPowerType();
|
||||
lua_pushnumber(L, pt);
|
||||
lua_pushstring(L, (pt < 7) ? kPowerNames[pt] : "MANA");
|
||||
return 2;
|
||||
}
|
||||
// Fallback: party member stats for out-of-range members
|
||||
auto* gh = getGameHandler(L);
|
||||
std::string uidStr(uid);
|
||||
for (char& c : uidStr) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||
uint64_t guid = gh ? resolveUnitGuid(gh, uidStr) : 0;
|
||||
const auto* pm = findPartyMember(gh, guid);
|
||||
if (pm) {
|
||||
uint8_t pt = pm->powerType;
|
||||
lua_pushnumber(L, pt);
|
||||
lua_pushstring(L, (pt < 7) ? kPowerNames[pt] : "MANA");
|
||||
return 2;
|
||||
}
|
||||
|
|
@ -1011,6 +1351,27 @@ static int lua_SetRaidTarget(lua_State* L) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
// GetSpellPowerCost(spellId) → {{ type=powerType, cost=manaCost, name=powerName }}
|
||||
static int lua_GetSpellPowerCost(lua_State* L) {
|
||||
auto* gh = getGameHandler(L);
|
||||
if (!gh) { lua_newtable(L); return 1; }
|
||||
uint32_t spellId = static_cast<uint32_t>(luaL_checknumber(L, 1));
|
||||
auto data = gh->getSpellData(spellId);
|
||||
lua_newtable(L); // outer table (array of cost entries)
|
||||
if (data.manaCost > 0) {
|
||||
lua_newtable(L); // cost entry
|
||||
lua_pushnumber(L, data.powerType);
|
||||
lua_setfield(L, -2, "type");
|
||||
lua_pushnumber(L, data.manaCost);
|
||||
lua_setfield(L, -2, "cost");
|
||||
static const char* kPowerNames[] = {"MANA","RAGE","FOCUS","ENERGY","HAPPINESS","","RUNIC_POWER"};
|
||||
lua_pushstring(L, data.powerType < 7 ? kPowerNames[data.powerType] : "MANA");
|
||||
lua_setfield(L, -2, "name");
|
||||
lua_rawseti(L, -2, 1); // outer[1] = entry
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
// --- GetSpellInfo / GetSpellTexture ---
|
||||
// GetSpellInfo(spellIdOrName) -> name, rank, icon, castTime, minRange, maxRange, spellId
|
||||
static int lua_GetSpellInfo(lua_State* L) {
|
||||
|
|
@ -3077,6 +3438,17 @@ void LuaEngine::registerCoreAPI() {
|
|||
{"UnitIsAFK", lua_UnitIsAFK},
|
||||
{"UnitIsDND", lua_UnitIsDND},
|
||||
{"UnitPlayerControlled", lua_UnitPlayerControlled},
|
||||
{"UnitIsTapped", lua_UnitIsTapped},
|
||||
{"UnitIsTappedByPlayer", lua_UnitIsTappedByPlayer},
|
||||
{"UnitIsTappedByAllThreatList", lua_UnitIsTappedByAllThreatList},
|
||||
{"UnitIsVisible", lua_UnitIsVisible},
|
||||
{"UnitGroupRolesAssigned", lua_UnitGroupRolesAssigned},
|
||||
{"UnitCanAttack", lua_UnitCanAttack},
|
||||
{"UnitCanCooperate", lua_UnitCanCooperate},
|
||||
{"UnitCreatureFamily", lua_UnitCreatureFamily},
|
||||
{"UnitOnTaxi", lua_UnitOnTaxi},
|
||||
{"UnitThreatSituation", lua_UnitThreatSituation},
|
||||
{"UnitDetailedThreatSituation", lua_UnitDetailedThreatSituation},
|
||||
{"UnitSex", lua_UnitSex},
|
||||
{"UnitClass", lua_UnitClass},
|
||||
{"GetMoney", lua_GetMoney},
|
||||
|
|
@ -3090,6 +3462,10 @@ void LuaEngine::registerCoreAPI() {
|
|||
{"CastSpellByName", lua_CastSpellByName},
|
||||
{"IsSpellKnown", lua_IsSpellKnown},
|
||||
{"GetSpellCooldown", lua_GetSpellCooldown},
|
||||
{"GetSpellPowerCost", lua_GetSpellPowerCost},
|
||||
{"IsSpellInRange", lua_IsSpellInRange},
|
||||
{"UnitDistanceSquared", lua_UnitDistanceSquared},
|
||||
{"CheckInteractDistance", lua_CheckInteractDistance},
|
||||
{"HasTarget", lua_HasTarget},
|
||||
{"TargetUnit", lua_TargetUnit},
|
||||
{"ClearTarget", lua_ClearTarget},
|
||||
|
|
@ -3542,13 +3918,22 @@ void LuaEngine::registerCoreAPI() {
|
|||
"function StaticPopup_Show() end\n"
|
||||
"function StaticPopup_Hide() end\n"
|
||||
// CreateTexture/CreateFontString are now C frame methods in the metatable
|
||||
"RAID_CLASS_COLORS = {\n"
|
||||
" WARRIOR={r=0.78,g=0.61,b=0.43}, PALADIN={r=0.96,g=0.55,b=0.73},\n"
|
||||
" HUNTER={r=0.67,g=0.83,b=0.45}, ROGUE={r=1.0,g=0.96,b=0.41},\n"
|
||||
" PRIEST={r=1.0,g=1.0,b=1.0}, DEATHKNIGHT={r=0.77,g=0.12,b=0.23},\n"
|
||||
" SHAMAN={r=0.0,g=0.44,b=0.87}, MAGE={r=0.41,g=0.80,b=0.94},\n"
|
||||
" WARLOCK={r=0.58,g=0.51,b=0.79}, DRUID={r=1.0,g=0.49,b=0.04},\n"
|
||||
"}\n"
|
||||
"do\n"
|
||||
" local function cc(r,g,b)\n"
|
||||
" local t = {r=r, g=g, b=b}\n"
|
||||
" t.colorStr = string.format('%02x%02x%02x', math.floor(r*255), math.floor(g*255), math.floor(b*255))\n"
|
||||
" function t:GenerateHexColor() return '|cff' .. self.colorStr end\n"
|
||||
" function t:GenerateHexColorMarkup() return '|cff' .. self.colorStr end\n"
|
||||
" return t\n"
|
||||
" end\n"
|
||||
" RAID_CLASS_COLORS = {\n"
|
||||
" WARRIOR=cc(0.78,0.61,0.43), PALADIN=cc(0.96,0.55,0.73),\n"
|
||||
" HUNTER=cc(0.67,0.83,0.45), ROGUE=cc(1.0,0.96,0.41),\n"
|
||||
" PRIEST=cc(1.0,1.0,1.0), DEATHKNIGHT=cc(0.77,0.12,0.23),\n"
|
||||
" SHAMAN=cc(0.0,0.44,0.87), MAGE=cc(0.41,0.80,0.94),\n"
|
||||
" WARLOCK=cc(0.58,0.51,0.79), DRUID=cc(1.0,0.49,0.04),\n"
|
||||
" }\n"
|
||||
"end\n"
|
||||
// Money formatting utility
|
||||
"function GetCoinTextureString(copper)\n"
|
||||
" if not copper or copper == 0 then return '0c' end\n"
|
||||
|
|
@ -3663,6 +4048,21 @@ void LuaEngine::registerCoreAPI() {
|
|||
"function GetShapeshiftFormInfo(index) return nil, nil, nil, nil end\n"
|
||||
// Pet action bar
|
||||
"NUM_PET_ACTION_SLOTS = 10\n"
|
||||
// Common WoW constants used by many addons
|
||||
"MAX_TALENT_TABS = 3\n"
|
||||
"MAX_NUM_TALENTS = 100\n"
|
||||
"BOOKTYPE_SPELL = 0\n"
|
||||
"BOOKTYPE_PET = 1\n"
|
||||
"MAX_PARTY_MEMBERS = 4\n"
|
||||
"MAX_RAID_MEMBERS = 40\n"
|
||||
"MAX_ARENA_TEAMS = 3\n"
|
||||
"INVSLOT_FIRST_EQUIPPED = 1\n"
|
||||
"INVSLOT_LAST_EQUIPPED = 19\n"
|
||||
"NUM_BAG_SLOTS = 4\n"
|
||||
"NUM_BANKBAGSLOTS = 7\n"
|
||||
"CONTAINER_BAG_OFFSET = 0\n"
|
||||
"MAX_SKILLLINE_TABS = 8\n"
|
||||
"TRADE_ENCHANT_SLOT = 7\n"
|
||||
"function GetPetActionInfo(slot) return nil end\n"
|
||||
"function GetPetActionsUsable() return false end\n"
|
||||
);
|
||||
|
|
|
|||
|
|
@ -3933,7 +3933,7 @@ void Application::spawnPlayerCharacter() {
|
|||
// Facial hair geoset: group 2 = 200 + variation + 1
|
||||
activeGeosets.insert(static_cast<uint16_t>(200 + facialId + 1));
|
||||
activeGeosets.insert(401); // Bare forearms (no gloves) — group 4
|
||||
activeGeosets.insert(502); // Bare shins (no boots) — group 5
|
||||
activeGeosets.insert(503); // Bare shins (no boots) — group 5
|
||||
activeGeosets.insert(702); // Ears: default
|
||||
activeGeosets.insert(801); // Bare wrists (no chest armor sleeves) — group 8
|
||||
activeGeosets.insert(902); // Kneepads: default — group 9
|
||||
|
|
@ -6464,7 +6464,7 @@ void Application::spawnOnlineCreature(uint64_t guid, uint32_t displayId, float x
|
|||
};
|
||||
|
||||
uint16_t geosetGloves = pickGeoset(401, 4); // Bare gloves/forearms (group 4)
|
||||
uint16_t geosetBoots = pickGeoset(502, 5); // Bare boots/shins (group 5)
|
||||
uint16_t geosetBoots = pickGeoset(503, 5); // Bare boots/shins (group 5)
|
||||
uint16_t geosetSleeves = pickGeoset(801, 8); // Bare wrists (group 8, controlled by chest)
|
||||
uint16_t geosetPants = pickGeoset(1301, 13); // Bare legs (group 13)
|
||||
uint16_t geosetCape = 0; // Group 15 disabled unless cape is equipped
|
||||
|
|
@ -7276,7 +7276,7 @@ void Application::spawnOnlinePlayer(uint64_t guid,
|
|||
activeGeosets.insert(static_cast<uint16_t>(100 + hairStyleId + 1));
|
||||
activeGeosets.insert(static_cast<uint16_t>(200 + facialFeatures + 1));
|
||||
activeGeosets.insert(401); // Bare forearms (no gloves) — group 4
|
||||
activeGeosets.insert(502); // Bare shins (no boots) — group 5
|
||||
activeGeosets.insert(503); // Bare shins (no boots) — group 5
|
||||
activeGeosets.insert(702); // Ears
|
||||
activeGeosets.insert(801); // Bare wrists (no sleeves) — group 8
|
||||
activeGeosets.insert(902); // Kneepads — group 9
|
||||
|
|
@ -7385,7 +7385,7 @@ void Application::setOnlinePlayerEquipment(uint64_t guid,
|
|||
|
||||
// Per-group defaults — overridden below when equipment provides a geoset value.
|
||||
uint16_t geosetGloves = 401; // Bare forearms (group 4, no gloves)
|
||||
uint16_t geosetBoots = 502; // Bare shins (group 5, no boots)
|
||||
uint16_t geosetBoots = 503; // Bare shins (group 5, no boots)
|
||||
uint16_t geosetSleeves = 801; // Bare wrists (group 8, no chest/sleeves)
|
||||
uint16_t geosetPants = 1301; // Bare legs (group 13, no leggings)
|
||||
|
||||
|
|
|
|||
|
|
@ -2078,6 +2078,8 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
// XP is updated via PLAYER_XP update fields from the server.
|
||||
if (areaDiscoveryCallback_)
|
||||
areaDiscoveryCallback_(areaName, xpGained);
|
||||
if (addonEventCallback_)
|
||||
addonEventCallback_("CHAT_MSG_COMBAT_XP_GAIN", {msg, std::to_string(xpGained)});
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
|
@ -2211,6 +2213,8 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
uint32_t value = packet.readUInt32();
|
||||
worldStates_[field] = value;
|
||||
LOG_DEBUG("SMSG_UPDATE_WORLD_STATE: field=", field, " value=", value);
|
||||
if (addonEventCallback_)
|
||||
addonEventCallback_("UPDATE_WORLD_STATES", {});
|
||||
break;
|
||||
}
|
||||
case Opcode::SMSG_WORLD_STATE_UI_TIMER_UPDATE: {
|
||||
|
|
@ -2302,6 +2306,8 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
uint8_t paused = packet.readUInt8();
|
||||
if (type < 3) {
|
||||
mirrorTimers_[type].paused = (paused != 0);
|
||||
if (addonEventCallback_)
|
||||
addonEventCallback_("MIRROR_TIMER_PAUSE", {paused ? "1" : "0"});
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
@ -2772,7 +2778,27 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
// Sent by server when player sits in barber chair — triggers barber shop UI
|
||||
LOG_INFO("SMSG_ENABLE_BARBER_SHOP: barber shop available");
|
||||
barberShopOpen_ = true;
|
||||
if (addonEventCallback_) addonEventCallback_("BARBER_SHOP_OPEN", {});
|
||||
break;
|
||||
case Opcode::MSG_CORPSE_QUERY: {
|
||||
// Server response: uint8 found + (if found) uint32 mapId + float x + float y + float z + uint32 corpseMapId
|
||||
if (packet.getSize() - packet.getReadPos() < 1) break;
|
||||
uint8_t found = packet.readUInt8();
|
||||
if (found && packet.getSize() - packet.getReadPos() >= 20) {
|
||||
/*uint32_t mapId =*/ packet.readUInt32();
|
||||
float cx = packet.readFloat();
|
||||
float cy = packet.readFloat();
|
||||
float cz = packet.readFloat();
|
||||
uint32_t corpseMapId = packet.readUInt32();
|
||||
// Server coords: x=west, y=north (opposite of canonical)
|
||||
corpseX_ = cx;
|
||||
corpseY_ = cy;
|
||||
corpseZ_ = cz;
|
||||
corpseMapId_ = corpseMapId;
|
||||
LOG_INFO("MSG_CORPSE_QUERY: corpse at (", cx, ",", cy, ",", cz, ") map=", corpseMapId);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Opcode::SMSG_FEIGN_DEATH_RESISTED:
|
||||
addUIError("Your Feign Death was resisted.");
|
||||
addSystemChatMessage("Your Feign Death attempt was resisted.");
|
||||
|
|
@ -2845,6 +2871,7 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
// All threat dropped on the local player (e.g. Vanish, Feign Death)
|
||||
threatLists_.clear();
|
||||
LOG_DEBUG("SMSG_THREAT_CLEAR: threat wiped");
|
||||
if (addonEventCallback_) addonEventCallback_("UNIT_THREAT_LIST_UPDATE", {});
|
||||
break;
|
||||
case Opcode::SMSG_THREAT_REMOVE: {
|
||||
// packed_guid (unit) + packed_guid (victim whose threat was removed)
|
||||
|
|
@ -2888,6 +2915,8 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
std::sort(list.begin(), list.end(),
|
||||
[](const ThreatEntry& a, const ThreatEntry& b){ return a.threat > b.threat; });
|
||||
threatLists_[unitGuid] = std::move(list);
|
||||
if (addonEventCallback_)
|
||||
addonEventCallback_("UNIT_THREAT_LIST_UPDATE", {});
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -4119,6 +4148,10 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
if (auto* sfx = renderer->getUiSoundManager())
|
||||
sfx->playQuestActivate();
|
||||
}
|
||||
if (addonEventCallback_) {
|
||||
addonEventCallback_("TRAINER_UPDATE", {});
|
||||
addonEventCallback_("SPELLS_CHANGED", {});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Opcode::SMSG_TRAINER_BUY_FAILED: {
|
||||
|
|
@ -4258,8 +4291,10 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
addSystemChatMessage(buf);
|
||||
watchedFactionId_ = factionId;
|
||||
if (repChangeCallback_) repChangeCallback_(name, delta, standing);
|
||||
if (addonEventCallback_)
|
||||
if (addonEventCallback_) {
|
||||
addonEventCallback_("UPDATE_FACTION", {});
|
||||
addonEventCallback_("CHAT_MSG_COMBAT_FACTION_CHANGE", {std::string(buf)});
|
||||
}
|
||||
}
|
||||
LOG_DEBUG("SMSG_SET_FACTION_STANDING: faction=", factionId, " standing=", standing);
|
||||
}
|
||||
|
|
@ -5125,6 +5160,7 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
if (result == 0) {
|
||||
addSystemChatMessage("Hairstyle changed.");
|
||||
barberShopOpen_ = false;
|
||||
if (addonEventCallback_) addonEventCallback_("BARBER_SHOP_CLOSE", {});
|
||||
} else {
|
||||
const char* msg = (result == 1) ? "Not enough money for new hairstyle."
|
||||
: (result == 2) ? "You are not at a barber shop."
|
||||
|
|
@ -5407,11 +5443,14 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
}
|
||||
questLog_.erase(it);
|
||||
LOG_INFO(" Removed quest ", questId, " from quest log");
|
||||
if (addonEventCallback_)
|
||||
addonEventCallback_("QUEST_TURNED_IN", {std::to_string(questId)});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (addonEventCallback_) addonEventCallback_("QUEST_LOG_UPDATE", {});
|
||||
if (addonEventCallback_)
|
||||
addonEventCallback_("QUEST_LOG_UPDATE", {});
|
||||
// Re-query all nearby quest giver NPCs so markers refresh
|
||||
if (socket) {
|
||||
for (const auto& [guid, entity] : entityManager.getEntities()) {
|
||||
|
|
@ -11900,11 +11939,42 @@ void GameHandler::applyUpdateObjectBlock(const UpdateBlock& block, bool& newItem
|
|||
} else if (key == ufMaxHealth) { unit->setMaxHealth(val); }
|
||||
else if (key == ufLevel) {
|
||||
unit->setLevel(val);
|
||||
} else if (key == ufFaction) { unit->setFactionTemplate(val); }
|
||||
else if (key == ufFlags) { unit->setUnitFlags(val); }
|
||||
} else if (key == ufFaction) {
|
||||
unit->setFactionTemplate(val);
|
||||
if (addonEventCallback_) {
|
||||
std::string uid;
|
||||
if (block.guid == playerGuid) uid = "player";
|
||||
else if (block.guid == targetGuid) uid = "target";
|
||||
else if (block.guid == focusGuid) uid = "focus";
|
||||
if (!uid.empty())
|
||||
addonEventCallback_("UNIT_FACTION", {uid});
|
||||
}
|
||||
}
|
||||
else if (key == ufFlags) {
|
||||
unit->setUnitFlags(val);
|
||||
if (addonEventCallback_) {
|
||||
std::string uid;
|
||||
if (block.guid == playerGuid) uid = "player";
|
||||
else if (block.guid == targetGuid) uid = "target";
|
||||
else if (block.guid == focusGuid) uid = "focus";
|
||||
if (!uid.empty())
|
||||
addonEventCallback_("UNIT_FLAGS", {uid});
|
||||
}
|
||||
}
|
||||
else if (key == ufBytes0) {
|
||||
unit->setPowerType(static_cast<uint8_t>((val >> 24) & 0xFF));
|
||||
} else if (key == ufDisplayId) { unit->setDisplayId(val); }
|
||||
} else if (key == ufDisplayId) {
|
||||
unit->setDisplayId(val);
|
||||
if (addonEventCallback_) {
|
||||
std::string uid;
|
||||
if (block.guid == playerGuid) uid = "player";
|
||||
else if (block.guid == targetGuid) uid = "target";
|
||||
else if (block.guid == focusGuid) uid = "focus";
|
||||
else if (block.guid == petGuid_) uid = "pet";
|
||||
if (!uid.empty())
|
||||
addonEventCallback_("UNIT_MODEL_CHANGED", {uid});
|
||||
}
|
||||
}
|
||||
else if (key == ufNpcFlags) { unit->setNpcFlags(val); }
|
||||
else if (key == ufDynFlags) {
|
||||
unit->setDynamicFlags(val);
|
||||
|
|
@ -11924,6 +11994,8 @@ void GameHandler::applyUpdateObjectBlock(const UpdateBlock& block, bool& newItem
|
|||
uint32_t old = currentMountDisplayId_;
|
||||
currentMountDisplayId_ = val;
|
||||
if (val != old && mountCallback_) mountCallback_(val);
|
||||
if (val != old && addonEventCallback_)
|
||||
addonEventCallback_("UNIT_MODEL_CHANGED", {"player"});
|
||||
if (old == 0 && val != 0) {
|
||||
// Just mounted — find the mount aura (indefinite duration, self-cast)
|
||||
mountAuraSpellId_ = 0;
|
||||
|
|
@ -11978,6 +12050,11 @@ void GameHandler::applyUpdateObjectBlock(const UpdateBlock& block, bool& newItem
|
|||
playerDead_ = true;
|
||||
LOG_INFO("Player logged in as ghost (PLAYER_FLAGS)");
|
||||
if (ghostStateCallback_) ghostStateCallback_(true);
|
||||
// Query corpse position so minimap marker is accurate on reconnect
|
||||
if (socket) {
|
||||
network::Packet cq(wireOpcode(Opcode::MSG_CORPSE_QUERY));
|
||||
socket->send(cq);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Classic: rebuild playerAuras from UNIT_FIELD_AURAS on initial object create
|
||||
|
|
@ -14095,6 +14172,7 @@ void GameHandler::followTarget() {
|
|||
|
||||
addSystemChatMessage("Now following " + targetName + ".");
|
||||
LOG_INFO("Following target: ", targetName, " (GUID: 0x", std::hex, targetGuid, std::dec, ")");
|
||||
if (addonEventCallback_) addonEventCallback_("AUTOFOLLOW_BEGIN", {});
|
||||
}
|
||||
|
||||
void GameHandler::cancelFollow() {
|
||||
|
|
@ -14104,6 +14182,7 @@ void GameHandler::cancelFollow() {
|
|||
}
|
||||
followTargetGuid_ = 0;
|
||||
addSystemChatMessage("You stop following.");
|
||||
if (addonEventCallback_) addonEventCallback_("AUTOFOLLOW_END", {});
|
||||
}
|
||||
|
||||
void GameHandler::assistTarget() {
|
||||
|
|
@ -14673,6 +14752,9 @@ void GameHandler::releaseSpirit() {
|
|||
repopPending_ = true;
|
||||
lastRepopRequestMs_ = static_cast<uint64_t>(now);
|
||||
LOG_INFO("Sent CMSG_REPOP_REQUEST (Release Spirit)");
|
||||
// Query server for authoritative corpse position (response updates corpseX_/Y_/Z_)
|
||||
network::Packet cq(wireOpcode(Opcode::MSG_CORPSE_QUERY));
|
||||
socket->send(cq);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -24592,6 +24674,7 @@ void GameHandler::handleLogoutResponse(network::Packet& packet) {
|
|||
logoutCountdown_ = 20.0f;
|
||||
}
|
||||
LOG_INFO("Logout response: success, instant=", (int)data.instant);
|
||||
if (addonEventCallback_) addonEventCallback_("PLAYER_LOGOUT", {});
|
||||
} else {
|
||||
// Failure
|
||||
addSystemChatMessage("Cannot logout right now.");
|
||||
|
|
|
|||
|
|
@ -4237,6 +4237,12 @@ void GameScreen::renderTargetFrame(game::GameHandler& gameHandler) {
|
|||
if (u->getHealth() == 0 && u->getMaxHealth() > 0) {
|
||||
hostileColor = ImVec4(0.5f, 0.5f, 0.5f, 1.0f);
|
||||
} else if (u->isHostile()) {
|
||||
// Check tapped-by-other: grey name for mobs tagged by someone else
|
||||
uint32_t tgtDynFlags = u->getDynamicFlags();
|
||||
bool tgtTapped = (tgtDynFlags & 0x0004) != 0 && (tgtDynFlags & 0x0008) == 0;
|
||||
if (tgtTapped) {
|
||||
hostileColor = ImVec4(0.6f, 0.6f, 0.6f, 1.0f); // Grey — tapped by other
|
||||
} else {
|
||||
// WoW level-based color for hostile mobs
|
||||
uint32_t playerLv = gameHandler.getPlayerLevel();
|
||||
uint32_t mobLv = u->getLevel();
|
||||
|
|
@ -4257,6 +4263,7 @@ void GameScreen::renderTargetFrame(game::GameHandler& gameHandler) {
|
|||
hostileColor = ImVec4(0.3f, 1.0f, 0.3f, 1.0f); // Green - easy
|
||||
}
|
||||
}
|
||||
} // end tapped else
|
||||
} else {
|
||||
hostileColor = ImVec4(0.3f, 1.0f, 0.3f, 1.0f); // Friendly
|
||||
}
|
||||
|
|
@ -5181,6 +5188,12 @@ void GameScreen::renderFocusFrame(game::GameHandler& gameHandler) {
|
|||
if (u->getHealth() == 0 && u->getMaxHealth() > 0) {
|
||||
focusColor = ImVec4(0.5f, 0.5f, 0.5f, 1.0f);
|
||||
} else if (u->isHostile()) {
|
||||
// Tapped-by-other: grey focus frame name
|
||||
uint32_t focDynFlags = u->getDynamicFlags();
|
||||
bool focTapped = (focDynFlags & 0x0004) != 0 && (focDynFlags & 0x0008) == 0;
|
||||
if (focTapped) {
|
||||
focusColor = ImVec4(0.6f, 0.6f, 0.6f, 1.0f);
|
||||
} else {
|
||||
uint32_t playerLv = gameHandler.getPlayerLevel();
|
||||
uint32_t mobLv = u->getLevel();
|
||||
if (mobLv == 0) {
|
||||
|
|
@ -5198,6 +5211,7 @@ void GameScreen::renderFocusFrame(game::GameHandler& gameHandler) {
|
|||
else
|
||||
focusColor = ImVec4(0.3f, 1.0f, 0.3f, 1.0f);
|
||||
}
|
||||
} // end tapped else
|
||||
} else {
|
||||
focusColor = ImVec4(0.3f, 1.0f, 0.3f, 1.0f);
|
||||
}
|
||||
|
|
@ -11695,8 +11709,16 @@ void GameScreen::renderNameplates(game::GameHandler& gameHandler) {
|
|||
barColor = IM_COL32(140, 140, 140, A(200));
|
||||
bgColor = IM_COL32(70, 70, 70, A(160));
|
||||
} else if (unit->isHostile()) {
|
||||
// Check if mob is tapped by another player (grey nameplate)
|
||||
uint32_t dynFlags = unit->getDynamicFlags();
|
||||
bool tappedByOther = (dynFlags & 0x0004) != 0 && (dynFlags & 0x0008) == 0; // TAPPED but not TAPPED_BY_ALL_THREAT_LIST
|
||||
if (tappedByOther) {
|
||||
barColor = IM_COL32(160, 160, 160, A(200));
|
||||
bgColor = IM_COL32(80, 80, 80, A(160));
|
||||
} else {
|
||||
barColor = IM_COL32(220, 60, 60, A(200));
|
||||
bgColor = IM_COL32(100, 25, 25, A(160));
|
||||
}
|
||||
} else if (isPlayer) {
|
||||
// Player nameplates: use class color for easy identification
|
||||
uint8_t cid = entityClassId(unit);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue