diff --git a/CHANGELOG.md b/CHANGELOG.md
index 45416fd2e..8649a7fe2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,7 @@
Built with [Unity 2020.2](https://github.com/freezy/VisualPinball.Engine/pull/255).
### Added
+- [Lamp Manager](https://docs.visualpinball.org/creators-guide/editor/coil-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))
diff --git a/VisualPinball.Engine.Test/VPT/Mappings/CoilPopulationTests.cs b/VisualPinball.Engine.Test/VPT/Mappings/CoilPopulationTests.cs
index 08611e317..cc9602226 100644
--- a/VisualPinball.Engine.Test/VPT/Mappings/CoilPopulationTests.cs
+++ b/VisualPinball.Engine.Test/VPT/Mappings/CoilPopulationTests.cs
@@ -104,7 +104,7 @@ public void ShouldMapAHoldCoilByHint()
}
[Test]
- public void ShouldMapAHoldCoilAsMainCoilIfHintedMainCoilNotFound()
+ public void ShouldCreateMainCoilIfNotFoundByHoldCoil()
{
var table = new TableBuilder()
.AddFlipper("left_flipper")
@@ -123,9 +123,9 @@ public void ShouldMapAHoldCoilAsMainCoilIfHintedMainCoilNotFound()
table.Mappings.Data.Coils[0].PlayfieldItem.Should().Be("left_flipper");
table.Mappings.Data.Coils[0].HoldCoilId.Should().BeEmpty();
- table.Mappings.Data.Coils[1].Id.Should().Be("left_flipper_hold");
+ table.Mappings.Data.Coils[1].Id.Should().Be("foobar");
table.Mappings.Data.Coils[1].PlayfieldItem.Should().BeEmpty();
- table.Mappings.Data.Coils[1].HoldCoilId.Should().BeEmpty();
+ table.Mappings.Data.Coils[1].HoldCoilId.Should().Be("left_flipper_hold");
}
[Test]
diff --git a/VisualPinball.Engine.Test/VPT/Mappings/LampPopulationTests.cs b/VisualPinball.Engine.Test/VPT/Mappings/LampPopulationTests.cs
new file mode 100644
index 000000000..6bbb741cc
--- /dev/null
+++ b/VisualPinball.Engine.Test/VPT/Mappings/LampPopulationTests.cs
@@ -0,0 +1,182 @@
+// 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 System.Linq;
+using FluentAssertions;
+using NUnit.Framework;
+using VisualPinball.Engine.Game.Engines;
+using VisualPinball.Engine.VPT;
+using VisualPinball.Engine.VPT.Mappings;
+using VisualPinball.Engine.VPT.Table;
+
+namespace VisualPinball.Engine.Test.VPT.Mappings
+{
+ public class LampPopulationTests
+ {
+ [Test]
+ public void ShouldMapALampWithTheSameName()
+ {
+ var table = new TableBuilder()
+ .AddLight("some_light")
+ .Build();
+
+ var gameEngineLamps = new[] {
+ new GamelogicEngineLamp {Id = "some_light", Description = "Some Light"}
+ };
+
+ table.Mappings.PopulateLamps(gameEngineLamps, table.Lightables);
+
+ table.Mappings.Data.Lamps.Should().HaveCount(1);
+ table.Mappings.Data.Lamps[0].Destination.Should().Be(CoilDestination.Playfield);
+ table.Mappings.Data.Lamps[0].Id.Should().Be("some_light");
+ table.Mappings.Data.Lamps[0].Description.Should().Be("Some Light");
+ table.Mappings.Data.Lamps[0].PlayfieldItem.Should().Be("some_light");
+ }
+
+ [Test]
+ public void ShouldMapALampWithTheSameNumericalId()
+ {
+ var table = new TableBuilder()
+ .AddLight("l42")
+ .Build();
+
+ var gameEngineLamps = new[] {
+ new GamelogicEngineLamp {Id = "42", Description = "Light 42"}
+ };
+
+ table.Mappings.PopulateLamps(gameEngineLamps, table.Lightables);
+
+ table.Mappings.Data.Lamps.Should().HaveCount(1);
+ table.Mappings.Data.Lamps[0].Destination.Should().Be(CoilDestination.Playfield);
+ table.Mappings.Data.Lamps[0].Id.Should().Be("42");
+ table.Mappings.Data.Lamps[0].Description.Should().Be("Light 42");
+ table.Mappings.Data.Lamps[0].PlayfieldItem.Should().Be("l42");
+ }
+
+ [Test]
+ public void ShouldMapALampWithViaRegex()
+ {
+ var table = new TableBuilder()
+ .AddLight("lamp_foobar_name")
+ .Build();
+
+ var gameEngineLamps = new[] {
+ new GamelogicEngineLamp {Id = "11", Description = "Foobar", PlayfieldItemHint = "_foobar_"}
+ };
+
+ table.Mappings.PopulateLamps(gameEngineLamps, table.Lightables);
+
+ table.Mappings.Data.Lamps.Should().HaveCount(1);
+ table.Mappings.Data.Lamps[0].Destination.Should().Be(CoilDestination.Playfield);
+ table.Mappings.Data.Lamps[0].Id.Should().Be("11");
+ table.Mappings.Data.Lamps[0].Description.Should().Be("Foobar");
+ table.Mappings.Data.Lamps[0].PlayfieldItem.Should().Be("lamp_foobar_name");
+ }
+
+ [Test]
+ public void ShouldNotMapALampWithViaRegex()
+ {
+ var table = new TableBuilder()
+ .AddLight("lamp_foobar_name")
+ .Build();
+
+ var gameEngineLamps = new[] {
+ new GamelogicEngineLamp {Id = "12", Description = "Foobar", PlayfieldItemHint = "^_foobar_$"}
+ };
+
+ table.Mappings.PopulateLamps(gameEngineLamps, table.Lightables);
+
+ table.Mappings.Data.Lamps.Should().HaveCount(1);
+ table.Mappings.Data.Lamps[0].Destination.Should().Be(CoilDestination.Playfield);
+ table.Mappings.Data.Lamps[0].Id.Should().Be("12");
+ table.Mappings.Data.Lamps[0].Description.Should().Be("Foobar");
+ table.Mappings.Data.Lamps[0].PlayfieldItem.Should().BeEmpty();
+ }
+
+ [Test]
+ public void ShouldMapAnRgbLamp()
+ {
+ var table = new TableBuilder()
+ .AddLight("my_rgb_light")
+ .Build();
+
+ var gameEngineLamps = new[] {
+ new GamelogicEngineLamp {Id = "rgb", Description = "RGB", PlayfieldItemHint = "rgb"},
+ new GamelogicEngineLamp {Id = "g", MainLampIdOfGreen = "rgb"},
+ new GamelogicEngineLamp {Id = "b", MainLampIdOfBlue = "rgb"}
+ };
+
+ table.Mappings.PopulateLamps(gameEngineLamps, table.Lightables);
+
+ table.Mappings.Data.Lamps.Should().HaveCount(1);
+ table.Mappings.Data.Lamps[0].Destination.Should().Be(CoilDestination.Playfield);
+ table.Mappings.Data.Lamps[0].Id.Should().Be("rgb");
+ table.Mappings.Data.Lamps[0].Green.Should().Be("g");
+ table.Mappings.Data.Lamps[0].Blue.Should().Be("b");
+ table.Mappings.Data.Lamps[0].Description.Should().Be("RGB");
+ table.Mappings.Data.Lamps[0].PlayfieldItem.Should().Be("my_rgb_light");
+ }
+
+ [Test]
+ public void ShouldCreateMapAnRgbLampIfRisMissing()
+ {
+ var table = new TableBuilder()
+ .AddLight("my_rgb_light")
+ .Build();
+
+ var gameEngineLamps = new[] {
+ new GamelogicEngineLamp {Id = "g", Description = "RGB", MainLampIdOfGreen = "rgb"},
+ new GamelogicEngineLamp {Id = "b", MainLampIdOfBlue = "rgb"}
+ };
+
+ table.Mappings.PopulateLamps(gameEngineLamps, table.Lightables);
+
+ table.Mappings.Data.Lamps.Should().HaveCount(1);
+ table.Mappings.Data.Lamps[0].Destination.Should().Be(CoilDestination.Playfield);
+ table.Mappings.Data.Lamps[0].Id.Should().Be("rgb");
+ table.Mappings.Data.Lamps[0].Green.Should().Be("g");
+ table.Mappings.Data.Lamps[0].Blue.Should().Be("b");
+ table.Mappings.Data.Lamps[0].Description.Should().BeEmpty();
+ table.Mappings.Data.Lamps[0].PlayfieldItem.Should().BeEmpty();
+ }
+
+ [Test]
+ public void ShouldReturnAllLampIds()
+ {
+ var table = new TableBuilder()
+ .AddLight("l11")
+ .AddLight("l12")
+ .Build();
+
+ var gameEngineLamps = new[] {
+ new GamelogicEngineLamp {Id = "11" },
+ };
+
+ table.Mappings.PopulateLamps(gameEngineLamps, table.Lightables);
+ table.Mappings.Data.AddLamp(new MappingsLampData {
+ Id = "12",
+ Destination = LampDestination.Playfield,
+ PlayfieldItem = "l12"
+ });
+
+ var lampIds = table.Mappings.GetLamps(gameEngineLamps).ToArray();
+
+ lampIds.Length.Should().Be(2);
+ lampIds[0].Id.Should().Be("11");
+ lampIds[1].Id.Should().Be("12");
+ }
+ }
+}
diff --git a/VisualPinball.Engine.Test/VPT/MaterialFileTests.cs b/VisualPinball.Engine.Test/VPT/MaterialFileTests.cs
index 4cd3b6605..7b088b9d6 100644
--- a/VisualPinball.Engine.Test/VPT/MaterialFileTests.cs
+++ b/VisualPinball.Engine.Test/VPT/MaterialFileTests.cs
@@ -1,5 +1,5 @@
// Visual Pinball Engine
-// Copyright (C) 2020 freezy and VPE Team
+// 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
diff --git a/VisualPinball.Engine/Game/Engines/GamelogicEngineLamp.cs b/VisualPinball.Engine/Game/Engines/GamelogicEngineLamp.cs
new file mode 100644
index 000000000..dfdb186d2
--- /dev/null
+++ b/VisualPinball.Engine/Game/Engines/GamelogicEngineLamp.cs
@@ -0,0 +1,28 @@
+// 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 .
+
+namespace VisualPinball.Engine.Game.Engines
+{
+ public struct GamelogicEngineLamp
+ {
+ public string Id;
+ public string Description;
+ public string PlayfieldItemHint;
+ public int TypeHint;
+ public string MainLampIdOfGreen;
+ public string MainLampIdOfBlue;
+ }
+}
diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/WireDestConfig.cs.meta b/VisualPinball.Engine/Game/Engines/GamelogicEngineLamp.cs.meta
similarity index 83%
rename from VisualPinball.Unity/VisualPinball.Unity/Game/WireDestConfig.cs.meta
rename to VisualPinball.Engine/Game/Engines/GamelogicEngineLamp.cs.meta
index 8a8f1a663..850a7ce34 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/Game/WireDestConfig.cs.meta
+++ b/VisualPinball.Engine/Game/Engines/GamelogicEngineLamp.cs.meta
@@ -1,5 +1,5 @@
fileFormatVersion: 2
-guid: cf8ea31819f4e2b47b6a5ccd3cfce295
+guid: 359865907fa734abd8d0543d3f670a1a
MonoImporter:
externalObjects: {}
serializedVersion: 2
diff --git a/VisualPinball.Engine/Game/ILightable.cs b/VisualPinball.Engine/Game/ILightable.cs
new file mode 100644
index 000000000..368d15dc0
--- /dev/null
+++ b/VisualPinball.Engine/Game/ILightable.cs
@@ -0,0 +1,23 @@
+// 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 .
+
+namespace VisualPinball.Engine.Game
+{
+ public interface ILightable
+ {
+ string Name { get; }
+ }
+}
diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/IGameEngineAuthoring.cs.meta b/VisualPinball.Engine/Game/ILightable.cs.meta
similarity index 83%
rename from VisualPinball.Unity/VisualPinball.Unity/VPT/IGameEngineAuthoring.cs.meta
rename to VisualPinball.Engine/Game/ILightable.cs.meta
index f6b23cfec..7f3f191f8 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/VPT/IGameEngineAuthoring.cs.meta
+++ b/VisualPinball.Engine/Game/ILightable.cs.meta
@@ -1,5 +1,5 @@
fileFormatVersion: 2
-guid: e961a77fb54254a69b2968b8c3ab3532
+guid: 4297c2291ecb74623b898c9acf230dac
MonoImporter:
externalObjects: {}
serializedVersion: 2
diff --git a/VisualPinball.Engine/IO/BiffMappingsLampAttribute.cs b/VisualPinball.Engine/IO/BiffMappingsLampAttribute.cs
new file mode 100644
index 000000000..7315070e1
--- /dev/null
+++ b/VisualPinball.Engine/IO/BiffMappingsLampAttribute.cs
@@ -0,0 +1,47 @@
+// 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 System.IO;
+using VisualPinball.Engine.VPT.Mappings;
+using VisualPinball.Engine.VPT.Table;
+
+namespace VisualPinball.Engine.IO
+{
+ public class BiffMappingsLampAttribute : BiffAttribute
+ {
+ public BiffMappingsLampAttribute(string name) : base(name) { }
+
+ public override void Parse(T obj, BinaryReader reader, int len)
+ {
+ ParseValue(obj, reader, len, ReadMappingsLamp);
+ }
+
+ public override void Write(TItem obj, BinaryWriter writer, HashWriter hashWriter)
+ {
+ WriteValue(obj, writer, (w, v) => WriteMappingsLamp(w, v, hashWriter), hashWriter, x => 0);
+ }
+
+ private static void WriteMappingsLamp(BinaryWriter writer, BiffData value, HashWriter hashWriter)
+ {
+ value.Write(writer, hashWriter);
+ }
+
+ private static MappingsLampData ReadMappingsLamp(BinaryReader reader, int len)
+ {
+ return new MappingsLampData(reader);
+ }
+ }
+}
diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/DefaultGameEngineAuthoring.cs.meta b/VisualPinball.Engine/IO/BiffMappingsLampAttribute.cs.meta
similarity index 61%
rename from VisualPinball.Unity/VisualPinball.Unity/Game/DefaultGameEngineAuthoring.cs.meta
rename to VisualPinball.Engine/IO/BiffMappingsLampAttribute.cs.meta
index 5687a8b9d..853608b78 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/Game/DefaultGameEngineAuthoring.cs.meta
+++ b/VisualPinball.Engine/IO/BiffMappingsLampAttribute.cs.meta
@@ -1,11 +1,11 @@
fileFormatVersion: 2
-guid: 3d509f22d6cc7324b8de9bd704baeb75
+guid: 52ca1e80f328c42089e3f035bb833401
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
- icon: {fileID: 2800000, guid: 5d2f099f5d6bcdd47ad8b5cd9f62e65c, type: 3}
+ icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
diff --git a/VisualPinball.Engine/Math/Color.cs b/VisualPinball.Engine/Math/Color.cs
index bb7e2a31c..6338d511c 100644
--- a/VisualPinball.Engine/Math/Color.cs
+++ b/VisualPinball.Engine/Math/Color.cs
@@ -90,6 +90,11 @@ public enum ColorFormat
Bgr, Argb
}
+ public enum ColorChannel
+ {
+ Red, Green, Blue, Alpha
+ }
+
public static class Colors
{
public static readonly Color Black = new Color(0, 0, 0, 255);
diff --git a/VisualPinball.Engine/VPT/Enums.cs b/VisualPinball.Engine/VPT/Enums.cs
index 1a3fa6b7e..64a595360 100644
--- a/VisualPinball.Engine/VPT/Enums.cs
+++ b/VisualPinball.Engine/VPT/Enums.cs
@@ -171,6 +171,7 @@ public static class CoilDestination
{
public const int Playfield = 0;
public const int Device = 1;
+ public const int Lamp = 2;
}
public static class CoilType
@@ -190,4 +191,24 @@ public static class WireType
public const int OnOff = 0;
public const int Pulse = 1;
}
+
+ public static class LampDestination
+ {
+ public const int Playfield = 0;
+ public const int Device = 1;
+ }
+
+ public static class LampSource
+ {
+ public const int Lamps = 0;
+ public const int Coils = 1;
+ }
+
+ public static class LampType
+ {
+ public const int SingleOnOff = 0;
+ public const int SingleFading = 1;
+ public const int RgbMulti = 2;
+ public const int Rgb = 3;
+ }
}
diff --git a/VisualPinball.Engine/VPT/Flasher/Flasher.cs b/VisualPinball.Engine/VPT/Flasher/Flasher.cs
index 10fd3552f..ce730ad03 100644
--- a/VisualPinball.Engine/VPT/Flasher/Flasher.cs
+++ b/VisualPinball.Engine/VPT/Flasher/Flasher.cs
@@ -15,10 +15,11 @@
// along with this program. If not, see .
using System.IO;
+using VisualPinball.Engine.Game;
namespace VisualPinball.Engine.VPT.Flasher
{
- public class Flasher : Item
+ public class Flasher : Item, ILightable
{
public override string ItemName { get; } = "Flasher";
public override string ItemGroupName { get; } = "Flashers";
diff --git a/VisualPinball.Engine/VPT/Light/Light.cs b/VisualPinball.Engine/VPT/Light/Light.cs
index 18d9457a2..ce2c2bbbc 100644
--- a/VisualPinball.Engine/VPT/Light/Light.cs
+++ b/VisualPinball.Engine/VPT/Light/Light.cs
@@ -20,7 +20,7 @@
namespace VisualPinball.Engine.VPT.Light
{
- public class Light : Item, IRenderable
+ public class Light : Item, IRenderable, ILightable
{
public override string ItemName { get; } = "Light";
public override string ItemGroupName { get; } = "Lights";
diff --git a/VisualPinball.Engine/VPT/Mappings/Mappings.cs b/VisualPinball.Engine/VPT/Mappings/Mappings.cs
index 421215593..78ecafe4c 100644
--- a/VisualPinball.Engine/VPT/Mappings/Mappings.cs
+++ b/VisualPinball.Engine/VPT/Mappings/Mappings.cs
@@ -238,11 +238,12 @@ public void PopulateCoils(GamelogicEngineCoil[] engineCoils, IEnumerable coils,
#endregion
+ #region Lamp Population
+
+ ///
+ /// Auto-matches the lamps provided by the gamelogic engine with the
+ /// lamps on the playfield.
+ ///
+ /// List of lamps provided by the gamelogic engine
+ /// List of lamps on the playfield
+ public void PopulateLamps(GamelogicEngineLamp[] engineLamps, IEnumerable tableLamps)
+ {
+ var lamps = tableLamps
+ .GroupBy(x => x.Name.ToLower())
+ .ToDictionary(x => x.Key, x => x.First());
+
+ var gbLamps = new List();
+ foreach (var engineLamp in GetLamps(engineLamps)) {
+
+ var lampMapping = Data.Lamps.FirstOrDefault(mappingsLampData => mappingsLampData.Id == engineLamp.Id);
+ if (lampMapping == null) {
+
+ // we'll handle those in a second loop when all the R lamps are added
+ if (!string.IsNullOrEmpty(engineLamp.MainLampIdOfGreen) || !string.IsNullOrEmpty(engineLamp.MainLampIdOfBlue)) {
+ gbLamps.Add(engineLamp);
+ continue;
+ }
+
+ var description = string.IsNullOrEmpty(engineLamp.Description) ? string.Empty : engineLamp.Description;
+ var playfieldItem = GuessPlayfieldLamp(lamps, engineLamp);
+
+ Data.AddLamp(new MappingsLampData {
+ Id = engineLamp.Id,
+ Description = description,
+ Destination = LampDestination.Playfield,
+ PlayfieldItem = playfieldItem != null ? playfieldItem.Name : string.Empty,
+ });
+ }
+ }
+
+ foreach (var gbLamp in gbLamps) {
+ var rLampId = !string.IsNullOrEmpty(gbLamp.MainLampIdOfGreen) ? gbLamp.MainLampIdOfGreen : gbLamp.MainLampIdOfBlue;
+ var rLamp = Data.Lamps.FirstOrDefault(c => c.Id == rLampId);
+ if (rLamp == null) {
+ var playfieldItem = GuessPlayfieldLamp(lamps, gbLamp);
+ rLamp = new MappingsLampData {
+ Id = rLampId,
+ Destination = LampDestination.Playfield,
+ PlayfieldItem = playfieldItem != null ? playfieldItem.Name : string.Empty,
+ };
+ Data.AddLamp(rLamp);
+ }
+
+ rLamp.Type = LampType.RgbMulti;
+ if (!string.IsNullOrEmpty(gbLamp.MainLampIdOfGreen)) {
+ rLamp.Green = gbLamp.Id;
+
+ } else {
+ rLamp.Blue = gbLamp.Id;
+ }
+ }
+ }
+
+ ///
+ /// Returns a sorted list of lamp names from the gamelogic engine,
+ /// appended with the additional names in the lamp mapping. In short,
+ /// the list of lamp names to choose from.
+ ///
+ /// Lamp names provided by the gamelogic engine
+ /// All lamp names
+ public IEnumerable GetLamps(GamelogicEngineLamp[] engineLamps)
+ {
+ var lamps = new List();
+
+ // first, add lamps from the gamelogic engine
+ if (engineLamps != null) {
+ lamps.AddRange(engineLamps);
+ }
+
+ // then add lamp ids that were added manually
+ foreach (var mappingsLampData in Data.Lamps) {
+ if (!lamps.Exists(entry => entry.Id == mappingsLampData.Id)) {
+ lamps.Add(new GamelogicEngineLamp {
+ Id = mappingsLampData.Id
+ });
+ }
+ }
+
+ lamps.Sort((s1, s2) => s1.Id.CompareTo(s2.Id));
+ return lamps;
+ }
+
+ private static ILightable GuessPlayfieldLamp(Dictionary lamps, GamelogicEngineLamp engineLamp)
+ {
+ // first, match by regex if hint provided
+ if (!string.IsNullOrEmpty(engineLamp.PlayfieldItemHint)) {
+ foreach (var lampName in lamps.Keys) {
+ var regex = new Regex(engineLamp.PlayfieldItemHint.ToLower());
+ if (regex.Match(lampName).Success) {
+ return lamps[lampName];
+ }
+ }
+ }
+
+ // second, match by "lXX" or name
+ var matchKey = int.TryParse(engineLamp.Id, out var numericLampId)
+ ? $"l{numericLampId}"
+ : engineLamp.Id;
+
+ return lamps.ContainsKey(matchKey) ? lamps[matchKey] : null;
+ }
+
+ #endregion
}
}
diff --git a/VisualPinball.Engine/VPT/Mappings/MappingsData.cs b/VisualPinball.Engine/VPT/Mappings/MappingsData.cs
index 0e36c3515..6effdd201 100644
--- a/VisualPinball.Engine/VPT/Mappings/MappingsData.cs
+++ b/VisualPinball.Engine/VPT/Mappings/MappingsData.cs
@@ -48,6 +48,9 @@ public class MappingsData : ItemData
[BiffMappingsWireAttribute("MWIR", TagAll = true, Pos = 1002)]
public MappingsWireData[] Wires = Array.Empty();
+ [BiffMappingsLampAttribute("MLMP", TagAll = true, Pos = 1003)]
+ public MappingsLampData[] Lamps = Array.Empty();
+
#region BIFF
static MappingsData()
@@ -131,5 +134,24 @@ public void RemoveAllWires()
}
#endregion
+
+ #region Lamps
+
+ public void AddLamp(MappingsLampData data)
+ {
+ Lamps = Lamps.Append(data).ToArray();
+ }
+
+ public void RemoveLamp(MappingsLampData data)
+ {
+ Lamps = Lamps.Except(new[] { data }).ToArray();
+ }
+
+ public void RemoveAllLamps()
+ {
+ Lamps = Array.Empty();
+ }
+
+ #endregion
}
}
diff --git a/VisualPinball.Engine/VPT/Mappings/MappingsLampData.cs b/VisualPinball.Engine/VPT/Mappings/MappingsLampData.cs
new file mode 100644
index 000000000..5584bcdf5
--- /dev/null
+++ b/VisualPinball.Engine/VPT/Mappings/MappingsLampData.cs
@@ -0,0 +1,94 @@
+// 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 .
+
+#region ReSharper
+
+// ReSharper disable UnassignedField.Global
+// ReSharper disable StringLiteralTypo
+// ReSharper disable FieldCanBeMadeReadOnly.Global
+// ReSharper disable ConvertToConstant.Global
+// ReSharper disable CompareOfFloatsByEqualityOperator
+
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using VisualPinball.Engine.IO;
+using VisualPinball.Engine.VPT.Table;
+
+namespace VisualPinball.Engine.VPT.Mappings
+{
+ [Serializable]
+ public class MappingsLampData : BiffData
+ {
+ [BiffString("MCID", IsWideString = true, Pos = 1)]
+ public string Id = string.Empty;
+
+ [BiffInt("LSRC", Pos = 2)]
+ public int Source = LampSource.Lamps;
+
+ [BiffString("DESC", IsWideString = true, Pos = 3)]
+ public string Description = string.Empty;
+
+ [BiffInt("DEST", Pos = 4)]
+ public int Destination = LampDestination.Playfield;
+
+ [BiffString("PITM", IsWideString = true, Pos = 5)]
+ public string PlayfieldItem = string.Empty;
+
+ [BiffString("DEVC", IsWideString = true, Pos = 6)]
+ public string Device = string.Empty;
+
+ [BiffString("DITM", IsWideString = true, Pos = 7)]
+ public string DeviceItem = string.Empty;
+
+ [BiffInt("LTYP", Pos = 8)]
+ public int Type = LampType.SingleOnOff;
+
+ [BiffString("RGBG", IsWideString = true, Pos = 9)]
+ public string Green = string.Empty;
+
+ [BiffString("RGBB", IsWideString = true, Pos = 10)]
+ public string Blue = string.Empty;
+
+ #region BIFF
+
+ static MappingsLampData()
+ {
+ Init(typeof(MappingsLampData), Attributes);
+ }
+
+ public MappingsLampData() : base(null)
+ {
+ }
+
+ public MappingsLampData(BinaryReader reader) : base(null)
+ {
+ Load(this, reader, Attributes);
+ }
+
+ public override void Write(BinaryWriter writer, HashWriter hashWriter)
+ {
+ WriteRecord(writer, Attributes, hashWriter);
+ WriteEnd(writer, hashWriter);
+ }
+
+ private static readonly Dictionary> Attributes = new Dictionary>();
+
+ #endregion
+ }
+}
diff --git a/VisualPinball.Engine/VPT/Mappings/MappingsLampData.cs.meta b/VisualPinball.Engine/VPT/Mappings/MappingsLampData.cs.meta
new file mode 100644
index 000000000..4316bf6c2
--- /dev/null
+++ b/VisualPinball.Engine/VPT/Mappings/MappingsLampData.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: daf52ad04f73240db80698c7780505c7
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/VisualPinball.Engine/VPT/MaterialLoader.cs b/VisualPinball.Engine/VPT/MaterialLoader.cs
index 1cdb680ae..cf571835d 100644
--- a/VisualPinball.Engine/VPT/MaterialLoader.cs
+++ b/VisualPinball.Engine/VPT/MaterialLoader.cs
@@ -1,5 +1,5 @@
// Visual Pinball Engine
-// Copyright (C) 2020 freezy and VPE Team
+// 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
diff --git a/VisualPinball.Engine/VPT/Table/Table.cs b/VisualPinball.Engine/VPT/Table/Table.cs
index f5b92d1fc..d2554d1e2 100644
--- a/VisualPinball.Engine/VPT/Table/Table.cs
+++ b/VisualPinball.Engine/VPT/Table/Table.cs
@@ -249,6 +249,10 @@ private IEnumerable ApplyColliderOverrides(IHittable hittable)
public IEnumerable CoilableDevices => new ICoilableDevice[0]
.Concat(_troughs.Values);
+ public IEnumerable Lightables => new ILightable[0]
+ .Concat(_lights.Values)
+ .Concat(_flashers.Values);
+
private void AddItem(string name, TItem item, IDictionary d, bool updateStorageIndices) where TItem : IItem
{
if (updateStorageIndices) {
diff --git a/VisualPinball.Engine/VPT/Table/TableBuilder.cs b/VisualPinball.Engine/VPT/Table/TableBuilder.cs
index b081c57ba..b0ce787bf 100644
--- a/VisualPinball.Engine/VPT/Table/TableBuilder.cs
+++ b/VisualPinball.Engine/VPT/Table/TableBuilder.cs
@@ -18,6 +18,7 @@
using VisualPinball.Engine.Math;
using VisualPinball.Engine.VPT.Bumper;
using VisualPinball.Engine.VPT.Flipper;
+using VisualPinball.Engine.VPT.Light;
using VisualPinball.Engine.VPT.Trough;
namespace VisualPinball.Engine.VPT.Table
@@ -81,6 +82,12 @@ public TableBuilder AddTrough(string name)
return this;
}
+ public TableBuilder AddLight(string name)
+ {
+ _table.Add(new Light.Light(new LightData(name, 500, 500)));
+ return this;
+ }
+
public Table Build(string name = null)
{
if (name != null) {
diff --git a/VisualPinball.Unity/Documentation~/creators-guide/editor/coil-manager-lamp.png b/VisualPinball.Unity/Documentation~/creators-guide/editor/coil-manager-lamp.png
new file mode 100644
index 000000000..6b875cb46
Binary files /dev/null and b/VisualPinball.Unity/Documentation~/creators-guide/editor/coil-manager-lamp.png differ
diff --git a/VisualPinball.Unity/Documentation~/creators-guide/editor/coil-manager.md b/VisualPinball.Unity/Documentation~/creators-guide/editor/coil-manager.md
index 394f5129e..a53f35941 100644
--- a/VisualPinball.Unity/Documentation~/creators-guide/editor/coil-manager.md
+++ b/VisualPinball.Unity/Documentation~/creators-guide/editor/coil-manager.md
@@ -26,10 +26,11 @@ The **Description** column is optional. If you're setting up a re-creation, you
### Destination
-The **Destination** column defines where the element in the following column is located. There are two options:
+The **Destination** column defines where the element in the following column is located. There are three options:
- *Playfield* lets you select a game element on the playfield that features the coil
- *Device* lets you choose a *coil device*, a mechanism which may include multiple coils, such as a [trough](../manual/mechanisms/troughs.md).
+- *Lamp* sets the coil to be configured in the lamp manager (see [flashers in the lamp manager](lamp-manager.html#flashers) for more details).
### Element
diff --git a/VisualPinball.Unity/Documentation~/creators-guide/editor/lamp-manager-coil.png b/VisualPinball.Unity/Documentation~/creators-guide/editor/lamp-manager-coil.png
new file mode 100644
index 000000000..c3b798344
Binary files /dev/null and b/VisualPinball.Unity/Documentation~/creators-guide/editor/lamp-manager-coil.png differ
diff --git a/VisualPinball.Unity/Documentation~/creators-guide/editor/lamp-manager-gameplay.gif b/VisualPinball.Unity/Documentation~/creators-guide/editor/lamp-manager-gameplay.gif
new file mode 100644
index 000000000..be280938e
Binary files /dev/null and b/VisualPinball.Unity/Documentation~/creators-guide/editor/lamp-manager-gameplay.gif differ
diff --git a/VisualPinball.Unity/Documentation~/creators-guide/editor/lamp-manager.md b/VisualPinball.Unity/Documentation~/creators-guide/editor/lamp-manager.md
new file mode 100644
index 000000000..bd345b2de
--- /dev/null
+++ b/VisualPinball.Unity/Documentation~/creators-guide/editor/lamp-manager.md
@@ -0,0 +1,93 @@
+---
+description: The lamp manager lets you connect and configure lights, flashers and GIs of the playfield to the gamelogic engine.
+---
+# Lamp Manager
+
+There are many types of lamps a real pinball machine might use, and there are different ways a gamelogic engine might address them. VPE uses the Unity game engine to accurately simulate lamps on the playfield. Lamps have a standardized set of parameters, which can be tweaked in the editor. Lamps in a game are also dynamic, so the gamelogic engine will toggle them, fade them, or even change their color.
+
+In order to link each of the playfield lamps to the gamelogic engine and configure how they react during gameplay, the *Lamp Manager* is used. You can find it under *Visual Pinball -> Lamp Manager*.
+
+
+
+> [!note]
+> We use the terms *lights* and *lamps* as follows:
+> - With **light** we're referring to the render engine's [light](https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@10.2/manual/Light-Component.html). It's a simulated light source and doesn't have to be a physical element on the table, but can also refer to the sun, some directional scene light, or other types of lighting used in the simulation.
+> - With **lamp** we're referring to a "bulb" that is "screwed" into the table. It's more of a logical component VPE has to deal with during gameplay, decoupled for the rendering aspect.
+
+## About Lamps
+
+Physical machines have a bunch of different concepts when it comes to lighting. The vast majority of solid state machines from the eighties until the early 2010s used a **lamp matrix**, where lamps were addressed by row/column, and they only could be turned on or off. Historically, incandescent light bulbs were used, which resulted in a warm-up period until they reached full illuminosity (and a cool-down period when turned off). For this, VPE adopted the fade-in and fade-out properties from Visual Pinball that can be set on a light.
+
+Later machines used single colored **LEDs** that were each directly connected to the controller board (see also: [Lights vs LEDs](https://docs.missionpinball.org/en/latest/mechs/lights/lights_versus_leds.html)). Contrarily to matrix lamps, the intensity here could be set more fine grained by the game software.
+
+More recently, games started using **RGB-LEDs** that are additionally able to change the color during gameplay. In VPE, these can be handled in two different ways:
+- As three single connections from the gamelogic engine (e.g. that's what PinMAME provides)
+- With a single RGB connection, where the gamelogic engine always provides the full color (e.g. MPF, or custom table logic)
+
+Additionally, most pinball machines come with **GI strips**, which are a set of bulbs used for global illumination of the playfield. All lights from a strip are addressed at once, so one gamelogic GI strip maps to multiple lamps on the playfield.
+
+Finally, high-powered lamps such as flashers might appear under the gamelogic engine's **coil outputs**, since those lamps operate with the same voltage and have the same properties as coils.
+
+## Setup
+
+Every row in the lamp manager corresponds to a logical connection between the gamelogic engine and the lamp on the playfield. A lamp can be linked to multiple outputs, and an output can be linked to multiple lamps.
+
+### IDs
+
+The first column, **ID** shows the name that the gamelogic engine exports for each lamp.
+
+> [!note]
+> As we cannot be 100% sure that the gamelogic engine has accurate data about the lamp names, you can also add lamp IDs manually, but that should be the exception.
+
+### Description
+
+The **Description** column is optional. If you're setting up a re-creation, you would typically use this for the lamp name from the game manual. It's purely for your own benefit, and you can keep this empty if you want.
+
+### Destination
+
+The **Destination** defines where the lamp is located. Currently, *Playfield* is the only option.
+
+### Element
+
+Under the **Element** column, you choose which lamp among the game items on the playfield should be controlled.
+
+### Type
+
+The **Type** column defines how the signal is interpreted by the lamp. This is important, because the gamelogic engine typically sends integer values to the lamp. There are four types:
+
+- *Single On|Off* - Typically lamps from the lamp matrix. They can only be on or off. Receiving `0` will turn the lamp off, any other value will tell it on.
+- *Single Fading* - Individually connected lamps that can be dimmed by the gamelogic engine. Received values can be `0` to `255`, where `0` turns the lamp off, and `255` sets it to full intensity.
+- *RGB Multi* - An RGB lamp that can change its color during gameplay. Lamps of this type receive three connections, one from each red, green and blue.
+- *RGB* - An RGB lamp that receives its data from a single connection. This is the only mode where the lamp doesn't receive an integer, but an entire color.
+
+### R G B
+
+If the type of the previous column has been set to *RGB Multi*, here is where you link each wire to a color. Note that *red* is always the one shown under the *ID* column, so changing the red link will also change the ID (and vice versa).
+
+## Flashers
+
+When using a gamelogic engine that behaves like real hardware like PinMAME, high-powered lamps such as flashers show usually up as connected driver board.
+
+VPE allows routing coil outputs to lamps. For that, go to the [Coil Manager](coil-manager.md) and select *Lamp* as **Destination**:
+
+
+
+This will make the coil show up in the lamp manager where you can configure it:
+
+
+
+You note that you cannot change the *ID* of the lamp, because it's still linked to the coil. Also, removing or changing the coil destination will remove the entry from the lamp manager. Changing the ID in the coil manager will also update it in the lamp manager.
+
+## GI Strips
+
+There is currently no special support for GI strips. In Visual Pinball, you can put GI lamps into a collection and address the whole collection at once via script. VPE doesn't have this feature yet. In order to hook up GI lamps, you can can add an entry per lamp and link all of them to the same ID.
+
+We want to make this easier in the future, so we're thinking of integrating this into the editor directly.
+
+## Editor vs Runtime
+
+While editing the table in the Unity editor, you can and probably should disable lamps you're not editing. During runtime, VPE first turns all lamps off, then turns on the constant lamps, and then waits for the gamelogic engine for further instructions.
+
+If you run the game in the editor, the lamp manager shows the lamp statuses in real-time:
+
+
diff --git a/VisualPinball.Unity/Documentation~/creators-guide/editor/lamp-manager.png b/VisualPinball.Unity/Documentation~/creators-guide/editor/lamp-manager.png
new file mode 100644
index 000000000..84d106f39
Binary files /dev/null and b/VisualPinball.Unity/Documentation~/creators-guide/editor/lamp-manager.png differ
diff --git a/VisualPinball.Unity/Documentation~/creators-guide/manual/gamelogic-engine.md b/VisualPinball.Unity/Documentation~/creators-guide/manual/gamelogic-engine.md
index ed928c234..f03a003c7 100644
--- a/VisualPinball.Unity/Documentation~/creators-guide/manual/gamelogic-engine.md
+++ b/VisualPinball.Unity/Documentation~/creators-guide/manual/gamelogic-engine.md
@@ -16,6 +16,12 @@ Classic examples of gamelogic engines are [MPF](https://missionpinball.org/) and
In Visual Pinball, the gamelogic engine is part of the table script, which in most cases uses VPM to drive the game. So a part of the table script is about piping data into VPM and handling its outputs (lamp changes, coil changes, and so on).
-Since VPE defines a clear API (like a contract) between the table and the gamelogic engine, we will provide tools to make this easy for you. Currently there is a [Switch Manager](~/creators-guide/editor/switch-manager.md) which manages switches. Soon there will be additional managers for lamps and coils, where you can connect your playfield elements to the gamelogic engine using a UI.
+Since VPE defines a clear API (like a contract) between the table and the gamelogic engine, we can provide tools to make this easy for you. Currently, VPE provides:
+
+- A [Switch Manager](~/creators-guide/editor/switch-manager.md)
+- A [Lamp Manager](~/creators-guide/editor/lamp-manager.md)
+- A [Coil Manager](~/creators-guide/editor/coil-manager.md)
+
+These tools provide a graphical user interface where you can link playfield elements to the gamelogic engine and configure them.
Ultimately, that means if your table uses an existing gamelogic engine like MPF or PinMAME, and the table doesn't contain any exotic game mechanics, that's all you need to do. You can set up your table without a single line of code!
diff --git a/VisualPinball.Unity/Documentation~/creators-guide/toc.yml b/VisualPinball.Unity/Documentation~/creators-guide/toc.yml
index a70efcd06..3e5d68a4d 100644
--- a/VisualPinball.Unity/Documentation~/creators-guide/toc.yml
+++ b/VisualPinball.Unity/Documentation~/creators-guide/toc.yml
@@ -1,5 +1,5 @@
- name: Visual Pinball Engine
- items:
+ items:
- name: Overview
href: introduction/overview.md
- name: Features
@@ -22,11 +22,13 @@
href: editor/switch-manager.md
- name: Coil Manager
href: editor/coil-manager.md
+ - name: Lamp Manager
+ href: editor/lamp-manager.md
- name: Wire Manager
href: editor/wire-manager.md
- name: Multiple Tables
href: editor/multiple-tables.md
- - name: Advanced Topics
+ - name: Advanced Topics
items:
- name: Camera Settings
href: editor/advanced/camera-settings.md
@@ -40,4 +42,4 @@
- name: Troughs / Ball Drains
href: manual/mechanisms/troughs.md
- name: Flippers
- href: manual/mechanisms/flippers.md
\ No newline at end of file
+ href: manual/mechanisms/flippers.md
diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Game/DefaultGamelogicEngineInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Game/DefaultGamelogicEngineInspector.cs
new file mode 100644
index 000000000..dd59b474d
--- /dev/null
+++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Game/DefaultGamelogicEngineInspector.cs
@@ -0,0 +1,65 @@
+// 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 UnityEditor;
+using UnityEngine;
+
+namespace VisualPinball.Unity.Editor
+{
+
+ [CustomEditor(typeof(DefaultGamelogicEngine)), CanEditMultipleObjects]
+ public class DefaultGamelogicEngineInspector : BaseEditor
+ {
+ private DefaultGamelogicEngine _gamelogicEngine;
+
+ private int _value1;
+ private int _value2;
+ private int _value3;
+
+ private Color _color;
+
+ private void OnEnable()
+ {
+ _gamelogicEngine = target as DefaultGamelogicEngine;
+ }
+
+ public override void OnInspectorGUI()
+ {
+ _value1 = EditorGUILayout.IntSlider("Value 1", _value1, 0, 255);
+ _value2 = EditorGUILayout.IntSlider("Value 2", _value2, 0, 255);
+ _value3 = EditorGUILayout.IntSlider("Value 3", _value3, 0, 255);
+
+ if (GUILayout.Button("Apply Each")) {
+ _gamelogicEngine.SetLamp("gi_1", _value1);
+ _gamelogicEngine.SetLamp("gi_2", _value2);
+ _gamelogicEngine.SetLamp("gi_3", _value3);
+ }
+
+ if (GUILayout.Button("Apply At Once")) {
+ _gamelogicEngine.SetLamps(new LampEventArgs[] {
+ new LampEventArgs("gi_1", _value1),
+ new LampEventArgs("gi_2", _value2),
+ new LampEventArgs("gi_3", _value3),
+ });
+ }
+
+ _color = EditorGUILayout.ColorField(_color);
+ if (GUILayout.Button("Apply Color")) {
+ _gamelogicEngine.SetLampColor("gi_4", _color);
+ }
+ }
+ }
+}
diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Game/DefaultGamelogicEngineInspector.cs.meta b/VisualPinball.Unity/VisualPinball.Unity.Editor/Game/DefaultGamelogicEngineInspector.cs.meta
new file mode 100644
index 000000000..6a7813f51
--- /dev/null
+++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Game/DefaultGamelogicEngineInspector.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: cf71671eddfe1234aa2dd13a4c772c11
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Coil/CoilListViewItemRenderer.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Coil/CoilListViewItemRenderer.cs
index e795395b9..1428b8fc3 100644
--- a/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Coil/CoilListViewItemRenderer.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Coil/CoilListViewItemRenderer.cs
@@ -22,12 +22,13 @@
using VisualPinball.Engine.VPT;
using System.Linq;
using VisualPinball.Engine.Game.Engines;
+using VisualPinball.Engine.VPT.Mappings;
namespace VisualPinball.Unity.Editor
{
public class CoilListViewItemRenderer
{
- private readonly string[] OPTIONS_COIL_DESTINATION = { "Playfield", "Device" };
+ private readonly string[] OPTIONS_COIL_DESTINATION = { "Playfield", "Device", "Lamp" };
private readonly string[] OPTIONS_COIL_TYPE = { "Single-Wound", "Dual-Wound" };
private enum CoilListColumn
@@ -40,14 +41,16 @@ private enum CoilListColumn
HoldCoilId = 5,
}
+ private readonly TableAuthoring _tableAuthoring;
private readonly List _gleCoils;
private readonly Dictionary _coils;
private readonly Dictionary _coilDevices;
private AdvancedDropdownState _itemPickDropdownState;
- public CoilListViewItemRenderer(List gleCoils, Dictionary coils, Dictionary coilDevices)
+ public CoilListViewItemRenderer(TableAuthoring tableAuthoring, List gleCoils, Dictionary coils, Dictionary coilDevices)
{
+ _tableAuthoring = tableAuthoring;
_gleCoils = gleCoils;
_coils = coils;
_coilDevices = coilDevices;
@@ -55,10 +58,15 @@ public CoilListViewItemRenderer(List gleCoils, Dictionary updateAction)
{
+ EditorGUI.BeginDisabledGroup(Application.isPlaying);
+ var coilStatuses = Application.isPlaying
+ ? tableAuthoring.gameObject.GetComponent()?.CoilStatuses
+ : null;
+
switch ((CoilListColumn)column)
{
case CoilListColumn.Id:
- RenderId(ref data.Id, id => data.Id = id, data, cellRect, updateAction);
+ RenderId(coilStatuses, ref data.Id, id => UpdateId(data, id), data, cellRect, updateAction);
break;
case CoilListColumn.Description:
RenderDescription(data, cellRect, updateAction);
@@ -74,35 +82,59 @@ public void Render(TableAuthoring tableAuthoring, CoilListData data, Rect cellRe
break;
case CoilListColumn.HoldCoilId:
if (data.Type == CoilType.DualWound) {
- RenderId(ref data.HoldCoilId, id => data.HoldCoilId = id, data, cellRect, updateAction);
+ RenderId(coilStatuses, ref data.HoldCoilId, id => data.HoldCoilId = id, data, cellRect, updateAction);
}
break;
}
+ EditorGUI.EndDisabledGroup();
+ }
+
+ private void UpdateId(CoilListData data, string id)
+ {
+ if (data.Destination == CoilDestination.Lamp) {
+ var lampEntry = _tableAuthoring.Mappings.Lamps.FirstOrDefault(l => l.Id == data.Id && l.Source == LampSource.Coils);
+ if (lampEntry != null) {
+ lampEntry.Id = id;
+ EditorWindow.GetWindow().Reload();
+ }
+ }
+ data.Id = id;
}
- private void RenderId(ref string id, Action setId, CoilListData coilListData, Rect cellRect, Action updateAction)
+ private void RenderId(Dictionary coilStatuses, ref string id, Action setId, CoilListData coilListData, Rect cellRect, Action updateAction)
{
// add some padding
cellRect.x += 2;
cellRect.width -= 4;
var options = new List(_gleCoils.Select(entry => entry.Id).ToArray());
-
if (options.Count > 0) {
options.Add("");
}
-
options.Add("Add...");
+ if (Application.isPlaying && coilStatuses != null) {
+ var iconRect = cellRect;
+ iconRect.width = 20;
+ cellRect.x += 25;
+ cellRect.width -= 25;
+ if (coilStatuses.ContainsKey(id)) {
+ var coilStatus = coilStatuses[id];
+ var icon = Icons.Bolt(IconSize.Small, coilStatus ? IconColor.Orange : IconColor.Gray);
+ var guiColor = GUI.color;
+ GUI.color = Color.clear;
+ EditorGUI.DrawTextureTransparent(iconRect, icon, ScaleMode.ScaleToFit);
+ GUI.color = guiColor;
+ }
+ }
+
EditorGUI.BeginChangeCheck();
var index = EditorGUI.Popup(cellRect, options.IndexOf(id), options.ToArray());
if (EditorGUI.EndChangeCheck()) {
if (index == options.Count - 1) {
PopupWindow.Show(cellRect, new ManagerListTextFieldPopup("ID", "", newId => {
- if (_gleCoils.Exists(entry => entry.Id == newId))
- {
- _gleCoils.Add(new GamelogicEngineCoil
- {
+ if (_gleCoils.Exists(entry => entry.Id == newId)) {
+ _gleCoils.Add(new GamelogicEngineCoil {
Id = newId
});
}
@@ -137,6 +169,22 @@ private void RenderDestination(CoilListData coilListData, Rect cellRect, Action<
{
if (coilListData.Destination != index)
{
+ if (coilListData.Destination == CoilDestination.Lamp) {
+
+ var lampEntry = _tableAuthoring.Mappings.Lamps.FirstOrDefault(l => l.Id == coilListData.Id && l.Source == LampSource.Coils);
+ if (lampEntry != null) {
+ _tableAuthoring.Mappings.RemoveLamp(lampEntry);
+ EditorWindow.GetWindow().Reload();
+ }
+
+ } else if (index == CoilDestination.Lamp) {
+ _tableAuthoring.Mappings.AddLamp(new MappingsLampData {
+ Id = coilListData.Id,
+ Source = LampSource.Coils,
+ Description = coilListData.Description
+ });
+ EditorWindow.GetWindow().Reload();
+ }
coilListData.Destination = index;
updateAction(coilListData);
}
@@ -172,6 +220,12 @@ private void RenderElement(TableAuthoring tableAuthoring, CoilListData coilListD
cellRect.x += cellRect.width + 10f;
RenderDeviceItemElement(coilListData, cellRect, updateAction);
break;
+
+ case CoilDestination.Lamp:
+ cellRect.x -= 25;
+ cellRect.width += 25;
+ EditorGUI.LabelField(cellRect, "Configure in Lamp Manager", new GUIStyle(GUI.skin.label) { fontStyle = FontStyle.Italic });
+ break;
}
}
diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Coil/CoilManager.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Coil/CoilManager.cs
index 292c1ed49..3e860d9f3 100644
--- a/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Coil/CoilManager.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Coil/CoilManager.cs
@@ -16,10 +16,12 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using NLog;
using UnityEditor;
using UnityEngine;
using VisualPinball.Engine.Game.Engines;
+using VisualPinball.Engine.VPT;
using VisualPinball.Engine.VPT.Mappings;
using Logger = NLog.Logger;
using Object = UnityEngine.Object;
@@ -70,7 +72,7 @@ public override void OnEnable()
private void OnFocus()
{
- _listViewItemRenderer = new CoilListViewItemRenderer(_gleCoils, _coils, _coilDevices);
+ _listViewItemRenderer = new CoilListViewItemRenderer(_tableAuthoring, _gleCoils, _coils, _coilDevices);
}
protected override bool SetupCompleted()
@@ -81,7 +83,7 @@ protected override bool SetupCompleted()
return false;
}
- var gle = _tableAuthoring.gameObject.GetComponent();
+ var gle = _tableAuthoring.gameObject.GetComponent();
if (gle == null)
{
@@ -148,6 +150,15 @@ protected override void RemoveData(string undoName, CoilListData data)
RecordUndo(undoName);
_tableAuthoring.Mappings.RemoveCoil(data.MappingsCoilData);
+
+ // if it's a lamp, also delete the lamp entry.
+ if (data.MappingsCoilData.Destination == CoilDestination.Lamp) {
+ var lampEntry = _tableAuthoring.Mappings.Lamps.FirstOrDefault(l => l.Id == data.Id && l.Source == LampSource.Coils);
+ if (lampEntry != null) {
+ _tableAuthoring.Mappings.RemoveLamp(lampEntry);
+ GetWindow().Reload();
+ }
+ }
}
protected override void CloneData(string undoName, string newName, CoilListData data)
@@ -206,8 +217,8 @@ private void RefreshCoilIds()
private GamelogicEngineCoil[] GetAvailableEngineCoils()
{
- var gle = _tableAuthoring.gameObject.GetComponent();
- return gle == null ? new GamelogicEngineCoil[0] : ((IGamelogicEngineWithCoils) gle.GameEngine).AvailableCoils;
+ var gle = _tableAuthoring.gameObject.GetComponent();
+ return gle == null ? new GamelogicEngineCoil[0] : ((IGamelogicEngineWithCoils) gle).AvailableCoils;
}
#endregion
diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/ItemSearchableDropdown.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/ItemSearchableDropdown.cs
index 3d38362a6..0481f342f 100644
--- a/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/ItemSearchableDropdown.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/ItemSearchableDropdown.cs
@@ -50,7 +50,7 @@ protected override AdvancedDropdownItem BuildRoot()
protected override void ItemSelected(AdvancedDropdownItem item)
{
var elementItem = (ElementDropdownItem) item;
- _onElementSelected?.Invoke(elementItem.Item);
+ _onElementSelected?.Invoke(elementItem?.Item);
}
private class ElementDropdownItem : AdvancedDropdownItem where TItem : class, IIdentifiableItemAuthoring
diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Lamp.meta b/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Lamp.meta
new file mode 100644
index 000000000..4a6519ce5
--- /dev/null
+++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Lamp.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 4b3361184c24541a3aa5e00b5899cbf1
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Lamp/LampListData.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Lamp/LampListData.cs
new file mode 100644
index 000000000..02a192600
--- /dev/null
+++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Lamp/LampListData.cs
@@ -0,0 +1,78 @@
+// 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 VisualPinball.Engine.VPT.Mappings;
+
+namespace VisualPinball.Unity.Editor
+{
+ public class LampListData : IManagerListData
+ {
+ [ManagerListColumn(Order = 0, HeaderName = "ID", Width = 135)]
+ public string Name => Id;
+
+ [ManagerListColumn(Order = 1, HeaderName = "Description", Width = 150)]
+ public string Description;
+
+ [ManagerListColumn(Order = 2, HeaderName = "Destination", Width = 150)]
+ public int Destination;
+
+ [ManagerListColumn(Order = 3, HeaderName = "Element", Width = 200)]
+ public string Element;
+
+ [ManagerListColumn(Order = 4, HeaderName = "Type", Width = 110)]
+ public int Type;
+
+ [ManagerListColumn(Order = 5, HeaderName = "R G B", Width = 300)]
+ public string Green;
+ public string Blue;
+
+ public string Id;
+ public string PlayfieldItem;
+ public string Device;
+ public string DeviceItem;
+ public int Source;
+
+ public MappingsLampData MappingsLampData;
+
+ public LampListData(MappingsLampData mappingsLampData)
+ {
+ Id = mappingsLampData.Id;
+ Source = mappingsLampData.Source;
+ Description = mappingsLampData.Description;
+ PlayfieldItem = mappingsLampData.PlayfieldItem;
+ Device = mappingsLampData.Device;
+ DeviceItem = mappingsLampData.DeviceItem;
+ Type = mappingsLampData.Type;
+ Green = mappingsLampData.Green;
+ Blue = mappingsLampData.Blue;
+
+ MappingsLampData = mappingsLampData;
+ }
+
+ public void Update()
+ {
+ MappingsLampData.Id = Id;
+ MappingsLampData.Source = Source;
+ MappingsLampData.Description = Description;
+ MappingsLampData.PlayfieldItem = PlayfieldItem;
+ MappingsLampData.Device = Device;
+ MappingsLampData.DeviceItem = DeviceItem;
+ MappingsLampData.Type = Type;
+ MappingsLampData.Green = Green;
+ MappingsLampData.Blue = Blue;
+ }
+ }
+}
diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Lamp/LampListData.cs.meta b/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Lamp/LampListData.cs.meta
new file mode 100644
index 000000000..a3204a554
--- /dev/null
+++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Lamp/LampListData.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: e9beff7bbb35646e5a988a07d97bccba
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Lamp/LampListViewItemRenderer.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Lamp/LampListViewItemRenderer.cs
new file mode 100644
index 000000000..431583ac3
--- /dev/null
+++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Lamp/LampListViewItemRenderer.cs
@@ -0,0 +1,270 @@
+// 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 UnityEngine;
+using UnityEditor;
+using System;
+using System.Collections.Generic;
+using UnityEditor.IMGUI.Controls;
+using VisualPinball.Engine.VPT;
+using System.Linq;
+using VisualPinball.Engine.Game.Engines;
+
+namespace VisualPinball.Unity.Editor
+{
+ public class LampListViewItemRenderer
+ {
+ private readonly string[] OPTIONS_LAMP_DESTINATION = { "Playfield" };
+ private readonly string[] OPTIONS_LAMP_TYPE = { "Single On|Off", "Single Fading", "RGB Multi", "RGB" };
+
+ private enum LampListColumn
+ {
+ Id = 0,
+ Description = 1,
+ Destination = 2,
+ Element = 3,
+ Type = 4,
+ Color = 5,
+ }
+
+ private readonly List _gleLamps;
+ private readonly Dictionary _lamps;
+
+ private AdvancedDropdownState _itemPickDropdownState;
+
+ public LampListViewItemRenderer(List gleLamps, Dictionary lamps)
+ {
+ _gleLamps = gleLamps;
+ _lamps = lamps;
+ }
+
+ public void Render(TableAuthoring tableAuthoring, LampListData data, Rect cellRect, int column, Action updateAction)
+ {
+ EditorGUI.BeginDisabledGroup(Application.isPlaying);
+ var lampStatuses = Application.isPlaying
+ ? tableAuthoring.gameObject.GetComponent()?.LampStatuses
+ : null;
+
+ switch ((LampListColumn)column) {
+ case LampListColumn.Id:
+ if (data.Source == LampSource.Coils) {
+ RenderCoilId(lampStatuses, data, cellRect);
+ } else {
+ RenderId(lampStatuses, ref data.Id, id => data.Id = id, data, cellRect, updateAction);
+ }
+ break;
+ case LampListColumn.Description:
+ RenderDescription(data, cellRect, updateAction);
+ break;
+ case LampListColumn.Destination:
+ RenderDestination(data, cellRect, updateAction);
+ break;
+ case LampListColumn.Element:
+ RenderElement(tableAuthoring, data, cellRect, updateAction);
+ break;
+ case LampListColumn.Type:
+ RenderType(data, cellRect, updateAction);
+ break;
+ case LampListColumn.Color:
+ switch (data.Type) {
+ case LampType.RgbMulti:
+ RenderRgb(lampStatuses, data, cellRect, updateAction);
+ break;
+ }
+ break;
+ }
+ EditorGUI.EndDisabledGroup();
+ }
+
+ private void RenderCoilId(Dictionary lampStatuses, LampListData lampListData, Rect cellRect)
+ {
+ // add some padding
+ cellRect.x += 2;
+ cellRect.width -= 4;
+
+
+ var statusAvail = Application.isPlaying && lampStatuses != null && lampStatuses.ContainsKey(lampListData.Id);
+ var icon = Icons.Coil(IconSize.Small, statusAvail && lampStatuses[lampListData.Id] > 0 ? IconColor.Orange : IconColor.Gray);
+ if (icon != null) {
+ var iconRect = cellRect;
+ iconRect.width = 20;
+ var guiColor = GUI.color;
+ GUI.color = Color.clear;
+ EditorGUI.DrawTextureTransparent(iconRect, icon, ScaleMode.ScaleToFit);
+ GUI.color = guiColor;
+ }
+ cellRect.x += 20;
+ cellRect.width -= 20;
+
+ EditorGUI.LabelField(cellRect, lampListData.Id);
+ }
+
+ private void RenderId(IReadOnlyDictionary lampStatuses, ref string id, Action setId, LampListData lampListData, Rect cellRect, Action updateAction)
+ {
+ // add some padding
+ cellRect.x += 2;
+ cellRect.width -= 4;
+
+ var options = new List(_gleLamps.Select(entry => entry.Id).ToArray());
+
+ if (options.Count > 0) {
+ options.Add("");
+ }
+ options.Add("Add...");
+
+ if (Application.isPlaying && lampStatuses != null) {
+ var iconRect = cellRect;
+ iconRect.width = 20;
+ cellRect.x += 25;
+ cellRect.width -= 25;
+ if (lampStatuses.ContainsKey(id)) {
+ var lampStatus = lampStatuses[id];
+ var icon = Icons.Light(IconSize.Small, lampStatus > 0 ? IconColor.Orange : IconColor.Gray);
+ var guiColor = GUI.color;
+ GUI.color = Color.clear;
+ EditorGUI.DrawTextureTransparent(iconRect, icon, ScaleMode.ScaleToFit);
+ GUI.color = guiColor;
+ }
+ }
+
+ EditorGUI.BeginChangeCheck();
+ var index = EditorGUI.Popup(cellRect, options.IndexOf(id), options.ToArray());
+ if (EditorGUI.EndChangeCheck()) {
+ if (index == options.Count - 1) {
+ PopupWindow.Show(cellRect, new ManagerListTextFieldPopup("ID", "", newId => {
+ if (_gleLamps.Exists(entry => entry.Id == newId)) {
+ _gleLamps.Add(new GamelogicEngineLamp
+ {
+ Id = newId
+ });
+ }
+
+ setId(newId);
+ updateAction(lampListData);
+ }));
+
+ }
+ else {
+ setId(_gleLamps[index].Id);
+ updateAction(lampListData);
+ }
+ }
+ }
+
+ private void RenderDescription(LampListData lampListData, Rect cellRect, Action updateAction)
+ {
+ EditorGUI.BeginChangeCheck();
+ var value = EditorGUI.TextField(cellRect, lampListData.Description);
+ if (EditorGUI.EndChangeCheck()) {
+ lampListData.Description = value;
+ updateAction(lampListData);
+ }
+ }
+
+ private void RenderDestination(LampListData lampListData, Rect cellRect, Action updateAction)
+ {
+ EditorGUI.BeginChangeCheck();
+ var index = EditorGUI.Popup(cellRect, lampListData.Destination, OPTIONS_LAMP_DESTINATION);
+ if (EditorGUI.EndChangeCheck()) {
+ if (lampListData.Destination != index) {
+ lampListData.Destination = index;
+ updateAction(lampListData);
+ }
+ }
+ }
+
+ private void RenderElement(TableAuthoring tableAuthoring, LampListData lampListData, Rect cellRect, Action updateAction)
+ {
+ var icon = GetIcon(lampListData);
+ if (icon != null) {
+ var iconRect = cellRect;
+ iconRect.width = 20;
+ var guiColor = GUI.color;
+ GUI.color = Color.clear;
+ EditorGUI.DrawTextureTransparent(iconRect, icon, ScaleMode.ScaleToFit);
+ GUI.color = guiColor;
+ }
+
+ cellRect.x += 25;
+ cellRect.width -= 25;
+
+ switch (lampListData.Destination) {
+ case LampDestination.Playfield:
+ RenderPlayfieldElement(tableAuthoring, lampListData, cellRect, updateAction);
+ break;
+ }
+ }
+
+ private void RenderPlayfieldElement(TableAuthoring tableAuthoring, LampListData lampListData, Rect cellRect, Action updateAction)
+ {
+ if (GUI.Button(cellRect, lampListData.PlayfieldItem, EditorStyles.objectField) || GUI.Button(cellRect, "", GUI.skin.GetStyle("IN ObjectField"))) {
+ if (_itemPickDropdownState == null) {
+ _itemPickDropdownState = new AdvancedDropdownState();
+ }
+
+ var dropdown = new ItemSearchableDropdown(
+ _itemPickDropdownState,
+ tableAuthoring,
+ "Lamp Items",
+ item => {
+ lampListData.PlayfieldItem = item?.Name ?? string.Empty;
+ updateAction(lampListData);
+ }
+ );
+ dropdown.Show(cellRect);
+ }
+ }
+
+ private void RenderType(LampListData lampListData, Rect cellRect, Action updateAction)
+ {
+ EditorGUI.BeginChangeCheck();
+ var index = EditorGUI.Popup(cellRect, (int)lampListData.Type, OPTIONS_LAMP_TYPE);
+ if (EditorGUI.EndChangeCheck()) {
+ lampListData.Type = index;
+ updateAction(lampListData);
+ }
+ }
+
+ private void RenderRgb(IReadOnlyDictionary lampStatuses, LampListData data, Rect cellRect, Action updateAction)
+ {
+ var pad = 2;
+ var width = cellRect.width / 3;
+ var c = cellRect;
+ c.width = width - pad;
+ RenderId(lampStatuses, ref data.Id, id => data.Id = id, data, c, updateAction);
+ c.x += width + pad;
+ RenderId(lampStatuses, ref data.Green, id => data.Green = id, data, c, updateAction);
+ c.x += width + pad;
+ RenderId(lampStatuses, ref data.Blue, id => data.Blue = id, data, c, updateAction);
+ }
+
+ private UnityEngine.Texture GetIcon(LampListData lampListData)
+ {
+ Texture2D icon = null;
+
+ switch (lampListData.Destination) {
+ case LampDestination.Playfield: {
+ if (_lamps.ContainsKey(lampListData.PlayfieldItem.ToLower())) {
+ icon = Icons.ByComponent(_lamps[lampListData.PlayfieldItem.ToLower()], size: IconSize.Small);
+ }
+ break;
+ }
+ }
+
+ return icon;
+ }
+ }
+}
diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Lamp/LampListViewItemRenderer.cs.meta b/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Lamp/LampListViewItemRenderer.cs.meta
new file mode 100644
index 000000000..96047366e
--- /dev/null
+++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Lamp/LampListViewItemRenderer.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: ffa24be5be8864c54ae501901eadbf60
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Lamp/LampManager.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Lamp/LampManager.cs
new file mode 100644
index 000000000..f105e581b
--- /dev/null
+++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Lamp/LampManager.cs
@@ -0,0 +1,234 @@
+// 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 System.Collections.Generic;
+using System.Linq;
+using NLog;
+using UnityEditor;
+using UnityEngine;
+using VisualPinball.Engine.Game.Engines;
+using VisualPinball.Engine.VPT.Mappings;
+using Logger = NLog.Logger;
+
+namespace VisualPinball.Unity.Editor
+{
+ ///
+ /// Editor UI for VPE lamps
+ ///
+ ///
+ internal class LampManager : ManagerWindow
+ {
+ private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
+
+ protected override string DataTypeName => "Lamp";
+
+ protected override bool DetailsEnabled => false;
+ protected override bool ListViewItemRendererEnabled => true;
+
+ private readonly List _gleLamps = new List();
+ private readonly Dictionary _lamps = new Dictionary();
+
+ private LampListViewItemRenderer _listViewItemRenderer;
+
+ private class SerializedMappings : ScriptableObject
+ {
+ public TableAuthoring Table;
+ public MappingsData Mappings;
+ }
+ private SerializedMappings _recordMappings;
+
+ [MenuItem("Visual Pinball/Lamp Manager", false, 304)]
+ public static void ShowWindow()
+ {
+ GetWindow();
+ }
+
+ public override void OnEnable()
+ {
+ titleContent = new GUIContent("Lamp Manager", Icons.Light(IconSize.Small));
+ RowHeight = 22;
+
+ base.OnEnable();
+ }
+
+ private void OnFocus()
+ {
+ _listViewItemRenderer = new LampListViewItemRenderer(_gleLamps, _lamps);
+ }
+
+ protected override bool SetupCompleted()
+ {
+ if (_tableAuthoring == null) {
+ DisplayMessage("No table set.");
+ return false;
+ }
+
+ var gle = _tableAuthoring.gameObject.GetComponent();
+
+ if (gle == null) {
+ DisplayMessage("No gamelogic engine set.");
+ return false;
+ }
+
+ return true;
+ }
+
+ protected override void OnButtonBarGUI()
+ {
+ if (GUILayout.Button("Populate All", GUILayout.ExpandWidth(false))) {
+ if (_tableAuthoring != null) {
+ RecordUndo("Populate all lamp mappings");
+ _tableAuthoring.Table.Mappings.PopulateLamps(GetAvailableEngineLamps(), _tableAuthoring.Table.Lightables);
+ Reload();
+ }
+ }
+
+ if (GUILayout.Button("Remove All", GUILayout.ExpandWidth(false))) {
+ if (_tableAuthoring != null) {
+ if (EditorUtility.DisplayDialog("Lamp Manager", "Are you sure want to remove all lamp mappings?", "Yes", "Cancel")) {
+ RecordUndo("Remove all lamp mappings");
+ _tableAuthoring.Mappings.RemoveAllLamps();
+ }
+ Reload();
+ }
+ }
+ }
+
+ protected override void OnListViewItemRenderer(LampListData data, Rect cellRect, int column)
+ {
+ _listViewItemRenderer.Render(_tableAuthoring, data, cellRect, column, lampListData => {
+ RecordUndo(DataTypeName + " Data Change");
+
+ lampListData.Update();
+
+ var lamp = _tableAuthoring.Table.Lightables.FirstOrDefault(c => c.Name == lampListData.PlayfieldItem);
+ });
+ }
+
+ #region Data management
+ protected override List CollectData()
+ {
+ List data = new List();
+
+ foreach (var mappingsLampData in _tableAuthoring.Mappings.Lamps) {
+ data.Add(new LampListData(mappingsLampData));
+ }
+
+ RefreshLamps();
+ RefreshLampIds();
+
+ return data;
+ }
+
+ protected override void AddNewData(string undoName, string newName)
+ {
+ RecordUndo(undoName);
+ _tableAuthoring.Mappings.AddLamp(new MappingsLampData());
+ }
+
+ protected override void RemoveData(string undoName, LampListData data)
+ {
+ RecordUndo(undoName);
+ _tableAuthoring.Mappings.RemoveLamp(data.MappingsLampData);
+ }
+
+ protected override void CloneData(string undoName, string newName, LampListData data)
+ {
+ RecordUndo(undoName);
+
+ _tableAuthoring.Mappings.AddLamp(new MappingsLampData
+ {
+ Id = data.Id,
+ Description = data.Description,
+ Destination = data.Destination,
+ PlayfieldItem = data.PlayfieldItem,
+ Device = data.Device,
+ DeviceItem = data.DeviceItem,
+ Type = data.Type,
+ Blue = data.Blue,
+ Green = data.Green,
+ });
+ }
+ #endregion
+
+ #region Helper methods
+ private void DisplayMessage(string message)
+ {
+ GUILayout.BeginHorizontal();
+ GUILayout.BeginVertical();
+ GUILayout.FlexibleSpace();
+ var style = new GUIStyle(GUI.skin.label) { alignment = TextAnchor.MiddleCenter };
+ EditorGUILayout.LabelField(message, style, GUILayout.ExpandWidth(true));
+ GUILayout.FlexibleSpace();
+ GUILayout.EndVertical();
+ GUILayout.EndHorizontal();
+ }
+
+ private void RefreshLamps()
+ {
+ _lamps.Clear();
+
+ if (_tableAuthoring != null) {
+ foreach (var item in _tableAuthoring.GetComponentsInChildren()) {
+ _lamps.Add(item.Name.ToLower(), item);
+ }
+ }
+ }
+
+ private void RefreshLampIds()
+ {
+ _gleLamps.Clear();
+ _gleLamps.AddRange(_tableAuthoring.Table.Mappings.GetLamps(GetAvailableEngineLamps()));
+ }
+
+ private GamelogicEngineLamp[] GetAvailableEngineLamps()
+ {
+ var gle = _tableAuthoring.gameObject.GetComponent();
+ return gle == null ? new GamelogicEngineLamp[0] : ((IGamelogicEngineWithLamps)gle).AvailableLamps;
+ }
+
+ #endregion
+
+ #region Undo Redo
+ private void RestoreMappings()
+ {
+ if (_recordMappings == null) { return; }
+ if (_tableAuthoring == null) { return; }
+ if (_recordMappings.Table == _tableAuthoring) {
+ _tableAuthoring.RestoreMappings(_recordMappings.Mappings);
+ }
+ }
+
+ protected override void UndoPerformed()
+ {
+ RestoreMappings();
+ base.UndoPerformed();
+ }
+
+ private void RecordUndo(string undoName)
+ {
+ if (_tableAuthoring == null) { return; }
+ if (_recordMappings == null) {
+ _recordMappings = CreateInstance();
+ }
+ _recordMappings.Table = _tableAuthoring;
+ _recordMappings.Mappings = _tableAuthoring.Mappings;
+
+ Undo.RecordObjects(new Object[] { this, _recordMappings }, undoName);
+ }
+ #endregion
+ }
+}
diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Lamp/LampManager.cs.meta b/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Lamp/LampManager.cs.meta
new file mode 100644
index 000000000..0239793b0
--- /dev/null
+++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Lamp/LampManager.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 0c341f3ff525a4da48384d8fbc41b025
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/ManagerWindow.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/ManagerWindow.cs
index 02cf957b9..7f9144e53 100644
--- a/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/ManagerWindow.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/ManagerWindow.cs
@@ -79,7 +79,7 @@ protected float RowHeight {
private bool _isImplRenameExistingItem;
private Vector2 _scrollPos = Vector2.zero;
- protected void Reload()
+ public void Reload()
{
if (_tableAuthoring != null) {
_data = CollectData();
diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Switch/SwitchListViewItemRenderer.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Switch/SwitchListViewItemRenderer.cs
index 0d77e3f80..0d89af1c8 100644
--- a/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Switch/SwitchListViewItemRenderer.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Switch/SwitchListViewItemRenderer.cs
@@ -22,6 +22,8 @@
using VisualPinball.Engine.VPT;
using System.Linq;
using VisualPinball.Engine.Game.Engines;
+using VisualPinball.Engine.Math;
+using Color = UnityEngine.Color;
namespace VisualPinball.Unity.Editor
{
@@ -62,10 +64,14 @@ public SwitchListViewItemRenderer(List gleSwitches, Dicti
public void Render(TableAuthoring tableAuthoring, SwitchListData data, Rect cellRect, int column, Action updateAction)
{
+ EditorGUI.BeginDisabledGroup(Application.isPlaying);
+ var switchStatuses = Application.isPlaying
+ ? tableAuthoring.gameObject.GetComponent()?.SwitchStatuses
+ : null;
switch ((SwitchListColumn)column)
{
case SwitchListColumn.Id:
- RenderId(data, cellRect, updateAction);
+ RenderId(switchStatuses, data, cellRect, updateAction);
break;
case SwitchListColumn.Description:
RenderDescription(data, cellRect, updateAction);
@@ -80,23 +86,36 @@ public void Render(TableAuthoring tableAuthoring, SwitchListData data, Rect cell
RenderPulseDelay(data, cellRect, updateAction);
break;
}
+ EditorGUI.EndDisabledGroup();
}
- private void RenderId(SwitchListData switchListData, Rect cellRect, Action updateAction)
+ private void RenderId(Dictionary switchStatuses, SwitchListData switchListData, Rect cellRect, Action updateAction)
{
// add some padding
cellRect.x += 2;
cellRect.width -= 4;
var options = new List(_gleSwitches.Select(entry => entry.Id).ToArray());
-
- if (options.Count > 0)
- {
+ if (options.Count > 0) {
options.Add("");
}
-
options.Add("Add...");
+ if (Application.isPlaying && switchStatuses != null) {
+ var iconRect = cellRect;
+ iconRect.width = 20;
+ cellRect.x += 25;
+ cellRect.width -= 25;
+ if (switchStatuses.ContainsKey(switchListData.Id)) {
+ var switchStatus = switchStatuses[switchListData.Id];
+ var icon = Icons.Switch(switchStatus, IconSize.Small, switchStatus ? IconColor.Orange : IconColor.Gray);
+ var guiColor = GUI.color;
+ GUI.color = Color.clear;
+ EditorGUI.DrawTextureTransparent(iconRect, icon, ScaleMode.ScaleToFit);
+ GUI.color = guiColor;
+ }
+ }
+
EditorGUI.BeginChangeCheck();
var index = EditorGUI.Popup(cellRect, options.IndexOf(switchListData.Id), options.ToArray());
if (EditorGUI.EndChangeCheck())
@@ -114,14 +133,12 @@ private void RenderId(SwitchListData switchListData, Rect cellRect, Action();
+ var gle = _tableAuthoring.gameObject.GetComponent();
if (gle == null)
{
@@ -216,8 +216,8 @@ private void RefreshSwitchIds()
private GamelogicEngineSwitch[] GetAvailableEngineSwitches()
{
- var gle = _tableAuthoring.gameObject.GetComponent();
- return gle == null ? new GamelogicEngineSwitch[0] : ((IGamelogicEngineWithSwitches) gle.GameEngine).AvailableSwitches;
+ var gle = _tableAuthoring.gameObject.GetComponent();
+ return gle == null ? new GamelogicEngineSwitch[0] : ((IGamelogicEngineWithSwitches) gle).AvailableSwitches;
}
#endregion
diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Wire/WireListViewItemRenderer.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Wire/WireListViewItemRenderer.cs
index b30ec499d..2b59e2d79 100644
--- a/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Wire/WireListViewItemRenderer.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Wire/WireListViewItemRenderer.cs
@@ -54,9 +54,16 @@ private enum WireListColumn
private readonly Dictionary _coils;
private readonly Dictionary _coilDevices;
+
+ private readonly Dictionary _lamps;
private AdvancedDropdownState _destinationElementDeviceDropdownState;
- public WireListViewItemRenderer(Dictionary switches, Dictionary switchDevices, InputManager inputManager, Dictionary coils, Dictionary coilDevices)
+ public WireListViewItemRenderer(Dictionary switches,
+ Dictionary switchDevices,
+ Dictionary coils,
+ Dictionary coilDevices,
+ Dictionary lamps,
+ InputManager inputManager)
{
_switches = switches;
_switchDevices = switchDevices;
@@ -64,6 +71,8 @@ public WireListViewItemRenderer(Dictionary switches, D
_coils = coils;
_coilDevices = coilDevices;
+
+ _lamps = lamps;
}
public void Render(TableAuthoring tableAuthoring, WireListData data, Rect cellRect, int column, Action updateAction)
@@ -337,10 +346,10 @@ private void RenderDestinationElementPlayfield(TableAuthoring tableAuthoring, Wi
_destinationElementDeviceDropdownState = new AdvancedDropdownState();
}
- var dropdown = new ItemSearchableDropdown(
+ var dropdown = new ItemSearchableDropdown(
_destinationElementDeviceDropdownState,
tableAuthoring,
- "Coil Items",
+ "Wireable Items",
item => {
wireListData.DestinationPlayfieldItem = item != null ? item.Name : string.Empty;
updateAction(wireListData);
@@ -466,7 +475,11 @@ private UnityEngine.Texture GetDestinationIcon(WireListData wireListData)
{
case CoilDestination.Playfield:
if (_coils.ContainsKey(wireListData.DestinationPlayfieldItem.ToLower())) {
- icon = Icons.ByComponent(_coils[wireListData.DestinationPlayfieldItem.ToLower()], size: IconSize.Small);
+ icon = Icons.ByComponent(_coils[wireListData.DestinationPlayfieldItem.ToLower()], IconSize.Small);
+ }
+
+ if (_lamps.ContainsKey(wireListData.DestinationPlayfieldItem.ToLower())) {
+ icon = Icons.ByComponent(_lamps[wireListData.DestinationPlayfieldItem.ToLower()], IconSize.Small);
}
break;
diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Wire/WireManager.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Wire/WireManager.cs
index c2cf617d1..7b7ae5509 100644
--- a/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Wire/WireManager.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Wire/WireManager.cs
@@ -45,6 +45,8 @@ class WireManager : ManagerWindow
private readonly Dictionary _coils = new Dictionary();
private readonly Dictionary _coilDevices = new Dictionary();
+ private readonly Dictionary _lamps = new Dictionary();
+
private InputManager _inputManager;
private bool _needsAssetRefresh;
@@ -75,7 +77,7 @@ public override void OnEnable()
private void OnFocus()
{
_inputManager = new InputManager(RESOURCE_PATH);
- _listViewItemRenderer = new WireListViewItemRenderer(_switches, _switchDevices, _inputManager, _coils, _coilDevices);
+ _listViewItemRenderer = new WireListViewItemRenderer(_switches, _switchDevices, _coils, _coilDevices, _lamps, _inputManager);
_needsAssetRefresh = true;
}
@@ -87,7 +89,7 @@ protected override bool SetupCompleted()
return false;
}
- var gle = _tableAuthoring.gameObject.GetComponent();
+ var gle = _tableAuthoring.gameObject.GetComponent();
if (gle == null)
{
@@ -137,6 +139,7 @@ protected override List CollectData()
RefreshSwitches();
RefreshCoils();
+ RefreshLamps();
return data;
}
@@ -215,6 +218,16 @@ private void RefreshCoils()
}
}
}
+
+ private void RefreshLamps()
+ {
+ _lamps.Clear();
+ if (_tableAuthoring != null) {
+ foreach (var item in _tableAuthoring.GetComponentsInChildren()) {
+ _lamps.Add(item.Name.ToLower(), item);
+ }
+ }
+ }
#endregion
#region Undo Redo
diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Utils/Icons.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Utils/Icons.cs
index bc5efe2b6..acf3613b0 100644
--- a/VisualPinball.Unity/VisualPinball.Unity.Editor/Utils/Icons.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Utils/Icons.cs
@@ -150,7 +150,7 @@ public static void DisableGizmoIcons()
DisableGizmo();
DisableGizmo();
DisableGizmo();
- DisableGizmo();
+ DisableGizmo();
DisableGizmo();
DisableGizmo();
DisableGizmo();
diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Utils/LayoutUtility.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Utils/LayoutUtility.cs
index 4b312e495..1589c0ee6 100644
--- a/VisualPinball.Unity/VisualPinball.Unity.Editor/Utils/LayoutUtility.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Utils/LayoutUtility.cs
@@ -1,5 +1,5 @@
// Visual Pinball Engine
-// Copyright (C) 2020 freezy and VPE Team
+// 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
diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/CoilPlayer.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/CoilPlayer.cs
new file mode 100644
index 000000000..6c15ea4f4
--- /dev/null
+++ b/VisualPinball.Unity/VisualPinball.Unity/Game/CoilPlayer.cs
@@ -0,0 +1,188 @@
+// 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 System.Collections.Generic;
+using NLog;
+using UnityEngine;
+using VisualPinball.Engine.VPT;
+using VisualPinball.Engine.VPT.Table;
+using Logger = NLog.Logger;
+
+namespace VisualPinball.Unity
+{
+ public class CoilPlayer
+ {
+ private readonly Dictionary _coils = new Dictionary();
+ private readonly Dictionary _coilDevices = new Dictionary();
+ private readonly Dictionary> _coilAssignments = new Dictionary>();
+
+ private Table _table;
+ private IGamelogicEngine _gamelogicEngine;
+ private LampPlayer _lampPlayer;
+
+ private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
+
+ internal Dictionary CoilStatuses { get; } = new Dictionary();
+ internal void RegisterCoil(IItem item, IApiCoil coilApi) => _coils[item.Name] = coilApi;
+ internal void RegisterCoilDevice(IItem item, IApiCoilDevice coilDeviceApi) => _coilDevices[item.Name] = coilDeviceApi;
+
+ public void Awake(Table table, IGamelogicEngine gamelogicEngine, LampPlayer lampPlayer)
+ {
+ _table = table;
+ _gamelogicEngine = gamelogicEngine;
+ _lampPlayer = lampPlayer;
+ }
+
+ public void OnStart()
+ {
+ if (_gamelogicEngine is IGamelogicEngineWithCoils gamelogicEngineWithCoils) {
+ var config = _table.Mappings;
+ _coilAssignments.Clear();
+ foreach (var coilData in config.Data.Coils) {
+ switch (coilData.Destination) {
+ case CoilDestination.Playfield:
+
+ if (string.IsNullOrEmpty(coilData.PlayfieldItem)) {
+ Logger.Warn($"Ignoring unassigned coil \"{coilData.Id}\".");
+ break;
+ }
+
+ AssignCoilMapping(coilData.Id, coilData.PlayfieldItem);
+ if (coilData.Type == CoilType.DualWound) {
+ AssignCoilMapping(coilData.HoldCoilId, coilData.PlayfieldItem, true);
+ }
+ break;
+
+ case CoilDestination.Device:
+
+ // mapping values must be set
+ if (string.IsNullOrEmpty(coilData.Device) || string.IsNullOrEmpty(coilData.DeviceItem)) {
+ Logger.Warn($"Ignoring unassigned device coil \"{coilData.Id}\".");
+ break;
+ }
+
+ // check if device exists
+ if (!_coilDevices.ContainsKey(coilData.Device)) {
+ Logger.Error($"Unknown coil device \"{coilData.Device}\".");
+ break;
+ }
+
+ var device = _coilDevices[coilData.Device];
+ var coil = device.Coil(coilData.DeviceItem);
+ if (coil != null) {
+ AssignCoilMapping(coilData.Id, coilData.DeviceItem, deviceName: coilData.Device);
+
+ } else {
+ Logger.Error($"Unknown coil \"{coilData.DeviceItem}\" in coil device \"{coilData.Device}\".");
+ }
+ break;
+
+ case CoilDestination.Lamp:
+ AssignCoilMapping(coilData.Id, coilData.PlayfieldItem, isLampCoil: true);
+ break;
+ }
+ }
+
+ if (_coilAssignments.Count > 0) {
+ gamelogicEngineWithCoils.OnCoilChanged += HandleCoilEvent;
+ }
+ }
+ }
+
+ private void AssignCoilMapping(string id, string playfieldItem, bool isHoldCoil = false, bool isLampCoil = false, string deviceName = null)
+ {
+ if (!_coilAssignments.ContainsKey(id)) {
+ _coilAssignments[id] = new List();
+ }
+ _coilAssignments[id].Add(new CoilDestConfig(playfieldItem, isHoldCoil, isLampCoil, deviceName));
+ CoilStatuses[id] = false;
+ }
+
+ private void HandleCoilEvent(object sender, CoilEventArgs coilEvent)
+ {
+ if (_coilAssignments.ContainsKey(coilEvent.Id)) {
+ CoilStatuses[coilEvent.Id] = coilEvent.IsEnabled;
+ Debug.LogWarning($"Setting coil {coilEvent.Id} to {coilEvent.IsEnabled}.");
+
+ foreach (var destConfig in _coilAssignments[coilEvent.Id]) {
+
+ if (destConfig.IsLampCoil) {
+ _lampPlayer.HandleLampEvent(new LampEventArgs(coilEvent.Id, coilEvent.IsEnabled ? 1 : 0, LampSource.Coils));
+ continue;
+ }
+
+ // device coil?
+ if (destConfig.DeviceName != null) {
+
+ // check device
+ if (!_coilDevices.ContainsKey(destConfig.DeviceName)) {
+ Logger.Error($"Cannot trigger coil on non-existing device \"{destConfig.DeviceName}\" for {coilEvent.Id}.");
+ continue;
+ }
+
+ // check coil in device
+ var coil = _coilDevices[destConfig.DeviceName].Coil(destConfig.ItemName);
+ if (coil == null) {
+ Logger.Error($"Cannot trigger non-existing coil \"{destConfig.ItemName}\" in coil device \"{destConfig.DeviceName}\" for {coilEvent.Id}.");
+ continue;
+ }
+
+ coil.OnCoil(coilEvent.IsEnabled, destConfig.IsHoldCoil);
+
+ } else if (_coils.ContainsKey(destConfig.ItemName)) {
+ _coils[destConfig.ItemName].OnCoil(coilEvent.IsEnabled, destConfig.IsHoldCoil);
+
+ } else {
+ Logger.Error($"Cannot trigger unknown coil item \"{destConfig.ItemName}\" for {coilEvent.Id}.");
+ }
+ }
+
+#if UNITY_EDITOR
+ UnityEditorInternal.InternalEditorUtility.RepaintAllViews();
+#endif
+
+ } else {
+ Logger.Info($"Ignoring unassigned coil \"{coilEvent.Id}\".");
+ }
+ }
+
+ public void OnDestroy()
+ {
+ if (_coilAssignments.Count > 0 && _gamelogicEngine is IGamelogicEngineWithCoils gamelogicEngineWithCoils) {
+ gamelogicEngineWithCoils.OnCoilChanged -= HandleCoilEvent;
+ }
+ }
+ }
+
+ internal readonly struct CoilDestConfig
+ {
+ ///
+ /// Playfield item if is null or device item otherwise.
+ ///
+ public readonly string ItemName;
+ public readonly bool IsHoldCoil;
+ public readonly bool IsLampCoil;
+ public readonly string DeviceName;
+
+ public CoilDestConfig(string itemName, bool isHoldCoil, bool isLampCoil, string deviceName)
+ {
+ ItemName = itemName;
+ IsHoldCoil = isHoldCoil;
+ IsLampCoil = isLampCoil;
+ DeviceName = deviceName;
+ }
+ }
+}
diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/CoilPlayer.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Game/CoilPlayer.cs.meta
new file mode 100644
index 000000000..fe8bffc74
--- /dev/null
+++ b/VisualPinball.Unity/VisualPinball.Unity/Game/CoilPlayer.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: a9d3730a4e826534c855eee8061e99d8
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/DefaultGameEngineAuthoring.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/DefaultGameEngineAuthoring.cs
deleted file mode 100644
index 130d3830a..000000000
--- a/VisualPinball.Unity/VisualPinball.Unity/Game/DefaultGameEngineAuthoring.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-using UnityEngine;
-
-namespace VisualPinball.Unity
-{
- [AddComponentMenu("Visual Pinball/Game Logic Engine/Default Game Logic")]
- public class DefaultGameEngineAuthoring : MonoBehaviour, IGameEngineAuthoring
- {
- public IGamelogicEngine GameEngine => new DefaultGamelogicEngine();
- }
-}
diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/DeviceSwitch.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/DeviceSwitch.cs
index 1da08c7fe..9e75b5928 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/Game/DeviceSwitch.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/Game/DeviceSwitch.cs
@@ -34,6 +34,7 @@ public class DeviceSwitch : IApiSwitch
/// Indicates whether the switch is currently opened or closed.
///
public bool IsClosed => _switchHandler.IsClosed;
+ public bool IsSwitchClosed => _switchHandler.IsClosed;
///
/// Indicates whether the switch is currently enabled.
diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/Engine/DefaultGamelogicEngine.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/Engine/DefaultGamelogicEngine.cs
index 959e4b548..ae6a02e26 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/Game/Engine/DefaultGamelogicEngine.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/Game/Engine/DefaultGamelogicEngine.cs
@@ -18,9 +18,11 @@
using System.Collections.Generic;
using System.Diagnostics;
using NLog;
+using UnityEngine;
using VisualPinball.Engine.Common;
using VisualPinball.Engine.Game.Engines;
using VisualPinball.Engine.VPT.Trough;
+using Logger = NLog.Logger;
namespace VisualPinball.Unity
{
@@ -29,12 +31,17 @@ namespace VisualPinball.Unity
/// gamelogic engine. For now it just tries to find the flippers and hook
/// them up to the switches.
///
- [Serializable]
- public class DefaultGamelogicEngine : IGamelogicEngine, IGamelogicEngineWithSwitches, IGamelogicEngineWithCoils
+ [DisallowMultipleComponent]
+ [AddComponentMenu("Visual Pinball/Game Logic Engine/Default Game Logic")]
+ public class DefaultGamelogicEngine : MonoBehaviour, IGamelogicEngine,
+ IGamelogicEngineWithSwitches, IGamelogicEngineWithCoils, IGamelogicEngineWithLamps
{
public string Name { get; } = "Default Game Engine";
public event EventHandler OnCoilChanged;
+ public event EventHandler OnLampChanged;
+ public event EventHandler OnLampsChanged;
+ public event EventHandler OnLampColorChanged;
private const string SwLeftFlipper = "s_left_flipper";
private const string SwLeftFlipperEos = "s_left_flipper_eos";
@@ -47,6 +54,7 @@ public class DefaultGamelogicEngine : IGamelogicEngine, IGamelogicEngineWithSwit
private const string SwTrough4 = "s_trough4";
private const string SwPlunger = "s_plunger";
private const string SwCreateBall = "s_create_ball";
+ private const string SwRedBumper = "s_red_bumper";
public GamelogicEngineSwitch[] AvailableSwitches { get; } = {
new GamelogicEngineSwitch { Id = SwLeftFlipper, Description = "Left Flipper (button)", InputActionHint = InputConstants.ActionLeftFlipper },
@@ -60,7 +68,8 @@ public class DefaultGamelogicEngine : IGamelogicEngine, IGamelogicEngineWithSwit
new GamelogicEngineSwitch { Id = SwTrough3, Description = "Trough 3", DeviceHint = "^Trough\\s*\\d?", DeviceItemHint = "3"},
new GamelogicEngineSwitch { Id = SwTrough4, Description = "Trough 4", DeviceHint = "^Trough\\s*\\d?", DeviceItemHint = "4"},
new GamelogicEngineSwitch { Id = SwTrough4, Description = "Trough 4", DeviceHint = "^Trough\\s*\\d?", DeviceItemHint = "4"},
- new GamelogicEngineSwitch { Id = SwCreateBall, Description = "Create Debug Ball", InputActionHint = InputConstants.ActionCreateBall, InputMapHint = InputConstants.MapDebug }
+ new GamelogicEngineSwitch { Id = SwCreateBall, Description = "Create Debug Ball", InputActionHint = InputConstants.ActionCreateBall, InputMapHint = InputConstants.MapDebug },
+ new GamelogicEngineSwitch { Id = SwRedBumper, Description = "Red Bumper", PlayfieldItemHint = "^Bumper1$" }
};
private const string CoilLeftFlipperMain = "c_flipper_left_main";
@@ -81,31 +90,70 @@ public class DefaultGamelogicEngine : IGamelogicEngine, IGamelogicEngineWithSwit
new GamelogicEngineCoil { Id = CoilTroughEntry, Description = "Trough Entry", DeviceHint = "^Trough\\s*\\d?", DeviceItemHint = Trough.EntryCoilId},
};
- private TableApi _tableApi;
+ private const string GiSlingshotRightLower = "gi_1";
+ private const string GiSlingshotRightUpper = "gi_2";
+ private const string GiSlingshotLeftLower = "gi_3";
+ private const string GiSlingshotLeftUpper = "gi_4";
+ private const string GiDropTargetsRightLower = "gi_5";
+ private const string GiDropTargetsLeftLower = "gi_6";
+ private const string GiDropTargetsLeftUpper = "gi_7";
+ private const string GiDropTargetsRightUpper = "gi_8";
+ private const string GiTop3 = "gi_9";
+ private const string GiTop2 = "gi_10";
+ private const string GiTop4 = "gi_11";
+ private const string GiTop5 = "gi_12";
+ private const string GiTop1 = "gi_13";
+ private const string GiLowerRamp = "gi_14";
+ private const string GiUpperRamp = "gi_15";
+ private const string GiTopLeftPlastic = "gi_16";
+
+ private const string LampRedBumper = "l_bumper";
+
+
+ public GamelogicEngineLamp[] AvailableLamps { get; } =
+ {
+ new GamelogicEngineLamp { Id = GiSlingshotRightLower, Description = "Right Slingshot (lower)", PlayfieldItemHint = "gi1$" },
+ new GamelogicEngineLamp { Id = GiSlingshotRightUpper, Description = "Right Slingshot (upper)", PlayfieldItemHint = "gi2$" },
+ new GamelogicEngineLamp { Id = GiSlingshotLeftLower, Description = "Left Slingshot (lower)", PlayfieldItemHint = "gi3$" },
+ new GamelogicEngineLamp { Id = GiSlingshotLeftUpper, Description = "Left Slingshot (upper)", PlayfieldItemHint = "gi4$" },
+ new GamelogicEngineLamp { Id = GiDropTargetsRightLower, Description = "Right Drop Targets (lower)", PlayfieldItemHint = "gi5$" },
+ new GamelogicEngineLamp { Id = GiDropTargetsRightUpper, Description = "Right Drop Targets (upper)", PlayfieldItemHint = "gi8$" },
+ new GamelogicEngineLamp { Id = GiDropTargetsLeftLower, Description = "Left Drop Targets (lower)", PlayfieldItemHint = "gi6$" },
+ new GamelogicEngineLamp { Id = GiDropTargetsLeftUpper, Description = "Left Drop Targets (upper)", PlayfieldItemHint = "gi7$" },
+ new GamelogicEngineLamp { Id = GiTop1, Description = "Top 1 (left)", PlayfieldItemHint = "gi13$" },
+ new GamelogicEngineLamp { Id = GiTop2, Description = "Top 2", PlayfieldItemHint = "gi10$" },
+ new GamelogicEngineLamp { Id = GiTop3, Description = "Top 3", PlayfieldItemHint = "gi9$" },
+ new GamelogicEngineLamp { Id = GiTop4, Description = "Top 4", PlayfieldItemHint = "gi11$" },
+ new GamelogicEngineLamp { Id = GiTop5, Description = "Top 5 (right)", PlayfieldItemHint = "gi12$" },
+ new GamelogicEngineLamp { Id = GiLowerRamp, Description = "Ramp (lower)", PlayfieldItemHint = "gi14$" },
+ new GamelogicEngineLamp { Id = GiUpperRamp, Description = "Ramp (upper)", PlayfieldItemHint = "gi15$" },
+ new GamelogicEngineLamp { Id = GiTopLeftPlastic, Description = "Top Left Plastics", PlayfieldItemHint = "gi16$" },
+ new GamelogicEngineLamp { Id = LampRedBumper, Description = "Red Bumper", PlayfieldItemHint = "^b1l2$" }
+ };
+
+ private Player _player;
private BallManager _ballManager;
- private Dictionary _switchStatus = new Dictionary();
- private Dictionary _switchTime = new Dictionary();
+ private readonly Dictionary _switchTime = new Dictionary();
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
- public void OnInit(TableApi tableApi, BallManager ballManager)
+ public DefaultGamelogicEngine()
+ {
+ Logger.Info("New Gamelogic engine instantiated.");
+ }
+
+ public void OnInit(Player player, TableApi tableApi, BallManager ballManager)
{
- _tableApi = tableApi;
+ _player = player;
_ballManager = ballManager;
// debug print stuff
OnCoilChanged += DebugPrintCoil;
- _switchStatus[SwLeftFlipper] = false;
- _switchStatus[SwLeftFlipperEos] = false;
- _switchStatus[SwRightFlipper] = false;
- _switchStatus[SwRightFlipperEos] = false;
- _switchStatus[SwPlunger] = false;
- _switchStatus[SwCreateBall] = false;
-
// eject ball onto playfield
OnCoilChanged?.Invoke(this, new CoilEventArgs(CoilTroughEject, true));
+ _player.ScheduleAction(100, () => OnCoilChanged?.Invoke(this, new CoilEventArgs(CoilTroughEject, false)));
}
public void OnUpdate()
@@ -119,7 +167,6 @@ public void OnDestroy()
public void Switch(string id, bool isClosed)
{
- _switchStatus[id] = isClosed;
if (!_switchTime.ContainsKey(id)) {
_switchTime[id] = new Stopwatch();
}
@@ -139,7 +186,7 @@ public void Switch(string id, bool isClosed)
} else {
OnCoilChanged?.Invoke(this,
- _switchStatus[SwLeftFlipperEos]
+ _player.SwitchStatuses[SwLeftFlipperEos]
? new CoilEventArgs(CoilLeftFlipperHold, false)
: new CoilEventArgs(CoilLeftFlipperMain, false)
);
@@ -147,8 +194,10 @@ public void Switch(string id, bool isClosed)
break;
case SwLeftFlipperEos:
- OnCoilChanged?.Invoke(this, new CoilEventArgs(CoilLeftFlipperMain, false));
- OnCoilChanged?.Invoke(this, new CoilEventArgs(CoilLeftFlipperHold, true));
+ if (isClosed) {
+ OnCoilChanged?.Invoke(this, new CoilEventArgs(CoilLeftFlipperMain, false));
+ OnCoilChanged?.Invoke(this, new CoilEventArgs(CoilLeftFlipperHold, true));
+ }
break;
case SwRightFlipper:
@@ -156,7 +205,7 @@ public void Switch(string id, bool isClosed)
OnCoilChanged?.Invoke(this, new CoilEventArgs(CoilRightFlipperMain, true));
} else {
OnCoilChanged?.Invoke(this,
- _switchStatus[SwRightFlipperEos]
+ _player.SwitchStatuses[SwRightFlipperEos]
? new CoilEventArgs(CoilRightFlipperHold, false)
: new CoilEventArgs(CoilRightFlipperMain, false)
);
@@ -164,8 +213,10 @@ public void Switch(string id, bool isClosed)
break;
case SwRightFlipperEos:
- OnCoilChanged?.Invoke(this, new CoilEventArgs(CoilRightFlipperMain, false));
- OnCoilChanged?.Invoke(this, new CoilEventArgs(CoilRightFlipperHold, true));
+ if (isClosed) {
+ OnCoilChanged?.Invoke(this, new CoilEventArgs(CoilRightFlipperMain, false));
+ OnCoilChanged?.Invoke(this, new CoilEventArgs(CoilRightFlipperHold, true));
+ }
break;
case SwPlunger:
@@ -175,9 +226,14 @@ public void Switch(string id, bool isClosed)
case SwTrough4:
if (isClosed) {
OnCoilChanged?.Invoke(this, new CoilEventArgs(CoilTroughEject, true));
+ _player.ScheduleAction(100, () => OnCoilChanged?.Invoke(this, new CoilEventArgs(CoilTroughEject, false)));
}
break;
+ case SwRedBumper:
+ OnLampChanged?.Invoke(this, new LampEventArgs(LampRedBumper, isClosed ? 1 : 0));
+ break;
+
case SwCreateBall: {
if (isClosed) {
_ballManager.CreateBall(new DebugBallCreator());
@@ -187,6 +243,26 @@ public void Switch(string id, bool isClosed)
}
}
+ public void SetCoil(string n, bool value)
+ {
+ OnCoilChanged?.Invoke(this, new CoilEventArgs(n, value));
+ }
+
+ public void SetLamp(string n, int value)
+ {
+ OnLampChanged?.Invoke(this, new LampEventArgs(n, value));
+ }
+
+ public void SetLampColor(string n, Color color)
+ {
+ OnLampColorChanged?.Invoke(this, new LampColorEventArgs(n, color));
+ }
+
+ public void SetLamps(LampEventArgs[] values)
+ {
+ OnLampsChanged?.Invoke(this, new LampsEventArgs(values));
+ }
+
private void DebugPrintCoil(object sender, CoilEventArgs e)
{
Logger.Info("Coil {0} set to {1}.", e.Id, e.IsEnabled);
diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/Engine/DefaultGamelogicEngine.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Game/Engine/DefaultGamelogicEngine.cs.meta
index 3975f1a82..75c23a296 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/Game/Engine/DefaultGamelogicEngine.cs.meta
+++ b/VisualPinball.Unity/VisualPinball.Unity/Game/Engine/DefaultGamelogicEngine.cs.meta
@@ -5,7 +5,7 @@ MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
- icon: {instanceID: 0}
+ icon: {fileID: 2800000, guid: 5d2f099f5d6bcdd47ad8b5cd9f62e65c, type: 3}
userData:
assetBundleName:
assetBundleVariant:
diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/Engine/IGamelogicEngine.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/Engine/IGamelogicEngine.cs
index 4eac6381f..72224d1dc 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/Game/Engine/IGamelogicEngine.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/Game/Engine/IGamelogicEngine.cs
@@ -25,7 +25,7 @@ public interface IGamelogicEngine
{
string Name { get; }
- void OnInit(TableApi tableApi, BallManager ballManager);
+ void OnInit(Player player, TableApi tableApi, BallManager ballManager);
void OnUpdate();
diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/Engine/IGamelogicEngineWithLamps.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/Engine/IGamelogicEngineWithLamps.cs
new file mode 100644
index 000000000..f63d5c181
--- /dev/null
+++ b/VisualPinball.Unity/VisualPinball.Unity/Game/Engine/IGamelogicEngineWithLamps.cs
@@ -0,0 +1,119 @@
+// 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 System;
+using UnityEngine;
+using VisualPinball.Engine.Game.Engines;
+using VisualPinball.Engine.VPT;
+
+namespace VisualPinball.Unity
+{
+ ///
+ /// A game logic engine working with lamps
+ ///
+ /// It provides a list of available lamps and an event handler to trigger
+ /// them.
+ ///
+ public interface IGamelogicEngineWithLamps
+ {
+ ///
+ /// A list of available lamps.
+ ///
+ GamelogicEngineLamp[] AvailableLamps { get; }
+
+ ///
+ /// Triggered when a lamp is turned on or off.
+ ///
+ event EventHandler OnLampChanged;
+
+ ///
+ /// Triggered when multiple lamps are turned on or off at once.
+ ///
+ ///
+ ///
+ /// This also allows to to group RGB updates, i.e. updating the color
+ /// at once instead of each channel individually.
+ ///
+ event EventHandler OnLampsChanged;
+
+ ///
+ /// Triggered when the an RGB lamp changes color.
+ ///
+ event EventHandler OnLampColorChanged;
+ }
+
+ public readonly struct LampEventArgs
+ {
+ ///
+ /// Id of the lamp, as defined by .
+ ///
+ public readonly string Id;
+
+ ///
+ /// Value of the lamp. Depending on its type, it can be 0/1 for on/off, or 0-255 for
+ /// a fading light.
+ ///
+ public readonly int Value;
+
+ ///
+ /// Source which triggered the lamp.
+ ///
+ public readonly int Source;
+
+ public LampEventArgs(string id, int value)
+ {
+ Id = id;
+ Value = value;
+ Source = LampSource.Lamps;
+ }
+
+ public LampEventArgs(string id, int value, int source)
+ {
+ Id = id;
+ Value = value;
+ Source = source;
+ }
+ }
+
+ public readonly struct LampColorEventArgs
+ {
+ ///
+ /// Id of the lamp, as defined by .
+ ///
+ public readonly string Id;
+
+ ///
+ /// New color
+ ///
+ public readonly Color Color;
+
+ public LampColorEventArgs(string id, Color color)
+ {
+ Id = id;
+ Color = color;
+ }
+ }
+
+ public readonly struct LampsEventArgs
+ {
+ public readonly LampEventArgs[] LampsChanged;
+
+ public LampsEventArgs(LampEventArgs[] lampsChanged)
+ {
+ LampsChanged = lampsChanged;
+ }
+ }
+}
diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/Engine/IGamelogicEngineWithLamps.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Game/Engine/IGamelogicEngineWithLamps.cs.meta
new file mode 100644
index 000000000..1f7369630
--- /dev/null
+++ b/VisualPinball.Unity/VisualPinball.Unity/Game/Engine/IGamelogicEngineWithLamps.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 17c472a5cc0f74c4ba0d741649f50198
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/LampPlayer.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/LampPlayer.cs
new file mode 100644
index 000000000..2c06a6ade
--- /dev/null
+++ b/VisualPinball.Unity/VisualPinball.Unity/Game/LampPlayer.cs
@@ -0,0 +1,235 @@
+// 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 System;
+using System.Collections.Generic;
+using NLog;
+using VisualPinball.Engine.Math;
+using VisualPinball.Engine.VPT;
+using VisualPinball.Engine.VPT.Mappings;
+using VisualPinball.Engine.VPT.Table;
+using Color = UnityEngine.Color;
+using Logger = NLog.Logger;
+
+namespace VisualPinball.Unity
+{
+ public class LampPlayer
+ {
+ private readonly Dictionary _lamps = new Dictionary();
+ private readonly Dictionary> _lampAssignments = new Dictionary>();
+ private readonly Dictionary> _lampMappings = new Dictionary>();
+
+ private Table _table;
+ private IGamelogicEngine _gamelogicEngine;
+
+ private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
+
+ internal Dictionary LampStatuses { get; } = new Dictionary();
+ internal void RegisterLamp(IItem item, IApiLamp lampApi) => _lamps[item.Name] = lampApi;
+
+ public void Awake(Table table, IGamelogicEngine gamelogicEngine)
+ {
+ _table = table;
+ _gamelogicEngine = gamelogicEngine;
+ }
+
+ public void OnStart()
+ {
+ if (_gamelogicEngine is IGamelogicEngineWithLamps gamelogicEngineWithLamps) {
+ var config = _table.Mappings;
+ _lampAssignments.Clear();
+ _lampMappings.Clear();
+ foreach (var lampData in config.Data.Lamps) {
+ switch (lampData.Destination) {
+ case LampDestination.Playfield:
+
+ if (string.IsNullOrEmpty(lampData.PlayfieldItem)) {
+ Logger.Warn($"Ignoring unassigned lamp \"{lampData.Id}\".");
+ break;
+ }
+
+ AssignLampMapping(lampData.Id, lampData);
+
+ if (lampData.Type == LampType.RgbMulti) {
+ if (!string.IsNullOrEmpty(lampData.Green)) {
+ AssignLampMapping(lampData.Green, lampData);
+ }
+ if (!string.IsNullOrEmpty(lampData.Blue)) {
+ AssignLampMapping(lampData.Blue, lampData);
+ }
+ }
+
+ break;
+ }
+ }
+
+ if (_lampAssignments.Count > 0) {
+ gamelogicEngineWithLamps.OnLampChanged += HandleLampEvent;
+ gamelogicEngineWithLamps.OnLampsChanged += HandleLampsEvent;
+ gamelogicEngineWithLamps.OnLampColorChanged += HandleLampColorEvent;
+ }
+ }
+ }
+
+ public void HandleLampEvent(LampEventArgs lampEvent)
+ {
+ HandleLampEvent(null, lampEvent);
+ }
+
+ private void AssignLampMapping(string id, MappingsLampData lampData)
+ {
+ if (!_lampAssignments.ContainsKey(id)) {
+ _lampAssignments[id] = new List();
+ _lampMappings[id] = new Dictionary();
+ }
+ _lampAssignments[id].Add(lampData.PlayfieldItem);
+ _lampMappings[id][lampData.PlayfieldItem] = lampData;
+ LampStatuses[id] = 0f;
+ }
+
+ private void HandleLampColorEvent(object sender, LampColorEventArgs lampEvent)
+ {
+ if (_lampAssignments.ContainsKey(lampEvent.Id)) {
+ foreach (var itemName in _lampAssignments[lampEvent.Id]) {
+ var mapping = _lampMappings[lampEvent.Id][itemName];
+ if (_lamps.ContainsKey(itemName)) {
+ var lamp = _lamps[itemName];
+ switch (mapping.Type) {
+ case LampType.Rgb:
+ lamp.OnLampColor(lampEvent.Color);
+ break;
+
+ default:
+ Logger.Error($"Received an RGB event for lamp {itemName} but lamp mapping type is {mapping.Type} ({mapping.Id})");
+ break;
+ }
+
+ } else {
+ Logger.Error($"Cannot trigger unknown lamp {itemName}.");
+ }
+ }
+
+ } else {
+ Logger.Error($"Should update unassigned lamp {lampEvent.Id}.");
+ }
+ }
+
+ private void HandleLampsEvent(object sender, LampsEventArgs lampsEvent)
+ {
+ var colors = new Dictionary();
+ var lamps = new Dictionary();
+ foreach (var lampEvent in lampsEvent.LampsChanged) {
+ HandleLampEvent(lampEvent, (lamp, mapping, itemName) => {
+ var color = colors.ContainsKey(mapping.Id) ? colors[mapping.Id] : lamp.Color;
+ if (lampEvent.Id == mapping.Id) {
+ color.r = lampEvent.Value / 255f;
+
+ } else if (lampEvent.Id == mapping.Green) {
+ color.g = lampEvent.Value / 255f;
+
+ } else if (lampEvent.Id == mapping.Blue) {
+ color.b = lampEvent.Value / 255f;
+
+ } else {
+ Logger.Error($"Cannot assign lamp {lampEvent.Id} to an RGB value of light {itemName}");
+ }
+ colors[mapping.Id] = color;
+ lamps[mapping.Id] = lamp;
+ });
+ }
+
+ foreach (var mappingId in colors.Keys) {
+ lamps[mappingId].Color = colors[mappingId];
+ LampStatuses[mappingId] = colors[mappingId].grayscale;
+ }
+ }
+
+ private void HandleLampEvent(object sender, LampEventArgs lampEvent)
+ {
+ HandleLampEvent(lampEvent, (lamp, mapping, itemName) => {
+ if (lampEvent.Id == mapping.Id) {
+ lamp.OnLamp(lampEvent.Value / 255f, ColorChannel.Red);
+
+ } else if (lampEvent.Id == mapping.Green) {
+ lamp.OnLamp(lampEvent.Value / 255f, ColorChannel.Green);
+
+ } else if (lampEvent.Id == mapping.Blue) {
+ lamp.OnLamp(lampEvent.Value / 255f, ColorChannel.Blue);
+
+ } else {
+ Logger.Error($"Cannot assign lamp {lampEvent.Id} to an RGB value of light {itemName}");
+ }
+ LampStatuses[lampEvent.Id] = lamp.Color.grayscale;
+ });
+ }
+
+ private void HandleLampEvent(LampEventArgs lampEvent, Action handleRgb)
+ {
+ if (_lampAssignments.ContainsKey(lampEvent.Id)) {
+ foreach (var itemName in _lampAssignments[lampEvent.Id]) {
+ var mapping = _lampMappings[lampEvent.Id][itemName];
+ if (mapping.Source != lampEvent.Source) {
+ // so, if we have a coil here that happens to have the same name as a lamp,
+ // skip if the source isn't the same.
+ continue;
+ }
+ if (_lamps.ContainsKey(itemName)) {
+ var lamp = _lamps[itemName];
+ switch (mapping.Type) {
+ case LampType.SingleOnOff: {
+ var value = lampEvent.Value > 0 ? 1f : 0f;
+ lamp.OnLamp(value, ColorChannel.Alpha);
+ LampStatuses[lampEvent.Id] = value;
+ break;
+ }
+
+ case LampType.Rgb:
+ case LampType.SingleFading: {
+ var value = lampEvent.Value / 255f;
+ lamp.OnLamp(value, ColorChannel.Alpha);
+ LampStatuses[lampEvent.Id] = value;
+ break;
+ }
+
+ case LampType.RgbMulti:
+ handleRgb(lamp, mapping, itemName);
+ break;
+
+ default:
+ Logger.Error($"Unknown mapping type \"{mapping.Type}\" of lamp ID {lampEvent.Id} for light {itemName}.");
+ break;
+ }
+
+ } else {
+ Logger.Error($"Cannot trigger unknown lamp {itemName}.");
+ }
+ }
+#if UNITY_EDITOR
+ UnityEditorInternal.InternalEditorUtility.RepaintAllViews();
+#endif
+ }
+ }
+
+ public void OnDestroy()
+ {
+ if (_lampAssignments.Count > 0 && _gamelogicEngine is IGamelogicEngineWithLamps gamelogicEngineWithLamps) {
+ gamelogicEngineWithLamps.OnLampColorChanged -= HandleLampColorEvent;
+ gamelogicEngineWithLamps.OnLampChanged -= HandleLampEvent;
+ gamelogicEngineWithLamps.OnLampsChanged -= HandleLampsEvent;
+ }
+ }
+ }
+}
diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/LampPlayer.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Game/LampPlayer.cs.meta
new file mode 100644
index 000000000..8247c7bfa
--- /dev/null
+++ b/VisualPinball.Unity/VisualPinball.Unity/Game/LampPlayer.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: c31c8b5b6e7f19e4880f9148f78e1c4b
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/Player.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/Player.cs
index 49cce00e5..86f02fdd3 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/Game/Player.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/Game/Player.cs
@@ -16,14 +16,11 @@
using System;
using System.Collections.Generic;
-using NLog;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
-using UnityEngine.InputSystem;
using VisualPinball.Engine.Common;
using VisualPinball.Engine.Game;
-using VisualPinball.Engine.VPT;
using VisualPinball.Engine.VPT.Bumper;
using VisualPinball.Engine.VPT.Flipper;
using VisualPinball.Engine.VPT.Gate;
@@ -38,7 +35,7 @@
using VisualPinball.Engine.VPT.Table;
using VisualPinball.Engine.VPT.Trigger;
using VisualPinball.Engine.VPT.Trough;
-using Logger = NLog.Logger;
+using Light = VisualPinball.Engine.VPT.Light.Light;
namespace VisualPinball.Unity
{
@@ -67,22 +64,13 @@ public class Player : MonoBehaviour
private readonly Dictionary _collidables = new Dictionary();
private readonly Dictionary _spinnables = new Dictionary();
private readonly Dictionary _slingshots = new Dictionary();
- private readonly Dictionary _switches = new Dictionary();
- private readonly Dictionary _switchDevices = new Dictionary();
- private readonly Dictionary _coils = new Dictionary();
- private readonly Dictionary _coilDevices = new Dictionary();
- private readonly Dictionary _wires = new Dictionary();
- private readonly Dictionary _wireDevices = new Dictionary();
- private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
-
- // input related
private InputManager _inputManager;
-
- [NonSerialized]
- private readonly Dictionary> _keySwitchAssignments = new Dictionary>();
- private readonly Dictionary> _keyWireAssignments = new Dictionary>();
- private readonly Dictionary>> _coilAssignments = new Dictionary>>();
+ private VisualPinballSimulationSystemGroup _simulationSystemGroup;
+ [NonSerialized] private readonly LampPlayer _lampPlayer = new LampPlayer();
+ [NonSerialized] private readonly CoilPlayer _coilPlayer = new CoilPlayer();
+ [NonSerialized] private readonly SwitchPlayer _switchPlayer = new SwitchPlayer();
+ [NonSerialized] private readonly WirePlayer _wirePlayer = new WirePlayer();
public Player()
{
@@ -92,9 +80,12 @@ public Player()
#region Access
- internal IApiSwitch Switch(string n) => _switches.ContainsKey(n) ? _switches[n] : null;
- internal IApiWireDest Wire(string n) => _wires.ContainsKey(n) ? _wires[n] : null;
- internal IApiWireDeviceDest WireDevice(string n) => _wireDevices.ContainsKey(n) ? _wireDevices[n] : null;
+ internal IApiSwitch Switch(string n) => _switchPlayer.Switch(n);
+ internal IApiWireDest Wire(string n) => _wirePlayer.Wire(n);
+ internal IApiWireDeviceDest WireDevice(string n) => _wirePlayer.WireDevice(n);
+ public Dictionary SwitchStatuses => _switchPlayer.SwitchStatuses;
+ public Dictionary CoilStatuses => _coilPlayer.CoilStatuses;
+ public Dictionary LampStatuses => _lampPlayer.LampStatuses;
#endregion
@@ -103,14 +94,18 @@ public Player()
private void Awake()
{
var tableComponent = gameObject.GetComponent();
- var engineComponent = GetComponent();
+ var engineComponent = GetComponent();
Table = tableComponent.CreateTable(tableComponent.Data);
BallManager = new BallManager(Table, TableToWorld);
_inputManager = new InputManager();
if (engineComponent != null) {
- GameEngine = engineComponent.GameEngine;
+ GameEngine = engineComponent;
+ _lampPlayer.Awake(Table, GameEngine);
+ _coilPlayer.Awake(Table, GameEngine, _lampPlayer);
+ _switchPlayer.Awake(Table, GameEngine, _inputManager);
+ _wirePlayer.Awake(Table, _inputManager, _switchPlayer);
}
EngineProvider.Set(physicsEngineId);
@@ -118,28 +113,22 @@ private void Awake()
if (!string.IsNullOrEmpty(debugUiId)) {
EngineProvider.Set(debugUiId);
}
+ _simulationSystemGroup = World.DefaultGameObjectInjectionWorld.GetOrCreateSystem();
}
private void Start()
{
-
- // bootstrap table script(s)
- var tableScripts = GetComponents();
- foreach (var tableScript in tableScripts) {
- tableScript.OnAwake(TableApi, BallManager);
- }
-
// trigger init events now
foreach (var i in _initializables) {
i.OnInit(BallManager);
}
- // hook up mapping configuration
- SetupSwitchMapping();
- SetupCoilMapping();
- SetupWireMapping();
+ _coilPlayer.OnStart();
+ _switchPlayer.OnStart();
+ _lampPlayer.OnStart();
+ _wirePlayer.OnStart();
- GameEngine?.OnInit(TableApi, BallManager);
+ GameEngine?.OnInit(this, TableApi, BallManager);
}
private void Update()
@@ -149,17 +138,14 @@ private void Update()
private void OnDestroy()
{
- if (_keySwitchAssignments.Count > 0) {
- _inputManager.Disable(HandleKeyInput);
- }
- if (_coilAssignments.Count > 0 && GameEngine is IGamelogicEngineWithCoils gamelogicEngineWithCoils) {
- gamelogicEngineWithCoils.OnCoilChanged -= HandleCoilEvent;
- }
-
foreach (var i in _apis) {
i.OnDestroy();
}
+ _coilPlayer.OnDestroy();
+ _switchPlayer.OnDestroy();
+ _lampPlayer.OnDestroy();
+ _wirePlayer.OnDestroy();
GameEngine?.OnDestroy();
}
@@ -174,9 +160,9 @@ public void RegisterBumper(Bumper bumper, Entity entity, GameObject go)
_apis.Add(bumperApi);
_initializables.Add(bumperApi);
_hittables[entity] = bumperApi;
- _switches[bumper.Name] = bumperApi;
- _coils[bumper.Name] = bumperApi;
- _wires[bumper.Name] = bumperApi;
+ _switchPlayer.RegisterSwitch(bumper, bumperApi);
+ _coilPlayer.RegisterCoil(bumper, bumperApi);
+ _wirePlayer.RegisterWire(bumper, bumperApi);
}
public void RegisterFlipper(Flipper flipper, Entity entity, GameObject go)
@@ -188,9 +174,9 @@ public void RegisterFlipper(Flipper flipper, Entity entity, GameObject go)
_hittables[entity] = flipperApi;
_rotatables[entity] = flipperApi;
_collidables[entity] = flipperApi;
- _switches[flipper.Name] = flipperApi;
- _coils[flipper.Name] = flipperApi;
- _wires[flipper.Name] = flipperApi;
+ _switchPlayer.RegisterSwitch(flipper, flipperApi);
+ _coilPlayer.RegisterCoil(flipper, flipperApi);
+ _wirePlayer.RegisterWire(flipper, flipperApi);
if (EngineProvider.Exists) {
EngineProvider.Get().OnRegisterFlipper(entity, flipper.Name);
@@ -205,7 +191,7 @@ public void RegisterGate(Gate gate, Entity entity, GameObject go)
_initializables.Add(gateApi);
_hittables[entity] = gateApi;
_rotatables[entity] = gateApi;
- _switches[gate.Name] = gateApi;
+ _switchPlayer.RegisterSwitch(gate, gateApi);
}
public void RegisterHitTarget(HitTarget hitTarget, Entity entity, GameObject go)
@@ -215,7 +201,7 @@ public void RegisterHitTarget(HitTarget hitTarget, Entity entity, GameObject go)
_apis.Add(hitTargetApi);
_initializables.Add(hitTargetApi);
_hittables[entity] = hitTargetApi;
- _switches[hitTarget.Name] = hitTargetApi;
+ _switchPlayer.RegisterSwitch(hitTarget, hitTargetApi);
}
public void RegisterKicker(Kicker kicker, Entity entity, GameObject go)
@@ -225,9 +211,19 @@ public void RegisterKicker(Kicker kicker, Entity entity, GameObject go)
_apis.Add(kickerApi);
_initializables.Add(kickerApi);
_hittables[entity] = kickerApi;
- _switches[kicker.Name] = kickerApi;
- _coils[kicker.Name] = kickerApi;
- _wires[kicker.Name] = kickerApi;
+ _switchPlayer.RegisterSwitch(kicker, kickerApi);
+ _coilPlayer.RegisterCoil(kicker, kickerApi);
+ _wirePlayer.RegisterWire(kicker, kickerApi);
+ }
+
+ public void RegisterLamp(Light lamp, GameObject go)
+ {
+ var lightApi = new LightApi(lamp, go, this);
+ TableApi.Lights[lamp.Name] = lightApi;
+ _apis.Add(lightApi);
+ _initializables.Add(lightApi);
+ _lampPlayer.RegisterLamp(lamp, lightApi);
+ _wirePlayer.RegisterWire(lamp, lightApi);
}
public void RegisterPlunger(Plunger plunger, Entity entity, GameObject go)
@@ -237,8 +233,8 @@ public void RegisterPlunger(Plunger plunger, Entity entity, GameObject go)
_apis.Add(plungerApi);
_initializables.Add(plungerApi);
_rotatables[entity] = plungerApi;
- _coils[plunger.Name] = plungerApi;
- _wires[plunger.Name] = plungerApi;
+ _coilPlayer.RegisterCoil(plunger, plungerApi);
+ _wirePlayer.RegisterWire(plunger, plungerApi);
}
public void RegisterPrimitive(Primitive primitive, Entity entity, GameObject go)
@@ -285,7 +281,7 @@ public void RegisterSpinner(Spinner spinner, Entity entity, GameObject go)
_initializables.Add(spinnerApi);
_spinnables[entity] = spinnerApi;
_rotatables[entity] = spinnerApi;
- _switches[spinner.Name] = spinnerApi;
+ _switchPlayer.RegisterSwitch(spinner, spinnerApi);
}
public void RegisterTrigger(Trigger trigger, Entity entity, GameObject go)
@@ -295,7 +291,7 @@ public void RegisterTrigger(Trigger trigger, Entity entity, GameObject go)
_apis.Add(triggerApi);
_initializables.Add(triggerApi);
_hittables[entity] = triggerApi;
- _switches[trigger.Name] = triggerApi;
+ _switchPlayer.RegisterSwitch(trigger, triggerApi);
}
public void RegisterTrough(Trough trough, GameObject go)
@@ -304,255 +300,16 @@ public void RegisterTrough(Trough trough, GameObject go)
TableApi.Troughs[trough.Name] = troughApi;
_apis.Add(troughApi);
_initializables.Add(troughApi);
- _switchDevices[trough.Name] = troughApi;
- _coilDevices[trough.Name] = troughApi;
- _wireDevices[trough.Name] = troughApi;
- }
-
- #endregion
-
- #region Mappings
-
- private void SetupCoilMapping()
- {
- if (GameEngine is IGamelogicEngineWithCoils gamelogicEngineWithCoils) {
- var config = Table.Mappings;
- _coilAssignments.Clear();
- foreach (var coilData in config.Data.Coils) {
- switch (coilData.Destination) {
- case CoilDestination.Playfield:
- if (!_coilAssignments.ContainsKey(coilData.Id)) {
- _coilAssignments[coilData.Id] = new List>();
- }
- _coilAssignments[coilData.Id].Add(new Tuple(coilData.PlayfieldItem, false, null));
- if (coilData.Type == CoilType.DualWound) {
- if (!_coilAssignments.ContainsKey(coilData.HoldCoilId)) {
- _coilAssignments[coilData.HoldCoilId] = new List>();
- }
- _coilAssignments[coilData.HoldCoilId].Add(new Tuple(coilData.PlayfieldItem, true, null));
- }
- break;
-
- case CoilDestination.Device:
- if (_coilDevices.ContainsKey(coilData.Device)) {
- var device = _coilDevices[coilData.Device];
- var coil = device.Coil(coilData.DeviceItem);
- if (coil != null) {
- if (!_coilAssignments.ContainsKey(coilData.Id)) {
- _coilAssignments[coilData.Id] = new List>();
- }
- _coilAssignments[coilData.Id].Add(new Tuple(coilData.DeviceItem, false, coilData.Device));
-
- } else {
- Logger.Warn($"Unknown coil \"{coilData.DeviceItem}\" in coil device \"{coilData.Device}\".");
- }
- }
- break;
- }
- }
-
- if (_coilAssignments.Count > 0) {
- gamelogicEngineWithCoils.OnCoilChanged += HandleCoilEvent;
- }
- }
- }
-
- private void SetupSwitchMapping()
- {
- // hook-up game switches
- if (GameEngine is IGamelogicEngineWithSwitches) {
-
- var config = Table.Mappings;
- _keySwitchAssignments.Clear();
- foreach (var switchData in config.Data.Switches) {
- switch (switchData.Source) {
-
- case SwitchSource.Playfield
- when !string.IsNullOrEmpty(switchData.PlayfieldItem)
- && _switches.ContainsKey(switchData.PlayfieldItem):
- {
- var element = _switches[switchData.PlayfieldItem];
- element.AddSwitchId(new SwitchConfig(switchData));
- break;
- }
-
- case SwitchSource.InputSystem:
- if (!_keySwitchAssignments.ContainsKey(switchData.InputAction)) {
- _keySwitchAssignments[switchData.InputAction] = new List();
- }
- _keySwitchAssignments[switchData.InputAction].Add(switchData.Id);
- break;
-
- case SwitchSource.Playfield:
- Logger.Warn($"Cannot find switch \"{switchData.PlayfieldItem}\" on playfield!");
- break;
-
- case SwitchSource.Device
- when !string.IsNullOrEmpty(switchData.Device)
- && _switchDevices.ContainsKey(switchData.Device):
- {
- var device = _switchDevices[switchData.Device];
- var deviceSwitch = device.Switch(switchData.DeviceItem);
- if (deviceSwitch != null) {
- deviceSwitch.AddSwitchId(new SwitchConfig(switchData));
-
- } else {
- Logger.Warn($"Unknown switch \"{switchData.DeviceItem}\" in switch device \"{switchData.Device}\".");
- }
- break;
- }
- case SwitchSource.Device when string.IsNullOrEmpty(switchData.Device):
- Logger.Warn($"Switch device not set for switch \"{switchData.Id}\".");
- break;
-
- case SwitchSource.Device when !_switchDevices.ContainsKey(switchData.Device):
- Logger.Warn($"Unknown switch device \"{switchData.Device}\" for switch \"{switchData.Id}\".");
- break;
-
- case SwitchSource.Constant:
- break;
-
- default:
- Logger.Warn($"Unknown switch source \"{switchData.Source}\".");
- break;
- }
- }
-
- if (_keySwitchAssignments.Count > 0) {
- _inputManager.Enable(HandleKeyInput);
- }
- }
- }
-
- private void SetupWireMapping()
- {
- var config = Table.Mappings;
- _keyWireAssignments.Clear();
- foreach (var wireData in config.Data.Wires) {
- switch (wireData.Source) {
-
- case SwitchSource.Playfield
- when !string.IsNullOrEmpty(wireData.SourcePlayfieldItem)
- && _switches.ContainsKey(wireData.SourcePlayfieldItem):
- {
- _switches[wireData.SourcePlayfieldItem].AddWireDest(new WireDestConfig(wireData));
- break;
- }
-
- case SwitchSource.InputSystem:
- if (!_keyWireAssignments.ContainsKey(wireData.SourceInputAction)) {
- _keyWireAssignments[wireData.SourceInputAction] = new List();
- }
- _keyWireAssignments[wireData.SourceInputAction].Add(new WireDestConfig(wireData));
- break;
-
- case SwitchSource.Playfield:
- Logger.Warn($"Cannot find wire switch \"{wireData.Src}\" on playfield!");
- break;
-
- case SwitchSource.Device
- when !string.IsNullOrEmpty(wireData.SourceDevice)
- && _switchDevices.ContainsKey(wireData.SourceDevice):
- {
- var device = _switchDevices[wireData.SourceDevice];
- var deviceSwitch = device.Switch(wireData.SourceDeviceItem);
- if (deviceSwitch != null) {
- deviceSwitch.AddWireDest(new WireDestConfig(wireData));
- Logger.Info($"Wiring device switch \"{wireData.Src}\" to \"{wireData.Dst}\"");
-
- } else {
- Logger.Warn($"Unknown switch \"{wireData.Src}\" to wire to \"{wireData.Dst}\".");
- }
- break;
- }
- case SwitchSource.Device when string.IsNullOrEmpty(wireData.SourceDevice):
- Logger.Warn($"Switch device not set for switch \"{wireData.Src}\".");
- break;
-
- case SwitchSource.Device when !_switchDevices.ContainsKey(wireData.SourceDevice):
- Logger.Warn($"Unknown switch device \"{wireData.SourceDevice}\" to wire to \"{wireData.Dst}\".");
- break;
-
- case SwitchSource.Constant:
- break;
-
- default:
- Logger.Warn($"Unknown wire switch source \"{wireData.Source}\".");
- break;
- }
- }
-
- if (_keySwitchAssignments.Count > 0) {
- _inputManager.Enable(HandleKeyInput);
- }
- }
-
- private void HandleKeyInput(object obj, InputActionChange change)
- {
- switch (change) {
- case InputActionChange.ActionStarted:
- case InputActionChange.ActionCanceled:
- var action = (InputAction) obj;
- if (_keySwitchAssignments.ContainsKey(action.name)) {
- if (GameEngine is IGamelogicEngineWithSwitches engineWithSwitches) {
- foreach (var switchId in _keySwitchAssignments[action.name]) {
- engineWithSwitches.Switch(switchId, change == InputActionChange.ActionStarted);
- }
- }
- } else {
- Logger.Info($"Unmapped input command \"{action.name}\".");
- }
-
- if (_keyWireAssignments != null && _keyWireAssignments.ContainsKey(action.name)) {
- foreach (var wireConfig in _keyWireAssignments[action.name]) {
- switch (wireConfig.Destination) {
- case WireDestination.Playfield:
- Wire(wireConfig.PlayfieldItem)?.OnChange(change == InputActionChange.ActionStarted);
- break;
-
- case WireDestination.Device:
- if (_wireDevices.ContainsKey(wireConfig.Device)) {
- var device = _wireDevices[wireConfig.Device];
- var wire = device.Wire(wireConfig.DeviceItem);
- if (wire != null) {
- wire.OnChange(change == InputActionChange.ActionStarted);
- } else {
- Logger.Warn($"Unknown wire \"{wireConfig.DeviceItem}\" in wire device \"{wireConfig.Device}\".");
- }
- }
- break;
- }
- }
- }
- break;
- }
- }
-
- private void HandleCoilEvent(object sender, CoilEventArgs coilEvent)
- {
- if (_coilAssignments.ContainsKey(coilEvent.Id)) {
- foreach (var (itemName, isHoldCoil, deviceName) in _coilAssignments[coilEvent.Id]) {
- if (deviceName != null && _coilDevices.ContainsKey(deviceName)) {
- _coilDevices[deviceName].Coil(itemName).OnCoil(coilEvent.IsEnabled, isHoldCoil);
-
- } else if (_coils.ContainsKey(itemName)) {
- _coils[itemName].OnCoil(coilEvent.IsEnabled, isHoldCoil);
-
- } else {
- Logger.Warn($"Cannot trigger unknown coil item {itemName}.");
- }
- }
-
- } else {
- var what = coilEvent.IsEnabled ? "turn on" : "turn off";
- Logger.Warn($"Should {what} unassigned coil {coilEvent.Id}.");
- }
+ _switchPlayer.RegisterSwitchDevice(trough, troughApi);
+ _coilPlayer.RegisterCoilDevice(trough, troughApi);
}
#endregion
#region Events
+ public void ScheduleAction(int timeMs, Action action) => _simulationSystemGroup.ScheduleAction(timeMs, action);
+
public void OnEvent(in EventData eventData)
{
switch (eventData.eventId) {
@@ -598,7 +355,7 @@ public float3 GetGravity()
{
var slope = Table.Data.AngleTiltMin + (Table.Data.AngleTiltMax - Table.Data.AngleTiltMin) * Table.Data.GlobalDifficulty;
var strength = Table.Data.OverridePhysics != 0 ? PhysicsConstants.DefaultTableGravity : Table.Data.Gravity;
- return new float3(0, math.sin(math.radians(slope)) * strength, -math.cos(math.radians(slope)) * strength);
+ return new float3(0, math.sin(math.radians(slope)) * strength, -math.cos(math.radians(slope)) * strength);
}
}
}
diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/SwitchHandler.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/SwitchHandler.cs
index 5b3b1fba7..735d01453 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/Game/SwitchHandler.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/Game/SwitchHandler.cs
@@ -82,8 +82,14 @@ internal void OnSwitch(bool closed)
// if it's pulse, schedule to re-open
if (closed && switchConfig.IsPulseSwitch) {
- SimulationSystemGroup.ScheduleSwitch(switchConfig.PulseDelay,
- () => Engine.Switch(switchConfig.SwitchId, false));
+ SimulationSystemGroup.ScheduleAction(switchConfig.PulseDelay,
+ () => {
+ Engine.Switch(switchConfig.SwitchId, false);
+ IsClosed = false;
+#if UNITY_EDITOR
+ UnityEditorInternal.InternalEditorUtility.RepaintAllViews();
+#endif
+ });
}
}
}
@@ -114,7 +120,7 @@ internal void OnSwitch(bool closed)
// if it's pulse, schedule to re-open
if (closed && wireConfig.IsPulseSource) {
if (dest != null) {
- SimulationSystemGroup.ScheduleSwitch(wireConfig.PulseDelay,
+ SimulationSystemGroup.ScheduleAction(wireConfig.PulseDelay,
() => dest.OnChange(false));
}
}
@@ -123,6 +129,10 @@ internal void OnSwitch(bool closed)
// handle own status
IsClosed = closed;
+
+#if UNITY_EDITOR
+ UnityEditorInternal.InternalEditorUtility.RepaintAllViews();
+#endif
}
internal void ScheduleSwitch(bool closed, int delay, Action onSwitched)
@@ -130,7 +140,7 @@ internal void ScheduleSwitch(bool closed, int delay, Action onSwitched)
// handle switch -> gamelogic engine
if (Engine != null && _switchIds != null) {
foreach (var switchConfig in _switchIds) {
- SimulationSystemGroup.ScheduleSwitch(delay,
+ SimulationSystemGroup.ScheduleAction(delay,
() => Engine.Switch(switchConfig.SwitchId, closed));
}
} else {
@@ -158,14 +168,13 @@ internal void ScheduleSwitch(bool closed, int delay, Action onSwitched)
}
if (dest != null) {
- SimulationSystemGroup.ScheduleSwitch(delay,
- () => dest.OnChange(closed));
+ SimulationSystemGroup.ScheduleAction(delay, () => dest.OnChange(closed));
}
}
}
// handle own status
- SimulationSystemGroup.ScheduleSwitch(delay, () => {
+ SimulationSystemGroup.ScheduleAction(delay, () => {
Debug.Log($"Setting scheduled switch {_name} to {closed}.");
IsClosed = closed;
diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/SwitchPlayer.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/SwitchPlayer.cs
new file mode 100644
index 000000000..4c54bce2b
--- /dev/null
+++ b/VisualPinball.Unity/VisualPinball.Unity/Game/SwitchPlayer.cs
@@ -0,0 +1,184 @@
+// 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 System.Collections.Generic;
+using System.Linq;
+using NLog;
+using UnityEngine.InputSystem;
+using VisualPinball.Engine.VPT;
+using VisualPinball.Engine.VPT.Mappings;
+using VisualPinball.Engine.VPT.Table;
+
+namespace VisualPinball.Unity
+{
+ public class SwitchPlayer
+ {
+ private readonly Dictionary _switches = new Dictionary();
+ private readonly Dictionary _switchStatuses = new Dictionary();
+ private readonly Dictionary _switchDevices = new Dictionary();
+ private readonly Dictionary> _keySwitchAssignments = new Dictionary>();
+
+ private Table _table;
+ private IGamelogicEngine _gamelogicEngine;
+ private InputManager _inputManager;
+
+ private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
+ internal Dictionary SwitchStatuses => _switchStatuses.ToDictionary(s => s.Key, s => s.Value.IsSwitchClosed);
+
+ internal IApiSwitch Switch(string itemName) => _switches.ContainsKey(itemName) ? _switches[itemName] : null;
+ internal IApiSwitch Switch(string device, string itemName) => _switchDevices.ContainsKey(device) ? _switchDevices[device].Switch(itemName) : null;
+ internal void RegisterSwitch(IItem item, IApiSwitch switchApi) => _switches[item.Name] = switchApi;
+ internal void RegisterSwitchDevice(IItem item, IApiSwitchDevice switchDeviceApi) => _switchDevices[item.Name] = switchDeviceApi;
+ public void RegisterWire(MappingsWireData wireData) => _switches[wireData.SourcePlayfieldItem].AddWireDest(new WireDestConfig(wireData));
+ public bool SwitchExists(string name) => _switches.ContainsKey(name);
+ public bool SwitchDeviceExists(string name) => _switchDevices.ContainsKey(name);
+
+ public void Awake(Table table, IGamelogicEngine gamelogicEngine, InputManager inputManager)
+ {
+ _table = table;
+ _gamelogicEngine = gamelogicEngine;
+ _inputManager = inputManager;
+ }
+
+ public void OnStart()
+ {
+ // hook-up game switches
+ if (_gamelogicEngine is IGamelogicEngineWithSwitches) {
+
+ var config = _table.Mappings;
+ _keySwitchAssignments.Clear();
+ foreach (var switchData in config.Data.Switches) {
+ switch (switchData.Source) {
+
+ case SwitchSource.Playfield: {
+
+ if (string.IsNullOrEmpty(switchData.PlayfieldItem)) {
+ Logger.Warn($"Ignoring unassigned switch \"{switchData.Id}\".");
+ break;
+ }
+
+ if (!_switches.ContainsKey(switchData.PlayfieldItem)) {
+ Logger.Error($"Cannot find item \"{switchData.PlayfieldItem}\" for switch \"{switchData.Id}\".");
+ break;
+ }
+
+ var element = _switches[switchData.PlayfieldItem];
+ element.AddSwitchId(new SwitchConfig(switchData));
+ _switchStatuses[switchData.Id] = element;
+ break;
+ }
+
+ case SwitchSource.InputSystem:
+ if (!_keySwitchAssignments.ContainsKey(switchData.InputAction)) {
+ _keySwitchAssignments[switchData.InputAction] = new List();
+ }
+ var keyboardSwitch = new KeyboardSwitch(switchData.Id);
+ _keySwitchAssignments[switchData.InputAction].Add(keyboardSwitch);
+ _switchStatuses[switchData.Id] = keyboardSwitch;
+ break;
+
+ case SwitchSource.Device: {
+
+ // mapping values must be set
+ if (string.IsNullOrEmpty(switchData.Device) || string.IsNullOrEmpty(switchData.DeviceItem)) {
+ Logger.Warn($"Ignoring unassigned device switch \"{switchData.Id}\".");
+ break;
+ }
+
+ // check if device exists
+ if (!_switchDevices.ContainsKey(switchData.Device)) {
+ Logger.Error($"Unknown switch device \"{switchData.Device}\".");
+ break;
+ }
+
+ var device = _switchDevices[switchData.Device];
+ var deviceSwitch = device.Switch(switchData.DeviceItem);
+ if (deviceSwitch != null) {
+ deviceSwitch.AddSwitchId(new SwitchConfig(switchData));
+ _switchStatuses[switchData.Id] = deviceSwitch;
+
+ } else {
+ Logger.Error($"Unknown switch \"{switchData.DeviceItem}\" in switch device \"{switchData.Device}\".");
+ }
+
+ break;
+ }
+
+ case SwitchSource.Constant:
+ _switchStatuses[switchData.Id] = new ConstantSwitch(switchData.Constant == SwitchConstant.NormallyClosed);
+ break;
+
+ default:
+ Logger.Error($"Unknown switch source \"{switchData.Source}\".");
+ break;
+ }
+ }
+
+ if (_keySwitchAssignments.Count > 0) {
+ _inputManager.Enable(HandleKeyInput);
+ }
+ }
+ }
+
+ private void HandleKeyInput(object obj, InputActionChange change)
+ {
+ switch (change) {
+ case InputActionChange.ActionStarted:
+ case InputActionChange.ActionCanceled:
+ var action = (InputAction)obj;
+ if (_keySwitchAssignments.ContainsKey(action.name)) {
+ if (_gamelogicEngine is IGamelogicEngineWithSwitches engineWithSwitches) {
+ foreach (var sw in _keySwitchAssignments[action.name]) {
+ sw.IsSwitchClosed = change == InputActionChange.ActionStarted;
+ engineWithSwitches.Switch(sw.SwitchId, sw.IsSwitchClosed);
+ }
+ }
+ } else {
+ Logger.Info($"Unmapped input command \"{action.name}\".");
+ }
+ break;
+ }
+ }
+
+ public void OnDestroy()
+ {
+ if (_keySwitchAssignments.Count > 0) {
+ _inputManager.Disable(HandleKeyInput);
+ }
+ }
+ }
+
+ internal class KeyboardSwitch : IApiSwitchStatus
+ {
+ public readonly string SwitchId;
+ public bool IsSwitchClosed { get; set; }
+
+ public KeyboardSwitch(string switchId)
+ {
+ SwitchId = switchId;
+ }
+ }
+
+ internal class ConstantSwitch : IApiSwitchStatus
+ {
+ public bool IsSwitchClosed { get; }
+
+ public ConstantSwitch(bool isSwitchClosed)
+ {
+ IsSwitchClosed = isSwitchClosed;
+ }
+ }
+}
diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/SwitchPlayer.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Game/SwitchPlayer.cs.meta
new file mode 100644
index 000000000..a50973a95
--- /dev/null
+++ b/VisualPinball.Unity/VisualPinball.Unity/Game/SwitchPlayer.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 36759aa706930fc40b80fb7fdf9153cf
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/VisualPinballScript.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/VisualPinballScript.cs
deleted file mode 100644
index edacf9a51..000000000
--- a/VisualPinball.Unity/VisualPinball.Unity/Game/VisualPinballScript.cs
+++ /dev/null
@@ -1,53 +0,0 @@
-// 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 UnityEngine;
-
-namespace VisualPinball.Unity
-{
- public class VisualPinballScript : MonoBehaviour
- {
- public virtual void OnAwake(TableApi table, BallManager ballManager)
- {
- // table.Plunger("Plunger").Init += (sender, args) => {
- // KickNewBallToPlunger(table);
- // };
- //
- // table.Kicker("Drain").Hit += (sender, args) => {
- // ((KickerApi)sender).DestroyBall();
- // KickNewBallToPlunger(table);
- // };
-
- table.Init += (sender, args) => {
-
- ballManager.CreateBall(new DebugBallCreator(200f, 620f));
- ballManager.CreateBall(new DebugBallCreator(330f, 360f));
- ballManager.CreateBall(new DebugBallCreator(400f, 700f));
- ballManager.CreateBall(new DebugBallCreator(620f, 820f));
- ballManager.CreateBall(new DebugBallCreator(720f, 400f));
- ballManager.CreateBall(new DebugBallCreator(830f, 870f));
- ballManager.CreateBall(new DebugBallCreator(470f, 230f));
- ballManager.CreateBall(new DebugBallCreator(620f, 1200f));
- };
- }
-
- public void KickNewBallToPlunger(TableApi table)
- {
- table.Kicker("BallRelease").CreateBall();
- table.Kicker("BallRelease").Kick(90, 7);
- }
- }
-}
diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/VisualPinballScript.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Game/VisualPinballScript.cs.meta
deleted file mode 100644
index 829ce27f8..000000000
--- a/VisualPinball.Unity/VisualPinball.Unity/Game/VisualPinballScript.cs.meta
+++ /dev/null
@@ -1,3 +0,0 @@
-fileFormatVersion: 2
-guid: cc915bbd672b42529c0f79b68cd02027
-timeCreated: 1583591575
\ No newline at end of file
diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/VisualPinballSimulationSystemGroup.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/VisualPinballSimulationSystemGroup.cs
index 705d12f02..8466fd55d 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/Game/VisualPinballSimulationSystemGroup.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/Game/VisualPinballSimulationSystemGroup.cs
@@ -52,7 +52,7 @@ internal class VisualPinballSimulationSystemGroup : ComponentSystemGroup
private TransformMeshesSystemGroup _transformMeshesSystemGroup;
private readonly List _afterBallQueues = new List();
- private readonly List _switchQueues = new List();
+ private readonly List _scheduledActions = new List();
private const TimingMode Timing = TimingMode.UnityTime;
@@ -113,11 +113,11 @@ protected override void OnUpdate()
// advance physics position
_nextPhysicsFrameTime += PhysicsConstants.PhysicsStepTime;
- // close switches
- for (var i = _switchQueues.Count - 1; i >= 0; i--) {
- if (_currentPhysicsFrameTime > _switchQueues[i].SwitchAt) {
- _switchQueues[i].Close();
- _switchQueues.RemoveAt(i);
+ // run scheduled actions
+ for (var i = _scheduledActions.Count - 1; i >= 0; i--) {
+ if (_currentPhysicsFrameTime > _scheduledActions[i].ScheduleAt) {
+ _scheduledActions[i].Action();
+ _scheduledActions.RemoveAt(i);
}
}
}
@@ -164,9 +164,9 @@ public void QueueAfterBallCreation(Action action)
_afterBallQueues.Add(action);
}
- public void ScheduleSwitch(int timeoutMs, Action action)
+ public void ScheduleAction(int timeoutMs, Action action)
{
- _switchQueues.Add(new SwitchAction(_currentPhysicsFrameTime + (ulong)timeoutMs * 1000, action));
+ _scheduledActions.Add(new ScheduledAction(_currentPhysicsFrameTime + (ulong)timeoutMs * 1000, action));
}
@@ -178,15 +178,15 @@ private enum TimingMode
Locked60
}
- private class SwitchAction
+ private class ScheduledAction
{
- public readonly ulong SwitchAt;
- public readonly Action Close;
+ public readonly ulong ScheduleAt;
+ public readonly Action Action;
- public SwitchAction(ulong switchAt, Action close)
+ public ScheduledAction(ulong scheduleAt, Action action)
{
- SwitchAt = switchAt;
- Close = close;
+ ScheduleAt = scheduleAt;
+ Action = action;
}
}
}
diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/WirePlayer.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/WirePlayer.cs
new file mode 100644
index 000000000..f4eca2584
--- /dev/null
+++ b/VisualPinball.Unity/VisualPinball.Unity/Game/WirePlayer.cs
@@ -0,0 +1,182 @@
+// 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 System.Collections.Generic;
+using NLog;
+using UnityEngine.InputSystem;
+using VisualPinball.Engine.VPT;
+using VisualPinball.Engine.VPT.Mappings;
+using VisualPinball.Engine.VPT.Table;
+
+namespace VisualPinball.Unity
+{
+ public class WirePlayer
+ {
+ private readonly Dictionary _wires = new Dictionary();
+ private readonly Dictionary _wireDevices = new Dictionary();
+ private readonly Dictionary> _keyWireAssignments = new Dictionary>();
+
+
+ private Table _table;
+ private InputManager _inputManager;
+ private SwitchPlayer _switchPlayer;
+
+ private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
+
+ internal IApiWireDest Wire(string n) => _wires.ContainsKey(n) ? _wires[n] : null;
+ internal IApiWireDeviceDest WireDevice(string n) => _wireDevices.ContainsKey(n) ? _wireDevices[n] : null;
+ internal void RegisterWire(IItem item, IApiWireDest wireApi) => _wires[item.Name] = wireApi;
+ internal void RegisterWireDevice(IItem item, IApiWireDeviceDest wireDeviceApi) => _wireDevices[item.Name] = wireDeviceApi;
+
+ public void Awake(Table table, InputManager inputManager, SwitchPlayer switchPlayer)
+ {
+ _table = table;
+ _inputManager = inputManager;
+ _switchPlayer = switchPlayer;
+ }
+
+ public void OnStart()
+ {
+ var config = _table.Mappings;
+ _keyWireAssignments.Clear();
+ foreach (var wireData in config.Data.Wires) {
+ switch (wireData.Source) {
+
+ case SwitchSource.Playfield: {
+
+ if (string.IsNullOrEmpty(wireData.SourcePlayfieldItem)) {
+ break;
+ }
+
+ if (!_switchPlayer.SwitchExists(wireData.SourcePlayfieldItem)) {
+ Logger.Error($"Cannot find item \"{wireData.SourcePlayfieldItem}\" for wire source.");
+ break;
+ }
+
+ _switchPlayer.RegisterWire(wireData);
+ break;
+ }
+
+ case SwitchSource.InputSystem:
+ if (!_keyWireAssignments.ContainsKey(wireData.SourceInputAction)) {
+ _keyWireAssignments[wireData.SourceInputAction] = new List();
+ }
+ _keyWireAssignments[wireData.SourceInputAction].Add(new WireDestConfig(wireData));
+ break;
+
+ case SwitchSource.Device: {
+
+ // mapping values must be set
+ if (string.IsNullOrEmpty(wireData.SourceDevice) || string.IsNullOrEmpty(wireData.SourceDeviceItem)) {
+ break;
+ }
+
+ // check if device exists
+ if (!_switchPlayer.SwitchDeviceExists(wireData.SourceDevice)) {
+ Logger.Error($"Unknown wire switch device \"{wireData.SourceDevice}\".");
+ break;
+ }
+
+ var deviceSwitch = _switchPlayer.Switch(wireData.SourceDevice, wireData.SourceDeviceItem);
+ if (deviceSwitch != null) {
+ deviceSwitch.AddWireDest(new WireDestConfig(wireData));
+ Logger.Info($"Wiring device switch \"{wireData.Src}\" to \"{wireData.Dst}\"");
+
+ } else {
+ Logger.Warn($"Unknown switch \"{wireData.Src}\" to wire to \"{wireData.Dst}\".");
+ }
+ break;
+ }
+
+ case SwitchSource.Constant:
+ break;
+
+ default:
+ Logger.Warn($"Unknown wire switch source \"{wireData.Source}\".");
+ break;
+ }
+ }
+
+ if (_keyWireAssignments.Count > 0) {
+ _inputManager.Enable(HandleKeyInput);
+ }
+ }
+
+ private void HandleKeyInput(object obj, InputActionChange change)
+ {
+ switch (change) {
+ case InputActionChange.ActionStarted:
+ case InputActionChange.ActionCanceled:
+ var action = (InputAction)obj;
+ if (_keyWireAssignments != null && _keyWireAssignments.ContainsKey(action.name)) {
+ foreach (var wireConfig in _keyWireAssignments[action.name]) {
+ switch (wireConfig.Destination) {
+ case WireDestination.Playfield:
+ Wire(wireConfig.PlayfieldItem)?.OnChange(change == InputActionChange.ActionStarted);
+ break;
+
+ case WireDestination.Device:
+ if (_wireDevices.ContainsKey(wireConfig.Device)) {
+ var device = _wireDevices[wireConfig.Device];
+ var wire = device.Wire(wireConfig.DeviceItem);
+ if (wire != null) {
+ wire.OnChange(change == InputActionChange.ActionStarted);
+ } else {
+ Logger.Warn($"Unknown wire \"{wireConfig.DeviceItem}\" in wire device \"{wireConfig.Device}\".");
+ }
+ }
+ break;
+ }
+ }
+ }
+ break;
+ }
+ }
+
+ public void OnDestroy()
+ {
+ if (_keyWireAssignments.Count > 0) {
+ _inputManager.Disable(HandleKeyInput);
+ }
+ }
+ }
+
+ public struct WireDestConfig
+ {
+ public readonly int Destination;
+ public readonly string PlayfieldItem;
+ public readonly string Device;
+ public readonly string DeviceItem;
+ public readonly int PulseDelay;
+ public bool IsPulseSource;
+
+ public WireDestConfig(MappingsWireData data)
+ {
+ Destination = data.Destination;
+ PlayfieldItem = data.DestinationPlayfieldItem;
+ Device = data.DestinationDevice;
+ DeviceItem = data.DestinationDeviceItem;
+ PulseDelay = data.PulseDelay;
+ IsPulseSource = false;
+ }
+
+ public WireDestConfig WithPulse(bool isPulseSource)
+ {
+ IsPulseSource = isPulseSource;
+ return this;
+ }
+ }
+}
diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/WirePlayer.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Game/WirePlayer.cs.meta
new file mode 100644
index 000000000..8d3a6c40e
--- /dev/null
+++ b/VisualPinball.Unity/VisualPinball.Unity/Game/WirePlayer.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 47ef89abae693fe43acad81109183617
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/VisualPinball.Unity/VisualPinball.Unity/Import/VpxConverter.cs b/VisualPinball.Unity/VisualPinball.Unity/Import/VpxConverter.cs
index fc6c5f838..eeada5f8a 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/Import/VpxConverter.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/Import/VpxConverter.cs
@@ -101,7 +101,7 @@ public void Convert(string fileName, Table table, bool applyPatch = true, string
// add the player script and default game engine
go.AddComponent();
- var dga = go.AddComponent();
+ var dga = go.AddComponent();
// add trough if none available
if (!_table.HasTrough) {
@@ -110,8 +110,8 @@ public void Convert(string fileName, Table table, bool applyPatch = true, string
// populate mappings
if (_table.Mappings.IsEmpty()) {
- _table.Mappings.PopulateSwitches((dga.GameEngine as IGamelogicEngineWithSwitches).AvailableSwitches, table.Switchables, table.SwitchableDevices);
- _table.Mappings.PopulateCoils((dga.GameEngine as IGamelogicEngineWithCoils).AvailableCoils, table.Coilables, table.CoilableDevices);
+ _table.Mappings.PopulateSwitches(((IGamelogicEngineWithSwitches)dga).AvailableSwitches, table.Switchables, table.SwitchableDevices);
+ _table.Mappings.PopulateCoils(((IGamelogicEngineWithCoils)dga).AvailableCoils, table.Coilables, table.CoilableDevices);
}
// don't need that anymore.
diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperApi.cs
index ffeebbf0b..5061f4706 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperApi.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperApi.cs
@@ -41,6 +41,7 @@ public BumperApi(Bumper item, Entity entity, Player player) : base(item, entity,
{
}
+ bool IApiSwitchStatus.IsSwitchClosed => SwitchClosed;
void IApiSwitch.AddSwitchId(SwitchConfig switchConfig) => AddSwitchId(switchConfig.WithPulse(Item.IsPulseSwitch));
void IApiSwitch.AddWireDest(WireDestConfig wireConfig) => AddWireDest(wireConfig.WithPulse(Item.IsPulseSwitch));
void IApiSwitch.DestroyBall(Entity ballEntity) => DestroyBall(ballEntity);
diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperApi.cs
index 4cdf25faf..a3f55040b 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperApi.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperApi.cs
@@ -90,6 +90,7 @@ public void RotateToStart()
EngineProvider.Get().FlipperRotateToStart(Entity);
}
+ bool IApiSwitchStatus.IsSwitchClosed => SwitchClosed;
void IApiSwitch.AddSwitchId(SwitchConfig switchConfig) => AddSwitchId(switchConfig.WithPulse(Item.IsPulseSwitch));
void IApiSwitch.AddWireDest(WireDestConfig wireConfig) => AddWireDest(wireConfig.WithPulse(Item.IsPulseSwitch));
void IApiSwitch.DestroyBall(Entity ballEntity) => DestroyBall(ballEntity);
diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateApi.cs
index 5bb2e2acd..729060ed8 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateApi.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateApi.cs
@@ -74,6 +74,7 @@ public GateApi(Engine.VPT.Gate.Gate item, Entity entity, Player player) : base(i
{
}
+ bool IApiSwitchStatus.IsSwitchClosed => SwitchClosed;
void IApiSwitch.AddSwitchId(SwitchConfig switchConfig) => AddSwitchId(switchConfig.WithPulse(Item.IsPulseSwitch));
void IApiSwitch.AddWireDest(WireDestConfig wireConfig) => AddWireDest(wireConfig.WithPulse(Item.IsPulseSwitch));
void IApiSwitch.DestroyBall(Entity ballEntity) => DestroyBall(ballEntity);
diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetApi.cs
index ab23fab4a..ec6f9ed9a 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetApi.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetApi.cs
@@ -82,6 +82,7 @@ private void SetIsDropped(bool isDropped)
EntityManager.SetComponentData(Entity, data);
}
+ bool IApiSwitchStatus.IsSwitchClosed => SwitchClosed;
void IApiSwitch.AddSwitchId(SwitchConfig switchConfig) => AddSwitchId(switchConfig.WithPulse(Item.IsPulseSwitch));
void IApiSwitch.AddWireDest(WireDestConfig wireConfig) => AddWireDest(wireConfig.WithPulse(Item.IsPulseSwitch));
void IApiSwitch.DestroyBall(Entity ballEntity) => DestroyBall(ballEntity);
diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/IApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/IApi.cs
index ea4b7c3dd..3091e6d6a 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/VPT/IApi.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/IApi.cs
@@ -16,6 +16,7 @@
using System;
using Unity.Entities;
+using VisualPinball.Engine.Math;
namespace VisualPinball.Unity
{
@@ -56,7 +57,7 @@ internal interface IApiSlingshot
void OnSlingshot(Entity ballEntity);
}
- internal interface IApiSwitch
+ internal interface IApiSwitch : IApiSwitchStatus
{
///
/// Set up this switch to send its status to the gamelogic engine with the given ID.
@@ -84,6 +85,11 @@ internal interface IApiSwitch
event EventHandler Switch;
}
+ internal interface IApiSwitchStatus
+ {
+ bool IsSwitchClosed { get; }
+ }
+
internal interface IApiSwitchDevice
{
IApiSwitch Switch(string switchId);
@@ -99,6 +105,27 @@ internal interface IApiCoil : IApiWireDest
void OnCoil(bool enabled, bool isHoldCoil);
}
+ internal interface IApiLamp : IApiWireDest
+ {
+ ///
+ /// Sets the color of the light.
+ ///
+ UnityEngine.Color Color { get; set; }
+
+ ///
+ /// Sets the light intensity to a given value between 0 and 1.
+ ///
+ /// 0.0 = off, 1.0 = full intensity
+ /// If channel is , change intensity, otherwise update color.
+ void OnLamp(float value, ColorChannel channel);
+
+ ///
+ /// Sets the light color of the lamp.
+ ///
+ /// New color to set
+ void OnLampColor(UnityEngine.Color color);
+ }
+
internal interface IApiWireDest
{
void OnChange(bool enabled);
diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ICoilAuthoring.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/ICoilAuthoring.cs
index 11bd22ed1..c7c9ae5cf 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/VPT/ICoilAuthoring.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ICoilAuthoring.cs
@@ -17,7 +17,7 @@
namespace VisualPinball.Unity
{
- public interface ICoilAuthoring : IIdentifiableItemAuthoring
+ public interface ICoilAuthoring : IWireableAuthoring
{
}
}
diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/WireDestConfig.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/ILampAuthoring.cs
similarity index 51%
rename from VisualPinball.Unity/VisualPinball.Unity/Game/WireDestConfig.cs
rename to VisualPinball.Unity/VisualPinball.Unity/VPT/ILampAuthoring.cs
index 2e9983cb5..c2040322a 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/Game/WireDestConfig.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ILampAuthoring.cs
@@ -14,33 +14,13 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
-using VisualPinball.Engine.VPT.Mappings;
+
+using VisualPinball.Engine.Game;
namespace VisualPinball.Unity
{
- public struct WireDestConfig
+ public interface ILampAuthoring : IWireableAuthoring
{
- public readonly int Destination;
- public readonly string PlayfieldItem;
- public readonly string Device;
- public readonly string DeviceItem;
- public readonly int PulseDelay;
- public bool IsPulseSource;
-
- public WireDestConfig(MappingsWireData data)
- {
- Destination = data.Destination;
- PlayfieldItem = data.DestinationPlayfieldItem;
- Device = data.DestinationDevice;
- DeviceItem = data.DestinationDeviceItem;
- PulseDelay = data.PulseDelay;
- IsPulseSource = false;
- }
-
- public WireDestConfig WithPulse(bool isPulseSource)
- {
- IsPulseSource = isPulseSource;
- return this;
- }
+ ILightable Lightable { get; }
}
}
diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ILampAuthoring.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/ILampAuthoring.cs.meta
new file mode 100644
index 000000000..125a7746e
--- /dev/null
+++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ILampAuthoring.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 466fa2b1cbde64c5abd7d457611e1a7f
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/IGameEngineAuthoring.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/IWireableAuthoring.cs
similarity index 88%
rename from VisualPinball.Unity/VisualPinball.Unity/VPT/IGameEngineAuthoring.cs
rename to VisualPinball.Unity/VisualPinball.Unity/VPT/IWireableAuthoring.cs
index 1970b9e62..c1a39a337 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/VPT/IGameEngineAuthoring.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/IWireableAuthoring.cs
@@ -14,11 +14,10 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
-
namespace VisualPinball.Unity
{
- public interface IGameEngineAuthoring
+ public interface IWireableAuthoring : IIdentifiableItemAuthoring
{
- IGamelogicEngine GameEngine { get; }
+
}
}
diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/IWireableAuthoring.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/IWireableAuthoring.cs.meta
new file mode 100644
index 000000000..5a9d1e2a2
--- /dev/null
+++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/IWireableAuthoring.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: a7a06d0a5dfa8b343956da529e83f630
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ItemApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/ItemApi.cs
index 21b632280..4457147b0 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/VPT/ItemApi.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ItemApi.cs
@@ -42,6 +42,8 @@ public abstract class ItemApi : IApi where T : Item where TData
private protected EntityManager EntityManager;
+ private protected bool SwitchClosed => _switchHandler.IsClosed;
+
internal VisualPinballSimulationSystemGroup SimulationSystemGroup => World.DefaultGameObjectInjectionWorld.GetOrCreateSystem();
private readonly Player _player;
diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerApi.cs
index 96b1477d2..053a1650f 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerApi.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerApi.cs
@@ -181,6 +181,7 @@ private static void KickXYZ(Table table, Entity kickerEntity, float angle, float
}
}
+ bool IApiSwitchStatus.IsSwitchClosed => SwitchClosed;
void IApiSwitch.AddSwitchId(SwitchConfig switchConfig) => AddSwitchId(switchConfig.WithPulse(Item.IsPulseSwitch));
void IApiSwitch.AddWireDest(WireDestConfig wireConfig) => AddWireDest(wireConfig.WithPulse(Item.IsPulseSwitch));
diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightApi.cs
new file mode 100644
index 000000000..677846720
--- /dev/null
+++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightApi.cs
@@ -0,0 +1,133 @@
+// 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 System;
+using UnityEngine;
+using VisualPinball.Engine.Math;
+using VisualPinball.Engine.VPT;
+using VisualPinball.Engine.VPT.Light;
+using Color = UnityEngine.Color;
+using Light = VisualPinball.Engine.VPT.Light.Light;
+
+namespace VisualPinball.Unity
+{
+ public class LightApi : ItemApi, IApiInitializable, IApiLamp
+ {
+ ///
+ /// Event emitted when the table is started.
+ ///
+ public event EventHandler Init;
+
+ public int State {
+ get => _state;
+ set => Set(value, value == LightStatus.LightStateOn ? 1.0f : 0f);
+ }
+
+ private int _state;
+ private readonly LightAuthoring _lightAuthoring;
+
+ void IApiWireDest.OnChange(bool enabled) => Set(
+ enabled ? LightStatus.LightStateOn : LightStatus.LightStateOff,
+ enabled ? 1.0f : 0f);
+
+ public Color Color { get => _lightAuthoring.Color; set => _lightAuthoring.Color = value; }
+
+ void IApiLamp.OnLamp(float value, ColorChannel channel)
+ {
+ switch (channel) {
+ case ColorChannel.Alpha: {
+ Set(value == 0.0f ? LightStatus.LightStateOff : LightStatus.LightStateOn, value);
+ break;
+ }
+ case ColorChannel.Red: {
+ var color = _lightAuthoring.Color;
+ color.r = value;
+ _lightAuthoring.Color = color;
+ break;
+ }
+ case ColorChannel.Green: {
+ var color = _lightAuthoring.Color;
+ color.g = value;
+ _lightAuthoring.Color = color;
+ break;
+ }
+ case ColorChannel.Blue: {
+ var color = _lightAuthoring.Color;
+ color.b = value;
+ _lightAuthoring.Color = color;
+ break;
+ }
+ default:
+ throw new ArgumentOutOfRangeException(nameof(channel), channel, null);
+ }
+ }
+
+ void IApiLamp.OnLampColor(Color color)
+ {
+ _lightAuthoring.Color = color;
+ }
+
+ internal LightApi(Light item, GameObject go, Player player) : base(item, player)
+ {
+ _lightAuthoring = go.GetComponentInChildren();
+ _state = item.Data.State;
+ }
+
+ private void Set(int lightStatus, float value)
+ {
+ switch (lightStatus) {
+ case LightStatus.LightStateOff: {
+ if (Data.FadeSpeedDown > 0) {
+ _lightAuthoring.FadeTo(Data.FadeSpeedDown, 0);
+
+ } else {
+ _lightAuthoring.Enabled = false;
+ }
+ break;
+ }
+
+ case LightStatus.LightStateOn: {
+ if (Data.FadeSpeedUp > 0) {
+ _lightAuthoring.FadeTo(Data.FadeSpeedUp, value);
+
+ } else {
+ _lightAuthoring.Enabled = true;
+ }
+ break;
+ }
+
+ case LightStatus.LightStateBlinking: {
+ _lightAuthoring.StartBlinking();
+ break;
+ }
+
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ _state = lightStatus;
+ }
+
+ #region Events
+
+ void IApiInitializable.OnInit(BallManager ballManager)
+ {
+ base.OnInit(ballManager);
+ Init?.Invoke(this, EventArgs.Empty);
+ }
+
+ #endregion
+ }
+}
diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightApi.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightApi.cs.meta
new file mode 100644
index 000000000..cf1c65479
--- /dev/null
+++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightApi.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 00ac33dcc0cd52b4786f94e1bf049fee
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightAuthoring.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightAuthoring.cs
index bc168ff07..1702836ea 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightAuthoring.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightAuthoring.cs
@@ -21,28 +21,111 @@
#endregion
using System;
+using System.Collections;
using System.Collections.Generic;
using System.Linq;
+using NLog;
using UnityEngine;
+using VisualPinball.Engine.Game;
using VisualPinball.Engine.VPT.Light;
using Light = VisualPinball.Engine.VPT.Light.Light;
+using Logger = NLog.Logger;
namespace VisualPinball.Unity
{
[AddComponentMenu("Visual Pinball/Game Item/Light")]
- public class LightAuthoring : ItemMainRenderableAuthoring
+ public class LightAuthoring : ItemMainRenderableAuthoring, ILampAuthoring
{
+ public ILightable Lightable => Item;
+
+ public bool Enabled {
+ set {
+ StopAllCoroutines();
+ _unityLight.enabled = value;
+ }
+ }
+
+ public Color Color {
+ get => _unityLight.color;
+ set => _unityLight.color = value;
+ }
+
private UnityEngine.Light _unityLight;
+ private float _fullIntensity;
protected override Light InstantiateItem(LightData data) => new Light(data);
protected override Type MeshAuthoringType { get; } = typeof(ItemMeshAuthoring);
protected override Type ColliderAuthoringType { get; } = null;
+ private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
+
public override IEnumerable ValidParents => LightBulbMeshAuthoring.ValidParentTypes
.Concat(LightSocketMeshAuthoring.ValidParentTypes)
.Distinct();
+ private void Start()
+ {
+ var player = GetComponentInParent();
+ if (player == null) {
+ Logger.Error($"Cannot find player for lamp {Name}.");
+ return;
+ }
+
+ player.RegisterLamp(Item, gameObject);
+ _unityLight = GetComponentInChildren();
+ _fullIntensity = _unityLight.intensity;
+ }
+
+ public void FadeTo(float seconds, float value)
+ {
+ StopAllCoroutines();
+ StartCoroutine(nameof(Fade), value);
+ }
+
+
+ public void StartBlinking()
+ {
+ StopAllCoroutines();
+ StartCoroutine(nameof(Blink));
+ }
+
+ private IEnumerator Blink()
+ {
+ // parse blink sequence
+ var sequence = Data.BlinkPattern.ToCharArray().Select(c => c == '1').ToArray();
+
+ // step time is stored in ms but we need seconds
+ var stepTime = Data.BlinkInterval / 1000f;
+
+ while (true) {
+ foreach (var on in sequence) {
+ yield return Fade(on ? 1 : 0);
+ var timeFading = on ? Data.FadeSpeedUp : Data.FadeSpeedDown;
+ if (timeFading < stepTime) {
+ yield return new WaitForSeconds(stepTime - timeFading);
+ }
+ }
+ }
+ }
+
+ private IEnumerator Fade(float value)
+ {
+ var counter = 0f;
+
+ var a = _unityLight.intensity;
+ var b = _fullIntensity * value;
+ var duration = a < b
+ ? _data.FadeSpeedUp * (_fullIntensity - a) / _fullIntensity
+ : _data.FadeSpeedDown * (1 - (_fullIntensity - a) / _fullIntensity);
+
+ while (counter < duration) {
+ counter += Time.deltaTime;
+ _unityLight.intensity = Mathf.Lerp(a, b, counter / duration);
+ yield return null;
+ }
+ }
+
public override void Restore()
{
// update the name
diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerApi.cs
index cdba9462b..109c656fe 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerApi.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerApi.cs
@@ -69,6 +69,7 @@ public SpinnerApi(Engine.VPT.Spinner.Spinner item, Entity entity, Player player)
{
}
+ bool IApiSwitchStatus.IsSwitchClosed => SwitchClosed;
void IApiSwitch.AddSwitchId(SwitchConfig switchConfig) => AddSwitchId(switchConfig.WithPulse(Item.IsPulseSwitch));
void IApiSwitch.AddWireDest(WireDestConfig wireConfig) => AddWireDest(wireConfig.WithPulse(Item.IsPulseSwitch));
void IApiSwitch.DestroyBall(Entity ballEntity) => DestroyBall(ballEntity);
diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Table/TableApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Table/TableApi.cs
index 894d9b2ed..2dfa4d0fe 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Table/TableApi.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Table/TableApi.cs
@@ -28,6 +28,7 @@ public class TableApi : IApiInitializable
internal readonly Dictionary Gates = new Dictionary();
internal readonly Dictionary