Fix Turtle WoW compatibility: NPC spawning, quests, spells, realm display, and music

- Add TurtlePacketParsers with dedicated movement block parser (Classic format + transport timestamp)
- Fix quest giver status: read uint32 and translate vanilla enum values for Classic/Turtle
- Fix quest accept packet: remove trailing uint32 that vanilla servers reject
- Fix quest details parser: auto-detect vanilla vs WotLK format (informUnit field)
- Fix spellbook and action bar icons: fallback to WotLK DBC field indices when expansion layout fails
- Fix spell cast failure messages: translate vanilla SpellCastResult codes (+1 offset)
- Fix realm list: correct type values (6=RP, 8=RP-PvP) and population thresholds
- Fix music: disable looping for zone music, auto-advance to next random track when finished
- Add music anti-repeat: avoid playing the same track back-to-back
- Make TBC update block parsing resilient (keep parsed blocks on failure instead of aborting)
- Add right-click attack on hostile mobs
- Add name query diagnostic logging
This commit is contained in:
Kelsi 2026-02-17 05:27:03 -08:00
parent d850fe6fc0
commit 36fc1df706
12 changed files with 358 additions and 48 deletions

View file

@ -1328,6 +1328,8 @@ void GameScreen::processTargetInput(game::GameHandler& gameHandler) {
bool allowSpiritInteract = (gameHandler.isPlayerDead() || gameHandler.isPlayerGhost()) && isSpiritNpc();
if (!unit->isHostile() && (unit->isInteractable() || allowSpiritInteract)) {
gameHandler.interactWithNpc(target->getGuid());
} else if (unit->isHostile()) {
gameHandler.startAutoAttack(target->getGuid());
}
}
} else if (target->getType() == game::ObjectType::GAMEOBJECT) {
@ -2967,16 +2969,29 @@ GLuint GameScreen::getSpellIcon(uint32_t spellId, pipeline::AssetManager* am) {
}
}
// Load Spell.dbc: field 133 = SpellIconID
// Load Spell.dbc: SpellIconID field
auto spellDbc = am->loadDBC("Spell.dbc");
const auto* spellL = pipeline::getActiveDBCLayout() ? pipeline::getActiveDBCLayout()->getLayout("Spell") : nullptr;
if (spellDbc && spellDbc->isLoaded() && spellDbc->getFieldCount() > 133) {
for (uint32_t i = 0; i < spellDbc->getRecordCount(); i++) {
uint32_t id = spellDbc->getUInt32(i, spellL ? (*spellL)["ID"] : 0);
uint32_t iconId = spellDbc->getUInt32(i, spellL ? (*spellL)["IconID"] : 133);
if (id > 0 && iconId > 0) {
spellIconIds_[id] = iconId;
if (spellDbc && spellDbc->isLoaded()) {
uint32_t fieldCount = spellDbc->getFieldCount();
// Try expansion layout first
auto tryLoadIcons = [&](uint32_t idField, uint32_t iconField) {
spellIconIds_.clear();
if (iconField >= fieldCount) return;
for (uint32_t i = 0; i < spellDbc->getRecordCount(); i++) {
uint32_t id = spellDbc->getUInt32(i, idField);
uint32_t iconId = spellDbc->getUInt32(i, iconField);
if (id > 0 && iconId > 0) {
spellIconIds_[id] = iconId;
}
}
};
if (spellL) {
tryLoadIcons((*spellL)["ID"], (*spellL)["IconID"]);
}
// Fallback to WotLK field 133 if expansion layout yielded nothing
if (spellIconIds_.empty() && fieldCount > 133) {
tryLoadIcons(0, 133);
}
}
}

View file

@ -127,8 +127,8 @@ void RealmScreen::render(auth::AuthHandler& authHandler) {
ImGui::TableSetColumnIndex(1);
if (realm.icon == 0) ImGui::Text("Normal");
else if (realm.icon == 1) ImGui::Text("PvP");
else if (realm.icon == 4) ImGui::Text("RP");
else if (realm.icon == 6) ImGui::Text("RP-PvP");
else if (realm.icon == 6) ImGui::Text("RP");
else if (realm.icon == 8) ImGui::Text("RP-PvP");
else ImGui::Text("Type %d", realm.icon);
// Population column
@ -136,8 +136,8 @@ void RealmScreen::render(auth::AuthHandler& authHandler) {
ImVec4 popColor = getPopulationColor(realm.population);
ImGui::PushStyleColor(ImGuiCol_Text, popColor);
if (realm.population < 0.5f) ImGui::Text("Low");
else if (realm.population < 1.0f) ImGui::Text("Medium");
else if (realm.population < 2.0f) ImGui::Text("High");
else if (realm.population < 1.5f) ImGui::Text("Medium");
else if (realm.population < 2.5f) ImGui::Text("High");
else ImGui::Text("Full");
ImGui::PopStyleColor();
@ -238,9 +238,9 @@ const char* RealmScreen::getRealmStatus(uint8_t flags) const {
ImVec4 RealmScreen::getPopulationColor(float population) const {
if (population < 0.5f) {
return ImVec4(0.3f, 1.0f, 0.3f, 1.0f); // Green - Low
} else if (population < 1.0f) {
} else if (population < 1.5f) {
return ImVec4(1.0f, 1.0f, 0.3f, 1.0f); // Yellow - Medium
} else if (population < 2.0f) {
} else if (population < 2.5f) {
return ImVec4(1.0f, 0.6f, 0.0f, 1.0f); // Orange - High
} else {
return ImVec4(1.0f, 0.3f, 0.3f, 1.0f); // Red - Full

View file

@ -29,28 +29,46 @@ void SpellbookScreen::loadSpellDBC(pipeline::AssetManager* assetManager) {
return;
}
// WoW 3.3.5a Spell.dbc fields (0-based):
// 0 = SpellID, 4 = Attributes, 133 = SpellIconID, 136 = SpellName_enUS, 153 = RankText_enUS
// Try expansion-specific layout first, then fall back to WotLK hardcoded indices
// if the DBC is from WotLK MPQs but the active expansion uses different field offsets.
const auto* spellL = pipeline::getActiveDBCLayout() ? pipeline::getActiveDBCLayout()->getLayout("Spell") : nullptr;
uint32_t count = dbc->getRecordCount();
for (uint32_t i = 0; i < count; ++i) {
uint32_t spellId = dbc->getUInt32(i, spellL ? (*spellL)["ID"] : 0);
if (spellId == 0) continue;
SpellInfo info;
info.spellId = spellId;
info.attributes = dbc->getUInt32(i, spellL ? (*spellL)["Attributes"] : 4);
info.iconId = dbc->getUInt32(i, spellL ? (*spellL)["IconID"] : 133);
info.name = dbc->getString(i, spellL ? (*spellL)["Name"] : 136);
info.rank = dbc->getString(i, spellL ? (*spellL)["Rank"] : 153);
auto tryLoad = [&](uint32_t idField, uint32_t attrField, uint32_t iconField,
uint32_t nameField, uint32_t rankField, const char* label) {
spellData.clear();
uint32_t count = dbc->getRecordCount();
for (uint32_t i = 0; i < count; ++i) {
uint32_t spellId = dbc->getUInt32(i, idField);
if (spellId == 0) continue;
if (!info.name.empty()) {
spellData[spellId] = std::move(info);
SpellInfo info;
info.spellId = spellId;
info.attributes = dbc->getUInt32(i, attrField);
info.iconId = dbc->getUInt32(i, iconField);
info.name = dbc->getString(i, nameField);
info.rank = dbc->getString(i, rankField);
if (!info.name.empty()) {
spellData[spellId] = std::move(info);
}
}
LOG_INFO("Spellbook: Loaded ", spellData.size(), " spells from Spell.dbc (", label, ")");
};
// Try active expansion layout
if (spellL) {
tryLoad((*spellL)["ID"], (*spellL)["Attributes"], (*spellL)["IconID"],
(*spellL)["Name"], (*spellL)["Rank"], "expansion layout");
}
dbcLoaded = true;
LOG_INFO("Spellbook: Loaded ", spellData.size(), " spells from Spell.dbc");
// If layout failed or loaded 0 spells, try WotLK hardcoded indices
// (binary DBC may be from WotLK MPQs regardless of active expansion)
if (spellData.empty() && fieldCount >= 200) {
LOG_INFO("Spellbook: Retrying with WotLK field indices (DBC has ", fieldCount, " fields)");
tryLoad(0, 4, 133, 136, 153, "WotLK fallback");
}
dbcLoaded = !spellData.empty();
}
void SpellbookScreen::loadSpellIconDBC(pipeline::AssetManager* assetManager) {