diff --git a/CHANGELOG.md b/CHANGELOG.md index 3167093c6..84d9f2cd7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,16 +7,23 @@ Built with [Unity 2020.2](https://github.com/freezy/VisualPinball.Engine/pull/255). ### Added +- Support for Extended ASCII strings ([#291](https://github.com/freezy/VisualPinball.Engine/pull/291)). +- Support for Elasticity Falloff in walls (added in VP 10.7) ([#291](https://github.com/freezy/VisualPinball.Engine/pull/291)). +- Support for table notes (added in VP 10.7) ([#291](https://github.com/freezy/VisualPinball.Engine/pull/291)). - Slow motion during gameplay ([#288](https://github.com/freezy/VisualPinball.Engine/pull/288)). - [Lamp Manager](https://docs.visualpinball.org/creators-guide/editor/lamp-manager.html) ([#282](https://github.com/freezy/VisualPinball.Engine/pull/282)). - The VPE core is now also available on [NuGet](https://www.nuget.org/packages/VisualPinball.Engine/). - VPE is now packaged and published on every merge! -- Native trough component ([#229](https://github.com/freezy/VisualPinball.Engine/pull/229), [#248](https://github.com/freezy/VisualPinball.Engine/pull/248), [#256](https://github.com/freezy/VisualPinball.Engine/pull/256), [Documentation](https://docs.visualpinball.org/creators-guide/manual/mechanisms/troughs.html)) +- Native trough component ([#229](https://github.com/freezy/VisualPinball.Engine/pull/229), [#248](https://github.com/freezy/VisualPinball.Engine/pull/248), [#256](https://github.com/freezy/VisualPinball.Engine/pull/256), [Documentation](https://docs.visualpinball.org/creators-guide/manual/mechanisms/troughs.html)). ### Changed - Plunger is now a coil device, meaning it can both be pulled back and fired through different inputs. -- Move render pipelines into separate repos ([#259](https://github.com/freezy/VisualPinball.Engine/pull/259)) -- Put game-, mesh-, collision- animation data into separate components ([#227](https://github.com/freezy/VisualPinball.Engine/pull/227), [Documentation](https://docs.visualpinball.org/creators-guide/editor/unity-components.html)) +- Move render pipelines into separate repos ([#259](https://github.com/freezy/VisualPinball.Engine/pull/259)). +- Put game-, mesh-, collision- animation data into separate components ([#227](https://github.com/freezy/VisualPinball.Engine/pull/227), [Documentation](https://docs.visualpinball.org/creators-guide/editor/unity-components.html)). ### Fixed -- Fixed a few bugs in drag point gizmos ([#246](https://github.com/freezy/VisualPinball.Engine/pull/246)) +- Alpha channel of color values is now correctly written ([#291](https://github.com/freezy/VisualPinball.Engine/pull/291)). +- Layer names are correctly computed when importing a 10.6 file ([#291](https://github.com/freezy/VisualPinball.Engine/pull/291)). +- Clear texture and material references that don't exist before writing (VP 10.7 behavior) ([#291](https://github.com/freezy/VisualPinball.Engine/pull/291)). +- Bug in writing animation vertices which caused VP to hang when re-reading the file ([#291](https://github.com/freezy/VisualPinball.Engine/pull/291)). +- A few bugs in drag point gizmos ([#246](https://github.com/freezy/VisualPinball.Engine/pull/246)). diff --git a/VisualPinball.Engine.Test/IO/BiffAttributeTest.cs b/VisualPinball.Engine.Test/IO/BiffAttributeTest.cs index 767089847..08ea934ea 100644 --- a/VisualPinball.Engine.Test/IO/BiffAttributeTest.cs +++ b/VisualPinball.Engine.Test/IO/BiffAttributeTest.cs @@ -51,10 +51,9 @@ public void ShouldBeAppliedToStrings() [Test] public void ShouldBeAppliedToIntegers() { - GetAttributes(typeof(BiffIntAttribute), (memberType, member, biffDataType, attr) => - { - if (memberType != typeof(int) && memberType != typeof(int[])) { - throw new Exception($"BiffInt of {biffDataType.FullName}.{member.Name} ({attr.Name}) must be either int or int[], but is {memberType.Name}."); + GetAttributes(typeof(BiffIntAttribute), (memberType, member, biffDataType, attr) => { + if (memberType != typeof(int) && memberType != typeof(int[]) && memberType != typeof(uint) && memberType != typeof(uint[])) { + throw new Exception($"BiffInt of {biffDataType.FullName}.{member.Name} ({attr.Name}) must be either (u)int or (u)int[], but is {memberType.Name}."); } }); } diff --git a/VisualPinball.Engine.Test/IO/ConsistencyTests.cs b/VisualPinball.Engine.Test/IO/ConsistencyTests.cs new file mode 100644 index 000000000..8a75ceb49 --- /dev/null +++ b/VisualPinball.Engine.Test/IO/ConsistencyTests.cs @@ -0,0 +1,64 @@ +// Visual Pinball Engine +// Copyright (C) 2021 freezy and VPE Team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using FluentAssertions; +using NUnit.Framework; +using VisualPinball.Engine.VPT; +using VisualPinball.Engine.VPT.Table; + +namespace VisualPinball.Engine.Test.IO +{ + public class ConsistencyTests + { + [Test] + public void ShouldClearWrongMaterialReference() + { + const string tmpFileName = "ShouldClearWrongMaterialReference.vpx"; + + var table = new TableBuilder() + .AddBumper("Bumper1") + .AddMaterial(new Material("DoesExist")) + .Build(); + + table.Bumper("Bumper1").Data.BaseMaterial = "DoesExist"; + table.Bumper("Bumper1").Data.CapMaterial = "DoesNotExist"; + + table.Save(tmpFileName); + + table.Bumper("Bumper1").Data.BaseMaterial.Should().Be("DoesExist"); + table.Bumper("Bumper1").Data.CapMaterial.Should().BeEmpty(); + } + + [Test] + public void ShouldClearWrongTextureReference() + { + const string tmpFileName = "ShouldClearWrongTextureReference.vpx"; + + var table = new TableBuilder() + .AddFlipper("Flipper") + .AddTexture("DoesExist") + .Build(); + + table.Flipper("Flipper").Data.Image = "DoesExist"; + table.Save(tmpFileName); + table.Flipper("Flipper").Data.Image.Should().Be("DoesExist"); + + table.Flipper("Flipper").Data.Image = "DoesNotExist"; + table.Save(tmpFileName); + table.Flipper("Flipper").Data.Image.Should().BeEmpty(); + } + } +} diff --git a/VisualPinball.Engine.Test/Math/ColorTest.cs b/VisualPinball.Engine.Test/Math/ColorTests.cs similarity index 71% rename from VisualPinball.Engine.Test/Math/ColorTest.cs rename to VisualPinball.Engine.Test/Math/ColorTests.cs index adbc848a2..5e71695b7 100644 --- a/VisualPinball.Engine.Test/Math/ColorTest.cs +++ b/VisualPinball.Engine.Test/Math/ColorTests.cs @@ -14,13 +14,14 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +using System.IO; using FluentAssertions; using NUnit.Framework; using VisualPinball.Engine.Math; namespace VisualPinball.Engine.Test.Math { - public class TableDataTests + public class ColorTests { [Test] public void ShouldCorrectlyParseRgbColor() @@ -40,5 +41,20 @@ public void ShouldCorrectlyParseArgbColor() color.Blue.Should().Be(0x78); color.Alpha.Should().Be(0x12); } + + [Test] + public void TestColor() + { + using (var memStream = new MemoryStream(new byte[] {0xff, 0xa0, 0xb4, 0x80 })) { + using (var reader = new BinaryReader(memStream)) { + var intCol = reader.ReadUInt32(); + var color = new Color(intCol, ColorFormat.Bgr); + color.Red.Should().Be(0xff); + color.Green.Should().Be(0xa0); + color.Blue.Should().Be(0xb4); + color.Alpha.Should().Be(0x80); + } + } + } } } diff --git a/VisualPinball.Engine.Test/VPT/Bumper/BumperDataTests.cs b/VisualPinball.Engine.Test/VPT/Bumper/BumperDataTests.cs index 3167d49af..a6a6b5aaa 100644 --- a/VisualPinball.Engine.Test/VPT/Bumper/BumperDataTests.cs +++ b/VisualPinball.Engine.Test/VPT/Bumper/BumperDataTests.cs @@ -71,7 +71,7 @@ private static void ValidateTableData(BumperData data) data.IsTimerEnabled.Should().Be(false); data.EditorLayer.Should().Be(0); - data.EditorLayerName.Should().Be(string.Empty); + data.EditorLayerName.Should().Be("Layer_1"); data.EditorLayerVisibility.Should().Be(true); data.IsLocked.Should().Be(false); } diff --git a/VisualPinball.Engine.Test/VPT/Decal/DecalDataTest.cs b/VisualPinball.Engine.Test/VPT/Decal/DecalDataTest.cs index 3b89d2825..a8b4a18ee 100644 --- a/VisualPinball.Engine.Test/VPT/Decal/DecalDataTest.cs +++ b/VisualPinball.Engine.Test/VPT/Decal/DecalDataTest.cs @@ -63,7 +63,7 @@ private static void ValidateDecal0(DecalData data) data.VerticalText.Should().Be(false); data.Width.Should().Be(66.1165f); data.EditorLayer.Should().Be(2); - data.EditorLayerName.Should().Be(string.Empty); + data.EditorLayerName.Should().Be("Layer_3"); data.EditorLayerVisibility.Should().Be(true); data.IsLocked.Should().Be(false); } @@ -88,7 +88,7 @@ private static void ValidateDecal1(DecalData data) data.VerticalText.Should().Be(true); data.Width.Should().Be(100f); data.EditorLayer.Should().Be(0); - data.EditorLayerName.Should().Be(string.Empty); + data.EditorLayerName.Should().Be("Layer_1"); data.EditorLayerVisibility.Should().Be(true); data.IsLocked.Should().Be(true); } diff --git a/VisualPinball.Engine.Test/VPT/DispReel/DispReelDataTest.cs b/VisualPinball.Engine.Test/VPT/DispReel/DispReelDataTest.cs index 6a63c2322..86de042c8 100644 --- a/VisualPinball.Engine.Test/VPT/DispReel/DispReelDataTest.cs +++ b/VisualPinball.Engine.Test/VPT/DispReel/DispReelDataTest.cs @@ -49,7 +49,7 @@ private static void ValidateDispReel1(DispReelData data) data.BackColor.Blue.Should().Be(19); data.DigitRange.Should().Be(3); data.EditorLayer.Should().Be(6); - data.EditorLayerName.Should().Be(string.Empty); + data.EditorLayerName.Should().Be("Layer_7"); data.EditorLayerVisibility.Should().Be(true); data.Height.Should().Be(42); data.Image.Should().Be("tex_transparent"); @@ -80,7 +80,7 @@ private static void ValidateDispReel2(DispReelData data) data.BackColor.Blue.Should().Be(255); data.DigitRange.Should().Be(9); data.EditorLayer.Should().Be(0); - data.EditorLayerName.Should().Be(string.Empty); + data.EditorLayerName.Should().Be("Layer_1"); data.EditorLayerVisibility.Should().Be(true); data.Height.Should().Be(40); data.Image.Should().Be(""); diff --git a/VisualPinball.Engine.Test/VPT/Flasher/FlasherDataTests.cs b/VisualPinball.Engine.Test/VPT/Flasher/FlasherDataTests.cs index 1382d3e6c..f5ba43f6e 100644 --- a/VisualPinball.Engine.Test/VPT/Flasher/FlasherDataTests.cs +++ b/VisualPinball.Engine.Test/VPT/Flasher/FlasherDataTests.cs @@ -73,7 +73,7 @@ private static void ValidateFlasher(FlasherData data) data.IsTimerEnabled.Should().Be(false); data.EditorLayer.Should().Be(10); - data.EditorLayerName.Should().Be(string.Empty); + data.EditorLayerName.Should().Be("Layer_11"); data.EditorLayerVisibility.Should().Be(true); data.IsLocked.Should().Be(true); } diff --git a/VisualPinball.Engine.Test/VPT/Flipper/FlipperDataTests.cs b/VisualPinball.Engine.Test/VPT/Flipper/FlipperDataTests.cs index 68dafcf9a..f425c9798 100644 --- a/VisualPinball.Engine.Test/VPT/Flipper/FlipperDataTests.cs +++ b/VisualPinball.Engine.Test/VPT/Flipper/FlipperDataTests.cs @@ -77,7 +77,7 @@ private static void ValidateFlipper(FlipperData data) data.IsTimerEnabled.Should().Be(false); data.EditorLayer.Should().Be(0); - data.EditorLayerName.Should().Be(string.Empty); + data.EditorLayerName.Should().Be("Layer_1"); data.EditorLayerVisibility.Should().Be(true); data.IsLocked.Should().Be(false); } diff --git a/VisualPinball.Engine.Test/VPT/Layers/LayerDataTests.cs b/VisualPinball.Engine.Test/VPT/Layers/LayerDataTests.cs index ddec8da92..be95c7e27 100644 --- a/VisualPinball.Engine.Test/VPT/Layers/LayerDataTests.cs +++ b/VisualPinball.Engine.Test/VPT/Layers/LayerDataTests.cs @@ -54,7 +54,7 @@ public void ShouldWriteLayerData() private static void ValidateTableDataVPX1060(BumperData data) { data.EditorLayer.Should().Be(0); - data.EditorLayerName.Should().Be(string.Empty); + data.EditorLayerName.Should().Be("Layer_1"); data.EditorLayerVisibility.Should().Be(true); data.IsLocked.Should().Be(false); } diff --git a/VisualPinball.Engine/IO/BiffAttribute.cs b/VisualPinball.Engine/IO/BiffAttribute.cs index b70459ffc..0cdce6fcf 100644 --- a/VisualPinball.Engine/IO/BiffAttribute.cs +++ b/VisualPinball.Engine/IO/BiffAttribute.cs @@ -90,6 +90,22 @@ public abstract class BiffAttribute : Attribute, ISortableBiffRecord /// public bool SkipHash = false; + /// + /// If set, counts as data exclusively used by VPE and can be disabled when writing with WRITE_VP106 + /// or WRITE_VP107 active. + /// + public bool IsVpeEnhancement = false; + + /// + /// If set, this attribute won't written if the WRITE_VP106 flag is set. + /// + public bool WasAddedInVp107 = false; + + /// + /// If set, this attribute won't written if the WRITE_VP107 flag is set. + /// + public bool WasRemovedInVp107 = false; + /// /// If put on a field, this is the info from C#'s reflection API. /// @@ -249,9 +265,9 @@ public object GetValue(object obj) return Field != null ? Field.GetValue(obj) : null; } - protected void WriteStart(BinaryWriter writer, int dataLength, HashWriter hashWriter) + protected void WriteStart(BinaryWriter writer, int dataLength, HashWriter hashWriter, string name = null) { - var tag = Encoding.Default.GetBytes(Name); + var tag = Encoding.Default.GetBytes(name ?? Name); if (Name.Length < 4) { tag = tag.Concat(new byte[4 - Name.Length]).ToArray(); } diff --git a/VisualPinball.Engine/IO/BiffColorAttribute.cs b/VisualPinball.Engine/IO/BiffColorAttribute.cs index 23b5ca1a2..0192cc7f5 100644 --- a/VisualPinball.Engine/IO/BiffColorAttribute.cs +++ b/VisualPinball.Engine/IO/BiffColorAttribute.cs @@ -46,7 +46,7 @@ private static void WriteColor(BinaryWriter writer, Color value) private Color ReadColor(BinaryReader reader, int len) { - return new Color(reader.ReadInt32(), ColorFormat); + return new Color(reader.ReadUInt32(), ColorFormat); } } } diff --git a/VisualPinball.Engine/IO/BiffData.cs b/VisualPinball.Engine/IO/BiffData.cs index c20de3990..d74bbba13 100644 --- a/VisualPinball.Engine/IO/BiffData.cs +++ b/VisualPinball.Engine/IO/BiffData.cs @@ -141,8 +141,11 @@ protected static void Init(Type type, Dictionary> at /// protected static T Load(T obj, BinaryReader reader, Dictionary> attributes) where T : BiffData { - + #if WRITE_VP106 || WRITE_VP106 + var ignoredTags = typeof(T).GetCustomAttributes().Where(a => !a.IsDeprecatedInVP).Select(a => a.Name).ToArray(); + #else var ignoredTags = typeof(T).GetCustomAttributes().Select(a => a.Name).ToArray(); + #endif // initially read length and BIFF record name var len = reader.ReadInt32(); @@ -204,6 +207,12 @@ protected void WriteRecord(BinaryWriter writer, Dictionary !a[0].SkipWrite && !SkipWrite(a[0])) + #if WRITE_VP107 + .Where(a => !a[0].IsVpeEnhancement && !a[0].WasRemovedInVp107) + #endif + #if WRITE_VP106 + .Where(a => !a[0].IsVpeEnhancement && !a[0].WasAddedInVp107) + #endif .Select(a => a[0] as ISortableBiffRecord) .Concat(UnknownRecords ?? new List()) .OrderBy(r => r.GetPosition()); diff --git a/VisualPinball.Engine/IO/BiffIgnoreAttribute.cs b/VisualPinball.Engine/IO/BiffIgnoreAttribute.cs index 8ccccd52c..592c45ca6 100644 --- a/VisualPinball.Engine/IO/BiffIgnoreAttribute.cs +++ b/VisualPinball.Engine/IO/BiffIgnoreAttribute.cs @@ -23,6 +23,11 @@ public class BiffIgnoreAttribute : Attribute { public readonly string Name; + /// + /// If set, marks this as "deprecated in VP", which still writes it when the WRITE_VP106 or WRITE_VP107 flag is set. + /// + public bool IsDeprecatedInVP; + public BiffIgnoreAttribute(string name) { Name = name; diff --git a/VisualPinball.Engine/IO/BiffStringAttribute.cs b/VisualPinball.Engine/IO/BiffStringAttribute.cs index 48437d85e..880d1666d 100644 --- a/VisualPinball.Engine/IO/BiffStringAttribute.cs +++ b/VisualPinball.Engine/IO/BiffStringAttribute.cs @@ -23,8 +23,7 @@ namespace VisualPinball.Engine.IO { public class BiffStringAttribute : BiffAttribute { - - private static readonly Encoding StringEncoding = Encoding.ASCII; + private static readonly Encoding StringEncoding = Encoding.GetEncoding("iso-8859-1"); /// /// Wide strings have a zero byte between each character. diff --git a/VisualPinball.Engine/Math/Color.cs b/VisualPinball.Engine/Math/Color.cs index 6338d511c..9bde27d77 100644 --- a/VisualPinball.Engine/Math/Color.cs +++ b/VisualPinball.Engine/Math/Color.cs @@ -31,7 +31,7 @@ public class Color public float B => Blue / 255f; public float A => Alpha / 255f; - public int Bgr => Blue * 65536 + Green * 256 + Red; + public uint Bgr => (uint)Alpha * 16777216 + (uint)Blue * 65536 + (uint)Green * 256 + (uint)Red; public Color(int red, int green, int blue, int alpha) { @@ -41,19 +41,20 @@ public Color(int red, int green, int blue, int alpha) Alpha = alpha; } - public Color(long color, ColorFormat format) + public Color(uint color, ColorFormat format) { switch (format) { case ColorFormat.Bgr: Red = (int)(color & 0x000000ff); - Green = (int)(color & 0x0000ff00) >> 8; - Blue = (int)(color & 0x00ff0000) >> 16; + Green = (int)((color & 0x0000ff00) >> 8); + Blue = (int)((color & 0x00ff0000) >> 16); + Alpha = (int)(color >> 24); break; case ColorFormat.Argb: - Red = (int)(color & 0x00ff0000) >> 16; - Green = (int)(color & 0x0000ff00) >> 8; + Red = (int)((color & 0x00ff0000) >> 16); + Green = (int)((color & 0x0000ff00) >> 8); Blue = (int)(color & 0x000000ff); - Alpha = (int)(color & 0xff000000) >> 24; + Alpha = (int)((color & 0xff000000) >> 24); break; } } @@ -73,13 +74,13 @@ public override string ToString() return $"rgba({System.Math.Round(R, 3)}, {System.Math.Round(G, 3)}, {System.Math.Round(B, 3)}, {System.Math.Round(A, 3)})"; } - public int ToInt(ColorFormat format) + public uint ToInt(ColorFormat format) { switch (format) { case ColorFormat.Bgr: - return Red + (Green << 8) + (Blue << 16); + return (uint)Red + ((uint)Green << 8) + ((uint)Blue << 16) + ((uint)Alpha << 24); case ColorFormat.Argb: - return (Red << 16) + (Green << 8) + Blue + (Alpha << 24); + return ((uint)Red << 16) + ((uint)Green << 8) + (uint)Blue + ((uint)Alpha << 24); } return 0; } diff --git a/VisualPinball.Engine/Math/DragPointData.cs b/VisualPinball.Engine/Math/DragPointData.cs index 2d04f9a8c..480136b9e 100644 --- a/VisualPinball.Engine/Math/DragPointData.cs +++ b/VisualPinball.Engine/Math/DragPointData.cs @@ -31,9 +31,6 @@ namespace VisualPinball.Engine.Math { [Serializable] - [BiffIgnore("LANR")] - [BiffIgnore("LAYR")] - [BiffIgnore("LVIS")] public class DragPointData : BiffData { [BiffVertex("VCEN", Pos = 1, WriteAsVertex2D = true)] @@ -60,6 +57,12 @@ public class DragPointData : BiffData [BiffInt("LAYR", Pos = 8)] public int EditorLayer; + [BiffString("LANR", Pos = 9, WasAddedInVp107 = true)] + public string EditorLayerName = string.Empty; + + [BiffBool("LVIS", Pos = 10, WasAddedInVp107 = true)] + public bool EditorLayerVisibility = true; + public float CalcHeight; public override string ToString() diff --git a/VisualPinball.Engine/VPT/BinaryData.cs b/VisualPinball.Engine/VPT/BinaryData.cs index f8bebccdb..010d9fc0e 100644 --- a/VisualPinball.Engine/VPT/BinaryData.cs +++ b/VisualPinball.Engine/VPT/BinaryData.cs @@ -41,7 +41,7 @@ public class BinaryData : ItemData, IImageData [BiffString("NAME", HasExplicitLength = true, Pos = 1)] public string Name = string.Empty; - [BiffString("INME", Pos = 2)] + [BiffString("INME", Pos = 2, WasRemovedInVp107 = true)] public string InternalName = string.Empty; [BiffString("PATH", Pos = 3)] diff --git a/VisualPinball.Engine/VPT/Flasher/FlasherData.cs b/VisualPinball.Engine/VPT/Flasher/FlasherData.cs index 00b8b13fb..ce28bed14 100644 --- a/VisualPinball.Engine/VPT/Flasher/FlasherData.cs +++ b/VisualPinball.Engine/VPT/Flasher/FlasherData.cs @@ -59,9 +59,11 @@ public class FlasherData : ItemData [BiffColor("COLR", Pos = 7)] public Color Color = new Color(0xfffffff, ColorFormat.Bgr); + [TextureReference] [BiffString("IMAG", Pos = 11)] public string ImageA; + [TextureReference] [BiffString("IMAB", Pos = 12)] public string ImageB; diff --git a/VisualPinball.Engine/VPT/Flipper/FlipperData.cs b/VisualPinball.Engine/VPT/Flipper/FlipperData.cs index 490749b6c..af82ccca5 100644 --- a/VisualPinball.Engine/VPT/Flipper/FlipperData.cs +++ b/VisualPinball.Engine/VPT/Flipper/FlipperData.cs @@ -32,9 +32,9 @@ namespace VisualPinball.Engine.VPT.Flipper { [Serializable] - [BiffIgnore("RWDT")] - [BiffIgnore("RHGT")] - [BiffIgnore("RTHK")] + [BiffIgnore("RWDT", IsDeprecatedInVP = true)] + [BiffIgnore("RHGT", IsDeprecatedInVP = true)] + [BiffIgnore("RTHK", IsDeprecatedInVP = true)] public class FlipperData : ItemData { public override string GetName() => Name; @@ -146,7 +146,7 @@ public class FlipperData : ItemData // new fields by VPE // ----------------- - [BiffBool("DWND", Pos = 100, SkipHash = true)] + [BiffBool("DWND", Pos = 100, SkipHash = true, IsVpeEnhancement = true)] public bool IsDualWound; public float OverrideMass; diff --git a/VisualPinball.Engine/VPT/ItemData.cs b/VisualPinball.Engine/VPT/ItemData.cs index edef913a4..e0cb5dbd8 100644 --- a/VisualPinball.Engine/VPT/ItemData.cs +++ b/VisualPinball.Engine/VPT/ItemData.cs @@ -34,14 +34,24 @@ public abstract class ItemData : BiffData public bool IsLocked; [BiffInt("LAYR", Pos = 1001)] - public int EditorLayer; + public int EditorLayer { + get => _editorLayer; + set { + _editorLayer = value; + if (string.IsNullOrEmpty(EditorLayerName)) { + EditorLayerName = $"Layer_{value + 1}"; + } + } + } - [BiffString("LANR", Pos = 1002)] + [BiffString("LANR", Pos = 1002, WasAddedInVp107 = true)] public string EditorLayerName = string.Empty; - [BiffBool("LVIS", Pos = 1003)] + [BiffBool("LVIS", Pos = 1003, WasAddedInVp107 = true)] public bool EditorLayerVisibility = true; + private int _editorLayer; + public abstract string GetName(); public abstract void SetName(string name); diff --git a/VisualPinball.Engine/VPT/Kicker/KickerData.cs b/VisualPinball.Engine/VPT/Kicker/KickerData.cs index 9010b7380..ef0c19fa9 100644 --- a/VisualPinball.Engine/VPT/Kicker/KickerData.cs +++ b/VisualPinball.Engine/VPT/Kicker/KickerData.cs @@ -87,10 +87,10 @@ public class KickerData : ItemData // new fields by VPE // ----------------- - [BiffFloat("ANGL", Pos = 16, SkipHash = true)] + [BiffFloat("ANGL", Pos = 16, SkipHash = true, IsVpeEnhancement = true)] public float Angle = 90f; - [BiffFloat("SPED", Pos = 17, SkipHash = true)] + [BiffFloat("SPED", Pos = 17, SkipHash = true, IsVpeEnhancement = true)] public float Speed = 3f; public KickerData(string name, float x, float y) : base(StoragePrefix.GameItem) diff --git a/VisualPinball.Engine/VPT/MaterialData.cs b/VisualPinball.Engine/VPT/MaterialData.cs index 366148d26..469ba8267 100644 --- a/VisualPinball.Engine/VPT/MaterialData.cs +++ b/VisualPinball.Engine/VPT/MaterialData.cs @@ -34,17 +34,17 @@ internal class MaterialData /// /// can be overriden by texture on object itself /// - public int BaseColor; + public uint BaseColor; /// /// specular of glossy layer /// - public int Glossiness; + public uint Glossiness; /// /// specular of clear coat layer /// - public int ClearCoat; + public uint ClearCoat; /// /// wrap/rim lighting factor (0(off)..1(full)) @@ -91,9 +91,9 @@ public MaterialData(BinaryReader reader) { var startPos = reader.BaseStream.Position; Name = BiffUtil.ReadNullTerminatedString(reader, 32); - BaseColor = reader.ReadInt32(); - Glossiness = reader.ReadInt32(); - ClearCoat = reader.ReadInt32(); + BaseColor = reader.ReadUInt32(); + Glossiness = reader.ReadUInt32(); + ClearCoat = reader.ReadUInt32(); WrapLighting = reader.ReadSingle(); IsMetal = reader.ReadByte(); reader.BaseStream.Seek(3, SeekOrigin.Current); diff --git a/VisualPinball.Engine/VPT/Primitive/PrimitiveData.cs b/VisualPinball.Engine/VPT/Primitive/PrimitiveData.cs index 6277be190..677473034 100644 --- a/VisualPinball.Engine/VPT/Primitive/PrimitiveData.cs +++ b/VisualPinball.Engine/VPT/Primitive/PrimitiveData.cs @@ -57,14 +57,17 @@ public class PrimitiveData : ItemData, IPhysicalData [BiffInt("M3CJ", Pos = 44)] public int CompressedIndices = 0; - [BiffInt("M3AY", Pos = 46)] - public int CompressedAnimationVertices; + /// + /// Is written with mesh below. Also it seems redundant and probably don't even needs to be read. + /// + [BiffInt("M3AY", Pos = 46, SkipWrite = true)] + public int[] CompressedAnimationVertices; [BiffVertices("M3DX", SkipWrite = true)] [BiffVertices("M3CX", IsCompressed = true, Pos = 42)] [BiffIndices("M3DI", SkipWrite = true)] [BiffIndices("M3CI", IsCompressed = true, Pos = 45)] - [BiffAnimation("M3AX", IsCompressed = true, Pos = 47)] + [BiffAnimation("M3AX", IsCompressed = true, Pos = 47 )] public Mesh Mesh = new Mesh(); [BiffFloat("RTV0", Index = 0, Pos = 3)] @@ -180,15 +183,18 @@ public class PrimitiveData : ItemData, IPhysicalData protected override bool SkipWrite(BiffAttribute attr) { - if (!Use3DMesh) { - switch (attr.Name) { - case "M3VN": - case "M3CY": - case "M3FN": - case "M3CJ": + switch (attr.Name) { + case "M3VN": + case "M3CY": + case "M3FN": + case "M3DN": + case "M3CJ": + if (!Use3DMesh) { return true; - } + } + break; } + return false; } @@ -397,16 +403,13 @@ public BiffAnimationAttribute(string name) : base(name) { } public override void Parse(T obj, BinaryReader reader, int len) { - if (obj is PrimitiveData primitiveData) - { - try - { + if (obj is PrimitiveData primitiveData) { + try { ParseAnimation(primitiveData, IsCompressed ? BiffZlib.Decompress(reader.ReadBytes(len)) : reader.ReadBytes(len)); - } - catch (Exception e) - { + + } catch (Exception e) { throw new Exception($"Error parsing animation data for {primitiveData.Name} ({primitiveData.StorageName}).", e); } } @@ -414,10 +417,8 @@ public override void Parse(T obj, BinaryReader reader, int len) public override void Write(TItem obj, BinaryWriter writer, HashWriter hashWriter) { - if (obj is PrimitiveData primitiveData) - { - if (!primitiveData.Use3DMesh) - { + if (obj is PrimitiveData primitiveData) { + if (!primitiveData.Use3DMesh) { // don't write animation if not using 3d mesh return; } @@ -425,32 +426,29 @@ public override void Write(TItem obj, BinaryWriter writer, HashWriter has for (var i = 0; i < primitiveData.Mesh.AnimationFrames.Count; i++) { var animationData = SerializeAnimation(primitiveData.Mesh.AnimationFrames[i]); var data = IsCompressed ? BiffZlib.Compress(animationData) : animationData; + WriteStart(writer, 4, hashWriter, "M3AY"); + writer.Write(data.Length); WriteStart(writer, data.Length, hashWriter); writer.Write(data); hashWriter?.Write(data); } - } - else - { + } else { throw new InvalidOperationException("Unknown type for [" + GetType().Name + "] on field \"" + Name + "\"."); } } private void ParseAnimation(PrimitiveData data, byte[] bytes) { - if (data.NumVertices == 0) - { + if (data.NumVertices == 0) { throw new ArgumentOutOfRangeException(nameof(data), "Cannot create animation when size is unknown."); } - if (bytes.Length != data.NumVertices * Mesh.VertData.Size) - { + if (bytes.Length != data.NumVertices * Mesh.VertData.Size) { throw new ArgumentOutOfRangeException($"Tried to read {data.NumVertices} vertex animations for primitive item \"${data.Name}\" (${data.StorageName}), but had ${bytes.Length} bytes available."); } - if (!(GetValue(data) is Mesh mesh)) - { + if (!(GetValue(data) is Mesh mesh)) { throw new ArgumentException("BiffAnimationAttribute attribute must sit on a Mesh object."); } diff --git a/VisualPinball.Engine/VPT/Surface/SurfaceData.cs b/VisualPinball.Engine/VPT/Surface/SurfaceData.cs index 60a5d1729..b09accd03 100644 --- a/VisualPinball.Engine/VPT/Surface/SurfaceData.cs +++ b/VisualPinball.Engine/VPT/Surface/SurfaceData.cs @@ -74,7 +74,7 @@ public class SurfaceData : ItemData, IPhysicalData public string TopMaterial = string.Empty; [MaterialReference] - [BiffString("MAPH", Pos = 29)] + [BiffString("MAPH", Pos = 30)] public string PhysicsMaterial = string.Empty; [MaterialReference] @@ -99,34 +99,37 @@ public class SurfaceData : ItemData, IPhysicalData [BiffFloat("SLTH", Pos = 19)] public float SlingshotThreshold = 0f; - [BiffBool("SLGA", Pos = 24)] + [BiffBool("SLGA", Pos = 25)] public bool SlingshotAnimation = true; [BiffFloat("ELAS", Pos = 20)] public float Elasticity; - [BiffFloat("WFCT", Pos = 21)] + [BiffFloat("ELFO", Pos = 21, WasAddedInVp107 = true)] + public float ElasticityFalloff; + + [BiffFloat("WFCT", Pos = 22)] public float Friction; - [BiffFloat("WSCT", Pos = 22)] + [BiffFloat("WSCT", Pos = 23)] public float Scatter; - [BiffBool("VSBL", Pos = 23)] + [BiffBool("VSBL", Pos = 24)] public bool IsTopBottomVisible = true; - [BiffBool("OVPH", Pos = 30)] + [BiffBool("OVPH", Pos = 31)] public bool OverwritePhysics = true; - [BiffFloat("DILI", QuantizedUnsignedBits = 8, Pos = 26)] + [BiffFloat("DILI", QuantizedUnsignedBits = 8, Pos = 27)] public float DisableLightingTop; - [BiffFloat("DILB", Pos = 27)] + [BiffFloat("DILB", Pos = 28)] public float DisableLightingBelow; - [BiffBool("SVBL", Pos = 25)] + [BiffBool("SVBL", Pos = 26)] public bool IsSideVisible = true; - [BiffBool("REEN", Pos = 28)] + [BiffBool("REEN", Pos = 29)] public bool IsReflectionEnabled = true; [BiffDragPoint("DPNT", TagAll = true, Pos = 2000)] @@ -143,7 +146,7 @@ public class SurfaceData : ItemData, IPhysicalData // IPhysicalData public float GetElasticity() => Elasticity; - public float GetElasticityFalloff() => 0; + public float GetElasticityFalloff() => ElasticityFalloff; public float GetFriction() => Friction; public float GetScatter() => Scatter; public bool GetOverwritePhysics() => OverwritePhysics; diff --git a/VisualPinball.Engine/VPT/Table/TableBuilder.cs b/VisualPinball.Engine/VPT/Table/TableBuilder.cs index b0ce787bf..814406a2a 100644 --- a/VisualPinball.Engine/VPT/Table/TableBuilder.cs +++ b/VisualPinball.Engine/VPT/Table/TableBuilder.cs @@ -19,6 +19,7 @@ using VisualPinball.Engine.VPT.Bumper; using VisualPinball.Engine.VPT.Flipper; using VisualPinball.Engine.VPT.Light; +using VisualPinball.Engine.VPT.Rubber; using VisualPinball.Engine.VPT.Trough; namespace VisualPinball.Engine.VPT.Table @@ -62,6 +63,14 @@ public TableBuilder AddMaterial(Material material) return this; } + public TableBuilder AddTexture(string name) + { + _table.Textures.Add(new Texture(name)); + _table.Data.NumTextures = _table.Textures.Count; + + return this; + } + public TableBuilder AddFlipper(string name) { var data = new FlipperData($"GameItem{_gameItem++}") { diff --git a/VisualPinball.Engine/VPT/Table/TableData.cs b/VisualPinball.Engine/VPT/Table/TableData.cs index 939de17ef..2ec23b937 100644 --- a/VisualPinball.Engine/VPT/Table/TableData.cs +++ b/VisualPinball.Engine/VPT/Table/TableData.cs @@ -135,6 +135,9 @@ public class TableData : ItemData [BiffString("BLIF", Pos = 66)] public string BallImageFront; + [BiffString("NOTX", Pos = 67.5, WasAddedInVp107 = true)] + public string Notes; + [BiffString("SSHT", Pos = 68)] public string ScreenShot; @@ -173,7 +176,7 @@ public class TableData : ItemData public Color LightAmbient = new Color(0, 0, 0, 255); [BiffInt("LZDI", Pos = 76)] - public int Light0Emission { + public uint Light0Emission { set => Light[0].Emission = new Color(value, ColorFormat.Bgr); get => Light[0].Emission.Bgr; } diff --git a/VisualPinball.Engine/VPT/Table/TableWriter.cs b/VisualPinball.Engine/VPT/Table/TableWriter.cs index aeb2d85e7..a9a77745d 100644 --- a/VisualPinball.Engine/VPT/Table/TableWriter.cs +++ b/VisualPinball.Engine/VPT/Table/TableWriter.cs @@ -15,7 +15,9 @@ // along with this program. If not, see . using System; +using System.Collections.Generic; using System.Linq; +using System.Reflection; using OpenMcdf; using VisualPinball.Engine.IO; @@ -104,6 +106,15 @@ private void WriteGameItems(HashWriter hashWriter) // 2. game items foreach (var writeable in _table.ItemDatas.OrderBy(gi => gi.StorageIndex)) { + + #if !WRITE_VP106 + + // clean material and texture references + CleanInvalidReferences(writeable, v => _table.GetMaterial(v)); + CleanInvalidReferences(writeable, v => _table.GetTexture(v)); + + #endif + writeable.WriteData(_gameStorage); } @@ -114,7 +125,9 @@ private void WriteGameItems(HashWriter hashWriter) } // 5. Mappings + #if !WRITE_VP106 && !WRITE_VP107 _table.Mappings.Data.WriteData(_gameStorage); + #endif } private void WriteImages() @@ -140,5 +153,49 @@ private static void WriteStream(CFStorage storage, string streamName, byte[] dat storage.AddStream(streamName).SetData(data); hashWriter?.Write(data); } + + private static void CleanInvalidReferences(ItemData data, Func getter) where TAttr: Attribute + { + var refs = GetMembersWithAttribute(data); + foreach (var r in refs) { + var value = GetValue(r, data); + if (getter(value) == null) { + SetValue(r, data, string.Empty); + } + } + } + + private static IEnumerable GetMembersWithAttribute(ItemData data) where TAttr: Attribute + { + return data.GetType() + .GetMembers(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) + .Where(member => member.GetCustomAttribute() != null); + } + + private static T GetValue(MemberInfo memberInfo, ItemData data) + { + switch (memberInfo.MemberType) { + case MemberTypes.Field: + return (T)((FieldInfo)memberInfo).GetValue(data); + case MemberTypes.Property: + return (T)((PropertyInfo)memberInfo).GetValue(data); + default: + throw new NotImplementedException(); + } + } + + private static void SetValue(MemberInfo memberInfo, ItemData data, T value) + { + switch (memberInfo.MemberType) { + case MemberTypes.Field: + ((FieldInfo)memberInfo).SetValue(data, value); + break; + case MemberTypes.Property: + ((PropertyInfo)memberInfo).SetValue(data, value); + break; + default: + throw new NotImplementedException(); + } + } } } diff --git a/VisualPinball.Engine/VPT/TextureData.cs b/VisualPinball.Engine/VPT/TextureData.cs index 137429825..a7398d0b9 100644 --- a/VisualPinball.Engine/VPT/TextureData.cs +++ b/VisualPinball.Engine/VPT/TextureData.cs @@ -40,7 +40,7 @@ public class TextureData : ItemData [BiffString("NAME", HasExplicitLength = true, Pos = 1)] public string Name = string.Empty; - [BiffString("INME", Pos = 2)] + [BiffString("INME", Pos = 2, WasRemovedInVp107 = true)] public string InternalName; [BiffString("PATH", Pos = 3)]