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: