diff --git a/.gitignore b/.gitignore index dc00d664..fb37a608 100644 --- a/.gitignore +++ b/.gitignore @@ -25,10 +25,13 @@ # Visual Studio cache directory .vs/ +.vscode/ +bin/ # Gradle cache directory .gradle/ + # Autogenerated VS/MD/Consulo solution and project files ExportedObj/ .consulo/ diff --git a/Assets/Build/DLS.unity b/Assets/Build/DLS.unity index 209cbc4f..11788549 100644 --- a/Assets/Build/DLS.unity +++ b/Assets/Build/DLS.unity @@ -241,7 +241,7 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: openSaveDirectory: 0 - openInMainMenu: 0 + openInMainMenu: 1 testProjectName: MainTest openA: 1 chipToOpenA: BuzzTest diff --git a/Assets/Scripts/DLS.asmdef b/Assets/Scripts/DLS.asmdef index 73295ad9..a1cb2884 100644 --- a/Assets/Scripts/DLS.asmdef +++ b/Assets/Scripts/DLS.asmdef @@ -1,17 +1,18 @@ { - "name": "DLS", - "rootNamespace": "", - "references": [ - "GUID:d4f8eab5cdd4a544c9923829818c11c0", - "GUID:2b65bcfb6aad3f1459e94b356ff58293" - ], - "includePlatforms": [], - "excludePlatforms": [], - "allowUnsafeCode": false, - "overrideReferences": false, - "precompiledReferences": [], - "autoReferenced": true, - "defineConstraints": [], - "versionDefines": [], - "noEngineReferences": false + "name": "DLS", + "rootNamespace": "", + "references": [ + "Assembly-Seb", + "DLS.Description", + "DLSModdingAPI" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false } \ No newline at end of file diff --git a/Assets/Scripts/Description/Helpers/ChipTypeHelper.cs b/Assets/Scripts/Description/Helpers/ChipTypeHelper.cs index 3d1e60d0..51e4606b 100644 --- a/Assets/Scripts/Description/Helpers/ChipTypeHelper.cs +++ b/Assets/Scripts/Description/Helpers/ChipTypeHelper.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using UnityEngine; namespace DLS.Description { @@ -49,7 +50,7 @@ public static class ChipTypeHelper { ChipType.Bus_8Bit, "BUS-8" }, { ChipType.BusTerminus_1Bit, "BUS-TERMINUS-1" }, { ChipType.BusTerminus_4Bit, "BUS-TERMINUS-4" }, - { ChipType.BusTerminus_8Bit, "BUS-TERMINUS-8" } + { ChipType.BusTerminus_8Bit, "BUS-TERMINUS-8" }, }; public static string GetName(ChipType type) => Names[type]; diff --git a/Assets/Scripts/Description/Types/ChipDescription.cs b/Assets/Scripts/Description/Types/ChipDescription.cs index b5b0e67f..69d14336 100644 --- a/Assets/Scripts/Description/Types/ChipDescription.cs +++ b/Assets/Scripts/Description/Types/ChipDescription.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using UnityEngine; namespace DLS.Description @@ -11,6 +12,7 @@ public class ChipDescription // ---- Data ---- public string DLSVersion; + public List DependsOnModIDs = new(); public string Name; public NameDisplayLocation NameLocation; public ChipType ChipType; diff --git a/Assets/Scripts/Description/Types/ProjectDescription.cs b/Assets/Scripts/Description/Types/ProjectDescription.cs index a8dc0f33..5010fc98 100644 --- a/Assets/Scripts/Description/Types/ProjectDescription.cs +++ b/Assets/Scripts/Description/Types/ProjectDescription.cs @@ -81,6 +81,7 @@ public class ChipCollection [JsonIgnore] string displayName_open; public bool IsToggledOpen; public string Name; + public List DependsOnModIDs = new(); public ChipCollection(string name, params string[] chips) { diff --git a/Assets/Scripts/Description/Types/SubTypes/ChipTypes.cs b/Assets/Scripts/Description/Types/SubTypes/ChipTypes.cs index 610f0298..75a25fce 100644 --- a/Assets/Scripts/Description/Types/SubTypes/ChipTypes.cs +++ b/Assets/Scripts/Description/Types/SubTypes/ChipTypes.cs @@ -3,6 +3,7 @@ namespace DLS.Description public enum ChipType { Custom, + Modded, // ---- Basic Chips ---- Nand, diff --git a/Assets/Scripts/Description/Types/SubTypes/DisplayDescription.cs b/Assets/Scripts/Description/Types/SubTypes/DisplayDescription.cs index 258c5e23..93ac544f 100644 --- a/Assets/Scripts/Description/Types/SubTypes/DisplayDescription.cs +++ b/Assets/Scripts/Description/Types/SubTypes/DisplayDescription.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using UnityEngine; namespace DLS.Description @@ -9,12 +10,14 @@ public struct DisplayDescription public int SubChipID; public Vector2 Position; public float Scale; + public List DependsOnModIDs; public DisplayDescription(int subChipID, Vector2 position, float scale) { SubChipID = subChipID; Position = position; Scale = scale; + DependsOnModIDs = new(); } } } \ No newline at end of file diff --git a/Assets/Scripts/Description/Types/SubTypes/PinDescription.cs b/Assets/Scripts/Description/Types/SubTypes/PinDescription.cs index 62745260..8176f495 100644 --- a/Assets/Scripts/Description/Types/SubTypes/PinDescription.cs +++ b/Assets/Scripts/Description/Types/SubTypes/PinDescription.cs @@ -20,6 +20,16 @@ public PinDescription(string name, int id, Vector2 position, PinBitCount bitCoun Colour = colour; ValueDisplayMode = valueDisplayMode; } + + public PinDescription(string name, int id) + { + Name = name; + ID = id; + Position = Vector2.zero; + BitCount = PinBitCount.Bit1; + Colour = PinColour.Red; + ValueDisplayMode = PinValueDisplayMode.Off; + } } public enum PinBitCount diff --git a/Assets/Scripts/Game/Elements/SubChipInstance.cs b/Assets/Scripts/Game/Elements/SubChipInstance.cs index d85725ee..c639e660 100644 --- a/Assets/Scripts/Game/Elements/SubChipInstance.cs +++ b/Assets/Scripts/Game/Elements/SubChipInstance.cs @@ -233,11 +233,13 @@ static List CreateDisplayInstances(ChipDescription chipDesc) static DisplayInstance CreateDisplayInstance(DisplayDescription displayDesc, ChipDescription chipDesc) { - DisplayInstance instance = new(); - instance.Desc = displayDesc; - instance.DisplayType = chipDesc.ChipType; + DisplayInstance instance = new() + { + Desc = displayDesc, + DisplayType = chipDesc.ChipType + }; - if (chipDesc.ChipType == ChipType.Custom) + if (chipDesc.ChipType == ChipType.Custom) { ChipDescription childDesc = GetDescriptionOfDisplayedSubChip(chipDesc, displayDesc.SubChipID); instance.ChildDisplays = CreateDisplayInstances(childDesc); diff --git a/Assets/Scripts/Game/Interaction/ChipInteractionController.cs b/Assets/Scripts/Game/Interaction/ChipInteractionController.cs index da9491d3..b3b96d7e 100644 --- a/Assets/Scripts/Game/Interaction/ChipInteractionController.cs +++ b/Assets/Scripts/Game/Interaction/ChipInteractionController.cs @@ -2,9 +2,13 @@ using System.Linq; using DLS.Description; using DLS.Graphics; +using DLS.ModdingAPI; +using DLS.Mods; using DLS.SaveSystem; using Seb.Helpers; using UnityEngine; +using PinBitCount = DLS.Description.PinBitCount; +using PinDescription = DLS.Description.PinDescription; namespace DLS.Game { @@ -239,9 +243,25 @@ void HandleMouseInput() if (HasControl) UpdatePositionsToMouse(); // --- Mouse button input --- - if (InputHelper.IsMouseDownThisFrame(MouseButton.Left)) HandleLeftMouseDown(); + if (InputHelper.IsMouseDownThisFrame(MouseButton.Left)) + { + ModLoader.NotifyMods((mod, args) => mod.OnMouseClick(args), new IMod.InputEventArgs + { + Position = InputHelper.MousePosWorld, + Button = IMod.MouseButton.Left + }); + HandleLeftMouseDown(); + } if (InputHelper.IsMouseUpThisFrame(MouseButton.Left)) HandleLeftMouseUp(); - if (InputHelper.IsMouseDownThisFrame(MouseButton.Right)) HandleRightMouseDown(); + if (InputHelper.IsMouseDownThisFrame(MouseButton.Right)) + { + ModLoader.NotifyMods((mod, args) => mod.OnMouseClick(args), new IMod.InputEventArgs + { + Position = InputHelper.MousePosWorld, + Button = IMod.MouseButton.Right + }); + HandleRightMouseDown(); + } // Shift + scroll to increase vertical spacing between elements when placing multiple at a time // (disabled if elements were duplicated since then we want to preserve relative positions) @@ -565,7 +585,16 @@ void FinishMovingElements() return; } - hasMoved |= (element.MoveStartPosition != element.Position); + hasMoved |= element.MoveStartPosition != element.Position; + + if (hasMoved) + { + ModLoader.NotifyMods((mod, args) => mod.OnMoveChip(args, element.Position), new IMod.ChipEventArgs + { + ChipName = element is SubChipInstance subchip ? subchip.Description.Name : string.Empty, + Position = element.MoveStartPosition + }); + } } if (hasMoved) ActiveDevChip.UndoController.RecordMoveElements(SelectedElements); @@ -598,8 +627,16 @@ void FinishPlacingNewElements() if (elementToPlace is SubChipInstance subchip) { ActiveDevChip.AddNewSubChip(subchip, false); + ModLoader.NotifyMods((mod, args) => mod.OnPlaceChip(args), new IMod.ChipEventArgs + { + ChipName = subchip.Description.Name, + Position = subchip.Position + }); + } + else if (elementToPlace is DevPinInstance devPin) + { + ActiveDevChip.AddNewDevPin(devPin, false); } - else if (elementToPlace is DevPinInstance devPin) ActiveDevChip.AddNewDevPin(devPin, false); } foreach (WireInstance wire in DuplicatedWires) @@ -630,6 +667,12 @@ void ExitWireEditMode() if (wireToEdit != null && isMovingWireEditPoint) { wireToEdit.SetWirePoint(wireEditPointOld, wireEditPointSelectedIndex); + ModLoader.NotifyMods((mod, args) => mod.OnEditWire(args), new IMod.WireEventArgs + { + SourcePinName = wireToEdit.SourcePin.Name, + TargetPinName = wireToEdit.TargetPin.Name, + BitCount = (int) wireToEdit.bitCount + }); } wireToEdit = null; @@ -841,6 +884,12 @@ void CompleteConnection(WireInstance.ConnectionInfo info) WireToPlace.FinishPlacingWire(info); ActiveDevChip.AddWire(WireToPlace, false); ActiveDevChip.UndoController.RecordAddWire(WireToPlace); + ModLoader.NotifyMods((mod, args) => mod.OnPlaceWire(args), new IMod.WireEventArgs + { + SourcePinName = WireToPlace.SourcePin.Name, + TargetPinName = WireToPlace.TargetPin.Name, + BitCount = (int) WireToPlace.bitCount + }); } } diff --git a/Assets/Scripts/Game/Interaction/KeyboardShortcuts.cs b/Assets/Scripts/Game/Interaction/KeyboardShortcuts.cs index 21f9dbd0..ca0cfe87 100644 --- a/Assets/Scripts/Game/Interaction/KeyboardShortcuts.cs +++ b/Assets/Scripts/Game/Interaction/KeyboardShortcuts.cs @@ -1,3 +1,5 @@ +using System.Collections.Generic; +using DLS.ModdingAPI; using Seb.Helpers; using UnityEngine; @@ -51,5 +53,20 @@ public static class KeyboardShortcuts static bool CtrlShortcutTriggered(KeyCode key) => InputHelper.IsKeyDownThisFrame(key) && InputHelper.CtrlIsHeld && !(InputHelper.AltIsHeld || InputHelper.ShiftIsHeld); static bool CtrlShiftShortcutTriggered(KeyCode key) => InputHelper.IsKeyDownThisFrame(key) && InputHelper.CtrlIsHeld && InputHelper.ShiftIsHeld && !(InputHelper.AltIsHeld); static bool ShiftShortcutTriggered(KeyCode key) => InputHelper.IsKeyDownThisFrame(key) && InputHelper.ShiftIsHeld && !(InputHelper.AltIsHeld || InputHelper.CtrlIsHeld); + + // ---- Modded shortcuts ---- + public static bool GetModdedShortcut(string shortcutName) + { + if (Registry.ModdedShortcuts.TryGetValue(shortcutName, out var shortcut)) + { + bool keyTriggered = InputHelper.IsKeyDownThisFrame(shortcut.Key); + bool modifierConditionMet = shortcut.ModifierCondition?.Invoke() ?? true; + + return keyTriggered && modifierConditionMet; + } + + Debug.LogWarning($"Shortcut '{shortcutName}' is not registered."); + return false; + } } } \ No newline at end of file diff --git a/Assets/Scripts/Game/Main/Main.cs b/Assets/Scripts/Game/Main/Main.cs index 247a085f..69c97178 100644 --- a/Assets/Scripts/Game/Main/Main.cs +++ b/Assets/Scripts/Game/Main/Main.cs @@ -4,6 +4,7 @@ using System.Linq; using DLS.Description; using DLS.Graphics; +using DLS.Mods; using DLS.SaveSystem; using UnityEngine; @@ -24,6 +25,8 @@ public static class Main public static void Init(AudioState audioState) { SavePaths.EnsureDirectoryExists(SavePaths.ProjectsPath); + SavePaths.EnsureDirectoryExists(SavePaths.ModsPath); + ModLoader.InitializeMods(SavePaths.ModsPath); SaveAndApplyAppSettings(Loader.LoadAppSettings()); Main.audioState = audioState; } @@ -68,6 +71,7 @@ public static void CreateOrLoadProject(string projectName, string startupChipNam else ActiveProject = CreateProject(projectName); ActiveProject.LoadDevChipOrCreateNewIfDoesntExist(startupChipName); + ActiveProject.StartSimulation(); ActiveProject.audioState = audioState; UIDrawer.SetActiveMenu(UIDrawer.MenuType.None); diff --git a/Assets/Scripts/Game/Project/BuiltinChipCreator.cs b/Assets/Scripts/Game/Project/BuiltinChipCreator.cs index c757f1e1..45d1d4ff 100644 --- a/Assets/Scripts/Game/Project/BuiltinChipCreator.cs +++ b/Assets/Scripts/Game/Project/BuiltinChipCreator.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using DLS.Description; using UnityEngine; using static DLS.Graphics.DrawSettings; @@ -55,6 +56,8 @@ public static ChipDescription[] CreateAllBuiltinChipDescriptions() }; } + + static ChipDescription CreateNand() { Color col = new(0.73f, 0.26f, 0.26f); diff --git a/Assets/Scripts/Game/Project/ChipLibrary.cs b/Assets/Scripts/Game/Project/ChipLibrary.cs index 602a6427..ce509701 100644 --- a/Assets/Scripts/Game/Project/ChipLibrary.cs +++ b/Assets/Scripts/Game/Project/ChipLibrary.cs @@ -1,6 +1,8 @@ using System.Collections.Generic; using System.Linq; using DLS.Description; +using DLS.Mods; +using UnityEngine; namespace DLS.Game { @@ -9,11 +11,12 @@ public class ChipLibrary public readonly List allChips = new(); readonly HashSet builtinChipNames = new(ChipDescription.NameComparer); + readonly HashSet moddedChipNames = new(ChipDescription.NameComparer); readonly Dictionary descriptionFromNameLookup = new(ChipDescription.NameComparer); readonly List hiddenChips = new(); - public ChipLibrary(ChipDescription[] customChips, ChipDescription[] builtinChips) + public ChipLibrary(ChipDescription[] customChips, ChipDescription[] builtinChips, ChipDescription[] moddedChips) { // Add built-in chips to list of all chips foreach (ChipDescription chip in builtinChips) @@ -25,6 +28,13 @@ public ChipLibrary(ChipDescription[] customChips, ChipDescription[] builtinChips builtinChipNames.Add(chip.Name); } + // Add modded chips to list of all chips + foreach (ChipDescription chip in moddedChips) + { + AddChipToLibrary(chip); + moddedChipNames.Add(chip.Name); + } + // Add custom chips to list of all chips foreach (ChipDescription chip in customChips) { @@ -39,7 +49,10 @@ void RebuildChipDescriptionLookup() descriptionFromNameLookup.Clear(); foreach (ChipDescription desc in allChips) { - descriptionFromNameLookup.Add(desc.Name, desc); + if (!descriptionFromNameLookup.ContainsKey(desc.Name)) + { + descriptionFromNameLookup.Add(desc.Name, desc); + } } foreach (ChipDescription desc in hiddenChips) @@ -53,7 +66,16 @@ void RebuildChipDescriptionLookup() public bool HasChip(string name) => TryGetChipDescription(name, out _); - public ChipDescription GetChipDescription(string name) => descriptionFromNameLookup[name]; + public ChipDescription GetChipDescription(string name) + { + try { + return descriptionFromNameLookup[name]; + } + catch (KeyNotFoundException) + { + return null; + } + } public bool TryGetChipDescription(string name, out ChipDescription description) => descriptionFromNameLookup.TryGetValue(name, out description); @@ -105,7 +127,7 @@ public string[] GetAllCustomChipNames() foreach (ChipDescription chip in allChips) { - if (!IsBuiltinChip(chip.Name)) + if (!IsBuiltinChip(chip.Name) && !(chip.ChipType == ChipType.Modded)) { customChipNames.Add(chip.Name); } diff --git a/Assets/Scripts/Game/Project/ModdedChipCreator.cs b/Assets/Scripts/Game/Project/ModdedChipCreator.cs new file mode 100644 index 00000000..d389d4f7 --- /dev/null +++ b/Assets/Scripts/Game/Project/ModdedChipCreator.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using DLS.Description; +using DLS.ModdingAPI; +using UnityEngine; +using PinDescription = DLS.Description.PinDescription; + +namespace DLS.Game +{ + public static class ModdedChipCreator + { + static List unbuiltChips = new(); + public static List ModdedChips = new(); + private static readonly Dictionary> ModdedChipFunctions = new(); + + public static ChipDescription[] CreateAllModdedChipDescriptions() + { + unbuiltChips = Registry.moddedChips; + foreach (ChipBuilder chip in unbuiltChips) + { + RegisterChip(chip.modID, chip.name, chip.size, chip.color, ConvertToDescriptionPins(chip.inputs), ConvertToDescriptionPins(chip.outputs), chip.displays != null ? ModdedDisplayCreator.RegisterDisplays(chip.displays) : null, chip.hideName, chip.simulationFunction); + } + return ModdedChips.ToArray(); + } + + static void RegisterChip( + string modID, + string name, + Vector2 size, + Color col, + PinDescription[] inputs = null, + PinDescription[] outputs = null, + DisplayDescription[] displays = null, + bool hideName = false, + Action simulationFunction = null) + { + // Register the chip description + ChipDescription chipDescription = CreateModdedChipDescription(name, ChipType.Modded, size, col, inputs, outputs, displays, hideName); + chipDescription.DependsOnModIDs.Add(modID); + ModdedChips.Add(chipDescription); + + // Register the simulation function + if (simulationFunction != null) + { + ModdedChipFunctions[chipDescription] = simulationFunction; + } + } + + static ChipDescription CreateModdedChipDescription(string name, ChipType type, Vector2 size, Color col, PinDescription[] inputs = null, PinDescription[] outputs = null, DisplayDescription[] displays = null, bool hideName = false) + { + ValidatePinIDs(inputs, outputs, name); + + return new ChipDescription + { + Name = name, + NameLocation = hideName ? NameDisplayLocation.Hidden : NameDisplayLocation.Centre, + Colour = col, + Size = new Vector2(size.x, size.y), + InputPins = inputs ?? Array.Empty(), + OutputPins = outputs ?? Array.Empty(), + SubChips = Array.Empty(), + Wires = Array.Empty(), + Displays = displays, + ChipType = type + }; + } + + static PinDescription[] ConvertToDescriptionPins(ModdingAPI.PinDescription[] moddingPins) + { + if (moddingPins == null) return null; + + return moddingPins.Select(pin => new PinDescription( + pin.Name, + pin.ID, + pin.Position, + (Description.PinBitCount) pin.BitCount, + (Description.PinColour) pin.Colour, + (Description.PinValueDisplayMode) pin.ValueDisplayMode + )).ToArray(); + } + + public static bool TryGetSimulationFunction(ChipDescription chipDescription, out Action simulationFunction) + { + return ModdedChipFunctions.TryGetValue(chipDescription, out simulationFunction); + } + + static void ValidatePinIDs(PinDescription[] inputs, PinDescription[] outputs, string chipName) + { + HashSet pinIDs = new(); + + AddPins(inputs); + AddPins(outputs); + return; + + void AddPins(PinDescription[] pins) + { + if (pins == null) return; + foreach (PinDescription pin in pins) + { + if (!pinIDs.Add(pin.ID)) + { + throw new Exception($"Pin has duplicate ID ({pin.ID}) in modded chip: {chipName}"); + } + } + } + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/Game/Project/ModdedChipCreator.cs.meta b/Assets/Scripts/Game/Project/ModdedChipCreator.cs.meta new file mode 100644 index 00000000..0078f94e --- /dev/null +++ b/Assets/Scripts/Game/Project/ModdedChipCreator.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b63348c6a526e7846ab863f2d9d473fb \ No newline at end of file diff --git a/Assets/Scripts/Game/Project/ModdedCollectionCreator.cs b/Assets/Scripts/Game/Project/ModdedCollectionCreator.cs new file mode 100644 index 00000000..117bf468 --- /dev/null +++ b/Assets/Scripts/Game/Project/ModdedCollectionCreator.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using System.Linq; +using DLS.Description; +using DLS.ModdingAPI; + +namespace DLS.Game +{ + public static class ModdedCollectionCreator + { + static List unbuiltCollections = new(); + public static List ModdedCollections = new(); + + static ModdedCollectionCreator() + { + unbuiltCollections = Registry.moddedCollections; + foreach(CollectionBuilder collection in unbuiltCollections) + { + RegisterCollection(collection.name, collection.chips.Select(chip => chip.name).ToArray()); + } + } + public static void RegisterCollection(string name, string[] chipNames) + { + ModdedCollections.Add(new ChipCollection(name, chipNames)); + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/Game/Project/ModdedCollectionCreator.cs.meta b/Assets/Scripts/Game/Project/ModdedCollectionCreator.cs.meta new file mode 100644 index 00000000..d577f1e5 --- /dev/null +++ b/Assets/Scripts/Game/Project/ModdedCollectionCreator.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 893d399dbc5f0524fb19be71c34bb475 \ No newline at end of file diff --git a/Assets/Scripts/Game/Project/ModdedDisplayCreator.cs b/Assets/Scripts/Game/Project/ModdedDisplayCreator.cs new file mode 100644 index 00000000..87f5f56d --- /dev/null +++ b/Assets/Scripts/Game/Project/ModdedDisplayCreator.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using UnityEngine; +using DLS.Description; +using DLS.ModdingAPI; +using System; + +namespace DLS.Game +{ + public static class ModdedDisplayCreator + { + public static List ModdedDisplays = new(); + public static Dictionary> ModdedDrawFunctions = new(); + + public static DisplayDescription[] RegisterDisplays(DisplayBuilder[] displays) + { + List descriptions = new(); + foreach (DisplayBuilder display in displays) + { + descriptions.Add(RegisterDisplay(display.Position, display.Scale, display.DrawFunction)); + } + return descriptions.ToArray(); + } + + public static DisplayDescription RegisterDisplay(Vector2 position, float scale, Action drawFunction) + { + DisplayDescription displayDescription = new(-1, position, scale); + ModdedDisplays.Add(displayDescription); + ModdedDrawFunctions[displayDescription] = drawFunction; + return displayDescription; + } + + public static bool TryGetDrawFunction(DisplayDescription displayDescription, out Action drawFunction) + { + return ModdedDrawFunctions.TryGetValue(displayDescription, out drawFunction); + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/Game/Project/ModdedDisplayCreator.cs.meta b/Assets/Scripts/Game/Project/ModdedDisplayCreator.cs.meta new file mode 100644 index 00000000..def12e10 --- /dev/null +++ b/Assets/Scripts/Game/Project/ModdedDisplayCreator.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 44039eececa77894b831a4c537b7cf62 \ No newline at end of file diff --git a/Assets/Scripts/Game/Project/Project.cs b/Assets/Scripts/Game/Project/Project.cs index e2a5be6e..71a8540f 100644 --- a/Assets/Scripts/Game/Project/Project.cs +++ b/Assets/Scripts/Game/Project/Project.cs @@ -5,11 +5,14 @@ using System.Threading; using DLS.Description; using DLS.Graphics; +using DLS.ModdingAPI; +using DLS.Mods; using DLS.SaveSystem; using DLS.Simulation; using Seb.Helpers; using UnityEngine; using Debug = UnityEngine.Debug; +using PinDescription = DLS.Description.PinDescription; namespace DLS.Game { @@ -258,6 +261,11 @@ public void LoadDevChipOrCreateNewIfDoesntExist(string chipName) { CreateBlankDevChip(); } + + ModLoader.NotifyMods((mod, args) => mod.OnProjectLoad(args), new IMod.ProjectEventArgs + { + ProjectName = this.description.ProjectName + }); } void SetNewActiveDevChip(DevChipInstance devChip) @@ -345,6 +353,32 @@ public void DeleteChip(string chipToDeleteName) } } + public void DeleteModdedChip(ChipDescription chipToDelete) + { + bool simReloadRequired = ChipContainsSubchipIndirectly(ViewedChip, chipToDelete.Name); + + if (ChipContainsSubChipDirectly(ViewedChip, chipToDelete.Name)) + { + ViewedChip.UndoController.Clear(); + } + + ModdedChipCreator.ModdedChips.RemoveAll(chip => ChipDescription.NameMatch(chip.Name, chipToDelete.Name)); + + // Delete chip save file, remove from library, and update project description + UpdateAndSaveAffectedChips(chipToDelete, null, true); + chipLibrary.RemoveChip(chipToDelete.Name); + EnsureChipRemovedFromCollections(chipToDelete.Name); + SetStarred(chipToDelete.Name, false, false, false); // ensure removed from starred list + UpdateAndSaveProjectDescription(); + + // Remove any instances of the deleted chip from the active chip + ViewedChip.DeleteSubchipsByName(chipToDelete.Name); + if (simReloadRequired) + { + ViewedChip.RebuildSimulation(); + } + } + // Test if chip's subchips (or any of their subchips, etc...) contain the target subchip bool ChipContainsSubchipIndirectly(DevChipInstance devChip, string targetSubchip) { @@ -483,7 +517,19 @@ public void ToggleGridDisplay() public void NotifyExit() { + ModLoader.NotifyMods((mod, args) => mod.OnProjectUnload(args), new IMod.ProjectEventArgs + { + ProjectName = description.ProjectName + }); + simThreadActive = false; + + // List moddedChipsCopy = new(ModdedChipCreator.ModdedChips); + // foreach (ChipDescription chip in moddedChipsCopy) + // { + + // DeleteModdedChip(chip); + // } } void SimThread() diff --git a/Assets/Scripts/Graphics/UI/Menus/BottomBarUI.cs b/Assets/Scripts/Graphics/UI/Menus/BottomBarUI.cs index 932230d8..ceec4ddd 100644 --- a/Assets/Scripts/Graphics/UI/Menus/BottomBarUI.cs +++ b/Assets/Scripts/Graphics/UI/Menus/BottomBarUI.cs @@ -1,6 +1,9 @@ using System; +using System.Collections.Generic; +using System.Linq; using DLS.Description; using DLS.Game; +using DLS.Mods; using Seb.Helpers; using Seb.Types; using Seb.Vis; @@ -188,6 +191,24 @@ static void DrawBottomBar(Project project) for (int i = 0; i < project.description.StarredList.Count; i++) { StarredItem starred = project.description.StarredList[i]; + object desc = starred.IsCollection + ? GetChipCollectionByName(starred.Name) + : project.chipLibrary.GetChipDescription(starred.Name); + + List dependsOnModIDs = starred.IsCollection + ? ((ChipCollection) desc)?.DependsOnModIDs + : ((ChipDescription) desc)?.DependsOnModIDs; + + if (desc == null) + { + continue; + } + + if (dependsOnModIDs != null && !dependsOnModIDs.All(ModLoader.IsModLoaded)) + { + continue; + } + bool isToggledOpenCollection = activeCollection != null && ChipDescription.NameMatch(starred.Name, activeCollection.Name); string buttonName = starred.GetDisplayStringForBottomBar(isToggledOpenCollection); @@ -365,6 +386,14 @@ static ChipCollection GetChipCollectionByName(string name) } } + foreach (ChipCollection c in ModdedCollectionCreator.ModdedCollections) + { + if (ChipDescription.NameMatch(c.Name, name)) + { + return c; + } + } + throw new Exception("Failed to find collection with name: " + name); } diff --git a/Assets/Scripts/Graphics/UI/Menus/ChipLibraryMenu.cs b/Assets/Scripts/Graphics/UI/Menus/ChipLibraryMenu.cs index 4a333008..1441d78e 100644 --- a/Assets/Scripts/Graphics/UI/Menus/ChipLibraryMenu.cs +++ b/Assets/Scripts/Graphics/UI/Menus/ChipLibraryMenu.cs @@ -2,6 +2,7 @@ using System.Linq; using DLS.Description; using DLS.Game; +using DLS.Mods; using Seb.Helpers; using Seb.Types; using Seb.Vis; @@ -61,9 +62,15 @@ public static class ChipLibraryMenu static ChipCollection lastAutoOpenedCollection; static List collections => project.description.ChipCollections; + static List moddedCollections => ModdedCollectionCreator.ModdedCollections; static Project project => Project.ActiveProject; + static ChipCollection GetCollectionAtIndex(int index) + { + return index < collections.Count ? collections[index] : moddedCollections[index - collections.Count]; + } + public static void DrawMenu() { MenuHelper.DrawBackgroundOverlay(); @@ -138,6 +145,23 @@ static void DrawStarredPanel(Vector2 topLeft, Vector2 size) static void DrawStarredEntry(Vector2 topLeft, float width, int index, bool isLayoutPass) { StarredItem starredItem = project.description.StarredList[index]; + object desc = starredItem.IsCollection + ? GetCollectionAtIndex(index) + : project.chipLibrary.GetChipDescription(starredItem.Name); + + List dependsOnModIDs = starredItem.IsCollection + ? ((ChipCollection) desc)?.DependsOnModIDs + : ((ChipDescription) desc)?.DependsOnModIDs; + + if (desc == null) + { + return; + } + + if (dependsOnModIDs != null && !dependsOnModIDs.All(ModLoader.IsModLoaded)) + { + return; + } ButtonTheme theme = GetButtonTheme(starredItem.IsCollection, index == selectedStarredItemIndex); interactableStates_starredList[0] = index < project.description.StarredList.Count - 1; // can move down @@ -160,13 +184,13 @@ static void DrawCollectionsPanel(Vector2 topLeft, Vector2 size) Bounds2D panelBoundsMinusHeader = Bounds2D.CreateFromTopLeftAndSize(UI.PrevBounds.BottomLeft, new Vector2(size.x, size.y - UI.PrevBounds.Height)); Bounds2D panelContentBounds = Bounds2D.Shrink(panelBoundsMinusHeader, PanelUIPadding); - UI.DrawScrollView(ID_CollectionsScrollbar, panelContentBounds.TopLeft, panelContentBounds.Size, UILayoutHelper.DefaultSpacing, Anchor.TopLeft, ActiveUITheme.ScrollTheme, drawCollectionEntry, collections.Count); + UI.DrawScrollView(ID_CollectionsScrollbar, panelContentBounds.TopLeft, panelContentBounds.Size, UILayoutHelper.DefaultSpacing, Anchor.TopLeft, ActiveUITheme.ScrollTheme, drawCollectionEntry, collections.Count + moddedCollections.Count); MenuHelper.DrawReservedMenuPanel(panelID, panelBounds, false); } static void DrawCollectionEntry(Vector2 topLeft, float width, int collectionIndex, bool isLayoutPass) { - ChipCollection collection = collections[collectionIndex]; + ChipCollection collection = GetCollectionAtIndex(collectionIndex); string label = collection.GetDisplayString(); bool collectionHighlighted = collectionIndex == selectedCollectionIndex; @@ -189,7 +213,12 @@ static void DrawCollectionEntry(Vector2 topLeft, float width, int collectionInde { for (int chipIndex = 0; chipIndex < collection.Chips.Count; chipIndex++) { - string chipName = collection.Chips[chipIndex]; + ChipDescription chip = project.chipLibrary.GetChipDescription(collection.Chips[chipIndex]); + if (chip == null || !chip.DependsOnModIDs.All(ModLoader.IsModLoaded)) + { + continue; + } + string chipName = chip.Name; ButtonTheme activeChipTheme = collectionIndex == selectedCollectionIndex && chipIndex == selectedChipInCollectionIndex ? ActiveUITheme.ChipLibraryChipToggleOn : ActiveUITheme.ChipLibraryChipToggleOff; Vector2 chipLabelPos = new(topLeft.x + nestedInset, UI.PrevBounds.Bottom - UILayoutHelper.DefaultSpacing); bool chipPressed = UI.Button(chipName, activeChipTheme, chipLabelPos, new Vector2(width - nestedInset, 2), true, false, false, Anchor.TopLeft, true, 1, isScrolling); @@ -230,7 +259,7 @@ static void DrawSelectedItemPanel(Vector2 topLeft, Vector2 size) if (hasChipSelected) { // ---- Draw ---- - ChipCollection collection = collections[selectedCollectionIndex]; + ChipCollection collection = GetCollectionAtIndex(selectedCollectionIndex); string selectedChipName = collection.Chips[selectedChipInCollectionIndex]; bool canStepUpInCollection = selectedChipInCollectionIndex > 0; bool canStepDownInCollection = selectedChipInCollectionIndex < collection.Chips.Count - 1; @@ -297,18 +326,19 @@ static void DrawSelectedItemPanel(Vector2 topLeft, Vector2 size) else if (hasCollectionSelected) { // ---- Draw ---- - ChipCollection collection = collections[selectedCollectionIndex]; + ChipCollection collection = GetCollectionAtIndex(selectedCollectionIndex); ButtonTheme colSource = GetButtonTheme(true, true); DrawHeader(collection.Name, colSource.buttonCols.normal, colSource.textCols.normal, ref topLeft, panelContentBounds.Width); bool isStarred = project.description.IsStarred(collection.Name, true); - bool toggleStarred = DrawHorizontalButtonGroup(buttonName_starUnstar[isStarred ? 1 : 0], null, ref topLeft, panelContentBounds.Width) == 0; + bool[] canStar = new[] {!moddedCollections.Contains(collection)}; + bool toggleStarred = DrawHorizontalButtonGroup(buttonName_starUnstar[isStarred ? 1 : 0], canStar, ref topLeft, panelContentBounds.Width) == 0; - interactableStates_move[0] = selectedCollectionIndex > 0; - interactableStates_move[1] = selectedCollectionIndex < collections.Count - 1; + interactableStates_move[0] = selectedCollectionIndex > 0 && !moddedCollections.Contains(collection); + interactableStates_move[1] = selectedCollectionIndex < collections.Count - 1 && !moddedCollections.Contains(collection); int buttonIndexOrganize = DrawHorizontalButtonGroup(buttonNames_moveSingleStep, interactableStates_move, ref topLeft, panelContentBounds.Width); - bool canRenameOrDelete = !ChipDescription.NameMatch(collection.Name, defaultOtherChipsCollectionName); + bool canRenameOrDelete = !ChipDescription.NameMatch(collection.Name, defaultOtherChipsCollectionName) && !moddedCollections.Contains(collection); interactableStates_renameDelete[0] = canRenameOrDelete; interactableStates_renameDelete[1] = canRenameOrDelete; int buttonIndexEditCollection = DrawHorizontalButtonGroup(buttonNames_collectionRenameOrDelete, interactableStates_renameDelete, ref topLeft, panelContentBounds.Width); @@ -342,7 +372,7 @@ static void DrawSelectedItemPanel(Vector2 topLeft, Vector2 size) int indexEnd = selectedCollectionIndex - 1; (collections[indexStart], collections[indexEnd]) = (collections[indexEnd], collections[indexStart]); selectedCollectionIndex = indexEnd; - collection = collections[selectedCollectionIndex]; + collection = GetCollectionAtIndex(selectedCollectionIndex); } else if (buttonIndexOrganize == 1) // Move collection down { @@ -350,7 +380,7 @@ static void DrawSelectedItemPanel(Vector2 topLeft, Vector2 size) int indexEnd = selectedCollectionIndex + 1; (collections[indexStart], collections[indexEnd]) = (collections[indexEnd], collections[indexStart]); selectedCollectionIndex = indexEnd; - collection = collections[selectedCollectionIndex]; + collection = GetCollectionAtIndex(selectedCollectionIndex); } } else if (hasStarredItemSelected) diff --git a/Assets/Scripts/Graphics/UI/Menus/MainMenu.cs b/Assets/Scripts/Graphics/UI/Menus/MainMenu.cs index 97f8205d..7a974f41 100644 --- a/Assets/Scripts/Graphics/UI/Menus/MainMenu.cs +++ b/Assets/Scripts/Graphics/UI/Menus/MainMenu.cs @@ -136,6 +136,7 @@ public static void OnMenuOpened() activeMenuScreen = MenuScreen.Main; activePopup = PopupKind.None; selectedProjectIndex = -1; + ModWarningPopup.MenuShown = false; } static void DrawMainScreen() diff --git a/Assets/Scripts/Graphics/UI/Menus/ModWarningPopup.cs b/Assets/Scripts/Graphics/UI/Menus/ModWarningPopup.cs new file mode 100644 index 00000000..d073fffb --- /dev/null +++ b/Assets/Scripts/Graphics/UI/Menus/ModWarningPopup.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using DLS.Game; +using DLS.Mods; +using Seb.Vis; +using Seb.Vis.UI; +using UnityEngine; +using UnityEngine.TextCore.Text; + +namespace DLS.Graphics +{ + public static class ModWarningPopup + { + public static bool MenuShown = false; + public static void DrawMenu() + { + MenuHelper.DrawBackgroundOverlay(); + Draw.ID panelID = UI.ReservePanel(); + DrawSettings.UIThemeDLS theme = DrawSettings.ActiveUITheme; + + Vector2 pos = UI.Centre + Vector2.up * (UI.HalfHeight * 0.25f); + + // Collect chip names hidden due to missing mods + string hiddenChipNames = string.Join("\n", Project.ActiveProject.chipLibrary.allChips + .Where(chip => chip.DependsOnModIDs != null && !chip.DependsOnModIDs.All(ModLoader.IsModLoaded)) + .Select(chip => chip.Name)); + + // Format chip names and their dependencies + string hiddenChipsDependencies = string.Join("\n", Project.ActiveProject.chipLibrary.allChips + .Where(chip => chip.DependsOnModIDs != null && !chip.DependsOnModIDs.All(ModLoader.IsModLoaded)) + .Select(chip => $"{chip.Name,-30}{string.Join(", ", chip.DependsOnModIDs.Where(id => !ModLoader.IsModLoaded(id))),30}")); + + using (UI.BeginBoundsScope(true)) + { + // Draw warning text + UI.DrawText( + "Some chips contain subchips from mods that are not loaded.\nThe following chips have been disabled until their\nassociated mods have been loaded:", + theme.FontBold, + theme.FontSizeRegular, + pos, + Anchor.TextCentre, + Color.white + ); + + UI.DrawText( + $"{"Chip Name", -30}{"Mod ID", 30}", + theme.FontBold, + theme.FontSizeRegular, + UI.GetCurrentBoundsScope().BottomLeft + Vector2.down * 3f, + Anchor.TextCentreLeft, + Color.white + ); + + // Draw list of missing mod IDs + UI.DrawText( + hiddenChipsDependencies, + theme.FontRegular, + theme.FontSizeRegular, + UI.GetCurrentBoundsScope().BottomLeft + Vector2.down * 1.5f, + Anchor.TextCentreLeft, + Color.white + ); + + bool result = UI.Button( + "OK", + theme.ButtonTheme, + UI.GetCurrentBoundsScope().CentreBottom + Vector2.down * 3f, + size: (UI.GetCurrentBoundsScope().Width - DrawSettings.DefaultButtonSpacing * 6) * Vector2.right + ); + + MenuHelper.DrawReservedMenuPanel(panelID, UI.GetCurrentBoundsScope()); + + if (result || KeyboardShortcuts.CancelShortcutTriggered) + { + UIDrawer.SetActiveMenu(UIDrawer.MenuType.None); + } + } + } + + public static void OnMenuOpened() + { + MenuShown = true; + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/Graphics/UI/Menus/ModWarningPopup.cs.meta b/Assets/Scripts/Graphics/UI/Menus/ModWarningPopup.cs.meta new file mode 100644 index 00000000..77e389f3 --- /dev/null +++ b/Assets/Scripts/Graphics/UI/Menus/ModWarningPopup.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d51e67903e3b19c49b7ed29f39cafe25 \ No newline at end of file diff --git a/Assets/Scripts/Graphics/UI/Menus/SearchPopup.cs b/Assets/Scripts/Graphics/UI/Menus/SearchPopup.cs index 203fdf1f..b6cc7a1f 100644 --- a/Assets/Scripts/Graphics/UI/Menus/SearchPopup.cs +++ b/Assets/Scripts/Graphics/UI/Menus/SearchPopup.cs @@ -3,6 +3,7 @@ using System.Linq; using DLS.Description; using DLS.Game; +using DLS.Mods; using Seb.Helpers; using Seb.Types; using Seb.Vis; @@ -131,6 +132,13 @@ static void CreateFilteredChipsList(string searchString) HashSet remainingChipNames = new(allChipNames); remainingChipNames.ExceptWith(recentChipNames); + // Filter out chips with null descriptions or unloaded mod dependencies + remainingChipNames.RemoveWhere(chipName => + { + ChipDescription desc = Project.ActiveProject.chipLibrary.GetChipDescription(chipName); + return desc == null || (desc.DependsOnModIDs != null && !desc.DependsOnModIDs.All(ModLoader.IsModLoaded)); + }); + List sortedList = remainingChipNames.ToList(); sortedList.Sort(); sortedList.Reverse(); @@ -154,9 +162,15 @@ static void CreateFilteredChipsList(string searchString) contains.ExceptWith(startsWith); contains.ExceptWith(startsWith_Lenient); - List all = ToSortedList(startsWith); - all.AddRange(ToSortedList(startsWith_Lenient)); - all.AddRange(ToSortedList(contains)); + // Filter out chips with null descriptions or unloaded mod dependencies + HashSet allFiltered = new(startsWith.Concat(startsWith_Lenient).Concat(contains)); + allFiltered.RemoveWhere(chipName => + { + ChipDescription desc = Project.ActiveProject.chipLibrary.GetChipDescription(chipName); + return desc == null || (desc.DependsOnModIDs != null && !desc.DependsOnModIDs.All(ModLoader.IsModLoaded)); + }); + + List all = ToSortedList(allFiltered); filteredChipNames = all.ToArray(); static string LenientString(string s) diff --git a/Assets/Scripts/Graphics/UI/UIDrawer.cs b/Assets/Scripts/Graphics/UI/UIDrawer.cs index 091248e4..b8c20c88 100644 --- a/Assets/Scripts/Graphics/UI/UIDrawer.cs +++ b/Assets/Scripts/Graphics/UI/UIDrawer.cs @@ -1,5 +1,8 @@ +using System.Linq; using DLS.Game; +using DLS.Mods; using Seb.Vis.UI; +using UnityEngine; namespace DLS.Graphics { @@ -20,7 +23,8 @@ public enum MenuType PulseEdit, UnsavedChanges, Search, - ChipLabelPopup + ChipLabelPopup, + ModWarning } static MenuType activeMenuOld; @@ -68,6 +72,7 @@ static void DrawProjectMenus(Project project) else if (menuToDraw == MenuType.Search) SearchPopup.DrawMenu(); else if (menuToDraw == MenuType.ChipLabelPopup) ChipLabelMenu.DrawMenu(); else if (menuToDraw == MenuType.PulseEdit) PulseEditMenu.DrawMenu(); + else if (menuToDraw == MenuType.ModWarning) ModWarningPopup.DrawMenu(); else { bool showSimPausedBanner = project.simPaused; @@ -98,12 +103,20 @@ static void NotifyIfActiveMenuChanged() else if (ActiveMenu == MenuType.Search) SearchPopup.OnMenuOpened(); else if (ActiveMenu == MenuType.ChipLabelPopup) ChipLabelMenu.OnMenuOpened(); else if (ActiveMenu == MenuType.PulseEdit) PulseEditMenu.OnMenuOpened(); + else if (ActiveMenu == MenuType.ModWarning) ModWarningPopup.OnMenuOpened(); if (InInputBlockingMenu() && Project.ActiveProject != null && Project.ActiveProject.controller != null) { Project.ActiveProject.controller.CancelEverything(); } + // Trigger ModWarningPopup if there are chips dependent on unloaded mods + if (Project.ActiveProject != null && Project.ActiveProject.controller != null && Project.ActiveProject.chipLibrary.allChips.Any(chip => chip.DependsOnModIDs != null && !chip.DependsOnModIDs.All(ModLoader.IsModLoaded)) && ModWarningPopup.MenuShown == false) + { + SetActiveMenu(MenuType.ModWarning); + ModWarningPopup.OnMenuOpened(); + } + activeMenuOld = ActiveMenu; } } diff --git a/Assets/Scripts/Graphics/World/DevSceneDrawer.cs b/Assets/Scripts/Graphics/World/DevSceneDrawer.cs index eefb8307..953f5691 100644 --- a/Assets/Scripts/Graphics/World/DevSceneDrawer.cs +++ b/Assets/Scripts/Graphics/World/DevSceneDrawer.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using DLS.Description; using DLS.Game; using DLS.Simulation; @@ -440,6 +441,17 @@ public static Bounds2D DrawDisplay(DisplayInstance display, Vector2 posParent, f bounds = DrawDisplay_LED(posWorld, scaleWorld, col); } + else if (display.DisplayType == ChipType.Modded) + { + if (ModdedDisplayCreator.TryGetDrawFunction(display.Desc, out var DrawDisplay_Modded) && sim != null) + { + uint[] inputStates = sim.InputPins.Select(pin => (uint) PinState.GetBitStates(pin.State)).ToArray(); + uint[] outputStates = sim.OutputPins.Select(pin => (uint) PinState.GetBitStates(pin.State)).ToArray(); + + DrawDisplay_Modded(posWorld, scaleWorld, inputStates, outputStates); + } + bounds = Bounds2D.CreateFromCentreAndSize(posWorld, Vector2.one * scaleWorld); + } display.LastDrawBounds = bounds; return bounds; diff --git a/Assets/Scripts/ModdingAPI.meta b/Assets/Scripts/ModdingAPI.meta new file mode 100644 index 00000000..27aa6fe2 --- /dev/null +++ b/Assets/Scripts/ModdingAPI.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 43f23a4e7b724f94395dc5f05cde7b10 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/ModdingAPI/ChipBuilder.cs b/Assets/Scripts/ModdingAPI/ChipBuilder.cs new file mode 100644 index 00000000..3edf16c8 --- /dev/null +++ b/Assets/Scripts/ModdingAPI/ChipBuilder.cs @@ -0,0 +1,66 @@ +using System; +using UnityEngine; + +namespace DLS.ModdingAPI +{ + public class ChipBuilder + { + public readonly string modID; + public readonly string name; + public Vector2 size = Vector2.one; + public Color color = Color.white; + public PinDescription[] inputs = null; + public PinDescription[] outputs = null; + public DisplayBuilder[] displays = null; + public bool hideName = false; + public Action simulationFunction = null; + + public ChipBuilder(string modID, string name) + { + this.modID = modID; + this.name = name; + } + + public ChipBuilder SetSize(Vector2 size) + { + this.size = size; + return this; + } + + public ChipBuilder SetColor(Color color) + { + this.color = color; + return this; + } + + public ChipBuilder SetInputs(PinDescription[] inputs) + { + this.inputs = inputs; + return this; + } + + public ChipBuilder SetOutputs(PinDescription[] outputs) + { + this.outputs = outputs; + return this; + } + + public ChipBuilder SetDisplays(DisplayBuilder[] displays) + { + this.displays = displays; + return this; + } + + public ChipBuilder HideName(bool hide = true) + { + this.hideName = hide; + return this; + } + + public ChipBuilder SetSimulationFunction(Action simulationFunction) + { + this.simulationFunction = simulationFunction; + return this; + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/ModdingAPI/ChipBuilder.cs.meta b/Assets/Scripts/ModdingAPI/ChipBuilder.cs.meta new file mode 100644 index 00000000..09cb8775 --- /dev/null +++ b/Assets/Scripts/ModdingAPI/ChipBuilder.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d225081c06659cc44a5c898a31668903 \ No newline at end of file diff --git a/Assets/Scripts/ModdingAPI/CollectionBuilder.cs b/Assets/Scripts/ModdingAPI/CollectionBuilder.cs new file mode 100644 index 00000000..6a33d2c3 --- /dev/null +++ b/Assets/Scripts/ModdingAPI/CollectionBuilder.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; + +namespace DLS.ModdingAPI +{ + public class CollectionBuilder + { + public string modID; + public readonly string name; + public List chips; + + public CollectionBuilder(string modID, string name) + { + this.modID = modID; + this.name = name; + chips = new(); + } + + public CollectionBuilder AddChip(ChipBuilder chip) + { + chips.Add(chip); + return this; + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/ModdingAPI/CollectionBuilder.cs.meta b/Assets/Scripts/ModdingAPI/CollectionBuilder.cs.meta new file mode 100644 index 00000000..a2d3b256 --- /dev/null +++ b/Assets/Scripts/ModdingAPI/CollectionBuilder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 04c2257c20452374dab4167bb79b9d9f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/ModdingAPI/DLS.ModdingAPI.asmdef b/Assets/Scripts/ModdingAPI/DLS.ModdingAPI.asmdef new file mode 100644 index 00000000..5f0922f9 --- /dev/null +++ b/Assets/Scripts/ModdingAPI/DLS.ModdingAPI.asmdef @@ -0,0 +1,3 @@ +{ + "name": "DLSModdingAPI" +} \ No newline at end of file diff --git a/Assets/Scripts/ModdingAPI/DLS.ModdingAPI.asmdef.meta b/Assets/Scripts/ModdingAPI/DLS.ModdingAPI.asmdef.meta new file mode 100644 index 00000000..6173a3fe --- /dev/null +++ b/Assets/Scripts/ModdingAPI/DLS.ModdingAPI.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 61a9784a30ea9384088f2051bf313a3e +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/ModdingAPI/DisplayBuilder.cs b/Assets/Scripts/ModdingAPI/DisplayBuilder.cs new file mode 100644 index 00000000..fb4deb9a --- /dev/null +++ b/Assets/Scripts/ModdingAPI/DisplayBuilder.cs @@ -0,0 +1,36 @@ +using System; +using UnityEngine; + +namespace DLS.ModdingAPI +{ + public class DisplayBuilder + { + public string modID; + public Vector2 Position; + public float Scale; + public Action DrawFunction; + + public DisplayBuilder(string modID) + { + this.modID = modID; + } + + public DisplayBuilder SetPosition(Vector2 position) + { + Position = position; + return this; + } + + public DisplayBuilder SetScale(float scale) + { + Scale = scale; + return this; + } + + public DisplayBuilder SetDrawFunction(Action drawFunction) + { + DrawFunction = drawFunction; + return this; + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/ModdingAPI/DisplayBuilder.cs.meta b/Assets/Scripts/ModdingAPI/DisplayBuilder.cs.meta new file mode 100644 index 00000000..75631ec5 --- /dev/null +++ b/Assets/Scripts/ModdingAPI/DisplayBuilder.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: debca0986f38b304ebe01b3c988e32b4 \ No newline at end of file diff --git a/Assets/Scripts/ModdingAPI/IMod.cs b/Assets/Scripts/ModdingAPI/IMod.cs new file mode 100644 index 00000000..2f2c924e --- /dev/null +++ b/Assets/Scripts/ModdingAPI/IMod.cs @@ -0,0 +1,53 @@ +using UnityEngine; + +namespace DLS.ModdingAPI +{ + public abstract class IMod + { + public abstract string ModID { get; } + public abstract string Name { get; } + public abstract string Version { get; } + public abstract void Initialize(); + + // Optional event methods with default implementations + public virtual void OnPlaceChip(ChipEventArgs chip) { } + public virtual void OnMoveChip(ChipEventArgs chip, Vector2 newPosition) { } + public virtual void OnPlaceWire(WireEventArgs wire) { } + public virtual void OnEditWire(WireEventArgs wire) { } + public virtual void OnProjectLoad(ProjectEventArgs project) { } + public virtual void OnProjectUnload(ProjectEventArgs project) { } + public virtual void OnMouseClick(InputEventArgs args) { } + + public enum MouseButton + { + Left = 0, + Right = 1, + Middle = 2 + } + + public struct ChipEventArgs + { + public string ChipName; + public Vector2 Position; + } + + public struct WireEventArgs + { + public string SourcePinName; + public string TargetPinName; + public int BitCount; + } + + public struct ProjectEventArgs + { + public string ProjectName; + public string FilePath; + } + + public struct InputEventArgs + { + public Vector2 Position; + public MouseButton Button; + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/ModdingAPI/IMod.cs.meta b/Assets/Scripts/ModdingAPI/IMod.cs.meta new file mode 100644 index 00000000..9495d021 --- /dev/null +++ b/Assets/Scripts/ModdingAPI/IMod.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 7fe56ef1ac2353c45baa0cdea81b9c8b \ No newline at end of file diff --git a/Assets/Scripts/ModdingAPI/PinBuilder.cs b/Assets/Scripts/ModdingAPI/PinBuilder.cs new file mode 100644 index 00000000..96b408bd --- /dev/null +++ b/Assets/Scripts/ModdingAPI/PinBuilder.cs @@ -0,0 +1,61 @@ +using UnityEngine; + +namespace DLS.ModdingAPI +{ + public struct PinDescription + { + public string Name; + public int ID; + public Vector2 Position; + public PinBitCount BitCount; + public PinColour Colour; + public PinValueDisplayMode ValueDisplayMode; + + public PinDescription(string name, int id, Vector2 position, PinBitCount bitCount, PinColour colour, PinValueDisplayMode valueDisplayMode) + { + Name = name; + ID = id; + Position = position; + BitCount = bitCount; + Colour = colour; + ValueDisplayMode = valueDisplayMode; + } + + public PinDescription(string name, int id) + { + Name = name; + ID = id; + Position = Vector2.zero; + BitCount = PinBitCount.Bit1; + Colour = PinColour.Red; + ValueDisplayMode = PinValueDisplayMode.Off; + } + } + + public enum PinBitCount + { + Bit1 = 1, + Bit4 = 4, + Bit8 = 8 + } + + public enum PinColour + { + Red, + Orange, + Yellow, + Green, + Blue, + Violet, + Pink, + White + } + + public enum PinValueDisplayMode + { + Off, + UnsignedDecimal, + SignedDecimal, + HEX + } +} \ No newline at end of file diff --git a/Assets/Scripts/ModdingAPI/PinBuilder.cs.meta b/Assets/Scripts/ModdingAPI/PinBuilder.cs.meta new file mode 100644 index 00000000..3d7b83a1 --- /dev/null +++ b/Assets/Scripts/ModdingAPI/PinBuilder.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 1dea80cfc7501f14c97a02b257032127 \ No newline at end of file diff --git a/Assets/Scripts/ModdingAPI/Registry.cs b/Assets/Scripts/ModdingAPI/Registry.cs new file mode 100644 index 00000000..4e1ff786 --- /dev/null +++ b/Assets/Scripts/ModdingAPI/Registry.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace DLS.ModdingAPI +{ + public static class Registry + { + public static List moddedChips = new(); + public static List moddedCollections = new(); + public static readonly Dictionary ModdedShortcuts = new(); + public static void RegisterChips(params ChipBuilder[] chips) + { + foreach (ChipBuilder chip in chips) + { + moddedChips.Add(chip); + } + } + + public static void RegisterCollections(params CollectionBuilder[] collections) + { + foreach (CollectionBuilder collection in collections) + { + moddedCollections.Add(collection); + } + } + + public static void RegisterShortcuts(params ShortcutBuilder[] shortcuts) + { + foreach (ShortcutBuilder shortcut in shortcuts) + { + if (!ModdedShortcuts.ContainsKey(shortcut.Name)) + { + ModdedShortcuts[shortcut.Name] = shortcut; + } + else + { + Debug.LogWarning($"Shortcut with name '{shortcut.Name}' is already registered."); + } + } + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/ModdingAPI/Registry.cs.meta b/Assets/Scripts/ModdingAPI/Registry.cs.meta new file mode 100644 index 00000000..dfbdc9e3 --- /dev/null +++ b/Assets/Scripts/ModdingAPI/Registry.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 608a3f0c921587f4d9eaa2b21f01ad20 \ No newline at end of file diff --git a/Assets/Scripts/ModdingAPI/ShortcutBuilder.cs b/Assets/Scripts/ModdingAPI/ShortcutBuilder.cs new file mode 100644 index 00000000..a63a1dc8 --- /dev/null +++ b/Assets/Scripts/ModdingAPI/ShortcutBuilder.cs @@ -0,0 +1,31 @@ +using System; +using UnityEngine; + +namespace DLS.ModdingAPI +{ + public class ShortcutBuilder + { + public readonly string modID; + public readonly string Name; + public KeyCode Key; + public Func ModifierCondition; + + public ShortcutBuilder(string modID, string name) + { + this.modID = modID; + Name = name; + } + + public ShortcutBuilder SetKey(KeyCode key) + { + Key = key; + return this; + } + + public ShortcutBuilder SetModifierCondition(Func modifierCondition) + { + ModifierCondition = modifierCondition; + return this; + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/ModdingAPI/ShortcutBuilder.cs.meta b/Assets/Scripts/ModdingAPI/ShortcutBuilder.cs.meta new file mode 100644 index 00000000..7f55217c --- /dev/null +++ b/Assets/Scripts/ModdingAPI/ShortcutBuilder.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: c8251fa4c880009479062b4ee6d192f7 \ No newline at end of file diff --git a/Assets/Scripts/Mods.meta b/Assets/Scripts/Mods.meta new file mode 100644 index 00000000..168c7681 --- /dev/null +++ b/Assets/Scripts/Mods.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d2c495a8eb211094f8aa588da8bc03ec +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Mods/ModLoader.cs b/Assets/Scripts/Mods/ModLoader.cs new file mode 100644 index 00000000..9694c8f8 --- /dev/null +++ b/Assets/Scripts/Mods/ModLoader.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using DLS.ModdingAPI; +using UnityEngine; + +namespace DLS.Mods +{ + public static class ModLoader + { + public static readonly List loadedMods = new(); + + public static void NotifyMods(Action action, T arg) + { + foreach (IMod mod in loadedMods) + { + action(mod, arg); + } + } + + public static void NotifyMods(Action action) + { + foreach (IMod mod in loadedMods) + { + action(mod); + } + } + + public static void InitializeMods(string modsDirectory) + { + Debug.Log("Loading mods..."); + foreach (string dllPath in Directory.GetFiles(modsDirectory, "*.dls")) + { + try + { + Assembly modAssembly = Assembly.LoadFrom(dllPath); + + foreach (Type type in modAssembly.GetTypes().Where(t => typeof(IMod).IsAssignableFrom(t) && !t.IsAbstract)) + { + IMod modInstance = (IMod) Activator.CreateInstance(type); + loadedMods.Add(modInstance); + modInstance.Initialize(); + Debug.Log($"Loaded mod: {modInstance.Name} v{modInstance.Version}"); + } + } + catch (ReflectionTypeLoadException ex) + { + foreach (Exception inner in ex.LoaderExceptions) + { + Debug.LogError(inner.Message); + } + } + catch (FileLoadException ex) + { + Debug.LogError(ex.Message); + } + } + } + + public static bool IsModLoaded(string modId) + { + foreach (IMod mod in loadedMods) + { + if (mod.ModID == modId) + { + return true; + } + } + return false; + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/Mods/ModLoader.cs.meta b/Assets/Scripts/Mods/ModLoader.cs.meta new file mode 100644 index 00000000..c2bf5efb --- /dev/null +++ b/Assets/Scripts/Mods/ModLoader.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: ab9befd72784fff46bbe84536e22cf9a \ No newline at end of file diff --git a/Assets/Scripts/SaveSystem/DescriptionCreator.cs b/Assets/Scripts/SaveSystem/DescriptionCreator.cs index 196e0ea1..ba4a1af9 100644 --- a/Assets/Scripts/SaveSystem/DescriptionCreator.cs +++ b/Assets/Scripts/SaveSystem/DescriptionCreator.cs @@ -27,6 +27,19 @@ public static ChipDescription CreateChipDescription(DevChipInstance chip) Vector2 minChipsSize = SubChipInstance.CalculateMinChipSize(inputPins, outputPins, name); size = Vector2.Max(minChipsSize, size); + // Update DependsOnModIDs if any subchip is a modded chip + List dependsOnModIDs = new(); + foreach (var subchip in chip.GetSubchips()) + { + if (subchip.Description.DependsOnModIDs != null && subchip.Description.DependsOnModIDs.Count != 0) + { + dependsOnModIDs.AddRange(subchip.Description.DependsOnModIDs); + } + } + + // Remove duplicates + dependsOnModIDs = dependsOnModIDs.Distinct().ToList(); + UpdateWireIndicesForDescriptionCreation(chip); // Create and return the chip description @@ -43,7 +56,8 @@ public static ChipDescription CreateChipDescription(DevChipInstance chip) OutputPins = outputPins, Wires = chip.Wires.Select(CreateWireDescription).ToArray(), Displays = displays, - ChipType = ChipType.Custom + ChipType = ChipType.Custom, + DependsOnModIDs = dependsOnModIDs }; } diff --git a/Assets/Scripts/SaveSystem/Loader.cs b/Assets/Scripts/SaveSystem/Loader.cs index 590a3af1..537ca64c 100644 --- a/Assets/Scripts/SaveSystem/Loader.cs +++ b/Assets/Scripts/SaveSystem/Loader.cs @@ -4,6 +4,9 @@ using System.Linq; using DLS.Description; using DLS.Game; +using DLS.Graphics; +using DLS.Mods; +using UnityEngine; namespace DLS.SaveSystem { @@ -80,6 +83,8 @@ public static ProjectDescription[] LoadAllProjectDescriptions() static ChipLibrary LoadChipLibrary(ProjectDescription projectDescription) { + ChipDescription[] moddedChips = ModdedChipCreator.CreateAllModdedChipDescriptions(); + string chipDirectoryPath = SavePaths.GetChipsPath(projectDescription.ProjectName); ChipDescription[] loadedChips = new ChipDescription[projectDescription.AllCustomChipNames.Length]; @@ -98,13 +103,12 @@ static ChipLibrary LoadChipLibrary(ProjectDescription projectDescription) customChipNameHashset.Add(chipDesc.Name); } - // If built-in chip name conflicts with a custom chip, the built-in chip must have been added in a newer version. // In that case, simply exclude the built-in chip. TODO: warn player that they should rename their chip if they want access to new builtin version builtinChips = builtinChips.Where(b => !customChipNameHashset.Contains(b.Name)).ToArray(); UpgradeHelper.ApplyVersionChanges(loadedChips, builtinChips); - return new ChipLibrary(loadedChips, builtinChips); + return new ChipLibrary(loadedChips, builtinChips, moddedChips); } } } \ No newline at end of file diff --git a/Assets/Scripts/SaveSystem/SavePaths.cs b/Assets/Scripts/SaveSystem/SavePaths.cs index 7fe94974..64e495c6 100644 --- a/Assets/Scripts/SaveSystem/SavePaths.cs +++ b/Assets/Scripts/SaveSystem/SavePaths.cs @@ -16,6 +16,7 @@ public static class SavePaths public static readonly string ProjectsPath = Path.Combine(AllData, "Projects"); public static readonly string DeletedProjectsPath = Path.Combine(AllData, "Deleted Projects"); public static readonly string AppSettingsPath = Path.Combine(AllData, "AppSettings.json"); + public static readonly string ModsPath = Path.Combine(AllData, "Mods"); public static void EnsureDirectoryExists(string directoryPath) => Directory.CreateDirectory(directoryPath); diff --git a/Assets/Scripts/Simulation/SimChip.cs b/Assets/Scripts/Simulation/SimChip.cs index 5efec7c5..ffc868ff 100644 --- a/Assets/Scripts/Simulation/SimChip.cs +++ b/Assets/Scripts/Simulation/SimChip.cs @@ -20,6 +20,8 @@ public class SimChip public SimPin[] OutputPins = Array.Empty(); public SimChip[] SubChips = Array.Empty(); + public ChipDescription Description {get; private set;} + public SimChip() { @@ -32,6 +34,7 @@ public SimChip(ChipDescription desc, int id, uint[] internalState, SimChip[] sub ID = id; ChipType = desc.ChipType; IsBuiltin = ChipType != ChipType.Custom; + Description = desc; // ---- Create pins (don't allocate unnecessarily as very many sim chips maybe created!) ---- if (desc.InputPins.Length > 0) diff --git a/Assets/Scripts/Simulation/Simulator.cs b/Assets/Scripts/Simulation/Simulator.cs index 1d777628..93407527 100644 --- a/Assets/Scripts/Simulation/Simulator.cs +++ b/Assets/Scripts/Simulation/Simulator.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Concurrent; using System.Diagnostics; +using System.Linq; using DLS.Description; using DLS.Game; using Random = System.Random; @@ -506,6 +507,23 @@ static void ProcessBuiltinChip(SimChip chip) audioState.RegisterNote(freqIndex, (uint)volumeIndex); break; } + case ChipType.Modded: + { + if (ModdedChipCreator.TryGetSimulationFunction(chip.Description, out var simulationFunction)) + { + uint[] inputStates = chip.InputPins.Select(pin => (uint) PinState.GetBitStates(pin.State)).ToArray(); + uint[] outputStates = chip.OutputPins.Select(pin => (uint) PinState.GetBitStates(pin.State)).ToArray(); + + // Call the modded chip's simulation function + simulationFunction(inputStates, outputStates); + + for (int i = 0; i < chip.OutputPins.Length; i++) + { + chip.OutputPins[i].State = outputStates[i]; + } + } + break; + } // ---- Bus types ---- default: { diff --git a/ProjectSettings/ProjectSettings.asset b/ProjectSettings/ProjectSettings.asset index 8908a3b3..3eb5fee1 100644 --- a/ProjectSettings/ProjectSettings.asset +++ b/ProjectSettings/ProjectSettings.asset @@ -733,7 +733,7 @@ PlayerSettings: PS4: 1 PS5: 1 Stadia: 1 - Standalone: 2 + Standalone: 4 WebGL: 1 Windows Store Apps: 1 XboxOne: 1 @@ -748,7 +748,7 @@ PlayerSettings: gcIncremental: 0 gcWBarrierValidation: 0 apiCompatibilityLevelPerPlatform: {} - editorAssembliesCompatibilityLevel: 1 + editorAssembliesCompatibilityLevel: 2 m_RenderingPath: 1 m_MobileRenderingPath: 1 metroPackageName: 2D_BuiltInRenderer diff --git a/ProjectSettings/ProjectVersion.txt b/ProjectSettings/ProjectVersion.txt index 8678d93f..7b22109e 100644 --- a/ProjectSettings/ProjectVersion.txt +++ b/ProjectSettings/ProjectVersion.txt @@ -1,2 +1,2 @@ -m_EditorVersion: 6000.0.46f1 -m_EditorVersionWithRevision: 6000.0.46f1 (fb93bc360d3a) +m_EditorVersion: 6000.0.47f1 +m_EditorVersionWithRevision: 6000.0.47f1 (2ad1ed33fd3b) diff --git a/TestData/Mods/ExampleMod.dls.gone b/TestData/Mods/ExampleMod.dls.gone new file mode 100644 index 00000000..383686a2 Binary files /dev/null and b/TestData/Mods/ExampleMod.dls.gone differ diff --git a/TestData/Projects/MainTest/Chips/display test.json b/TestData/Projects/MainTest/Chips/display test.json new file mode 100644 index 00000000..c1eb7532 --- /dev/null +++ b/TestData/Projects/MainTest/Chips/display test.json @@ -0,0 +1,73 @@ +{ + "DLSVersion": "2.1.5", + "DependsOnModIDs":[ + "exmod" + ], + "Name": "display test", + "NameLocation": 0, + "ChipType": 0, + "Size": { + "x": 1.25, + "y": 0.5 + }, + "Colour": { + "r": 0.378672957, + "g": 0.4155134, + "b": 0.5271267, + "a": 1 + }, + "InputPins":[ + { + "Name":"IN", + "ID":2122438459, + "Position":{ + "x":-2.38431, + "y":1.92406 + }, + "BitCount":8, + "Colour":0, + "ValueDisplayMode":0 + } + ], + "OutputPins":[], + "SubChips":[ + { + "Name":"HUGE-LED", + "ID":1102152879, + "Label":"", + "Position":{ + "x":0.75733, + "y":1.38436 + }, + "OutputPinColourInfo":[], + "InternalData":null + } + ], + "Wires":[ + { + "SourcePinAddress":{ + "PinID":0, + "PinOwnerID":2122438459 + }, + "TargetPinAddress":{ + "PinID":0, + "PinOwnerID":1102152879 + }, + "ConnectionType":0, + "ConnectedWireIndex":-1, + "ConnectedWireSegmentIndex":-1, + "Points":[{"x":0.0,"y":0.0},{"x":0.0,"y":0.0}] + } + ], + "Displays":[ + { + "SubChipID":1102152879, + "Position":{ + "x":-0.00286, + "y":-0.0116 + }, + "Scale":0.202367663, + "DependsOnModIDs":[] + } + ] +} \ No newline at end of file diff --git a/TestData/Projects/MainTest/ProjectDescription.json b/TestData/Projects/MainTest/ProjectDescription.json index 6d1823ff..fdfcc9e4 100644 --- a/TestData/Projects/MainTest/ProjectDescription.json +++ b/TestData/Projects/MainTest/ProjectDescription.json @@ -1,9 +1,10 @@ { "ProjectName": "MainTest", "DLSVersion_LastSaved": "2.1.5", + "DLSVersion_LastSaved": "2.1.5", "DLSVersion_EarliestCompatible": "2.0.0", - "CreationTime": "2025-03-14T18:23:30.404+01:00", - "LastSaveTime": "2025-05-04T09:15:41.061+02:00", + "CreationTime": "2025-03-14T17:23:30.404+00:00", + "LastSaveTime": "2025-05-05T21:33:27.513+01:00", "Prefs_MainPinNamesDisplayMode": 2, "Prefs_ChipPinNamesDisplayMode": 1, "Prefs_GridDisplayMode": 1, @@ -140,7 +141,7 @@ "Name":"TEST" }, { - "Chips":["PULSE","TEST MergeSplit"], + "Chips":["PULSE","TEST MergeSplit","#","PASS","CONST","HUGE-LED"], "IsToggledOpen":true, "Name":"OTHER" }