diff --git a/VisualPinball.Engine.Test/Fixtures~/TroughTest.vpx b/VisualPinball.Engine.Test/Fixtures~/TroughTest.vpx
index 24e3db48a..136b02bc0 100644
Binary files a/VisualPinball.Engine.Test/Fixtures~/TroughTest.vpx and b/VisualPinball.Engine.Test/Fixtures~/TroughTest.vpx differ
diff --git a/VisualPinball.Engine.Test/VPT/Mappings/CoilPopulationTests.cs b/VisualPinball.Engine.Test/VPT/Mappings/CoilPopulationTests.cs
index 5c82848cd..8c3e16076 100644
--- a/VisualPinball.Engine.Test/VPT/Mappings/CoilPopulationTests.cs
+++ b/VisualPinball.Engine.Test/VPT/Mappings/CoilPopulationTests.cs
@@ -145,7 +145,7 @@ public void ShouldMapADeviceCoilByHint()
table.Mappings.Data.Coils[0].Destination.Should().Be(CoilDestination.Device);
table.Mappings.Data.Coils[0].Id.Should().Be("eject_trough");
table.Mappings.Data.Coils[0].Device.Should().Be("my_trough");
- table.Mappings.Data.Coils[0].DeviceItem.Should().Be("eject");
+ table.Mappings.Data.Coils[0].DeviceItem.Should().Be("eject_coil");
}
[Test]
diff --git a/VisualPinball.Engine.Test/VPT/Trough/TroughDataTests.cs b/VisualPinball.Engine.Test/VPT/Trough/TroughDataTests.cs
index c69fefdb0..4ea8b0f00 100644
--- a/VisualPinball.Engine.Test/VPT/Trough/TroughDataTests.cs
+++ b/VisualPinball.Engine.Test/VPT/Trough/TroughDataTests.cs
@@ -44,10 +44,10 @@ private static void ValidateTroughData(TroughData data)
{
data.BallCount.Should().Be(3);
data.SwitchCount.Should().Be(4);
- data.SettleTime.Should().Be(112);
- data.EntryKicker.Should().Be("BallDrain");
- data.ExitKicker.Should().Be("BallRelease");
- data.JamSwitch.Should().Be("TroughJam");
+ data.KickTime.Should().Be(112);
+ data.RollTime.Should().Be(113);
+ data.PlayfieldEntrySwitch.Should().Be("BallDrain");
+ data.PlayfieldExitKicker.Should().Be("BallRelease");
}
}
}
diff --git a/VisualPinball.Engine.Test/VPT/Trough/TroughTests.cs b/VisualPinball.Engine.Test/VPT/Trough/TroughTests.cs
new file mode 100644
index 000000000..706a04ad6
--- /dev/null
+++ b/VisualPinball.Engine.Test/VPT/Trough/TroughTests.cs
@@ -0,0 +1,142 @@
+// Visual Pinball Engine
+// Copyright (C) 2020 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.VPT;
+using VisualPinball.Engine.VPT.Trough;
+
+namespace VisualPinball.Engine.Test.VPT.Trough
+{
+ public class TroughTests
+ {
+ [Test]
+ public void ShouldReturnCorrectSwitchesForModern()
+ {
+ var data = new TroughData("Trough") {
+ Type = TroughType.Modern,
+ SwitchCount = 3
+ };
+ var trough = new Engine.VPT.Trough.Trough(data);
+ var switches = trough.AvailableSwitches.ToArray();
+
+ switches.Should().HaveCount(3);
+ switches[0].Id.Should().Be("1");
+ switches[1].Id.Should().Be("2");
+ switches[2].Id.Should().Be("3");
+ }
+
+ [Test]
+ public void ShouldReturnCorrectCoilsForModern()
+ {
+ var data = new TroughData("Trough") {
+ Type = TroughType.Modern,
+ };
+ var trough = new Engine.VPT.Trough.Trough(data);
+ var coils = trough.AvailableCoils.ToArray();
+
+ coils.Should().HaveCount(1);
+ coils[0].Id.Should().Be(Engine.VPT.Trough.Trough.EjectCoilId);
+ }
+
+ [Test]
+ public void ShouldReturnCorrectSwitchesForTwoCoilsNSwitches()
+ {
+ var data = new TroughData("Trough") {
+ Type = TroughType.TwoCoilsNSwitches,
+ SwitchCount = 3
+ };
+ var trough = new Engine.VPT.Trough.Trough(data);
+ var switches = trough.AvailableSwitches.ToArray();
+
+ switches.Should().HaveCount(4);
+ switches[0].Id.Should().Be(Engine.VPT.Trough.Trough.EntrySwitchId);
+ switches[1].Id.Should().Be("1");
+ switches[2].Id.Should().Be("2");
+ switches[3].Id.Should().Be("3");
+ }
+
+ [Test]
+ public void ShouldReturnCorrectCoilsForTwoCoilsNSwitches()
+ {
+ var data = new TroughData("Trough") {
+ Type = TroughType.TwoCoilsNSwitches,
+ };
+ var trough = new Engine.VPT.Trough.Trough(data);
+ var coils = trough.AvailableCoils.ToArray();
+
+ coils.Should().HaveCount(2);
+ coils[0].Id.Should().Be(Engine.VPT.Trough.Trough.EntryCoilId);
+ coils[1].Id.Should().Be(Engine.VPT.Trough.Trough.EjectCoilId);
+ }
+
+ [Test]
+ public void ShouldReturnCorrectSwitchesForTwoCoilsOneSwitch()
+ {
+ var data = new TroughData("Trough") {
+ Type = TroughType.TwoCoilsOneSwitch,
+ };
+ var trough = new Engine.VPT.Trough.Trough(data);
+ var switches = trough.AvailableSwitches.ToArray();
+
+ switches.Should().HaveCount(2);
+ switches[0].Id.Should().Be(Engine.VPT.Trough.Trough.EntrySwitchId);
+ switches[1].Id.Should().Be(Engine.VPT.Trough.Trough.TroughSwitchId);
+ }
+
+ [Test]
+ public void ShouldReturnCorrectCoilsForTwoCoilsOneSwitch()
+ {
+ var data = new TroughData("Trough") {
+ Type = TroughType.TwoCoilsOneSwitch,
+ };
+ var trough = new Engine.VPT.Trough.Trough(data);
+ var coils = trough.AvailableCoils.ToArray();
+
+ coils.Should().HaveCount(2);
+ coils[0].Id.Should().Be(Engine.VPT.Trough.Trough.EntryCoilId);
+ coils[1].Id.Should().Be(Engine.VPT.Trough.Trough.EjectCoilId);
+ }
+
+
+ [Test]
+ public void ShouldReturnCorrectSwitchesForClassicSingleBall()
+ {
+ var data = new TroughData("Trough") {
+ Type = TroughType.ClassicSingleBall,
+ };
+ var trough = new Engine.VPT.Trough.Trough(data);
+ var switches = trough.AvailableSwitches.ToArray();
+
+ switches.Should().HaveCount(1);
+ switches[0].Id.Should().Be(Engine.VPT.Trough.Trough.EntrySwitchId);
+ }
+
+ [Test]
+ public void ShouldReturnCorrectCoilsForClassicSingleBall()
+ {
+ var data = new TroughData("Trough") {
+ Type = TroughType.ClassicSingleBall,
+ };
+ var trough = new Engine.VPT.Trough.Trough(data);
+ var coils = trough.AvailableCoils.ToArray();
+
+ coils.Should().HaveCount(1);
+ coils[0].Id.Should().Be(Engine.VPT.Trough.Trough.EntryCoilId);
+ }
+ }
+}
diff --git a/VisualPinball.Engine/VPT/Enums.cs b/VisualPinball.Engine/VPT/Enums.cs
index 84bbaf275..4a6897ef5 100644
--- a/VisualPinball.Engine/VPT/Enums.cs
+++ b/VisualPinball.Engine/VPT/Enums.cs
@@ -158,6 +158,14 @@ public static class SwitchType
public const int Pulse = 1;
}
+ public static class TroughType
+ {
+ public const int Modern = 0;
+ public const int TwoCoilsNSwitches = 1;
+ public const int TwoCoilsOneSwitch = 2;
+ public const int ClassicSingleBall = 3;
+ }
+
public static class CoilDestination
{
public const int Playfield = 0;
diff --git a/VisualPinball.Engine/VPT/Table/Table.cs b/VisualPinball.Engine/VPT/Table/Table.cs
index bbef877ed..7f5fdfb33 100644
--- a/VisualPinball.Engine/VPT/Table/Table.cs
+++ b/VisualPinball.Engine/VPT/Table/Table.cs
@@ -149,6 +149,12 @@ private IEnumerable ApplyColliderOverrides(IHittable hittable)
.Concat(_surfaces.Values)
.Concat(_triggers.Values);
+ ///
+ /// Game items that need to be converted but aren't rendered.
+ ///
+ public IEnumerable NonRenderables => new IItem[0]
+ .Concat(_troughs.Values);
+
public IEnumerable GameItems => new IItem[] { }
.Concat(_bumpers.Values)
.Concat(_decals.Select(i => i))
diff --git a/VisualPinball.Engine/VPT/Trough/Trough.cs b/VisualPinball.Engine/VPT/Trough/Trough.cs
index 035dea9d6..aea248c51 100644
--- a/VisualPinball.Engine/VPT/Trough/Trough.cs
+++ b/VisualPinball.Engine/VPT/Trough/Trough.cs
@@ -14,6 +14,7 @@
// 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 System.IO;
using System.Linq;
@@ -27,18 +28,67 @@ public class Trough : Item, ISwitchableDevice, ICoilableDevice
public override string ItemName { get; } = "Trough";
public override string ItemGroupName { get; } = null;
- public const string JamSwitchId = "jam";
- public const string EjectCoilId = "eject";
- public const string EntryCoilId = "entry";
+ public const string EntrySwitchId = "drain_switch";
+ public const string TroughSwitchId = "trough_switch";
+ public const string EjectCoilId = "eject_coil";
+ public const string EntryCoilId = "entry_coil";
- public IEnumerable AvailableSwitches => Enumerable.Repeat(0, Data.SwitchCount)
- .Select((_, i) => new GamelogicEngineSwitch {Description = SwitchDescription(i), Id = $"{i + 1}"})
- .Concat( new[]{ new GamelogicEngineSwitch{Description = "Jam Switch", Id = JamSwitchId} });
+ public IEnumerable AvailableSwitches {
+ get {
+ switch (Data.Type) {
+ case TroughType.Modern:
+ return Enumerable.Repeat(0, Data.SwitchCount)
+ .Select((_, i) => new GamelogicEngineSwitch
+ { Description = SwitchDescription(i), Id = $"{i + 1}" });
- public IEnumerable AvailableCoils => new[] {
- new GamelogicEngineCoil {Description = "Entry", Id = EntryCoilId},
- new GamelogicEngineCoil {Description = "Eject", Id = EjectCoilId}
- };
+ case TroughType.TwoCoilsNSwitches:
+ return new[] {
+ new GamelogicEngineSwitch {Description = "Entry Switch", Id = EntrySwitchId}
+ }.Concat(Enumerable.Repeat(0, Data.SwitchCount)
+ .Select((_, i) => new GamelogicEngineSwitch
+ { Description = SwitchDescription(i), Id = $"{i + 1}"} )
+ );
+
+ case TroughType.TwoCoilsOneSwitch:
+ return new[] {
+ new GamelogicEngineSwitch {Description = "Entry Switch", Id = EntrySwitchId},
+ new GamelogicEngineSwitch {Description = "Trough Switch", Id = TroughSwitchId},
+ };
+
+ case TroughType.ClassicSingleBall:
+ return new[] {
+ new GamelogicEngineSwitch {Description = "Drain Switch", Id = EntrySwitchId},
+ };
+
+ default:
+ throw new ArgumentException("Invalid trough type " + Data.Type);
+
+ }
+ }
+ }
+
+ public IEnumerable AvailableCoils {
+ get {
+ switch (Data.Type) {
+ case TroughType.Modern:
+ return new[] {
+ new GamelogicEngineCoil {Description = "Eject", Id = EjectCoilId}
+ };
+ case TroughType.TwoCoilsNSwitches:
+ case TroughType.TwoCoilsOneSwitch:
+ return new[] {
+ new GamelogicEngineCoil {Description = "Entry", Id = EntryCoilId},
+ new GamelogicEngineCoil {Description = "Eject", Id = EjectCoilId}
+ };
+ case TroughType.ClassicSingleBall:
+ return new[] {
+ new GamelogicEngineCoil {Description = "Entry", Id = EntryCoilId}
+ };
+ default:
+ throw new ArgumentException("Invalid trough type " + Data.Type);
+ }
+ }
+ }
public Trough(TroughData data) : base(data)
{
diff --git a/VisualPinball.Engine/VPT/Trough/TroughData.cs b/VisualPinball.Engine/VPT/Trough/TroughData.cs
index 74e79b289..745c7c880 100644
--- a/VisualPinball.Engine/VPT/Trough/TroughData.cs
+++ b/VisualPinball.Engine/VPT/Trough/TroughData.cs
@@ -38,14 +38,14 @@ public class TroughData : ItemData
[BiffString("NAME", IsWideString = true, Pos = 1)]
public string Name;
- [BiffString("ENTK", Pos = 2)]
- public string EntryKicker = string.Empty;
+ [BiffInt("TYPE", Pos = 2)]
+ public int Type = TroughType.Modern;
- [BiffString("EXIT", Pos = 3)]
- public string ExitKicker = string.Empty;
+ [BiffString("ENTS", Pos = 3)]
+ public string PlayfieldEntrySwitch = string.Empty;
- [BiffString("JAMS", Pos = 4)]
- public string JamSwitch = string.Empty;
+ [BiffString("EXIT", Pos = 4)]
+ public string PlayfieldExitKicker = string.Empty;
[BiffInt("BCNT", Pos = 5)]
public int BallCount = 6;
@@ -53,8 +53,11 @@ public class TroughData : ItemData
[BiffInt("SCNT", Pos = 6)]
public int SwitchCount = 6;
- [BiffInt("TIME", Pos = 7)]
- public int SettleTime = 100;
+ [BiffInt("RTIM", Pos = 7)]
+ public int RollTime = 100;
+
+ [BiffInt("KTIM", Pos = 8)]
+ public int KickTime = 200;
public TroughData(string name) : base(StoragePrefix.GameItem)
{
diff --git a/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/trough-2c1s.png b/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/trough-2c1s.png
new file mode 100644
index 000000000..2e228a27d
Binary files /dev/null and b/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/trough-2c1s.png differ
diff --git a/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/trough-2cns.png b/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/trough-2cns.png
new file mode 100644
index 000000000..768c32185
Binary files /dev/null and b/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/trough-2cns.png differ
diff --git a/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/trough-inspector.png b/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/trough-inspector.png
index b158e77b1..c0b69e343 100644
Binary files a/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/trough-inspector.png and b/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/trough-inspector.png differ
diff --git a/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/trough-modern.png b/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/trough-modern.png
new file mode 100644
index 000000000..c34e0dd67
Binary files /dev/null and b/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/trough-modern.png differ
diff --git a/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/trough-single-ball.png b/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/trough-single-ball.png
new file mode 100644
index 000000000..6a4cae59e
Binary files /dev/null and b/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/trough-single-ball.png differ
diff --git a/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/troughs.md b/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/troughs.md
index 79fefbf78..d05d67396 100644
--- a/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/troughs.md
+++ b/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/troughs.md
@@ -6,26 +6,74 @@ VPE comes with a trough mechanism that simulates the behaviour of a real-world b
## Creating a Trough
-When importing a `.vpx` file that doesn't have any troughs (which is likely, because Visual Pinball doesn't currently handle them in the same way as VPE), VPE will automatically add a main trough to the root of the table. If you're creating a trough for a new game, click on the *Trough* button in the toolbox.
+When importing a `.vpx` file that doesn't have any troughs (which is likely, because Visual Pinball doesn't currently handle them in the same way as VPE) or creating a new table, VPE will automatically add a main trough to the root of the table. In order to create a trough manually, click on the *Trough* button in the toolbox.
## Linking to the Playfield
-
+
-To interact with the game, you'll need to setup an entry kicker to drain the ball into the trough, and an exit kicker to release a new ball from the trough. This terminology may seem weird, since the ball *exits* the playfield when draining, but from the the trough's perspective, that's where the ball *enters*.
+To interact with the game, you'll need to setup an **entry switch** to drain the ball into the trough, and an **exit kicker** to release a new ball from the trough. This terminology may seem weird, since the ball *exits* the playfield when draining, but the links are labelled in relation to the trough.
-You can setup the kickers by selecting the trough in the hierarchy panel and linking them to the desired kickers using the inspector.
+You can setup these links under *Playfield Links* by selecting the trough in the hierarchy panel and linking them to the desired items using the inspector.
+
+The inspector also lets you configure other options:
+
+- **Ball Count** defines how many balls the trough can hold.
+- **Switch Count** sets how many ball switches are available. This is usually the same number as the ball count.
+- **Roll Time** sets how long it takes the ball to roll from one switch to the next.
+- **Kick Time** defines how long it takes the ball to get kicked from the drain into the trough.
+
+## Trough Types
+
+VPE supports several variants of troughs found on real machines. You can configure the behavior of the trough by changing the *Type* in the inspector when the trough is selected in the hierarchy.
+
+In this section we'll again link to the excellent MPF documentation explaining each of the different types.
+
+### Modern (opto or mechanical)
+
+
+
+Modern troughs with both [optical](https://docs.missionpinball.org/en/latest/mechs/troughs/#option-1-modern-trough-with-opto-sensors) and [mechanical](https://docs.missionpinball.org/en/latest/mechs/troughs/#option-2-modern-trough-with-mechanical-switches) switches are covered by this type.
+
+The ball drains from the playfield directly into the ball stack, and every ball slot has an associated switch.
+
+During gameplay, if you select the trough in the hierarchy, it displays the status of every switch in real time for debug purposes.
+
+### Two coils and multiple switches
+
+
+
+[Troughs of this type](https://docs.missionpinball.org/en/latest/mechs/troughs/#option-3-older-style-with-two-coils-and-switches-for-each-ball) can be found in older machines from the 80s and early 90s. They consist of two parts:
+
+1. A drain, the ball rolls into when leaving the playfield
+2. A ball stack, where the out of play balls are kept.
+
+In terms of switches, they still include a switch per ball in the stack, but also an additional drain switch to trigger kicking the ball from the drain into the stack.
+
+### Two coils and one switch
+
+
+
+A trough can also have [only one switch](https://docs.missionpinball.org/en/latest/mechs/troughs/#option-4-older-style-with-two-coils-and-only-one-ball-switch) in the ball stack.
+
+Instead of a *Switch Count* like the previous types, you select a *Switch Position*, which is the position in the ball stack at which the ball farthest away from the eject coil sits.
+
+### Classic single ball
+
+
+
+A single ball trough may work [with](https://docs.missionpinball.org/en/latest/mechs/troughs/#option-5-classic-single-ball-single-coil) or [without](https://docs.missionpinball.org/en/latest/mechs/troughs/#option-6-classic-single-ball-single-coil-no-shooter-lane) a shooter lane. The principle is simple: After draining, the ball is kept on the drain coil, which ejects the ball either directly into the plunger lane or back onto the playfield.
## Switch Setup
-The number of simulated switches in the trough depends on the *Switch Count* property in the inspector panel. For recreations, you can quickly determine the number of trough switches by looking at the switch matrix in the operation manual, it usually matches the number of balls installed in the game.
+The number of simulated switches in the trough depends on the type of the trough and the *Switch Count* property in the inspector panel. For recreations, you can quickly determine the number of trough switches by looking at the switch matrix in the operation manual, it usually matches the number of balls installed in the game.
-Open the [switch manager](../../editor/switch-manager.md) and add the trough switches if they're not already there. As *Destination* select "Device", under *Element*, select the trough you've created and which switch to connect. For a five-ball trough, it will look something like this:
+To configure the switches, open the [switch manager](../../editor/switch-manager.md) and add the trough switches if they're not already there. For *Destination* select "Device", under *Element*, select the trough you've created and which switch to connect. For a modern five-ball trough, it will look something like this:

## Coil Setup
-VPE's trough supports two coils, an entry coil which drains the ball from the outhole into the trough, and an eject coil which pushes a new ball into the plunger lane. Open the [coil manager](../../editor/coil-manager.md), find or add the coils, and link them to the trough like you did with the switches:
+VPE's trough supports two coils, an entry coil which drains the ball from the outhole into the trough, and an eject coil which pushes a new ball into the plunger lane. To configure the coils, open the [coil manager](../../editor/coil-manager.md), find or add the coils, and link them to the trough like you did with the switches:

\ No newline at end of file
diff --git a/VisualPinball.Unity/Documentation~/filterConfig.yml b/VisualPinball.Unity/Documentation~/filterConfig.yml
index 2a0daa1b1..61502fb71 100644
--- a/VisualPinball.Unity/Documentation~/filterConfig.yml
+++ b/VisualPinball.Unity/Documentation~/filterConfig.yml
@@ -4,4 +4,7 @@ apiRules:
uid: VisualPinball.Unity.ApiAttribute
- exclude:
uidRegex: '^VisualPinball\.Unity\..*'
+ type: Type
+ - exclude:
+ uidRegex: ^System\.Object
type: Type
\ No newline at end of file
diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Inspectors/TroughInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Inspectors/TroughInspector.cs
index 919fb3ce3..16dc59fe3 100644
--- a/VisualPinball.Unity/VisualPinball.Unity.Editor/Inspectors/TroughInspector.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Inspectors/TroughInspector.cs
@@ -18,24 +18,98 @@
using UnityEditor;
using UnityEngine;
+using VisualPinball.Engine.VPT;
using VisualPinball.Engine.VPT.Trough;
-using VisualPinball.Engine.VPT.Kicker;
-using VisualPinball.Engine.VPT.Trigger;
namespace VisualPinball.Unity.Editor
{
[CustomEditor(typeof(TroughAuthoring))]
public class TroughInspector : ItemMainInspector
{
+ private static readonly string[] TypeLabels = {
+ "Modern (opto or mechanical)",
+ "Two coils multiple switches",
+ "Two coils one switch",
+ "Classic single ball",
+ };
+
+ private static readonly int[] TypeValues = {
+ TroughType.Modern,
+ TroughType.TwoCoilsNSwitches,
+ TroughType.TwoCoilsOneSwitch,
+ TroughType.ClassicSingleBall
+ };
+
+ private bool _togglePlayfield = true;
+
public override void OnInspectorGUI()
{
- ItemReferenceField("Entry Kicker", "entryKicker", ref Data.EntryKicker);
- ItemReferenceField("Exit Kicker", "exitKicker", ref Data.ExitKicker);
- ItemReferenceField("Jam Switch", "jamSwitch", ref Data.JamSwitch);
+ DropDownField("Type", ref Data.Type, TypeLabels, TypeValues);
+
+ if (Data.Type != TroughType.ClassicSingleBall) {
+ ItemDataSlider("Ball Count", ref Data.BallCount, 1, 10, false);
+ }
+
+ switch (Data.Type) {
+ case TroughType.Modern:
+ case TroughType.TwoCoilsNSwitches:
+ ItemDataSlider("Switch Count", ref Data.SwitchCount, 1, 10, false);
+ break;
+ case TroughType.TwoCoilsOneSwitch:
+ ItemDataSlider("Switch Position", ref Data.SwitchCount, 1, 10, false);
+ break;
+ }
+
+ if (Data.Type != TroughType.Modern && Data.Type != TroughType.TwoCoilsNSwitches) {
+ ItemDataField("Kick Time (ms)", ref Data.KickTime, false);
+ }
+
+ ItemDataField("Roll Time (ms)", ref Data.RollTime, false);
+
+ if (!Application.isPlaying) {
+ if (_togglePlayfield = EditorGUILayout.BeginFoldoutHeaderGroup(_togglePlayfield, "Playfield Links")) {
+ EditorGUI.indentLevel++;
+ ObjectReferenceField("Input Switch", "Switches", "None (Switch)", "inputSwitch", Data.PlayfieldEntrySwitch, n => Data.PlayfieldEntrySwitch = n);
+ ObjectReferenceField("Exit Kicker", "Kickers", "None (Kicker)", "exitKicker", Data.PlayfieldExitKicker, n => Data.PlayfieldExitKicker = n);
+ EditorGUI.indentLevel--;
+ }
+ EditorGUILayout.EndFoldoutHeaderGroup();
+ }
+
+ if (Application.isPlaying) {
+ EditorGUILayout.Separator();
+ EditorGUILayout.LabelField("Switch status:", new GUIStyle(GUI.skin.label) { fontStyle = FontStyle.Bold });
+ var troughApi = _table.GetComponent().TableApi.Trough(Item.Name);
- ItemDataField("Max Balls", ref Data.BallCount, false);
- ItemDataField("Switch Count", ref Data.SwitchCount, false);
- ItemDataField("Settle Time", ref Data.SettleTime, false);
+ if (Data.Type != TroughType.Modern) {
+ DrawSwitch("Drain Switch", troughApi.EntrySwitch);
+ }
+
+ if (Data.Type == TroughType.TwoCoilsOneSwitch) {
+ DrawSwitch("Stack Switch", troughApi.StackSwitch());
+
+ } else if (Data.Type != TroughType.ClassicSingleBall) {
+ for (var i = troughApi.NumStackSwitches - 1; i >= 0; i--) {
+ DrawSwitch(SwitchDescription(i), troughApi.StackSwitch(i));
+ }
+ }
+
+ if (troughApi.UncountedDrainBalls > 0) {
+ EditorGUILayout.LabelField("Undrained balls:", troughApi.UncountedDrainBalls.ToString());
+ }
+ if (troughApi.UncountedStackBalls > 0) {
+ EditorGUILayout.LabelField("Unswitched balls:", troughApi.UncountedStackBalls.ToString());
+ }
+ }
+ }
+
+ private static void DrawSwitch(string label, DeviceSwitch sw)
+ {
+ var labelPos = EditorGUILayout.GetControlRect();
+ labelPos.height = 18;
+ var switchPos = new Rect((float) (labelPos.x + (double) EditorGUIUtility.labelWidth + 2.0), labelPos.y, labelPos.height, labelPos.height);
+ GUI.Label(labelPos, label);
+ GUI.DrawTexture(switchPos, Icons.Switch(sw.IsClosed, IconSize.Small, sw.IsClosed ? IconColor.Orange : IconColor.Gray));
}
protected override void FinishEdit(string label, bool dirtyMesh = true)
@@ -43,5 +117,10 @@ protected override void FinishEdit(string label, bool dirtyMesh = true)
base.FinishEdit(label, dirtyMesh);
ItemAuthoring.UpdatePosition();
}
+
+ private static string SwitchDescription(int i)
+ {
+ return i == 0 ? "Ball 1 (eject)" : $"Ball {i + 1}";
+ }
}
}
diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Coil/CoilListViewItemRenderer.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Coil/CoilListViewItemRenderer.cs
index ff87480ff..f578be7ea 100644
--- a/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Coil/CoilListViewItemRenderer.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Coil/CoilListViewItemRenderer.cs
@@ -189,7 +189,7 @@ private void RenderPlayfieldElement(TableAuthoring tableAuthoring, CoilListData
tableAuthoring,
"Coil Items",
item => {
- coilListData.PlayfieldItem = item.Name;
+ coilListData.PlayfieldItem = item != null ? item.Name : string.Empty;
updateAction(coilListData);
}
);
@@ -210,7 +210,7 @@ private void RenderDeviceElement(TableAuthoring tableAuthoring, CoilListData coi
tableAuthoring,
"Coil Devices",
item => {
- coilListData.Device = item.Name;
+ coilListData.Device = item != null ? item.Name : string.Empty;
updateAction(coilListData);
}
);
diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/ItemSearchableDropdown.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/ItemSearchableDropdown.cs
index 327d14d34..8f9c4ed28 100644
--- a/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/ItemSearchableDropdown.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/ItemSearchableDropdown.cs
@@ -40,6 +40,7 @@ protected override AdvancedDropdownItem BuildRoot()
{
var node = new AdvancedDropdownItem(_title);
var elements = _tableAuthoring.GetComponentsInChildren();
+ node.AddChild(new ElementDropdownItem(null));
foreach (var element in elements) {
node.AddChild(new ElementDropdownItem(element));
}
@@ -56,10 +57,12 @@ private class ElementDropdownItem : AdvancedDropdownItem where TItem : cl
{
public readonly TItem Item;
- public ElementDropdownItem(TItem element) : base(element.Name)
+ public ElementDropdownItem(TItem element) : base(element == null ? "None" : element.Name)
{
- Item = element;
- icon = Icons.ByComponent(element, color: IconColor.Gray, size: IconSize.Small);
+ if (element != null) {
+ Item = element;
+ icon = Icons.ByComponent(element, color: IconColor.Gray, size: IconSize.Small);
+ }
}
}
}
diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Switch/SwitchListViewItemRenderer.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Switch/SwitchListViewItemRenderer.cs
index 21f3dddc8..712d22cb9 100644
--- a/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Switch/SwitchListViewItemRenderer.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Switch/SwitchListViewItemRenderer.cs
@@ -250,7 +250,7 @@ private void RenderPlayfieldElement(TableAuthoring tableAuthoring, SwitchListDat
tableAuthoring,
"Switch Items",
item => {
- switchListData.PlayfieldItem = item.Name;
+ switchListData.PlayfieldItem = item != null ? item.Name : string.Empty;
updateAction(switchListData);
}
);
@@ -282,7 +282,7 @@ private void RenderDeviceElement(TableAuthoring tableAuthoring, SwitchListData s
tableAuthoring,
"Switch Devices",
item => {
- switchListData.Device = item.Name;
+ switchListData.Device = item != null ? item.Name : string.Empty;
updateAction(switchListData);
}
);
diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Wire/WireListViewItemRenderer.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Wire/WireListViewItemRenderer.cs
index 29fe44650..abd27c463 100644
--- a/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Wire/WireListViewItemRenderer.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Wire/WireListViewItemRenderer.cs
@@ -215,7 +215,7 @@ private void RenderSourceElementPlayfield(TableAuthoring tableAuthoring, WireLis
tableAuthoring,
"Switch Items",
item => {
- wireListData.SourcePlayfieldItem = item.Name;
+ wireListData.SourcePlayfieldItem = item != null ? item.Name : string.Empty;
updateAction(wireListData);
}
);
@@ -248,7 +248,7 @@ private void RenderSourceElementDevice(TableAuthoring tableAuthoring, WireListDa
tableAuthoring,
"Switch Devices",
item => {
- wireListData.SourceDevice = item.Name;
+ wireListData.SourceDevice = item != null ? item.Name : string.Empty;
updateAction(wireListData);
}
);
@@ -342,7 +342,7 @@ private void RenderDestinationElementPlayfield(TableAuthoring tableAuthoring, Wi
tableAuthoring,
"Coil Items",
item => {
- wireListData.DestinationPlayfieldItem = item.Name;
+ wireListData.DestinationPlayfieldItem = item != null ? item.Name : string.Empty;
updateAction(wireListData);
}
);
@@ -364,7 +364,7 @@ private void RenderDestinationElementDevice(TableAuthoring tableAuthoring, WireL
tableAuthoring,
"Coil Devices",
item => {
- wireListData.DestinationDevice = item.Name;
+ wireListData.DestinationDevice = item != null ? item.Name : string.Empty;
updateAction(wireListData);
}
);
diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Toolbox/ToolboxEditor.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Toolbox/ToolboxEditor.cs
index eb4eb7ec8..98b20e168 100644
--- a/VisualPinball.Unity/VisualPinball.Unity.Editor/Toolbox/ToolboxEditor.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Toolbox/ToolboxEditor.cs
@@ -182,14 +182,14 @@ private void CreateItem(Func create, string actionName) whe
var table = _tableAuthoring.Table;
var item = create(table);
table.Add(item, true);
- Selection.activeGameObject = CreateRenderable(item as IRenderable);
+ Selection.activeGameObject = CreateRenderable(item);
ItemCreated?.Invoke(Selection.activeGameObject);
Undo.RegisterCreatedObjectUndo(Selection.activeGameObject, actionName);
}
- private GameObject CreateRenderable(IRenderable renderable)
+ private GameObject CreateRenderable(IItem item)
{
- var convertedItem = VpxConverter.CreateGameObjects(_tableAuthoring.Table, renderable, GetOrCreateParent(_tableAuthoring, renderable));
+ var convertedItem = VpxConverter.CreateGameObjects(_tableAuthoring.Table, item, GetOrCreateParent(_tableAuthoring, item));
return convertedItem.MainAuthoring.gameObject;
}
diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Utils/Icons.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Utils/Icons.cs
index 4f4b8eef0..fb7506069 100644
--- a/VisualPinball.Unity/VisualPinball.Unity.Editor/Utils/Icons.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Utils/Icons.cs
@@ -112,7 +112,7 @@ private Icons()
public static Texture2D Target(IconSize size = IconSize.Large, IconColor color = IconColor.Gray) => Instance.GetItem(HitTargetName, size, color);
public static Texture2D Trigger(IconSize size = IconSize.Large, IconColor color = IconColor.Gray) => Instance.GetItem(TriggerName, size, color);
public static Texture2D Trough(IconSize size = IconSize.Large, IconColor color = IconColor.Gray) => Instance.GetItem(TroughName, size, color);
- public static Texture2D Switch(bool normallyClosed, IconSize size = IconSize.Large, IconColor color = IconColor.Gray) => Instance.GetItem(normallyClosed ? SwitchNcName : SwitchNoName, size, color);
+ public static Texture2D Switch(bool isClosed, IconSize size = IconSize.Large, IconColor color = IconColor.Gray) => Instance.GetItem(isClosed ? SwitchNcName : SwitchNoName, size, color);
public static Texture2D Coil(IconSize size = IconSize.Large, IconColor color = IconColor.Gray) => Instance.GetItem(CoilName, size, color);
public static Texture2D Key(IconSize size = IconSize.Large, IconColor color = IconColor.Gray) => Instance.GetItem(KeyName, size, color);
diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/ItemInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/ItemInspector.cs
index 6e8fe6bf7..692e1a8e3 100644
--- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/ItemInspector.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/ItemInspector.cs
@@ -18,6 +18,7 @@
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
+using UnityEditor.IMGUI.Controls;
using UnityEngine;
using VisualPinball.Engine.Game;
using VisualPinball.Engine.Math;
@@ -32,7 +33,10 @@ public abstract class ItemInspector : UnityEditor.Editor
protected TableAuthoring _table;
- private Dictionary _refItems = new Dictionary();
+ private AdvancedDropdownState _itemPickDropdownState;
+
+ private readonly Dictionary _refItems = new Dictionary();
+ private readonly Dictionary _objItems = new Dictionary();
private string[] _allMaterials = new string[0];
private string[] _allTextures = new string[0];
@@ -317,6 +321,69 @@ protected void MaterialField(string label, ref string field, bool dirtyMesh = tr
}
}
+ protected void ObjectReferenceField(string label, string pickerLabel, string noneLabel, string cacheKey, string field, Action onSelected)
+ where T: class, IIdentifiableItemAuthoring
+ {
+ var pos = EditorGUILayout.GetControlRect(true, 18f);
+ pos = EditorGUI.PrefixLabel(pos, new GUIContent(label));
+
+ MonoBehaviour obj = null;
+ if (!_objItems.ContainsKey(cacheKey)) {
+ if (!string.IsNullOrEmpty(field)) {
+ obj = _table.gameObject.GetComponentsInChildren(true)
+ .FirstOrDefault(s => s.Name == field) as MonoBehaviour;
+ _objItems[cacheKey] = obj;
+ }
+ } else {
+ obj = _objItems[cacheKey];
+ }
+
+ var content = obj == null
+ ? new GUIContent(noneLabel)
+ : new GUIContent(obj.name, Icons.ByComponent(obj, IconSize.Small, IconColor.Orange));
+
+ var id = GUIUtility.GetControlID(FocusType.Keyboard, pos);
+ var objectFieldButton = GUI.skin.GetStyle("ObjectFieldButton");
+ var suffixButtonPos = new Rect(pos.xMax - 19f, pos.y + 1, 19f, pos.height - 2);
+
+ EditorGUIUtility.SetIconSize(new Vector2(12f, 12f));
+ if (Event.current.type == EventType.MouseDown && pos.Contains(Event.current.mousePosition)) {
+
+ if (obj != null && !suffixButtonPos.Contains(Event.current.mousePosition)) {
+ EditorGUIUtility.PingObject(obj.gameObject);
+
+ } else {
+ if (_itemPickDropdownState == null) {
+ _itemPickDropdownState = new AdvancedDropdownState();
+ }
+
+ var dropdown = new ItemSearchableDropdown(
+ _itemPickDropdownState,
+ _table,
+ pickerLabel,
+ item => {
+ switch (item) {
+ case null:
+ _objItems[cacheKey] = null;
+ onSelected(string.Empty);
+ break;
+ case MonoBehaviour mb:
+ _objItems[cacheKey] = mb;
+ onSelected(item.Name);
+ break;
+ }
+ }
+ );
+ dropdown.Show(pos);
+ }
+
+ }
+ if (Event.current.type == EventType.Repaint) {
+ EditorStyles.objectField.Draw(pos, content, id, DragAndDrop.activeControlID == id, pos.Contains(Event.current.mousePosition));
+ objectFieldButton.Draw(suffixButtonPos, GUIContent.none, id, DragAndDrop.activeControlID == id, suffixButtonPos.Contains(Event.current.mousePosition));
+ }
+ }
+
#endregion
protected virtual void FinishEdit(string label, bool dirtyMesh = true)
diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/DeviceCoil.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/DeviceCoil.cs
new file mode 100644
index 000000000..2a4d6143b
--- /dev/null
+++ b/VisualPinball.Unity/VisualPinball.Unity/Game/DeviceCoil.cs
@@ -0,0 +1,43 @@
+// Visual Pinball Engine
+// Copyright (C) 2020 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;
+
+namespace VisualPinball.Unity
+{
+ public class DeviceCoil: IApiCoil
+ {
+ private readonly Action _onEnable;
+ private readonly Action _onDisable;
+
+ public DeviceCoil(Action onEnable = null, Action onDisable = null)
+ {
+ _onEnable = onEnable;
+ _onDisable = onDisable;
+ }
+
+ public void OnCoil(bool enabled, bool isHoldCoil)
+ {
+ if (enabled) {
+ _onEnable?.Invoke();
+ } else {
+ _onDisable?.Invoke();
+ }
+ }
+
+ public void OnChange(bool enabled) => OnCoil(enabled, false);
+ }
+}
diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/DeviceCoil.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Game/DeviceCoil.cs.meta
new file mode 100644
index 000000000..60844cb30
--- /dev/null
+++ b/VisualPinball.Unity/VisualPinball.Unity/Game/DeviceCoil.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: c17d60daa61f942409d0fabec92233fe
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/DeviceSwitch.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/DeviceSwitch.cs
index ede683a0c..c1f578733 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/Game/DeviceSwitch.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/Game/DeviceSwitch.cs
@@ -14,29 +14,55 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
+using System;
+using Unity.Entities;
+
namespace VisualPinball.Unity
{
///
- /// Devices switches are switches withing a device that are not directly
- /// linked to any game item.
+ /// Devices switches are switches within a device that are not directly linked to any game item.
///
+ [Api]
public class DeviceSwitch : IApiSwitch
{
+ ///
+ /// Event emitted when the switch opens or closes.
+ ///
+ public event EventHandler Switch;
+
+ ///
+ /// Indicates whether the switch is currently opened or closed.
+ ///
+ public bool IsClosed => _switchHandler.IsClosed;
+
private readonly bool _isPulseSwitch;
private readonly SwitchHandler _switchHandler;
- public DeviceSwitch(bool isPulseSwitch, IGamelogicEngineWithSwitches engine, Player player)
+ public DeviceSwitch(string name, bool isPulseSwitch, Player player)
{
_isPulseSwitch = isPulseSwitch;
- _switchHandler = new SwitchHandler(player, engine);
+ _switchHandler = new SwitchHandler(name, player);
}
public void AddSwitchId(SwitchConfig switchConfig) => _switchHandler.AddSwitchId(switchConfig.WithPulse(_isPulseSwitch));
-
public void AddWireDest(WireDestConfig wireConfig) => _switchHandler.AddWireDest(wireConfig);
+ public void DestroyBall(Entity ballEntity) { } // device switches can't destroy balls
- public void SetSwitch(bool closed) => _switchHandler.OnSwitch(closed);
+ public void SetSwitch(bool closed)
+ {
+ _switchHandler.OnSwitch(closed);
+ Switch?.Invoke(this, new SwitchEventArgs(closed, Entity.Null));
+ }
- public void ScheduleSwitch(bool closed, int delay) => _switchHandler.ScheduleSwitch(closed, delay);
+ public void ScheduleSwitch(bool closed, int delay)
+ {
+ if (delay == 0) {
+ SetSwitch(closed);
+ } else {
+ _switchHandler.ScheduleSwitch(closed, delay, c => {
+ Switch?.Invoke(this, new SwitchEventArgs(c, Entity.Null));
+ });
+ }
+ }
}
}
diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/Engine/DefaultGamelogicEngine.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/Engine/DefaultGamelogicEngine.cs
index 498dee722..ad21d8c4c 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/Game/Engine/DefaultGamelogicEngine.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/Game/Engine/DefaultGamelogicEngine.cs
@@ -20,6 +20,7 @@
using NLog;
using VisualPinball.Engine.Common;
using VisualPinball.Engine.Game.Engines;
+using VisualPinball.Engine.VPT.Trough;
namespace VisualPinball.Unity
{
@@ -33,17 +34,17 @@ public class DefaultGamelogicEngine : IGamelogicEngine, IGamelogicEngineWithSwit
{
public string Name { get; } = "Default Game Engine";
+ public event EventHandler OnCoilChanged;
+
private const string SwLeftFlipper = "s_left_flipper";
private const string SwLeftFlipperEos = "s_left_flipper_eos";
private const string SwRightFlipper = "s_right_flipper";
private const string SwRightFlipperEos = "s_right_flipper_eos";
+ private const string SwTroughDrain = "s_trough_drain";
private const string SwTrough1 = "s_trough1";
private const string SwTrough2 = "s_trough2";
private const string SwTrough3 = "s_trough3";
private const string SwTrough4 = "s_trough4";
- private const string SwTrough5 = "s_trough5";
- private const string SwTrough6 = "s_trough6";
- private const string SwTroughJam = "s_trough_jam";
private const string SwPlunger = "s_plunger";
private const string SwCreateBall = "s_create_ball";
@@ -53,13 +54,12 @@ public class DefaultGamelogicEngine : IGamelogicEngine, IGamelogicEngineWithSwit
new GamelogicEngineSwitch { Id = SwLeftFlipperEos, Description = "Left Flipper (EOS)", PlayfieldItemHint = "^(LeftFlipper|LFlipper|FlipperLeft|FlipperL)$"},
new GamelogicEngineSwitch { Id = SwRightFlipperEos, Description = "Right Flipper (EOS)", PlayfieldItemHint = "^(RightFlipper|RFlipper|FlipperRight|FlipperR)$"},
new GamelogicEngineSwitch { Id = SwPlunger, Description = "Plunger", InputActionHint = InputConstants.ActionPlunger },
+ new GamelogicEngineSwitch { Id = SwTroughDrain, Description = "Trough Drain", DeviceHint = "^Trough\\s*\\d?", DeviceItemHint = Trough.EntrySwitchId },
new GamelogicEngineSwitch { Id = SwTrough1, Description = "Trough 1 (eject)", DeviceHint = "^Trough\\s*\\d?", DeviceItemHint = "1"},
new GamelogicEngineSwitch { Id = SwTrough2, Description = "Trough 2", DeviceHint = "^Trough\\s*\\d?", DeviceItemHint = "2"},
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 = SwTrough5, Description = "Trough 5", DeviceHint = "^Trough\\s*\\d?", DeviceItemHint = "5"},
- new GamelogicEngineSwitch { Id = SwTrough6, Description = "Trough 6 (entry)", DeviceHint = "^Trough\\s*\\d?", DeviceItemHint = "6"},
- new GamelogicEngineSwitch { Id = SwTroughJam, Description = "Trough Jam", DeviceHint = "^Trough\\s*\\d?", DeviceItemHint = "jam"},
+ 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 }
};
@@ -68,6 +68,7 @@ public class DefaultGamelogicEngine : IGamelogicEngine, IGamelogicEngineWithSwit
private const string CoilRightFlipperMain = "c_flipper_right_main";
private const string CoilRightFlipperHold = "c_flipper_right_hold";
private const string CoilAutoPlunger = "c_auto_plunger";
+ private const string CoilTroughEntry = "c_trough_entry";
private const string CoilTroughEject = "c_trough_eject";
public GamelogicEngineCoil[] AvailableCoils { get; } = {
@@ -76,7 +77,8 @@ public class DefaultGamelogicEngine : IGamelogicEngine, IGamelogicEngineWithSwit
new GamelogicEngineCoil { Id = CoilRightFlipperMain, Description = "Right Flipper", PlayfieldItemHint = "^(RightFlipper|RFlipper|FlipperRight|FlipperR)$" },
new GamelogicEngineCoil { Id = CoilRightFlipperHold, MainCoilIdOfHoldCoil = CoilRightFlipperMain },
new GamelogicEngineCoil { Id = CoilAutoPlunger, Description = "Plunger", PlayfieldItemHint = "Plunger" },
- new GamelogicEngineCoil { Id = CoilTroughEject, Description = "Trough Eject", DeviceHint = "^Trough\\s*\\d?", DeviceItemHint = "eject"}
+ new GamelogicEngineCoil { Id = CoilTroughEject, Description = "Trough Eject", DeviceHint = "^Trough\\s*\\d?", DeviceItemHint = Trough.EjectCoilId},
+ new GamelogicEngineCoil { Id = CoilTroughEntry, Description = "Trough Entry", DeviceHint = "^Trough\\s*\\d?", DeviceItemHint = Trough.EntryCoilId},
};
private TableApi _tableApi;
@@ -92,6 +94,9 @@ public void OnInit(TableApi tableApi, BallManager ballManager)
_tableApi = tableApi;
_ballManager = ballManager;
+ // debug print stuff
+ OnCoilChanged += DebugPrintCoil;
+
_switchStatus[SwLeftFlipper] = false;
_switchStatus[SwLeftFlipperEos] = false;
_switchStatus[SwRightFlipper] = false;
@@ -99,8 +104,8 @@ public void OnInit(TableApi tableApi, BallManager ballManager)
_switchStatus[SwPlunger] = false;
_switchStatus[SwCreateBall] = false;
- // debug print stuff
- OnCoilChanged += DebugPrintCoil;
+ // eject ball onto playfield
+ //OnCoilChanged?.Invoke(this, new CoilEventArgs(CoilTroughEject, true));
}
public void OnUpdate()
@@ -112,26 +117,24 @@ public void OnDestroy()
OnCoilChanged -= DebugPrintCoil;
}
- public event EventHandler OnCoilChanged;
-
- public void Switch(string id, bool normallyClosed)
+ public void Switch(string id, bool isClosed)
{
- _switchStatus[id] = normallyClosed;
+ _switchStatus[id] = isClosed;
if (!_switchTime.ContainsKey(id)) {
_switchTime[id] = new Stopwatch();
}
- if (normallyClosed) {
+ if (isClosed) {
_switchTime[id].Restart();
} else {
_switchTime[id].Stop();
}
- Logger.Info("Switch {0} is {1}.", id, normallyClosed ? "closed" : "open after " + _switchTime[id].ElapsedMilliseconds + "ms");
+ Logger.Info("Switch {0} is {1}.", id, isClosed ? "closed" : "open after " + _switchTime[id].ElapsedMilliseconds + "ms");
switch (id) {
case SwLeftFlipper:
- if (normallyClosed) {
+ if (isClosed) {
OnCoilChanged?.Invoke(this, new CoilEventArgs(CoilLeftFlipperMain, true));
} else {
@@ -149,7 +152,7 @@ public void Switch(string id, bool normallyClosed)
break;
case SwRightFlipper:
- if (normallyClosed) {
+ if (isClosed) {
OnCoilChanged?.Invoke(this, new CoilEventArgs(CoilRightFlipperMain, true));
} else {
OnCoilChanged?.Invoke(this,
@@ -166,17 +169,18 @@ public void Switch(string id, bool normallyClosed)
break;
case SwPlunger:
- OnCoilChanged?.Invoke(this, new CoilEventArgs(CoilAutoPlunger, normallyClosed));
+ OnCoilChanged?.Invoke(this, new CoilEventArgs(CoilAutoPlunger, isClosed));
break;
- case SwTrough6:
- if (normallyClosed) {
- OnCoilChanged?.Invoke(this, new CoilEventArgs(CoilTroughEject, true));
+ case SwTroughDrain:
+ if (isClosed) {
+ //OnCoilChanged?.Invoke(this, new CoilEventArgs(CoilTroughEject, true));
+ //OnCoilChanged?.Invoke(this, new CoilEventArgs(CoilTroughEntry, true));
}
break;
case SwCreateBall: {
- if (normallyClosed) {
+ if (isClosed) {
_ballManager.CreateBall(new DebugBallCreator());
}
break;
diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/Engine/IGamelogicEngineWithSwitches.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/Engine/IGamelogicEngineWithSwitches.cs
index def5e299c..6485f1fb0 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/Game/Engine/IGamelogicEngineWithSwitches.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/Game/Engine/IGamelogicEngineWithSwitches.cs
@@ -35,7 +35,7 @@ public interface IGamelogicEngineWithSwitches
/// Enables or disables a switch.
///
/// Name of the switch, as defined by .
- /// True for normally closed (NC) i.e. contact, a.k.a. "on". False for normally open (NO), i.e. no contact, a.k.a "off".
- void Switch(string id, bool normallyClosed);
+ /// True for normally closed (NC) i.e. contact, a.k.a. "on". False for normally open (NO), i.e. no contact, a.k.a "off".
+ void Switch(string id, bool isClosed);
}
}
diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/Player.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/Player.cs
index 5500cacae..3e230cb24 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/Game/Player.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/Game/Player.cs
@@ -45,7 +45,7 @@ namespace VisualPinball.Unity
public class Player : MonoBehaviour
{
public Table Table { get; private set; }
- public TableApi TableApi { get; } = new TableApi();
+ public TableApi TableApi { get; }
// shortcuts
public Matrix4x4 TableToWorld => transform.localToWorldMatrix;
@@ -86,12 +86,19 @@ public class Player : MonoBehaviour
public Player()
{
+ TableApi = new TableApi(this);
_initializables.Add(TableApi);
}
+ #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;
+ #endregion
+
+
#region Lifecycle
private void Awake()
@@ -114,27 +121,6 @@ private void Awake()
}
}
- private void Update()
- {
- GameEngine?.OnUpdate();
- }
-
- 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();
- }
-
- GameEngine?.OnDestroy();
- }
-
private void Start()
{
@@ -157,6 +143,27 @@ private void Start()
GameEngine?.OnInit(TableApi, BallManager);
}
+ private void Update()
+ {
+ GameEngine?.OnUpdate();
+ }
+
+ 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();
+ }
+
+ GameEngine?.OnDestroy();
+ }
+
#endregion
#region Registrations
@@ -295,6 +302,7 @@ public void RegisterTrigger(Trigger trigger, Entity entity, GameObject go)
public void RegisterTrough(Trough trough, GameObject go)
{
var troughApi = new TroughApi(trough, this);
+ TableApi.Troughs[trough.Name] = troughApi;
_apis.Add(troughApi);
_initializables.Add(troughApi);
_switchDevices[trough.Name] = troughApi;
@@ -504,6 +512,15 @@ private void HandleKeyInput(object obj, InputActionChange change)
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;
}
}
@@ -544,11 +561,11 @@ public void OnEvent(in EventData eventData)
if (!_hittables.ContainsKey(eventData.ItemEntity)) {
Debug.LogError($"Cannot find entity {eventData.ItemEntity} in hittables.");
}
- _hittables[eventData.ItemEntity].OnHit();
+ _hittables[eventData.ItemEntity].OnHit(eventData.BallEntity);
break;
case EventId.HitEventsUnhit:
- _hittables[eventData.ItemEntity].OnHit(true);
+ _hittables[eventData.ItemEntity].OnHit(eventData.BallEntity, true);
break;
case EventId.LimitEventsBos:
@@ -564,11 +581,11 @@ public void OnEvent(in EventData eventData)
break;
case EventId.FlipperEventsCollide:
- _collidables[eventData.ItemEntity].OnCollide(eventData.FloatParam);
+ _collidables[eventData.ItemEntity].OnCollide(eventData.BallEntity, eventData.FloatParam);
break;
case EventId.SurfaceEventsSlingshot:
- _slingshots[eventData.ItemEntity].OnSlingshot();
+ _slingshots[eventData.ItemEntity].OnSlingshot(eventData.BallEntity);
break;
default:
diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/SwitchHandler.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/SwitchHandler.cs
index aaccfb920..cae551391 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/Game/SwitchHandler.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/Game/SwitchHandler.cs
@@ -1,7 +1,10 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using NLog;
using Unity.Entities;
+using UnityEngine;
using VisualPinball.Engine.VPT;
+using Logger = NLog.Logger;
namespace VisualPinball.Unity
{
@@ -13,8 +16,11 @@ namespace VisualPinball.Unity
///
public class SwitchHandler
{
+ public bool IsClosed;
+
+ private readonly string _name;
private readonly Player _player;
- private readonly IGamelogicEngineWithSwitches _engine;
+ private IGamelogicEngineWithSwitches Engine => (IGamelogicEngineWithSwitches)_player.GameEngine;
///
/// The list of switches that need to be triggered in the gamelogic engine.
@@ -29,10 +35,10 @@ public class SwitchHandler
private static VisualPinballSimulationSystemGroup SimulationSystemGroup => World.DefaultGameObjectInjectionWorld.GetOrCreateSystem();
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
- public SwitchHandler(Player player, IGamelogicEngineWithSwitches engine)
+ public SwitchHandler(string name, Player player)
{
+ _name = name;
_player = player;
- _engine = engine;
}
///
@@ -64,22 +70,24 @@ public void AddWireDest(WireDestConfig wireConfig)
/// Sends the switch element to the gamelogic engine and linked wires.
///
/// Switch status
- public void OnSwitch(bool closed)
+ internal void OnSwitch(bool closed)
{
- if (_engine != null && _switchIds != null) {
+ // handle switch -> gamelogic engine
+ if (Engine != null && _switchIds != null) {
foreach (var switchConfig in _switchIds) {
- // close the switch now
- _engine.Switch(switchConfig.SwitchId, closed);
+ // set new status now
+ Engine.Switch(switchConfig.SwitchId, closed);
// if it's pulse, schedule to re-open
if (closed && switchConfig.IsPulseSwitch) {
SimulationSystemGroup.ScheduleSwitch(switchConfig.PulseDelay,
- () => _engine.Switch(switchConfig.SwitchId, false));
+ () => Engine.Switch(switchConfig.SwitchId, false));
}
}
}
+ // handle switch -> wire
if (_wires != null) {
foreach (var wireConfig in _wires) {
IApiWireDest dest = null;
@@ -111,19 +119,24 @@ public void OnSwitch(bool closed)
}
}
}
+
+ // handle own status
+ IsClosed = closed;
}
- public void ScheduleSwitch(bool closed, int delay)
+ internal void ScheduleSwitch(bool closed, int delay, Action onSwitched)
{
- if (_engine != null && _switchIds != null) {
+ // handle switch -> gamelogic engine
+ if (Engine != null && _switchIds != null) {
foreach (var switchConfig in _switchIds) {
SimulationSystemGroup.ScheduleSwitch(delay,
- () => _engine.Switch(switchConfig.SwitchId, closed));
+ () => Engine.Switch(switchConfig.SwitchId, closed));
}
} else {
Logger.Warn("Cannot schedule device switch.");
}
+ // handle switch -> wire
if (_wires != null) {
foreach (var wireConfig in _wires) {
IApiWireDest dest = null;
@@ -144,11 +157,22 @@ public void ScheduleSwitch(bool closed, int delay)
}
if (dest != null) {
- SimulationSystemGroup.ScheduleSwitch(wireConfig.PulseDelay,
+ SimulationSystemGroup.ScheduleSwitch(delay,
() => dest.OnChange(closed));
}
}
}
+
+ // handle own status
+ SimulationSystemGroup.ScheduleSwitch(delay, () => {
+ Debug.Log($"Setting scheduled switch {_name} to {closed}.");
+ IsClosed = closed;
+
+#if UNITY_EDITOR
+ UnityEditorInternal.InternalEditorUtility.RepaintAllViews();
+#endif
+ onSwitched.Invoke(closed);
+ });
}
}
}
diff --git a/VisualPinball.Unity/VisualPinball.Unity/Import/VpxConverter.cs b/VisualPinball.Unity/VisualPinball.Unity/Import/VpxConverter.cs
index fbee9a2e4..1abdb9777 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/Import/VpxConverter.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/Import/VpxConverter.cs
@@ -131,17 +131,7 @@ orderby renderable.SubComponent
var lookupName = renderable.Name.ToLower();
renderableLookup[lookupName] = renderable;
- // create group parent if not created (if null, attach it to the table directly).
- if (!string.IsNullOrEmpty(renderable.ItemGroupName)) {
- if (!_groupParents.ContainsKey(renderable.ItemGroupName)) {
- var parent = new GameObject(renderable.ItemGroupName);
- parent.transform.parent = gameObject.transform;
- _groupParents[renderable.ItemGroupName] = parent;
- }
- }
- var groupParent = !string.IsNullOrEmpty(renderable.ItemGroupName)
- ? _groupParents[renderable.ItemGroupName]
- : gameObject;
+ var groupParent = GetGroupParent(renderable);
if (renderable.SubComponent == ItemSubComponent.None) {
// create object(s)
@@ -195,6 +185,31 @@ orderby renderable.SubComponent
_tableAuthoring.Patcher.ApplyPatches(renderableLookup[lookupName], meshMb.gameObject, tableGameObject);
}
}
+
+ // convert non-renderables
+ foreach (var item in _table.NonRenderables) {
+ var groupParent = GetGroupParent(item);
+
+ // create object(s)
+ CreateGameObjects(_table, item, groupParent);
+ }
+ }
+
+ private GameObject GetGroupParent(IItem item)
+ {
+ // create group parent if not created (if null, attach it to the table directly).
+ if (!string.IsNullOrEmpty(item.ItemGroupName)) {
+ if (!_groupParents.ContainsKey(item.ItemGroupName)) {
+ var parent = new GameObject(item.ItemGroupName);
+ parent.transform.parent = gameObject.transform;
+ _groupParents[item.ItemGroupName] = parent;
+ }
+ }
+ var groupParent = !string.IsNullOrEmpty(item.ItemGroupName)
+ ? _groupParents[item.ItemGroupName]
+ : gameObject;
+
+ return groupParent;
}
public static ConvertedItem CreateGameObjects(Table table, IItem item, GameObject parent)
@@ -274,12 +289,16 @@ private void MakeSerializable(GameObject go, Table table)
private void CreateTrough()
{
- var troughData = new TroughData("Trough");
+ var troughData = new TroughData("Trough") {
+ BallCount = 4,
+ SwitchCount = 4,
+ Type = TroughType.TwoCoilsNSwitches
+ };
if (_table.Has("BallRelease")) {
- troughData.ExitKicker = "BallRelease";
+ troughData.PlayfieldExitKicker = "BallRelease";
}
if (_table.Has("Drain")) {
- troughData.EntryKicker = "Drain";
+ troughData.PlayfieldEntrySwitch = "Drain";
}
var item = new Trough(troughData);
_table.Add(item, true);
diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/Collider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/Collider.cs
index 9421c7c85..f0697c748 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/Collider.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/Collider.cs
@@ -167,8 +167,8 @@ public static unsafe float HitTest(ref Collider coll, ref CollisionEventData col
/// are cast and dispatched to their respective implementation.
///
public static unsafe void Collide(ref Collider coll, ref BallData ballData,
- ref NativeQueue.ParallelWriter events, in CollisionEventData collEvent,
- ref Random random)
+ ref NativeQueue.ParallelWriter events, in Entity ballEntity,
+ in CollisionEventData collEvent, ref Random random)
{
fixed (Collider* collider = &coll)
{
@@ -178,22 +178,22 @@ public static unsafe void Collide(ref Collider coll, ref BallData ballData,
((CircleCollider*) collider)->Collide(ref ballData, in collEvent, ref random);
break;
case ColliderType.Line:
- ((LineCollider*) collider)->Collide(ref ballData, ref events, in collEvent, ref random);
+ ((LineCollider*) collider)->Collide(ref ballData, ref events, in ballEntity, in collEvent, ref random);
break;
case ColliderType.Line3D:
- ((Line3DCollider*) collider)->Collide(ref ballData, ref events, in collEvent, ref random);
+ ((Line3DCollider*) collider)->Collide(ref ballData, ref events, in ballEntity, in collEvent, ref random);
break;
case ColliderType.LineZ:
- ((LineZCollider*) collider)->Collide(ref ballData, ref events, in collEvent, ref random);
+ ((LineZCollider*) collider)->Collide(ref ballData, ref events, in ballEntity, in collEvent, ref random);
break;
case ColliderType.Plane:
((PlaneCollider*) collider)->Collide(ref ballData, in collEvent, ref random);
break;
case ColliderType.Point:
- ((PointCollider*) collider)->Collide(ref ballData, ref events, in collEvent, ref random);
+ ((PointCollider*) collider)->Collide(ref ballData, ref events, in ballEntity, in collEvent, ref random);
break;
case ColliderType.Triangle:
- ((TriangleCollider*) collider)->Collide(ref ballData, ref events, in collEvent, ref random);
+ ((TriangleCollider*) collider)->Collide(ref ballData, ref events, in ballEntity, in collEvent, ref random);
break;
default:
@@ -202,7 +202,7 @@ public static unsafe void Collide(ref Collider coll, ref BallData ballData,
}
}
- public static void FireHitEvent(ref BallData ball, ref NativeQueue.ParallelWriter events, in ColliderHeader collHeader)
+ public static void FireHitEvent(ref BallData ball, ref NativeQueue.ParallelWriter events, in Entity ballEntity, in ColliderHeader collHeader)
{
if (collHeader.FireEvents/* && collHeader.IsEnabled*/) { // todo enabled
@@ -218,7 +218,7 @@ public static void FireHitEvent(ref BallData ball, ref NativeQueue.Pa
// must be a new place if only by a little
if (distLs > normalDist) {
- events.Enqueue(new EventData(EventId.HitEventsHit, collHeader.ParentEntity, true));
+ events.Enqueue(new EventData(EventId.HitEventsHit, collHeader.ParentEntity, ballEntity, true));
}
}
}
diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/Line3DCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/Line3DCollider.cs
index 731f768ba..6f64e8168 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/Line3DCollider.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/Line3DCollider.cs
@@ -81,15 +81,15 @@ private static float HitTest(ref CollisionEventData collEvent, ref Line3DCollide
#endregion
- public void Collide(ref BallData ball, ref NativeQueue.ParallelWriter hitEvents,
- in CollisionEventData collEvent, ref Random random)
+ public void Collide(ref BallData ball, ref NativeQueue.ParallelWriter hitEvents,
+ in Entity ballEntity, in CollisionEventData collEvent, ref Random random)
{
var dot = math.dot(collEvent.HitNormal, ball.Velocity);
BallCollider.Collide3DWall(ref ball, in _header.Material, in collEvent, in collEvent.HitNormal, ref random);
if (_header.FireEvents && dot >= _header.Threshold && _header.IsPrimitive) {
// todo m_obj->m_currentHitThreshold = dot;
- Collider.FireHitEvent(ref ball, ref hitEvents, in _header);
+ Collider.FireHitEvent(ref ball, ref hitEvents, in ballEntity, in _header);
}
}
}
diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineCollider.cs
index 4e18581c0..514d0629b 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineCollider.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineCollider.cs
@@ -209,14 +209,14 @@ public static float HitTestBasic(ref CollisionEventData collEvent, ref DynamicBu
#endregion
- public void Collide(ref BallData ball, ref NativeQueue.ParallelWriter hitEvents,
- in CollisionEventData collEvent, ref Random random)
+ public void Collide(ref BallData ball, ref NativeQueue.ParallelWriter hitEvents,
+ in Entity ballEntity, in CollisionEventData collEvent, ref Random random)
{
var dot = math.dot(collEvent.HitNormal, ball.Velocity);
BallCollider.Collide3DWall(ref ball, in _header.Material, in collEvent, in collEvent.HitNormal, ref random);
if (dot <= -_header.Threshold) {
- Collider.FireHitEvent(ref ball, ref hitEvents, in _header);
+ Collider.FireHitEvent(ref ball, ref hitEvents, in ballEntity, in _header);
}
}
diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineSlingshotCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineSlingshotCollider.cs
index b080ec841..cdfaf5830 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineSlingshotCollider.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineSlingshotCollider.cs
@@ -68,7 +68,7 @@ private static float HitTest(ref CollisionEventData collEvent, ref LineSlingshot
return LineCollider.HitTestBasic(ref collEvent, ref insideOfs, in lineColl, in ball, dTime, true, true, true);
}
- public void Collide(ref BallData ball, ref NativeQueue.ParallelWriter events, in LineSlingshotData slingshotData, in CollisionEventData collEvent, ref Random random)
+ public void Collide(ref BallData ball, ref NativeQueue.ParallelWriter events, in Entity ballEntity, in LineSlingshotData slingshotData, in CollisionEventData collEvent, ref Random random)
{
var hitNormal = collEvent.HitNormal;
@@ -113,7 +113,7 @@ public void Collide(ref BallData ball, ref NativeQueue.ParallelWriter
// !! magic distance, must be a new place if only by a little
if (distLs > 0.25f) {
- events.Enqueue(new EventData(EventId.SurfaceEventsSlingshot, _header.ParentEntity, true));
+ events.Enqueue(new EventData(EventId.SurfaceEventsSlingshot, _header.ParentEntity, ballEntity, true));
// todo slingshot animation
// m_slingshotanim.m_TimeReset = g_pplayer->m_time_msec + 100;
diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineZCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineZCollider.cs
index 5c29b51c2..d5eeddc80 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineZCollider.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineZCollider.cs
@@ -155,14 +155,14 @@ public static float HitTest(ref CollisionEventData collEvent, in LineZCollider c
#endregion
- public void Collide(ref BallData ball, ref NativeQueue.ParallelWriter hitEvents,
- in CollisionEventData collEvent, ref Random random)
+ public void Collide(ref BallData ball, ref NativeQueue.ParallelWriter hitEvents,
+ in Entity ballEntity, in CollisionEventData collEvent, ref Random random)
{
var dot = math.dot(collEvent.HitNormal, ball.Velocity);
BallCollider.Collide3DWall(ref ball, in _header.Material, in collEvent, in collEvent.HitNormal, ref random);
if (dot <= -_header.Threshold) {
- Collider.FireHitEvent(ref ball, ref hitEvents, in _header);
+ Collider.FireHitEvent(ref ball, ref hitEvents, in ballEntity, in _header);
}
}
}
diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/PointCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/PointCollider.cs
index 0153d1759..2c0e8b776 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/PointCollider.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/PointCollider.cs
@@ -126,13 +126,13 @@ public float HitTest(ref CollisionEventData collEvent, in BallData ball, float d
#endregion
public void Collide(ref BallData ball, ref NativeQueue.ParallelWriter hitEvents,
- in CollisionEventData collEvent, ref Random random)
+ in Entity ballEntity, in CollisionEventData collEvent, ref Random random)
{
var dot = math.dot(collEvent.HitNormal, ball.Velocity);
BallCollider.Collide3DWall(ref ball, in _header.Material, in collEvent, in collEvent.HitNormal, ref random);
if (dot <= -_header.Threshold) {
- Collider.FireHitEvent(ref ball, ref hitEvents, in _header);
+ Collider.FireHitEvent(ref ball, ref hitEvents, in ballEntity, in _header);
}
}
}
diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/TriangleCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/TriangleCollider.cs
index a72a9f06a..60bc22fef 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/TriangleCollider.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/TriangleCollider.cs
@@ -151,15 +151,15 @@ public float HitTest(ref CollisionEventData collEvent, in DynamicBuffer.ParallelWriter hitEvents,
- in CollisionEventData collEvent, ref Random random)
+ public void Collide(ref BallData ball, ref NativeQueue.ParallelWriter hitEvents,
+ in Entity ballEntity, in CollisionEventData collEvent, ref Random random)
{
var dot = -math.dot(collEvent.HitNormal, ball.Velocity);
BallCollider.Collide3DWall(ref ball, in _header.Material, in collEvent, in _normal, ref random);
if (_header.FireEvents && dot >= _header.Threshold && _header.IsPrimitive) {
// todo m_obj->m_currentHitThreshold = dot;
- Collider.FireHitEvent(ref ball, ref hitEvents, in _header);
+ Collider.FireHitEvent(ref ball, ref hitEvents, in ballEntity, in _header);
}
}
}
diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/StaticCollisionSystem.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/StaticCollisionSystem.cs
index b48d36478..83ae3d3c5 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/StaticCollisionSystem.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/StaticCollisionSystem.cs
@@ -97,7 +97,8 @@ protected override void OnUpdate()
var bumperStaticData = GetComponent(coll.Entity);
var ringData = GetComponent(bumperStaticData.RingEntity);
var skirtData = GetComponent(bumperStaticData.SkirtEntity);
- BumperCollider.Collide(ref ballData, ref events, ref collEvent, ref ringData, ref skirtData, in coll, bumperStaticData, ref random);
+ BumperCollider.Collide(ref ballData, ref events, ref collEvent, ref ringData, ref skirtData,
+ in ballEntity, in coll, bumperStaticData, ref random);
SetComponent(bumperStaticData.RingEntity, ringData);
SetComponent(bumperStaticData.SkirtEntity, skirtData);
break;
@@ -110,7 +111,7 @@ protected override void OnUpdate()
((FlipperCollider*) collider)->Collide(
ref ballData, ref collEvent, ref flipperMovementData, ref events,
- in flipperMaterialData, in flipperVelocityData, in flipperHitData, timeMsec
+ in ballEntity, in flipperMaterialData, in flipperVelocityData, in flipperHitData, timeMsec
);
SetComponent(coll.Entity, flipperMovementData);
break;
@@ -120,7 +121,7 @@ protected override void OnUpdate()
var gateMovementData = GetComponent(gateStaticData.WireEntity);
GateCollider.Collide(
ref ballData, ref collEvent, ref gateMovementData, ref events,
- in coll, in gateStaticData
+ in ballEntity, in coll, in gateStaticData
);
SetComponent(gateStaticData.WireEntity, gateMovementData);
break;
@@ -128,8 +129,8 @@ protected override void OnUpdate()
case ColliderType.LineSlingShot:
var slingshotData = GetComponent(coll.Entity);
((LineSlingshotCollider*) collider)->Collide(
- ref ballData, ref events, in slingshotData,
- in collEvent, ref random);
+ ref ballData, ref events,
+ in ballEntity, in slingshotData, in collEvent, ref random);
break;
case ColliderType.Plunger:
@@ -157,7 +158,7 @@ in spinnerStaticData
var triggerAnimationData = GetComponent(coll.Entity);
TriggerCollider.Collide(
ref ballData, ref events, ref collEvent, ref insideOfs, ref triggerAnimationData,
- in coll
+ in ballEntity, in coll
);
SetComponent(coll.Entity, triggerAnimationData);
break;
@@ -191,19 +192,20 @@ in coll
: collEvent.HitNormal;
var hitTargetAnimationData = GetComponent(coll.Entity);
HitTargetCollider.Collide(ref ballData, ref events, ref hitTargetAnimationData,
- in normal, in collEvent, in coll, ref random);
+ in normal, in ballEntity, in collEvent, in coll, ref random);
SetComponent(coll.Entity, hitTargetAnimationData);
// trigger
} else if (coll.Header.ItemType == ItemType.Trigger) {
var triggerAnimationData = GetComponent(coll.Entity);
TriggerCollider.Collide(
- ref ballData, ref events, ref collEvent, ref insideOfs, ref triggerAnimationData, in coll
+ ref ballData, ref events, ref collEvent, ref insideOfs, ref triggerAnimationData,
+ in ballEntity, in coll
);
SetComponent(coll.Entity, triggerAnimationData);
} else {
- Collider.Collide(ref coll, ref ballData, ref events, in collEvent, ref random);
+ Collider.Collide(ref coll, ref ballData, ref events, in ballEntity, in collEvent, ref random);
}
break;
diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Event/EventData.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Event/EventData.cs
index 4d8a1da51..494aff2f2 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Event/EventData.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Event/EventData.cs
@@ -27,13 +27,33 @@ public readonly struct EventData
{
public readonly EventId eventId;
public readonly Entity ItemEntity;
+ public readonly Entity BallEntity;
public readonly float FloatParam;
public readonly bool GroupEvent;
+ public EventData(EventId eventId, Entity itemEntity, Entity ballEntity, bool groupEvent = false) : this()
+ {
+ this.eventId = eventId;
+ ItemEntity = itemEntity;
+ BallEntity = ballEntity;
+ GroupEvent = groupEvent;
+ }
+
+ public EventData(EventId eventId, Entity itemEntity, Entity ballEntity, float floatParam, bool groupEvent = false) : this()
+ {
+ this.eventId = eventId;
+ ItemEntity = itemEntity;
+ BallEntity = ballEntity;
+ FloatParam = floatParam;
+ GroupEvent = groupEvent;
+ }
+
+
public EventData(EventId eventId, Entity itemEntity, bool groupEvent = false) : this()
{
this.eventId = eventId;
ItemEntity = itemEntity;
+ BallEntity = Entity.Null;
GroupEvent = groupEvent;
}
@@ -41,6 +61,7 @@ public EventData(EventId eventId, Entity itemEntity, float floatParam, bool grou
{
this.eventId = eventId;
ItemEntity = itemEntity;
+ BallEntity = Entity.Null;
FloatParam = floatParam;
GroupEvent = groupEvent;
}
diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperApi.cs
index b8d269614..125bee1f7 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperApi.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperApi.cs
@@ -15,6 +15,7 @@
// along with this program. If not, see .
using System;
+using System.Transactions;
using Unity.Entities;
using VisualPinball.Engine.VPT.Bumper;
@@ -30,7 +31,12 @@ public class BumperApi : ItemApi, IApiInitializable, IApiHit
///
/// Event emitted when the ball hits the bumper.
///
- public event EventHandler Hit;
+ public event EventHandler Hit;
+
+ ///
+ /// Event emitted when the trigger is switched on or off.
+ ///
+ public event EventHandler Switch;
public BumperApi(Bumper item, Entity entity, Player player) : base(item, entity, player)
{
@@ -38,6 +44,7 @@ public BumperApi(Bumper item, Entity entity, Player player) : base(item, entity,
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);
void IApiCoil.OnCoil(bool enabled, bool _)
{
@@ -55,12 +62,14 @@ void IApiCoil.OnCoil(bool enabled, bool _)
void IApiInitializable.OnInit(BallManager ballManager)
{
+ base.OnInit(ballManager);
Init?.Invoke(this, EventArgs.Empty);
}
- void IApiHittable.OnHit(bool isUnHit)
+ void IApiHittable.OnHit(Entity ballEntity, bool isUnHit)
{
- Hit?.Invoke(this, EventArgs.Empty);
+ Hit?.Invoke(this, new HitEventArgs(ballEntity));
+ Switch?.Invoke(this, new SwitchEventArgs(!isUnHit, ballEntity));
OnSwitch(true);
}
diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperCollider.cs
index 932d4bc53..d3da63f01 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperCollider.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperCollider.cs
@@ -15,6 +15,7 @@
// along with this program. If not, see .
using Unity.Collections;
+using Unity.Entities;
using Unity.Mathematics;
using VisualPinball.Engine.Game;
@@ -24,7 +25,7 @@ internal static class BumperCollider
{
public static void Collide(ref BallData ball, ref NativeQueue.ParallelWriter events,
ref CollisionEventData collEvent, ref BumperRingAnimationData ringData, ref BumperSkirtAnimationData skirtData,
- in Collider collider, in BumperStaticData data, ref Random random)
+ in Entity ballEntity, in Collider collider, in BumperStaticData data, ref Random random)
{
// todo
// if (!m_enabled) return;
@@ -41,7 +42,7 @@ public static void Collide(ref BallData ball, ref NativeQueue.Paralle
skirtData.HitEvent = true;
skirtData.BallPosition = ball.Position;
- events.Enqueue(new EventData(EventId.HitEventsHit, collider.ParentEntity, true));
+ events.Enqueue(new EventData(EventId.HitEventsHit, collider.ParentEntity, ballEntity, true));
}
}
}
diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/EventArgs.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/EventArgs.cs
index 0455eae0e..60dd08e42 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/VPT/EventArgs.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/EventArgs.cs
@@ -14,6 +14,8 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
+using Unity.Entities;
+
namespace VisualPinball.Unity
{
@@ -28,4 +30,26 @@ public struct RotationEventArgs
///
public float AngleSpeed;
}
+
+ public struct HitEventArgs
+ {
+ public Entity BallEntity;
+
+ public HitEventArgs(Entity ballEntity)
+ {
+ BallEntity = ballEntity;
+ }
+ }
+
+ public readonly struct SwitchEventArgs
+ {
+ public readonly bool IsClosed;
+ public readonly Entity BallEntity;
+
+ public SwitchEventArgs(bool isClosed, Entity ballEntity)
+ {
+ IsClosed = isClosed;
+ BallEntity = ballEntity;
+ }
+ }
}
diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperApi.cs
index bb7840052..24648341b 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperApi.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperApi.cs
@@ -40,7 +40,7 @@ public class FlipperApi : ItemApi, IApiInitializable, IApi
/// Event emitted when the flipper was touched by the ball, but did
/// not collide.
///
- public event EventHandler Hit;
+ public event EventHandler Hit;
///
/// Event emitted when the flipper collided with the ball.
@@ -58,6 +58,11 @@ public class FlipperApi : ItemApi, IApiInitializable, IApi
///
public event EventHandler LimitEos;
+ ///
+ /// Event emitted when the trigger is switched on or off.
+ ///
+ public event EventHandler Switch;
+
// todo
public event EventHandler Timer;
@@ -87,6 +92,7 @@ public void RotateToStart()
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);
void IApiCoil.OnCoil(bool enabled, bool isHoldCoil)
{
@@ -99,6 +105,7 @@ void IApiCoil.OnCoil(bool enabled, bool isHoldCoil)
} else {
if (_isEos && isHoldCoil) {
_isEos = false;
+ Switch?.Invoke(this, new SwitchEventArgs(false, Entity.Null));
OnSwitch(false);
RotateToStart();
}
@@ -114,27 +121,29 @@ void IApiCoil.OnCoil(bool enabled, bool isHoldCoil)
void IApiInitializable.OnInit(BallManager ballManager)
{
+ base.OnInit(ballManager);
Init?.Invoke(this, EventArgs.Empty);
}
- void IApiHittable.OnHit(bool _)
+ void IApiHittable.OnHit(Entity ballEntity, bool _)
{
- Hit?.Invoke(this, EventArgs.Empty);
+ Hit?.Invoke(this, new HitEventArgs(ballEntity));
}
void IApiRotatable.OnRotate(float speed, bool direction)
{
if (direction) {
_isEos = true;
- OnSwitch(true);
LimitEos?.Invoke(this, new RotationEventArgs { AngleSpeed = speed });
+ Switch?.Invoke(this, new SwitchEventArgs(true, Entity.Null));
+ OnSwitch(true);
} else {
LimitBos?.Invoke(this, new RotationEventArgs { AngleSpeed = speed });
}
}
- void IApiCollidable.OnCollide(float hit)
+ void IApiCollidable.OnCollide(Entity ballEntity, float hit)
{
Collide?.Invoke(this, new CollideEventArgs { FlipperHit = hit });
}
diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs
index 86cd19539..b1971eddd 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs
@@ -626,7 +626,7 @@ private void GetRelativeVelocity(in float3 normal, in BallData ball, in FlipperM
#region Collision
public void Collide(ref BallData ball, ref CollisionEventData collEvent, ref FlipperMovementData movementData,
- ref NativeQueue.ParallelWriter events, in FlipperStaticData matData,
+ ref NativeQueue.ParallelWriter events, in Entity ballEntity, in FlipperStaticData matData,
in FlipperVelocityData velData, in FlipperHitData hitData, uint timeMsec)
{
var normal = collEvent.HitNormal;
@@ -762,11 +762,11 @@ public void Collide(ref BallData ball, ref CollisionEventData collEvent, ref Fli
var flipperHit = hitData.HitMomentBit ? -1.0f : -bnv; // move event processing to end of collision handler...
if (flipperHit < 0f) {
// simple hit event
- events.Enqueue(new EventData(EventId.HitEventsHit, _header.ParentEntity, true));
+ events.Enqueue(new EventData(EventId.HitEventsHit, _header.ParentEntity, ballEntity, true));
} else {
// collision velocity (normal to face)
- events.Enqueue(new EventData(EventId.FlipperEventsCollide, _header.ParentEntity, flipperHit));
+ events.Enqueue(new EventData(EventId.FlipperEventsCollide, _header.ParentEntity, ballEntity, flipperHit));
}
}
movementData.LastHitTime = timeMsec; // keep resetting until idle for 250 milliseconds
diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateApi.cs
index 0e77bdc37..41aec7ab1 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateApi.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateApi.cs
@@ -38,7 +38,7 @@ public class GateApi : ItemApi,
///
/// Also note that the gate must be collidable.
///
- public event EventHandler Hit;
+ public event EventHandler Hit;
///
/// Event emitted when the gate passes its parked position. Only
@@ -62,6 +62,11 @@ public class GateApi : ItemApi,
///
public event EventHandler LimitEos;
+ ///
+ /// Event emitted when the trigger is switched on or off.
+ ///
+ public event EventHandler Switch;
+
// todo
public event EventHandler Timer;
@@ -71,17 +76,20 @@ public GateApi(Engine.VPT.Gate.Gate item, Entity entity, Player player) : base(i
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);
#region Events
void IApiInitializable.OnInit(BallManager ballManager)
{
+ base.OnInit(ballManager);
Init?.Invoke(this, EventArgs.Empty);
}
- void IApiHittable.OnHit(bool _)
+ void IApiHittable.OnHit(Entity ballEntity, bool _)
{
- Hit?.Invoke(this, EventArgs.Empty);
+ Hit?.Invoke(this, new HitEventArgs(ballEntity));
+ Switch?.Invoke(this, new SwitchEventArgs(true, ballEntity));
OnSwitch(true);
}
diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateCollider.cs
index b0dc96e3e..5da163d0c 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateCollider.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateCollider.cs
@@ -76,7 +76,7 @@ public float HitTest(ref CollisionEventData collEvent, ref DynamicBuffer.ParallelWriter events, in Collider coll, in GateStaticData data)
+ ref NativeQueue.ParallelWriter events, in Entity ballEntity, in Collider coll, in GateStaticData data)
{
var dot = math.dot(collEvent.HitNormal, ball.Velocity);
var h = data.Height * 0.5f;
@@ -100,7 +100,7 @@ public static void Collide(ref BallData ball, ref CollisionEventData collEvent,
movementData.AngleSpeed = -movementData.AngleSpeed;
}
- Collider.FireHitEvent(ref ball, ref events, in coll.Header);
+ Collider.FireHitEvent(ref ball, ref events, in ballEntity, in coll.Header);
}
#endregion
diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetApi.cs
index cc87b6cec..0a2b294b6 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetApi.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetApi.cs
@@ -30,7 +30,12 @@ public class HitTargetApi : ItemApi
/// Event emitted when the ball hits the hit target.
///
- public event EventHandler Hit;
+ public event EventHandler Hit;
+
+ ///
+ /// Event emitted when the trigger is switched on or off.
+ ///
+ public event EventHandler Switch;
///
/// Sets the status of a drop target.
@@ -79,17 +84,20 @@ private void SetIsDropped(bool isDropped)
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);
#region Events
void IApiInitializable.OnInit(BallManager ballManager)
{
+ base.OnInit(ballManager);
Init?.Invoke(this, EventArgs.Empty);
}
- void IApiHittable.OnHit(bool _)
+ void IApiHittable.OnHit(Entity ballEntity, bool _)
{
- Hit?.Invoke(this, EventArgs.Empty);
+ Hit?.Invoke(this, new HitEventArgs(ballEntity));
+ Switch?.Invoke(this, new SwitchEventArgs(true, ballEntity));
OnSwitch(true);
}
diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetCollider.cs
index 96daf7a79..fb48da9ef 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetCollider.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetCollider.cs
@@ -15,6 +15,7 @@
// along with this program. If not, see .
using Unity.Collections;
+using Unity.Entities;
using Unity.Mathematics;
namespace VisualPinball.Unity
@@ -22,7 +23,7 @@ namespace VisualPinball.Unity
internal static class HitTargetCollider
{
public static void Collide(ref BallData ball, ref NativeQueue.ParallelWriter hitEvents,
- ref HitTargetAnimationData animationData, in float3 normal, in CollisionEventData collEvent,
+ ref HitTargetAnimationData animationData, in float3 normal, in Entity ballEntity, in CollisionEventData collEvent,
in Collider coll, ref Random random)
{
var dot = -math.dot(collEvent.HitNormal, ball.Velocity);
@@ -31,7 +32,7 @@ public static void Collide(ref BallData ball, ref NativeQueue.Paralle
if (coll.FireEvents && dot >= coll.Threshold && !animationData.IsDropped) {
animationData.HitEvent = true;
//todo m_obj->m_currentHitThreshold = dot;
- Collider.FireHitEvent(ref ball, ref hitEvents, in coll.Header);
+ Collider.FireHitEvent(ref ball, ref hitEvents, in ballEntity, in coll.Header);
}
}
}
diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/IApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/IApi.cs
index dea730265..1eafe9920 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/VPT/IApi.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/IApi.cs
@@ -14,6 +14,9 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
+using System;
+using Unity.Entities;
+
namespace VisualPinball.Unity
{
public interface IApi
@@ -29,7 +32,8 @@ internal interface IApiInitializable
internal interface IApiHittable
{
- void OnHit(bool isUnHit = false);
+ void OnHit(Entity ballEntity, bool isUnHit = false);
+ event EventHandler Hit;
}
internal interface IApiRotatable
@@ -39,7 +43,7 @@ internal interface IApiRotatable
internal interface IApiCollidable
{
- void OnCollide(float hit);
+ void OnCollide(Entity ballEntity, float hit);
}
internal interface IApiSpinnable
@@ -49,13 +53,35 @@ internal interface IApiSpinnable
internal interface IApiSlingshot
{
- void OnSlingshot();
+ void OnSlingshot(Entity ballEntity);
}
internal interface IApiSwitch
{
+ ///
+ /// Set up this switch to send its status to the gamelogic engine with the given ID.
+ ///
+ /// Config containing gamelogic engine's switch ID and pulse settings
void AddSwitchId(SwitchConfig switchConfig);
+
+ ///
+ /// Set up this switch to directly trigger another game item (coil or lamp), or
+ /// a coil within a coil device.
+ ///
+ /// Configuration which game item to link to
void AddWireDest(WireDestConfig wireConfig);
+
+ void DestroyBall(Entity ballEntity);
+
+ ///
+ /// Event emitted when the trigger is switched on or off.
+ ///
+ ///
+ ///
+ /// If the ball triggered the switch, you'll get the ball entity as well.
+ /// Note that for pulse switches, you currently only get the "closed" event.
+ ///
+ event EventHandler Switch;
}
internal interface IApiSwitchDevice
diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ItemApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/ItemApi.cs
index 66465d9f0..4ec347cfe 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/VPT/ItemApi.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ItemApi.cs
@@ -20,40 +20,58 @@
namespace VisualPinball.Unity
{
+ ///
+ /// Base class for all item APIs.
+ ///
+ /// Item type
+ /// Item data type
+ [Api]
public abstract class ItemApi : IApi where T : Item where TData : ItemData
{
+ ///
+ /// Item name
+ ///
public string Name => Item.Name;
- protected readonly T Item;
+ private protected readonly T Item;
internal readonly Entity Entity;
- protected TData Data => Item.Data;
- protected Table Table => _player.Table;
- protected TableApi TableApi => _player.TableApi;
+ private protected TData Data => Item.Data;
+ private protected Table Table => _player.Table;
+ private protected TableApi TableApi => _player.TableApi;
- protected EntityManager EntityManager;
+ private protected EntityManager EntityManager;
internal VisualPinballSimulationSystemGroup SimulationSystemGroup => World.DefaultGameObjectInjectionWorld.GetOrCreateSystem();
private readonly Player _player;
private readonly SwitchHandler _switchHandler;
+ private protected BallManager BallManager;
- protected ItemApi(T item, Player player)
+ private protected ItemApi(T item, Player player)
{
Item = item;
Entity = Entity.Null;
_player = player;
- _gamelogicEngineWithSwitches = (IGamelogicEngineWithSwitches)player.GameEngine;
}
- protected ItemApi(T item, Entity entity, Player player)
+ private protected ItemApi(T item, Entity entity, Player player)
{
EntityManager = World.DefaultGameObjectInjectionWorld.EntityManager;
Item = item;
Entity = entity;
_player = player;
- _switchHandler = new SwitchHandler(player, (IGamelogicEngineWithSwitches)player.GameEngine);
- _gamelogicEngineWithSwitches = (IGamelogicEngineWithSwitches)player.GameEngine;
+ _switchHandler = new SwitchHandler(Name, player);
+ }
+
+ private protected void OnInit(BallManager ballManager)
+ {
+ BallManager = ballManager;
+ }
+
+ private protected void DestroyBall(Entity ballEntity)
+ {
+ BallManager.DestroyEntity(ballEntity);
}
void IApi.OnDestroy()
@@ -62,15 +80,13 @@ void IApi.OnDestroy()
#region IApiSwitchable
- private readonly IGamelogicEngineWithSwitches _gamelogicEngineWithSwitches;
-
- protected DeviceSwitch CreateSwitch(bool isPulseSwitch) => new DeviceSwitch(isPulseSwitch, _gamelogicEngineWithSwitches, _player);
+ private protected DeviceSwitch CreateSwitch(string name, bool isPulseSwitch) => new DeviceSwitch(name, isPulseSwitch, _player);
- protected void AddSwitchId(SwitchConfig switchConfig) => _switchHandler.AddSwitchId(switchConfig);
+ private protected void AddSwitchId(SwitchConfig switchConfig) => _switchHandler.AddSwitchId(switchConfig);
internal void AddWireDest(WireDestConfig wireConfig) => _switchHandler.AddWireDest(wireConfig);
- protected void OnSwitch(bool normallyClosed) => _switchHandler.OnSwitch(normallyClosed);
+ private protected void OnSwitch(bool closed) => _switchHandler.OnSwitch(closed);
#endregion
}
diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerApi.cs
index 6d060aae6..ade263587 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerApi.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerApi.cs
@@ -25,8 +25,6 @@ namespace VisualPinball.Unity
{
public class KickerApi : ItemApi, IApiInitializable, IApiHittable, IApiSwitch, IApiCoil
{
- private BallManager _ballManager;
-
///
/// Event emitted when the table is started.
///
@@ -35,12 +33,17 @@ public class KickerApi : ItemApi, IApiInitializable, IApiHit
///
/// Event emitted when the ball moves into the kicker.
///
- public event EventHandler Hit;
+ public event EventHandler Hit;
///
/// Event emitted when the ball leaves the kicker.
///
- public event EventHandler UnHit;
+ public event EventHandler UnHit;
+
+ ///
+ /// Event emitted when the trigger is switched on or off.
+ ///
+ public event EventHandler Switch;
public KickerApi(Kicker item, Entity entity, Player player) : base(item, entity, player)
{
@@ -48,17 +51,17 @@ public KickerApi(Kicker item, Entity entity, Player player) : base(item, entity,
public void CreateBall()
{
- _ballManager.CreateBall(Item, 25f, 1f, Entity);
+ BallManager.CreateBall(Item, 25f, 1f, Entity);
}
public void CreateSizedBallWithMass(float radius, float mass)
{
- _ballManager.CreateBall(Item, radius, mass, Entity);
+ BallManager.CreateBall(Item, radius, mass, Entity);
}
public void CreateSizedBall(float radius)
{
- _ballManager.CreateBall(Item, radius, 1f, Entity);
+ BallManager.CreateBall(Item, radius, 1f, Entity);
}
public void Kick()
@@ -84,9 +87,15 @@ public void DestroyBall()
var entityManager = World.DefaultGameObjectInjectionWorld.EntityManager;
var kickerCollisionData = entityManager.GetComponentData(Entity);
var ballEntity = kickerCollisionData.BallEntity;
+ (this as IApiSwitch).DestroyBall(ballEntity);
+ }
+
+
+ void IApiSwitch.DestroyBall(Entity ballEntity)
+ {
if (ballEntity != Entity.Null) {
- _ballManager.DestroyEntity(ballEntity);
- SimulationSystemGroup.QueueAfterBallCreation(() => DestroyBall(Entity));
+ BallManager.DestroyEntity(ballEntity);
+ SimulationSystemGroup.QueueAfterBallCreation(OnBallDestroyed);
}
}
@@ -98,16 +107,16 @@ void IApiCoil.OnCoil(bool enabled, bool _)
}
void IApiWireDest.OnChange(bool enabled) => (this as IApiCoil).OnCoil(enabled, false);
- private static void DestroyBall(Entity kickerEntity)
+ private void OnBallDestroyed()
{
var entityManager = World.DefaultGameObjectInjectionWorld.EntityManager;
- var kickerCollisionData = entityManager.GetComponentData(kickerEntity);
+ var kickerCollisionData = entityManager.GetComponentData(Entity);
var ballEntity = kickerCollisionData.BallEntity;
if (ballEntity != Entity.Null) {
// update kicker status
kickerCollisionData.BallEntity = Entity.Null;
- entityManager.SetComponentData(kickerEntity, kickerCollisionData);
+ entityManager.SetComponentData(Entity, kickerCollisionData);
}
}
@@ -179,18 +188,20 @@ private static void KickXYZ(Table table, Entity kickerEntity, float angle, float
void IApiInitializable.OnInit(BallManager ballManager)
{
- _ballManager = ballManager;
+ base.OnInit(ballManager);
Init?.Invoke(this, EventArgs.Empty);
}
- void IApiHittable.OnHit(bool isUnHit)
+ void IApiHittable.OnHit(Entity ballEntity, bool isUnHit)
{
if (isUnHit) {
- UnHit?.Invoke(this, EventArgs.Empty);
+ UnHit?.Invoke(this, new HitEventArgs(ballEntity));
+ Switch?.Invoke(this, new SwitchEventArgs(false, ballEntity));
OnSwitch(false);
} else {
- Hit?.Invoke(this, EventArgs.Empty);
+ Hit?.Invoke(this, new HitEventArgs(ballEntity));
+ Switch?.Invoke(this, new SwitchEventArgs(true, ballEntity));
OnSwitch(true);
}
}
diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerCollider.cs
index 967d6cb09..8f18cdeae 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerCollider.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerCollider.cs
@@ -89,7 +89,7 @@ public static void Collide(ref BallData ball, ref NativeQueue.Paralle
}
// Fire the event before changing ball attributes, so scripters can get a useful ball state
- events.Enqueue(new EventData(EventId.HitEventsHit, collEntity, true));
+ events.Enqueue(new EventData(EventId.HitEventsHit, collEntity, ballEntity, true));
if (ball.IsFrozen || staticData.FallThrough) { // script may have unfrozen the ball
@@ -114,7 +114,7 @@ public static void Collide(ref BallData ball, ref NativeQueue.Paralle
} else { // exiting kickers volume
// remove kicker to ball's volume set
BallData.SetOutsideOf(ref insideOfs, collEntity);
- events.Enqueue(new EventData(EventId.HitEventsUnhit, collEntity, true));
+ events.Enqueue(new EventData(EventId.HitEventsUnhit, collEntity, ballEntity, true));
}
}
}
diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerApi.cs
index a28a29873..46e938f86 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerApi.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerApi.cs
@@ -106,6 +106,7 @@ void IApiCoil.OnCoil(bool enabled, bool _)
void IApiInitializable.OnInit(BallManager ballManager)
{
+ base.OnInit(ballManager);
Init?.Invoke(this, EventArgs.Empty);
}
diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveApi.cs
index 9b168436f..52a87bc82 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveApi.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveApi.cs
@@ -30,7 +30,7 @@ public class PrimitiveApi : ItemApi
/// Event emitted when the ball glides on the primitive.
///
- public event EventHandler Hit;
+ public event EventHandler Hit;
internal PrimitiveApi(Engine.VPT.Primitive.Primitive item, Entity entity, Player player) : base(item, entity, player)
{
@@ -40,12 +40,13 @@ internal PrimitiveApi(Engine.VPT.Primitive.Primitive item, Entity entity, Player
void IApiInitializable.OnInit(BallManager ballManager)
{
+ base.OnInit(ballManager);
Init?.Invoke(this, EventArgs.Empty);
}
- void IApiHittable.OnHit(bool _)
+ void IApiHittable.OnHit(Entity ballEntity, bool _)
{
- Hit?.Invoke(this, EventArgs.Empty);
+ Hit?.Invoke(this, new HitEventArgs(ballEntity));
}
#endregion
diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampApi.cs
index ed4168e2f..2cac86e8f 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampApi.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampApi.cs
@@ -34,6 +34,7 @@ internal RampApi(Engine.VPT.Ramp.Ramp item, Entity entity, Player player) : base
void IApiInitializable.OnInit(BallManager ballManager)
{
+ base.OnInit(ballManager);
Init?.Invoke(this, EventArgs.Empty);
}
diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberApi.cs
index 95a8225d9..bf66d400e 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberApi.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberApi.cs
@@ -29,7 +29,7 @@ public class RubberApi : ItemApi
/// Event emitted when the ball hits the rubber.
///
- public event EventHandler Hit;
+ public event EventHandler Hit;
internal RubberApi(Engine.VPT.Rubber.Rubber item, Entity entity, Player player) : base(item, entity, player)
{
@@ -39,12 +39,13 @@ internal RubberApi(Engine.VPT.Rubber.Rubber item, Entity entity, Player player)
void IApiInitializable.OnInit(BallManager ballManager)
{
+ base.OnInit(ballManager);
Init?.Invoke(this, EventArgs.Empty);
}
- void IApiHittable.OnHit(bool _)
+ void IApiHittable.OnHit(Entity ballEntity, bool _)
{
- Hit?.Invoke(this, EventArgs.Empty);
+ Hit?.Invoke(this, new HitEventArgs(ballEntity));
}
#endregion
diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerApi.cs
index 3bd1e86ae..612b6c5e1 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerApi.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerApi.cs
@@ -57,6 +57,11 @@ public class SpinnerApi : ItemApi
public event EventHandler Spin;
+ ///
+ /// Event emitted when the trigger is switched on or off.
+ ///
+ public event EventHandler Switch;
+
// todo
public event EventHandler Timer;
@@ -66,17 +71,20 @@ public SpinnerApi(Engine.VPT.Spinner.Spinner item, Entity entity, Player player)
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);
#region Events
void IApiInitializable.OnInit(BallManager ballManager)
{
+ base.OnInit(ballManager);
Init?.Invoke(this, EventArgs.Empty);
}
void IApiSpinnable.OnSpin()
{
Spin?.Invoke(this, EventArgs.Empty);
+ Switch?.Invoke(this, new SwitchEventArgs(true, Entity.Null));
OnSwitch(true);
}
diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceApi.cs
index caf29eb5a..6a2948ccb 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceApi.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceApi.cs
@@ -30,7 +30,7 @@ public class SurfaceApi : ItemApi
/// Event emitted when the ball hits the surface.
///
- public event EventHandler Hit;
+ public event EventHandler Hit;
///
/// Event emitted when a slingshot segment was hit.
@@ -45,15 +45,16 @@ internal SurfaceApi(Engine.VPT.Surface.Surface item, Entity entity, Player playe
void IApiInitializable.OnInit(BallManager ballManager)
{
+ base.OnInit(ballManager);
Init?.Invoke(this, EventArgs.Empty);
}
- void IApiHittable.OnHit(bool _)
+ void IApiHittable.OnHit(Entity ballEntity, bool _)
{
- Hit?.Invoke(this, EventArgs.Empty);
+ Hit?.Invoke(this, new HitEventArgs(ballEntity));
}
- public void OnSlingshot()
+ public void OnSlingshot(Entity ballEntity)
{
Slingshot?.Invoke(this, EventArgs.Empty);
}
diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Table/TableApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Table/TableApi.cs
index 0069ac2e7..400bd5993 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Table/TableApi.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Table/TableApi.cs
@@ -21,6 +21,8 @@ namespace VisualPinball.Unity
{
public class TableApi : IApiInitializable
{
+ private readonly Player _player;
+
internal readonly Dictionary Bumpers = new Dictionary();
internal readonly Dictionary Flippers = new Dictionary();
internal readonly Dictionary Gates = new Dictionary();
@@ -32,8 +34,16 @@ public class TableApi : IApiInitializable
internal readonly Dictionary Spinners = new Dictionary();
internal readonly Dictionary Surfaces = new Dictionary();
internal readonly Dictionary Triggers = new Dictionary();
+ internal readonly Dictionary Troughs = new Dictionary();
internal readonly Dictionary Primitives = new Dictionary();
+ public TableApi(Player player)
+ {
+ _player = player;
+ }
+
+ internal IApiSwitch Switch(string name) => _player.Switch(name);
+
///
/// Event emitted before the game starts.
///
@@ -116,6 +126,13 @@ public class TableApi : IApiInitializable
/// Trigger or `null` if no trigger with that name exists.
public TriggerApi Trigger(string name) => Triggers.ContainsKey(name) ? Triggers[name] : null;
+ ///
+ /// Returns a trough by name.
+ ///
+ /// Name of the trough
+ /// Trigger or `null` if no trough with that name exists.
+ public TroughApi Trough(string name) => Troughs.ContainsKey(name) ? Troughs[name] : null;
+
#region Events
void IApiInitializable.OnInit(BallManager ballManager)
diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerApi.cs
index 7a8b01ceb..d883a724f 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerApi.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerApi.cs
@@ -30,12 +30,17 @@ public class TriggerApi : ItemApi
/// Event emitted when the ball glides on the trigger.
///
- public event EventHandler Hit;
+ public event EventHandler Hit;
///
/// Event emitted when the ball leaves the trigger.
///
- public event EventHandler UnHit;
+ public event EventHandler UnHit;
+
+ ///
+ /// Event emitted when the trigger is switched on or off.
+ ///
+ public event EventHandler Switch;
internal TriggerApi(Engine.VPT.Trigger.Trigger item, Entity entity, Player player) : base(item, entity, player)
{
@@ -43,22 +48,26 @@ internal TriggerApi(Engine.VPT.Trigger.Trigger item, Entity entity, Player playe
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);
#region Events
void IApiInitializable.OnInit(BallManager ballManager)
{
+ base.OnInit(ballManager);
Init?.Invoke(this, EventArgs.Empty);
}
- void IApiHittable.OnHit(bool isUnHit)
+ void IApiHittable.OnHit(Entity ballEntity, bool isUnHit)
{
if (isUnHit) {
- UnHit?.Invoke(this, EventArgs.Empty);
+ UnHit?.Invoke(this, new HitEventArgs(ballEntity));
+ Switch?.Invoke(this, new SwitchEventArgs(false, ballEntity));
OnSwitch(false);
} else {
- Hit?.Invoke(this, EventArgs.Empty);
+ Hit?.Invoke(this, new HitEventArgs(ballEntity));
+ Switch?.Invoke(this, new SwitchEventArgs(true, ballEntity));
OnSwitch(true);
}
}
diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerCollider.cs
index 048189aed..8ad98a082 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerCollider.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerCollider.cs
@@ -25,14 +25,14 @@ internal static class TriggerCollider
{
public static void Collide(ref BallData ball, ref NativeQueue.ParallelWriter events,
ref CollisionEventData collEvent, ref DynamicBuffer insideOfs,
- ref TriggerAnimationData animationData, in Collider coll)
+ ref TriggerAnimationData animationData, in Entity ballEntity, in Collider coll)
{
- Collide(ref ball, ref events, ref collEvent, ref insideOfs, ref animationData, in coll, true);
+ Collide(ref ball, ref events, ref collEvent, ref insideOfs, ref animationData, in ballEntity, in coll, true);
}
private static void Collide(ref BallData ball, ref NativeQueue.ParallelWriter events,
ref CollisionEventData collEvent, ref DynamicBuffer insideOfs,
- ref TriggerAnimationData animationData, in Collider coll, bool animate)
+ ref TriggerAnimationData animationData, in Entity ballEntity, in Collider coll, bool animate)
{
// todo?
// if (!ball.isRealBall()) {
@@ -49,7 +49,7 @@ private static void Collide(ref BallData ball, ref NativeQueue.Parall
animationData.HitEvent = true;
}
- events.Enqueue(new EventData(EventId.HitEventsHit, coll.ParentEntity, true));
+ events.Enqueue(new EventData(EventId.HitEventsHit, coll.ParentEntity, ballEntity, true));
} else {
BallData.SetOutsideOf(ref insideOfs, coll.Entity);
@@ -57,7 +57,7 @@ private static void Collide(ref BallData ball, ref NativeQueue.Parall
animationData.UnHitEvent = true;
}
- events.Enqueue(new EventData(EventId.HitEventsUnhit, coll.ParentEntity, true));
+ events.Enqueue(new EventData(EventId.HitEventsUnhit, coll.ParentEntity, ballEntity, true));
}
}
}
diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trough/TroughApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trough/TroughApi.cs
index ce1c494d6..8b01dafea 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trough/TroughApi.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trough/TroughApi.cs
@@ -18,54 +18,128 @@
using System.Collections.Generic;
using NLog;
using UnityEngine;
+using VisualPinball.Engine.VPT;
using VisualPinball.Engine.VPT.Trough;
using Logger = NLog.Logger;
namespace VisualPinball.Unity
{
+ ///
+ /// A trough implements all known trough behaviors that exist in the real world.
+ ///
+ ///
+ ///
+ /// A trough consists of two parts:
+ ///
+ /// - The **drain** where the ball lands after it exists the playfield. In [modern troughs](#) this part does not
+ /// exist, since the balls go directly into the trough.
+ /// - The **ball stack**, where balls are stored for games that hold more than one ball.
+ ///
+ [Api]
public class TroughApi : ItemApi, IApi, IApiInitializable, IApiSwitchDevice, IApiCoilDevice, IApiWireDeviceDest
{
///
- /// The entry kicker is where the ball rolls into the trough.
+ /// How many stack switches there are available.
///
- private KickerApi _entryKicker;
+ ///
+ ///
+ /// The drain switch is not considered a stack switch.
+ ///
+ public int NumStackSwitches => Data.SwitchCount;
///
- /// The exit kicker is where new balls are created when we get the eject
- /// coil event from the gamelogic engine.
+ /// The entry switch.
+ ///
+ /// This is the switch that is closed when the ball lands in the drain.
///
- private KickerApi _exitKicker;
+ ///
+ ///
+ /// Is null for , all of modern's switches are in .
+ ///
+ public DeviceSwitch EntrySwitch { get; private set; }
///
- /// The ball switches. These are virtual switches that don't exist on the
- /// playfield, but running on them will
- /// send the event to the gamelogic engine.
+ /// Returns the switch for multi ball troughs that only have one
+ /// switch.
///
- private DeviceSwitch[] _ballSwitches;
+ ///
+ /// The stack switch
+ public DeviceSwitch StackSwitch() => _stackSwitches[0];
///
- /// Number of virtual balls currently in the trough
+ /// Returns the stack switch at a given position for and
+ /// troughs.
///
- private int _ballCount = 0;
+ ///
+ /// Position, where 0 is the switch of the ball being ejected next.
+ /// Switch in the ball stack
+ public DeviceSwitch StackSwitch(int pos) => _stackSwitches[pos];
///
- /// Eject coil triggers the kicker that throws out the ball.
+ /// The stack of a trough can hold an unlimited number of balls. This counts the number of balls in the stack
+ /// *additionally* to those counted by the stack's switches.
///
- private IApiCoil _ejectCoil;
+ ///
+ ///
+ /// Usually, games only have as many balls as stack switches and this value should always be zero.
+ ///
+ public int UncountedStackBalls { get; private set; }
///
- /// Since TriggerApi implements IApiSwitch, we return directly this when the
- /// engine asks for the jam switch.
+ /// The number of balls waiting to be drained because the drain slot is occupied.
+ ///
+ /// Once the drain slot is freed, i.e. the ball is kicked over into the ball stack, the next undrained ball
+ /// enters the drain and this number is decremented by one.
///
///
///
- /// Basically we short-wire the jam trigger to the switch, so the jam trigger
- /// events go directly to the gamelogic engine.
+ /// Usually, the gamelogic engine immediately frees the drain slot and this value should always be zero.
+ ///
+ public int UncountedDrainBalls { get; private set; }
+
+ ///
+ /// A reference to the drain switch on the playfield needed to destroy the ball and update the state of the
+ /// .
+ ///
+ private IApiSwitch _drainSwitch;
+
+ ///
+ /// A reference to the exit kicker on the playfield needed to create and kick new balls into the plunger lane.
+ ///
+ private KickerApi _ejectKicker;
+
+ ///
+ /// The stack switches. These are virtual switches that don't exist on the playfield, but changing their values
+ /// on them sends the event to the gamelogic engine.
+ ///
+ ///
+ ///
+ /// Note that for entry-coil troughs, the entry switch isn't part of this array.
+ ///
+ private DeviceSwitch[] _stackSwitches;
+
+ ///
+ /// Entry coil shoots the ball from the drain into the trough.
+ ///
///
- /// In case we need to hook into the jam trigger logic here, uncomment the
- /// blocks below (ctrl+f jam events)
+ ///
+ /// Is null for
///
- private TriggerApi _jamTrigger;
+ private DeviceCoil _entryCoil;
+
+ ///
+ /// Triggers the kicker that ejects the ball.
+ ///
+ private DeviceCoil _exitCoil;
+
+ ///
+ /// Number of virtual balls on switches in the ball stack.
+ ///
+ ///
+ ///
+ /// This does not include balls sitting in drain before being pushed into the trough.
+ ///
+ private int _countedStackBalls;
///
/// The player will ask for switches to hook up to the gamelogic engine,
@@ -73,8 +147,7 @@ public class TroughApi : ItemApi, IApi, IApiInitializable, I
///
private readonly Dictionary _switchLookup = new Dictionary();
- private DeviceSwitch EntrySwitch => _ballSwitches[Data.SwitchCount - 1];
- private DeviceSwitch EjectSwitch => _ballSwitches[0];
+ private bool _isSetup;
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
@@ -88,180 +161,427 @@ internal TroughApi(Trough item, Player player) : base(item, player)
Debug.Log("Trough API instantiated.");
}
- ///
- /// This is called when the player starts. It tells the trough
- /// "please give me switch XXX so I can hook it up to the gamelogic engine".
- ///
- ///
- ///
- IApiSwitch IApiSwitchDevice.Switch(string switchId)
+ void IApiInitializable.OnInit(BallManager ballManager)
{
- // if the engine is asking for the jam switch, return the trigger directly.
- if (switchId == Trough.JamSwitchId) {
- return _jamTrigger;
+ base.OnInit(ballManager);
+
+ // reference playfield elements
+ _drainSwitch = TableApi.Switch(Data.PlayfieldEntrySwitch);
+ _ejectKicker = TableApi.Kicker(Data.PlayfieldExitKicker);
+ _isSetup = _drainSwitch != null && _ejectKicker != null;
+
+ // setup entry handler
+ if (_drainSwitch != null) {
+ _drainSwitch.Switch += OnEntry;
}
- return _switchLookup.ContainsKey(switchId) ? _switchLookup[switchId] : null;
+
+ // setup switches
+ if (Data.Type != TroughType.Modern) {
+ EntrySwitch = CreateSwitch(Trough.EntrySwitchId, false);
+ _switchLookup[Trough.EntrySwitchId] = EntrySwitch;
+ }
+
+ if (Data.Type == TroughType.TwoCoilsOneSwitch) {
+ _stackSwitches = new[] {
+ CreateSwitch(Trough.TroughSwitchId, false)
+ };
+ _switchLookup[Trough.TroughSwitchId] = StackSwitch();
+
+ } else {
+ _stackSwitches = new DeviceSwitch[Data.SwitchCount];
+ foreach (var sw in Item.AvailableSwitches) {
+ if (int.TryParse(sw.Id, out var id)) {
+ _stackSwitches[id - 1] = CreateSwitch(sw.Id, false);
+ _switchLookup[sw.Id] = _stackSwitches[id - 1];
+
+ } else {
+ Logger.Warn($"Unknown switch ID {sw.Id}");
+ }
+ }
+
+ // pull next ball on modern
+ if (Data.Type == TroughType.Modern) {
+ _stackSwitches[Data.SwitchCount - 1].Switch += OnLastStackSwitch;
+ }
+ }
+
+ // setup coils
+ _entryCoil = new DeviceCoil(OnEntryCoilEnabled);
+ _exitCoil = new DeviceCoil(() => EjectBall());
+
+ // fill up the ball stack
+ for (var i = 0; i < Data.BallCount; i++) {
+ AddBall();
+ }
+
+ // finally, emit the event for anyone else to chew on
+ Init?.Invoke(this, EventArgs.Empty);
}
///
- /// Returns a coil by ID. Same principle as
+ /// Create a ball in the ball stack without triggering extra events.
///
- ///
- ///
- IApiCoil IApiCoilDevice.Coil(string coilId)
+ private void AddBall()
{
- if (coilId == Trough.EjectCoilId) {
- return _ejectCoil;
+ switch (Data.Type) {
+ case TroughType.Modern:
+ case TroughType.TwoCoilsNSwitches:
+ if (_countedStackBalls < Data.BallCount) {
+ _stackSwitches[_countedStackBalls].SetSwitch(true);
+ _countedStackBalls++;
+ } else {
+ UncountedStackBalls++;
+ }
+ break;
+
+ case TroughType.TwoCoilsOneSwitch:
+ if (_countedStackBalls < Data.SwitchCount - 1) {
+ _countedStackBalls++;
+
+ } else if (_countedStackBalls == Data.SwitchCount - 1) {
+ _countedStackBalls++;
+ StackSwitch().SetSwitch(true);
+
+ } else {
+ UncountedStackBalls++;
+ }
+ break;
+
+ case TroughType.ClassicSingleBall:
+ if (!EntrySwitch.IsClosed) {
+ EntrySwitch.SetSwitch(true);
+ }
+ break;
+
+ default:
+ throw new ArgumentOutOfRangeException();
}
- return null;
}
- IApiWireDest IApiWireDeviceDest.Wire(string coilId) => (this as IApiCoilDevice).Coil(coilId);
-
///
- /// Create a ball in the trough without triggering extra events
+ /// Destroys the ball and simulates a drain.
///
- internal void AddBall()
+ private void OnEntry(object sender, SwitchEventArgs args)
{
- if (_ballCount < Data.BallCount) {
- _ballSwitches[_ballCount].SetSwitch(true);
-
- _ballCount++;
- }
+ Logger.Info("Draining ball into trough.");
+ _drainSwitch.DestroyBall(args.BallEntity);
+ DrainBall();
}
///
- /// If there are any balls in the trough add one to play and
- /// trigger any switches which the remaining balls would activate
+ /// Simulates a drain, i.e. a ball entering the drain or trough directly,
+ /// depending on the .
///
- internal void OnEjectCoil(bool closed)
+ private void DrainBall()
{
- if (closed && (_ballCount > 0)) {
- Logger.Info("Spawning new ball.");
+ switch (Data.Type) {
+ case TroughType.Modern:
+ // ball rolls directly into the trough
+ RollOverEntryBall(0);
+ break;
- _exitKicker.CreateBall();
- _exitKicker.Kick();
+ case TroughType.TwoCoilsNSwitches:
+ case TroughType.TwoCoilsOneSwitch:
+ case TroughType.ClassicSingleBall:
- for (int i = 0; i < _ballCount; i++) {
- _ballSwitches[i].ScheduleSwitch(false, Data.SettleTime / 2);
- }
+ if (EntrySwitch.IsClosed) { // if the drain slot is already occupied, queue it.
+ UncountedDrainBalls++;
- _ballCount--;
+ } else { // otherwise just close the entry switch
+ EntrySwitch.ScheduleSwitch(true, Data.RollTime / 2);
+ }
- for (int i = 0; i < _ballCount; i++) {
- _ballSwitches[i].ScheduleSwitch(true, Data.SettleTime);
- }
+ break;
+
+ default:
+ throw new ArgumentOutOfRangeException();
}
+
+ RefreshUI();
}
///
- /// If there's room in the trough remove the ball from play
- /// and trigger any switches which it would roll over
+ /// Kicks the ball from the drain into the ball stack.
///
- private void OnEntryKickerHit(object sender, EventArgs args)
+ ///
+ ///
+ /// If there are any uncounted drain balls, the next ball is drained afterwards.
+ ///
+ private void OnEntryCoilEnabled()
{
- if (_ballCount < Data.BallCount) {
- Logger.Info("Draining ball.");
-
- (sender as KickerApi)?.DestroyBall();
-
- int openSwitches = Data.BallCount - _ballCount;
+ switch (Data.Type) {
+ case TroughType.Modern:
+ // modern troughs don't have an entry coil
+ break;
+
+ case TroughType.TwoCoilsNSwitches:
+ case TroughType.TwoCoilsOneSwitch:
+ // push the ball from the drain to the trough
+ if (EntrySwitch.IsClosed) {
+ EntrySwitch.SetSwitch(false);
+ RollOverEntryBall(0);
+ DrainNextUncountedBall();
+ }
+ break;
+
+ case TroughType.ClassicSingleBall:
+ // balls get ejected immediately
+ if (EntrySwitch.IsClosed) {
+ EntrySwitch.SetSwitch(false);
+ EjectBall();
+ DrainNextUncountedBall();
+ }
+ break;
+
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ RefreshUI();
+ }
- for (int i = 1; i < openSwitches; i++) {
- _ballSwitches[Data.BallCount - i].ScheduleSwitch(true, Data.SettleTime * i);
- _ballSwitches[Data.BallCount - i].ScheduleSwitch(false, Data.SettleTime * i + Data.SettleTime / 2);
- }
+ private void DrainNextUncountedBall()
+ {
+ if (UncountedDrainBalls > 0) {
+ DrainBall();
+ UncountedDrainBalls--;
+ }
+ }
- _ballSwitches[_ballCount].ScheduleSwitch(true, Data.SettleTime * openSwitches);
+ ///
+ /// Simulates rolling a new ball into the ball stack by enabling / disabling the switches it might hits.
+ ///
+ private void RollOverEntryBall(int t)
+ {
+ // if more balls than switches, just count and exit
+ if (_countedStackBalls >= Data.SwitchCount) {
+ UncountedStackBalls++;
+ return;
+ }
- _ballCount++;
+ // pos 0 is the eject position, ball enters at the opposite end
+ var pos = Data.SwitchCount - 1;
+ var openSwitches = Data.SwitchCount - _countedStackBalls;
+
+ switch (Data.Type) {
+ case TroughType.Modern:
+ case TroughType.TwoCoilsNSwitches:
+ // if entry position is occupied by another ball that just went in, queue.
+ if (_stackSwitches[pos].IsClosed) {
+ UncountedStackBalls++;
+ return;
+ }
+ // these are switches where the balls rolls over, so close and re-open them.
+ for (var i = 0; i < openSwitches - 1; i++) {
+ _stackSwitches[pos].ScheduleSwitch(true, t);
+
+ t += Data.RollTime / 2;
+ _stackSwitches[pos].ScheduleSwitch(false, t);
+ t += Data.RollTime / 2;
+ pos--;
+ }
+ // switch nearest to the eject comes last, but doesn't re-open.
+ _stackSwitches[pos].ScheduleSwitch(true, t);
+ _countedStackBalls++;
+ break;
+
+ case TroughType.TwoCoilsOneSwitch:
+ if (_countedStackBalls < Data.SwitchCount - 1) {
+ StackSwitch().ScheduleSwitch(true, t);
+ t += Data.RollTime / 2;
+ StackSwitch().ScheduleSwitch(false, t);
+
+ } else if (_countedStackBalls == Data.SwitchCount - 1) {
+ StackSwitch().SetSwitch(true);
+ }
+ _countedStackBalls++;
+ break;
+
+ case TroughType.ClassicSingleBall:
+ // nothing going on here on stack side
+ break;
+
+ default:
+ throw new ArgumentOutOfRangeException();
}
}
- #region Wiring
-
- void IApiInitializable.OnInit(BallManager ballManager)
+ ///
+ /// If there are any balls in the ball stack, eject one to the playfield.
+ ///
+ ///
+ ///
+ /// This triggers any switches which the remaining balls would activate by rolling to the next position.
+ ///
+ ///
+ /// True if a ball was ejected, false if there were no balls in the stack to eject.
+ public bool EjectBall()
{
- // playfield elements
- _entryKicker = TableApi.Kicker(Data.EntryKicker);
- _exitKicker = TableApi.Kicker(Data.ExitKicker);
- _jamTrigger = TableApi.Trigger(Data.JamSwitch);
-
- // setup entry kicker handler
- if (_entryKicker != null) {
- _entryKicker.Hit += OnEntryKickerHit;
+ if (!_isSetup) {
+ Logger.Warn($"Trough {Data.Name} not set up, ignoring.");
+ return false;
}
+ if (_countedStackBalls > 0) {
+ Logger.Info("Spawning new ball.");
- // in case we need also need to handle jam events here, uncomment
- // if (_jamTrigger != null) {
- // _jamTrigger.Hit += OnJamTriggerHit;
- // _jamTrigger.UnHit += OnJamTriggerUnHit;
- // }
-
- // create switches to hook up
- _ballSwitches = new DeviceSwitch[Data.SwitchCount];
- foreach (var sw in Item.AvailableSwitches) {
- if (int.TryParse(sw.Id, out var id)) {
- _ballSwitches[id - 1] = CreateSwitch(false);
- _switchLookup[sw.Id] = _ballSwitches[id - 1];
-
- } else if (sw.Id == Trough.JamSwitchId) {
- // we short-wire the jam trigger to the switch, so we don't care about it here,
- // all the jam trigger does push its switch events to the gamelogic engine. in
- // case we need to hook into the jam trigger logic here, uncomment those two
- // lines and and relay the events manually to the engine.
- //_jamSwitch = CreateSwitch(false);
- //_switchLookup[sw.Id] = _jamSwitch;
-
- } else {
- Logger.Warn($"Unknown switch ID {sw.Id}");
+ _ejectKicker.CreateBall();
+ _ejectKicker.Kick();
+
+ // open the switch of the ejected ball immediately
+ switch (Data.Type) {
+ case TroughType.Modern:
+ case TroughType.TwoCoilsNSwitches:
+ _stackSwitches[0].SetSwitch(false);
+ break;
+ case TroughType.TwoCoilsOneSwitch:
+ case TroughType.ClassicSingleBall:
+ // no switches at position 0 here.
+ break;
+
+ default:
+ throw new ArgumentOutOfRangeException();
}
+ RollOverStackBalls();
+ RollOverNextUncountedStackBall();
+ RefreshUI();
+ return true;
}
- // setup eject coil
- _ejectCoil = new TroughEjectCoil(this);
-
- // finally, emit the event for anyone else to chew on
- Init?.Invoke(this, EventArgs.Empty);
+ return false;
}
- void IApi.OnDestroy()
+ ///
+ /// Simulates all balls in the ball stack moving at once to the next position,
+ /// due to an ejected ball.
+ ///
+ private void RollOverStackBalls()
{
- Logger.Info("Destroying trough!");
+ var pos = _countedStackBalls - 1;
+ switch (Data.Type) {
+ case TroughType.Modern:
+ case TroughType.TwoCoilsNSwitches:
+
+ // don't re-close the switch nearest to the entry
+ _stackSwitches[pos].ScheduleSwitch(false, Data.RollTime / 2);
+
+ // move remaining but last ball (which has been ejected) one position further,
+ // all at the same time
+ for (var i = 0; i < _countedStackBalls - 2; i++) {
+ pos--;
+ _stackSwitches[pos].ScheduleSwitch(true, Data.RollTime / 2);
+ _stackSwitches[pos].ScheduleSwitch(false, Data.RollTime);
+ }
+
+ // just close the switch for the last ball, since it has already been opened.
+ if (pos-- > 0) {
+ _stackSwitches[pos].ScheduleSwitch(true, Data.RollTime / 2);
+ }
+ break;
+
+ case TroughType.TwoCoilsOneSwitch:
+ // there is only one switch in the trough, so if it's closed, open it.
+ if (StackSwitch().IsClosed) {
+ StackSwitch().ScheduleSwitch(false, Data.RollTime / 2);
+ }
+ break;
+
+ case TroughType.ClassicSingleBall:
+ // no switches in this trough at all.
+ break;
+
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ _countedStackBalls--;
+ }
- if (_entryKicker != null) {
- _entryKicker.Hit -= OnEntryKickerHit;
+ private void OnLastStackSwitch(object sender, SwitchEventArgs switchEventArgs)
+ {
+ if (!switchEventArgs.IsClosed && UncountedStackBalls > 0) {
+ RefreshUI();
+ UncountedStackBalls--;
+ RollOverEntryBall(Data.RollTime / 2);
+ RefreshUI();
}
+ }
- // in case we need also need to handle jam events here, uncomment
- // if (_jamTrigger != null) {
- // _jamTrigger.Hit -= OnJamTriggerHit;
- // _jamTrigger.UnHit -= OnJamTriggerUnHit;
- // }
+ private void RollOverNextUncountedStackBall()
+ {
+ if (UncountedStackBalls == 0) {
+ return;
+ }
+ _countedStackBalls++;
+ switch (Data.Type) {
+ case TroughType.Modern:
+ case TroughType.TwoCoilsNSwitches:
+ _stackSwitches[_countedStackBalls - 1].ScheduleSwitch(true, Data.RollTime);
+ break;
+
+ case TroughType.TwoCoilsOneSwitch:
+ StackSwitch().ScheduleSwitch(true, Data.RollTime / 2);
+ break;
+
+ case TroughType.ClassicSingleBall:
+ // no stack here
+ break;
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ UncountedStackBalls--;
}
- // in case we need also need to handle jam events here, uncomment
- // private IApiSwitch _jamSwitch;
- // private void OnJamTriggerHit(object sender, EventArgs e) => _jamSwitch?.OnSwitch(true);
- // private void OnJamTriggerUnHit(object sender, EventArgs e) => _jamSwitch?.OnSwitch(false);
+ private static void RefreshUI()
+ {
+#if UNITY_EDITOR
+ UnityEditorInternal.InternalEditorUtility.RepaintAllViews();
+#endif
+ }
- #endregion
- }
+ #region Wiring
- internal class TroughEjectCoil : IApiCoil
- {
- private readonly TroughApi _troughApi;
+ ///
+ /// This is called when the player starts. It tells the trough
+ /// "please give me switch XXX so I can hook it up to the gamelogic engine".
+ ///
+ ///
+ ///
+ IApiSwitch IApiSwitchDevice.Switch(string switchId)
+ {
+ return _switchLookup.ContainsKey(switchId) ? _switchLookup[switchId] : null;
+ }
- public TroughEjectCoil(TroughApi troughApi)
+ ///
+ /// Returns a coil by ID. Same principle as
+ ///
+ ///
+ ///
+ IApiCoil IApiCoilDevice.Coil(string coilId)
{
- _troughApi = troughApi;
+ switch (coilId) {
+ case Trough.EntryCoilId:
+ return _entryCoil;
+
+ case Trough.EjectCoilId:
+ return _exitCoil;
+
+ default:
+ return null;
+ }
}
- public void OnCoil(bool closed, bool isHoldCoil)
+ IApiWireDest IApiWireDeviceDest.Wire(string coilId) => (this as IApiCoilDevice).Coil(coilId);
+
+ void IApi.OnDestroy()
{
- _troughApi.OnEjectCoil(closed);
+ Logger.Info("Destroying trough!");
+
+ if (_drainSwitch != null) {
+ _drainSwitch.Switch -= OnEntry;
+ }
+ if (Data.Type == TroughType.Modern) {
+ _stackSwitches[Data.SwitchCount - 1].Switch -= OnLastStackSwitch;
+ }
}
- public void OnChange(bool enabled) => OnCoil(enabled, false);
+ #endregion
}
}
diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trough/TroughAuthoring.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trough/TroughAuthoring.cs
index 70ec006d0..143d826fd 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trough/TroughAuthoring.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trough/TroughAuthoring.cs
@@ -27,6 +27,8 @@
using UnityEngine;
using UnityEngine.Networking.PlayerConnection;
using VisualPinball.Engine.Game.Engines;
+using VisualPinball.Engine.VPT.Kicker;
+using VisualPinball.Engine.VPT.Trigger;
using VisualPinball.Engine.VPT.Trough;
namespace VisualPinball.Unity
@@ -40,15 +42,24 @@ public class TroughAuthoring : ItemMainAuthoring, ISwitchDev
protected override Trough InstantiateItem(TroughData data) => new Trough(data);
public override IEnumerable ValidParents { get; } = new Type[0];
- private Vector3 EntryPickerPos(float height) => string.IsNullOrEmpty(Data.EntryKicker)
- ? Vector3.zero
- : Table.Kicker(Data.EntryKicker).Data.Center.ToUnityVector3(height);
+ private Vector3 EntryPos(float height)
+ {
+ if (string.IsNullOrEmpty(Data.PlayfieldEntrySwitch)) {
+ return Vector3.zero;
+ }
+ if (Table.Has(Data.PlayfieldEntrySwitch)) {
+ return Table.Trigger(Data.PlayfieldEntrySwitch).Data.Center.ToUnityVector3(height);
+ }
+ return Table.Has(Data.PlayfieldEntrySwitch)
+ ? Table.Kicker(Data.PlayfieldEntrySwitch).Data.Center.ToUnityVector3(height)
+ : Vector3.zero;
+ }
- private Vector3 ExitKickerPos(float height) => string.IsNullOrEmpty(Data.ExitKicker)
+ private Vector3 ExitPos(float height) => string.IsNullOrEmpty(Data.PlayfieldExitKicker)
? Vector3.zero
- : Table.Kicker(Data.ExitKicker).Data.Center.ToUnityVector3(height);
+ : Table.Kicker(Data.PlayfieldExitKicker).Data.Center.ToUnityVector3(height);
- private void Start()
+ private void Awake()
{
GetComponentInParent().RegisterTrough(Item, gameObject);
}
@@ -60,10 +71,10 @@ public override void Restore()
private void OnDrawGizmosSelected()
{
- if (!string.IsNullOrEmpty(Data.EntryKicker) && !string.IsNullOrEmpty(Data.ExitKicker)) {
+ if (!string.IsNullOrEmpty(Data.PlayfieldEntrySwitch) && !string.IsNullOrEmpty(Data.PlayfieldExitKicker)) {
var ltw = GetComponentInParent().transform;
- var entryPos = EntryPickerPos(0f);
- var exitPos = ExitKickerPos(0f);
+ var entryPos = EntryPos(0f);
+ var exitPos = ExitPos(0f);
var entryWorldPos = ltw.TransformPoint(entryPos);
var exitWorldPos = ltw.TransformPoint(exitPos);
var localPos = transform.localPosition;
@@ -77,7 +88,7 @@ private void OnDrawGizmosSelected()
public void UpdatePosition()
{
// place trough between entry and exit kicker
- var pos = (EntryPickerPos(75f) + ExitKickerPos(75f)) / 2;
+ var pos = (EntryPos(75f) + ExitPos(75f)) / 2;
transform.localPosition = pos;
}
diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trough/TroughExtensions.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trough/TroughExtensions.cs
index 98aceafbc..6bcb488d5 100644
--- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trough/TroughExtensions.cs
+++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trough/TroughExtensions.cs
@@ -27,7 +27,7 @@ public static ConvertedItem SetupGameObject(this Engine.VPT.Trough.Trough trough
var mainAuthoring = obj.AddComponent();
mainAuthoring.SetItem(trough);
mainAuthoring.UpdatePosition();
- obj.GetComponentInParent().RegisterTrough(trough, obj);
+ //obj.GetComponentInParent()?.RegisterTrough(trough, obj);
return new ConvertedItem(mainAuthoring);
}
}