mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-04-17 17:43:52 +00:00
Add multi-expansion support with data-driven protocol layer
Replace hardcoded WotLK protocol constants with a data-driven architecture supporting Classic 1.12.1, TBC 2.4.3, and WotLK 3.3.5a. Each expansion has JSON profiles for opcodes, update fields, and DBC layouts, plus C++ polymorphic packet parsers for binary format differences (movement flags, speed fields, transport data, spline format, char enum layout). Key components: - ExpansionRegistry: scans Data/expansions/*/expansion.json at startup - OpcodeTable: logical enum <-> wire values loaded from JSON - UpdateFieldTable: field indices loaded from JSON per expansion - DBCLayout: schema-driven DBC field lookups replacing magic numbers - PacketParsers: WotLK/TBC/Classic parsers with correct flag positions - Multi-manifest AssetManager: layered manifests with priority ordering - HDPackManager: overlay texture packs with expansion compatibility - Auth screen expansion picker replacing hardcoded version dropdown
This commit is contained in:
parent
aa16a687c2
commit
7092844b5e
51 changed files with 5258 additions and 887 deletions
|
|
@ -103,9 +103,77 @@ void AssetManager::shutdown() {
|
|||
}
|
||||
|
||||
clearCache();
|
||||
overlayLayers_.clear();
|
||||
initialized = false;
|
||||
}
|
||||
|
||||
bool AssetManager::addOverlayManifest(const std::string& manifestPath, int priority, const std::string& id) {
|
||||
// Check for duplicate
|
||||
for (const auto& layer : overlayLayers_) {
|
||||
if (layer.id == id) {
|
||||
LOG_WARNING("Overlay '", id, "' already loaded, skipping");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ManifestLayer layer;
|
||||
layer.priority = priority;
|
||||
layer.id = id;
|
||||
|
||||
if (!layer.manifest.load(manifestPath)) {
|
||||
LOG_ERROR("Failed to load overlay manifest: ", manifestPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
overlayLayers_.push_back(std::move(layer));
|
||||
|
||||
// Sort by priority descending (highest priority first)
|
||||
std::sort(overlayLayers_.begin(), overlayLayers_.end(),
|
||||
[](const ManifestLayer& a, const ManifestLayer& b) {
|
||||
return a.priority > b.priority;
|
||||
});
|
||||
|
||||
LOG_INFO("Added overlay '", id, "' (priority ", priority, ", ",
|
||||
overlayLayers_.back().manifest.getEntryCount(), " files) from ", manifestPath);
|
||||
return true;
|
||||
}
|
||||
|
||||
void AssetManager::removeOverlay(const std::string& id) {
|
||||
auto it = std::remove_if(overlayLayers_.begin(), overlayLayers_.end(),
|
||||
[&id](const ManifestLayer& layer) { return layer.id == id; });
|
||||
if (it != overlayLayers_.end()) {
|
||||
overlayLayers_.erase(it, overlayLayers_.end());
|
||||
// Clear file cache since overlay removal changes file resolution
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(cacheMutex);
|
||||
fileCache.clear();
|
||||
fileCacheTotalBytes = 0;
|
||||
}
|
||||
LOG_INFO("Removed overlay '", id, "', file cache cleared");
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> AssetManager::getOverlayIds() const {
|
||||
std::vector<std::string> ids;
|
||||
ids.reserve(overlayLayers_.size());
|
||||
for (const auto& layer : overlayLayers_) {
|
||||
ids.push_back(layer.id);
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
std::string AssetManager::resolveLayeredPath(const std::string& normalizedPath) const {
|
||||
// Check overlay manifests first (sorted by priority desc)
|
||||
for (const auto& layer : overlayLayers_) {
|
||||
std::string fsPath = layer.manifest.resolveFilesystemPath(normalizedPath);
|
||||
if (!fsPath.empty()) {
|
||||
return fsPath;
|
||||
}
|
||||
}
|
||||
// Fall back to base manifest
|
||||
return manifest_.resolveFilesystemPath(normalizedPath);
|
||||
}
|
||||
|
||||
BLPImage AssetManager::loadTexture(const std::string& path) {
|
||||
if (!initialized) {
|
||||
LOG_ERROR("AssetManager not initialized");
|
||||
|
|
@ -144,7 +212,7 @@ BLPImage AssetManager::tryLoadPngOverride(const std::string& normalizedPath) con
|
|||
std::string ext = normalizedPath.substr(normalizedPath.size() - 4);
|
||||
if (ext != ".blp") return BLPImage();
|
||||
|
||||
std::string fsPath = manifest_.resolveFilesystemPath(normalizedPath);
|
||||
std::string fsPath = resolveLayeredPath(normalizedPath);
|
||||
if (fsPath.empty()) return BLPImage();
|
||||
|
||||
// Replace .blp/.BLP extension with .png
|
||||
|
|
@ -219,7 +287,14 @@ bool AssetManager::fileExists(const std::string& path) const {
|
|||
if (!initialized) {
|
||||
return false;
|
||||
}
|
||||
return manifest_.hasEntry(normalizePath(path));
|
||||
std::string normalized = normalizePath(path);
|
||||
// Check overlay manifests first
|
||||
for (const auto& layer : overlayLayers_) {
|
||||
if (layer.manifest.hasEntry(normalized)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return manifest_.hasEntry(normalized);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> AssetManager::readFile(const std::string& path) const {
|
||||
|
|
@ -240,8 +315,8 @@ std::vector<uint8_t> AssetManager::readFile(const std::string& path) const {
|
|||
}
|
||||
}
|
||||
|
||||
// Read from filesystem (fully parallel, no serialization needed)
|
||||
std::string fsPath = manifest_.resolveFilesystemPath(normalized);
|
||||
// Read from filesystem using layered resolution (overlays first, then base)
|
||||
std::string fsPath = resolveLayeredPath(normalized);
|
||||
if (fsPath.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
|
|
|||
226
src/pipeline/dbc_layout.cpp
Normal file
226
src/pipeline/dbc_layout.cpp
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
#include "pipeline/dbc_layout.hpp"
|
||||
#include "core/logger.hpp"
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
namespace wowee {
|
||||
namespace pipeline {
|
||||
|
||||
static const DBCLayout* g_activeDBCLayout = nullptr;
|
||||
|
||||
void setActiveDBCLayout(const DBCLayout* layout) { g_activeDBCLayout = layout; }
|
||||
const DBCLayout* getActiveDBCLayout() { return g_activeDBCLayout; }
|
||||
|
||||
void DBCLayout::loadWotlkDefaults() {
|
||||
layouts_.clear();
|
||||
|
||||
// Spell.dbc
|
||||
layouts_["Spell"] = {{{ "ID", 0 }, { "Attributes", 4 }, { "IconID", 133 },
|
||||
{ "Name", 136 }, { "Tooltip", 139 }, { "Rank", 153 }}};
|
||||
|
||||
// ItemDisplayInfo.dbc
|
||||
layouts_["ItemDisplayInfo"] = {{{ "ID", 0 }, { "LeftModel", 1 }, { "LeftModelTexture", 3 },
|
||||
{ "InventoryIcon", 5 }, { "GeosetGroup1", 7 }, { "GeosetGroup3", 9 }}};
|
||||
|
||||
// CharSections.dbc
|
||||
layouts_["CharSections"] = {{{ "RaceID", 1 }, { "SexID", 2 }, { "BaseSection", 3 },
|
||||
{ "Texture1", 4 }, { "Texture2", 5 }, { "Texture3", 6 },
|
||||
{ "VariationIndex", 8 }, { "ColorIndex", 9 }}};
|
||||
|
||||
// SpellIcon.dbc (Icon.dbc in code but actually SpellIcon)
|
||||
layouts_["SpellIcon"] = {{{ "ID", 0 }, { "Path", 1 }}};
|
||||
|
||||
// FactionTemplate.dbc
|
||||
layouts_["FactionTemplate"] = {{{ "ID", 0 }, { "Faction", 1 }, { "FactionGroup", 3 },
|
||||
{ "FriendGroup", 4 }, { "EnemyGroup", 5 },
|
||||
{ "Enemy0", 6 }, { "Enemy1", 7 }, { "Enemy2", 8 }, { "Enemy3", 9 }}};
|
||||
|
||||
// Faction.dbc
|
||||
layouts_["Faction"] = {{{ "ID", 0 }, { "ReputationRaceMask0", 2 }, { "ReputationRaceMask1", 3 },
|
||||
{ "ReputationRaceMask2", 4 }, { "ReputationRaceMask3", 5 },
|
||||
{ "ReputationBase0", 10 }, { "ReputationBase1", 11 },
|
||||
{ "ReputationBase2", 12 }, { "ReputationBase3", 13 }}};
|
||||
|
||||
// AreaTable.dbc
|
||||
layouts_["AreaTable"] = {{{ "ID", 0 }, { "ExploreFlag", 3 }}};
|
||||
|
||||
// CreatureDisplayInfoExtra.dbc
|
||||
layouts_["CreatureDisplayInfoExtra"] = {{{ "ID", 0 }, { "RaceID", 1 }, { "SexID", 2 },
|
||||
{ "SkinID", 3 }, { "FaceID", 4 }, { "HairStyleID", 5 }, { "HairColorID", 6 },
|
||||
{ "FacialHairID", 7 }, { "EquipDisplay0", 8 }, { "EquipDisplay1", 9 },
|
||||
{ "EquipDisplay2", 10 }, { "EquipDisplay3", 11 }, { "EquipDisplay4", 12 },
|
||||
{ "EquipDisplay5", 13 }, { "EquipDisplay6", 14 }, { "EquipDisplay7", 15 },
|
||||
{ "EquipDisplay8", 16 }, { "EquipDisplay9", 17 }, { "EquipDisplay10", 18 },
|
||||
{ "BakeName", 20 }}};
|
||||
|
||||
// CreatureDisplayInfo.dbc
|
||||
layouts_["CreatureDisplayInfo"] = {{{ "ID", 0 }, { "ModelID", 1 }, { "ExtraDisplayId", 3 },
|
||||
{ "Skin1", 6 }, { "Skin2", 7 }, { "Skin3", 8 }}};
|
||||
|
||||
// TaxiNodes.dbc
|
||||
layouts_["TaxiNodes"] = {{{ "ID", 0 }, { "MapID", 1 }, { "X", 2 }, { "Y", 3 }, { "Z", 4 },
|
||||
{ "Name", 5 }, { "MountDisplayIdAllianceFallback", 20 },
|
||||
{ "MountDisplayIdHordeFallback", 21 },
|
||||
{ "MountDisplayIdAlliance", 22 }, { "MountDisplayIdHorde", 23 }}};
|
||||
|
||||
// TaxiPath.dbc
|
||||
layouts_["TaxiPath"] = {{{ "ID", 0 }, { "FromNode", 1 }, { "ToNode", 2 }, { "Cost", 3 }}};
|
||||
|
||||
// TaxiPathNode.dbc
|
||||
layouts_["TaxiPathNode"] = {{{ "ID", 0 }, { "PathID", 1 }, { "NodeIndex", 2 },
|
||||
{ "MapID", 3 }, { "X", 4 }, { "Y", 5 }, { "Z", 6 }}};
|
||||
|
||||
// TalentTab.dbc
|
||||
layouts_["TalentTab"] = {{{ "ID", 0 }, { "Name", 1 }, { "ClassMask", 20 },
|
||||
{ "OrderIndex", 22 }, { "BackgroundFile", 23 }}};
|
||||
|
||||
// Talent.dbc
|
||||
layouts_["Talent"] = {{{ "ID", 0 }, { "TabID", 1 }, { "Row", 2 }, { "Column", 3 },
|
||||
{ "RankSpell0", 4 }, { "PrereqTalent0", 9 }, { "PrereqRank0", 12 }}};
|
||||
|
||||
// SkillLineAbility.dbc
|
||||
layouts_["SkillLineAbility"] = {{{ "SkillLineID", 1 }, { "SpellID", 2 }}};
|
||||
|
||||
// SkillLine.dbc
|
||||
layouts_["SkillLine"] = {{{ "ID", 0 }, { "Category", 1 }, { "Name", 3 }}};
|
||||
|
||||
// Map.dbc
|
||||
layouts_["Map"] = {{{ "ID", 0 }, { "InternalName", 1 }}};
|
||||
|
||||
// CreatureModelData.dbc
|
||||
layouts_["CreatureModelData"] = {{{ "ID", 0 }, { "ModelPath", 2 }}};
|
||||
|
||||
// CharHairGeosets.dbc
|
||||
layouts_["CharHairGeosets"] = {{{ "RaceID", 1 }, { "SexID", 2 },
|
||||
{ "Variation", 3 }, { "GeosetID", 4 }}};
|
||||
|
||||
// CharacterFacialHairStyles.dbc
|
||||
layouts_["CharacterFacialHairStyles"] = {{{ "RaceID", 0 }, { "SexID", 1 },
|
||||
{ "Variation", 2 }, { "Geoset100", 3 }, { "Geoset300", 4 }, { "Geoset200", 5 }}};
|
||||
|
||||
// GameObjectDisplayInfo.dbc
|
||||
layouts_["GameObjectDisplayInfo"] = {{{ "ID", 0 }, { "ModelName", 1 }}};
|
||||
|
||||
// Emotes.dbc
|
||||
layouts_["Emotes"] = {{{ "ID", 0 }, { "AnimID", 2 }}};
|
||||
|
||||
// EmotesText.dbc
|
||||
layouts_["EmotesText"] = {{{ "Command", 1 }, { "EmoteRef", 2 },
|
||||
{ "SenderTargetTextID", 5 }, { "SenderNoTargetTextID", 9 }}};
|
||||
|
||||
// EmotesTextData.dbc
|
||||
layouts_["EmotesTextData"] = {{{ "ID", 0 }, { "Text", 1 }}};
|
||||
|
||||
// Light.dbc
|
||||
layouts_["Light"] = {{{ "ID", 0 }, { "MapID", 1 }, { "X", 2 }, { "Z", 3 }, { "Y", 4 },
|
||||
{ "InnerRadius", 5 }, { "OuterRadius", 6 }, { "LightParamsID", 7 },
|
||||
{ "LightParamsIDRain", 8 }, { "LightParamsIDUnderwater", 9 }}};
|
||||
|
||||
// LightParams.dbc
|
||||
layouts_["LightParams"] = {{{ "LightParamsID", 0 }}};
|
||||
|
||||
// LightParamsBands.dbc (custom split from LightIntBand/LightFloatBand)
|
||||
layouts_["LightParamsBands"] = {{{ "BlockIndex", 1 }, { "NumKeyframes", 2 },
|
||||
{ "TimeKey0", 3 }, { "Value0", 19 }}};
|
||||
|
||||
// LightIntBand.dbc (same structure as LightParamsBands)
|
||||
layouts_["LightIntBand"] = {{{ "BlockIndex", 1 }, { "NumKeyframes", 2 },
|
||||
{ "TimeKey0", 3 }, { "Value0", 19 }}};
|
||||
|
||||
// LightFloatBand.dbc
|
||||
layouts_["LightFloatBand"] = {{{ "BlockIndex", 1 }, { "NumKeyframes", 2 },
|
||||
{ "TimeKey0", 3 }, { "Value0", 19 }}};
|
||||
|
||||
// WorldMapArea.dbc
|
||||
layouts_["WorldMapArea"] = {{{ "ID", 0 }, { "MapID", 1 }, { "AreaID", 2 },
|
||||
{ "AreaName", 3 }, { "LocLeft", 4 }, { "LocRight", 5 }, { "LocTop", 6 },
|
||||
{ "LocBottom", 7 }, { "DisplayMapID", 8 }, { "ParentWorldMapID", 10 }}};
|
||||
|
||||
LOG_INFO("DBCLayout: loaded ", layouts_.size(), " WotLK default layouts");
|
||||
}
|
||||
|
||||
bool DBCLayout::loadFromJson(const std::string& path) {
|
||||
std::ifstream f(path);
|
||||
if (!f.is_open()) {
|
||||
LOG_WARNING("DBCLayout: cannot open ", path);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string json((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
|
||||
|
||||
layouts_.clear();
|
||||
size_t loaded = 0;
|
||||
size_t pos = 0;
|
||||
|
||||
// Parse top-level object: { "DbcName": { "FieldName": index, ... }, ... }
|
||||
// Find the first '{'
|
||||
pos = json.find('{', pos);
|
||||
if (pos == std::string::npos) return false;
|
||||
++pos;
|
||||
|
||||
while (pos < json.size()) {
|
||||
// Find DBC name key
|
||||
size_t dbcKeyStart = json.find('"', pos);
|
||||
if (dbcKeyStart == std::string::npos) break;
|
||||
size_t dbcKeyEnd = json.find('"', dbcKeyStart + 1);
|
||||
if (dbcKeyEnd == std::string::npos) break;
|
||||
std::string dbcName = json.substr(dbcKeyStart + 1, dbcKeyEnd - dbcKeyStart - 1);
|
||||
|
||||
// Find the nested object '{'
|
||||
size_t objStart = json.find('{', dbcKeyEnd);
|
||||
if (objStart == std::string::npos) break;
|
||||
|
||||
// Find the matching '}'
|
||||
size_t objEnd = json.find('}', objStart);
|
||||
if (objEnd == std::string::npos) break;
|
||||
|
||||
// Parse the inner object
|
||||
std::string inner = json.substr(objStart + 1, objEnd - objStart - 1);
|
||||
DBCFieldMap fieldMap;
|
||||
size_t ipos = 0;
|
||||
while (ipos < inner.size()) {
|
||||
size_t fkStart = inner.find('"', ipos);
|
||||
if (fkStart == std::string::npos) break;
|
||||
size_t fkEnd = inner.find('"', fkStart + 1);
|
||||
if (fkEnd == std::string::npos) break;
|
||||
std::string fieldName = inner.substr(fkStart + 1, fkEnd - fkStart - 1);
|
||||
|
||||
size_t colon = inner.find(':', fkEnd);
|
||||
if (colon == std::string::npos) break;
|
||||
size_t valStart = colon + 1;
|
||||
while (valStart < inner.size() && (inner[valStart] == ' ' || inner[valStart] == '\t' ||
|
||||
inner[valStart] == '\r' || inner[valStart] == '\n'))
|
||||
++valStart;
|
||||
size_t valEnd = inner.find_first_of(",}\r\n", valStart);
|
||||
if (valEnd == std::string::npos) valEnd = inner.size();
|
||||
std::string valStr = inner.substr(valStart, valEnd - valStart);
|
||||
while (!valStr.empty() && (valStr.back() == ' ' || valStr.back() == '\t'))
|
||||
valStr.pop_back();
|
||||
|
||||
try {
|
||||
uint32_t idx = static_cast<uint32_t>(std::stoul(valStr));
|
||||
fieldMap.fields[fieldName] = idx;
|
||||
} catch (...) {}
|
||||
|
||||
ipos = valEnd + 1;
|
||||
}
|
||||
|
||||
if (!fieldMap.fields.empty()) {
|
||||
layouts_[dbcName] = std::move(fieldMap);
|
||||
++loaded;
|
||||
}
|
||||
|
||||
pos = objEnd + 1;
|
||||
}
|
||||
|
||||
LOG_INFO("DBCLayout: loaded ", loaded, " layouts from ", path);
|
||||
return loaded > 0;
|
||||
}
|
||||
|
||||
const DBCFieldMap* DBCLayout::getLayout(const std::string& dbcName) const {
|
||||
auto it = layouts_.find(dbcName);
|
||||
return (it != layouts_.end()) ? &it->second : nullptr;
|
||||
}
|
||||
|
||||
} // namespace pipeline
|
||||
} // namespace wowee
|
||||
204
src/pipeline/hd_pack_manager.cpp
Normal file
204
src/pipeline/hd_pack_manager.cpp
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
#include "pipeline/hd_pack_manager.hpp"
|
||||
#include "pipeline/asset_manager.hpp"
|
||||
#include "core/logger.hpp"
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
|
||||
namespace wowee {
|
||||
namespace pipeline {
|
||||
|
||||
namespace {
|
||||
// Minimal JSON string value parser (key must be unique in the flat object)
|
||||
std::string jsonStringValue(const std::string& json, const std::string& key) {
|
||||
std::string needle = "\"" + key + "\"";
|
||||
size_t pos = json.find(needle);
|
||||
if (pos == std::string::npos) return "";
|
||||
pos = json.find(':', pos + needle.size());
|
||||
if (pos == std::string::npos) return "";
|
||||
pos = json.find('"', pos + 1);
|
||||
if (pos == std::string::npos) return "";
|
||||
size_t end = json.find('"', pos + 1);
|
||||
if (end == std::string::npos) return "";
|
||||
return json.substr(pos + 1, end - pos - 1);
|
||||
}
|
||||
|
||||
// Parse a JSON number value
|
||||
uint32_t jsonUintValue(const std::string& json, const std::string& key) {
|
||||
std::string needle = "\"" + key + "\"";
|
||||
size_t pos = json.find(needle);
|
||||
if (pos == std::string::npos) return 0;
|
||||
pos = json.find(':', pos + needle.size());
|
||||
if (pos == std::string::npos) return 0;
|
||||
++pos;
|
||||
while (pos < json.size() && (json[pos] == ' ' || json[pos] == '\t')) ++pos;
|
||||
return static_cast<uint32_t>(std::strtoul(json.c_str() + pos, nullptr, 10));
|
||||
}
|
||||
|
||||
// Parse a JSON string array value
|
||||
std::vector<std::string> jsonStringArray(const std::string& json, const std::string& key) {
|
||||
std::vector<std::string> result;
|
||||
std::string needle = "\"" + key + "\"";
|
||||
size_t pos = json.find(needle);
|
||||
if (pos == std::string::npos) return result;
|
||||
pos = json.find('[', pos + needle.size());
|
||||
if (pos == std::string::npos) return result;
|
||||
size_t end = json.find(']', pos);
|
||||
if (end == std::string::npos) return result;
|
||||
std::string arr = json.substr(pos + 1, end - pos - 1);
|
||||
size_t p = 0;
|
||||
while (p < arr.size()) {
|
||||
size_t qs = arr.find('"', p);
|
||||
if (qs == std::string::npos) break;
|
||||
size_t qe = arr.find('"', qs + 1);
|
||||
if (qe == std::string::npos) break;
|
||||
result.push_back(arr.substr(qs + 1, qe - qs - 1));
|
||||
p = qe + 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void HDPackManager::initialize(const std::string& hdRootPath) {
|
||||
packs_.clear();
|
||||
|
||||
if (!std::filesystem::exists(hdRootPath) || !std::filesystem::is_directory(hdRootPath)) {
|
||||
LOG_DEBUG("HD pack directory not found: ", hdRootPath);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& entry : std::filesystem::directory_iterator(hdRootPath)) {
|
||||
if (!entry.is_directory()) continue;
|
||||
|
||||
std::string packJsonPath = entry.path().string() + "/pack.json";
|
||||
if (!std::filesystem::exists(packJsonPath)) continue;
|
||||
|
||||
std::ifstream f(packJsonPath);
|
||||
if (!f.is_open()) continue;
|
||||
|
||||
std::string json((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
|
||||
|
||||
HDPack pack;
|
||||
pack.id = jsonStringValue(json, "id");
|
||||
pack.name = jsonStringValue(json, "name");
|
||||
pack.group = jsonStringValue(json, "group");
|
||||
pack.totalSizeMB = jsonUintValue(json, "totalSizeMB");
|
||||
pack.expansions = jsonStringArray(json, "expansions");
|
||||
pack.packDir = entry.path().string();
|
||||
pack.manifestPath = entry.path().string() + "/manifest.json";
|
||||
|
||||
if (pack.id.empty()) {
|
||||
LOG_WARNING("HD pack in ", entry.path().string(), " has no id, skipping");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!std::filesystem::exists(pack.manifestPath)) {
|
||||
LOG_WARNING("HD pack '", pack.id, "' missing manifest.json, skipping");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Apply saved enabled state if available
|
||||
auto it = enabledState_.find(pack.id);
|
||||
if (it != enabledState_.end()) {
|
||||
pack.enabled = it->second;
|
||||
}
|
||||
|
||||
LOG_INFO("Discovered HD pack: '", pack.id, "' (", pack.name, ") ",
|
||||
pack.totalSizeMB, " MB, ", pack.expansions.size(), " expansions");
|
||||
packs_.push_back(std::move(pack));
|
||||
}
|
||||
|
||||
LOG_INFO("HDPackManager: found ", packs_.size(), " packs in ", hdRootPath);
|
||||
}
|
||||
|
||||
std::vector<const HDPack*> HDPackManager::getPacksForExpansion(const std::string& expansionId) const {
|
||||
std::vector<const HDPack*> result;
|
||||
for (const auto& pack : packs_) {
|
||||
if (pack.expansions.empty()) {
|
||||
// No expansion filter = compatible with all
|
||||
result.push_back(&pack);
|
||||
} else {
|
||||
for (const auto& exp : pack.expansions) {
|
||||
if (exp == expansionId) {
|
||||
result.push_back(&pack);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void HDPackManager::setPackEnabled(const std::string& packId, bool enabled) {
|
||||
enabledState_[packId] = enabled;
|
||||
for (auto& pack : packs_) {
|
||||
if (pack.id == packId) {
|
||||
pack.enabled = enabled;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool HDPackManager::isPackEnabled(const std::string& packId) const {
|
||||
auto it = enabledState_.find(packId);
|
||||
return it != enabledState_.end() && it->second;
|
||||
}
|
||||
|
||||
void HDPackManager::applyToAssetManager(AssetManager* assetManager, const std::string& expansionId) {
|
||||
if (!assetManager) return;
|
||||
|
||||
// Remove previously applied overlays
|
||||
for (const auto& overlayId : appliedOverlayIds_) {
|
||||
assetManager->removeOverlay(overlayId);
|
||||
}
|
||||
appliedOverlayIds_.clear();
|
||||
|
||||
// Get packs compatible with current expansion
|
||||
auto compatiblePacks = getPacksForExpansion(expansionId);
|
||||
int priorityOffset = 0;
|
||||
|
||||
for (const auto* pack : compatiblePacks) {
|
||||
if (!pack->enabled) continue;
|
||||
|
||||
std::string overlayId = "hd_" + pack->id;
|
||||
int priority = HD_OVERLAY_PRIORITY_BASE + priorityOffset;
|
||||
|
||||
if (assetManager->addOverlayManifest(pack->manifestPath, priority, overlayId)) {
|
||||
appliedOverlayIds_.push_back(overlayId);
|
||||
LOG_INFO("Applied HD pack '", pack->id, "' as overlay (priority ", priority, ")");
|
||||
}
|
||||
++priorityOffset;
|
||||
}
|
||||
|
||||
if (!appliedOverlayIds_.empty()) {
|
||||
LOG_INFO("Applied ", appliedOverlayIds_.size(), " HD pack overlays");
|
||||
}
|
||||
}
|
||||
|
||||
void HDPackManager::saveSettings(const std::string& settingsPath) const {
|
||||
std::ofstream f(settingsPath, std::ios::app);
|
||||
if (!f.is_open()) return;
|
||||
|
||||
for (const auto& [packId, enabled] : enabledState_) {
|
||||
f << "hd_pack_" << packId << "=" << (enabled ? "1" : "0") << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
void HDPackManager::loadSettings(const std::string& settingsPath) {
|
||||
std::ifstream f(settingsPath);
|
||||
if (!f.is_open()) return;
|
||||
|
||||
std::string line;
|
||||
while (std::getline(f, line)) {
|
||||
if (line.substr(0, 8) != "hd_pack_") continue;
|
||||
size_t eq = line.find('=');
|
||||
if (eq == std::string::npos) continue;
|
||||
std::string packId = line.substr(8, eq - 8);
|
||||
bool enabled = (line.substr(eq + 1) == "1");
|
||||
enabledState_[packId] = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace pipeline
|
||||
} // namespace wowee
|
||||
Loading…
Add table
Add a link
Reference in a new issue