UNPKG

patchwork-mapconverter

Version:

Executable wrapper for https://github.com/ChiefOfGxBxL/WC3MapTranslator

290 lines (251 loc) 11.5 kB
import { HexBuffer } from '../HexBuffer' import { W3Buffer } from '../W3Buffer' import { type WarResult, type JsonResult } from '../CommonInterfaces' import { RandomSpawn, type Unit } from '../data/Unit' import { type Translator } from './Translator' import { type UnitSet } from '../data/UnitSet' export class UnitsTranslator implements Translator<Unit[]> { private static instance: UnitsTranslator private constructor() { } public static getInstance(): UnitsTranslator { if (this.instance == null) { this.instance = new this() } return this.instance } public static jsonToWar(units: Unit[]): WarResult { return this.getInstance().jsonToWar(units) } public static warToJson(buffer: Buffer): JsonResult<Unit[]> { return this.getInstance().warToJson(buffer) } public jsonToWar(unitsJson: Unit[]): WarResult { const outBufferToWar = new HexBuffer() /* * Header */ outBufferToWar.addChars('W3do') outBufferToWar.addInt(9) outBufferToWar.addInt(11) outBufferToWar.addInt(unitsJson?.length || 0) // number of units /* * Body */ unitsJson?.forEach((unit) => { outBufferToWar.addChars(unit.type) // type outBufferToWar.addInt(unit.variation != null ? unit.variation : 0) // variation outBufferToWar.addFloat(unit.position[0]) // position x outBufferToWar.addFloat(unit.position[1]) // position y outBufferToWar.addFloat(unit.position[2]) // position z outBufferToWar.addFloat(unit.rotation != null ? unit.rotation : 0) // rotation angle if (unit.scale == null) unit.scale = [1, 1, 1] outBufferToWar.addFloat(unit.scale[0] != null ? unit.scale[0] : 1) // scale x outBufferToWar.addFloat(unit.scale[1] != null ? unit.scale[1] : 1) // scale y outBufferToWar.addFloat(unit.scale[2] != null ? unit.scale[2] : 1) // scale z outBufferToWar.addChars(unit.skin) // Unit flags outBufferToWar.addByte(0) // UNSUPPORTED: flags outBufferToWar.addInt(unit.player) // player # outBufferToWar.addByte(0) // (byte unknown - 0) outBufferToWar.addByte(0) // (byte unknown - 0) outBufferToWar.addInt(unit.hitpoints) // hitpoints outBufferToWar.addInt(unit.mana != null ? unit.mana : 0) // mana outBufferToWar.addInt(unit.randomItemSetPtr) outBufferToWar.addInt(unit.droppedItemSets?.length || 0) unit.droppedItemSets?.forEach(itemSet => { outBufferToWar.addInt(itemSet.items?.length || 0) itemSet.items?.forEach(item => { outBufferToWar.addChars(item.itemId) outBufferToWar.addInt(item.chance) }) }) // Gold amount // Required if unit is a gold mine // Optional (set to zero) if unit is not a gold mine outBufferToWar.addInt(unit.gold) // outBufferToWar.addInt(unit.type === 'ngol' ? unit.gold : 0); outBufferToWar.addFloat(unit.targetAcquisition != null ? unit.targetAcquisition : 0) // target acquisition // Unit hero attributes // Can be left unspecified, but values can never be below 1 if (unit.hero == null) unit.hero = { level: 1, str: 1, agi: 1, int: 1 } outBufferToWar.addInt(unit.hero.level) outBufferToWar.addInt(unit.hero.str) outBufferToWar.addInt(unit.hero.agi) outBufferToWar.addInt(unit.hero.int) // Inventory - - - if (unit.inventory == null) unit.inventory = [] outBufferToWar.addInt(unit.inventory?.length || 0) // # items in inventory unit.inventory?.forEach(item => { outBufferToWar.addInt(item.slot - 1) // zero-index item slot outBufferToWar.addChars(item.type) }) // Modified abilities - - - if (unit.abilities == null) unit.abilities = [] outBufferToWar.addInt(unit.abilities?.length || 0) // # modified abilities unit.abilities?.forEach((ability) => { outBufferToWar.addChars(ability.ability) // ability string outBufferToWar.addInt(+ability.active) // 0 = not active, 1 = active outBufferToWar.addInt(ability.level) }) // Random outBufferToWar.addInt(unit.random.type) switch (unit.random.type) { case 0: outBufferToWar.addByte(unit.random.level as number) outBufferToWar.addByte(0) // Unknown - apparently it's part of level ^ outBufferToWar.addByte(0) // Unknown - apparently it's part of level ^ outBufferToWar.addByte(unit.random.itemClass as number) break case 1: outBufferToWar.addInt(unit.random.groupIndex as number) outBufferToWar.addInt(unit.random.columnIndex as number) break case 2: outBufferToWar.addInt((unit.random.unitSet as UnitSet)?.length || 0) unit.random.unitSet?.forEach(spawnableUnit => { outBufferToWar.addChars(spawnableUnit.unitId) outBufferToWar.addInt(spawnableUnit.chance) }) break } outBufferToWar.addInt(unit.color != null ? unit.color : unit.player) // custom color, defaults to owning player outBufferToWar.addInt(unit.waygate) // waygate outBufferToWar.addInt(unit.id) // id }) return { errors: [], buffer: outBufferToWar.getBuffer() } } public warToJson(buffer: Buffer): JsonResult<Unit[]> { const result: Unit[] = [] const outBufferToJSON = new W3Buffer(buffer) const fileId = outBufferToJSON.readChars(4) // W3do for doodad file const fileVersion = outBufferToJSON.readInt() // File version = 7 const subVersion = outBufferToJSON.readInt() // 0B 00 00 00 const numUnits = outBufferToJSON.readInt() // # of units for (let i = 0; i < numUnits; i++) { const unit: Unit = { type: '', variation: -1, position: [0, 0, 0], rotation: 0, scale: [0, 0, 0], hero: { level: 1, str: 1, agi: 1, int: 1 }, skin: '', inventory: [], abilities: [], player: 0, hitpoints: -1, mana: -1, randomItemSetPtr: -1, droppedItemSets: [], gold: 0, targetAcquisition: -1, random: { type: -1, } as RandomSpawn, color: -1, waygate: -1, id: -1 } unit.type = outBufferToJSON.readChars(4) // (iDNR = random item, uDNR = random unit) unit.variation = outBufferToJSON.readInt() unit.position = [outBufferToJSON.readFloat(), outBufferToJSON.readFloat(), outBufferToJSON.readFloat()] // X Y Z coords unit.rotation = outBufferToJSON.readFloat() unit.scale = [outBufferToJSON.readFloat(), outBufferToJSON.readFloat(), outBufferToJSON.readFloat()] // X Y Z scaling if (fileVersion > 7) { unit.skin = outBufferToJSON.readChars(4) } else { // default unit's skin - Note: Probably fails for items? unit.skin = unit.type } // UNSUPPORTED: flags const flags = outBufferToJSON.readByte() unit.player = outBufferToJSON.readInt() // (player1 = 0, 16=neutral passive); note: wc3 patch now has 24 max players outBufferToJSON.readByte() // unknown outBufferToJSON.readByte() // unknown unit.hitpoints = outBufferToJSON.readInt() // -1 = use default unit.mana = outBufferToJSON.readInt() // -1 = use default, 0 = unit doesn't have mana if (subVersion !== 9) { // not RoC unit.randomItemSetPtr = outBufferToJSON.readInt() } const numDroppedItemSets = outBufferToJSON.readInt() for (let j = 0; j < numDroppedItemSets; j++) { unit.droppedItemSets.push({ items: [] }) const numDroppableItems = outBufferToJSON.readInt() for (let k = 0; k < numDroppableItems; k++) { unit.droppedItemSets[j].items.push({ itemId: outBufferToJSON.readChars(4), // Item ID chance: outBufferToJSON.readInt() // % chance to drop }) } } unit.gold = outBufferToJSON.readInt() unit.targetAcquisition = outBufferToJSON.readFloat() // (-1 = normal, -2 = camp) unit.hero = { level: outBufferToJSON.readInt(), // non-hero units = 1 str: 0, agi: 0, int: 0 } if (subVersion !== 9) { // not RoC unit.hero.str = outBufferToJSON.readInt() unit.hero.agi = outBufferToJSON.readInt() unit.hero.int = outBufferToJSON.readInt() } const numItemsInventory = outBufferToJSON.readInt() for (let j = 0; j < numItemsInventory; j++) { unit.inventory.push({ slot: outBufferToJSON.readInt() + 1, // the int is 0-based, but json format wants 1-6 type: outBufferToJSON.readChars(4) // Item ID }) } const numModifiedAbil = outBufferToJSON.readInt() for (let j = 0; j < numModifiedAbil; j++) { unit.abilities.push({ ability: outBufferToJSON.readChars(4), // Ability ID active: outBufferToJSON.readInt() === 1, // autocast active? 0=no, 1=active level: outBufferToJSON.readInt() }) } unit.random.type = outBufferToJSON.readInt() // random unit/item flag "r" (for uDNR units and iDNR items) if (unit.random.type === 0) { // 0 = Any neutral passive building/item, in this case we have // byte[3]: level of the random unit/item,-1 = any (this is actually interpreted as a 24-bit number) // byte: item class of the random item, 0 = any, 1 = permanent ... (this is 0 for units) // r is also 0 for non random units/items so we have these 4 bytes anyway (even if the id wasnt uDNR or iDNR) unit.random.level = outBufferToJSON.readByte() outBufferToJSON.readByte() // unknown outBufferToJSON.readByte() // unknown unit.random.itemClass = outBufferToJSON.readByte() } else if (unit.random.type === 1) { // 1 = random unit from random group (defined in the w3i), in this case we have // int: unit group number (which group from the global table) // int: position number (which column of this group) // the column should of course have the item flag set (in the w3i) if this is a random item unit.random.groupIndex = outBufferToJSON.readInt() unit.random.columnIndex = outBufferToJSON.readInt() } else if (unit.random.type === 2) { // 2 = random unit from custom table, in this case we have // int: number "n" of different available units // then we have n times a random unit structure const numDiffAvailUnits = outBufferToJSON.readInt() unit.random.unitSet = [] for (let k = 0; k < numDiffAvailUnits; k++) { unit.random.unitSet.push({ unitId: outBufferToJSON.readChars(4), // Unit ID chance: outBufferToJSON.readInt() // % chance }) } } unit.color = outBufferToJSON.readInt() unit.waygate = outBufferToJSON.readInt() // waygate (-1 = deactivated, else its the creation number of the target rect as in war3map.w3r) unit.id = outBufferToJSON.readInt() result.push(unit) } return { errors: [], json: result } } }