mdx-m3-viewer
Version:
A browser WebGL model viewer. Mainly focused on models of the games Warcraft 3 and Starcraft 2.
351 lines (282 loc) • 8.02 kB
text/typescript
import BinaryStream from '../../common/binarystream';
import MpqArchive from '../mpq/archive';
import War3MapImp from './imp/file';
import War3MapW3d from './w3d/file';
import War3MapW3u from './w3u/file';
import War3MapWct from './wct/file';
import War3MapWtg from './wtg/file';
import War3MapWts from './wts/file';
import TriggerData from './wtg/triggerdata';
type War3MapModificationNames = 'w3a' | 'w3b' | 'w3d' | 'w3h' | 'w3q' | 'w3t' | 'w3u';
interface War3MapModifications {
w3a?: War3MapW3d;
w3b?: War3MapW3u;
w3d?: War3MapW3d;
w3h?: War3MapW3u;
w3q?: War3MapW3d;
w3t?: War3MapW3u;
w3u?: War3MapW3u;
}
/**
* Warcraft 3 map (W3X and W3M).
*/
export default class War3Map {
unknown: number = 0;
name: string = '';
flags: number = 0;
maxPlayers: number = 0;
archive: MpqArchive = new MpqArchive();
imports: War3MapImp = new War3MapImp();
readonly: boolean = false;
u1: number = 0;
/**
* Load an existing map.
*
* Note that this clears the map from whatever it had in it before.
*/
load(buffer: ArrayBuffer, readonly: boolean = false) {
let stream = new BinaryStream(buffer);
// The header no longer exists since some 1.3X.X patch?
if (stream.readBinary(4) === 'HM3W') {
this.u1 = stream.readUint32();
this.name = stream.readNull();
this.flags = stream.readUint32();
this.maxPlayers = stream.readUint32();
}
this.readonly = readonly;
// Read the archive.
// If it failed to be read, abort.
if (!this.archive.load(buffer, readonly)) {
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.
*/
save() {
if (this.readonly) {
return null;
}
// Update the imports if needed.
this.setImportsFile();
let headerSize = 512;
let archiveBuffer = this.archive.save();
if (!archiveBuffer) {
return null;
}
let buffer = new ArrayBuffer(headerSize + archiveBuffer.byteLength);
let typedArray = new Uint8Array(buffer);
let stream = new BinaryStream(buffer);
// Write the header.
stream.writeBinary('HM3W');
stream.writeUint32(this.u1);
stream.writeNull(this.name);
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.
*/
getFileNames() {
return this.archive.getFileNames();
}
/**
* Gets a list of the file names imported in this map.
*/
getImportNames() {
let names = [];
for (let entry of this.imports.entries.values()) {
let isCustom = entry.isCustom;
if (isCustom === 10 || isCustom === 13) {
names.push(entry.path);
} else {
names.push(`war3mapImported\\${entry.path}`);
}
}
return names;
}
/**
* Sets the imports file with all of the imports.
*
* Does nothing if the archive is in readonly mode.
*/
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.
*/
import(name: string, buffer: ArrayBuffer | string) {
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.
*/
set(name: string, buffer: ArrayBuffer | string) {
if (this.readonly) {
return false;
}
return this.archive.set(name, buffer);
}
/**
* A shortcut to the internal archive function.
*/
get(name: string) {
return this.archive.get(name);
}
/**
* Get the map's script.
*/
getScriptFile() {
return this.get('war3map.j') || this.get('scripts\\war3map.j') || this.get('war3map.lua') || this.get('scripts\\war3map.lua');
}
/**
* A shortcut to the internal archive function.
*/
has(name: string) {
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.
*/
delete(name: string) {
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.
*/
rename(name: string, newName: string) {
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;
}
/**
* 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 and parse the trigger file.
*/
readTriggers(triggerData: TriggerData) {
let file = this.archive.get('war3map.wtg');
if (file) {
let buffer = file.arrayBuffer();
if (buffer) {
let object = new War3MapWtg();
object.load(buffer, triggerData);
return object;
}
}
}
/**
* Read and parse the custom text trigger file.
*/
readCustomTextTriggers() {
let file = this.archive.get('war3map.wct');
if (file) {
let buffer = file.arrayBuffer();
if (buffer) {
let object = new War3MapWct();
object.load(buffer);
return object;
}
}
}
/**
* Read and parse the string table file.
*/
readStringTable() {
let file = this.archive.get('war3map.wts');
if (file) {
let buffer = file.text();
if (buffer) {
let object = new War3MapWts();
object.load(buffer);
return object;
}
}
}
/**
* Read and parse all of the modification tables.
*/
readModifications() {
let modifications: War3MapModifications = {};
// useOptionalInts:
// w3u: no (units)
// w3t: no (items)
// w3b: no (destructables)
// w3d: yes (doodads)
// w3a: yes (abilities)
// w3h: no (buffs)
// w3q: yes (upgrades)
let fileNames: War3MapModificationNames[] = ['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();
if (buffer) {
let modification;
if (useOptionalInts[i]) {
modification = new War3MapW3d();
} else {
modification = new War3MapW3u();
}
modification.load(buffer);
modifications[fileNames[i]] = modification;
}
}
}
return modifications;
}
}