diff --git a/Editor/Descriptors/EMAddPointsUnitDescriptor.cs b/Editor/Descriptors/EMAddPointsUnitDescriptor.cs new file mode 100644 index 0000000..9d4966b --- /dev/null +++ b/Editor/Descriptors/EMAddPointsUnitDescriptor.cs @@ -0,0 +1,78 @@ +// Visual Pinball Engine +// Copyright (C) 2022 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 . + +// ReSharper disable UnusedType.Global + +using Unity.VisualScripting; + +namespace VisualPinball.Unity.VisualScripting.Editor +{ + [Descriptor(typeof(EMAddPointsUnit))] + public class EMAddPointsUnitDescriptor : UnitDescriptor + { + public EMAddPointsUnitDescriptor(EMAddPointsUnit target) : base(target) + { + } + + protected override string DefinedSummary() + { + return "This node takes an incoming point value and pulses values to that can be used to simulate adding points to a score reel. " + + "\n\nFor example, an incoming point value of 500 will provide 5 pulses of 100. " + + "\n\nSingle pulse points (1, 10, 100, 1000, 10000) will be blocked if the score motor is running and Block Points is enabled."; + } + + protected override EditorTexture DefinedIcon() => EditorTexture.Single(Unity.Editor.Icons.Mech(Unity.Editor.IconSize.Large, Unity.Editor.IconColor.Orange)); + + protected override void DefinedPort(IUnitPort port, UnitPortDescription desc) + { + base.DefinedPort(port, desc); + + switch (port.key) { + case nameof(EMAddPointsUnit.pointValue): + desc.summary = "The total amount of points to add."; + break; + + case nameof(EMAddPointsUnit.blockPoints): + desc.summary = "Block single pulse points when score motor running."; + break; + + case nameof(EMAddPointsUnit.positions): + desc.summary = "Score motor positions."; + break; + + case nameof(EMAddPointsUnit.duration): + desc.summary = "The amount of time (in ms) the score motor runs."; + break; + + case nameof(EMAddPointsUnit.started): + desc.summary = "Triggered when score motor starts."; + break; + + case nameof(EMAddPointsUnit.stopped): + desc.summary = "Triggered when score motor finishes."; + break; + + case nameof(EMAddPointsUnit.pulse): + desc.summary = "Triggered during each pulse of the score motor."; + break; + + case nameof(EMAddPointsUnit.OutputPointValue): + desc.summary = "The current pulses calculated points value that can be used to increment a score value and update a score reel."; + break; + } + } + } +} diff --git a/Editor/Descriptors/EMAddPointsUnitDescriptor.cs.meta b/Editor/Descriptors/EMAddPointsUnitDescriptor.cs.meta new file mode 100644 index 0000000..dad0342 --- /dev/null +++ b/Editor/Descriptors/EMAddPointsUnitDescriptor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 88349127e95cc4b64bb76e45f82a88e4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Descriptors/EMResetPointsUnitDescriptor.cs b/Editor/Descriptors/EMResetPointsUnitDescriptor.cs new file mode 100644 index 0000000..c62cee2 --- /dev/null +++ b/Editor/Descriptors/EMResetPointsUnitDescriptor.cs @@ -0,0 +1,73 @@ +// Visual Pinball Engine +// Copyright (C) 2022 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 . + +// ReSharper disable UnusedType.Global + +using Unity.VisualScripting; + +namespace VisualPinball.Unity.VisualScripting.Editor +{ + [Descriptor(typeof(EMResetPointsUnit))] + public class EMResetPointsUnitDescriptor : UnitDescriptor + { + public EMResetPointsUnitDescriptor(EMResetPointsUnit target) : base(target) + { + } + + protected override string DefinedSummary() + { + return "This node takes an incoming point value and pulses values to that can be used to simulate the resetting of a score reel. " + + "\n\nFor example, an incoming point value of 2041 will provide the following pulses: 3052, 4063, 5074, 6085, 7096, 8007, 9008, 0009, 0000"; + } + + protected override EditorTexture DefinedIcon() => EditorTexture.Single(Unity.Editor.Icons.Mech(Unity.Editor.IconSize.Large, Unity.Editor.IconColor.Orange)); + + protected override void DefinedPort(IUnitPort port, UnitPortDescription desc) + { + base.DefinedPort(port, desc); + + switch (port.key) { + case nameof(EMResetPointsUnit.pointValue): + desc.summary = "The starting points value used to seed the reset sequence."; + break; + + case nameof(EMResetPointsUnit.duration): + desc.summary = "The amount of time (in ms) the score motor runs."; + break; + + case nameof(EMAddPointsUnit.positions): + desc.summary = "Score motor positions."; + break; + + case nameof(EMResetPointsUnit.started): + desc.summary = "Triggered when score motor starts."; + break; + + case nameof(EMResetPointsUnit.stopped): + desc.summary = "Triggered when score motor finishes."; + break; + + case nameof(EMResetPointsUnit.pulse): + desc.summary = "Triggered during each pulse of the score motor."; + break; + + case nameof(EMResetPointsUnit.OutputPointValue): + desc.summary = "The current pulses calculated points value that can be used to update a score reel."; + break; + } + } + } +} diff --git a/Editor/Descriptors/EMResetPointsUnitDescriptor.cs.meta b/Editor/Descriptors/EMResetPointsUnitDescriptor.cs.meta new file mode 100644 index 0000000..58f710d --- /dev/null +++ b/Editor/Descriptors/EMResetPointsUnitDescriptor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e5a9f36c94e9543139b6f68c1b0faf5e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Widgets/EMAddPointsUnitWidget.cs b/Editor/Widgets/EMAddPointsUnitWidget.cs new file mode 100644 index 0000000..7dc60bd --- /dev/null +++ b/Editor/Widgets/EMAddPointsUnitWidget.cs @@ -0,0 +1,30 @@ +// Visual Pinball Engine +// Copyright (C) 2022 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 . + +// ReSharper disable UnusedType.Global + +using Unity.VisualScripting; + +namespace VisualPinball.Unity.VisualScripting.Editor +{ + [Widget(typeof(EMAddPointsUnit))] + public sealed class EMAddPointsUnitWidget : GleUnitWidget + { + public EMAddPointsUnitWidget(FlowCanvas canvas, EMAddPointsUnit unit) : base(canvas, unit) + { + } + } +} diff --git a/Editor/Widgets/EMAddPointsUnitWidget.cs.meta b/Editor/Widgets/EMAddPointsUnitWidget.cs.meta new file mode 100644 index 0000000..9f0f456 --- /dev/null +++ b/Editor/Widgets/EMAddPointsUnitWidget.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6659f99df4fbf4ce59b36703a7385e0f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Widgets/EMResetPointsUnitWidget.cs b/Editor/Widgets/EMResetPointsUnitWidget.cs new file mode 100644 index 0000000..9462d10 --- /dev/null +++ b/Editor/Widgets/EMResetPointsUnitWidget.cs @@ -0,0 +1,30 @@ +// Visual Pinball Engine +// Copyright (C) 2022 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 . + +// ReSharper disable UnusedType.Global + +using Unity.VisualScripting; + +namespace VisualPinball.Unity.VisualScripting.Editor +{ + [Widget(typeof(EMResetPointsUnit))] + public sealed class EMResetPointsUnitWidget : GleUnitWidget + { + public EMResetPointsUnitWidget(FlowCanvas canvas, EMResetPointsUnit unit) : base(canvas, unit) + { + } + } +} diff --git a/Editor/Widgets/EMResetPointsUnitWidget.cs.meta b/Editor/Widgets/EMResetPointsUnitWidget.cs.meta new file mode 100644 index 0000000..a366979 --- /dev/null +++ b/Editor/Widgets/EMResetPointsUnitWidget.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 71cb8e09a554f4b1aa91d7d87bea76ea +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Gamelogic/State.cs b/Runtime/Gamelogic/State.cs index 3663421..df7084d 100644 --- a/Runtime/Gamelogic/State.cs +++ b/Runtime/Gamelogic/State.cs @@ -61,6 +61,8 @@ public T Get(string variableId) where T : class return _variables[variableId].Get(); } + public bool IsDefined(string variableId) => _variables.ContainsKey(variableId); + public StateVariable GetVariable(string variableId) => _variables[variableId]; public object Get(string variableId) diff --git a/Runtime/Nodes/EM.meta b/Runtime/Nodes/EM.meta new file mode 100644 index 0000000..adb6d56 --- /dev/null +++ b/Runtime/Nodes/EM.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c0f219f0a0a654b79862bd68d18a2228 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Nodes/EM/EMAddPointsUnit.cs b/Runtime/Nodes/EM/EMAddPointsUnit.cs new file mode 100644 index 0000000..5ad1395 --- /dev/null +++ b/Runtime/Nodes/EM/EMAddPointsUnit.cs @@ -0,0 +1,170 @@ +// Visual Pinball Engine +// Copyright (C) 2022 freezy and VPE Team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using System.Collections; +using Unity.VisualScripting; +using UnityEngine; + +namespace VisualPinball.Unity.VisualScripting +{ + [UnitShortTitle("EM Add Points")] + [UnitTitle("EM Add Points")] + [UnitSurtitle("EM")] + [UnitCategory("Visual Pinball/EM")] + public class EMAddPointsUnit : GleUnit + { + [DoNotSerialize] + [PortLabelHidden] + public ControlInput InputTrigger; + + [DoNotSerialize] + public ValueInput blockPoints { get; private set; } + + [DoNotSerialize] + public ValueInput positions { get; private set; } + + [DoNotSerialize] + public ValueInput duration { get; private set; } + + [DoNotSerialize] + [PortLabel("Unscaled")] + public ValueInput unscaledTime { get; private set; } + + [DoNotSerialize] + [PortLabelHidden] + public ControlOutput OutputTrigger; + + [DoNotSerialize] + public ControlOutput started; + + [DoNotSerialize] + public ControlOutput stopped; + + [DoNotSerialize] + public ControlOutput pulse { get; private set; } + + [DoNotSerialize] + public ValueInput pointValue { get; private set; } + + [DoNotSerialize] + [PortLabel("Point Value")] + public ValueOutput OutputPointValue { get; private set; } + + private State State => VsGle.TableState; + private static string VARIABLE_EM_SCORE_MOTOR = "EM_SCORE_MOTOR"; + + protected override void Definition() + { + InputTrigger = ControlInputCoroutine(nameof(InputTrigger), Process); + + pointValue = ValueInput(nameof(pointValue), 0); + + blockPoints = ValueInput(nameof(blockPoints), true); + positions = ValueInput(nameof(positions), 6); + duration = ValueInput(nameof(duration), 750); + unscaledTime = ValueInput(nameof(unscaledTime), false); + + OutputTrigger = ControlOutput(nameof(OutputTrigger)); + + started = ControlOutput(nameof(started)); + stopped = ControlOutput(nameof(stopped)); + + pulse = ControlOutput(nameof(pulse)); + + OutputPointValue = ValueOutput(nameof(OutputPointValue)); + } + + private IEnumerator Process(Flow flow) + { + if (!AssertVsGle(flow)) { + yield return OutputTrigger; + } + else { + var running = false; + + if (State.IsDefined(VARIABLE_EM_SCORE_MOTOR)) { + running = State.Get(VARIABLE_EM_SCORE_MOTOR); + } + else { + State.AddProperty(new StateVariable(VARIABLE_EM_SCORE_MOTOR, "", false)); + } + + var points = flow.GetValue(pointValue); + + if (points > 0) { + var pulses = + (points % 100000 == 0) ? points / 100000 : + (points % 10000 == 0) ? points / 10000 : + (points % 1000 == 0) ? points / 1000 : + (points % 100 == 0) ? points / 100 : + (points % 10 == 0) ? points / 10 : + points; + + if (pulses == 1) { + if (!running || (running && !flow.GetValue(blockPoints))) { + Debug.Log($"Single pulse triggering with {points} points"); + + flow.SetValue(OutputPointValue, points); + + yield return pulse; + } + } + else if (running) { + Debug.Log($"Score motor is already running."); + } + else { + Debug.Log("Starting score motor"); + + State.Set(VARIABLE_EM_SCORE_MOTOR, true); + + yield return started; + + var motorPositions = flow.GetValue(positions); + + var delay = (flow.GetValue(duration) / 1000f) / motorPositions; + var realtime = flow.GetValue(unscaledTime); + + var pointsPerPulse = points / pulses; + + for (int loop = 0; loop < motorPositions; loop++) { + var outputPoints = loop < pulses ? pointsPerPulse : 0; + + Debug.Log($"Pulse {loop + 1} of {motorPositions} - waiting {delay}ms and triggering with {outputPoints} points"); + + if (realtime) { + yield return new WaitForSecondsRealtime(delay); + } + else { + yield return new WaitForSeconds(delay); + } + + flow.SetValue(OutputPointValue, outputPoints); + + yield return pulse; + } + + Debug.Log("Stopping score motor"); + + State.Set(VARIABLE_EM_SCORE_MOTOR, false); + + yield return stopped; + } + } + + } + } + } +} diff --git a/Runtime/Nodes/EM/EMAddPointsUnit.cs.meta b/Runtime/Nodes/EM/EMAddPointsUnit.cs.meta new file mode 100644 index 0000000..a82bb67 --- /dev/null +++ b/Runtime/Nodes/EM/EMAddPointsUnit.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d673fe77022c74cdf8426b744e12cf9b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Nodes/EM/EMResetPointsUnit.cs b/Runtime/Nodes/EM/EMResetPointsUnit.cs new file mode 100644 index 0000000..f150c88 --- /dev/null +++ b/Runtime/Nodes/EM/EMResetPointsUnit.cs @@ -0,0 +1,189 @@ +// Visual Pinball Engine +// Copyright (C) 2022 freezy and VPE Team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using System.Collections; +using Unity.VisualScripting; +using UnityEngine; + +namespace VisualPinball.Unity.VisualScripting +{ + [UnitShortTitle("EM Reset Points")] + [UnitTitle("EM Reset Points")] + [UnitSurtitle("EM")] + [UnitCategory("Visual Pinball/EM")] + public class EMResetPointsUnit : GleUnit + { + [DoNotSerialize] + [PortLabelHidden] + public ControlInput InputTrigger; + + [DoNotSerialize] + public ValueInput duration { get; private set; } + + [DoNotSerialize] + public ValueInput positions { get; private set; } + + [DoNotSerialize] + [PortLabel("Unscaled")] + public ValueInput unscaledTime { get; private set; } + + [DoNotSerialize] + [PortLabelHidden] + public ControlOutput OutputTrigger; + + [DoNotSerialize] + public ControlOutput started; + + [DoNotSerialize] + public ControlOutput stopped; + + [DoNotSerialize] + public ControlOutput pulse { get; private set; } + + [DoNotSerialize] + public ValueInput pointValue { get; private set; } + + [DoNotSerialize] + [PortLabel("Point Value")] + public ValueOutput OutputPointValue { get; private set; } + + private State State => VsGle.TableState; + private static string VARIABLE_EM_SCORE_MOTOR = "EM_SCORE_MOTOR"; + + protected override void Definition() + { + InputTrigger = ControlInputCoroutine(nameof(InputTrigger), Process); + + pointValue = ValueInput(nameof(pointValue), 0); + + positions = ValueInput(nameof(positions), 6); + duration = ValueInput(nameof(duration), 750); + unscaledTime = ValueInput(nameof(unscaledTime), false); + + OutputTrigger = ControlOutput(nameof(OutputTrigger)); + + started = ControlOutput(nameof(started)); + stopped = ControlOutput(nameof(stopped)); + + pulse = ControlOutput(nameof(pulse)); + + OutputPointValue = ValueOutput(nameof(OutputPointValue)); + } + + private IEnumerator Process(Flow flow) + { + if (!AssertVsGle(flow)) { + yield return OutputTrigger; + } + else { + yield return OutputTrigger; + + var running = false; + + if (State.IsDefined(VARIABLE_EM_SCORE_MOTOR)) { + running = State.Get(VARIABLE_EM_SCORE_MOTOR); + } + else { + State.AddProperty(new StateVariable(VARIABLE_EM_SCORE_MOTOR, "", false)); + } + + if (running) { + Debug.Log($"Score motor is already running."); + } + else { + Debug.Log("Starting score motor"); + + State.Set(VARIABLE_EM_SCORE_MOTOR, true); + + yield return started; + + var motorPositions = flow.GetValue(positions); + + var delay = (flow.GetValue(duration) / 1000f) / motorPositions; + var realtime = flow.GetValue(unscaledTime); + + var points = flow.GetValue(pointValue); + + while (points > 0) { + for (int loop = 0; loop < motorPositions; loop++) { + points = AdvancePoints(points); + + Debug.Log($"Pulse {loop + 1} of {motorPositions} - waiting {delay}ms and triggering with {points} points"); + + if (realtime) { + yield return new WaitForSecondsRealtime(delay); + } + else { + yield return new WaitForSeconds(delay); + } + + flow.SetValue(OutputPointValue, points); + + yield return pulse; + } + } + + Debug.Log("Stopping score motor"); + + State.Set(VARIABLE_EM_SCORE_MOTOR, false); + + yield return stopped; + } + } + } + + private static int NumDigits(int n) + { + if (n < 0) + { + n = n == int.MinValue ? int.MaxValue : -n; + } + return n switch + { + < 10 => 1, + < 100 => 2, + < 1000 => 3, + < 10000 => 4, + < 100000 => 5, + < 1000000 => 6, + < 10000000 => 7, + < 100000000 => 8, + < 1000000000 => 9, + _ => 10 + }; + } + + private static int[] DigitArr(int n) + { + var result = new int[NumDigits(n)]; + for (var i = result.Length - 1; i >= 0; i--) { + result[i] = n % 10; + n /= 10; + } + return result; + } + + private static int AdvancePoints(int points) { + var value = 0; + + foreach (var i in DigitArr(points)) { + value = (value * 10) + ((i > 0 && i < 9) ? (i + 1) : 0); + } + + return value; + } + } +} diff --git a/Runtime/Nodes/EM/EMResetPointsUnit.cs.meta b/Runtime/Nodes/EM/EMResetPointsUnit.cs.meta new file mode 100644 index 0000000..dc8aca3 --- /dev/null +++ b/Runtime/Nodes/EM/EMResetPointsUnit.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 37d591d7c9d694b89a42a1ff9ce2545f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: