mdx-m3-viewer
Version:
A browser WebGL model viewer. Mainly focused on models of the games Warcraft 3 and Starcraft 2.
432 lines (366 loc) • 9.48 kB
JavaScript
import BinaryStream from '../../common/binarystream';
import MpqArchive from '../mpq/archive';
import War3MapDoo from './doo/file';
import War3MapImp from './imp/file';
// import War3MapMmp from './mmp/file';
// import War3MapShd from './shd/file';
// import War3MapW3c from './w3c/file';
import War3MapW3d from './w3d/file';
import War3MapW3e from './w3e/file';
import War3MapW3i from './w3i/file';
// import War3MapW3o from './w3o/file';
// import War3MapW3r from './w3r/file';
// import War3MapW3s from './w3s/file';
import War3MapW3u from './w3u/file';
import War3MapWct from './wct/file';
// import War3MapWpm from './wpm/file';
import War3MapWtg from './wtg/file';
import War3MapWts from './wts/file';
import War3MapUnitsDoo from './unitsdoo/file';
/**
* Warcraft 3 map (W3X and W3M).
*/
export default class War3Map {
/**
* @param {?ArrayBuffer} buffer If given an ArrayBuffer, load() will be called immediately
* @param {?boolean} readonly If true, disables editing and saving the map (and the internal archive), allowing to optimize other things
*/
constructor(buffer, readonly) {
/** @member {number} */
this.unknown = 0;
/** @member {string} */
this.name = '';
/** @member {number} */
this.flags = 0;
/** @member {number} */
this.maxPlayers = 0;
/** @member {MpqArchive} */
this.archive = new MpqArchive(null, readonly);
/** @member {War3MapImp} */
this.imports = new War3MapImp();
/** @member {boolean} */
this.readonly = !!readonly;
if (buffer) {
this.load(buffer);
}
}
/**
* Load an existing map.
* Note that this clears the map from whatever it had in it before.
*
* @param {ArrayBuffer} buffer
* @return {boolean}
*/
load(buffer) {
let stream = new BinaryStream(buffer);
// The header no longer exists since some 1.3X.X patch?
if (stream.read(4) === 'HM3W') {
this.u1 = stream.readUint32();
this.name = stream.readUntilNull();
this.flags = stream.readUint32();
this.maxPlayers = stream.readUint32();
}
// Read the archive.
// If it failed to be read, abort.
if (!this.archive.load(buffer)) {
return false;
}
// Read in the imports file if there is one.
this.readImports();
return true;
}
/**
* Save this map.
* If the archive is in readonly mode, returns null.
*
* @return {?ArrayBuffer}
*/
save() {
if (this.readonly) {
return null;
}
// Update the imports if needed.
this.setImportsFile();
let headerSize = 512;
let archiveBuffer = this.archive.save();
let buffer = new ArrayBuffer(headerSize + archiveBuffer.byteLength);
let typedArray = new Uint8Array(buffer);
let stream = new BinaryStream(buffer);
// Write the header.
stream.write('HM3W');
stream.writeUint32(this.u1);
stream.write(`${this.name}\0`);
stream.writeUint32(this.flags);
stream.writeUint32(this.maxPlayers);
// Write the archive.
typedArray.set(new Uint8Array(archiveBuffer), headerSize);
return buffer;
}
/**
* A shortcut to the internal archive function.
*
* @return {Array<string>}
*/
getFileNames() {
return this.archive.getFileNames();
}
/**
* Gets a list of the file names imported in this map.
*
* @return {Array<string>}
*/
getImportNames() {
let names = [];
for (let entry of this.imports.entries.values()) {
let isCustom = entry.isCustom;
if (isCustom === 10 || isCustom === 13) {
names.push(entry.name);
} else {
names.push(`war3mapImported\\${entry.name}`);
}
}
return names;
}
/**
* Sets the imports file with all of the imports.
* Does nothing if the archive is in readonly mode.
*
* @return {boolean}
*/
setImportsFile() {
if (this.readonly) {
return false;
}
if (this.imports.entries.size > 0) {
return this.set('war3map.imp', this.imports.save());
}
return false;
}
/**
* Imports a file to this archive.
* If the file already exists, its buffer will be set.
* Files added to the archive but not to the imports list will be deleted by the World Editor automatically.
* This of course doesn't apply to internal map files.
* Does nothing if the archive is in readonly mode.
*
* @param {string} name
* @param {ArrayBuffer} buffer
* @return {boolean}
*/
import(name, buffer) {
if (this.readonly) {
return false;
}
if (this.archive.set(name, buffer)) {
this.imports.set(name);
return true;
}
return false;
}
/**
* A shortcut to the internal archive function.
*
* @param {string} name
* @param {ArrayBuffer} buffer
* @return {boolean}
*/
set(name, buffer) {
if (this.readonly) {
return false;
}
return this.archive.set(name, buffer);
}
/**
* A shortcut to the internal archive function.
*
* @param {string} name
* @return {?MpqFile}
*/
get(name) {
return this.archive.get(name);
}
/**
* Get the map's script.
*
* @return {string}
*/
getScript() {
let file = this.get('war3map.j') || this.get('scripts\\war3map.j');
return file.text();
}
/**
* A shortcut to the internal archive function.
*
* @param {string} name
* @return {boolean}
*/
has(name) {
return this.archive.has(name);
}
/**
* Deletes a file from the internal archive.
* Note that if the file is in the imports list, it will be removed from it too.
* Use this rather than the internal archive's delete.
*
* @param {string} name
* @return {boolean}
*/
delete(name) {
if (this.readonly) {
return false;
}
// If this file is in the import list, remove it.
this.imports.delete(name);
return this.archive.delete(name);
}
/**
* A shortcut to the internal archive function.
*
* @param {string} name
* @param {string} newName
* @return {boolean}
*/
rename(name, newName) {
if (this.readonly) {
return false;
}
if (this.archive.rename(name, newName)) {
// If the file was actually renamed, and it is an import, rename also the import entry.
this.imports.rename(name, newName);
return true;
}
return false;
}
/**
* @param {string} path
* @param {Constructor} Constructor
* @return {Constructor|null|undefined}
*/
readHelper(path, Constructor) {
let file = this.archive.get(path);
if (file) {
let buffer = file.arrayBuffer();
if (buffer) {
return new Constructor(buffer);
}
return null;
}
return undefined;
}
/**
* Read the imports file.
*/
readImports() {
let file = this.archive.get('war3map.imp');
if (file) {
let buffer = file.arrayBuffer();
if (buffer) {
this.imports.load(buffer);
}
}
}
/**
* Read the map information file.
*
* @return {?War3MapW3i}
*/
readMapInformation() {
return this.readHelper('war3map.w3i', War3MapW3i);
}
/**
* Read the environment file.
*
* @return {?War3MapW3e}
*/
readEnvironment() {
return this.readHelper('war3map.w3e', War3MapW3e);
}
/**
* Read and parse the doodads file.
*
* @return {?War3MapDoo}
*/
readDoodads() {
let file = this.archive.get('war3map.doo');
if (file) {
return new War3MapDoo(file.arrayBuffer());
}
}
/**
* Read and parse the units file.
*
* @return {?War3MapUnitsDoo}
*/
readUnits() {
let file = this.archive.get('war3mapUnits.doo');
if (file) {
return new War3MapUnitsDoo(file.arrayBuffer());
}
}
/**
* Read and parse the trigger file.
*
* @param {TriggerData} triggerData
* @return {?War3MapWtg}
*/
readTriggers(triggerData) {
let file = this.archive.get('war3map.wtg');
if (file) {
return new War3MapWtg(file.arrayBuffer(), triggerData);
}
}
/**
* Read and parse the custom text trigger file.
*
* @return {?War3MapWct}
*/
readCustomTextTriggers() {
let file = this.archive.get('war3map.wct');
if (file) {
return new War3MapWct(file.arrayBuffer());
}
}
/**
* Read and parse the string table file.
*
* @return {?War3MapWts}
*/
readStringTable() {
let file = this.archive.get('war3map.wts');
if (file) {
return new War3MapWts(file.text());
}
}
/**
* Read and parse all of the modification tables.
*
* @return {Map}
*/
readModifications() {
let modifications = {};
// useOptionalInts:
// w3u: no (units)
// w3t: no (items)
// w3b: no (destructables)
// w3d: yes (doodads)
// w3a: yes (abilities)
// w3h: no (buffs)
// w3q: yes (upgrades)
let fileNames = ['w3u', 'w3t', 'w3b', 'w3d', 'w3a', 'w3h', 'w3q'];
let useOptionalInts = [false, false, false, true, true, false, true];
for (let i = 0, l = fileNames.length; i < l; i++) {
let file = this.archive.get(`war3map.${fileNames[i]}`);
if (file) {
let buffer = file.arrayBuffer();
let modification;
if (useOptionalInts[i]) {
modification = new War3MapW3d(buffer);
} else {
modification = new War3MapW3u(buffer);
}
modifications[fileNames[i]] = modification;
}
}
return modifications;
}
}