diff --git a/VRCOSC.Desktop/VRCOSC.Desktop.csproj b/VRCOSC.Desktop/VRCOSC.Desktop.csproj
index af85ae82..8d69ed32 100644
--- a/VRCOSC.Desktop/VRCOSC.Desktop.csproj
+++ b/VRCOSC.Desktop/VRCOSC.Desktop.csproj
@@ -6,12 +6,12 @@
game.ico
app.manifest
0.0.0
- 2022.1203.0
+ 2022.1211.0
VRCOSC
VolcanicArts
VolcanicArts
enable
- 2022.1203.0
+ 2022.1211.0
diff --git a/VRCOSC.Game.Tests/Tests/TimedTaskTests.cs b/VRCOSC.Game.Tests/Tests/TimedTaskTests.cs
new file mode 100644
index 00000000..438aa351
--- /dev/null
+++ b/VRCOSC.Game.Tests/Tests/TimedTaskTests.cs
@@ -0,0 +1,115 @@
+// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License.
+// See the LICENSE file in the repository root for full license text.
+
+using System.Threading;
+using System.Threading.Tasks;
+using NUnit.Framework;
+using VRCOSC.Game.Modules;
+
+namespace VRCOSC.Game.Tests.Tests;
+
+public class TimedTaskTests
+{
+ [Test]
+ public void TestStart()
+ {
+ var actionInvoked = false;
+
+ Task action()
+ {
+ actionInvoked = true;
+ return Task.CompletedTask;
+ }
+
+ var task = new TimedTask(action, 100);
+ task.Start().Wait();
+
+ Thread.Sleep(500);
+
+ Assert.IsTrue(actionInvoked);
+ }
+
+ [Test]
+ public void TestStop()
+ {
+ var actionInvoked = false;
+
+ Task action()
+ {
+ actionInvoked = true;
+ return Task.CompletedTask;
+ }
+
+ var task = new TimedTask(action, 100);
+ task.Start().Wait();
+
+ Thread.Sleep(500);
+ Assert.IsTrue(actionInvoked);
+
+ task.Stop().Wait();
+
+ actionInvoked = false;
+
+ Thread.Sleep(500);
+ Assert.IsFalse(actionInvoked);
+ }
+
+ [Test]
+ public void TestExecuteOnceImmediately()
+ {
+ var actionInvoked = false;
+
+ Task action()
+ {
+ actionInvoked = true;
+ return Task.CompletedTask;
+ }
+
+ var task = new TimedTask(action, 100, true);
+ task.Start().Wait();
+
+ Assert.IsTrue(actionInvoked);
+
+ Thread.Sleep(500);
+ Assert.IsTrue(actionInvoked);
+ }
+
+ [Test]
+ public void TestStartMultipleTimes()
+ {
+ var actionInvoked = false;
+
+ Task action()
+ {
+ actionInvoked = true;
+ return Task.CompletedTask;
+ }
+
+ var task = new TimedTask(action, 100);
+ task.Start().Wait();
+ task.Start().Wait();
+
+ Thread.Sleep(500);
+
+ Assert.IsTrue(actionInvoked);
+ }
+
+ [Test]
+ public void TestStopWithoutStart()
+ {
+ var actionInvoked = false;
+
+ Task action()
+ {
+ actionInvoked = true;
+ return Task.CompletedTask;
+ }
+
+ var task = new TimedTask(action, 100);
+
+ task.Stop().Wait();
+
+ Thread.Sleep(500);
+ Assert.IsFalse(actionInvoked);
+ }
+}
diff --git a/VRCOSC.Game.Tests/VRCOSCTestBrowser.cs b/VRCOSC.Game.Tests/VRCOSCTestBrowser.cs
index f381bb68..a045934d 100644
--- a/VRCOSC.Game.Tests/VRCOSCTestBrowser.cs
+++ b/VRCOSC.Game.Tests/VRCOSCTestBrowser.cs
@@ -8,7 +8,7 @@
namespace VRCOSC.Game.Tests;
-public class VRCOSCTestBrowser : VRCOSCGameBase
+public partial class VRCOSCTestBrowser : VRCOSCGameBase
{
protected override void LoadComplete()
{
diff --git a/VRCOSC.Game.Tests/Visual/TestNotifications.cs b/VRCOSC.Game.Tests/Visual/TestNotifications.cs
deleted file mode 100644
index fc04506f..00000000
--- a/VRCOSC.Game.Tests/Visual/TestNotifications.cs
+++ /dev/null
@@ -1,83 +0,0 @@
-// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License.
-// See the LICENSE file in the repository root for full license text.
-
-using NUnit.Framework;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Shapes;
-using osu.Framework.Graphics.Sprites;
-using osu.Framework.Utils;
-using VRCOSC.Game.Graphics;
-using VRCOSC.Game.Graphics.Notifications;
-
-namespace VRCOSC.Game.Tests.Visual;
-
-public class TestNotifications : VRCOSCTestScene
-{
- private NotificationContainer notifications = null!;
-
- [SetUp]
- public void SetUp()
- {
- Add(new Box
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- RelativeSizeAxes = Axes.Both,
- Colour = Colour4.Gray
- });
- Add(notifications = new NotificationContainer());
- }
-
- [Test]
- public void TestNotificationContainer()
- {
- AddStep("show", () => notifications.Show());
- AddUntilStep("ensure hidden", () => Precision.AlmostEquals(notifications.Position.X, 1));
- AddStep("notify", notifyBasic);
- AddUntilStep("ensure shown", () => Precision.AlmostEquals(notifications.Position.X, 0));
- AddStep("hide", () => notifications.Hide());
- AddUntilStep("ensure hidden", () => Precision.AlmostEquals(notifications.Position.X, 1));
- }
-
- [Test]
- public void TestBasicNotification()
- {
- AddStep("notify", notifyBasic);
- }
-
- [Test]
- public void TestProgressNotification()
- {
- ProgressNotification? progress = null;
- AddStep("notify", () => progress = notifyProgress());
- AddSliderStep("set progress", 0f, 1f, 0f, p =>
- {
- if (progress is not null)
- progress.Progress = p;
- });
- }
-
- private void notifyBasic()
- {
- notifications.Notify(new BasicNotification
- {
- Title = "Basic Title",
- Description = "This is basic",
- Colour = VRCOSCColour.GreenLight,
- Icon = FontAwesome.Solid.Check
- });
- }
-
- private ProgressNotification notifyProgress()
- {
- var progress = new ProgressNotification
- {
- Title = "Progress Title",
- Description = "This is progressing",
- Colour = VRCOSCColour.YellowDark,
- Icon = FontAwesome.Solid.ExclamationTriangle
- };
- notifications.Notify(progress);
- return progress;
- }
-}
diff --git a/VRCOSC.Game.Tests/Visual/VRCOSCGame.cs b/VRCOSC.Game.Tests/Visual/VRCOSCGame.cs
index 2416fc87..28a29095 100644
--- a/VRCOSC.Game.Tests/Visual/VRCOSCGame.cs
+++ b/VRCOSC.Game.Tests/Visual/VRCOSCGame.cs
@@ -6,7 +6,7 @@
namespace VRCOSC.Game.Tests.Visual;
-public class VRCOSCGame : VRCOSCTestScene
+public partial class VRCOSCGame : VRCOSCTestScene
{
[SetUp]
public void SetUp()
diff --git a/VRCOSC.Game.Tests/Visual/VRCOSCTestScene.cs b/VRCOSC.Game.Tests/Visual/VRCOSCTestScene.cs
index eb456082..f955428c 100644
--- a/VRCOSC.Game.Tests/Visual/VRCOSCTestScene.cs
+++ b/VRCOSC.Game.Tests/Visual/VRCOSCTestScene.cs
@@ -5,14 +5,14 @@
namespace VRCOSC.Game.Tests.Visual;
-public class VRCOSCTestScene : TestScene
+public partial class VRCOSCTestScene : TestScene
{
protected override ITestSceneTestRunner CreateRunner()
{
return new VRCOSCTestSceneTestRunner();
}
- private class VRCOSCTestSceneTestRunner : VRCOSCGameBase, ITestSceneTestRunner
+ private partial class VRCOSCTestSceneTestRunner : VRCOSCGameBase, ITestSceneTestRunner
{
private TestSceneTestRunner.TestRunner runner = null!;
diff --git a/VRCOSC.Game/Config/VRCOSCConfigManager.cs b/VRCOSC.Game/Config/VRCOSCConfigManager.cs
index 6e714af5..5d3be613 100644
--- a/VRCOSC.Game/Config/VRCOSCConfigManager.cs
+++ b/VRCOSC.Game/Config/VRCOSCConfigManager.cs
@@ -4,6 +4,7 @@
using osu.Framework.Configuration;
using osu.Framework.Platform;
using VRCOSC.Game.Graphics.Settings;
+using VRCOSC.Game.Graphics.Themes;
namespace VRCOSC.Game.Config;
@@ -25,6 +26,8 @@ protected override void InitialiseDefaults()
SetDefault(VRCOSCSetting.ReceivePort, 9001);
SetDefault(VRCOSCSetting.UpdateMode, UpdateMode.Auto);
SetDefault(VRCOSCSetting.UpdateRepo, @"https://github.com/VolcanicArts/VRCOSC");
+ SetDefault(VRCOSCSetting.Theme, ColourTheme.Dark);
+ SetDefault(VRCOSCSetting.ChatBoxTimeSpan, 1500);
}
}
@@ -36,5 +39,7 @@ public enum VRCOSCSetting
SendPort,
ReceivePort,
UpdateMode,
- UpdateRepo
+ UpdateRepo,
+ Theme,
+ ChatBoxTimeSpan
}
diff --git a/VRCOSC.Game/Constants.cs b/VRCOSC.Game/Constants.cs
new file mode 100644
index 00000000..4b24f404
--- /dev/null
+++ b/VRCOSC.Game/Constants.cs
@@ -0,0 +1,17 @@
+// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License.
+// See the LICENSE file in the repository root for full license text.
+
+// ReSharper disable MemberCanBePrivate.Global
+
+namespace VRCOSC.Game;
+
+public static class Constants
+{
+ public const string OSC_ADDRESS_AVATAR_PARAMETERS_PREFIX = @"/avatar/parameters/";
+ public const string OSC_ADDRESS_AVATAR_CHANGE = @"/avatar/change";
+ public const string OSC_ADDRESS_CHATBOX_INPUT = @"/chatbox/input";
+ public const string OSC_ADDRESS_CHATBOX_TYPING = @"/chatbox/typing";
+
+ public const int OSC_UPDATE_FREQUENCY = 20;
+ public const int OSC_UPDATE_DELTA = (int)(1f / OSC_UPDATE_FREQUENCY * 1000f);
+}
diff --git a/VRCOSC.Game/Modules/Util/TypeExtensions.cs b/VRCOSC.Game/Extensions.cs
similarity index 81%
rename from VRCOSC.Game/Modules/Util/TypeExtensions.cs
rename to VRCOSC.Game/Extensions.cs
index e47b7d9d..105a2767 100644
--- a/VRCOSC.Game/Modules/Util/TypeExtensions.cs
+++ b/VRCOSC.Game/Extensions.cs
@@ -3,18 +3,20 @@
using System;
-namespace VRCOSC.Game.Modules.Util;
+namespace VRCOSC.Game;
+
+public static class EnumExtensions
+{
+ public static string ToLookup(this Enum key) => key.ToString().ToLowerInvariant();
+}
public static class TypeExtensions
{
public static string ToReadableName(this Type type)
{
- if (type.IsSubclassOf(typeof(Enum)))
- return "Enum";
-
- var typeCode = Type.GetTypeCode(type);
+ if (type.IsSubclassOf(typeof(Enum))) return "Enum";
- return typeCode switch
+ return Type.GetTypeCode(type) switch
{
TypeCode.Empty => "Null",
TypeCode.Object => "Object",
diff --git a/VRCOSC.Game/Graphics/About/AboutScreen.cs b/VRCOSC.Game/Graphics/About/AboutScreen.cs
index 34d56f18..6b13b104 100644
--- a/VRCOSC.Game/Graphics/About/AboutScreen.cs
+++ b/VRCOSC.Game/Graphics/About/AboutScreen.cs
@@ -11,6 +11,7 @@
using osu.Framework.Platform;
using osuTK;
using VRCOSC.Game.Config;
+using VRCOSC.Game.Graphics.Themes;
using VRCOSC.Game.Graphics.UI.Button;
namespace VRCOSC.Game.Graphics.About;
@@ -36,7 +37,7 @@ public AboutScreen()
new Box
{
RelativeSizeAxes = Axes.Both,
- Colour = VRCOSCColour.Gray5
+ Colour = ThemeManager.Current[ThemeAttribute.Light]
},
new Container
{
@@ -53,7 +54,7 @@ public AboutScreen()
Direction = FillDirection.Full,
Spacing = new Vector2(5)
},
- text = new TextFlowContainer
+ text = new TextFlowContainer(t => t.Colour = ThemeManager.Current[ThemeAttribute.Text])
{
RelativeSizeAxes = Axes.Both,
TextAnchor = Anchor.BottomCentre
diff --git a/VRCOSC.Game/Graphics/LineSeparator.cs b/VRCOSC.Game/Graphics/LineSeparator.cs
index d41dcede..3e46ca1c 100644
--- a/VRCOSC.Game/Graphics/LineSeparator.cs
+++ b/VRCOSC.Game/Graphics/LineSeparator.cs
@@ -5,6 +5,7 @@
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osuTK;
+using VRCOSC.Game.Graphics.Themes;
namespace VRCOSC.Game.Graphics;
@@ -20,7 +21,7 @@ public LineSeparator()
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
- Colour = VRCOSCColour.Gray2
+ Colour = ThemeManager.Current[ThemeAttribute.Darker]
};
}
}
diff --git a/VRCOSC.Game/Graphics/MainContent.cs b/VRCOSC.Game/Graphics/MainContent.cs
index b1e29dcf..297ff548 100644
--- a/VRCOSC.Game/Graphics/MainContent.cs
+++ b/VRCOSC.Game/Graphics/MainContent.cs
@@ -33,7 +33,7 @@ private void load()
ColumnDimensions = new[]
{
new Dimension(GridSizeMode.Absolute, 75),
- new Dimension(),
+ new Dimension()
},
Content = new[]
{
diff --git a/VRCOSC.Game/Graphics/ModuleEditing/AttributeFlow.cs b/VRCOSC.Game/Graphics/ModuleEditing/AttributeFlow.cs
index b4167334..7f621435 100644
--- a/VRCOSC.Game/Graphics/ModuleEditing/AttributeFlow.cs
+++ b/VRCOSC.Game/Graphics/ModuleEditing/AttributeFlow.cs
@@ -54,10 +54,20 @@ protected override void LoadComplete()
{
Clear();
AttributesList.ForEach(attributeData => Add(generateCard(attributeData)));
+ checkShouldDisplay();
}, true);
}
- private static AttributeCard generateCard(ModuleAttribute attributeData)
+ private void checkShouldDisplay()
+ {
+ this.ForEach(card =>
+ {
+ card.Enable = card.AttributeData.Enabled;
+ card.FadeTo(card.AttributeData.Enabled ? 1 : 0.25f, 250, Easing.OutQuad);
+ });
+ }
+
+ private AttributeCard generateCard(ModuleAttribute attributeData)
{
return attributeData switch
{
@@ -67,7 +77,7 @@ private static AttributeCard generateCard(ModuleAttribute attributeData)
};
}
- private static AttributeCard generateListCard(ModuleAttributeList attributeData)
+ private AttributeCard generateListCard(ModuleAttributeList attributeData)
{
if (attributeData.Type == typeof(int))
return new IntTextAttributeCardList(attributeData);
@@ -78,12 +88,13 @@ private static AttributeCard generateListCard(ModuleAttributeList attributeData)
throw new ArgumentOutOfRangeException(nameof(attributeData), "Cannot generate lists for non-text values");
}
- private static AttributeCard generateSingleCard(ModuleAttributeSingle attributeData)
+ private AttributeCard generateSingleCard(ModuleAttributeSingle attributeData)
{
var value = attributeData.Attribute.Value;
if (value.GetType().IsSubclassOf(typeof(Enum)))
{
+ attributeData.Attribute.BindValueChanged(_ => checkShouldDisplay());
Type instanceType = typeof(DropdownAttributeCard<>).MakeGenericType(value.GetType());
return (Activator.CreateInstance(instanceType, attributeData) as AttributeCard)!;
}
@@ -123,6 +134,7 @@ private static AttributeCard generateSingleCard(ModuleAttributeSingle attributeD
return new IntTextAttributeCard(attributeData);
case bool:
+ attributeData.Attribute.BindValueChanged(_ => checkShouldDisplay());
return new ToggleAttributeCard(attributeData);
default:
diff --git a/VRCOSC.Game/Graphics/ModuleEditing/Attributes/AttributeCard.cs b/VRCOSC.Game/Graphics/ModuleEditing/Attributes/AttributeCard.cs
index 4b1095b1..bf9abdf5 100644
--- a/VRCOSC.Game/Graphics/ModuleEditing/Attributes/AttributeCard.cs
+++ b/VRCOSC.Game/Graphics/ModuleEditing/Attributes/AttributeCard.cs
@@ -2,12 +2,12 @@
// See the LICENSE file in the repository root for full license text.
using osu.Framework.Allocation;
-using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osuTK;
+using VRCOSC.Game.Graphics.Themes;
using VRCOSC.Game.Graphics.UI;
using VRCOSC.Game.Graphics.UI.Button;
using VRCOSC.Game.Modules;
@@ -20,11 +20,14 @@ public abstract partial class AttributeCard : Container
protected FillFlowContainer ContentFlow = null!;
protected FillFlowContainer LayoutFlow = null!;
- private readonly ModuleAttribute attributeData;
+ public readonly ModuleAttribute AttributeData;
+ public bool Enable { get; set; } = true;
+
+ protected override bool ShouldBeConsideredForInput(Drawable child) => Enable;
protected AttributeCard(ModuleAttribute attributeData)
{
- this.attributeData = attributeData;
+ AttributeData = attributeData;
}
[BackgroundDependencyLoader]
@@ -54,8 +57,8 @@ private void load()
IconPadding = 4,
CornerRadius = 10,
BorderThickness = 2,
- BorderColour = VRCOSCColour.Gray0,
- BackgroundColour = VRCOSCColour.BlueDark.Darken(0.25f),
+ BorderColour = ThemeManager.Current[ThemeAttribute.Border],
+ BackgroundColour = ThemeManager.Current[ThemeAttribute.Action],
Icon = FontAwesome.Solid.Undo,
IconShadow = true
}
@@ -68,7 +71,7 @@ private void load()
AutoSizeAxes = Axes.Y,
Masking = true,
CornerRadius = 10,
- BorderColour = VRCOSCColour.Gray0,
+ BorderColour = ThemeManager.Current[ThemeAttribute.Border],
BorderThickness = 2,
Children = new Drawable[]
{
@@ -77,7 +80,7 @@ private void load()
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
- Colour = VRCOSCColour.Gray2
+ Colour = ThemeManager.Current[ThemeAttribute.Darker]
},
LayoutFlow = new FillFlowContainer
{
@@ -105,27 +108,28 @@ private void load()
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
- Spacing = new Vector2(0, 10),
+ Spacing = new Vector2(0, 10)
}
}
}
}
}
};
- textFlow.AddText(attributeData.Metadata.DisplayName, t =>
+ textFlow.AddText(AttributeData.Metadata.DisplayName, t =>
{
t.Font = FrameworkFont.Regular.With(size: 30);
+ t.Colour = ThemeManager.Current[ThemeAttribute.Text];
});
- textFlow.AddParagraph(attributeData.Metadata.Description, t =>
+ textFlow.AddParagraph(AttributeData.Metadata.Description, t =>
{
t.Font = FrameworkFont.Regular.With(size: 20);
- t.Colour = VRCOSCColour.Gray9;
+ t.Colour = ThemeManager.Current[ThemeAttribute.SubText];
});
}
protected virtual void SetDefault()
{
- attributeData.SetDefault();
+ AttributeData.SetDefault();
}
protected void UpdateResetToDefault(bool show)
@@ -145,7 +149,7 @@ protected static VRCOSCTextBox CreateTextBox()
Height = 40,
Masking = true,
CornerRadius = 5,
- BorderColour = VRCOSCColour.Gray0,
+ BorderColour = ThemeManager.Current[ThemeAttribute.Border],
BorderThickness = 2
};
}
diff --git a/VRCOSC.Game/Graphics/ModuleEditing/Attributes/AttributeCardList.cs b/VRCOSC.Game/Graphics/ModuleEditing/Attributes/AttributeCardList.cs
index 9aeb9cf4..efc2d078 100644
--- a/VRCOSC.Game/Graphics/ModuleEditing/Attributes/AttributeCardList.cs
+++ b/VRCOSC.Game/Graphics/ModuleEditing/Attributes/AttributeCardList.cs
@@ -7,6 +7,7 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
+using VRCOSC.Game.Graphics.Themes;
using VRCOSC.Game.Graphics.UI.Button;
using VRCOSC.Game.Modules;
@@ -14,7 +15,7 @@ namespace VRCOSC.Game.Graphics.ModuleEditing.Attributes;
public abstract partial class AttributeCardList : AttributeCard
{
- protected ModuleAttributeList AttributeData;
+ protected new ModuleAttributeList AttributeData;
protected AttributeCardList(ModuleAttributeList attributeData)
: base(attributeData)
@@ -40,7 +41,7 @@ private void load()
RelativeSizeAxes = Axes.Both,
Width = 0.8f,
Icon = FontAwesome.Solid.Plus,
- BackgroundColour = VRCOSCColour.Gray3,
+ BackgroundColour = ThemeManager.Current[ThemeAttribute.Dark],
CornerRadius = 5,
ShouldAnimate = false,
Action = () => AddItem(GetDefaultItem())
@@ -62,11 +63,11 @@ protected void AddContent(Drawable content)
ColumnDimensions = new[]
{
new Dimension(),
- new Dimension(GridSizeMode.Absolute, 36),
+ new Dimension(GridSizeMode.Absolute, 36)
},
Content = new[]
{
- new Drawable[]
+ new[]
{
content,
new Container
@@ -77,7 +78,7 @@ protected void AddContent(Drawable content)
Padding = new MarginPadding
{
Vertical = 4,
- Left = 4,
+ Left = 4
},
Child = removeButton = new IconButton
{
@@ -86,7 +87,7 @@ protected void AddContent(Drawable content)
RelativeSizeAxes = Axes.Both,
Icon = FontAwesome.Solid.Get(0xf00d),
CornerRadius = 5,
- BackgroundColour = VRCOSCColour.Gray4
+ BackgroundColour = ThemeManager.Current[ThemeAttribute.Mid]
}
}
}
diff --git a/VRCOSC.Game/Graphics/ModuleEditing/Attributes/AttributeCardSingle.cs b/VRCOSC.Game/Graphics/ModuleEditing/Attributes/AttributeCardSingle.cs
index d3f4ebe8..a4ae5308 100644
--- a/VRCOSC.Game/Graphics/ModuleEditing/Attributes/AttributeCardSingle.cs
+++ b/VRCOSC.Game/Graphics/ModuleEditing/Attributes/AttributeCardSingle.cs
@@ -1,6 +1,7 @@
// Copyright (c) VolcanicArts. Licensed under the GPL-3.0 License.
// See the LICENSE file in the repository root for full license text.
+using System;
using osu.Framework.Bindables;
using VRCOSC.Game.Modules;
@@ -8,17 +9,33 @@ namespace VRCOSC.Game.Graphics.ModuleEditing.Attributes;
public abstract partial class AttributeCardSingle : AttributeCard
{
- protected readonly ModuleAttributeSingle AttributeData;
+ protected new readonly ModuleAttributeSingle AttributeData;
+ protected virtual bool ShouldLimitSaves => false;
+
+ private DateTimeOffset lastUpdateTime;
+ private readonly TimeSpan timeUntilSave = TimeSpan.FromSeconds(0.5f);
+
+ private object lastValue = null!;
protected AttributeCardSingle(ModuleAttributeSingle attributeData)
: base(attributeData)
{
AttributeData = attributeData;
+ lastUpdateTime = DateTimeOffset.Now;
}
protected override void LoadComplete()
{
AttributeData.Attribute.BindValueChanged(e => Schedule(performAttributeUpdate, e), true);
+ lastValue = AttributeData.Attribute.Value;
+ }
+
+ protected override void Update()
+ {
+ if (lastUpdateTime + timeUntilSave < DateTimeOffset.Now && !AttributeData.Attribute.Value.Equals(lastValue))
+ {
+ AttributeData.Attribute.Value = lastValue;
+ }
}
private void performAttributeUpdate(ValueChangedEvent