mirror of
https://github.com/RandomityGuy/MBHaxe.git
synced 2026-04-27 21:21:41 +00:00
Mis parser start
This commit is contained in:
parent
c7b365cdcf
commit
4f9a0066f0
4 changed files with 520 additions and 0 deletions
62
src/Util.hx
62
src/Util.hx
|
|
@ -77,4 +77,66 @@ class Util {
|
||||||
}
|
}
|
||||||
bitmap.unlock();
|
bitmap.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function splitIgnoreStringLiterals(str:String, splitter:String, strLiteralToken = '"') {
|
||||||
|
var indices = [];
|
||||||
|
var inString = false;
|
||||||
|
for (i in 0...str.length) {
|
||||||
|
var c = str.charAt(i);
|
||||||
|
if (inString) {
|
||||||
|
if (c == strLiteralToken && str.charAt(i - 1) != '\\')
|
||||||
|
inString = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (c == strLiteralToken)
|
||||||
|
inString = true;
|
||||||
|
else if (c == splitter)
|
||||||
|
indices.push(i);
|
||||||
|
}
|
||||||
|
var parts = [];
|
||||||
|
var remaining = str;
|
||||||
|
for (i in 0...indices.length) {
|
||||||
|
var index = indices[i] - (str.length - remaining.length);
|
||||||
|
var part = remaining.substring(0, index);
|
||||||
|
remaining = remaining.substring(index + 1);
|
||||||
|
parts.push(part);
|
||||||
|
}
|
||||||
|
parts.push(remaining);
|
||||||
|
return parts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function unescape(str:String) {
|
||||||
|
var specialCases = [
|
||||||
|
'\\t' => '\t',
|
||||||
|
'\\v' => '\x0B',
|
||||||
|
'\\0' => '\x00',
|
||||||
|
'\\f' => '\x0C',
|
||||||
|
'\\n' => '\n',
|
||||||
|
'\\r' => '\r'
|
||||||
|
];
|
||||||
|
|
||||||
|
for (obj => esc in specialCases) {
|
||||||
|
str = StringTools.replace(str, obj, esc);
|
||||||
|
}
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gets the index of a substring like String.prototype.indexOf, but only if that index lies outside of string literals. */
|
||||||
|
public static function indexOfIgnoreStringLiterals(str:String, searchString:String, position = 0, strLiteralToken = '"') {
|
||||||
|
var inString = false;
|
||||||
|
for (i in position...str.length) {
|
||||||
|
var c = str.charAt(i);
|
||||||
|
if (inString) {
|
||||||
|
if (c == strLiteralToken && str.charAt(i - 1) != '\\')
|
||||||
|
inString = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (c == strLiteralToken)
|
||||||
|
inString = true;
|
||||||
|
else if (StringTools.startsWith(str.substr(i), searchString))
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
3
src/mis/MisFile.hx
Normal file
3
src/mis/MisFile.hx
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
package mis;
|
||||||
|
|
||||||
|
class MisFile {}
|
||||||
178
src/mis/MisParser.hx
Normal file
178
src/mis/MisParser.hx
Normal file
|
|
@ -0,0 +1,178 @@
|
||||||
|
package mis;
|
||||||
|
|
||||||
|
import mis.MissionElement.MissionElementType;
|
||||||
|
import mis.MissionElement.MissionElementScriptObject;
|
||||||
|
import src.Util;
|
||||||
|
import h3d.Vector;
|
||||||
|
import h3d.Quat;
|
||||||
|
|
||||||
|
final elementHeadRegEx = ~/new (\w+)\((\w*)\) *{/g;
|
||||||
|
final blockCommentRegEx = ~/\/\*(.|\n)*?\*\//g;
|
||||||
|
final lineCommentRegEx = ~/\/\/.*/g;
|
||||||
|
final assignmentRegEx = ~/(\$(?:\w|\d)+)\s*=\s*(.+?);/g;
|
||||||
|
final marbleAttributesRegEx = ~/setMarbleAttributes\("(\w+)",\s*(.+?)\);/g;
|
||||||
|
final activatePackageRegEx = ~/activatePackage\((.+?)\);/g;
|
||||||
|
|
||||||
|
class MisParser {
|
||||||
|
var text:String;
|
||||||
|
var index = 0;
|
||||||
|
var currentElementId = 0;
|
||||||
|
var variables:Map<String, String>;
|
||||||
|
|
||||||
|
public function new(text:String) {
|
||||||
|
this.text = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function parse() {}
|
||||||
|
|
||||||
|
function readValues() {
|
||||||
|
// Values are either strings or string arrays.
|
||||||
|
var obj:Map<String, Array<String>> = new Map();
|
||||||
|
var endingBraceIndex = Util.indexOfIgnoreStringLiterals(this.text, '};', this.index);
|
||||||
|
if (endingBraceIndex == -1)
|
||||||
|
endingBraceIndex = this.text.length;
|
||||||
|
var section = StringTools.trim(this.text.substring(this.index, endingBraceIndex));
|
||||||
|
var statements = Util.splitIgnoreStringLiterals(section, ';').map(x -> StringTools.trim(x)); // Get a list of all statements
|
||||||
|
for (statement in statements) {
|
||||||
|
if (statement == null || statement == "")
|
||||||
|
continue;
|
||||||
|
var splitIndex = statement.indexOf('=');
|
||||||
|
if (splitIndex == -1)
|
||||||
|
continue;
|
||||||
|
var parts = [statement.substring(0, splitIndex), statement.substring(splitIndex + 1)].map((part) -> StringTools.trim(part));
|
||||||
|
if (parts.length != 2)
|
||||||
|
continue;
|
||||||
|
var key = parts[0];
|
||||||
|
key = key.toLowerCase(); // TorqueScript is case-insensitive here
|
||||||
|
if (StringTools.endsWith(key, ']')) {
|
||||||
|
// The key is specifying array data, so handle that case.
|
||||||
|
var openingIndex = key.indexOf('[');
|
||||||
|
var arrayName = key.substring(0, openingIndex);
|
||||||
|
var array:Array<String>;
|
||||||
|
if (obj.exists(arrayName))
|
||||||
|
array = obj.get(arrayName);
|
||||||
|
else {
|
||||||
|
array = [];
|
||||||
|
obj.set(arrayName, array);
|
||||||
|
} // Create a new array or use the existing one
|
||||||
|
var index = Std.parseInt(key.substring(openingIndex + 1, -1));
|
||||||
|
array[index] = this.resolveExpression(parts[1]);
|
||||||
|
} else {
|
||||||
|
obj.set(key, [this.resolveExpression(parts[1])]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.index = endingBraceIndex + 2;
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
function readScriptObject(name:String) {
|
||||||
|
var obj = new MissionElementScriptObject();
|
||||||
|
obj._type = MissionElementType.ScriptObject;
|
||||||
|
obj._name = name;
|
||||||
|
var values = this.readValues();
|
||||||
|
|
||||||
|
for (key => value in values) {
|
||||||
|
if (value.length > 1) {
|
||||||
|
for (i in 0...value.length) {
|
||||||
|
Reflect.setField(obj, '${key}${i}', value[i]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Reflect.setField(obj, key, value[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Resolves a TorqueScript rvalue expression. Currently only supports the concatenation @ operator. */
|
||||||
|
function resolveExpression(expr:String) {
|
||||||
|
var parts = Util.splitIgnoreStringLiterals(expr, ' @ ').map(x -> {
|
||||||
|
x = StringTools.trim(x);
|
||||||
|
if (StringTools.startsWith(x, '$ ') && this.variables[x] != null) {
|
||||||
|
// Replace the variable with its value
|
||||||
|
x = this.resolveExpression(this.variables[x]);
|
||||||
|
} else if (StringTools.startsWith(x, ' "') && StringTools.endsWith(x, '" ')) {
|
||||||
|
x = Util.unescape(x.substring(1, x.length - 2)); // It' s a string literal, so remove " "
|
||||||
|
}
|
||||||
|
return x;
|
||||||
|
});
|
||||||
|
return parts.join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Parses a 4-component vector from a string of four numbers. */
|
||||||
|
public static function parseVector3(string:String) {
|
||||||
|
if (string == null)
|
||||||
|
return new Vector();
|
||||||
|
var parts = string.split(' ').map((part) -> Std.parseFloat(part));
|
||||||
|
|
||||||
|
if (parts.length < 3)
|
||||||
|
return new Vector();
|
||||||
|
if (parts.filter(x -> !Math.isFinite(x)).length != 0)
|
||||||
|
return new Vector();
|
||||||
|
return new Vector(parts[0], parts[1], parts[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Parses a 4-component vector from a string of four numbers. */
|
||||||
|
public static function parseVector4(string:String) {
|
||||||
|
if (string == null)
|
||||||
|
return new Vector();
|
||||||
|
var parts = string.split(' ').map((part) -> Std.parseFloat(part));
|
||||||
|
|
||||||
|
if (parts.length < 4)
|
||||||
|
return new Vector();
|
||||||
|
if (parts.filter(x -> !Math.isFinite(x)).length != 0)
|
||||||
|
return new Vector();
|
||||||
|
return new Vector(parts[0], parts[1], parts[2], parts[3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns a quaternion based on a rotation specified from 4 numbers. */
|
||||||
|
public static function parseRotation(string:String) {
|
||||||
|
if (string == null)
|
||||||
|
return new Quat();
|
||||||
|
var parts = string.split(' ').map((part) -> Std.parseFloat(part));
|
||||||
|
if (parts.length < 4)
|
||||||
|
return new Quat();
|
||||||
|
if (parts.filter(x -> !Math.isFinite(x)).length != 0)
|
||||||
|
return new Quat();
|
||||||
|
var quaternion = new Quat();
|
||||||
|
// The first 3 values represent the axis to rotate on, the last represents the negative angle in degrees.
|
||||||
|
quaternion.initRotateAxis(parts[0], parts[1], parts[2], -parts[3] * Math.PI / 180);
|
||||||
|
return quaternion;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Parses a numeric value. */
|
||||||
|
public static function parseNumber(string:String):Float {
|
||||||
|
if (string == null)
|
||||||
|
return 0;
|
||||||
|
// Strange thing here, apparently you can supply lists of numbers. In this case tho, we just take the first value.
|
||||||
|
var val = Std.parseFloat(string.split(',')[0]);
|
||||||
|
if (Math.isNaN(val))
|
||||||
|
return 0;
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Parses a list of space-separated numbers. */
|
||||||
|
public static function parseNumberList(string:String) {
|
||||||
|
var parts = string.split(' ');
|
||||||
|
var result = [];
|
||||||
|
for (part in parts) {
|
||||||
|
var number = Std.parseFloat(part);
|
||||||
|
if (!Math.isNaN(number)) {
|
||||||
|
// The number parsed without issues; simply add it to the array.
|
||||||
|
result.push(number);
|
||||||
|
} else {
|
||||||
|
// Since we got NaN, we assume the number did not parse correctly and we have a case where the space between multiple numbers are missing. So "0.0000000 1.0000000" turning into "0.00000001.0000000".
|
||||||
|
final assumedDecimalPlaces = 7; // Reasonable assumption
|
||||||
|
// Scan the part to try to find all numbers contained in it
|
||||||
|
while (part.length > 0) {
|
||||||
|
var dotIndex = part.indexOf('.');
|
||||||
|
if (dotIndex == -1)
|
||||||
|
break;
|
||||||
|
var section = part.substring(0, cast Math.min(dotIndex + assumedDecimalPlaces + 1, part.length));
|
||||||
|
result.push(Std.parseFloat(section));
|
||||||
|
part = part.substring(dotIndex + assumedDecimalPlaces + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
277
src/mis/MissionElement.hx
Normal file
277
src/mis/MissionElement.hx
Normal file
|
|
@ -0,0 +1,277 @@
|
||||||
|
package mis;
|
||||||
|
|
||||||
|
enum MissionElementType {
|
||||||
|
SimGroup;
|
||||||
|
ScriptObject;
|
||||||
|
MissionArea;
|
||||||
|
Sky;
|
||||||
|
Sun;
|
||||||
|
InteriorInstance;
|
||||||
|
StaticShape;
|
||||||
|
Item;
|
||||||
|
Path;
|
||||||
|
Marker;
|
||||||
|
PathedInterior;
|
||||||
|
Trigger;
|
||||||
|
AudioProfile;
|
||||||
|
MessageVector;
|
||||||
|
TSStatic;
|
||||||
|
ParticleEmitterNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@:publicFields
|
||||||
|
class MissionElementBase {
|
||||||
|
// Underscore prefix to avoid name clashes
|
||||||
|
|
||||||
|
/** The general type of the element. */
|
||||||
|
var _type:MissionElementType; /** The object name; specified in the () of the "constructor". */
|
||||||
|
|
||||||
|
var _name:String;
|
||||||
|
|
||||||
|
/** Is unique for every element in the mission file. */
|
||||||
|
var _id:Int;
|
||||||
|
}
|
||||||
|
|
||||||
|
@:publicFields
|
||||||
|
class MissionElementSimGroup extends MissionElementBase {
|
||||||
|
var elements:Array<MissionElementBase>;
|
||||||
|
|
||||||
|
public function new() {
|
||||||
|
_type = MissionElementType.SimGroup;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@:publicFields
|
||||||
|
/** Stores metadata about the mission. */
|
||||||
|
class MissionElementScriptObject extends MissionElementBase {
|
||||||
|
var time:String;
|
||||||
|
var name:String;
|
||||||
|
var desc:String;
|
||||||
|
var type:String;
|
||||||
|
var starthelptext:String;
|
||||||
|
var level:String;
|
||||||
|
var artist:String;
|
||||||
|
var goldtime:String;
|
||||||
|
|
||||||
|
public function new() {
|
||||||
|
_type = MissionElementType.ScriptObject;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@:publicFields
|
||||||
|
class MissionElementMissionArea extends MissionElementBase {
|
||||||
|
var area:String;
|
||||||
|
var flightceiling:String;
|
||||||
|
var flightceilingRange:String;
|
||||||
|
var locked:String;
|
||||||
|
|
||||||
|
public function new() {
|
||||||
|
_type = MissionElementType.MissionArea;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@:publicFields
|
||||||
|
class MissionElementSky extends MissionElementBase {
|
||||||
|
var position:String;
|
||||||
|
var rotation:String;
|
||||||
|
var scale:String;
|
||||||
|
var cloudheightper:Array<String>;
|
||||||
|
var cloudspeed1:String;
|
||||||
|
var cloudspeed2:String;
|
||||||
|
var cloudspeed3:String;
|
||||||
|
var visibledistance:String;
|
||||||
|
var useskytextures:String;
|
||||||
|
var renderbottomtexture:String;
|
||||||
|
var skysolidcolor:String;
|
||||||
|
var fogdistance:String;
|
||||||
|
var fogcolor:String;
|
||||||
|
var fogvolume1:String;
|
||||||
|
var fogvolume2:String;
|
||||||
|
var fogvolume3:String;
|
||||||
|
var materiallist:String;
|
||||||
|
var windvelocity:String;
|
||||||
|
var windeffectprecipitation:String;
|
||||||
|
var norenderbans:String;
|
||||||
|
var fogvolumecolor1:String;
|
||||||
|
var fogvolumecolor2:String;
|
||||||
|
var fogvolumecolor3:String;
|
||||||
|
|
||||||
|
public function new() {
|
||||||
|
_type = MissionElementType.Sky;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@:publicFields
|
||||||
|
/** Stores information about the lighting direction and color. */
|
||||||
|
class MissionElementSun extends MissionElementBase {
|
||||||
|
var direction:String;
|
||||||
|
var color:String;
|
||||||
|
var ambient:String;
|
||||||
|
|
||||||
|
public function new() {
|
||||||
|
_type = MissionElementType.Sun;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@:publicFields
|
||||||
|
/** Represents a static (non-moving) interior instance. */
|
||||||
|
class MissionElementInteriorInstance extends MissionElementBase {
|
||||||
|
var position:String;
|
||||||
|
var rotation:String;
|
||||||
|
var scale:String;
|
||||||
|
var interiorfile:String;
|
||||||
|
var showterraininside:String;
|
||||||
|
|
||||||
|
public function new() {
|
||||||
|
_type = MissionElementType.InteriorInstance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@:publicFields
|
||||||
|
/** Represents a static shape. */
|
||||||
|
class MissionElementStaticShape extends MissionElementBase {
|
||||||
|
var position:String;
|
||||||
|
var rotation:String;
|
||||||
|
var scale:String;
|
||||||
|
var datablock:String;
|
||||||
|
var resettime:Null<String>;
|
||||||
|
var timeout:Null<String>;
|
||||||
|
|
||||||
|
public function new() {
|
||||||
|
_type = MissionElementType.StaticShape;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@:publicFields
|
||||||
|
/** Represents an item. */
|
||||||
|
class MissionElementItem extends MissionElementBase {
|
||||||
|
var position:String;
|
||||||
|
var rotation:String;
|
||||||
|
var scale:String;
|
||||||
|
var datablock:String;
|
||||||
|
var collideable:String;
|
||||||
|
var isStatic:String;
|
||||||
|
var rotate:String;
|
||||||
|
var showhelponpickup:String;
|
||||||
|
var timebonus:Null<String>;
|
||||||
|
|
||||||
|
public function new() {
|
||||||
|
_type = MissionElementType.Item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@:publicFields
|
||||||
|
/** Holds the markers used for the path of a pathed interior. */
|
||||||
|
class MissionElementPath extends MissionElementBase {
|
||||||
|
var markers:Array<MissionElementMarker>;
|
||||||
|
|
||||||
|
public function new() {
|
||||||
|
_type = MissionElementType.Path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@:publicFields
|
||||||
|
/** One keyframe in a pathed interior path. */
|
||||||
|
class MissionElementMarker extends MissionElementBase {
|
||||||
|
var position:String;
|
||||||
|
var rotation:String;
|
||||||
|
var scale:String;
|
||||||
|
var seqnum:String;
|
||||||
|
var mstonext:String;
|
||||||
|
|
||||||
|
/** Either Linear; Accelerate or Spline. */
|
||||||
|
var smoothingtype:String;
|
||||||
|
|
||||||
|
public function new() {
|
||||||
|
_type = MissionElementType.Marker;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@:publicFields
|
||||||
|
/** Represents a moving interior. */
|
||||||
|
class MissionElementPathedInterior extends MissionElementBase {
|
||||||
|
var position:String;
|
||||||
|
var rotation:String;
|
||||||
|
var scale:String;
|
||||||
|
var datablock:String;
|
||||||
|
var interiorresource:String;
|
||||||
|
var interiorindex:String;
|
||||||
|
var baseposition:String;
|
||||||
|
var baserotation:String;
|
||||||
|
var basescale:String;
|
||||||
|
// These two following values are a bit weird. See usage for more explanation.
|
||||||
|
var initialtargetposition:String;
|
||||||
|
var initialposition:String;
|
||||||
|
|
||||||
|
public function new() {
|
||||||
|
_type = MissionElementType.PathedInterior;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@:publicFields
|
||||||
|
/** Represents a trigger area used for out-of-bounds and help. */
|
||||||
|
class MissionElementTrigger extends MissionElementBase {
|
||||||
|
var position:String;
|
||||||
|
var rotation:String;
|
||||||
|
var scale:String;
|
||||||
|
var datablock:String;
|
||||||
|
|
||||||
|
/** A list of 12 Strings representing 4 vectors. The first vector corresponds to the origin point of the cuboid; the other three are the side vectors. */
|
||||||
|
var polyhedron:String;
|
||||||
|
|
||||||
|
var text:Null<String>;
|
||||||
|
var targettime:Null<String>;
|
||||||
|
var instant:Null<String>;
|
||||||
|
var icontinuetottime:Null<String>;
|
||||||
|
|
||||||
|
public function new() {
|
||||||
|
_type = MissionElementType.Trigger;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@:publicFields
|
||||||
|
/** Represents the song choice. */
|
||||||
|
class MissionElementAudioProfile extends MissionElementBase {
|
||||||
|
var filename:String;
|
||||||
|
var description:String;
|
||||||
|
var preload:String;
|
||||||
|
|
||||||
|
public function new() {
|
||||||
|
_type = MissionElementType.AudioProfile;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@:publicFields
|
||||||
|
class MissionElementMessageVector extends MissionElementBase {
|
||||||
|
public function new() {
|
||||||
|
_type = MissionElementType.MessageVector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@:publicFields
|
||||||
|
/** Represents a static; unmoving; unanimated DTS shape. They're pretty dumb; tbh. */
|
||||||
|
class MissionElementTSStatic extends MissionElementBase {
|
||||||
|
var position:String;
|
||||||
|
var rotation:String;
|
||||||
|
var scale:String;
|
||||||
|
var shapename:String;
|
||||||
|
|
||||||
|
public function new() {
|
||||||
|
_type = MissionElementType.TSStatic;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@:publicFields
|
||||||
|
/** Represents a particle emitter. Currently unused by this port (these are really niche). */
|
||||||
|
class MissionElementParticleEmitterNode extends MissionElementBase {
|
||||||
|
var position:String;
|
||||||
|
var rotation:String;
|
||||||
|
var scale:String;
|
||||||
|
var datablock:String;
|
||||||
|
var emitter:String;
|
||||||
|
var velocity:String;
|
||||||
|
|
||||||
|
public function new() {
|
||||||
|
_type = MissionElementType.ParticleEmitterNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue