Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions lib/HexBuffer.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,15 @@ function HexBuffer() {
buffer.push('0x0');
},

addFourCCTwice: function(int) {
if (int >= 10) throw new Error(`idk how to encode ${int}`);

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

encoding an int can be done via HexBuffer.addInt

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is roughly supposed to store an integer as a decimal string, not as an int properly.

if (int === 0) {
buffer.push('0x0', '0x0', '0x0', '0x0');
} else {
buffer.push(`0x${30+int}`, '0x0', '0x0', '0x0');
}
},

getBuffer: () => { return new Buffer(buffer); }
};
}
Expand Down
15 changes: 13 additions & 2 deletions lib/W3Buffer.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ module.exports = function W3Buffer(buffer) {
return String.fromCharCode(ch);
}).join('');
},
readChars: function(len) {
readChars: function(len, allowNull = false) {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact it was your commit fe0b16c 😄

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I know. As I said, this is based on v1.0.0, so I had to include it for completeness.

Ultimately, gotta evaluate to what extent each implementation of random tables is accurate. It's a bit awkward that I didn't realize at first that you already had an implementation... Feel free to close and/or cherry-pick improvements.

let string = [],
numCharsToRead = len || 1;

Expand All @@ -36,10 +36,21 @@ module.exports = function W3Buffer(buffer) {
}

return string.map((ch) => {
if(ch === 0x0) return '0';
if(!allowNull && ch === 0x0) return '0';
return String.fromCharCode(ch);
}).join('');
},
readFourCCTwice: function() {
let value = 0;
let string = this.readChars(4, true);
for (let i = 0; i < string.length; i++) {
let c = string.charCodeAt(i);
if (c === 0) continue;
if (c < 0x30 || 0x39 < c) throw new Error(`Invalid numerical FourCC`);
value += (c - 0x30) * (1 << (8 * i));

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is interesting - I haven't seen documentation on WC3's FourCC format, so am unfamiliar.

Do you have any references or examples, perhaps ones where the prior WC3MapTranslator implementation fails and this one is correct?

@Slayer95 Slayer95 May 11, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have any references or examples, perhaps ones where the prior WC3MapTranslator implementation fails and this one is correct?

Will gather this. Note that the previous "FourCC" implementation is correct per se. This is a different thing, which I named "FourCC twice" for lack of a better name.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, regarding FourCC, this is a close reference https://ubershmekel.github.io/fourcc-to-text/. The difference is that WC3 treats FourCC texts as case-insensitive in some contexts, such as tooltips files. I haven't fully researched the sensitiveness stuff, but it's not relevant for WC3MapTranslator.

}
return value;
},
readByte: function() {
let byte = buffer[offset];
offset += 1;
Expand Down
87 changes: 65 additions & 22 deletions lib/translators/InfoTranslator.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,14 +88,18 @@ const InfoTranslator = {

// Misc.
// If globalWeather is not defined or is set to 'none', use 0 sentinel value, else add char[4]
if(!infoJson.globalWeather || infoJson.globalWeather.toLowerCase() === 'none') {
if(!infoJson.globalWeather || infoJson.globalWeather.toLowerCase() === 'none' || infoJson.globalWeather === '0000') {
outBuffer.addInt(0);
}
else {
outBuffer.addString(infoJson.globalWeather, false); // char[4] - lookup table
}
outBuffer.addString(infoJson.customSoundEnvironment || '', true);
outBuffer.addChar(infoJson.customLightEnv || 'L');
if (infoJson.customLightEnv === '0') {
outBuffer.addByte(0);
} else {
outBuffer.addChar(infoJson.customLightEnv || 'L');
}

// Custom water tinting
outBuffer.addByte(infoJson.water[0]);
Expand All @@ -110,6 +114,7 @@ const InfoTranslator = {
outBuffer.addInt(player.type);
outBuffer.addInt(player.race);
outBuffer.addInt(player.startingPos.fixed ? 1 : 0);
/* outBuffer.addInt(player.startingPos.fixed ? 1 : 2); */
outBuffer.addString(player.name, true);
outBuffer.addFloat(player.startingPos.x);
outBuffer.addFloat(player.startingPos.y);
Expand All @@ -129,10 +134,8 @@ const InfoTranslator = {
if(force.flags.shareAdvUnitControl) forceFlags |= 0x0020;

outBuffer.addInt(forceFlags);
outBuffer.addByte(255); // force players - unsupported
outBuffer.addByte(255); // force players - unsupported
outBuffer.addByte(255); // force players - unsupported
outBuffer.addByte(255); // force players - unsupported

outBuffer.addInt(force.players === -1 ? (1 << 11) - 1 : force.players);
outBuffer.addString(force.name, true);
});

Expand All @@ -142,8 +145,21 @@ const InfoTranslator = {
// Tech availability - unsupported
outBuffer.addInt(0);

// Unit table (random) - unsupported
outBuffer.addInt(0);
// Partial support: Unit table (random)
outBuffer.addInt(infoJson.randomUnits.length);
for (const randomUnitsGroup of infoJson.randomUnits) {
outBuffer.addInt(randomUnitsGroup.id);
outBuffer.addString(randomUnitsGroup.name, true);
outBuffer.addInt(randomUnitsGroup.subTables.length);
for (let i = 0; i < randomUnitsGroup.subTables.length; i++) outBuffer.addInt(0); // ????
outBuffer.addInt(randomUnitsGroup.subTables.length);
for (let i = 0; i < randomUnitsGroup.subTables.length; i++) {
outBuffer.addFourCCTwice(randomUnitsGroup.subTables[i].variants.length);
for (let j = 0; j < randomUnitsGroup.subTables[i].variants.length; j++) {
outBuffer.addString(randomUnitsGroup.subTables[i].variants[j].object, false);
}
}
}
Comment on lines +148 to +162

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like you're editing a .js file, when the project had several years ago been ported to TypeScript (.ts). The random unit/item table is already implemented via https://github.com/ChiefOfGxBxL/WC3MapTranslator/blob/master/src/translators/InfoTranslator.ts#L728-L757.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. After submitting this, I realized that v5.0.0 already supports random units tables. I was under the impression that v4.0.0 didn't and that v5.0.0 didn't either, which is why I originally thought this PR would be more useful 😅 , but I was wrong lol.

Still, there are some details here such as UTF8 strings, which I do correctly, but the rest of my algorithm is actually iffy.


// Item table (random) - unsupported
outBuffer.addInt(0);
Expand All @@ -154,7 +170,7 @@ const InfoTranslator = {
};
},
warToJson: function(buffer) {
let result = { map: {}, loadingScreen: {}, prologue: {}, fog: {}, camera: {}, players: [], forces: [] },
let result = { map: {}, loadingScreen: {}, prologue: {}, fog: {}, camera: {}, players: [], forces: [], randomUnits: [], randomItems: [] },
b = new W3Buffer(buffer);

let fileVersion = b.readInt(), // File version
Expand Down Expand Up @@ -278,51 +294,78 @@ const InfoTranslator = {

// UNSUPPORTED: Struct: upgrade avail.
let numUpgrades = b.readInt();
if (numUpgrades !== 0) throw new Error(`Custom upgrades unsupported`);
for(let i = 0; i < numUpgrades; i++) {
b.readInt(); // Player Flags (bit "x"=1 if this change applies for player "x")
b.readChars(4); // upgrade id (as in UpgradeData.slk)
b.readChars(4, true); // upgrade id (as in UpgradeData.slk)
b.readInt(); // Level of the upgrade for which the availability is changed (this is actually the level - 1, so 1 => 0)
b.readInt(); // Availability (0 = unavailable, 1 = available, 2 = researched)
}

// UNSUPPORTED: Struct: tech avail.
let numTech = b.readInt();
if (numTech !== 0) throw new Error(`Custom tech tree unsupported`);
for(let i = 0; i < numTech; i++) {
b.readInt(); // Player Flags (bit "x"=1 if this change applies for player "x")
b.readChars(4); // tech id (this can be an item, unit or ability)
b.readChars(4, true); // tech id (this can be an item, unit or ability)
}

// UNSUPPORTED: Struct: random unit table
// PARTIAL SUPPORT: Struct: random unit table
let numUnitTable = b.readInt();
let randomUnits = result.randomUnits;
for(let i = 0; i < numUnitTable; i++) {
b.readInt(); // Group number
b.readString(); // Group name
let randomUnitsGroup = {
id: b.readInt(), // Group number
name: b.readString(), // Group name
subTables: [],
};
randomUnits.push(randomUnitsGroup);

let numPositions = b.readInt(); // Number "m" of positions
for (let n = 0; n < numPositions; n++) if (b.readInt() !== 0) throw new Error(`Invalid random units spec`); // ?????
let numPositions2 = b.readInt();
if (numPositions !== numPositions2) throw new Error(`Invalid random units spec`);

for(let j = 0; j < numPositions; j++) {
b.readInt(); // unit table (=0), a building table (=1) or an item table (=2)
let thisTable = {
//type: b.readInt(), // unit table (=0), a building table (=1) or an item table (=2)
variants: [],
};
randomUnitsGroup.subTables.push(thisTable);

let numLinesInTable = b.readInt();
let numLinesInTable = b.readFourCCTwice();
for(let k = 0; k < numLinesInTable; k++) {
b.readInt(); // Chance of the unit/item (percentage)
b.readChar(); // unit/item id's for this line specified
thisTable.variants.push({
//chance: b.readInt(), // Chance of the unit/item (percentage)
object: b.readChars(4, true), // unit/item id's for this line specified
});
}
}
}

// UNSUPPORTED: Struct: random item table
let numItemTable = b.readInt();
if (numItemTable !== 0) throw new Error(`Custom random item table unsupported`);
let randomItems = result.randomItems;
for(let i = 0; i < numItemTable; i++) {
b.readInt(); // Table number
b.readString(); // Table name
let randomItemsGroup = {
id: b.readInt(), // Table number
name: b.readString(), // Table name
subTables: [],
};
randomItems.push(randomItemsGroup);

let itemSetsCurrentTable = b.readInt(); // Number "m" of item sets on the current item table
for(let j = 0; j < itemSetsCurrentTable; j++) {
let thisTable = [];
randomItemsGroup.push(thisTable);

let itemsInItemSet = b.readInt(); // Number "i" of items on the current item set
for(let k = 0; k < itemsInItemSet; k++) {
b.readInt(); // Percentual chance
b.readChars(4); // Item id (as in ItemData.slk)
thisTable.push({
chance: b.readInt(), // Percentual chance
object: b.readChars(4, true), // Item id (as in ItemData.slk)
});
}

}
Expand Down