mdx-m3-viewer
Version:
A browser WebGL model viewer. Mainly focused on models of the games Warcraft 3 and Starcraft 2.
235 lines (184 loc) • 7.46 kB
text/typescript
import Trigger from '../../parsers/w3x/wtg/trigger';
import ECA from '../../parsers/w3x/wtg/eca';
import Parameter from '../../parsers/w3x/wtg/parameter';
import SubParameters from '../../parsers/w3x/wtg/subparameters';
import WeuData from './data';
import { createCustomScriptECA, convertSingleToMultiple, isConditionECA, ensureCustomScriptCodeSafety } from './utils';
import { convertFunctionCall } from './conversions';
import transformFunction from './transformations/functions';
import transformPreset from './transformations/presets';
export function processTrigger(data: WeuData, trigger: Trigger, callbacks: string[]) {
data.push(trigger);
let eventsAndConditions = [];
let actions = [];
for (let eca of trigger.ecas) {
let type = eca.type;
if (type === 0 || type === 1) {
eventsAndConditions.push(eca);
} else if (type === 2) {
actions.push(eca);
}
}
let outputEcas = [];
for (let eventOrCondition of eventsAndConditions) {
let result = processECA(data, eventOrCondition, callbacks);
if (result.convert) {
data.pop();
return result;
}
outputEcas.push(eventOrCondition);
}
for (let action of actions) {
let result = processECA(data, action, callbacks);
if (result.convert) {
let customScripts = convertFunctionCall(data, action, callbacks);
data.change('inlinecustomscript', result.reason, customScripts.map((eca) => eca.parameters[0].value).join('\n'));
outputEcas.push(...customScripts);
} else {
outputEcas.push(action);
}
}
trigger.ecas = ensureCustomScriptCodeSafety(outputEcas);
data.pop();
return { convert: false, reason: '' };
}
export function processECA(data: WeuData, eca: ECA, callbacks: string[]) {
data.push(eca);
// Test if this function call, or anything down its hierarchy, needs to be converted to custom script.
let result = processFunctionCall(data, eca, callbacks);
if (result.convert) {
let reason = result.reason;
// If conversion is needed, try first to see if this is a RoC control flow ECA, and convert it to its TFT equivalent.
// This includes things like IfThenElse (RoC) and IfThenElseMultiple (TFT).
// This allows to potentially only convert to custom script one part of the control flow block, rather than all of it.
if (convertSingleToMultiple(eca)) {
// If the test passes here (that is, false is returned), the TFT conversion allowed to handle the conversion down the hierarchy.
// In this case, this ECA no longer needs to be converted to custom script.
let result = processFunctionCall(data, eca, callbacks);
if (result.convert) {
data.pop();
return result;
} else {
data.change('singletomultiple', reason, eca.name);
}
} else {
data.pop();
return result;
}
}
let outputEcas = [];
// Test the child ECAs if there are any.
for (let child of eca.ecas) {
let result = processECA(data, child, callbacks);
if (result.convert) {
let customScripts;
// If this is a condition ECA, make a custom script condition.
if (isConditionECA(eca.name, child.group)) {
let condition = convertFunctionCall(data, child, callbacks)[0].parameters[0].value;
// Normally type 2 (function) ECAs have the call keyword prepended to them.
// If one was added, remove it now, since this is a condition.
if (condition.startsWith('call ')) {
condition = condition.slice(5);
}
let finalCondition;
let returnValue;
// IfThenElseMultiple and AndMultiple return false if any condition is false.
// OrMultiple needs to return true if any condition is true.
if (eca.name === 'OrMultiple') {
finalCondition = condition;
returnValue = 'true';
} else {
finalCondition = `not (${condition})`;
returnValue = 'false';
}
customScripts = [
createCustomScriptECA(`if ${finalCondition} then`),
createCustomScriptECA(`return ${returnValue}`),
createCustomScriptECA('endif'),
];
} else {
customScripts = convertFunctionCall(data, child, callbacks);
}
// All of the custom scripts should be in the same group as the original child.
for (let script of customScripts) {
script.group = child.group;
}
data.change('inlinecustomscript', result.reason, customScripts.map((eca) => eca.parameters[0].value).join('\n'));
outputEcas.push(...customScripts);
} else {
outputEcas.push(child);
}
}
eca.ecas = ensureCustomScriptCodeSafety(outputEcas);
data.pop();
return { convert: false, reason: '' };
}
function processFunctionCall(data: WeuData, object: ECA | SubParameters, callbacks: string[]) {
let name = object.name;
// Check if this object can be converted back to normal GUI.
// If it's already normal GUI, nothing will happen.
if (transformFunction(data, object)) {
data.change('inlinegui', name, object.name);
}
// If this function is not from normal GUI, it has to be converted.
if (!data.triggerData.isBaseFunction(object.type, object.name)) {
return { convert: true, reason: object.name };
}
// Check the parameters.
// Note that they will also be checked if GUI was inlined.
// This is needed, because the inline functions don't check the parameters, only move them around.
for (let parameter of object.parameters) {
// Check for custom presets.
if (parameter.type === 0 && data.triggerData.isCustomPreset(parameter.value)) {
let value = parameter.value;
if (transformPreset(data, parameter)) {
data.change('inlinepreset', value, parameter.value);
} else {
return { convert: true, reason: value };
}
}
let result = processParameter(data, parameter, callbacks);
if (result.convert) {
return result;
}
}
return { convert: false, reason: '' };
}
function processParameter(data: WeuData, parameter: Parameter, callbacks: string[]): { convert: boolean, reason: string } {
data.push(parameter);
let type = parameter.type;
let value = parameter.value;
if (type === 1) {
if (value.startsWith('gg_')) {
// Used to track global generated variables and their status.
data.updateGUIReference(value, true);
}
} else if (type === 2) {
let result = processSubParameters(data, <SubParameters>parameter.subParameters, callbacks);
if (result.convert) {
data.pop();
return result;
}
}
// If this is an array element, test the array index.
let index = parameter.arrayIndex;
if (index) {
let result = processParameter(data, index, callbacks);
if (result.convert) {
data.pop();
return result;
}
}
data.pop();
return { convert: false, reason: '' };
}
function processSubParameters(data: WeuData, subParameters: SubParameters, callbacks: string[]) {
data.push(subParameters);
let result = processFunctionCall(data, subParameters, callbacks);
if (result.convert) {
data.pop();
return result;
}
data.pop();
return { convert: false, reason: '' };
}