mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
Track player skills from update fields and display in character screen
Extract skill data from PLAYER_SKILL_INFO_1_1 update fields (636-1019), detect skill increases with chat messages, and replace placeholder Skills tab with live data grouped by category with progress bars.
This commit is contained in:
parent
cc6fa12157
commit
5bfe4b61aa
3 changed files with 175 additions and 24 deletions
|
|
@ -14,12 +14,19 @@
|
|||
#include <cstdint>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <map>
|
||||
|
||||
namespace wowee {
|
||||
namespace network { class WorldSocket; class Packet; }
|
||||
|
||||
namespace game {
|
||||
|
||||
struct PlayerSkill {
|
||||
uint32_t skillId = 0;
|
||||
uint16_t value = 0;
|
||||
uint16_t maxValue = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Quest giver status values (WoW 3.3.5a)
|
||||
*/
|
||||
|
|
@ -343,6 +350,11 @@ public:
|
|||
uint32_t getPlayerLevel() const { return serverPlayerLevel_; }
|
||||
static uint32_t killXp(uint32_t playerLevel, uint32_t victimLevel);
|
||||
|
||||
// Player skills
|
||||
const std::map<uint32_t, PlayerSkill>& getPlayerSkills() const { return playerSkills_; }
|
||||
const std::string& getSkillName(uint32_t skillId) const;
|
||||
uint32_t getSkillCategory(uint32_t skillId) const;
|
||||
|
||||
// World entry callback (online mode - triggered when entering world)
|
||||
// Parameters: mapId, x, y, z (canonical WoW coordinates)
|
||||
using WorldEntryCallback = std::function<void(uint32_t mapId, float x, float y, float z)>;
|
||||
|
|
@ -804,6 +816,14 @@ private:
|
|||
uint32_t serverPlayerLevel_ = 1;
|
||||
static uint32_t xpForLevel(uint32_t level);
|
||||
|
||||
// ---- Player skills ----
|
||||
std::map<uint32_t, PlayerSkill> playerSkills_;
|
||||
std::unordered_map<uint32_t, std::string> skillLineNames_;
|
||||
std::unordered_map<uint32_t, uint32_t> skillLineCategories_;
|
||||
bool skillLineDbcLoaded_ = false;
|
||||
void loadSkillLineDbc();
|
||||
void extractSkillFields(const std::map<uint16_t, uint32_t>& fields);
|
||||
|
||||
NpcDeathCallback npcDeathCallback_;
|
||||
NpcRespawnCallback npcRespawnCallback_;
|
||||
MeleeSwingCallback meleeSwingCallback_;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@
|
|||
#include "network/world_socket.hpp"
|
||||
#include "network/packet.hpp"
|
||||
#include "core/coordinates.hpp"
|
||||
#include "core/application.hpp"
|
||||
#include "pipeline/asset_manager.hpp"
|
||||
#include "pipeline/dbc_loader.hpp"
|
||||
#include "core/logger.hpp"
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
|
@ -1251,7 +1254,7 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
|
|||
}
|
||||
}
|
||||
|
||||
// Extract XP / inventory slot fields for player entity
|
||||
// Extract XP / inventory slot / skill fields for player entity
|
||||
if (block.guid == playerGuid && block.objectType == ObjectType::PLAYER) {
|
||||
lastPlayerFields_ = block.fields;
|
||||
detectInventorySlotBases(block.fields);
|
||||
|
|
@ -1269,6 +1272,7 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
|
|||
}
|
||||
if (applyInventoryFields(block.fields)) slotsChanged = true;
|
||||
if (slotsChanged) rebuildOnlineInventory();
|
||||
extractSkillFields(lastPlayerFields_);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
@ -1331,7 +1335,7 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
|
|||
}
|
||||
}
|
||||
}
|
||||
// Update XP / inventory slot fields for player entity
|
||||
// Update XP / inventory slot / skill fields for player entity
|
||||
if (block.guid == playerGuid) {
|
||||
for (const auto& [key, val] : block.fields) {
|
||||
lastPlayerFields_[key] = val;
|
||||
|
|
@ -1365,6 +1369,7 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
|
|||
}
|
||||
if (applyInventoryFields(block.fields)) slotsChanged = true;
|
||||
if (slotsChanged) rebuildOnlineInventory();
|
||||
extractSkillFields(lastPlayerFields_);
|
||||
}
|
||||
|
||||
// Update item stack count for online items
|
||||
|
|
@ -3967,5 +3972,93 @@ void GameHandler::fail(const std::string& reason) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// Player Skills
|
||||
// ============================================================
|
||||
|
||||
static const std::string kEmptySkillName;
|
||||
|
||||
const std::string& GameHandler::getSkillName(uint32_t skillId) const {
|
||||
auto it = skillLineNames_.find(skillId);
|
||||
return (it != skillLineNames_.end()) ? it->second : kEmptySkillName;
|
||||
}
|
||||
|
||||
uint32_t GameHandler::getSkillCategory(uint32_t skillId) const {
|
||||
auto it = skillLineCategories_.find(skillId);
|
||||
return (it != skillLineCategories_.end()) ? it->second : 0;
|
||||
}
|
||||
|
||||
void GameHandler::loadSkillLineDbc() {
|
||||
if (skillLineDbcLoaded_) return;
|
||||
skillLineDbcLoaded_ = true;
|
||||
|
||||
auto* am = core::Application::getInstance().getAssetManager();
|
||||
if (!am || !am->isInitialized()) return;
|
||||
|
||||
auto dbc = am->loadDBC("SkillLine.dbc");
|
||||
if (!dbc || !dbc->isLoaded()) {
|
||||
LOG_WARNING("GameHandler: Could not load SkillLine.dbc");
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < dbc->getRecordCount(); i++) {
|
||||
uint32_t id = dbc->getUInt32(i, 0);
|
||||
uint32_t category = dbc->getUInt32(i, 1);
|
||||
std::string name = dbc->getString(i, 3);
|
||||
if (id > 0 && !name.empty()) {
|
||||
skillLineNames_[id] = name;
|
||||
skillLineCategories_[id] = category;
|
||||
}
|
||||
}
|
||||
LOG_INFO("GameHandler: Loaded ", skillLineNames_.size(), " skill line names");
|
||||
}
|
||||
|
||||
void GameHandler::extractSkillFields(const std::map<uint16_t, uint32_t>& fields) {
|
||||
loadSkillLineDbc();
|
||||
|
||||
// PLAYER_SKILL_INFO_1_1 = field 636, 128 slots x 3 fields each (636..1019)
|
||||
static constexpr uint16_t PLAYER_SKILL_INFO_START = 636;
|
||||
static constexpr int MAX_SKILL_SLOTS = 128;
|
||||
|
||||
std::map<uint32_t, PlayerSkill> newSkills;
|
||||
|
||||
for (int slot = 0; slot < MAX_SKILL_SLOTS; slot++) {
|
||||
uint16_t baseField = PLAYER_SKILL_INFO_START + slot * 3;
|
||||
|
||||
auto idIt = fields.find(baseField);
|
||||
if (idIt == fields.end()) continue;
|
||||
|
||||
uint32_t raw0 = idIt->second;
|
||||
uint16_t skillId = raw0 & 0xFFFF;
|
||||
if (skillId == 0) continue;
|
||||
|
||||
auto valIt = fields.find(baseField + 1);
|
||||
if (valIt == fields.end()) continue;
|
||||
|
||||
uint32_t raw1 = valIt->second;
|
||||
uint16_t value = raw1 & 0xFFFF;
|
||||
uint16_t maxValue = (raw1 >> 16) & 0xFFFF;
|
||||
|
||||
PlayerSkill skill;
|
||||
skill.skillId = skillId;
|
||||
skill.value = value;
|
||||
skill.maxValue = maxValue;
|
||||
newSkills[skillId] = skill;
|
||||
}
|
||||
|
||||
// Detect increases and emit chat messages
|
||||
for (const auto& [skillId, skill] : newSkills) {
|
||||
if (skill.value == 0) continue;
|
||||
auto oldIt = playerSkills_.find(skillId);
|
||||
if (oldIt != playerSkills_.end() && skill.value > oldIt->second.value) {
|
||||
const std::string& name = getSkillName(skillId);
|
||||
std::string skillName = name.empty() ? ("Skill #" + std::to_string(skillId)) : name;
|
||||
addSystemChatMessage("Your skill in " + skillName + " has increased to " + std::to_string(skill.value) + ".");
|
||||
}
|
||||
}
|
||||
|
||||
playerSkills_ = std::move(newSkills);
|
||||
}
|
||||
|
||||
} // namespace game
|
||||
} // namespace wowee
|
||||
|
|
|
|||
|
|
@ -704,32 +704,70 @@ void InventoryScreen::renderCharacterScreen(game::GameHandler& gameHandler) {
|
|||
}
|
||||
|
||||
if (ImGui::BeginTabItem("Skills")) {
|
||||
uint32_t level = gameHandler.getPlayerLevel();
|
||||
uint32_t cap = (level > 0) ? (level * 5) : 0;
|
||||
ImGui::TextDisabled("Skills (online sync pending)");
|
||||
ImGui::Spacing();
|
||||
if (ImGui::BeginTable("SkillsTable", 2, ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersInnerH)) {
|
||||
ImGui::TableSetupColumn("Skill", ImGuiTableColumnFlags_WidthStretch);
|
||||
ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthFixed, 120.0f);
|
||||
ImGui::TableHeadersRow();
|
||||
|
||||
const char* skills[] = {
|
||||
"Unarmed", "Swords", "Axes", "Maces", "Daggers",
|
||||
"Staves", "Polearms", "Bows", "Guns", "Crossbows"
|
||||
const auto& skills = gameHandler.getPlayerSkills();
|
||||
if (skills.empty()) {
|
||||
ImGui::TextDisabled("No skill data received yet.");
|
||||
} else {
|
||||
// Group skills by SkillLine.dbc category
|
||||
struct CategoryGroup {
|
||||
const char* label;
|
||||
uint32_t categoryId;
|
||||
};
|
||||
for (const char* skill : skills) {
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
ImGui::Text("%s", skill);
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
if (cap > 0) {
|
||||
ImGui::Text("-- / %u", cap);
|
||||
} else {
|
||||
ImGui::TextDisabled("--");
|
||||
static const CategoryGroup groups[] = {
|
||||
{ "Weapon Skills", 6 },
|
||||
{ "Armor Skills", 8 },
|
||||
{ "Secondary Skills", 10 },
|
||||
{ "Professions", 11 },
|
||||
{ "Languages", 9 },
|
||||
{ "Other", 0 },
|
||||
};
|
||||
|
||||
ImGui::BeginChild("##SkillsList", ImVec2(0, 0), true);
|
||||
|
||||
for (const auto& group : groups) {
|
||||
// Collect skills for this category
|
||||
std::vector<const game::PlayerSkill*> groupSkills;
|
||||
for (const auto& [id, skill] : skills) {
|
||||
if (skill.value == 0 && skill.maxValue == 0) continue;
|
||||
uint32_t cat = gameHandler.getSkillCategory(id);
|
||||
if (group.categoryId == 0) {
|
||||
// "Other" catches everything not in the named categories
|
||||
if (cat != 6 && cat != 8 && cat != 9 && cat != 10 && cat != 11) {
|
||||
groupSkills.push_back(&skill);
|
||||
}
|
||||
} else if (cat == group.categoryId) {
|
||||
groupSkills.push_back(&skill);
|
||||
}
|
||||
}
|
||||
if (groupSkills.empty()) continue;
|
||||
|
||||
if (ImGui::CollapsingHeader(group.label, ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
for (const game::PlayerSkill* skill : groupSkills) {
|
||||
const std::string& name = gameHandler.getSkillName(skill->skillId);
|
||||
char label[128];
|
||||
if (name.empty()) {
|
||||
snprintf(label, sizeof(label), "Skill #%u", skill->skillId);
|
||||
} else {
|
||||
snprintf(label, sizeof(label), "%s", name.c_str());
|
||||
}
|
||||
|
||||
// Show progress bar with value/max overlay
|
||||
float ratio = (skill->maxValue > 0)
|
||||
? static_cast<float>(skill->value) / static_cast<float>(skill->maxValue)
|
||||
: 0.0f;
|
||||
|
||||
char overlay[64];
|
||||
snprintf(overlay, sizeof(overlay), "%u / %u", skill->value, skill->maxValue);
|
||||
|
||||
ImGui::Text("%s", label);
|
||||
ImGui::SameLine(180.0f);
|
||||
ImGui::SetNextItemWidth(-1.0f);
|
||||
ImGui::ProgressBar(ratio, ImVec2(0, 14.0f), overlay);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::EndTable();
|
||||
ImGui::EndChild();
|
||||
}
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue