UNPKG

plaxtony

Version:

Static code analysis of SC2 Galaxy Script

576 lines 18.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.XMLReader = exports.TriggerStore = exports.Library = exports.ElementContainer = exports.Label = exports.Category = exports.FunctionCall = exports.Param = exports.PresetValue = exports.Preset = exports.FunctionDef = exports.ParamDef = exports.Element = exports.Tag = exports.ParameterType = exports.ElementReference = exports.ElementFlag = void 0; const xml = require("xml2js"); var ElementFlag; (function (ElementFlag) { ElementFlag[ElementFlag["Native"] = 2] = "Native"; ElementFlag[ElementFlag["FuncAction"] = 4] = "FuncAction"; ElementFlag[ElementFlag["FuncCall"] = 8] = "FuncCall"; ElementFlag[ElementFlag["Event"] = 16] = "Event"; ElementFlag[ElementFlag["Template"] = 32] = "Template"; ElementFlag[ElementFlag["PresetGenConstVar"] = 64] = "PresetGenConstVar"; ElementFlag[ElementFlag["PresetCustom"] = 128] = "PresetCustom"; ElementFlag[ElementFlag["CustomScript"] = 256] = "CustomScript"; ElementFlag[ElementFlag["Operator"] = 512] = "Operator"; ElementFlag[ElementFlag["CustomAI"] = 1024] = "CustomAI"; ElementFlag[ElementFlag["SubFunctions"] = 2048] = "SubFunctions"; ElementFlag[ElementFlag["AllowBreak"] = 4096] = "AllowBreak"; ElementFlag[ElementFlag["Hidden"] = 8192] = "Hidden"; ElementFlag[ElementFlag["NoScriptPrefix"] = 16384] = "NoScriptPrefix"; ElementFlag[ElementFlag["Deprecated"] = 32768] = "Deprecated"; ElementFlag[ElementFlag["Internal"] = 65536] = "Internal"; ElementFlag[ElementFlag["Restricted"] = 131072] = "Restricted"; })(ElementFlag = exports.ElementFlag || (exports.ElementFlag = {})); class ElementReference { constructor(container) { this.store = container; } link() { return this.type.name + '/' + this.id; } globalLink() { let link = this.link(); if (this.library) { link = this.library + '/' + link; } return link; } resolve() { if (this.library) { const lib = this.store.getLibraries().get(this.library); return lib.findElementById(this.link(), null); } return this.store.findElementById(this.link(), null); // return this.container.findElementById(this.id, this.type) as T; } } exports.ElementReference = ElementReference; class ParameterType { get galaxyType() { switch (this.type) { case 'anygamelink': case 'gamelink': case 'filepath': case 'actormsg': case 'aidef': case 'modelanim': case 'attributegame': case 'attributeplayer': case 'attributevalue': case 'catalogentry': case 'catalogfieldname': case 'catalogfieldpath': case 'reference': case 'catalogscope': case 'charge': case 'convcharacter': case 'convline': case 'convstateindex': case 'conversationtag': case 'cooldown': case 'fontstyle': case 'gameoption': case 'gameoptionvalue': case 'modelcamera': case 'timeofday': case 'layoutframe': case 'layoutframerel': case 'userfield': case 'userinstance': case 'water': { return 'string'; } case 'aidefwave': case 'cinematic': case 'conversation': case 'reply': case 'datatable': case 'dialog': case 'control': case 'difficulty': case 'objective': case 'path': case 'ping': case 'planet': case 'playercolor': case 'portrait': case 'transmission': { return 'int'; } case 'animlengthquery': { return 'generichandle'; } case 'targetfilter': { return 'unitfilter'; } case 'preset': return this.typeElement.resolve().baseType; default: return this.type; } } } exports.ParameterType = ParameterType; class Tag { link() { return (this.libId ? this.libId + '/' : '') + (this.constructor.name + '/') + this.id; } toString() { const parts = []; if (this.libId) { parts.push('lib' + this.libId); } const prefix = this.constructor.prefix; if (prefix) { parts.push(prefix); } parts.push(this.name ? this.name : this.id); return parts.join('_'); } textKey(kind) { const parts = []; parts.push(this.constructor.name); parts.push(kind); if (this.libId) { parts.push(['lib', this.libId, this.id].join('_')); } else { parts.push(this.id); } return parts.join('/'); } } exports.Tag = Tag; class Element extends Tag { constructor() { super(...arguments); this.items = []; } toString() { if (this.flags & 2 /* Native */) { return this.name ? this.name : this.id; } return super.toString(); } } exports.Element = Element; class ParamDef extends Element { get galaxyType() { const rtype = this.type.galaxyType; if (this.isReference) { switch (rtype) { case 'unit': { return 'unitref'; break; } } } return rtype; } } exports.ParamDef = ParamDef; class FunctionDef extends Element { constructor() { super(...arguments); this.parameters = []; this.eventResponses = []; } getParameters() { return this.parameters.map((paramRef) => { return paramRef.resolve(); }); } } exports.FunctionDef = FunctionDef; FunctionDef.prefix = 'gf'; class Preset extends Element { constructor() { super(...arguments); this.values = []; } } exports.Preset = Preset; class PresetValue extends Element { } exports.PresetValue = PresetValue; class Param extends Element { } exports.Param = Param; class FunctionCall extends Element { } exports.FunctionCall = FunctionCall; class Category extends Element { } exports.Category = Category; ; class Label extends Element { } exports.Label = Label; ; const ElementClasses = { ParamDef, FunctionDef, Preset, PresetValue, Param, FunctionCall, Category, Label, }; // export class TriggerExplorer { // protected containers: ElementContainer; // } class ElementContainer { constructor() { this.items = []; this.elements = new Map(); this.nameMap = new Map(); } addElement(identifier, el) { el.id = identifier; if (el instanceof FunctionDef) { this.nameMap.set(el.name, el); } this.elements.set(el.constructor.name + '/' + el.id, el); } findElementByName(name) { return this.nameMap.get(name); } findPresetValueByStr(value) { for (const el of this.elements.values()) { if (!(el instanceof PresetValue)) continue; if (el.value && el.value == value) { return el; } } return null; } findPresetByValue(value) { for (const el of this.elements.values()) { if (!(el instanceof Preset)) continue; const belongsTo = el.values.find((localVal) => { return value.id === localVal.id; }); if (belongsTo) { return el; } } return null; } findElementById(id, type) { if (type && type.name !== 'Element') { id = type.name + '/' + id; } return this.elements.get(id); } getElements() { return this.elements; } } exports.ElementContainer = ElementContainer; class Library extends ElementContainer { constructor(id) { super(); this.id = id; } addElement(identifier, el) { el.libId = this.id; super.addElement(identifier, el); } getId() { return this.id; } } exports.Library = Library; class TriggerStore extends ElementContainer { constructor() { super(...arguments); this.libraries = new Map(); this.unresolvedReferences = new Map(); } addElementReference(ref) { const link = ref.globalLink(); if (this.unresolvedReferences.has(link)) { const refList = this.unresolvedReferences.get(link); refList.push(ref); } else { this.unresolvedReferences.set(link, [ref]); } } addLibrary(library) { this.libraries.set(library.getId(), library); } // public findElementById<T extends Element>(elementId: string, libraryId?: string): T | undefined { // if (libraryId) { // return this.libraries.get(libraryId).findElementById(elementId) as T; // } // return super.findElementById(elementId) as T; // } getLibraries() { return this.libraries; } } exports.TriggerStore = TriggerStore; class XMLReader { constructor(container) { this.store = container; } parseReference(data, type) { const ref = new ElementReference(this.store); ref.id = data.$.Id; if (data.$.Library) { ref.library = data.$.Library; } ref.type = ElementClasses[type.name]; this.store.addElementReference(ref); return ref; } parseParam(item) { const element = new Param(); if (item.FunctionCall) { element.functionCall = this.parseReference(item.FunctionCall[0], FunctionCall); } if (item.Preset) { element.preset = this.parseReference(item.Preset[0], PresetValue); } if (item.Value) { element.value = item.Value[0]; } if (item.ValueType) { element.valueType = item.ValueType[0].$.Type; if (item.ValueGameType) { // item.ValueGameType[0].$.Type; } } if (item.ValueElement) { element.valueElement = this.parseReference(item.ValueElement[0], Preset); } return element; } parseParamDef(item) { const paramDef = new ParamDef(); paramDef.type = this.parseParameterType(item.ParameterType[0]); if (item.Default) { paramDef.default = this.parseReference(item.Default[0], Param); } if (item.ParamFlagReference) { paramDef.isReference = true; } return paramDef; } parseFunctionCall(item) { const element = new FunctionCall(); if (item.FunctionDef) { // TODO: check one of the void story libraries - it doesn't have FunctionDef element.functionDef = this.parseReference(item.FunctionDef[0], FunctionDef); } return element; } parseParameterType(item) { const element = new ParameterType(); element.type = item.Type[0].$.Value; if (element.type === 'gamelink' && item.GameType) { element.gameType = item.GameType[0].$.Value; } if (element.type === 'preset') { element.typeElement = this.parseReference(item.TypeElement[0], Preset); } return element; } parseCategory(item) { const element = new Category(); return element; } parseLabel(item) { const element = new Label(); if (item.Icon) { element.icon = item.Icon[0]; } if (item.Color) { element.color = item.Color[0]; } return element; } parseElement(item) { let el; switch (item.$.Type) { case 'FunctionDef': { const func = el = new FunctionDef(); func.flags |= item.FlagNative ? 2 /* Native */ : 0; func.flags |= item.FlagAction ? 4 /* FuncAction */ : 0; func.flags |= item.FlagCall ? 8 /* FuncCall */ : 0; func.flags |= item.FlagEvent ? 16 /* Event */ : 0; func.flags |= item.Template ? 32 /* Template */ : 0; func.flags |= item.FlagCustomScript ? 256 /* CustomScript */ : 0; func.flags |= item.FlagOperator ? 512 /* Operator */ : 0; func.flags |= item.FlagCustomAI ? 1024 /* CustomAI */ : 0; func.flags |= item.FlagSubFunctions ? 2048 /* SubFunctions */ : 0; func.flags |= item.FlagAllowBreak ? 4096 /* AllowBreak */ : 0; func.flags |= item.FlagHidden ? 8192 /* Hidden */ : 0; func.flags |= item.FlagNoScriptPrefix ? 16384 /* NoScriptPrefix */ : 0; func.flags |= item.Deprecated ? 32768 /* Deprecated */ : 0; func.flags |= item.Internal ? 65536 /* Internal */ : 0; func.flags |= item.FlagRestricted ? 131072 /* Restricted */ : 0; if (item.Parameter) { for (const param of item.Parameter) { if (param.$.Type === 'Comment') continue; func.parameters.push(this.parseReference(param, ParamDef)); } } if (item.ReturnType) { func.returnType = this.parseParameterType(item.ReturnType[0]); } if (item.ScriptCode) { func.scriptCode = item.ScriptCode[0]; const whitespace = func.scriptCode.match(/^\r?\n(\s+)/); if (whitespace) { func.scriptCode = func.scriptCode.trim().replace(/\r?\n/g, '\n').replace(new RegExp('^' + whitespace[1], 'gm'), ''); } } if (item.EventResponse) { for (const er of item.EventResponse) { func.eventResponses.push(this.parseReference(er, FunctionDef)); } } break; } case 'ParamDef': { el = this.parseParamDef(item); break; } case 'Param': el = this.parseParam(item); break; case 'FunctionCall': el = this.parseFunctionCall(item); break; case 'Preset': { const preset = el = new Preset(); preset.flags |= item.PresetGenConstVar ? 64 /* PresetGenConstVar */ : 0; preset.flags |= item.PresetCustom ? 128 /* PresetCustom */ : 0; preset.baseType = item.BaseType[0].$.Value; if (item.Item) { for (const val of item.Item) { if (val.$.Type !== 'PresetValue') continue; preset.values.push(this.parseReference(val, PresetValue)); } } // item.$.PresetCustom // item.$.PresetGenConstVar // item.$.PresetAsBits break; } case 'PresetValue': { const presetValue = el = new PresetValue(); if (item.Value) { presetValue.value = item.Value[0]; } break; } case 'Category': el = this.parseCategory(item); break; case 'Label': el = this.parseLabel(item); break; default: { return null; } } if (item.Item) { el.items = this.parseItems(item); } if (item.Label) { el.label = this.parseReference(item.Label[0], Label); } if (item.Identifier) { el.name = item.Identifier[0]; } return el; } parseItems(data) { const elItems = []; for (const itEntry of data.Item) { const cls = ElementClasses[itEntry.$.Type]; if (!cls) continue; elItems.push(this.parseReference(itEntry, cls)); } return elItems; } parseTree(data, container) { if (data.Root && data.Root[0].Item) { container.items = this.parseItems(data.Root[0]); } if (!data.Element) return; for (const item of data.Element) { const el = this.parseElement(item); if (el) { container.addElement(item.$.Id, el); } } } parseLibrary(id, data) { const lib = new Library(id); this.parseTree(data, lib); return lib; } loadXML(content) { return new Promise((resolve, reject) => { xml.parseString(content, (err, result) => { if (err) { reject(err); } else { try { resolve(result.TriggerData); } catch (err) { reject(err); } } }); }); } async loadLibrary(content) { const data = await this.loadXML(content); const lib = this.parseLibrary(data.Standard[0].$.Id, data); this.store.addLibrary(lib); return lib; } async load(content, onlyLibraries = false) { const data = await this.loadXML(content); if (data.Library) { for (const item of data.Library) { const lib = this.parseLibrary(item.$.Id, item); this.store.addLibrary(lib); } } if (!onlyLibraries) { this.parseTree(data, this.store); } return this.store; } } exports.XMLReader = XMLReader; // export function forEachElement(element: Element) { // function visitElement() // } //# sourceMappingURL=trigger.js.map