Add modding support

This commit is contained in:
Stone_Red
2023-10-16 14:19:46 +02:00
parent 7fa48fc050
commit 6175d29b3d
20 changed files with 151 additions and 46 deletions
@@ -2,7 +2,7 @@
using StoneRed.LogicSimulator.Api.Utilities;
namespace StoneRed.LogicSimulator.Api.Interfaces;
namespace StoneRed.LogicSimulator.Api;
#pragma warning disable S112 // General exceptions should never be thrown
internal abstract class LogicGate
@@ -13,12 +13,13 @@
</VerticalStackPanel>
<Panel GridColumn="2" TransformOrigin="1, 0" Background="#FFA500FF" Id="sidePanel">
<VerticalStackPanel Id="settings">
<Label Text="Frequency:" />
<Label Text="Frequency:" TextColor="#000000FF" />
<SpinButton Maximum="1E+09" Minimum="1" HorizontalAlignment="Stretch" Value="100" Integer="True" Id="frequency" />
<CheckBox Text="High Performance" Id="highPerformance" />
<Label Text="Native components:" Wrap="True" />
<CheckBox Text="High Performance" TextColor="#000000FF" Id="highPerformance" />
<CheckBox Text="Hide cables behind gates" TextColor="#000000FF" Id="hideCablesBehindGates" />
<Label Text="Native components:" Wrap="True" TextColor="#000000FF" />
<ListBox HorizontalAlignment="Stretch" Id="nativeComponents" />
<Label Text="Custom components:" Wrap="True" />
<Label Text="Custom components:" Wrap="True" TextColor="#000000FF" />
<ListBox HorizontalAlignment="Stretch">
<ListItem Text="This is a test 2" />
</ListBox>
+30 -6
View File
@@ -20,11 +20,6 @@ internal static class Paths
return srlsAppDataPath;
}
public static string GetAppDataPath(string fileName)
{
return Path.Combine(GetAppDataPath(), fileName);
}
public static string GetAppDataPath(params string[] paths)
{
return Path.Combine(GetAppDataPath(), Path.Combine(paths));
@@ -52,7 +47,7 @@ internal static class Paths
public static string GetWorldSavesPath()
{
string savesPath = Path.Combine(GetAppDataPath(), "Saves");
string savesPath = GetAppDataPath("Saves");
if (!Directory.Exists(savesPath))
{
@@ -78,4 +73,33 @@ internal static class Paths
{
return Path.Combine(GetWorldSaveDirectoryPath(saveName), saveName + ".srls");
}
public static string GetModsPath()
{
string modsPath = GetAppDataPath("Mods");
if (!Directory.Exists(modsPath))
{
_ = Directory.CreateDirectory(modsPath);
}
return modsPath;
}
public static string GetModDirectoryPath(string modName)
{
string modPath = Path.Combine(GetWorldSavesPath(), modName);
if (!Directory.Exists(modPath))
{
_ = Directory.CreateDirectory(modPath);
}
return modPath;
}
public static string GetModFilePath(string modName)
{
return Path.Combine(GetWorldSaveDirectoryPath(modName), modName + ".dll");
}
}
@@ -1,4 +1,4 @@
using StoneRed.LogicSimulator.Api.Interfaces;
using StoneRed.LogicSimulator.Api;
using System;
using System.Collections.Concurrent;
@@ -0,0 +1,18 @@
using StoneRed.LogicSimulator.Api;
using StoneRed.LogicSimulator.Api.Attributes;
namespace StoneRed.LogicSimulator.Simulation.LogicGates;
[LogicGateName("And Gate")]
[LogicGateDescription("A and gate is a gate that returns true if both inputs are true.")]
internal class AndGate : LogicGate
{
public override int InputCount { get; set; } = 2;
public override int OutputCount { get; set; } = 1;
protected override void Execute()
{
SetOutputBit(GetInputBit(0) == 1 && GetInputBit(1) == 1 ? 1 : 0, 0);
}
}
@@ -1,7 +1,7 @@
using Microsoft.Xna.Framework;
using MonoGame.Extended.Input;
using StoneRed.LogicSimulator.Api;
using StoneRed.LogicSimulator.Api.Attributes;
using StoneRed.LogicSimulator.Api.Interfaces;
@@ -1,5 +1,5 @@
using MonoGame.Extended.Input;
using StoneRed.LogicSimulator.Api;
using StoneRed.LogicSimulator.Api.Attributes;
using StoneRed.LogicSimulator.Api.Interfaces;
@@ -1,5 +1,5 @@
using Microsoft.Xna.Framework;
using StoneRed.LogicSimulator.Api;
using StoneRed.LogicSimulator.Api.Attributes;
using StoneRed.LogicSimulator.Api.Interfaces;
@@ -1,5 +1,5 @@
using StoneRed.LogicSimulator.Api.Attributes;
using StoneRed.LogicSimulator.Api.Interfaces;
using StoneRed.LogicSimulator.Api;
using StoneRed.LogicSimulator.Api.Attributes;
namespace StoneRed.LogicSimulator.Simulation.LogicGates;
@@ -0,0 +1,18 @@
using StoneRed.LogicSimulator.Api;
using StoneRed.LogicSimulator.Api.Attributes;
namespace StoneRed.LogicSimulator.Simulation.LogicGates;
[LogicGateName("Or Gate")]
[LogicGateDescription("A or gate is a gate that returns true if at least one of the inputs is true.")]
internal class OrGate : LogicGate
{
public override int InputCount { get; set; } = 1;
public override int OutputCount { get; set; } = 1;
protected override void Execute()
{
SetOutputBit(GetInputBit(0), 0);
}
}
@@ -1,5 +1,5 @@
using StoneRed.LogicSimulator.Api.Attributes;
using StoneRed.LogicSimulator.Api.Interfaces;
using StoneRed.LogicSimulator.Api;
using StoneRed.LogicSimulator.Api.Attributes;
namespace StoneRed.LogicSimulator.Simulation.LogicGates;
@@ -2,6 +2,7 @@
using MonoGame.Extended.Input;
using StoneRed.LogicSimulator.Api;
using StoneRed.LogicSimulator.Api.Attributes;
using StoneRed.LogicSimulator.Api.Interfaces;
@@ -3,18 +3,20 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using StoneRed.LogicSimulator.Api;
using StoneRed.LogicSimulator.Api.Attributes;
using StoneRed.LogicSimulator.Api.Interfaces;
using System.Linq;
namespace StoneRed.LogicSimulator.Simulation.LogicGates;
[LogicGateName("Test")]
internal class TestLogicGate : LogicGate, Api.Interfaces.IDrawable
{
private readonly Color[] colors = new Color[9];
private Color[] colors = new Color[15 * 15];
private Texture2D texture = null!;
public override int OutputCount { get; set; } = 0;
public override int InputCount { get; set; } = 9;
public override int InputCount { get; set; } = 7;
public Texture2D Texture
{
@@ -27,14 +29,39 @@ internal class TestLogicGate : LogicGate, Api.Interfaces.IDrawable
protected internal override void Initialize()
{
texture = CreateTexture(3, 3);
colors = Enumerable.Repeat(Color.Gray, 15 * 15).ToArray();
texture = CreateTexture(15, 15);
}
protected override void Execute()
{
for (int i = 0; i < InputCount; i++)
// A
SetRangeWithSpacing(20, 24, 1, GetInputBit(0) == 1 ? Color.Red : Color.Gray);
// B
SetRangeWithSpacing(40, 100, 15, GetInputBit(1) == 1 ? Color.Red : Color.Gray);
// C
SetRangeWithSpacing(130, 190, 15, GetInputBit(2) == 1 ? Color.Red : Color.Gray);
// D
SetRangeWithSpacing(200, 204, 1, GetInputBit(3) == 1 ? Color.Red : Color.Gray);
// E
SetRangeWithSpacing(124, 184, 15, GetInputBit(4) == 1 ? Color.Red : Color.Gray);
// F
SetRangeWithSpacing(34, 94, 15, GetInputBit(5) == 1 ? Color.Red : Color.Gray);
// G
SetRangeWithSpacing(110, 114, 1, GetInputBit(6) == 1 ? Color.Red : Color.Gray);
}
private void SetRangeWithSpacing(int start, int end, int spacing, Color value)
{
for (int i = start; i <= end; i += spacing)
{
colors[i] = GetInputBit(i) == 1 ? Color.Red : Color.Gray;
colors[i] = value;
}
}
}
@@ -9,6 +9,7 @@ using MonoGame.Extended.Input;
using Myra.Graphics2D.UI;
using StoneRed.LogicSimulator.Api;
using StoneRed.LogicSimulator.Api.Interfaces;
using StoneRed.LogicSimulator.Simulation;
using StoneRed.LogicSimulator.UserInterface.Windows;
@@ -33,8 +34,9 @@ internal class WorldScreen : SrlsScreen<Grid>
private Label fpsLabel = null!;
private Label upsLabel = null!;
private Label positionLabel = null!;
private SpinButton frequency = null!;
private CheckBox highPerformance = null!;
private SpinButton frequencySpinButton = null!;
private CheckBox highPerformanceCheckBox = null!;
private CheckBox hideCablesBehindGatesCheckBox = null!;
private ListBox nativeComponentsListBox = null!;
private float fps;
@@ -69,8 +71,9 @@ internal class WorldScreen : SrlsScreen<Grid>
positionLabel = infoPanel.FindChildById<Label>("position");
VerticalStackPanel settingsPanel = Root.FindChildById<VerticalStackPanel>("settings");
frequency = settingsPanel.FindChildById<SpinButton>("frequency");
highPerformance = settingsPanel.FindChildById<CheckBox>("highPerformance");
frequencySpinButton = settingsPanel.FindChildById<SpinButton>("frequency");
highPerformanceCheckBox = settingsPanel.FindChildById<CheckBox>("highPerformance");
hideCablesBehindGatesCheckBox = settingsPanel.FindChildById<CheckBox>("hideCablesBehindGates");
nativeComponentsListBox = Root.FindChildById<ListBox>("nativeComponents");
@@ -139,7 +142,7 @@ internal class WorldScreen : SrlsScreen<Grid>
foreach (LogicGateConnection connection in logicGate.LogicGateConnections)
{
Color lineColor = logicGate.GetCachedOutputBit(connection.OutputIndex) == 1 ? Color.Red : Color.LightBlue;
srls.SpriteBatch.DrawLine((logicGate.WorldData.Position * srls.Scale) + (logicGateSize / 2 * srls.Scale), (connection.LogicGate.WorldData.Position * srls.Scale) + (logicGateSize / 2 * srls.Scale), lineColor, 5 * srls.Scale, 0.1f);
srls.SpriteBatch.DrawLine((logicGate.WorldData.Position * srls.Scale) + (logicGateSize / 2 * srls.Scale), (connection.LogicGate.WorldData.Position * srls.Scale) + (logicGateSize / 2 * srls.Scale), lineColor, 5 * srls.Scale, hideCablesBehindGatesCheckBox.IsChecked ? 0.5f : 0.1f);
}
}
@@ -181,8 +184,8 @@ internal class WorldScreen : SrlsScreen<Grid>
upsLabel.Text = $"FRQ: {FrequencyCalculator.CalculateFrequency(simulator.ActualTicksPerSecond)}/{FrequencyCalculator.CalculateFrequency(simulator.TargetTicksPerSecond)}{(simulator.HighPerformanceClock ? "*" : string.Empty)} {(simulator.ClockCalibrating && simulator.HighPerformanceClock ? $"[Calibrating... {calibrationPercentage}%]" : string.Empty)}";
positionLabel.Text = $"X/Y: {(long)Math.Round(camera.Position.X / logicGateSize.X / srls.Scale)}/{(long)Math.Round(camera.Position.Y / logicGateSize.Y / srls.Scale)}";
simulator.TargetTicksPerSecond = (int)frequency.Value.GetValueOrDefault();
simulator.HighPerformanceClock = highPerformance.IsChecked;
simulator.TargetTicksPerSecond = (int)frequencySpinButton.Value.GetValueOrDefault();
simulator.HighPerformanceClock = highPerformanceCheckBox.IsChecked;
fpsLabel.Font = srls.FontSystem.GetFont(10 * srls.Scale);
upsLabel.Font = srls.FontSystem.GetFont(10 * srls.Scale);
@@ -22,9 +22,9 @@ internal class LoadWorldWindow : SrlsWindow
loadButton.Click += LoadButton_Clicked;
foreach (string directories in Directory.GetDirectories(Paths.GetWorldSavesPath()))
foreach (string directory in Directory.GetDirectories(Paths.GetWorldSavesPath()))
{
string saveName = Path.GetFileName(directories) ?? string.Empty;
string saveName = Path.GetFileName(directory) ?? string.Empty;
savesListBox.Items.Add(new(saveName));
}
@@ -31,9 +31,9 @@ internal class SaveWorldWindow : SrlsWindow
newSaveButton.Click += NewSaveButton_Click;
int index = 0;
foreach (string directories in Directory.GetDirectories(Paths.GetWorldSavesPath()))
foreach (string directory in Directory.GetDirectories(Paths.GetWorldSavesPath()))
{
string saveName = Path.GetFileName(directories) ?? string.Empty;
string saveName = Path.GetFileName(directory) ?? string.Empty;
savesListBox.Items.Add(new(saveName));
if (saveName == worldData.SaveName)
@@ -1,12 +1,15 @@
using Microsoft.Xna.Framework.Graphics;
using StoneRed.LogicSimulator.Api;
using StoneRed.LogicSimulator.Api.Attributes;
using StoneRed.LogicSimulator.Api.Interfaces;
using StoneRed.LogicSimulator.Misc;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Reflection;
namespace StoneRed.LogicSimulator.Utilities;
@@ -41,9 +44,16 @@ internal class LogicGatesManager
public void LoadLogicGates()
{
IEnumerable<Type> logicGateTypes = GetType()
.Assembly.GetTypes()
.Where(t => t.IsSubclassOf(typeof(LogicGate)) && !t.IsAbstract);
IEnumerable<Type> logicGateTypes = GetLogicGateTypesFromAssembly(GetType().Assembly);
foreach (string directory in Directory.GetDirectories(Paths.GetModsPath()))
{
string modName = Path.GetFileName(directory) ?? string.Empty;
byte[] assemblyBytes = File.ReadAllBytes(Paths.GetModFilePath(modName));
Assembly assembly = Assembly.Load(assemblyBytes);
logicGateTypes = logicGateTypes.Concat(GetLogicGateTypesFromAssembly(assembly));
}
foreach (Type type in logicGateTypes)
{
@@ -92,6 +102,12 @@ internal class LogicGatesManager
logicGate.Initialize();
return true;
}
private IEnumerable<Type> GetLogicGateTypesFromAssembly(Assembly assembly)
{
return assembly.GetTypes()
.Where(t => t.IsSubclassOf(typeof(LogicGate)) && !t.IsAbstract);
}
}
internal class LogicGateInfo
@@ -1,5 +1,4 @@
using StoneRed.LogicSimulator.Api.Interfaces;
using StoneRed.LogicSimulator.Api;
using System.Collections.Generic;
namespace StoneRed.LogicSimulator.WorldSaveSystem;
@@ -1,6 +1,5 @@
using FluentResults;
using StoneRed.LogicSimulator.Api.Interfaces;
using StoneRed.LogicSimulator.Api;
using StoneRed.LogicSimulator.Misc;
using System;
@@ -1,7 +1,6 @@
using FluentResults;
using StoneRed.LogicSimulator.Api;
using StoneRed.LogicSimulator.Api.Attributes;
using StoneRed.LogicSimulator.Api.Interfaces;
using StoneRed.LogicSimulator.Misc;
using StoneRed.LogicSimulator.Utilities;