mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
Fix CharSections.dbc field layout for classic/tbc/turtle expansions
The binary DBC files for all expansions use the same field ordering (VariationIndex=4, ColorIndex=5, Texture1=6), but classic/tbc/turtle dbc_layouts.json had swapped texture and variation/color fields, causing all skin/face/hair/underwear lookups to fail. Also adds generalized NxN texture scaling and a second video to README.
This commit is contained in:
parent
85714fd7f6
commit
b8f1f15eb4
6 changed files with 93 additions and 28 deletions
|
|
@ -12,8 +12,9 @@
|
|||
},
|
||||
"CharSections": {
|
||||
"RaceID": 1, "SexID": 2, "BaseSection": 3,
|
||||
"Texture1": 4, "Texture2": 5, "Texture3": 6,
|
||||
"Flags": 7, "VariationIndex": 8, "ColorIndex": 9
|
||||
"VariationIndex": 4, "ColorIndex": 5,
|
||||
"Texture1": 6, "Texture2": 7, "Texture3": 8,
|
||||
"Flags": 9
|
||||
},
|
||||
"SpellIcon": { "ID": 0, "Path": 1 },
|
||||
"FactionTemplate": {
|
||||
|
|
|
|||
|
|
@ -12,8 +12,9 @@
|
|||
},
|
||||
"CharSections": {
|
||||
"RaceID": 1, "SexID": 2, "BaseSection": 3,
|
||||
"Texture1": 4, "Texture2": 5, "Texture3": 6,
|
||||
"Flags": 7, "VariationIndex": 8, "ColorIndex": 9
|
||||
"VariationIndex": 4, "ColorIndex": 5,
|
||||
"Texture1": 6, "Texture2": 7, "Texture3": 8,
|
||||
"Flags": 9
|
||||
},
|
||||
"SpellIcon": { "ID": 0, "Path": 1 },
|
||||
"FactionTemplate": {
|
||||
|
|
|
|||
|
|
@ -12,8 +12,9 @@
|
|||
},
|
||||
"CharSections": {
|
||||
"RaceID": 1, "SexID": 2, "BaseSection": 3,
|
||||
"Texture1": 4, "Texture2": 5, "Texture3": 6,
|
||||
"Flags": 7, "VariationIndex": 8, "ColorIndex": 9
|
||||
"VariationIndex": 4, "ColorIndex": 5,
|
||||
"Texture1": 6, "Texture2": 7, "Texture3": 8,
|
||||
"Flags": 9
|
||||
},
|
||||
"SpellIcon": { "ID": 0, "Path": 1 },
|
||||
"FactionTemplate": {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ A native C++ World of Warcraft client with a custom OpenGL renderer.
|
|||
|
||||
[](https://youtu.be/Pd9JuYYxu0o)
|
||||
|
||||
[](https://youtu.be/J4NXegzqWSQ)
|
||||
|
||||
Primary target today is **WotLK 3.3.5a**, with active work to broaden compatibility across **Vanilla (Classic) + TBC + WotLK**.
|
||||
|
||||
> **Legal Disclaimer**: This is an educational/research project. It does not include any Blizzard Entertainment assets, data files, or proprietary code. World of Warcraft and all related assets are the property of Blizzard Entertainment, Inc. This project is not affiliated with or endorsed by Blizzard Entertainment. Users are responsible for supplying their own legally obtained game data files and for ensuring compliance with all applicable laws in their jurisdiction.
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@
|
|||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
#include <limits>
|
||||
|
||||
namespace wowee {
|
||||
|
|
@ -431,21 +432,22 @@ static void blitOverlay(std::vector<uint8_t>& composite, int compW, int compH,
|
|||
}
|
||||
}
|
||||
|
||||
// Nearest-neighbor 2x scale blit of overlay onto composite at (dstX, dstY)
|
||||
static void blitOverlayScaled2x(std::vector<uint8_t>& composite, int compW, int compH,
|
||||
const pipeline::BLPImage& overlay, int dstX, int dstY) {
|
||||
// Nearest-neighbor NxN scale blit of overlay onto composite at (dstX, dstY)
|
||||
static void blitOverlayScaledN(std::vector<uint8_t>& composite, int compW, int compH,
|
||||
const pipeline::BLPImage& overlay, int dstX, int dstY, int scale) {
|
||||
if (scale < 1) scale = 1;
|
||||
for (int sy = 0; sy < overlay.height; sy++) {
|
||||
for (int sx = 0; sx < overlay.width; sx++) {
|
||||
size_t srcIdx = (static_cast<size_t>(sy) * overlay.width + sx) * 4;
|
||||
uint8_t srcA = overlay.data[srcIdx + 3];
|
||||
if (srcA == 0) continue;
|
||||
|
||||
// Write to 2x2 block of destination pixels
|
||||
for (int dy2 = 0; dy2 < 2; dy2++) {
|
||||
int dy = dstY + sy * 2 + dy2;
|
||||
// Write to scale×scale block of destination pixels
|
||||
for (int dy2 = 0; dy2 < scale; dy2++) {
|
||||
int dy = dstY + sy * scale + dy2;
|
||||
if (dy < 0 || dy >= compH) continue;
|
||||
for (int dx2 = 0; dx2 < 2; dx2++) {
|
||||
int dx = dstX + sx * 2 + dx2;
|
||||
for (int dx2 = 0; dx2 < scale; dx2++) {
|
||||
int dx = dstX + sx * scale + dx2;
|
||||
if (dx < 0 || dx >= compW) continue;
|
||||
|
||||
size_t dstIdx = (static_cast<size_t>(dy) * compW + dx) * 4;
|
||||
|
|
@ -468,6 +470,12 @@ static void blitOverlayScaled2x(std::vector<uint8_t>& composite, int compW, int
|
|||
}
|
||||
}
|
||||
|
||||
// Legacy 2x wrapper
|
||||
static void blitOverlayScaled2x(std::vector<uint8_t>& composite, int compW, int compH,
|
||||
const pipeline::BLPImage& overlay, int dstX, int dstY) {
|
||||
blitOverlayScaledN(composite, compW, compH, overlay, dstX, dstY, 2);
|
||||
}
|
||||
|
||||
GLuint CharacterRenderer::compositeTextures(const std::vector<std::string>& layerPaths) {
|
||||
if (layerPaths.empty() || !assetManager || !assetManager->isInitialized()) {
|
||||
return whiteTexture;
|
||||
|
|
@ -502,13 +510,22 @@ GLuint CharacterRenderer::compositeTextures(const std::vector<std::string>& laye
|
|||
// Pelvis Lower 128 160 128 64
|
||||
// Foot 128 224 128 32
|
||||
|
||||
// Scale factor: base texture may be larger than the 256x256 reference atlas
|
||||
int coordScale = width / 256;
|
||||
if (coordScale < 1) coordScale = 1;
|
||||
|
||||
// Atlas region sizes at 256x256 base (w, h) for known regions
|
||||
struct AtlasRegion { int x, y, w, h; };
|
||||
static const AtlasRegion faceLowerRegion256 = {0, 192, 128, 64};
|
||||
static const AtlasRegion faceUpperRegion256 = {0, 160, 128, 32};
|
||||
|
||||
// Alpha-blend each overlay onto the composite
|
||||
for (size_t layer = 1; layer < layerPaths.size(); layer++) {
|
||||
if (layerPaths[layer].empty()) continue;
|
||||
|
||||
auto overlay = assetManager->loadTexture(layerPaths[layer]);
|
||||
if (!overlay.isValid()) {
|
||||
core::Logger::getInstance().warning("Composite: failed to load overlay: ", layerPaths[layer]);
|
||||
core::Logger::getInstance().warning("Composite: FAILED to load overlay: ", layerPaths[layer]);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -521,39 +538,82 @@ GLuint CharacterRenderer::compositeTextures(const std::vector<std::string>& laye
|
|||
} else {
|
||||
// Determine region by filename keywords
|
||||
// Coordinates scale with base texture size (256x256 is reference)
|
||||
float s = width / 256.0f;
|
||||
int dstX = 0, dstY = 0;
|
||||
int expectedW256 = 0, expectedH256 = 0; // Expected size at 256-base
|
||||
std::string pathLower = layerPaths[layer];
|
||||
for (auto& c : pathLower) c = std::tolower(c);
|
||||
|
||||
if (pathLower.find("faceupper") != std::string::npos) {
|
||||
dstX = 0; dstY = static_cast<int>(160 * s);
|
||||
dstX = faceUpperRegion256.x; dstY = faceUpperRegion256.y;
|
||||
expectedW256 = faceUpperRegion256.w; expectedH256 = faceUpperRegion256.h;
|
||||
} else if (pathLower.find("facelower") != std::string::npos) {
|
||||
dstX = 0; dstY = static_cast<int>(192 * s);
|
||||
dstX = faceLowerRegion256.x; dstY = faceLowerRegion256.y;
|
||||
expectedW256 = faceLowerRegion256.w; expectedH256 = faceLowerRegion256.h;
|
||||
} else if (pathLower.find("pelvis") != std::string::npos) {
|
||||
dstX = static_cast<int>(128 * s);
|
||||
dstY = static_cast<int>(96 * s);
|
||||
dstX = 128; dstY = 96;
|
||||
expectedW256 = 128; expectedH256 = 64;
|
||||
} else if (pathLower.find("torso") != std::string::npos) {
|
||||
dstX = static_cast<int>(128 * s);
|
||||
dstY = 0;
|
||||
dstX = 128; dstY = 0;
|
||||
expectedW256 = 128; expectedH256 = 64;
|
||||
} else if (pathLower.find("armupper") != std::string::npos) {
|
||||
dstX = 0; dstY = 0;
|
||||
expectedW256 = 128; expectedH256 = 64;
|
||||
} else if (pathLower.find("armlower") != std::string::npos) {
|
||||
dstX = 0; dstY = static_cast<int>(64 * s);
|
||||
dstX = 0; dstY = 64;
|
||||
expectedW256 = 128; expectedH256 = 64;
|
||||
} else if (pathLower.find("hand") != std::string::npos) {
|
||||
dstX = 0; dstY = static_cast<int>(128 * s);
|
||||
dstX = 0; dstY = 128;
|
||||
expectedW256 = 128; expectedH256 = 32;
|
||||
} else if (pathLower.find("foot") != std::string::npos || pathLower.find("feet") != std::string::npos) {
|
||||
dstX = static_cast<int>(128 * s); dstY = static_cast<int>(224 * s);
|
||||
dstX = 128; dstY = 224;
|
||||
expectedW256 = 128; expectedH256 = 32;
|
||||
} else if (pathLower.find("legupper") != std::string::npos || pathLower.find("leg") != std::string::npos) {
|
||||
dstX = static_cast<int>(128 * s); dstY = static_cast<int>(160 * s);
|
||||
dstX = 128; dstY = 160;
|
||||
expectedW256 = 128; expectedH256 = 64;
|
||||
} else {
|
||||
// Unknown — center placement as fallback
|
||||
dstX = (width - overlay.width) / 2;
|
||||
dstY = (height - overlay.height) / 2;
|
||||
core::Logger::getInstance().info("Composite: UNKNOWN region for '",
|
||||
layerPaths[layer], "', centering at (", dstX, ",", dstY, ")");
|
||||
blitOverlay(composite, width, height, overlay, dstX, dstY);
|
||||
continue;
|
||||
}
|
||||
|
||||
core::Logger::getInstance().info("Composite: placing '", layerPaths[layer], "' at (", dstX, ",", dstY, ") on ", width, "x", height);
|
||||
blitOverlay(composite, width, height, overlay, dstX, dstY);
|
||||
// Scale coordinates from 256-base to actual canvas
|
||||
dstX *= coordScale;
|
||||
dstY *= coordScale;
|
||||
|
||||
// If overlay is 256-base sized but canvas is larger, scale the overlay up
|
||||
int expectedW = expectedW256 * coordScale;
|
||||
int expectedH = expectedH256 * coordScale;
|
||||
bool needsScale = (coordScale > 1 &&
|
||||
overlay.width == expectedW256 && overlay.height == expectedH256);
|
||||
|
||||
core::Logger::getInstance().info("Composite: placing '", layerPaths[layer],
|
||||
"' (", overlay.width, "x", overlay.height,
|
||||
") at (", dstX, ",", dstY, ") on ", width, "x", height,
|
||||
" expected=", expectedW, "x", expectedH,
|
||||
needsScale ? " [SCALING]" : "");
|
||||
|
||||
if (needsScale) {
|
||||
blitOverlayScaledN(composite, width, height, overlay, dstX, dstY, coordScale);
|
||||
} else {
|
||||
blitOverlay(composite, width, height, overlay, dstX, dstY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Debug: dump composite to /tmp for visual inspection
|
||||
{
|
||||
std::string dumpPath = "/tmp/wowee_composite_debug_" +
|
||||
std::to_string(width) + "x" + std::to_string(height) + ".raw";
|
||||
std::ofstream dump(dumpPath, std::ios::binary);
|
||||
if (dump) {
|
||||
dump.write(reinterpret_cast<const char*>(composite.data()),
|
||||
static_cast<std::streamsize>(composite.size()));
|
||||
core::Logger::getInstance().info("Composite debug dump: ", dumpPath,
|
||||
" (", width, "x", height, ", ", composite.size(), " bytes)");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue