diff --git a/source/Plugins/Formats.OpenBve/Block.cs b/source/Plugins/Formats.OpenBve/Block.cs index 04b96797a..1721ff4ac 100644 --- a/source/Plugins/Formats.OpenBve/Block.cs +++ b/source/Plugins/Formats.OpenBve/Block.cs @@ -43,7 +43,7 @@ public abstract class Block where T1 : struct, Enum where T2 : struct, { internal readonly List> subBlocks; - internal readonly ConcurrentDictionary> keyValuePairs; + internal readonly ConcurrentDictionary>> keyValuePairs; internal readonly ConcurrentDictionary> indexedValues; @@ -124,11 +124,33 @@ public virtual List> ReadBlocks(T1[] blocks) public readonly string FileName; internal readonly HostInterface currentHost; + + protected bool TryPopValue(T2 key, out KeyValuePair value) + { + if (keyValuePairs.TryGetValue(key, out List> values)) + { + lock (values) + { + if (values.Count > 0) + { + value = values[0]; + values.RemoveAt(0); + if (values.Count == 0) + { + keyValuePairs.TryRemove(key, out _); + } + return true; + } + } + } + value = default; + return false; + } /// Unconditionally reads the specified string from the block public bool GetValue(T2 key, out string stringValue) { - if (keyValuePairs.TryRemove(key, out KeyValuePair value)) + if (TryPopValue(key, out var value)) { stringValue = value.Value; return true; @@ -138,9 +160,9 @@ public bool GetValue(T2 key, out string stringValue) } /// Unconditionally reads the specified boolean from the block - public bool GetValue(T2 key, out bool value) + public bool GetValue(T2 key, out bool value) { - if (keyValuePairs.TryRemove(key, out KeyValuePair s)) + if (TryPopValue(key, out var s)) { string ss = s.Value.ToLowerInvariant().Trim(); if (ss == "1" || ss == "true") @@ -163,7 +185,7 @@ public bool GetValue(T2 key, out double value, NumberRange range = NumberRange.A /// Reads the specified double from the block if it exists, preserving the prior value if not present public bool TryGetValue(T2 key, ref double value, NumberRange range = NumberRange.Any) { - if (keyValuePairs.TryRemove(key, out var s)) + if (TryPopValue(key, out var s)) { if (NumberFormats.TryParseDoubleVb6(s.Value, out double newValue)) { @@ -178,7 +200,7 @@ public bool TryGetValue(T2 key, ref double value, NumberRange range = NumberRang value = newValue; return true; } - currentHost.AddMessage(MessageType.Warning, false, "Value " + s + " is not a positive double in Key " + key + " in Section " + Key + " at line " + s.Key); + currentHost.AddMessage(MessageType.Warning, false, "Value " + s.Value + " is not a positive double in Key " + key + " in Section " + Key + " at line " + s.Key); return false; case NumberRange.NonNegative: if (newValue >= 0) @@ -186,7 +208,7 @@ public bool TryGetValue(T2 key, ref double value, NumberRange range = NumberRang value = newValue; return true; } - currentHost.AddMessage(MessageType.Warning, false, "Value " + s + " is not a non-negative double in Key " + key + " in Section " + Key + " at line " + s.Key); + currentHost.AddMessage(MessageType.Warning, false, "Value " + s.Value + " is not a non-negative double in Key " + key + " in Section " + Key + " at line " + s.Key); return false; case NumberRange.NonZero: if (newValue != 0) @@ -194,11 +216,11 @@ public bool TryGetValue(T2 key, ref double value, NumberRange range = NumberRang value = newValue; return true; } - currentHost.AddMessage(MessageType.Warning, false, "Value " + s + " is not a non-zero double in Key " + key + " in Section " + Key + " at line " + s.Key); + currentHost.AddMessage(MessageType.Warning, false, "Value " + s.Value + " is not a non-zero double in Key " + key + " in Section " + Key + " at line " + s.Key); return false; } } - currentHost.AddMessage(MessageType.Warning, false, "Value " + s + " is not a valid double in Key " + key + " in Section " + Key + " at line " + s.Key); + currentHost.AddMessage(MessageType.Warning, false, "Value " + s.Value + " is not a valid double in Key " + key + " in Section " + Key + " at line " + s.Key); } return false; } @@ -213,7 +235,7 @@ public bool GetValue(T2 key, out int value, NumberRange range = NumberRange.Any) /// Reads the specified integer from the block if it exists, preserving the prior value if not present public virtual bool TryGetValue(T2 key, ref int value, NumberRange range = NumberRange.Any) { - if (keyValuePairs.TryRemove(key, out var s)) + if (TryPopValue(key, out var s)) { if (NumberFormats.TryParseIntVb6(s.Value, out int newValue)) { @@ -233,7 +255,7 @@ public virtual bool TryGetValue(T2 key, ref int value, NumberRange range = Numbe value = newValue; return true; } - currentHost.AddMessage(MessageType.Warning, false, "Value " + s + " is not a positive integer in Key " + key + " in Section " + Key + " at line " + s.Key); + currentHost.AddMessage(MessageType.Warning, false, "Value " + s.Value + " is not a positive integer in Key " + key + " in Section " + Key + " at line " + s.Key); return false; case NumberRange.NonNegative: if (newValue >= 0) @@ -241,7 +263,7 @@ public virtual bool TryGetValue(T2 key, ref int value, NumberRange range = Numbe value = newValue; return true; } - currentHost.AddMessage(MessageType.Warning, false, "Value " + s + " is not a non-negative integer in Key " + key + " in Section " + Key + " at line " + s.Key); + currentHost.AddMessage(MessageType.Warning, false, "Value " + s.Value + " is not a non-negative integer in Key " + key + " in Section " + Key + " at line " + s.Key); return false; case NumberRange.NonZero: if (newValue != 0) @@ -249,7 +271,7 @@ public virtual bool TryGetValue(T2 key, ref int value, NumberRange range = Numbe value = newValue; return true; } - currentHost.AddMessage(MessageType.Warning, false, "Value " + s + " is not a non-zero integer in Key " + key + " in Section " + Key + " at line " + s.Key); + currentHost.AddMessage(MessageType.Warning, false, "Value " + s.Value + " is not a non-zero integer in Key " + key + " in Section " + Key + " at line " + s.Key); return false; } } @@ -335,9 +357,9 @@ public virtual bool GetIndexedEncoding(out TextEncoding.Encoding e, out string p /// Reads the specified string from the block, preserving the prior value if not present public virtual bool TryGetValue(T2 key, ref string stringValue) { - if (GetValue(key, out string value)) + if (TryPopValue(key, out var value)) { - stringValue = value; + stringValue = value.Value; return true; } return false; @@ -358,7 +380,7 @@ public virtual bool TryGetValue(T2 key, ref bool boolValue) public bool GetVector2(T2 key, char separator, out Vector2 value) { value = Vector2.Null; - if (keyValuePairs.TryRemove(key, out var rawValue)) + if (TryPopValue(key, out var rawValue)) { string[] splitStrings = rawValue.Value.ConsistantSplit(separator, 2); @@ -378,7 +400,7 @@ public bool GetVector2(T2 key, char separator, out Vector2 value) /// Reads the specified Vector2 from the block, preserving the prior value if not present public bool TryGetVector2(T2 key, char separator, ref Vector2 value) { - if (keyValuePairs.TryRemove(key, out var rawValue)) + if (TryPopValue(key, out var rawValue)) { string[] splitStrings = rawValue.Value.ConsistantSplit(separator, 2); bool error = false; @@ -410,7 +432,31 @@ public bool TryGetVector2(T2 key, char separator, ref Vector2 value) public virtual bool GetVector3(T2 key, char separator, out Vector3 value) { value = Vector3.Zero; - if (keyValuePairs.TryRemove(key, out var rawValue)) + if (TryPopValue(key, out var rawValue)) + { + string[] splitStrings = rawValue.Value.ConsistantSplit(separator, 3); + + if (!NumberFormats.TryParseDoubleVb6(splitStrings[0], out value.X)) + { + currentHost.AddMessage(MessageType.Warning, false, "X was invalid in " + key + " in Section " + Key + " at line " + rawValue.Key); + } + if (!NumberFormats.TryParseDoubleVb6(splitStrings[1], out value.Y)) + { + currentHost.AddMessage(MessageType.Warning, false, "Y was invalid in " + key + " in Section " + Key + " at line " + rawValue.Key); + } + if (!NumberFormats.TryParseDoubleVb6(splitStrings[2], out value.Z)) + { + currentHost.AddMessage(MessageType.Warning, false, "Z was invalid in " + key + " in Section " + Key + " at line " + rawValue.Key); + } + return true; + } + return false; + } + + /// Reads the specified Vector3 from the block, preserving the prior value if not present + public virtual bool TryGetVector3(T2 key, char separator, ref Vector3 value) + { + if (TryPopValue(key, out var rawValue)) { string[] splitStrings = rawValue.Value.ConsistantSplit(separator, 3); @@ -431,34 +477,60 @@ public virtual bool GetVector3(T2 key, char separator, out Vector3 value) return false; } - /// Reads the specified Vector3 from the block, preserving the prior value if not present - public virtual bool TryGetVector3(T2 key, char separator, ref Vector3 value) - { - if (keyValuePairs.TryRemove(key, out var rawValue)) + /// Reads all Vector3s from the block for the specified key + public virtual bool GetAllVector3s(T2 key, char separator, out Vector3[] values) + { + List returnedValues = new List(); + while (keyValuePairs.TryGetValue(key, out List> kvpList)) { - string[] splitStrings = rawValue.Value.ConsistantSplit(separator, 3); - - if (!NumberFormats.TryParseDoubleVb6(splitStrings[0], out value.X)) + if (TryPopValue(key, out var rawValue)) { - currentHost.AddMessage(MessageType.Warning, false, "X was invalid in " + key + " in Section " + Key + " at line " + rawValue.Key); - } - if (!NumberFormats.TryParseDoubleVb6(splitStrings[1], out value.Y)) - { - currentHost.AddMessage(MessageType.Warning, false, "Y was invalid in " + key + " in Section " + Key + " at line " + rawValue.Key); + string[] entries = rawValue.Value.Split('&'); + foreach (string entry in entries) + { + string[] splitStrings = entry.ConsistantSplit(separator, 3); + Vector3 v = Vector3.Zero; + bool error = false; + if (!NumberFormats.TryParseDoubleVb6(splitStrings[0], out v.X)) + { + currentHost.AddMessage(MessageType.Warning, false, "X was invalid in " + key + " in Section " + Key + " at line " + rawValue.Key); + error = true; + } + if (!NumberFormats.TryParseDoubleVb6(splitStrings[1], out v.Y)) + { + currentHost.AddMessage(MessageType.Warning, false, "Y was invalid in " + key + " in Section " + Key + " at line " + rawValue.Key); + error = true; + } + if (!NumberFormats.TryParseDoubleVb6(splitStrings[2], out v.Z)) + { + currentHost.AddMessage(MessageType.Warning, false, "Z was invalid in " + key + " in Section " + Key + " at line " + rawValue.Key); + error = true; + } + if (!error) + { + returnedValues.Add(v); + } + } } - if (!NumberFormats.TryParseDoubleVb6(splitStrings[2], out value.Z)) + else { - currentHost.AddMessage(MessageType.Warning, false, "Z was invalid in " + key + " in Section " + Key + " at line " + rawValue.Key); + break; } + } + + if (returnedValues.Count > 0) + { + values = returnedValues.ToArray(); return true; } + values = new Vector3[0]; return false; } /// Unconditionally reads the specified Color24 from the block public virtual bool GetColor24(T2 key, out Color24 value) { - if (keyValuePairs.TryRemove(key, out var color)) + if (TryPopValue(key, out var color)) { if (Color24.TryParseHexColor(color.Value, out value)) { @@ -479,7 +551,7 @@ public virtual bool GetColor24(T2 key, out Color24 value) /// Reads the specified Color24 from the block, preserving the prior value if not present public virtual bool TryGetColor24(T2 key, ref Color24 value) { - if (keyValuePairs.TryRemove(key, out var color)) + if (TryPopValue(key, out var color)) { if (Color24.TryParseHexColor(color.Value, out Color24 newValue)) { @@ -500,7 +572,7 @@ public virtual bool TryGetColor24(T2 key, ref Color24 value) /// Reads the specified Color24 from the block, preserving the prior value if not present public virtual bool TryGetColor32(T2 key, ref Color32 value) { - if (keyValuePairs.TryRemove(key, out var color)) + if (TryPopValue(key, out var color)) { if (Color32.TryParseHexColor(color.Value, out Color32 newValue)) { @@ -527,7 +599,7 @@ public virtual bool TryGetStringArray(T2 key, char separator, ref string[] value /// Reads the specified string array from the block, preserving the prior value if not present public virtual bool TryGetStringArray(T2 key, char[] separators, ref string[] values) { - if (keyValuePairs.TryRemove(key, out var value)) + if (TryPopValue(key, out var value)) { values = value.Value.Split(separators); return true; @@ -539,7 +611,7 @@ public virtual bool TryGetStringArray(T2 key, char[] separators, ref string[] va /// Reads the specified path array from the block public virtual bool TryGetPathArray(T2 key, char separator, string absolutePath, ref string[] values) { - if (keyValuePairs.TryRemove(key, out var value)) + if (TryPopValue(key, out var value)) { string[] splitValues = value.Value.Split(separator); if (splitValues.Length > 0) @@ -581,7 +653,7 @@ public virtual bool GetPathArray(T2 key, char separator, string absolutePath, re /// Reads the specified double array from the block public virtual bool TryGetDoubleArray(T2 key, char separator, ref double[] values) { - if (keyValuePairs.TryRemove(key, out var value)) + if (TryPopValue(key, out var value)) { string[] strings = value.Value.Split(separator); values = new double[strings.Length]; @@ -600,7 +672,7 @@ public virtual bool TryGetDoubleArray(T2 key, char separator, ref double[] value /// Reads the specified integer array from the block public virtual bool TryGetIntArray(T2 key, char separator, ref int[] values) { - if (keyValuePairs.TryRemove(key, out var value)) + if (TryPopValue(key, out var value)) { string[] strings = value.Value.Split(separator); values = new int[strings.Length]; @@ -633,7 +705,7 @@ public virtual bool GetFunctionScript(T2[] keys, string absolutePath, out Animat /// Reads the specified Enum value from the block public virtual bool GetEnumValue(T2 key, out T3 enumValue) where T3 : struct, Enum { - if (keyValuePairs.TryRemove(key, out var value)) + if (TryPopValue(key, out var value)) { if (Enum.TryParse(value.Value, true, out enumValue)) { @@ -650,7 +722,7 @@ public virtual bool GetEnumValue(T2 key, out T3 enumValue) where T3 : struct /// Reads the specified Enum value from the block public virtual bool TryGetEnumValue(T2 key, ref T3 enumValue) where T3 : struct, Enum { - if (keyValuePairs.TryRemove(key, out var value)) + if (TryPopValue(key, out var value)) { if (Enum.TryParse(value.Value, true, out enumValue)) { @@ -666,7 +738,7 @@ public virtual bool GetEnumValue(T2 key, out T3 enumValue, out int index, ou { index = -1; suffix = string.Empty; - if (keyValuePairs.TryRemove(key, out var value)) + if (TryPopValue(key, out var value)) { string s = value.Value.ToLowerInvariant(); @@ -732,7 +804,7 @@ public virtual bool GetEnumValue(T2 key, out T3 enumValue, out int index, ou public virtual bool GetEnumValue(T2 key, out T3 enumValue, out Color32 color) where T3 : struct, Enum { color = Color32.Black; - if (keyValuePairs.TryRemove(key, out var value)) + if (TryPopValue(key, out var value)) { int colonIndex = value.Value.IndexOf(':'); string colorValue = value.Value.Substring(colonIndex + 1); @@ -791,12 +863,12 @@ public virtual bool GetNextPath(string absolutePath, out string finalPath) return true; } - currentHost.AddMessage(MessageType.Warning, false, "File " + fileName + " was not found at line " + fileName.Key + " in Section " + Key + " in file " + FileName); + currentHost.AddMessage(MessageType.Warning, false, "File " + fileName.Value + " was not found at line " + fileName.Key + " in Section " + Key + " in file " + FileName); finalPath = string.Empty; return false; } - currentHost.AddMessage(MessageType.Warning, false, "Path contains invalid characters for " + fileName + " at line " + fileName.Key + " in Section " + Key + " in file " + FileName); + currentHost.AddMessage(MessageType.Warning, false, "Path contains invalid characters for " + fileName.Value + " at line " + fileName.Key + " in Section " + Key + " in file " + FileName); } finalPath = string.Empty; return false; @@ -805,7 +877,7 @@ public virtual bool GetNextPath(string absolutePath, out string finalPath) public virtual bool GetDamping(T2 key, char separator, out Damping damping) { damping = null; - if (keyValuePairs.TryRemove(key, out var value)) + if (TryPopValue(key, out var value)) { string[] s = value.Value.Split(separator); if (s.Length == 2) @@ -852,7 +924,7 @@ public virtual bool TryGetTime(T2 key, ref double Value) public virtual bool GetTime(T2 key, out double Value) { - if (keyValuePairs.TryRemove(key, out var value)) + if (TryPopValue(key, out var value)) { string Expression = value.Value.TrimInside(); if (Expression.Length != 0) @@ -930,7 +1002,7 @@ protected Block(int myIndex, T1 myKey, string myFile, HostInterface currentHost) FileName = myFile; this.currentHost = currentHost; subBlocks = new List>(); - keyValuePairs = new ConcurrentDictionary>(); + keyValuePairs = new ConcurrentDictionary>>(); indexedValues = new ConcurrentDictionary>(); rawValues = new Queue>(); } @@ -940,7 +1012,7 @@ public virtual void ReportErrors() for (int i = 0; i < keyValuePairs.Count; i++) { T2 key = keyValuePairs.ElementAt(i).Key; - currentHost.AddMessage(MessageType.Error, false, key + " is not valid in an " + Key + " section at line " + keyValuePairs[key].Key + " in file " + FileName); + currentHost.AddMessage(MessageType.Error, false, key + " is not valid in an " + Key + " section at line " + keyValuePairs[key][0].Key + " in file " + FileName); } for (int i = 0; i < rawValues.Count; i++) diff --git a/source/Plugins/Formats.OpenBve/CFG/ConfigFile.cs b/source/Plugins/Formats.OpenBve/CFG/ConfigFile.cs index 198a44ad1..0b4e610b8 100644 --- a/source/Plugins/Formats.OpenBve/CFG/ConfigFile.cs +++ b/source/Plugins/Formats.OpenBve/CFG/ConfigFile.cs @@ -221,11 +221,15 @@ internal ConfigSection(int myIndex, int startingLine, T1 myKey, string[] myLines } else if (Enum.TryParse(a.Replace(" ", ""), true, out T2 key)) { - keyValuePairs.TryAdd(key, new KeyValuePair(i + startingLine, b)); + if (!keyValuePairs.ContainsKey(key)) + { + keyValuePairs.TryAdd(key, new List>()); + } + keyValuePairs[key].Add(new KeyValuePair(i + startingLine, b)); } else { - currentHost.AddMessage(MessageType.Error, false, "Unknown Key " + a + " encountered in Section " + myKey + " at line " + i + startingLine); + currentHost.AddMessage(MessageType.Error, false, "Unknown Key " + a + " encountered in Section " + myKey + " at line " + (i + startingLine)); } } else @@ -240,7 +244,7 @@ internal ConfigSection(int myIndex, int startingLine, T1 myKey, string[] myLines public override bool GetFunctionScript(T2 key, out AnimationScript function) { - if (keyValuePairs.TryRemove(key, out var script)) + if (TryPopValue(key, out var script)) { try { @@ -250,7 +254,7 @@ public override bool GetFunctionScript(T2 key, out AnimationScript function) } catch { - currentHost.AddMessage(MessageType.Warning, false, "Function Script " + script + " was invalid in Key " + key + " in Section " + Key + " at line " + script.Key); + currentHost.AddMessage(MessageType.Warning, false, "Function Script " + script.Value + " was invalid in Key " + key + " in Section " + Key + " at line " + script.Key); function = null; return false; } @@ -265,7 +269,7 @@ public override bool GetFunctionScript(T2[] keys, string absolutePath, out Anima foreach (T2 key in keys) { - if (keyValuePairs.TryRemove(key, out var script)) + if (TryPopValue(key, out var script)) { if (key.ToString().IndexOf("script", StringComparison.InvariantCultureIgnoreCase) != -1) { @@ -277,13 +281,13 @@ public override bool GetFunctionScript(T2[] keys, string absolutePath, out Anima function = new CSAnimationScript(currentHost, Path.CombineDirectory(absolutePath, script.Value, true)); return true; } - currentHost.AddMessage(MessageType.Warning, false, "Function Script " + script + " was not found in Key " + key + " in Section " + Key + " at line " + script.Key); + currentHost.AddMessage(MessageType.Warning, false, "Function Script " + script.Value + " was not found in Key " + key + " in Section " + Key + " at line " + script.Key); function = null; return false; } catch { - currentHost.AddMessage(MessageType.Warning, false, "An error occured whilst attempting to load Function Script " + script + " in Key " + key + " in Section " + Key + " at line " + script.Key); + currentHost.AddMessage(MessageType.Warning, false, "An error occured whilst attempting to load Function Script " + script.Value + " in Key " + key + " in Section " + Key + " at line " + script.Key); } } else @@ -296,7 +300,7 @@ public override bool GetFunctionScript(T2[] keys, string absolutePath, out Anima } catch { - currentHost.AddMessage(MessageType.Warning, false, "Function Script " + script + " was invalid in Key " + key + " in Section " + Key + " at line " + script.Key); + currentHost.AddMessage(MessageType.Warning, false, "Function Script " + script.Value + " was invalid in Key " + key + " in Section " + Key + " at line " + script.Key); function = null; return false; } @@ -309,7 +313,7 @@ public override bool GetFunctionScript(T2[] keys, string absolutePath, out Anima public override bool GetPath(T2 key, string absolutePath, out string finalPath) { - if (keyValuePairs.TryRemove(key, out var value)) + if (TryPopValue(key, out var value)) { if (!Path.ContainsInvalidChars(value.Value)) { diff --git a/source/Plugins/Formats.OpenBve/XML/XMLFile.cs b/source/Plugins/Formats.OpenBve/XML/XMLFile.cs index 2b231fd0a..fa556b898 100644 --- a/source/Plugins/Formats.OpenBve/XML/XMLFile.cs +++ b/source/Plugins/Formats.OpenBve/XML/XMLFile.cs @@ -98,7 +98,11 @@ public XMLFile(XDocument currentXML, string fileName, string rootPath, HostInter if (Enum.TryParse(element.Name.LocalName, true, out T2 valueKey)) { - keyValuePairs.TryAdd(valueKey, new KeyValuePair(((IXmlLineInfo)element).LineNumber, element.Value)); + if (!keyValuePairs.ContainsKey(valueKey)) + { + keyValuePairs.TryAdd(valueKey, new List>()); + } + keyValuePairs[valueKey].Add(new KeyValuePair(((IXmlLineInfo)element).LineNumber, element.Value)); } else { @@ -192,7 +196,11 @@ public XMLSection(string fileName, XElement element, T1 myKey, HostInterface cur } else { - keyValuePairs.TryAdd(valueKey, new KeyValuePair(((IXmlLineInfo)childElement).LineNumber, childElement.Value)); + if (!keyValuePairs.ContainsKey(valueKey)) + { + keyValuePairs.TryAdd(valueKey, new List>()); + } + keyValuePairs[valueKey].Add(new KeyValuePair(((IXmlLineInfo)childElement).LineNumber, childElement.Value)); } } else @@ -215,7 +223,7 @@ public XMLSection(string fileName, XElement element, T1 myKey, HostInterface cur public override bool GetPath(T2 key, string absolutePath, out string finalPath) { - if (keyValuePairs.TryRemove(key, out var value)) + if (TryPopValue(key, out var value)) { if (!Path.ContainsInvalidChars(value.Value)) { diff --git a/source/Plugins/Object.Animated/Plugin.Parser.cs b/source/Plugins/Object.Animated/Plugin.Parser.cs index c2104658d..2ddb843ec 100644 --- a/source/Plugins/Object.Animated/Plugin.Parser.cs +++ b/source/Plugins/Object.Animated/Plugin.Parser.cs @@ -38,101 +38,104 @@ private static AnimatedObjectCollection ReadObject(string FileName, System.Text. switch (Block.Key) { case AnimatedSection.Include: - UnifiedObject[] obj = new UnifiedObject[4]; - int objCount = 0; - Block.GetVector3(AnimatedKey.Position, ',', out Position); + UnifiedObject[] includeObjects = new UnifiedObject[4]; + int includeObjectsCount = 0; + if (!Block.GetAllVector3s(AnimatedKey.Position, ',', out Vector3[] includePositions)) + { + includePositions = new[] { Vector3.Zero }; + } while (Block.RemainingDataValues > 0 && Block.GetNextPath(Folder, out string file)) { - if (obj.Length == objCount) + if (includeObjects.Length == includeObjectsCount) { - Array.Resize(ref obj, obj.Length << 1); + Array.Resize(ref includeObjects, includeObjects.Length << 1); } - currentHost.LoadObject(file, Encoding, out obj[objCount]); - objCount++; + currentHost.LoadObject(file, Encoding, out includeObjects[includeObjectsCount]); + includeObjectsCount++; } - for (int j = 0; j < objCount; j++) + for (int j = 0; j < includeObjectsCount; j++) { - if (obj[j] != null) + if (includeObjects[j] != null) { - if (obj[j] is StaticObject s) - { - s.Dynamic = true; - if (ObjectCount >= Result.Objects.Length) - { - Array.Resize(ref Result.Objects, Result.Objects.Length << 1); - } - - AnimatedObject a = new AnimatedObject(currentHost, FileName); - ObjectState aos = new ObjectState - { - Prototype = s, - Translation = Matrix4D.CreateTranslation(Position.X, Position.Y, -Position.Z) - }; - a.States = new[] { aos }; - Result.Objects[ObjectCount] = a; - ObjectCount++; - } - else if (obj[j] is AnimatedObjectCollection) + for (int p = 0; p < includePositions.Length; p++) { - AnimatedObjectCollection a = (AnimatedObjectCollection)obj[j].Clone(); - for (int k = 0; k < a.Objects.Length; k++) + Position = includePositions[p]; + if (includeObjects[j] is StaticObject s) { + s.Dynamic = true; if (ObjectCount >= Result.Objects.Length) { Array.Resize(ref Result.Objects, Result.Objects.Length << 1); } - for (int h = 0; h < a.Objects[k].States.Length; h++) + AnimatedObject a = new AnimatedObject(currentHost, FileName); + ObjectState aos = new ObjectState { - a.Objects[k].States[h].Translation *= Matrix4D.CreateTranslation(Position.X, Position.Y, -Position.Z); - } - - Result.Objects[ObjectCount] = a.Objects[k]; + Prototype = s, + Translation = Matrix4D.CreateTranslation(Position.X, Position.Y, -Position.Z) + }; + a.States = new[] { aos }; + Result.Objects[ObjectCount] = a; ObjectCount++; } - - for (int kk = 0; kk < a.Sounds.Length; kk++) + else if (includeObjects[j] is AnimatedObjectCollection) { - if (SoundCount >= Result.Sounds.Length) + AnimatedObjectCollection a = (AnimatedObjectCollection)includeObjects[j].Clone(); + for (int k = 0; k < a.Objects.Length; k++) { - Array.Resize(ref Result.Sounds, Result.Sounds.Length << 1); + if (ObjectCount >= Result.Objects.Length) + { + Array.Resize(ref Result.Objects, Result.Objects.Length << 1); + } + + for (int h = 0; h < a.Objects[k].States.Length; h++) + { + a.Objects[k].States[h].Translation *= Matrix4D.CreateTranslation(Position.X, Position.Y, -Position.Z); + } + + Result.Objects[ObjectCount] = a.Objects[k]; + ObjectCount++; } - Result.Sounds[SoundCount] = a.Sounds[kk]; - SoundCount++; + for (int kk = 0; kk < a.Sounds.Length; kk++) + { + if (SoundCount >= Result.Sounds.Length) + { + Array.Resize(ref Result.Sounds, Result.Sounds.Length << 1); + } + + Result.Sounds[SoundCount] = a.Sounds[kk]; + SoundCount++; + } } - } - else if (obj[j] is KeyframeAnimatedObject k) - { - currentHost.AddMessage(MessageType.Warning, false, "Including MSTS Shape in an AnimatedObject- Contained animations may be lost. In the Section " + Block.Key + " in file " + FileName); - StaticObject so = (StaticObject)k; - so.Dynamic = true; - if (ObjectCount >= Result.Objects.Length) + else if (includeObjects[j] is KeyframeAnimatedObject k) { - Array.Resize(ref Result.Objects, Result.Objects.Length << 1); - } + currentHost.AddMessage(MessageType.Warning, false, "Including MSTS Shape in an AnimatedObject- Contained animations may be lost. In the Section " + Block.Key + " in file " + FileName); + StaticObject so = (StaticObject)k; + so.Dynamic = true; + if (ObjectCount >= Result.Objects.Length) + { + Array.Resize(ref Result.Objects, Result.Objects.Length << 1); + } - AnimatedObject a = new AnimatedObject(currentHost, FileName); - ObjectState aos = new ObjectState - { - Prototype = so, - Translation = Matrix4D.CreateTranslation(Position.X, Position.Y, -Position.Z) - }; - a.States = new[] { aos }; - Result.Objects[ObjectCount] = a; - ObjectCount++; + AnimatedObject a = new AnimatedObject(currentHost, FileName); + ObjectState aos = new ObjectState + { + Prototype = so, + Translation = Matrix4D.CreateTranslation(Position.X, Position.Y, -Position.Z) + }; + a.States = new[] { aos }; + Result.Objects[ObjectCount] = a; + ObjectCount++; + } } } } break; case AnimatedSection.Object: - if (Result.Objects.Length == ObjectCount) - { - Array.Resize(ref Result.Objects, Result.Objects.Length << 1); - } - Result.Objects[ObjectCount] = new AnimatedObject(currentHost, FileName) + AnimatedObject template = new AnimatedObject(currentHost, FileName) { CurrentState = -1, TranslateXDirection = Vector3.Right, @@ -148,29 +151,32 @@ private static AnimatedObjectCollection ReadObject(string FileName, System.Text. string[] stateFiles = { }; if (Block.GetPathArray(AnimatedKey.States, ',', Folder, ref stateFiles)) { - Block.GetVector3(AnimatedKey.Position, ',', out Position); - Block.GetFunctionScript(new[] { AnimatedKey.RotateXFunction, AnimatedKey.RotateXFunctionRPN, AnimatedKey.RotateXScript }, Folder, out Result.Objects[ObjectCount].RotateXFunction); - Block.GetFunctionScript(new[] { AnimatedKey.RotateYFunction, AnimatedKey.RotateYFunctionRPN, AnimatedKey.RotateYScript }, Folder, out Result.Objects[ObjectCount].RotateYFunction); - Block.GetFunctionScript(new[] { AnimatedKey.RotateZFunction, AnimatedKey.RotateZFunctionRPN, AnimatedKey.RotateZScript }, Folder, out Result.Objects[ObjectCount].RotateZFunction); - Block.GetFunctionScript(new[] { AnimatedKey.TranslateXFunction, AnimatedKey.TranslateXFunctionRPN, AnimatedKey.TranslateXScript }, Folder, out Result.Objects[ObjectCount].TranslateXFunction); - Block.GetFunctionScript(new[] { AnimatedKey.TranslateYFunction, AnimatedKey.TranslateYFunctionRPN, AnimatedKey.TranslateYScript }, Folder, out Result.Objects[ObjectCount].TranslateYFunction); - Block.GetFunctionScript(new[] { AnimatedKey.TranslateZFunction, AnimatedKey.TranslateZFunctionRPN, AnimatedKey.TranslateZScript }, Folder, out Result.Objects[ObjectCount].TranslateZFunction); - Block.GetFunctionScript(new[] { AnimatedKey.StateFunction, AnimatedKey.StateFunctionRPN, AnimatedKey.StateScript }, Folder, out Result.Objects[ObjectCount].StateFunction); - Block.GetFunctionScript(new[] { AnimatedKey.TextureShiftXFunction, AnimatedKey.TextureShiftXFunctionRPN, AnimatedKey.TextureShiftXScript }, Folder, out Result.Objects[ObjectCount].TextureShiftXFunction); - Block.GetFunctionScript(new[] { AnimatedKey.TextureShiftYFunction, AnimatedKey.TextureShiftYFunctionRPN, AnimatedKey.TextureShiftYScript }, Folder, out Result.Objects[ObjectCount].TextureShiftYFunction); + if (!Block.GetAllVector3s(AnimatedKey.Position, ',', out Vector3[] objectPositions)) + { + objectPositions = new[] { Vector3.Zero }; + } + Block.GetFunctionScript(new[] { AnimatedKey.RotateXFunction, AnimatedKey.RotateXFunctionRPN, AnimatedKey.RotateXScript }, Folder, out template.RotateXFunction); + Block.GetFunctionScript(new[] { AnimatedKey.RotateYFunction, AnimatedKey.RotateYFunctionRPN, AnimatedKey.RotateYScript }, Folder, out template.RotateYFunction); + Block.GetFunctionScript(new[] { AnimatedKey.RotateZFunction, AnimatedKey.RotateZFunctionRPN, AnimatedKey.RotateZScript }, Folder, out template.RotateZFunction); + Block.GetFunctionScript(new[] { AnimatedKey.TranslateXFunction, AnimatedKey.TranslateXFunctionRPN, AnimatedKey.TranslateXScript }, Folder, out template.TranslateXFunction); + Block.GetFunctionScript(new[] { AnimatedKey.TranslateYFunction, AnimatedKey.TranslateYFunctionRPN, AnimatedKey.TranslateYScript }, Folder, out template.TranslateYFunction); + Block.GetFunctionScript(new[] { AnimatedKey.TranslateZFunction, AnimatedKey.TranslateZFunctionRPN, AnimatedKey.TranslateZScript }, Folder, out template.TranslateZFunction); + Block.GetFunctionScript(new[] { AnimatedKey.StateFunction, AnimatedKey.StateFunctionRPN, AnimatedKey.StateScript }, Folder, out template.StateFunction); + Block.GetFunctionScript(new[] { AnimatedKey.TextureShiftXFunction, AnimatedKey.TextureShiftXFunctionRPN, AnimatedKey.TextureShiftXScript }, Folder, out template.TextureShiftXFunction); + Block.GetFunctionScript(new[] { AnimatedKey.TextureShiftYFunction, AnimatedKey.TextureShiftYFunctionRPN, AnimatedKey.TextureShiftYScript }, Folder, out template.TextureShiftYFunction); // n.b. For unknown reasons, the ScaleFunction never had a RPN listing in the animated file. Michelle listed these as obsolete, and I've seen *one* use of them. As they're not really supposed to be used // don't add them to the new parser - Block.GetFunctionScript(new[] { AnimatedKey.ScaleXFunction, AnimatedKey.ScaleXScript }, Folder, out Result.Objects[ObjectCount].ScaleXFunction); - Block.GetFunctionScript(new[] { AnimatedKey.ScaleYFunction, AnimatedKey.ScaleYScript }, Folder, out Result.Objects[ObjectCount].ScaleYFunction); - Block.GetFunctionScript(new[] { AnimatedKey.ScaleZFunction, AnimatedKey.ScaleZScript }, Folder, out Result.Objects[ObjectCount].ScaleZFunction); - Block.TryGetVector3(AnimatedKey.TranslateXDirection, ',', ref Result.Objects[ObjectCount].TranslateXDirection); - Block.TryGetVector3(AnimatedKey.TranslateYDirection, ',', ref Result.Objects[ObjectCount].TranslateYDirection); - Block.TryGetVector3(AnimatedKey.TranslateZDirection, ',', ref Result.Objects[ObjectCount].TranslateZDirection); - Block.TryGetVector3(AnimatedKey.RotateXDirection, ',', ref Result.Objects[ObjectCount].RotateXDirection); - Block.TryGetVector3(AnimatedKey.RotateYDirection, ',', ref Result.Objects[ObjectCount].RotateYDirection); - Block.TryGetVector3(AnimatedKey.RotateZDirection, ',', ref Result.Objects[ObjectCount].RotateZDirection); - Block.TryGetVector2(AnimatedKey.TextureShiftXDirection, ',', ref Result.Objects[ObjectCount].TextureShiftXDirection); - Block.TryGetVector2(AnimatedKey.TextureShiftYDirection, ',', ref Result.Objects[ObjectCount].TextureShiftYDirection); + Block.GetFunctionScript(new[] { AnimatedKey.ScaleXFunction, AnimatedKey.ScaleXScript }, Folder, out template.ScaleXFunction); + Block.GetFunctionScript(new[] { AnimatedKey.ScaleYFunction, AnimatedKey.ScaleYScript }, Folder, out template.ScaleYFunction); + Block.GetFunctionScript(new[] { AnimatedKey.ScaleZFunction, AnimatedKey.ScaleZScript }, Folder, out template.ScaleZFunction); + Block.TryGetVector3(AnimatedKey.TranslateXDirection, ',', ref template.TranslateXDirection); + Block.TryGetVector3(AnimatedKey.TranslateYDirection, ',', ref template.TranslateYDirection); + Block.TryGetVector3(AnimatedKey.TranslateZDirection, ',', ref template.TranslateZDirection); + Block.TryGetVector3(AnimatedKey.RotateXDirection, ',', ref template.RotateXDirection); + Block.TryGetVector3(AnimatedKey.RotateYDirection, ',', ref template.RotateYDirection); + Block.TryGetVector3(AnimatedKey.RotateZDirection, ',', ref template.RotateZDirection); + Block.TryGetVector2(AnimatedKey.TextureShiftXDirection, ',', ref template.TextureShiftXDirection); + Block.TryGetVector2(AnimatedKey.TextureShiftYDirection, ',', ref template.TextureShiftYDirection); if (Block.GetVector2(AnimatedKey.Axles, ',', out Vector2 axleLocations)) { if (axleLocations.X <= axleLocations.Y) @@ -178,14 +184,14 @@ private static AnimatedObjectCollection ReadObject(string FileName, System.Text. currentHost.AddMessage(MessageType.Error, false, "Rear is expected to be less than Front in " + AnimatedKey.Axles + " in file " + FileName); axleLocations = new Vector2(0.5, -0.5); } - Result.Objects[ObjectCount].FrontAxlePosition = axleLocations.X; - Result.Objects[ObjectCount].RearAxlePosition = axleLocations.Y; + template.FrontAxlePosition = axleLocations.X; + template.RearAxlePosition = axleLocations.Y; } - Block.GetFunctionScript(new[] { AnimatedKey.TrackFollowerFunction, AnimatedKey.TrackFollowerScript }, Folder, out Result.Objects[ObjectCount].TrackFollowerFunction); - Block.GetDamping(AnimatedKey.RotateXDamping, ',', out Result.Objects[ObjectCount].RotateXDamping); - Block.GetDamping(AnimatedKey.RotateYDamping, ',', out Result.Objects[ObjectCount].RotateYDamping); - Block.GetDamping(AnimatedKey.RotateZDamping, ',', out Result.Objects[ObjectCount].RotateZDamping); + Block.GetFunctionScript(new[] { AnimatedKey.TrackFollowerFunction, AnimatedKey.TrackFollowerScript }, Folder, out template.TrackFollowerFunction); + Block.GetDamping(AnimatedKey.RotateXDamping, ',', out template.RotateXDamping); + Block.GetDamping(AnimatedKey.RotateYDamping, ',', out template.RotateYDamping); + Block.GetDamping(AnimatedKey.RotateZDamping, ',', out template.RotateZDamping); if (Block.GetValue(AnimatedKey.TextureOverride, out string textureOverride)) { @@ -194,8 +200,8 @@ private static AnimatedObjectCollection ReadObject(string FileName, System.Text. case "none": break; case "timetable": - currentHost.AddObjectForCustomTimeTable(Result.Objects[ObjectCount]); - Result.Objects[ObjectCount].StateFunction = new FunctionScript(currentHost, "timetable", true); + currentHost.AddObjectForCustomTimeTable(template); + template.StateFunction = new FunctionScript(currentHost, "timetable", true); break; default: currentHost.AddMessage(MessageType.Error, false, "Unknown texture override type " + textureOverride + " in Section " + Block.Key + " in File " + FileName); @@ -205,29 +211,28 @@ private static AnimatedObjectCollection ReadObject(string FileName, System.Text. if (Block.GetValue(AnimatedKey.RefreshRate, out double refreshRate, NumberRange.NonNegative)) { - Result.Objects[ObjectCount].RefreshRate = refreshRate; + template.RefreshRate = refreshRate; } - Result.Objects[ObjectCount].States = new ObjectState[stateFiles.Length]; - bool forceTextureRepeatX = Result.Objects[ObjectCount].TextureShiftXFunction != null & Result.Objects[ObjectCount].TextureShiftXDirection.X != 0.0 | - Result.Objects[ObjectCount].TextureShiftYFunction != null & Result.Objects[ObjectCount].TextureShiftYDirection.X != 0.0; - bool forceTextureRepeatY = Result.Objects[ObjectCount].TextureShiftXFunction != null & Result.Objects[ObjectCount].TextureShiftXDirection.Y != 0.0 | - Result.Objects[ObjectCount].TextureShiftYFunction != null & Result.Objects[ObjectCount].TextureShiftYDirection.Y != 0.0; + template.States = new ObjectState[stateFiles.Length]; + bool forceTextureRepeatX = template.TextureShiftXFunction != null & template.TextureShiftXDirection.X != 0.0 | + template.TextureShiftYFunction != null & template.TextureShiftYDirection.X != 0.0; + bool forceTextureRepeatY = template.TextureShiftXFunction != null & template.TextureShiftXDirection.Y != 0.0 | + template.TextureShiftYFunction != null & template.TextureShiftYDirection.Y != 0.0; for (int k = 0; k < stateFiles.Length; k++) { - Result.Objects[ObjectCount].States[k] = new ObjectState(); - Result.Objects[ObjectCount].States[k].Translation = Matrix4D.CreateTranslation(Position.X, Position.Y, -Position.Z); + template.States[k] = new ObjectState(); if (stateFiles[k] != null) { currentHost.LoadObject(stateFiles[k], Encoding, out UnifiedObject currentObject); if (currentObject is StaticObject staticObject) { - Result.Objects[ObjectCount].States[k].Prototype = staticObject; + template.States[k].Prototype = staticObject; } else if (currentObject is KeyframeAnimatedObject keyframeObject) { currentHost.AddMessage(MessageType.Warning, false, "Using MSTS Shape " + stateFiles[k] + " as an AnimatedObject State- Contained animations may be lost. In the Section " + Block.Key + " in file " + FileName); - Result.Objects[ObjectCount].States[k].Prototype = keyframeObject; + template.States[k].Prototype = keyframeObject; } else if (currentObject is AnimatedObjectCollection) { @@ -239,38 +244,38 @@ private static AnimatedObjectCollection ReadObject(string FileName, System.Text. * NOTE: If loading the object fails, or the prototype is null (empty), then we will get a * non-shown state */ - if (Result.Objects[ObjectCount].States[k].Prototype != null) + if (template.States[k].Prototype != null) { - Result.Objects[ObjectCount].States[k].Prototype.Dynamic = true; - for (int l = 0; l < Result.Objects[ObjectCount].States[k].Prototype.Mesh.Materials.Length; l++) + template.States[k].Prototype.Dynamic = true; + for (int l = 0; l < template.States[k].Prototype.Mesh.Materials.Length; l++) { if (forceTextureRepeatX && forceTextureRepeatY) { - Result.Objects[ObjectCount].States[k].Prototype.Mesh.Materials[l].WrapMode = OpenGlTextureWrapMode.RepeatRepeat; + template.States[k].Prototype.Mesh.Materials[l].WrapMode = OpenGlTextureWrapMode.RepeatRepeat; } else if (forceTextureRepeatX) { - switch (Result.Objects[ObjectCount].States[k].Prototype.Mesh.Materials[l].WrapMode) + switch (template.States[k].Prototype.Mesh.Materials[l].WrapMode) { case OpenGlTextureWrapMode.ClampRepeat: - Result.Objects[ObjectCount].States[k].Prototype.Mesh.Materials[l].WrapMode = OpenGlTextureWrapMode.RepeatRepeat; + template.States[k].Prototype.Mesh.Materials[l].WrapMode = OpenGlTextureWrapMode.RepeatRepeat; break; case OpenGlTextureWrapMode.ClampClamp: - Result.Objects[ObjectCount].States[k].Prototype.Mesh.Materials[l].WrapMode = OpenGlTextureWrapMode.RepeatClamp; + template.States[k].Prototype.Mesh.Materials[l].WrapMode = OpenGlTextureWrapMode.RepeatClamp; break; } } else if (forceTextureRepeatY) { - switch (Result.Objects[ObjectCount].States[k].Prototype.Mesh.Materials[l].WrapMode) + switch (template.States[k].Prototype.Mesh.Materials[l].WrapMode) { case OpenGlTextureWrapMode.RepeatClamp: - Result.Objects[ObjectCount].States[k].Prototype.Mesh.Materials[l].WrapMode = OpenGlTextureWrapMode.RepeatRepeat; + template.States[k].Prototype.Mesh.Materials[l].WrapMode = OpenGlTextureWrapMode.RepeatRepeat; break; case OpenGlTextureWrapMode.ClampClamp: - Result.Objects[ObjectCount].States[k].Prototype.Mesh.Materials[l].WrapMode = OpenGlTextureWrapMode.ClampRepeat; + template.States[k].Prototype.Mesh.Materials[l].WrapMode = OpenGlTextureWrapMode.ClampRepeat; break; } } @@ -280,88 +285,123 @@ private static AnimatedObjectCollection ReadObject(string FileName, System.Text. } else { - Result.Objects[ObjectCount].States[k].Prototype = null; + template.States[k].Prototype = null; + } + } + + for (int p = 0; p < objectPositions.Length; p++) + { + if (Result.Objects.Length == ObjectCount) + { + Array.Resize(ref Result.Objects, Result.Objects.Length << 1); + } + AnimatedObject clone = template.Clone(); + for (int k = 0; k < clone.States.Length; k++) + { + clone.States[k].Translation = Matrix4D.CreateTranslation(objectPositions[p].X, objectPositions[p].Y, -objectPositions[p].Z); } + Result.Objects[ObjectCount] = clone; + ObjectCount++; } } else { Result.Objects[ObjectCount].States = new ObjectState[] { }; + ObjectCount++; } - - ObjectCount++; break; case AnimatedSection.Sound: - if (Result.Sounds.Length >= SoundCount) + if (Block.GetPath(AnimatedKey.FileName, Folder, out string worldSoundPath)) { - Array.Resize(ref Result.Sounds, Result.Sounds.Length << 1); - } - if (Block.GetPath(AnimatedKey.FileName, Folder, out string soundPath)) - { - double pitch = 1.0, volume = 1.0, radius = 30; - Block.GetVector3(AnimatedKey.Position, ',', out Position); - Block.TryGetValue(AnimatedKey.Radius, ref radius, NumberRange.NonNegative); + if (!Block.GetAllVector3s(AnimatedKey.Position, ',', out Vector3[] soundPositions)) + { + soundPositions = new[] { Vector3.Zero }; + } + double worldSoundPitch = 1.0, worldSoundVolume = 1.0, worldSoundRadius = 30; + Block.TryGetValue(AnimatedKey.Radius, ref worldSoundRadius, NumberRange.NonNegative); Block.GetFunctionScript(new[] { AnimatedKey.Pitch, AnimatedKey.PitchFunction }, Folder, out AnimationScript pitchFunction); - Block.TryGetValue(AnimatedKey.Pitch, ref pitch, NumberRange.NonNegative); + Block.TryGetValue(AnimatedKey.Pitch, ref worldSoundPitch, NumberRange.NonNegative); Block.GetFunctionScript(new[] { AnimatedKey.VolumeFunction }, Folder, out AnimationScript volumeFunction); - Block.TryGetValue(AnimatedKey.Volume, ref volume, NumberRange.NonNegative); + Block.TryGetValue(AnimatedKey.Volume, ref worldSoundVolume, NumberRange.NonNegative); Block.GetFunctionScript(new[] { AnimatedKey.TrackFollowerFunction }, Folder, out AnimationScript trackFollowerFunction); - currentHost.RegisterSound(soundPath, radius, out SoundHandle currentSound); - WorldSound snd = new WorldSound(currentHost, currentSound) + currentHost.RegisterSound(worldSoundPath, worldSoundRadius, out SoundHandle currentSound); + + for (int p = 0; p < soundPositions.Length; p++) { - currentPitch = pitch, - currentVolume = volume, - Position = Position, - TrackFollowerFunction = trackFollowerFunction, - PitchFunction = pitchFunction, - VolumeFunction = volumeFunction - }; - Result.Sounds[SoundCount] = snd; - SoundCount++; + if (Result.Sounds.Length <= SoundCount) + { + Array.Resize(ref Result.Sounds, Result.Sounds.Length << 1); + } + WorldSound snd = new WorldSound(currentHost, currentSound) + { + currentPitch = worldSoundPitch, + currentVolume = worldSoundVolume, + Position = soundPositions[p], + TrackFollowerFunction = trackFollowerFunction, + PitchFunction = pitchFunction, + VolumeFunction = volumeFunction + }; + Result.Sounds[SoundCount] = snd; + SoundCount++; + } } break; case AnimatedSection.StateChangeSound: - if (Result.Sounds.Length == SoundCount) + string[] stateChangeFileNames = new string[1]; // hack to allow both + bool isSingleBuffer = Block.GetPath(AnimatedKey.FileName, Folder, out stateChangeFileNames[0]); + if (isSingleBuffer || Block.GetPathArray(AnimatedKey.FileNames, ',', Folder, ref stateChangeFileNames)) { - Array.Resize(ref Result.Objects, Result.Sounds.Length << 1); - } - string[] fileNames = new string[1]; // hack to allow both - bool singleBuffer = Block.GetPath(AnimatedKey.FileName, Folder, out fileNames[0]); - if (singleBuffer || Block.GetPathArray(AnimatedKey.FileNames, ',', Folder, ref fileNames)) - { - double pitch = 1.0, volume = 1.0, radius = 30; - Block.GetVector3(AnimatedKey.Position, ',', out Position); - Block.TryGetValue(AnimatedKey.Pitch, ref pitch, NumberRange.NonNegative); - Block.TryGetValue(AnimatedKey.Volume, ref volume, NumberRange.NonNegative); - Block.TryGetValue(AnimatedKey.Radius, ref radius, NumberRange.NonNegative); + if (!Block.GetAllVector3s(AnimatedKey.Position, ',', out Vector3[] stateChangePositions)) + { + stateChangePositions = new[] { Vector3.Zero }; + } + double stateChangePitch = 1.0, stateChangeVolume = 1.0, stateChangeRadius = 30; + Block.TryGetValue(AnimatedKey.Pitch, ref stateChangePitch, NumberRange.NonNegative); + Block.TryGetValue(AnimatedKey.Volume, ref stateChangeVolume, NumberRange.NonNegative); + Block.TryGetValue(AnimatedKey.Radius, ref stateChangeRadius, NumberRange.NonNegative); bool playOnShow = true, playOnHide = true; Block.TryGetValue(AnimatedKey.PlayOnShow, ref playOnShow); Block.TryGetValue(AnimatedKey.PlayOnHide, ref playOnHide); if (ObjectCount > 0) { - AnimatedWorldObjectStateSound snd = new AnimatedWorldObjectStateSound(currentHost) - { - Object = Result.Objects[ObjectCount - 1].Clone(), - Buffers = new SoundHandle[fileNames.Length] - }; - for (int j = 0; j < fileNames.Length; j++) + // NOTE: This attaches to the PREVIOUS object. + // If that object had multiple positions, we should probably attach to ALL of them? + // But the current logic removes the object from Result.Objects and replaces it with a world sound object. + + // Wait, the original code only handled ONE object. + // If there were multiple objects created by the previous section, what happens? + // The original code only deceremented ObjectCount once. + + // For now, let's keep the logic consistent with how it was, but support multiple sound positions if specified. + for (int p = 0; p < stateChangePositions.Length; p++) { - if (fileNames[j] != null) + if (Result.Sounds.Length <= SoundCount) { - currentHost.RegisterSound(fileNames[j], radius, out snd.Buffers[j]); + Array.Resize(ref Result.Sounds, Result.Sounds.Length << 1); + } + AnimatedWorldObjectStateSound snd = new AnimatedWorldObjectStateSound(currentHost) + { + Object = Result.Objects[ObjectCount - 1].Clone(), + Buffers = new SoundHandle[stateChangeFileNames.Length] + }; + for (int j = 0; j < stateChangeFileNames.Length; j++) + { + if (stateChangeFileNames[j] != null) + { + currentHost.RegisterSound(stateChangeFileNames[j], stateChangeRadius, out snd.Buffers[j]); + } } - } - snd.CurrentPitch = pitch; - snd.CurrentVolume = volume; - snd.SoundPosition = Position; - snd.SingleBuffer = fileNames.Length != 1; - snd.PlayOnShow = playOnShow; - snd.PlayOnHide = playOnHide; - snd.SingleBuffer = singleBuffer; - Result.Sounds[SoundCount] = snd; - SoundCount++; + snd.CurrentPitch = stateChangePitch; + snd.CurrentVolume = stateChangeVolume; + snd.SoundPosition = stateChangePositions[p]; + snd.PlayOnShow = playOnShow; + snd.PlayOnHide = playOnHide; + snd.SingleBuffer = isSingleBuffer; + Result.Sounds[SoundCount] = snd; + SoundCount++; + } Result.Objects[ObjectCount - 1] = null; ObjectCount--; }