diff --git a/VisualPinball.Engine.PinMAME.Unity/Editor/PinMameGamelogicEngineInspector.cs b/VisualPinball.Engine.PinMAME.Unity/Editor/PinMameGamelogicEngineInspector.cs index b7a517d..3d455b8 100644 --- a/VisualPinball.Engine.PinMAME.Unity/Editor/PinMameGamelogicEngineInspector.cs +++ b/VisualPinball.Engine.PinMAME.Unity/Editor/PinMameGamelogicEngineInspector.cs @@ -61,13 +61,15 @@ private void OnEnable() _games = new PinMameGame[] { new AttackFromMars(), new CreatureFromTheBlackLagoon(), - new MedievalMadness(), - new Terminator2(), new FlashGordon(), - new StarTrekEnterprise(), + new MedievalMadness(), new Rock(), new RockEncore(), + new StarTrekEnterprise(), + new Terminator2(), + new TheWalkingDead(), }; + _gameNames = new[] {"-- none --"} .Concat(_games.Select(g => g.Name)) .ToArray(); diff --git a/VisualPinball.Engine.PinMAME.Unity/Runtime/PinMameGamelogicEngine.cs b/VisualPinball.Engine.PinMAME.Unity/Runtime/PinMameGamelogicEngine.cs index bb62f1c..2631b50 100644 --- a/VisualPinball.Engine.PinMAME.Unity/Runtime/PinMameGamelogicEngine.cs +++ b/VisualPinball.Engine.PinMAME.Unity/Runtime/PinMameGamelogicEngine.cs @@ -20,13 +20,11 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Runtime.InteropServices; using NLog; using PinMame; using UnityEngine; -using UnityEngine.Networking; using VisualPinball.Engine.Game.Engines; using VisualPinball.Unity; using Logger = NLog.Logger; @@ -46,6 +44,8 @@ public class PinMameGamelogicEngine : MonoBehaviour, IGamelogicEngine public PinMameGame Game { get => _game; set => _game = value; } + #region Configuration + [HideInInspector] public string romId = string.Empty; @@ -54,24 +54,28 @@ public class PinMameGamelogicEngine : MonoBehaviour, IGamelogicEngine [Min(0f)] [Tooltip("Delay after startup to listen for solenoid events.")] - public float SolenoidDelay = 0; + public float SolenoidDelay; [Tooltip("Disable audio")] public bool DisableAudio; - public GamelogicEngineSwitch[] AvailableSwitches { + #endregion + + #region IGamelogicEngine + + public GamelogicEngineSwitch[] RequestedSwitches { get { UpdateCaches(); return _game?.AvailableSwitches ?? Array.Empty(); } } - public GamelogicEngineCoil[] AvailableCoils { + public GamelogicEngineCoil[] RequestedCoils { get { UpdateCaches(); return _coils.Values.ToArray(); } } - public GamelogicEngineLamp[] AvailableLamps { + public GamelogicEngineLamp[] RequestedLamps { get { UpdateCaches(); return _lamps.Values.ToArray(); @@ -84,29 +88,32 @@ public GamelogicEngineLamp[] AvailableLamps { public event EventHandler OnSwitchChanged; public event EventHandler OnLampChanged; public event EventHandler OnLampsChanged; - public event EventHandler OnLampColorChanged; - public event EventHandler OnDisplaysAvailable; + public event EventHandler OnDisplaysRequested; public event EventHandler OnDisplayFrame; public event EventHandler OnStarted; + #endregion + + #region Internals + [NonSerialized] private Player _player; [NonSerialized] private PinMame.PinMame _pinMame; [SerializeReference] private PinMameGame _game; - private Dictionary _switches = new Dictionary(); - private Dictionary _coils = new Dictionary(); - private Dictionary _lamps = new Dictionary(); + private Dictionary _switches = new(); + private Dictionary _coils = new(); + private Dictionary _lamps = new(); private bool _isRunning; private int _numMechs; - private Dictionary _frameBuffer = new Dictionary(); - private Dictionary> _dmdLevels = new Dictionary>(); + private Dictionary _frameBuffer = new(); + private Dictionary> _dmdLevels = new(); private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); - private static readonly Color Tint = new Color(1, 0.18f, 0); + private static readonly Color Tint = new(1, 0.18f, 0); - private readonly Queue _dispatchQueue = new Queue(); - private readonly Queue _audioQueue = new Queue(); + private readonly Queue _dispatchQueue = new(); + private readonly Queue _audioQueue = new(); private int _audioFilterChannels; private PinMameAudioInfo _audioInfo; @@ -124,6 +131,10 @@ public GamelogicEngineLamp[] AvailableLamps { private Dictionary _registeredMechs = new(); private HashSet _mechSwitches = new(); + #endregion + + #region Lifecycle + private void Awake() { Logger.Info("Project audio sample rate: " + AudioSettings.outputSampleRate); @@ -137,6 +148,61 @@ private void Start() _lastAudioFrameOffset = 0; } + private void Update() + { + if (_pinMame == null || !_isRunning) { + return; + } + + lock (_dispatchQueue) { + while (_dispatchQueue.Count > 0) { + _dispatchQueue.Dequeue().Invoke(); + } + } + + // lamps + foreach (var changedLamp in _pinMame.GetChangedLamps()) { + if (_lamps.ContainsKey(changedLamp.Id)) { + //Logger.Info($"[PinMAME] <= lamp {changedLamp.Id}: {changedLamp.Value}"); + OnLampChanged?.Invoke(this, new LampEventArgs(_lamps[changedLamp.Id].Id, changedLamp.Value)); + } + } + + // gi + foreach (var changedGi in _pinMame.GetChangedGIs()) { + if (_lamps.ContainsKey(changedGi.Id)) { + //Logger.Info($"[PinMAME] <= gi {changedGi.Id}: {changedGi.Value}"); + OnLampChanged?.Invoke(this, new LampEventArgs(_lamps[changedGi.Id].Id, changedGi.Value, LampSource.GI)); + } else { + Debug.Log($"No GI {changedGi.Id} found."); + } + } + } + + private void OnDestroy() + { + if (_pinMame != null) { + _pinMame.StopGame(); + _pinMame.OnGameStarted -= OnGameStarted; + _pinMame.OnGameEnded -= OnGameEnded; + _pinMame.OnDisplayAvailable -= OnDisplayRequested; + _pinMame.OnDisplayUpdated -= OnDisplayUpdated; + + if (!DisableAudio) + { + _pinMame.OnAudioAvailable -= OnAudioAvailable; + _pinMame.OnAudioUpdated -= OnAudioUpdated; + } + + _pinMame.OnMechAvailable -= OnMechAvailable; + _pinMame.OnMechUpdated -= OnMechUpdated; + _pinMame.OnSolenoidUpdated -= OnSolenoidUpdated; + + } + _frameBuffer.Clear(); + _dmdLevels.Clear(); + } + public void OnInit(Player player, TableApi tableApi, BallManager ballManager) { // turn off all lamps @@ -170,6 +236,7 @@ public void OnInit(Player player, TableApi tableApi, BallManager ballManager) Logger.Info($"New PinMAME instance at {(double)AudioSettings.outputSampleRate / 1000}kHz"); + // ReSharper disable once ExpressionIsAlwaysNull _pinMame = PinMame.PinMame.Instance(PinMameAudioFormat.AudioFormatFloat, AudioSettings.outputSampleRate, vpmPath); _pinMame.SetHandleKeyboard(false); @@ -177,7 +244,7 @@ public void OnInit(Player player, TableApi tableApi, BallManager ballManager) _pinMame.OnGameStarted += OnGameStarted; _pinMame.OnGameEnded += OnGameEnded; - _pinMame.OnDisplayAvailable += OnDisplayAvailable; + _pinMame.OnDisplayAvailable += OnDisplayRequested; _pinMame.OnDisplayUpdated += OnDisplayUpdated; if (!DisableAudio) { @@ -200,12 +267,6 @@ public void OnInit(Player player, TableApi tableApi, BallManager ballManager) } } - public void RegisterMech(PinMameMechComponent mechComponent) - { - var id = _numMechs++; - _registeredMechs[id] = mechComponent; - } - private void OnGameStarted() { Logger.Info($"[PinMAME] Game started."); @@ -221,43 +282,41 @@ private void OnGameStarted() } } - private void Update() + private void OnGameEnded() { - if (_pinMame == null || !_isRunning) { + Logger.Info($"[PinMAME] Game ended."); + _isRunning = false; + } + + private void UpdateCaches() + { + if (_game == null) { return; } - - lock (_dispatchQueue) { - while (_dispatchQueue.Count > 0) { - _dispatchQueue.Dequeue().Invoke(); - } + _lamps.Clear(); + _coils.Clear(); + _switches.Clear(); + foreach (var lamp in _game.AvailableLamps) { + _lamps[lamp.InternalId] = lamp; } - - // lamps - foreach (var changedLamp in _pinMame.GetChangedLamps()) { - if (_lamps.ContainsKey(changedLamp.Id)) { - //Logger.Info($"[PinMAME] <= lamp {changedLamp.Id}: {changedLamp.Value}"); - OnLampChanged?.Invoke(this, new LampEventArgs(_lamps[changedLamp.Id].Id, changedLamp.Value)); - } + foreach (var coil in _game.AvailableCoils) { + _coils[coil.InternalId] = coil; } - - // gi - foreach (var changedGi in _pinMame.GetChangedGIs()) { - if (_lamps.ContainsKey(changedGi.Id)) { - //Logger.Info($"[PinMAME] <= gi {changedGi.Id}: {changedGi.Value}"); - OnLampChanged?.Invoke(this, new LampEventArgs(_lamps[changedGi.Id].Id, changedGi.Value, LampSource.GI)); - } else { - Debug.Log($"No GI {changedGi.Id} found."); - } + foreach (var sw in _game.AvailableSwitches) { + _switches[sw.Id] = sw; } } - private void OnDisplayAvailable(int index, int displayCount, PinMameDisplayLayout displayLayout) + #endregion + + #region Displays + + private void OnDisplayRequested(int index, int displayCount, PinMameDisplayLayout displayLayout) { if (displayLayout.IsDmd) { lock (_dispatchQueue) { _dispatchQueue.Enqueue(() => - OnDisplaysAvailable?.Invoke(this, new AvailableDisplays( + OnDisplaysRequested?.Invoke(this, new RequestedDisplays( new DisplayConfig($"{DmdPrefix}{index}", displayLayout.Width, displayLayout.Height)))); } @@ -267,7 +326,7 @@ private void OnDisplayAvailable(int index, int displayCount, PinMameDisplayLayou } else { lock (_dispatchQueue) { _dispatchQueue.Enqueue(() => - OnDisplaysAvailable?.Invoke(this, new AvailableDisplays( + OnDisplaysRequested?.Invoke(this, new RequestedDisplays( new DisplayConfig($"{SegDispPrefix}{index}", displayLayout.Length, 1)))); } @@ -319,7 +378,62 @@ private void UpdateSegDisp(int index, PinMameDisplayLayout displayLayout, IntPtr } } - private int OnAudioAvailable(PinMameAudioInfo audioInfo) + public static DisplayFrameFormat GetDisplayFrameFormat(PinMameDisplayLayout layout) + { + if (layout.IsDmd) { + return layout.Depth == 4 ? DisplayFrameFormat.Dmd4 : DisplayFrameFormat.Dmd2; + } + + switch (layout.Type) { + case PinMameDisplayType.Seg8: // 7 segments and comma + case PinMameDisplayType.Seg7SC: // 7 segments, small, with comma + case PinMameDisplayType.Seg8D: // 7 segments and period + case PinMameDisplayType.Seg7: // 7 segments + case PinMameDisplayType.Seg7S: // 7 segments, small + case PinMameDisplayType.Seg87: // 7 segments, comma every three + case PinMameDisplayType.Seg87F: // 7 segments, forced comma every three + case PinMameDisplayType.Seg10: // 9 segments and comma + case PinMameDisplayType.Seg9: // 9 segments + case PinMameDisplayType.Seg98: // 9 segments, comma every three + case PinMameDisplayType.Seg98F: // 9 segments, forced comma every three + case PinMameDisplayType.Seg16: // 16 segments + case PinMameDisplayType.Seg16R: // 16 segments with comma and period reversed + case PinMameDisplayType.Seg16N: // 16 segments without commas + case PinMameDisplayType.Seg16D: // 16 segments with periods only + case PinMameDisplayType.Seg16S: // 16 segments with split top and bottom line + case PinMameDisplayType.Seg8H: + case PinMameDisplayType.Seg7H: + case PinMameDisplayType.Seg87H: + case PinMameDisplayType.Seg87FH: + case PinMameDisplayType.Seg7SH: + case PinMameDisplayType.Seg7SCH: + case PinMameDisplayType.Seg7 | PinMameDisplayType.NoDisp: + return DisplayFrameFormat.Segment; + + case PinMameDisplayType.Video: + break; + + case PinMameDisplayType.SegAll: + case PinMameDisplayType.Import: + case PinMameDisplayType.SegMask: + case PinMameDisplayType.SegHiBit: + case PinMameDisplayType.SegRev: + case PinMameDisplayType.DmdNoAA: + case PinMameDisplayType.NoDisp: + throw new ArgumentOutOfRangeException(nameof(layout), layout, null); + + default: + throw new ArgumentOutOfRangeException(nameof(layout), layout, null); + } + + throw new NotImplementedException($"Still unsupported segmented display format: {layout}."); + } + + #endregion + + #region Audio + + private int OnAudioAvailable(PinMameAudioInfo audioInfo) { Logger.Info("Game audio available: " + audioInfo); @@ -336,8 +450,8 @@ private int OnAudioUpdated(IntPtr framePtr, int frameSize) _audioNumSamplesInput += frameSize; if (_audioNumSamplesInput > 100000) { - var delta = AudioSettings.dspTime - _audioInputStart; - var queueMs = System.Math.Round(_audioQueue.Count * (double)_audioInfo.SamplesPerFrame / _audioInfo.SampleRate * 1000); + // var delta = AudioSettings.dspTime - _audioInputStart; + // var queueMs = System.Math.Round(_audioQueue.Count * (double)_audioInfo.SamplesPerFrame / _audioInfo.SampleRate * 1000); //Debug.Log($"INPUT: {System.Math.Round(_audioNumSamplesInput / delta)} - {_audioQueue.Count} in queue ({queueMs}ms)"); _audioInputStart = AudioSettings.dspTime; _audioNumSamplesInput = 0; @@ -345,14 +459,13 @@ private int OnAudioUpdated(IntPtr framePtr, int frameSize) float[] frame; if (_audioFilterChannels == _audioInfo.Channels) { // n channels -> n channels - frame = new float[frameSize]; + frame = new float[frameSize * 2]; unsafe { var src = (float*)framePtr; - for (var i = 0; i < frameSize; i++) { + for (var i = 0; i < frameSize * 2; i++) { frame[i] = src[i]; } } - } else if (_audioFilterChannels > _audioInfo.Channels) { // 1 channel -> 2 channels frame = new float[frameSize * 2]; unsafe { @@ -362,7 +475,6 @@ private int OnAudioUpdated(IntPtr framePtr, int frameSize) frame[i * 2 + 1] = frame[i * 2]; } } - } else { // 2 channels -> 1 channel frame = new float[frameSize / 2]; unsafe { @@ -388,7 +500,7 @@ private void OnAudioFilterRead(float[] data, int channels) { _audioNumSamplesOutput += data.Length / channels; if (_audioNumSamplesOutput > 100000) { - var delta = AudioSettings.dspTime - _audioOutputStart; + //var delta = AudioSettings.dspTime - _audioOutputStart; //Debug.Log($"OUTPUT: {System.Math.Round(_audioNumSamplesOutput / delta)}"); _audioOutputStart = AudioSettings.dspTime; _audioNumSamplesOutput = 0; @@ -441,45 +553,40 @@ private void OnAudioFilterRead(float[] data, int channels) } } - private void OnMechAvailable(int mechNo, PinMameMechInfo mechInfo) + #endregion + + #region Coils + + public void SetCoil(string n, bool value) { - Logger.Info($"[PinMAME] <= mech available: mechNo={mechNo}, mechInfo={mechInfo}"); + OnCoilChanged?.Invoke(this, new CoilEventArgs(n, value)); } - - private void OnMechUpdated(int mechNo, PinMameMechInfo mechInfo) + public bool GetCoil(string id) { - if (_registeredMechs.ContainsKey(mechNo)) { - lock (_dispatchQueue) { - _dispatchQueue.Enqueue(() => _registeredMechs[mechNo].UpdateMech(mechInfo)); - } - - } else { - Logger.Info($"[PinMAME] <= mech updated: mechNo={mechNo}, mechInfo={mechInfo}"); - } + return _player != null && _player.CoilStatuses.ContainsKey(id) && _player.CoilStatuses[id]; } private void OnSolenoidUpdated(int internalId, bool isActive) { - if (_coils.ContainsKey(internalId)) - { - if (!_solenoidsEnabled) - { - _solenoidsEnabled = (DateTimeOffset.Now.ToUnixTimeMilliseconds() - _solenoidDelayStart) >= SolenoidDelay; + if (_coils.ContainsKey(internalId)) { - if (_solenoidsEnabled) - { + if (!_solenoidsEnabled) { + _solenoidsEnabled = DateTimeOffset.Now.ToUnixTimeMilliseconds() - _solenoidDelayStart >= SolenoidDelay; + + if (_solenoidsEnabled) { Logger.Info($"Solenoids enabled, {SolenoidDelay}ms passed"); } } - if (_solenoidsEnabled) - { - Logger.Info($"[PinMAME] <= coil {_coils[internalId].Id} ({internalId}): {isActive} | {_coils[internalId].Description}"); + if (_solenoidsEnabled) { + var coil = _coils[internalId]; + var id = coil != null ? coil.Id : internalId.ToString(); + var desc = coil != null ? coil.Description : "-"; + Logger.Info($"[PinMAME] <= coil {id} ({internalId}): {isActive} | {desc}"); - lock (_dispatchQueue) - { - _dispatchQueue.Enqueue(() => - OnCoilChanged?.Invoke(this, new CoilEventArgs(_coils[internalId].Id, isActive))); + lock (_dispatchQueue) { + id = _coils[internalId].Id; + _dispatchQueue.Enqueue(() => OnCoilChanged?.Invoke(this, new CoilEventArgs(id, isActive))); } } else @@ -493,12 +600,24 @@ private void OnSolenoidUpdated(int internalId, bool isActive) } } - private void OnGameEnded() + #endregion + + #region Lamps + + public void SetLamp(string id, float value, bool isCoil = false, LampSource source = LampSource.Lamp) { - Logger.Info($"[PinMAME] Game ended."); - _isRunning = false; + OnLampChanged?.Invoke(this, new LampEventArgs(id, value, isCoil, source)); + } + + public LampState GetLamp(string id) + { + return _player != null && _player.LampStatuses.ContainsKey(id) ? _player.LampStatuses[id] : LampState.Default; } + #endregion + + #region Switches + public void SendInitialSwitches() { var switches = _player.SwitchStatuses; @@ -516,70 +635,6 @@ public void SendInitialSwitches() } } - private void SendMechs() - { - var max = _pinMame.GetMaxMechs(); - foreach (var (id, mech) in _registeredMechs) { - if (id > max) { - Logger.Error($"PinMAME only supports up to {max} custom mechs, ignoring {mech.name}."); - return; - } - var mechConfig = mech.Config(_player.SwitchMapping, _player.CoilMapping); - _pinMame.SetMech(id, mechConfig); - foreach (var c in mechConfig.SwitchList) { - _mechSwitches.Add(c.SwNo); - } - } - } - - private void UpdateCaches() - { - if (_game == null) { - return; - } - _lamps.Clear(); - _coils.Clear(); - _switches.Clear(); - foreach (var lamp in _game.AvailableLamps) { - _lamps[lamp.InternalId] = lamp; - } - foreach (var coil in _game.AvailableCoils) { - _coils[coil.InternalId] = coil; - } - foreach (var sw in _game.AvailableSwitches) { - _switches[sw.Id] = sw; - } - } - - private void OnDestroy() - { - StopGame(); - } - - public void StopGame() - { - if (_pinMame != null) { - _pinMame.StopGame(); - _pinMame.OnGameStarted -= OnGameStarted; - _pinMame.OnGameEnded -= OnGameEnded; - _pinMame.OnDisplayAvailable -= OnDisplayAvailable; - _pinMame.OnDisplayUpdated -= OnDisplayUpdated; - - if (!DisableAudio) - { - _pinMame.OnAudioAvailable -= OnAudioAvailable; - _pinMame.OnAudioUpdated -= OnAudioUpdated; - } - - _pinMame.OnMechAvailable -= OnMechAvailable; - _pinMame.OnMechUpdated -= OnMechUpdated; - _pinMame.OnSolenoidUpdated -= OnSolenoidUpdated; - - } - _frameBuffer.Clear(); - _dmdLevels.Clear(); - } - public void Switch(string id, bool isClosed) { if (_switches.ContainsKey(id)) { @@ -596,86 +651,54 @@ public void Switch(string id, bool isClosed) OnSwitchChanged?.Invoke(this, new SwitchEventArgs2(id, isClosed)); } - public static DisplayFrameFormat GetDisplayFrameFormat(PinMameDisplayLayout layout) + public bool GetSwitch(string id) { - if (layout.IsDmd) { - return layout.Depth == 4 ? DisplayFrameFormat.Dmd4 : DisplayFrameFormat.Dmd2; - } - - switch (layout.Type) { - case PinMameDisplayType.Seg8: // 7 segments and comma - case PinMameDisplayType.Seg7SC: // 7 segments, small, with comma - case PinMameDisplayType.Seg8D: // 7 segments and period - case PinMameDisplayType.Seg7: // 7 segments - case PinMameDisplayType.Seg7S: // 7 segments, small - case PinMameDisplayType.Seg87: // 7 segments, comma every three - case PinMameDisplayType.Seg87F: // 7 segments, forced comma every three - case PinMameDisplayType.Seg10: // 9 segments and comma - case PinMameDisplayType.Seg9: // 9 segments - case PinMameDisplayType.Seg98: // 9 segments, comma every three - case PinMameDisplayType.Seg98F: // 9 segments, forced comma every three - case PinMameDisplayType.Seg16: // 16 segments - case PinMameDisplayType.Seg16R: // 16 segments with comma and period reversed - case PinMameDisplayType.Seg16N: // 16 segments without commas - case PinMameDisplayType.Seg16D: // 16 segments with periods only - case PinMameDisplayType.Seg16S: // 16 segments with split top and bottom line - case PinMameDisplayType.Seg8H: - case PinMameDisplayType.Seg7H: - case PinMameDisplayType.Seg87H: - case PinMameDisplayType.Seg87FH: - case PinMameDisplayType.Seg7SH: - case PinMameDisplayType.Seg7SCH: - case PinMameDisplayType.Seg7 | PinMameDisplayType.NoDisp: - return DisplayFrameFormat.Segment; - - case PinMameDisplayType.Video: - break; - - case PinMameDisplayType.SegAll: - case PinMameDisplayType.Import: - case PinMameDisplayType.SegMask: - case PinMameDisplayType.SegHiBit: - case PinMameDisplayType.SegRev: - case PinMameDisplayType.DmdNoAA: - case PinMameDisplayType.NoDisp: - throw new ArgumentOutOfRangeException(nameof(layout), layout, null); - - default: - throw new ArgumentOutOfRangeException(nameof(layout), layout, null); - } - - throw new NotImplementedException($"Still unsupported segmented display format: {layout}."); + return _player != null && _player.SwitchStatuses.ContainsKey(id) && _player.SwitchStatuses[id].IsSwitchEnabled; } - public void SetCoil(string n, bool value) - { - OnCoilChanged?.Invoke(this, new CoilEventArgs(n, value)); - } + #endregion - public void SetLamp(string id, float value, bool isCoil = false, LampSource source = LampSource.Lamp) - { - OnLampChanged?.Invoke(this, new LampEventArgs(id, value, isCoil, source)); - } + #region Mechs - public void SetLamp(string id, Color color) + public void RegisterMech(PinMameMechComponent mechComponent) { - OnLampColorChanged?.Invoke(this, new LampColorEventArgs(id, color)); + var id = _numMechs++; + _registeredMechs[id] = mechComponent; } - public float GetLamp(string id) + private void OnMechAvailable(int mechNo, PinMameMechInfo mechInfo) { - return _player != null && _player.LampStatuses.ContainsKey(id) ? _player.LampStatuses[id] : 0; + Logger.Info($"[PinMAME] <= mech available: mechNo={mechNo}, mechInfo={mechInfo}"); } - public bool GetSwitch(string id) + private void OnMechUpdated(int mechNo, PinMameMechInfo mechInfo) { - return _player != null && _player.SwitchStatuses.ContainsKey(id) && _player.SwitchStatuses[id].IsSwitchEnabled; + if (_registeredMechs.ContainsKey(mechNo)) { + lock (_dispatchQueue) { + _dispatchQueue.Enqueue(() => _registeredMechs[mechNo].UpdateMech(mechInfo)); + } + + } else { + Logger.Info($"[PinMAME] <= mech updated: mechNo={mechNo}, mechInfo={mechInfo}"); + } } - public bool GetCoil(string id) + private void SendMechs() { - return _player != null && _player.CoilStatuses.ContainsKey(id) && _player.CoilStatuses[id]; + var max = _pinMame.GetMaxMechs(); + foreach (var (id, mech) in _registeredMechs) { + if (id > max) { + Logger.Error($"PinMAME only supports up to {max} custom mechs, ignoring {mech.name}."); + return; + } + var mechConfig = mech.Config(_player.SwitchMapping, _player.CoilMapping); + _pinMame.SetMech(id, mechConfig); + foreach (var c in mechConfig.SwitchList) { + _mechSwitches.Add(c.SwNo); + } + } } + #endregion } } diff --git a/VisualPinball.Engine.PinMAME.Unity/package.json b/VisualPinball.Engine.PinMAME.Unity/package.json index 75c6e17..7e7314f 100644 --- a/VisualPinball.Engine.PinMAME.Unity/package.json +++ b/VisualPinball.Engine.PinMAME.Unity/package.json @@ -8,9 +8,9 @@ "PinMAME" ], "unity": "2021.2", - "unityRelease": "7f1", + "unityRelease": "14f1", "dependencies": { - "org.visualpinball.engine.unity": "0.0.1-preview.84" + "org.visualpinball.engine.unity": "0.0.1-preview.95" }, "author": "freezy ", "contributors": [ diff --git a/VisualPinball.Engine.PinMAME/Games/Rock.cs b/VisualPinball.Engine.PinMAME/Games/Rock.cs index 7622e8a..a7610bc 100644 --- a/VisualPinball.Engine.PinMAME/Games/Rock.cs +++ b/VisualPinball.Engine.PinMAME/Games/Rock.cs @@ -116,6 +116,11 @@ public class Rock : System80 new GamelogicEngineLamp("46") { Description = "Spot Target" }, new GamelogicEngineLamp("47") { Description = "Left Outside Rollover / Right Spinner", DeviceHint = "^L47[a-b]$", NumMatches = 2 }, new GamelogicEngineLamp("51") { Description = "Right Outisde Rollover / Left Spinner", DeviceHint = "^L51[a-b]$", NumMatches = 2 }, + new GamelogicEngineLamp("l_auxiliary", 100) { Description = "Auxiliary Lamps", DeviceHint = "^Auxiliary$" }, + new GamelogicEngineLamp("l_lower", 101) { Description = "Lower", DeviceHint = "^Lower$" }, + new GamelogicEngineLamp("l_upper_left_1", 102) { Description = "Upper Left 1", DeviceHint = "^UpperLeft1$" }, + new GamelogicEngineLamp("l_upper_left_2", 103) { Description = "Upper Left 2", DeviceHint = "^UpperLeft2$" }, + new GamelogicEngineLamp("l_upper_right", 104) { Description = "Upper Right", DeviceHint = "^UpperRight$" }, }; protected override GamelogicEngineCoil[] GameCoils { get; } = { diff --git a/VisualPinball.Engine.PinMAME/Games/StarTrekEnterprise.cs b/VisualPinball.Engine.PinMAME/Games/StarTrekEnterprise.cs index fe90aa9..7de7f4b 100644 --- a/VisualPinball.Engine.PinMAME/Games/StarTrekEnterprise.cs +++ b/VisualPinball.Engine.PinMAME/Games/StarTrekEnterprise.cs @@ -15,7 +15,6 @@ // along with this program. If not, see . using System; -using VisualPinball.Engine.Common; using VisualPinball.Engine.Game.Engines; using VisualPinball.Engine.Math; using VisualPinball.Engine.PinMAME.MPUs; @@ -23,7 +22,7 @@ namespace VisualPinball.Engine.PinMAME.Games { [Serializable] - public class StarTrekEnterprise : Wpc + public class StarTrekEnterprise : Sam { public override string Name => "Star Trek - Enterprise Limited Edition"; public override string Id => "star-trek-stern"; diff --git a/VisualPinball.Engine.PinMAME/Games/TheWalkingDead.cs b/VisualPinball.Engine.PinMAME/Games/TheWalkingDead.cs new file mode 100644 index 0000000..12f0475 --- /dev/null +++ b/VisualPinball.Engine.PinMAME/Games/TheWalkingDead.cs @@ -0,0 +1,269 @@ +// Visual Pinball Engine +// Copyright (C) 2021 freezy and VPE Team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using System; +using VisualPinball.Engine.Game.Engines; +using VisualPinball.Engine.Math; +using VisualPinball.Engine.PinMAME.MPUs; + +namespace VisualPinball.Engine.PinMAME.Games +{ + [Serializable] + public class TheWalkingDead : Sam + { + public override string Name => "The Walking Dead - Limited Edition"; + public override string Id => "twd"; + public override int Year => 2014; + public override string Manufacturer => "Stern"; + public override int IpdbId => 6156; + public override PinMameRom[] Roms { get; } = { + new PinMameRom("twd_105", "1.05"), + new PinMameRom("twd_111", "1.11"), + new PinMameRom("twd_111h", "1.11h"), + new PinMameRom("twd_119", "1.19"), + new PinMameRom("twd_119h", "1.19h"), + new PinMameRom("twd_124", "1.24"), + new PinMameRom("twd_124h", "1.24h"), + new PinMameRom("twd_125", "1.25"), + new PinMameRom("twd_125h", "1.25h"), + new PinMameRom("twd_128", "1.28"), + new PinMameRom("twd_128h", "1.28h"), + new PinMameRom("twd_141", "1.41"), + new PinMameRom("twd_141h", "1.41h"), + new PinMameRom("twd_153", "1.53"), + new PinMameRom("twd_153h", "1.53h"), + new PinMameRom("twd_156", "1.56"), + new PinMameRom("twd_156h", "1.56h"), + new PinMameRom("twd_160", "1.60.0"), + new PinMameRom("twd_160h", "1.60.0h"), + }; + + protected override GamelogicEngineSwitch[] Switches { get; } = { + new GamelogicEngineSwitch("01") { Description = "Right Spinner" }, + new GamelogicEngineSwitch("02") { Description = "Well Walker" }, + new GamelogicEngineSwitch("03") { Description = "Prison Walker Hit" }, + new GamelogicEngineSwitch("04") { Description = "Prison Doors Closed" }, + new GamelogicEngineSwitch("09") { Description = "Left 3-Bank #1 (Bot)" }, + new GamelogicEngineSwitch("10") { Description = "Left 3-Bank #2 (Mid)" }, + new GamelogicEngineSwitch("11") { Description = "Left 3-Bank #3 (Top)" }, + new GamelogicEngineSwitch("15") { Description = "Tourn Start" }, + new GamelogicEngineSwitch("18") { Description = "Trough #4 Left" }, + new GamelogicEngineSwitch("19") { Description = "Trough #3" }, + new GamelogicEngineSwitch("20") { Description = "Trough #2" }, + new GamelogicEngineSwitch("21") { Description = "Trough #1 Right" }, + new GamelogicEngineSwitch("22") { Description = "Trough Jam" }, + new GamelogicEngineSwitch("23") { Description = "Shooter Lane" }, + new GamelogicEngineSwitch("24") { Description = "Left Outlane" }, + new GamelogicEngineSwitch("25") { Description = "Left Return Lane" }, + new GamelogicEngineSwitch("26") { Description = "Left Slingshot" }, + new GamelogicEngineSwitch("33") { Description = "Upper Shooter Lane" }, + new GamelogicEngineSwitch("34") { Description = "Right Ramp Enter" }, + new GamelogicEngineSwitch("35") { Description = "Left Ramp Exit" }, + new GamelogicEngineSwitch("36") { Description = "Left Top Lane" }, + new GamelogicEngineSwitch("37") { Description = "Right Top Lane" }, + new GamelogicEngineSwitch("38") { Description = "Tower Standup" }, + new GamelogicEngineSwitch("39") { Description = "Right Loop" }, + new GamelogicEngineSwitch("40") { Description = "Left Loop Spinner" }, + new GamelogicEngineSwitch("41") { Description = "Left Loop" }, + new GamelogicEngineSwitch("42") { Description = "Right Ramp Exit" }, + new GamelogicEngineSwitch("49") { Description = "Bicycle Girl Hit" }, + new GamelogicEngineSwitch("50") { Description = "Crossbow Home" }, + new GamelogicEngineSwitch("51") { Description = "Crossbow Mark" }, + new GamelogicEngineSwitch("52") { Description = "Crossbow Eject" }, + + /* + + From manual: + + new GamelogicEngineSwitch("D1") { Description = "Left Coin Slot" }, + new GamelogicEngineSwitch("D2") { Description = "Center Coin Slot" }, + new GamelogicEngineSwitch("D3") { Description = "Right Coin Slot" }, + new GamelogicEngineSwitch("D4") { Description = "Forth Coin Slot" }, + new GamelogicEngineSwitch("D5") { Description = "Fifth Coin Slot" }, + new GamelogicEngineSwitch("D6") { Description = "Star Rollover (Bottom)" }, + new GamelogicEngineSwitch("D7") { Description = "Fire Bottom" }, + new GamelogicEngineSwitch("D8") { Description = "Star Rollover (Top)" }, + new GamelogicEngineSwitch("D9") { Description = "Left Flipper Button" }, + new GamelogicEngineSwitch("D10") { Description = "Left Flipper EOS" }, + new GamelogicEngineSwitch("D17") { Description = "Tilt Pendulum" }, + new GamelogicEngineSwitch("D18") { Description = "Slam Tilt" }, + new GamelogicEngineSwitch("D19") { Description = "Ticket Notch" }, + new GamelogicEngineSwitch("D21") { Description = "Back (Green)" }, + new GamelogicEngineSwitch("D22") { Description = "Minus (Red)" }, + new GamelogicEngineSwitch("D23") { Description = "Plus (Red)" }, + new GamelogicEngineSwitch("D24") { Description = "Select (Black)" }, + new GamelogicEngineSwitch("D25") { Description = "Dip (1)2345678" }, + new GamelogicEngineSwitch("D26") { Description = "Dip 1(2)345678" }, + new GamelogicEngineSwitch("D27") { Description = "Dip 12(3)45678" }, + new GamelogicEngineSwitch("D28") { Description = "Dip 123(4)5678" }, + new GamelogicEngineSwitch("D29") { Description = "Dip 1234(5)678" }, + new GamelogicEngineSwitch("D30") { Description = "Dip 12345(6)78" }, + new GamelogicEngineSwitch("D31") { Description = "Dip 123456(7)8" }, + new GamelogicEngineSwitch("D32") { Description = "Dip 1234567(8)" }, + + */ + }; + + public override GamelogicEngineLamp[] AvailableLamps { get; } = { + new GamelogicEngineLamp("1") { Description = "Start Button" }, + new GamelogicEngineLamp("2") { Description = "Tournament Start Button" }, + new GamelogicEngineLamp("3") { Description = "12X Playfield values" }, + new GamelogicEngineLamp("4") { Description = "Shoot Again" }, + new GamelogicEngineLamp("5") { Description = "4 Walkers Killed" }, + new GamelogicEngineLamp("6") { Description = "3 Walkers Killed" }, + new GamelogicEngineLamp("7") { Description = "2 Walkers Killed" }, + new GamelogicEngineLamp("8") { Description = "1 Walker Killed" }, + new GamelogicEngineLamp("9") { Description = "40 Walkers Killed" }, + new GamelogicEngineLamp("10") { Description = "Last Man Standing" }, + new GamelogicEngineLamp("11") { Description = "5 Walkers Killed" }, + new GamelogicEngineLamp("12") { Description = "10 Walkers Killed" }, + new GamelogicEngineLamp("13") { Description = "20 Walkers Killed" }, + new GamelogicEngineLamp("14") { Description = "30 Walkers Killed" }, + new GamelogicEngineLamp("15") { Description = "Hammer Multi-Killed" }, + new GamelogicEngineLamp("16") { Description = "Sword Muiti-Kill" }, + new GamelogicEngineLamp("17") { Description = "Crossbow Muiti-Kill" }, + new GamelogicEngineLamp("18") { Description = "Gun Multi-Kill" }, + new GamelogicEngineLamp("19") { Description = "Knife Multi-Kill" }, + new GamelogicEngineLamp("20") { Description = "Axe Multi-Kill" }, + new GamelogicEngineLamp("21") { Description = "Horde" }, + new GamelogicEngineLamp("22") { Description = "Left Outlane" }, + new GamelogicEngineLamp("23") { Description = "Left Return Lane" }, + + new GamelogicEngineLamp("24", 168) { Description = "R. Loop Arrow Red", Type = LampType.RgbMulti, Channel = ColorChannel.Red }, + new GamelogicEngineLamp("24", 169) { Description = "R. Loop Arrow Grn", Type = LampType.RgbMulti, Channel = ColorChannel.Green }, + new GamelogicEngineLamp("24", 170) { Description = "R. Loop Arrow Blu", Type = LampType.RgbMulti, Channel = ColorChannel.Blue }, + + new GamelogicEngineLamp("25") { Description = "Blood Bath" }, + new GamelogicEngineLamp("26") { Description = "First Aid" }, + new GamelogicEngineLamp("27") { Description = "Weapons" }, + new GamelogicEngineLamp("28") { Description = "Food" }, + new GamelogicEngineLamp("29") { Description = "L. Ramp Walker Kill" }, + new GamelogicEngineLamp("30") { Description = "L. Loop Walker Kill" }, + new GamelogicEngineLamp("31") { Description = "L. Loop Multi-Kill" }, + new GamelogicEngineLamp("32") { Description = "Barn Mode" }, + + new GamelogicEngineLamp("33", 195) { Description = "L. Loop Arrow Red", Type = LampType.RgbMulti, Channel = ColorChannel.Red }, + new GamelogicEngineLamp("33", 196) { Description = "L. Loop Arrow Grn", Type = LampType.RgbMulti, Channel = ColorChannel.Green }, + new GamelogicEngineLamp("33", 197) { Description = "L. Loop Arrow Blu", Type = LampType.RgbMulti, Channel = ColorChannel.Blue }, + + new GamelogicEngineLamp("34") { Description = "L. Ramp Multi-Kill" }, + new GamelogicEngineLamp("35") { Description = "CDC Mode" }, + + new GamelogicEngineLamp("36", 203) { Description = "L. Ramp Arrow Red", Type = LampType.RgbMulti, Channel = ColorChannel.Red }, + new GamelogicEngineLamp("36", 204) { Description = "L. Ramp Arrow Grn", Type = LampType.RgbMulti, Channel = ColorChannel.Green }, + new GamelogicEngineLamp("36", 205) { Description = "L. Ramp Arrow Blu", Type = LampType.RgbMulti, Channel = ColorChannel.Blue }, + + new GamelogicEngineLamp("37") { Description = "Right Outlane" }, + new GamelogicEngineLamp("38") { Description = "Right Return Lane" }, + new GamelogicEngineLamp("39") { Description = "Extra Ball" }, + new GamelogicEngineLamp("40") { Description = "Welcome To Woodbury" }, + + new GamelogicEngineLamp("41", 152) { Description = "R. Ramp Arrow Red", Type = LampType.RgbMulti, Channel = ColorChannel.Red }, + new GamelogicEngineLamp("41", 153) { Description = "R. Ramp Arrow Grn", Type = LampType.RgbMulti, Channel = ColorChannel.Green }, + new GamelogicEngineLamp("41", 154) { Description = "R. Ramp Arrow Blu", Type = LampType.RgbMulti, Channel = ColorChannel.Blue }, + + new GamelogicEngineLamp("42") { Description = "R. Ramp Walker Kill" }, + new GamelogicEngineLamp("43") { Description = "R. Ramp Multi-Kill" }, + new GamelogicEngineLamp("44") { Description = "Arena Mode" }, + new GamelogicEngineLamp("45") { Description = "Well Walker Kill" }, + new GamelogicEngineLamp("46") { Description = "(W)ELL" }, + new GamelogicEngineLamp("47") { Description = "W(E)LL" }, + new GamelogicEngineLamp("48") { Description = "WE(L)L" }, + new GamelogicEngineLamp("49") { Description = "WEL(L)" }, + new GamelogicEngineLamp("50") { Description = "Well Walker" }, + new GamelogicEngineLamp("51") { Description = "R. Loop Walker Kill" }, + new GamelogicEngineLamp("52") { Description = "R. Loop Multi-Kill" }, + new GamelogicEngineLamp("53") { Description = "Tunnel Mode" }, + new GamelogicEngineLamp("54") { Description = "Siege" }, + new GamelogicEngineLamp("55") { Description = "R. Prison Standup" }, + new GamelogicEngineLamp("56") { Description = "L. Prison Standup" }, + + new GamelogicEngineLamp("57", 187) { Description = "C. Lane Arrow Red", Type = LampType.RgbMulti, Channel = ColorChannel.Red }, + new GamelogicEngineLamp("57", 188) { Description = "C. Lane Arrow Grn", Type = LampType.RgbMulti, Channel = ColorChannel.Green }, + new GamelogicEngineLamp("57", 189) { Description = "C. Lane Arrow Blu", Type = LampType.RgbMulti, Channel = ColorChannel.Blue }, + + new GamelogicEngineLamp("58") { Description = "Riot Mode" }, + new GamelogicEngineLamp("59") { Description = "C. Lane Multi-Kill" }, + new GamelogicEngineLamp("60") { Description = "Top Bumper" }, + new GamelogicEngineLamp("61") { Description = "Right Bumper" }, + new GamelogicEngineLamp("62") { Description = "Left Bumper" }, + new GamelogicEngineLamp("63") { Description = "C. Lane Walker" }, + new GamelogicEngineLamp("64") { Description = "(P)RISON" }, + new GamelogicEngineLamp("65") { Description = "P(R)ISON" }, + new GamelogicEngineLamp("66") { Description = "PR(I)SON" }, + new GamelogicEngineLamp("67") { Description = "PRI(S)ON" }, + new GamelogicEngineLamp("68") { Description = "PRIS(O)N" }, + new GamelogicEngineLamp("69") { Description = "PRISO(N)" }, + new GamelogicEngineLamp("70") { Description = "Crossbow" }, + new GamelogicEngineLamp("71") { Description = "Fish Tank" }, + new GamelogicEngineLamp("72") { Description = "Tower" }, + new GamelogicEngineLamp("73") { Description = "Fish Tank Head #1" }, + new GamelogicEngineLamp("74") { Description = "Fish Tank Head #2" }, + new GamelogicEngineLamp("75") { Description = "Fish Tank Head #3" }, + new GamelogicEngineLamp("76") { Description = "Left Top Lane" }, + new GamelogicEngineLamp("77") { Description = "Right Top Lane" }, + new GamelogicEngineLamp("78") { Description = "Bicycle Girl" }, + + new GamelogicEngineLamp("79", 136) { Description = "Star Rollover (Bot.) Red", Type = LampType.RgbMulti, Channel = ColorChannel.Red }, + new GamelogicEngineLamp("79", 137) { Description = "Star Rollover (Bot.) Grn", Type = LampType.RgbMulti, Channel = ColorChannel.Green }, + new GamelogicEngineLamp("79", 138) { Description = "Star Rollover (Bot.) Blu", Type = LampType.RgbMulti, Channel = ColorChannel.Blue }, + + new GamelogicEngineLamp("80", 133) { Description = "Star Rollover (Top.) Red", Type = LampType.RgbMulti, Channel = ColorChannel.Red }, + new GamelogicEngineLamp("80", 134) { Description = "Star Rollover (Top.) Grn", Type = LampType.RgbMulti, Channel = ColorChannel.Green }, + new GamelogicEngineLamp("80", 135) { Description = "Star Rollover (Top.) Blu", Type = LampType.RgbMulti, Channel = ColorChannel.Blue }, + + new GamelogicEngineLamp("81") { Description = "Fire Button (Red)", Type = LampType.RgbMulti, Channel = ColorChannel.Red }, + new GamelogicEngineLamp("81") { Description = "Fire Button (Grn)", Type = LampType.RgbMulti, Channel = ColorChannel.Green }, + new GamelogicEngineLamp("81") { Description = "Fire Button (Blu)", Type = LampType.RgbMulti, Channel = ColorChannel.Blue }, + + new GamelogicEngineLamp("gi", 106) { Description = "GI (Red)", Type = LampType.RgbMulti, Source = LampSource.GI, Channel = ColorChannel.Red }, + new GamelogicEngineLamp("gi", 107) { Description = "GI (Grn)", Type = LampType.RgbMulti, Source = LampSource.GI, Channel = ColorChannel.Green }, + new GamelogicEngineLamp("gi", 108) { Description = "GI (Blu)", Type = LampType.RgbMulti, Source = LampSource.GI, Channel = ColorChannel.Blue }, + + new GamelogicEngineLamp("gi_slings", 109) { Description = "GI: Slings (Red)", Type = LampType.RgbMulti, Source = LampSource.GI, Channel = ColorChannel.Red }, + new GamelogicEngineLamp("gi_slings", 114) { Description = "GI: Slings (Grn)", Type = LampType.RgbMulti, Source = LampSource.GI, Channel = ColorChannel.Green }, + new GamelogicEngineLamp("gi_slings", 149) { Description = "GI: Slings (Blu)", Type = LampType.RgbMulti, Source = LampSource.GI, Channel = ColorChannel.Blue }, + }; + + protected override GamelogicEngineCoil[] GameCoils { get; } = { + new GamelogicEngineCoil("01") { Description = "Trough Up-Kicker" }, + new GamelogicEngineCoil("02") { Description = "Auto Launch" }, + new GamelogicEngineCoil("03") { Description = "Prison Doors (Power)" }, + new GamelogicEngineCoil("04") { Description = "Prison Doors (Hold)" }, + new GamelogicEngineCoil("05") { Description = "Ramp Magnet Diverter" }, + new GamelogicEngineCoil("06") { Description = "Well Magnet" }, + new GamelogicEngineCoil("07") { Description = "Prison Magnet" }, + new GamelogicEngineCoil("08") { Description = "Shaker Motor (Optional)" }, + new GamelogicEngineCoil("09") { Description = "Left Pop Bumper" }, + new GamelogicEngineCoil("10") { Description = "Right Pop Bumper" }, + new GamelogicEngineCoil("11") { Description = "Top Pop Bumper" }, + new GamelogicEngineCoil("12") { Description = "Left 3-Bank Drop Target" }, + new GamelogicEngineCoil("13") { Description = "Left Slingshot" }, + new GamelogicEngineCoil("14") { Description = "Right Slingshot" }, + new GamelogicEngineCoil("19") { Description = "Flash: Well Walker", IsLamp = true }, + new GamelogicEngineCoil("20") { Description = "Flash: Right Spinner", IsLamp = true }, + new GamelogicEngineCoil("21") { Description = "Crossbow Motor" }, + new GamelogicEngineCoil("24") { Description = "Optional (e.g. Coin Meter)" }, + new GamelogicEngineCoil("25") { Description = "Flash: Pop Bumpers", IsLamp = true }, + new GamelogicEngineCoil("26") { Description = "Flash: Prison (Top)", IsLamp = true }, + new GamelogicEngineCoil("27") { Description = "Flash: Prison (Bottom) (X2)", IsLamp = true }, + new GamelogicEngineCoil("28") { Description = "Flash: Left Dome", IsLamp = true }, + new GamelogicEngineCoil("29") { Description = "Flash: Right Dome", IsLamp = true }, + new GamelogicEngineCoil("31") { Description = "Flash: Left Loop", IsLamp = true }, + new GamelogicEngineCoil("32") { Description = "Flash: Center Loop", IsLamp = true }, + }; + } +} diff --git a/VisualPinball.Engine.PinMAME/MPUs/Sam.cs b/VisualPinball.Engine.PinMAME/MPUs/Sam.cs new file mode 100644 index 0000000..8bbc3fe --- /dev/null +++ b/VisualPinball.Engine.PinMAME/MPUs/Sam.cs @@ -0,0 +1,51 @@ +// Visual Pinball Engine +// Copyright (C) 2021 freezy and VPE Team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using VisualPinball.Engine.Common; +using VisualPinball.Engine.Game.Engines; + +namespace VisualPinball.Engine.PinMAME.MPUs +{ + public abstract class Sam : PinMameGame + { + public override GamelogicEngineSwitch[] AvailableSwitches => Concat(_switches, Switches); + + protected override GamelogicEngineCoil[] Coils => Concat(_coils, GameCoils); + protected abstract GamelogicEngineCoil[] GameCoils { get; } + + protected abstract GamelogicEngineSwitch[] Switches { get; } + + private readonly GamelogicEngineSwitch[] _switches = { + new GamelogicEngineSwitch(SwCoin1, 65) { Description = "Coin Button 1", InputActionHint = InputConstants.ActionInsertCoin1, InputMapHint = InputConstants.MapCabinetSwitches }, + new GamelogicEngineSwitch(SwCoin2, 66) { Description = "Coin Button 2", InputActionHint = InputConstants.ActionInsertCoin2, InputMapHint = InputConstants.MapCabinetSwitches }, + new GamelogicEngineSwitch(SwCoin3, 67) { Description = "Coin Button 3", InputActionHint = InputConstants.ActionInsertCoin3, InputMapHint = InputConstants.MapCabinetSwitches }, + new GamelogicEngineSwitch(SwCancel, -3) { Description = "Coin Door Back", InputActionHint = InputConstants.ActionCoinDoorBack, InputMapHint = InputConstants.MapCabinetSwitches }, + new GamelogicEngineSwitch(SwDown, -2) { Description = "Coin Door -", InputActionHint = InputConstants.ActionCoinDoorMinus, InputMapHint = InputConstants.MapCabinetSwitches }, + new GamelogicEngineSwitch(SwUp, -1) { Description = "Coin Door +", InputActionHint = InputConstants.ActionCoinDoorPlus, InputMapHint = InputConstants.MapCabinetSwitches }, + new GamelogicEngineSwitch(SwEnter, 0) { Description = "Coin Door Select", InputActionHint = InputConstants.ActionCoinDoorSelect, InputMapHint = InputConstants.MapCabinetSwitches }, + new GamelogicEngineSwitch(SwStartButton, 16) { Description = "Start", InputActionHint = InputConstants.ActionStartGame, InputMapHint = InputConstants.MapCabinetSwitches }, + new GamelogicEngineSwitch(SwSlamTilt, -6) { Description = "Slam Tilt", InputActionHint = InputConstants.ActionSlamTilt, InputMapHint = InputConstants.MapCabinetSwitches }, + new GamelogicEngineSwitch(SwTilt, -7) { Description = "Tilt" }, + new GamelogicEngineSwitch(SwFlipperLowerLeft, 84) { Description = "Lower Left Flipper", InputActionHint = InputConstants.ActionLeftFlipper, InputMapHint = InputConstants.MapCabinetSwitches }, + new GamelogicEngineSwitch(SwFlipperLowerRight, 82) { Description = "Lower Right Flipper", InputActionHint = InputConstants.ActionRightFlipper, InputMapHint = InputConstants.MapCabinetSwitches }, + }; + + private readonly GamelogicEngineCoil[] _coils = { + new GamelogicEngineCoil(CoilFlipperLowerLeft, 15) { Description = "Lower Left Flipper", DeviceHint = "^(LeftFlipper|LFlipper|FlipperLeft|FlipperL)$"}, + new GamelogicEngineCoil(CoilFlipperLowerRight, 16) { Description = "Lower Right Flipper", DeviceHint = "^(RightFlipper|RFlipper|FlipperRight|FlipperR)$"}, + }; + } +} diff --git a/VisualPinball.Engine.PinMAME/VisualPinball.Engine.PinMAME.csproj b/VisualPinball.Engine.PinMAME/VisualPinball.Engine.PinMAME.csproj index 8999ea1..50eead4 100644 --- a/VisualPinball.Engine.PinMAME/VisualPinball.Engine.PinMAME.csproj +++ b/VisualPinball.Engine.PinMAME/VisualPinball.Engine.PinMAME.csproj @@ -14,8 +14,9 @@ - - + + +