forked from phenom/healer-man
Update Healer Man server
This commit is contained in:
1 parent
69809f7228
commit
ff20ab8779
101 files changed
+16713
-945
No files matched your search
@@ -0,0 +1,121 @@
|
||||
const WEIGHT_SIZE = 64 * 64;
|
||||
|
||||
function assertKnownAlphaLayer(layer, chunk) {
|
||||
if (!layer.usesAlphaMap) return;
|
||||
if (!layer.alphaMapAvailable || layer.alphaMapDataOffset == null || !layer.alphaMapBytes) {
|
||||
throw new Error(`MCNK ${chunk.indexX},${chunk.indexY} layer ${layer.index} has an unsupported or invalid MCAL span.`);
|
||||
}
|
||||
}
|
||||
|
||||
function decodeRle8(data, label) {
|
||||
const output = new Uint8Array(WEIGHT_SIZE);
|
||||
let inputOffset = 0;
|
||||
let outputOffset = 0;
|
||||
while (outputOffset < output.length) {
|
||||
if (inputOffset >= data.length) throw new Error(`${label}: truncated MCAL RLE stream.`);
|
||||
const control = data[inputOffset++];
|
||||
const count = control & 0x7f;
|
||||
if (!count) throw new Error(`${label}: zero-count MCAL RLE command cannot progress.`);
|
||||
if (outputOffset + count > output.length) throw new Error(`${label}: MCAL RLE expands beyond 4096 samples.`);
|
||||
if (control & 0x80) {
|
||||
if (inputOffset >= data.length) throw new Error(`${label}: truncated MCAL fill command.`);
|
||||
output.fill(data[inputOffset++], outputOffset, outputOffset + count);
|
||||
} else {
|
||||
if (inputOffset + count > data.length) throw new Error(`${label}: truncated MCAL copy command.`);
|
||||
output.set(data.subarray(inputOffset, inputOffset + count), outputOffset);
|
||||
inputOffset += count;
|
||||
}
|
||||
outputOffset += count;
|
||||
}
|
||||
if (inputOffset !== data.length) throw new Error(`${label}: ${data.length - inputOffset} trailing MCAL RLE bytes are unaccounted for.`);
|
||||
return output;
|
||||
}
|
||||
|
||||
function decodePacked4(data, noEdgeRepair, label) {
|
||||
if (data.length !== 2048) throw new Error(`${label}: packed 4-bit MCAL must be exactly 2048 bytes.`);
|
||||
const output = new Uint8Array(WEIGHT_SIZE);
|
||||
let source = 0;
|
||||
for (let row = 0; row < 64; row += 1) {
|
||||
for (let column = 0; column < 64; column += 2) {
|
||||
const value = data[source++];
|
||||
output[row * 64 + column] = (value & 0x0f) * 17;
|
||||
output[row * 64 + column + 1] = (value >>> 4) * 17;
|
||||
}
|
||||
}
|
||||
if (!noEdgeRepair) {
|
||||
for (let index = 0; index < 64; index += 1) {
|
||||
output[index * 64 + 63] = output[index * 64 + 62];
|
||||
output[63 * 64 + index] = output[62 * 64 + index];
|
||||
}
|
||||
output[63 * 64 + 63] = output[62 * 64 + 62];
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
export function decodeWowAdtAlphaMap(adtBuffer, chunk, layer) {
|
||||
assertKnownAlphaLayer(layer, chunk);
|
||||
if (!layer.usesAlphaMap) return new Uint8Array(WEIGHT_SIZE);
|
||||
const payload = adtBuffer.subarray(layer.alphaMapDataOffset, layer.alphaMapDataOffset + layer.alphaMapBytes);
|
||||
const label = `MCNK ${chunk.indexX},${chunk.indexY} layer ${layer.index}`;
|
||||
switch (layer.alphaMapEncoding) {
|
||||
case "packed-4":
|
||||
return decodePacked4(payload, (chunk.flags & 0x8000) !== 0, label);
|
||||
case "uncompressed-8":
|
||||
if (payload.length !== WEIGHT_SIZE) throw new Error(`${label}: 8-bit MCAL must be exactly 4096 bytes.`);
|
||||
return new Uint8Array(payload);
|
||||
case "rle-8":
|
||||
return decodeRle8(payload, label);
|
||||
default:
|
||||
throw new Error(`${label}: unsupported alpha encoding ${layer.alphaMapEncoding ?? "missing"}.`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode source overlays into the effective RGB weights expected by the
|
||||
* wow-overlay-residual shader. Classic 4-bit maps are source opacity maps;
|
||||
* their overlay order must be resolved before the shader consumes them.
|
||||
*/
|
||||
export function composeWowAdtTerrainWeights(adtBuffer, chunk) {
|
||||
const overlays = chunk.layers.slice(1);
|
||||
if (overlays.length > 3) throw new Error(`MCNK ${chunk.indexX},${chunk.indexY} has more than three overlay layers.`);
|
||||
const encodings = [...new Set(overlays.filter((layer) => layer.usesAlphaMap).map((layer) => layer.alphaMapEncoding))];
|
||||
if (encodings.length > 1) throw new Error(`MCNK ${chunk.indexX},${chunk.indexY} mixes MCAL alpha encodings.`);
|
||||
const sourceEncoding = encodings[0] ?? "none";
|
||||
const alphaMaps = overlays.map((layer) => decodeWowAdtAlphaMap(adtBuffer, chunk, layer));
|
||||
const packed = sourceEncoding === "packed-4";
|
||||
const effective = alphaMaps.map((map) => new Uint8Array(map));
|
||||
if (packed) {
|
||||
for (let pixel = 0; pixel < WEIGHT_SIZE; pixel += 1) {
|
||||
let remaining = 255;
|
||||
for (let index = effective.length - 1; index >= 0; index -= 1) {
|
||||
const weight = Math.floor((effective[index][pixel] * remaining + 127) / 255);
|
||||
effective[index][pixel] = weight;
|
||||
remaining -= weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
let maxWeightSum = 0;
|
||||
for (let pixel = 0; pixel < WEIGHT_SIZE; pixel += 1) {
|
||||
const sum = effective.reduce((value, map) => value + map[pixel], 0);
|
||||
maxWeightSum = Math.max(maxWeightSum, sum);
|
||||
if (sum > 255) throw new Error(`MCNK ${chunk.indexX},${chunk.indexY} effective overlays exceed 255 (${sum} at sample ${pixel}).`);
|
||||
}
|
||||
const rgba = Buffer.alloc(WEIGHT_SIZE * 4, 255);
|
||||
for (let pixel = 0; pixel < WEIGHT_SIZE; pixel += 1) {
|
||||
for (let channel = 0; channel < 3; channel += 1) rgba[pixel * 4 + channel] = effective[channel]?.[pixel] ?? 0;
|
||||
}
|
||||
return {
|
||||
rgba,
|
||||
sourceEncoding,
|
||||
sourcePolicy: packed ? "classic-nibble-to-big-alpha" : "direct-8bit-overlay-weights",
|
||||
channelLayerIndices: overlays.map((layer) => layer.index),
|
||||
maxWeightSum,
|
||||
};
|
||||
}
|
||||
|
||||
export function wowAdtNormalizedUv(row, column) {
|
||||
if (!Number.isInteger(row) || row < 0 || row > 16) throw new RangeError("MCVT row must be in [0,16].");
|
||||
const columns = row % 2 ? 8 : 9;
|
||||
if (!Number.isInteger(column) || column < 0 || column >= columns) throw new RangeError(`MCVT column must be in [0,${columns - 1}].`);
|
||||
return [(column + (row % 2 ? 0.5 : 0)) / 8, row / 16];
|
||||
}
|
||||
Reference in new issue
Block a user