From bfae8b91d8585eca1cb3d668b9cb8bbda19f3bcc Mon Sep 17 00:00:00 2001 From: RandomityGuy <31925790+RandomityGuy@users.noreply.github.com> Date: Sat, 12 Jun 2021 22:06:10 +0530 Subject: [PATCH] Add mis parser --- src/Main.hx | 6 + src/Util.hx | 17 ++ src/mis/MisFile.hx | 14 +- src/mis/MisParser.hx | 352 +++++++++++++++++++++++++++++++++++++- src/mis/MissionElement.hx | 4 +- 5 files changed, 384 insertions(+), 9 deletions(-) diff --git a/src/Main.hx b/src/Main.hx index 1a890bbc..654123bf 100644 --- a/src/Main.hx +++ b/src/Main.hx @@ -1,5 +1,7 @@ package; +import mis.MisParser; +import sys.io.File; import shapes.EndPad; import shapes.LandMine; import shapes.StartPad; @@ -57,6 +59,10 @@ class Main extends hxd.App { tform.setPosition(new Vector(0, 0, 0)); db.setTransform(tform); + var ltr = File.getContent("data/missions/beginner/finale.mis"); + var mfp = new MisParser(ltr); + var mis = mfp.parse(); + // var pi = new PathedInterior(); // DifBuilder.loadDif("data/interiors/addon/smallplatform.dif", loader, pi); // var pim = pi.getTransform(); diff --git a/src/Util.hx b/src/Util.hx index 70e0cfdc..8da95356 100644 --- a/src/Util.hx +++ b/src/Util.hx @@ -139,4 +139,21 @@ class Util { } return -1; } + + public static function indexIsInStringLiteral(str:String, index:Int, strLiteralToken = '"') { + var inString = false; + for (i in 0...str.length) { + var c = str.charAt(i); + if (inString) { + if (i == index) + return true; + if (c == strLiteralToken && str.charAt(i - 1) != '\\') + inString = false; + continue; + } + if (c == strLiteralToken) + inString = true; + } + return false; + } } diff --git a/src/mis/MisFile.hx b/src/mis/MisFile.hx index 9b6e520b..5b79bc8a 100644 --- a/src/mis/MisFile.hx +++ b/src/mis/MisFile.hx @@ -1,3 +1,15 @@ package mis; -class MisFile {} +import mis.MissionElement.MissionElementSimGroup; + +@:publicFields +class MisFile { + var root:MissionElementSimGroup; + + /** The custom marble attributes overrides specified in the file. */ + var marbleAttributes:Map; + + var activatedPackages:Array; + + public function new() {} +} diff --git a/src/mis/MisParser.hx b/src/mis/MisParser.hx index 5bdaab44..e5fcaeb6 100644 --- a/src/mis/MisParser.hx +++ b/src/mis/MisParser.hx @@ -1,5 +1,22 @@ package mis; +import haxe.Exception; +import mis.MissionElement.MissionElementPathedInterior; +import mis.MissionElement.MissionElementPath; +import mis.MissionElement.MissionElementSimGroup; +import mis.MissionElement.MissionElementBase; +import mis.MissionElement.MissionElementParticleEmitterNode; +import mis.MissionElement.MissionElementTSStatic; +import mis.MissionElement.MissionElementMessageVector; +import mis.MissionElement.MissionElementAudioProfile; +import mis.MissionElement.MissionElementTrigger; +import mis.MissionElement.MissionElementMarker; +import mis.MissionElement.MissionElementItem; +import mis.MissionElement.MissionElementStaticShape; +import mis.MissionElement.MissionElementInteriorInstance; +import mis.MissionElement.MissionElementSun; +import mis.MissionElement.MissionElementSky; +import mis.MissionElement.MissionElementMissionArea; import mis.MissionElement.MissionElementType; import mis.MissionElement.MissionElementScriptObject; import src.Util; @@ -23,7 +40,174 @@ class MisParser { this.text = text; } - public function parse() {} + public function parse() { + var objectWriteBeginIndex = this.text.indexOf("//--- OBJECT WRITE BEGIN ---"); + var objectWriteEndIndex = this.text.lastIndexOf("//--- OBJECT WRITE END ---"); + + var outsideText = this.text.substring(0, objectWriteBeginIndex) + this.text.substring(objectWriteEndIndex); + + // Find all specified variables + this.variables = ["$usermods" => '""']; // Just make $usermods point to nothing + + var startText = outsideText; + + while (assignmentRegEx.match(startText)) { + if (!this.variables.exists(assignmentRegEx.matched(1))) + this.variables.set(assignmentRegEx.matched(1), assignmentRegEx.matched(2)); + startText = assignmentRegEx.matchedRight(); + } + + var marbleAttributes = new Map(); + + startText = outsideText; + + while (marbleAttributesRegEx.match(startText)) { + marbleAttributes.set(marbleAttributesRegEx.matched(1), this.resolveExpression(marbleAttributesRegEx.matched(2))); + startText = marbleAttributesRegEx.matchedRight(); + } + + var activatedPackages = []; + startText = outsideText; + + while (activatePackageRegEx.match(startText)) { + activatedPackages.push(this.resolveExpression(activatePackageRegEx.matched(1))); + startText = marbleAttributesRegEx.matchedRight(); + } + + if (objectWriteBeginIndex != -1 && objectWriteEndIndex != -1) { + this.text = this.text.substring(objectWriteBeginIndex, objectWriteEndIndex); + } + + var currentIndex = 0; + while (true) { + // blockCommentRegEx.lastIndex = currentIndex; + // lineCommentRegEx.lastIndex = currentIndex; + + var blockMatch = blockCommentRegEx.matchSub(this.text, currentIndex); + var lineMatch = lineCommentRegEx.matchSub(this.text, currentIndex); + + // The detected "comment" might be inside a string literal, in which case we ignore it 'cause it ain't no comment. + if (blockMatch && Util.indexIsInStringLiteral(this.text, blockCommentRegEx.matchedPos().pos)) + blockMatch = false; + if (lineMatch && Util.indexIsInStringLiteral(this.text, lineCommentRegEx.matchedPos().pos)) + lineMatch = false; + + if (!blockMatch && !lineMatch) + break; + else if (!lineMatch || (blockMatch && lineMatch && blockCommentRegEx.matchedPos().pos < lineCommentRegEx.matchedPos().pos)) { + this.text = this.text.substring(0, blockCommentRegEx.matchedPos().pos) + + this.text.substring(blockCommentRegEx.matchedPos().pos + blockCommentRegEx.matchedPos().len); + currentIndex += blockCommentRegEx.matchedPos().pos; + } else { + this.text = this.text.substring(0, lineCommentRegEx.matchedPos().pos) + + this.text.substring(lineCommentRegEx.matchedPos().pos + lineCommentRegEx.matchedPos().len); + currentIndex += lineCommentRegEx.matchedPos().pos; + } + } + + var elements = []; + while (this.hasNextElement()) { + var element = this.readElement(); + if (element == null) + continue; + elements.push(element); + } + + if (elements.length != 1) { + // We expect there to be only one outer element; the MissionGroup SimGroup. + trace(elements); + throw new Exception("Mission file doesn't have exactly 1 outer element!"); + } + + var mf = new MisFile(); + mf.root = cast elements[0]; + mf.marbleAttributes = marbleAttributes; + mf.activatedPackages = activatedPackages; + return mf; + } + + function readElement() { + // Get information about the head + // elementHeadRegEx.lastIndex = this.index; + var head = elementHeadRegEx.match(this.text.substring(this.index)); + this.index += elementHeadRegEx.matchedPos().pos + elementHeadRegEx.matchedPos().len; + var type = elementHeadRegEx.matched(1); + var name = elementHeadRegEx.matched(2); + var element:MissionElementBase = null; + switch (type) { + case "SimGroup": + element = this.readSimGroup(name); + case "ScriptObject": + element = this.readScriptObject(name); + case "MissionArea": + element = this.readMissionArea(name); + case "Sky": + element = this.readSky(name); + case "Sun": + element = this.readSun(name); + case "InteriorInstance": + element = this.readInteriorInstance(name); + case "StaticShape": + element = this.readStaticShape(name); + case "Item": + element = this.readItem(name); + case "Path": + element = this.readPath(name); + case "Marker": + element = this.readMarker(name); + case "PathedInterior": + element = this.readPathedInterior(name); + case "Trigger": + element = this.readTrigger(name); + case "AudioProfile": + element = this.readAudioProfile(name); + case "MessageVector": + element = this.readMessageVector(name); + case "TSStatic": + element = this.readTSStatic(name); + case "ParticleEmitterNode": + element = this.readParticleEmitterNode(name); + default: + trace("Unknown element type! " + type); + // Still advance the index + var endingBraceIndex = Util.indexOfIgnoreStringLiterals(this.text, '};', this.index); + if (endingBraceIndex == -1) + endingBraceIndex = this.text.length; + this.index = endingBraceIndex + 2; + } + if (element != null) + element._id = this.currentElementId++; + return element; + } + + function hasNextElement() { + if (!elementHeadRegEx.match(this.text.substring(this.index))) + return false; + if (Util.indexOfIgnoreStringLiterals(this.text.substring(this.index, this.index + elementHeadRegEx.matchedPos().pos), '}') != -1) + return false; + return true; + } + + function readSimGroup(name:String) { + var elements = []; + // Read in all elements + while (this.hasNextElement()) { + var element = this.readElement(); + if (element == null) + continue; + elements.push(element); + } + var endingBraceIndex = Util.indexOfIgnoreStringLiterals(this.text, '};', this.index); + if (endingBraceIndex == -1) + endingBraceIndex = this.text.length; + this.index = endingBraceIndex + 2; + + var sg = new MissionElementSimGroup(); + sg._name = name; + sg._type = MissionElementType.SimGroup; + sg.elements = elements; + return sg; + } function readValues() { // Values are either strings or string arrays. @@ -65,21 +249,175 @@ class MisParser { return obj; } - function readScriptObject(name:String) { - var obj = new MissionElementScriptObject(); - obj._type = MissionElementType.ScriptObject; - obj._name = name; + function copyFields(obj:Dynamic) { var values = this.readValues(); + var objfields = Type.getInstanceFields(Type.getClass(obj)); for (key => value in values) { if (value.length > 1) { for (i in 0...value.length) { - Reflect.setField(obj, '${key}${i}', value[i]); + var fname = '${key}${i}'; + if (objfields.contains(fname)) + Reflect.setField(obj, fname, value[i]); } } else { - Reflect.setField(obj, key, value[0]); + if (key == "static") + key = "isStatic"; + if (objfields.contains(key)) + Reflect.setField(obj, key, value[0]); } } + } + + function readScriptObject(name:String) { + var obj = new MissionElementScriptObject(); + obj._type = MissionElementType.ScriptObject; + obj._name = name; + + copyFields(obj); + + return obj; + } + + function readMissionArea(name:String) { + var obj = new MissionElementMissionArea(); + obj._type = MissionElementType.MissionArea; + obj._name = name; + + copyFields(obj); + + return obj; + } + + function readSky(name:String) { + var obj = new MissionElementSky(); + obj._type = MissionElementType.Sky; + obj._name = name; + + copyFields(obj); + + return obj; + } + + function readSun(name:String) { + var obj = new MissionElementSun(); + obj._type = MissionElementType.Sun; + obj._name = name; + + copyFields(obj); + + return obj; + } + + function readInteriorInstance(name:String) { + var obj = new MissionElementInteriorInstance(); + obj._type = MissionElementType.InteriorInstance; + obj._name = name; + + copyFields(obj); + + return obj; + } + + function readStaticShape(name:String) { + var obj = new MissionElementStaticShape(); + obj._type = MissionElementType.StaticShape; + obj._name = name; + + copyFields(obj); + + return obj; + } + + function readItem(name:String) { + var obj = new MissionElementItem(); + obj._type = MissionElementType.Item; + obj._name = name; + + copyFields(obj); + + return obj; + } + + function readPath(name:String) { + var sg:MissionElementSimGroup = cast this.readSimGroup(name); + var obj = new MissionElementPath(); + obj._type = MissionElementType.Path; + obj._name = name; + obj.markers = sg.elements.map(x -> cast x); + obj.markers.sort((a, b) -> cast MisParser.parseNumber(a.seqnum) - MisParser.parseNumber(b.seqnum)); + copyFields(obj); + + return obj; + } + + function readMarker(name:String) { + var obj = new MissionElementMarker(); + obj._type = MissionElementType.Marker; + obj._name = name; + + copyFields(obj); + + return obj; + } + + function readPathedInterior(name:String) { + var obj = new MissionElementPathedInterior(); + obj._type = MissionElementType.PathedInterior; + obj._name = name; + + copyFields(obj); + + return obj; + } + + function readTrigger(name:String) { + var obj = new MissionElementTrigger(); + obj._type = MissionElementType.Trigger; + obj._name = name; + + copyFields(obj); + + return obj; + } + + function readAudioProfile(name:String) { + var obj = new MissionElementAudioProfile(); + obj._type = MissionElementType.AudioProfile; + obj._name = name; + + copyFields(obj); + + return obj; + } + + function readMessageVector(name:String) { + var obj = new MissionElementMessageVector(); + obj._type = MissionElementType.MessageVector; + obj._name = name; + + copyFields(obj); + + return obj; + } + + function readTSStatic(name:String) { + var obj = new MissionElementTSStatic(); + obj._type = MissionElementType.TSStatic; + obj._name = name; + + copyFields(obj); + + return obj; + } + + function readParticleEmitterNode(name:String) { + var obj = new MissionElementParticleEmitterNode(); + obj._type = MissionElementType.ParticleEmitterNode; + obj._name = name; + + copyFields(obj); + return obj; } diff --git a/src/mis/MissionElement.hx b/src/mis/MissionElement.hx index c1e5c2a7..26bd34fe 100644 --- a/src/mis/MissionElement.hx +++ b/src/mis/MissionElement.hx @@ -75,7 +75,9 @@ class MissionElementSky extends MissionElementBase { var position:String; var rotation:String; var scale:String; - var cloudheightper:Array; + var cloudheightper0:String; + var cloudheightper1:String; + var cloudheightper2:String; var cloudspeed1:String; var cloudspeed2:String; var cloudspeed3:String;