From edd5927bf28c605b5adb816e6bed03bad540c542 Mon Sep 17 00:00:00 2001 From: Ivo Julca Date: Tue, 5 May 2026 16:34:52 -0500 Subject: [PATCH] Commit randomUnits, and other stuff --- lib/HexBuffer.js | 9 ++++ lib/W3Buffer.js | 15 +++++- lib/translators/InfoTranslator.js | 87 +++++++++++++++++++++++-------- 3 files changed, 87 insertions(+), 24 deletions(-) diff --git a/lib/HexBuffer.js b/lib/HexBuffer.js index 1bb7cd6..d90479a 100644 --- a/lib/HexBuffer.js +++ b/lib/HexBuffer.js @@ -69,6 +69,15 @@ function HexBuffer() { buffer.push('0x0'); }, + addFourCCTwice: function(int) { + if (int >= 10) throw new Error(`idk how to encode ${int}`); + if (int === 0) { + buffer.push('0x0', '0x0', '0x0', '0x0'); + } else { + buffer.push(`0x${30+int}`, '0x0', '0x0', '0x0'); + } + }, + getBuffer: () => { return new Buffer(buffer); } }; } diff --git a/lib/W3Buffer.js b/lib/W3Buffer.js index 9ff2bce..34ee5fa 100644 --- a/lib/W3Buffer.js +++ b/lib/W3Buffer.js @@ -26,7 +26,7 @@ module.exports = function W3Buffer(buffer) { return String.fromCharCode(ch); }).join(''); }, - readChars: function(len) { + readChars: function(len, allowNull = false) { let string = [], numCharsToRead = len || 1; @@ -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)); + } + return value; + }, readByte: function() { let byte = buffer[offset]; offset += 1; diff --git a/lib/translators/InfoTranslator.js b/lib/translators/InfoTranslator.js index bef47dc..2151f78 100644 --- a/lib/translators/InfoTranslator.js +++ b/lib/translators/InfoTranslator.js @@ -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]); @@ -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); @@ -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); }); @@ -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); + } + } + } // Item table (random) - unsupported outBuffer.addInt(0); @@ -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 @@ -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) + }); } }