Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
0fa9672
physics: Pass ball entity to hit event.
freezy Nov 14, 2020
fe9160c
trough: Fix creation via toolbox.
freezy Nov 14, 2020
4fc1b8e
trough: Fix import from exported table.
freezy Nov 14, 2020
66900f8
Allow kicker OR trigger item for trough entry. Sort of a hack, but pl…
ecurtz Nov 17, 2020
38741a5
editor: Add object selector that works with interfaces.
freezy Nov 20, 2020
65f7f2b
editor: Add ping to interface selector.
freezy Nov 20, 2020
57222e3
editor: Add "None" value to searchable dropdown.
freezy Nov 21, 2020
25491a1
trough: Use IApiSwitch for entry switch.
freezy Nov 21, 2020
3d0adb6
trough: Display ball switch status during gameplay in inspector.
freezy Nov 21, 2020
c7b095c
trough: Add types.
freezy Nov 22, 2020
42b3c78
trough: Properly update status in inspector.
freezy Nov 22, 2020
d7c599d
trough: Implement all known trough behavior types.
freezy Nov 22, 2020
d4ed195
trough: Use switch icons in inspector.
freezy Nov 23, 2020
882d7fd
trough(doc): Update screenshot.
freezy Nov 23, 2020
af2ae50
trough: Update documentation.
freezy Nov 23, 2020
8d8eadc
trough: Fix tests.
freezy Nov 23, 2020
4637408
trough: Fix more tests.
freezy Nov 23, 2020
1e1cce4
trough: Add test coverage for switch and coil generation.
freezy Nov 23, 2020
a1150a1
trough: Minor refactoring and comments.
freezy Nov 23, 2020
6ae9163
Trough documentation pass.
ecurtz Nov 24, 2020
610bf81
unity: Make asset importer compatible with 2020.1.
freezy Nov 24, 2020
3661fe9
trough: Fix device switches losing link to engine.
freezy Nov 24, 2020
4f4f47e
trough: Fix some of the timings.
freezy Nov 24, 2020
d7ac990
doc: Don't show System.Object inheritances.
freezy Nov 24, 2020
72567a0
trough: Fix timing when two balls enter more rapidly than roll time.
freezy Nov 25, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified VisualPinball.Engine.Test/Fixtures~/TroughTest.vpx
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
8 changes: 4 additions & 4 deletions VisualPinball.Engine.Test/VPT/Trough/TroughDataTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
}
}
142 changes: 142 additions & 0 deletions VisualPinball.Engine.Test/VPT/Trough/TroughTests.cs
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/>.

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);
}
}
}
8 changes: 8 additions & 0 deletions VisualPinball.Engine/VPT/Enums.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
6 changes: 6 additions & 0 deletions VisualPinball.Engine/VPT/Table/Table.cs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,12 @@ private IEnumerable<IHittable> ApplyColliderOverrides(IHittable hittable)
.Concat(_surfaces.Values)
.Concat(_triggers.Values);

/// <summary>
/// Game items that need to be converted but aren't rendered.
/// </summary>
public IEnumerable<IItem> NonRenderables => new IItem[0]
.Concat(_troughs.Values);

public IEnumerable<IItem> GameItems => new IItem[] { }
.Concat(_bumpers.Values)
.Concat(_decals.Select(i => i))
Expand Down
70 changes: 60 additions & 10 deletions VisualPinball.Engine/VPT/Trough/Trough.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
Expand All @@ -27,18 +28,67 @@ public class Trough : Item<TroughData>, 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<GamelogicEngineSwitch> 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<GamelogicEngineSwitch> 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<GamelogicEngineCoil> 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<GamelogicEngineCoil> 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)
{
Expand Down
19 changes: 11 additions & 8 deletions VisualPinball.Engine/VPT/Trough/TroughData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,23 +38,26 @@ 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;

[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)
{
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -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

<img src="trough-inspector.png" width="418" class="img-responsive pull-right" style="margin-left: 15px">
<img src="trough-inspector.png" width="343" class="img-responsive pull-right" style="margin-left: 15px">

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)

<img src="trough-modern.png" width="343" class="img-responsive pull-right" style="margin-left: 15px">

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

<img src="trough-2cns.png" width="343" class="img-responsive pull-right" style="margin-left: 15px">

[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

<img src="trough-2c1s.png" width="343" class="img-responsive pull-right" style="margin-left: 15px">

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

<img src="trough-single-ball.png" width="343" class="img-responsive pull-right" style="margin-left: 15px">

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:

![Switch Manager](trough-switches.png)

## 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:

![Coil Manager](trough-coils.png)
Loading