Dev changes #5
@@ -0,0 +1,17 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using OmegaLeo.HelperLib.Game.Models;
|
||||||
|
|
||||||
|
namespace OmegaLeo.HelperLib.Game.Extensions
|
||||||
|
{
|
||||||
|
public static class ListExtensions
|
||||||
|
{
|
||||||
|
public static void Shuffle<T>(this IList<T> list, RandomNumberGenerator rng)
|
||||||
|
{
|
||||||
|
for (int i = list.Count - 1; i > 0; i--)
|
||||||
|
{
|
||||||
|
int j = rng.Range(0, i + 1);
|
||||||
|
(list[i], list[j]) = (list[j], list[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
using OmegaLeo.HelperLib.Game.Models;
|
||||||
|
|
||||||
|
namespace OmegaLeo.HelperLib.Game.Handlers
|
||||||
|
{
|
||||||
|
public class DelayedActionHandler
|
||||||
|
{
|
||||||
|
private readonly DelayedAction _action;
|
||||||
|
|
||||||
|
internal DelayedActionHandler(DelayedAction action)
|
||||||
|
{
|
||||||
|
_action = action;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Cancel()
|
||||||
|
{
|
||||||
|
if (_action != null)
|
||||||
|
_action.IsCancelled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsCancelled => _action?.IsCancelled ?? true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace OmegaLeo.HelperLib.Game.Interfaces
|
||||||
|
{
|
||||||
|
public interface ISaveFileProvider
|
||||||
|
{
|
||||||
|
string GetSavePath(string fileName);
|
||||||
|
string GetSaveDirectory();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace OmegaLeo.HelperLib.Game.Models
|
||||||
|
{
|
||||||
|
public class ConsoleCommand
|
||||||
|
{
|
||||||
|
public string Name { get; }
|
||||||
|
public string Description { get; }
|
||||||
|
public Action<string[]> Action { get; }
|
||||||
|
public int MinArgs { get; }
|
||||||
|
public int MaxArgs { get; }
|
||||||
|
|
||||||
|
public ConsoleCommand(string name, string description, Action<string[]> action, int minArgs = 0, int maxArgs = -1)
|
||||||
|
{
|
||||||
|
Name = name ?? throw new ArgumentNullException(nameof(name));
|
||||||
|
Description = description ?? "No description";
|
||||||
|
Action = action ?? throw new ArgumentNullException(nameof(action));
|
||||||
|
MinArgs = minArgs;
|
||||||
|
MaxArgs = maxArgs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace OmegaLeo.HelperLib.Game.Models
|
||||||
|
{
|
||||||
|
internal class DelayedAction
|
||||||
|
{
|
||||||
|
public float TimeRemaining { get; set; }
|
||||||
|
public float InitialDelay { get; }
|
||||||
|
public Action Action { get; }
|
||||||
|
public bool IsRepeating { get; }
|
||||||
|
public bool IsCancelled { get; set; }
|
||||||
|
|
||||||
|
public DelayedAction(float delay, Action action, bool isRepeating)
|
||||||
|
{
|
||||||
|
TimeRemaining = delay;
|
||||||
|
InitialDelay = delay;
|
||||||
|
Action = action;
|
||||||
|
IsRepeating = isRepeating;
|
||||||
|
IsCancelled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
namespace OmegaLeo.HelperLib.Game.Models
|
||||||
|
{
|
||||||
|
[System.Serializable]
|
||||||
|
public class Loot<T>
|
||||||
|
{
|
||||||
|
public T Item { get; set; }
|
||||||
|
public float Weight { get; set; }
|
||||||
|
|
||||||
|
public Loot(T item, float quantity)
|
||||||
|
{
|
||||||
|
Item = item;
|
||||||
|
Weight = quantity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace OmegaLeo.HelperLib.Game.Models
|
||||||
|
{
|
||||||
|
public class LootTable<T>
|
||||||
|
{
|
||||||
|
public List<Loot<T>> Loot = new List<Loot<T>>();
|
||||||
|
|
||||||
|
public void Add(T item, float weight)
|
||||||
|
{
|
||||||
|
Loot.Add(new Loot<T>(item, weight));
|
||||||
|
}
|
||||||
|
|
||||||
|
public T Roll(RandomNumberGenerator rng)
|
||||||
|
{
|
||||||
|
float total = Loot.Sum(e => e.Weight);
|
||||||
|
float roll = rng.Range(0f, total);
|
||||||
|
|
||||||
|
foreach (var e in Loot)
|
||||||
|
{
|
||||||
|
roll -= e.Weight;
|
||||||
|
if (roll <= 0)
|
||||||
|
return e.Item;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Loot.Last().Item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace OmegaLeo.HelperLib.Game.Models
|
||||||
|
{
|
||||||
|
public class RandomNumberGenerator
|
||||||
|
{
|
||||||
|
private readonly Random _random;
|
||||||
|
|
||||||
|
public int Seed { get; }
|
||||||
|
|
||||||
|
public RandomNumberGenerator(int? seed = null)
|
||||||
|
{
|
||||||
|
Seed = seed ?? Environment.TickCount;
|
||||||
|
_random = new Random(Seed);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float NextFloat() =>
|
||||||
|
(float)_random.NextDouble();
|
||||||
|
|
||||||
|
public float Range(float min, float max) =>
|
||||||
|
min + NextFloat() * (max - min);
|
||||||
|
|
||||||
|
public int Range(int min, int max) =>
|
||||||
|
_random.Next(min, max);
|
||||||
|
|
||||||
|
public bool Chance(float probability) =>
|
||||||
|
NextFloat() <= probability;
|
||||||
|
|
||||||
|
public bool Percent(float percent) =>
|
||||||
|
Chance(percent / 100f);
|
||||||
|
|
||||||
|
public bool OneIn(int x) =>
|
||||||
|
Range(0, x) == 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace OmegaLeo.HelperLib.Game.Models
|
||||||
|
{
|
||||||
|
[Serializable]
|
||||||
|
public class SaveData
|
||||||
|
{
|
||||||
|
public Guid Id;
|
||||||
|
|
||||||
|
public SaveData()
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netstandard2.1</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\OmegaLeo.HelperLib\OmegaLeo.HelperLib.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
namespace OmegaLeo.HelperLib.Game.Providers
|
||||||
|
{
|
||||||
|
#if GODOT
|
||||||
|
public partial class GodotDelayedActionRunner : Node
|
||||||
|
{
|
||||||
|
private static GodotDelayedActionRunner _instance;
|
||||||
|
public static DelayedActionManager Manager { get; private set; }
|
||||||
|
|
||||||
|
public override void _Ready()
|
||||||
|
{
|
||||||
|
if (_instance == null)
|
||||||
|
{
|
||||||
|
_instance = this;
|
||||||
|
Manager = new DelayedActionManager();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void _Process(double delta)
|
||||||
|
{
|
||||||
|
Manager?.Update((float)delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void _ExitTree()
|
||||||
|
{
|
||||||
|
if (_instance == this)
|
||||||
|
{
|
||||||
|
Manager?.CancelAll();
|
||||||
|
_instance = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
using System.IO;
|
||||||
|
using OmegaLeo.HelperLib.Game.Interfaces;
|
||||||
|
|
||||||
|
namespace OmegaLeo.HelperLib.Game.Providers
|
||||||
|
{
|
||||||
|
#if GODOT
|
||||||
|
public class GodotSaveFileProvider : ISaveFileProvider
|
||||||
|
{
|
||||||
|
private readonly string _saveFolder;
|
||||||
|
|
||||||
|
public GodotSaveFileProvider(string saveFolder = "Saves")
|
||||||
|
{
|
||||||
|
_saveFolder = saveFolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetSaveDirectory()
|
||||||
|
{
|
||||||
|
return Path.Combine("user://", _saveFolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetSavePath(string fileName)
|
||||||
|
{
|
||||||
|
if (!fileName.EndsWith(".json"))
|
||||||
|
fileName += ".json";
|
||||||
|
|
||||||
|
return Path.Combine(GetSaveDirectory(), fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
namespace OmegaLeo.HelperLib.Game.Providers
|
||||||
|
{
|
||||||
|
#if UNITY_EDITOR || UNITY_STANDALONE
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace OmegaLeo.HelperLib.DelayedActions
|
||||||
|
{
|
||||||
|
public class UnityDelayedActionRunner : MonoBehaviour
|
||||||
|
{
|
||||||
|
private static UnityDelayedActionRunner _instance;
|
||||||
|
public static DelayedActionManager Manager { get; private set; }
|
||||||
|
|
||||||
|
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
|
||||||
|
private static void Initialize()
|
||||||
|
{
|
||||||
|
if (_instance == null)
|
||||||
|
{
|
||||||
|
var go = new GameObject("[DelayedActionRunner]");
|
||||||
|
_instance = go.AddComponent<UnityDelayedActionRunner>();
|
||||||
|
DontDestroyOnLoad(go);
|
||||||
|
Manager = new DelayedActionManager();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
Manager?.Update(Time.deltaTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDestroy()
|
||||||
|
{
|
||||||
|
if (_instance == this)
|
||||||
|
{
|
||||||
|
Manager?.CancelAll();
|
||||||
|
_instance = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
namespace OmegaLeo.HelperLib.Game.Providers
|
||||||
|
{
|
||||||
|
#if UNITY_EDITOR || UNITY_STANDALONE
|
||||||
|
using System.IO;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace OmegaLeo.HelperLib.SaveSystem
|
||||||
|
{
|
||||||
|
public class UnitySaveFileProvider : ISaveFileProvider
|
||||||
|
{
|
||||||
|
private readonly string _saveFolder;
|
||||||
|
|
||||||
|
public UnitySaveFileProvider(string saveFolder = "Saves")
|
||||||
|
{
|
||||||
|
_saveFolder = saveFolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetSaveDirectory()
|
||||||
|
{
|
||||||
|
return Path.Combine(Application.persistentDataPath, _saveFolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetSavePath(string fileName)
|
||||||
|
{
|
||||||
|
if (!fileName.EndsWith(".json"))
|
||||||
|
fileName += ".json";
|
||||||
|
|
||||||
|
return Path.Combine(GetSaveDirectory(), fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
@@ -0,0 +1,200 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NetFlow.DocumentationHelper.Library.Attributes;
|
||||||
|
using OmegaLeo.HelperLib.Game.Models;
|
||||||
|
|
||||||
|
namespace OmegaLeo.HelperLib.Game.Services
|
||||||
|
{
|
||||||
|
public class CommandConsole
|
||||||
|
{
|
||||||
|
private readonly Dictionary<string, ConsoleCommand> _commands = new Dictionary<string, ConsoleCommand>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
private readonly List<string> _history = new List<string>();
|
||||||
|
private int _maxHistorySize = 100;
|
||||||
|
|
||||||
|
public event Action<string> OnOutput;
|
||||||
|
public event Action<string> OnError;
|
||||||
|
|
||||||
|
public IReadOnlyList<string> History => _history.AsReadOnly();
|
||||||
|
public IReadOnlyDictionary<string, ConsoleCommand> Commands => _commands;
|
||||||
|
|
||||||
|
[Documentation(nameof(RegisterCommand), "Registers a new command with the console.")]
|
||||||
|
public void RegisterCommand(string name, string description, Action<string[]> action, int minArgs = 0, int maxArgs = -1)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(name))
|
||||||
|
throw new ArgumentException("Command name cannot be empty", nameof(name));
|
||||||
|
|
||||||
|
var command = new ConsoleCommand(name, description, action, minArgs, maxArgs);
|
||||||
|
_commands[name] = command;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Documentation(nameof(RegisterCommand), "Registers a new command with the console using a delegate.")]
|
||||||
|
public void RegisterCommand(string name, string description, Func<string[], string> func, int minArgs = 0, int maxArgs = -1)
|
||||||
|
{
|
||||||
|
RegisterCommand(name, description, args =>
|
||||||
|
{
|
||||||
|
string result = func(args);
|
||||||
|
if (!string.IsNullOrEmpty(result))
|
||||||
|
Output(result);
|
||||||
|
}, minArgs, maxArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Documentation(nameof(UnregisterCommand), "Removes a command from the console.")]
|
||||||
|
public bool UnregisterCommand(string name)
|
||||||
|
{
|
||||||
|
return _commands.Remove(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Documentation(nameof(ExecuteCommand), "Executes a command string.")]
|
||||||
|
public bool ExecuteCommand(string input)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(input))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
_history.Add(input);
|
||||||
|
if (_history.Count > _maxHistorySize)
|
||||||
|
_history.RemoveAt(0);
|
||||||
|
|
||||||
|
string[] parts = ParseCommandLine(input);
|
||||||
|
if (parts.Length == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
string commandName = parts[0];
|
||||||
|
string[] args = parts.Skip(1).ToArray();
|
||||||
|
|
||||||
|
if (!_commands.TryGetValue(commandName, out var command))
|
||||||
|
{
|
||||||
|
Error($"Unknown command: {commandName}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.MinArgs > 0 && args.Length < command.MinArgs)
|
||||||
|
{
|
||||||
|
Error($"Command '{commandName}' requires at least {command.MinArgs} argument(s)");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.MaxArgs >= 0 && args.Length > command.MaxArgs)
|
||||||
|
{
|
||||||
|
Error($"Command '{commandName}' accepts at most {command.MaxArgs} argument(s)");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
command.Action(args);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Error($"Error executing command '{commandName}': {ex.Message}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Documentation(nameof(Output), "Outputs a message to the console.")]
|
||||||
|
public void Output(string message)
|
||||||
|
{
|
||||||
|
OnOutput?.Invoke(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Documentation(nameof(Error), "Outputs an error message to the console.")]
|
||||||
|
public void Error(string message)
|
||||||
|
{
|
||||||
|
OnError?.Invoke(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Documentation(nameof(ClearHistory), "Clears command history.")]
|
||||||
|
public void ClearHistory()
|
||||||
|
{
|
||||||
|
_history.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Documentation(nameof(GetAutoComplete), "Gets autocomplete suggestions for partial input.")]
|
||||||
|
public string[] GetAutoComplete(string partial)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(partial))
|
||||||
|
return _commands.Keys.ToArray();
|
||||||
|
|
||||||
|
return _commands.Keys
|
||||||
|
.Where(cmd => cmd.StartsWith(partial, StringComparison.OrdinalIgnoreCase))
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private string[] ParseCommandLine(string input)
|
||||||
|
{
|
||||||
|
var result = new List<string>();
|
||||||
|
bool inQuotes = false;
|
||||||
|
string current = "";
|
||||||
|
|
||||||
|
for (int i = 0; i < input.Length; i++)
|
||||||
|
{
|
||||||
|
char c = input[i];
|
||||||
|
|
||||||
|
if (c == '"')
|
||||||
|
{
|
||||||
|
inQuotes = !inQuotes;
|
||||||
|
}
|
||||||
|
else if (char.IsWhiteSpace(c) && !inQuotes)
|
||||||
|
{
|
||||||
|
if (current.Length > 0)
|
||||||
|
{
|
||||||
|
result.Add(current);
|
||||||
|
current = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
current += c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current.Length > 0)
|
||||||
|
result.Add(current);
|
||||||
|
|
||||||
|
return result.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Documentation(nameof(RegisterDefaultCommands), "Registers built-in commands.")]
|
||||||
|
public void RegisterDefaultCommands()
|
||||||
|
{
|
||||||
|
RegisterCommand("help", "Lists all available commands", args =>
|
||||||
|
{
|
||||||
|
if (args.Length == 0)
|
||||||
|
{
|
||||||
|
Output("Available commands:");
|
||||||
|
foreach (var cmd in _commands.Values.OrderBy(c => c.Name))
|
||||||
|
{
|
||||||
|
Output($" {cmd.Name} - {cmd.Description}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
string cmdName = args[0];
|
||||||
|
if (_commands.TryGetValue(cmdName, out var cmd))
|
||||||
|
{
|
||||||
|
Output($"{cmd.Name}: {cmd.Description}");
|
||||||
|
Output($"Min args: {cmd.MinArgs}, Max args: {(cmd.MaxArgs >= 0 ? cmd.MaxArgs.ToString() : "unlimited")}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Error($"Unknown command: {cmdName}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 0, 1);
|
||||||
|
|
||||||
|
RegisterCommand("clear", "Clears command history", _ => ClearHistory());
|
||||||
|
|
||||||
|
RegisterCommand("echo", "Echoes back the input", args =>
|
||||||
|
{
|
||||||
|
Output(string.Join(" ", args));
|
||||||
|
}, 1);
|
||||||
|
|
||||||
|
RegisterCommand("commands", "Lists all registered commands", _ =>
|
||||||
|
{
|
||||||
|
Output($"Total commands: {_commands.Count}");
|
||||||
|
Output(string.Join(", ", _commands.Keys.OrderBy(k => k)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using NetFlow.DocumentationHelper.Library.Attributes;
|
||||||
|
using OmegaLeo.HelperLib.Game.Handlers;
|
||||||
|
using OmegaLeo.HelperLib.Game.Models;
|
||||||
|
|
||||||
|
namespace OmegaLeo.HelperLib.Game.Services
|
||||||
|
{
|
||||||
|
public class DelayedActionManager
|
||||||
|
{
|
||||||
|
private readonly List<DelayedAction> _pendingActions = new List<DelayedAction>();
|
||||||
|
private readonly List<DelayedAction> _actionsToRemove = new List<DelayedAction>();
|
||||||
|
|
||||||
|
[Documentation(nameof(RunAfter), "Schedules an action to run after a specified delay.")]
|
||||||
|
public void RunAfter(float delaySeconds, Action action)
|
||||||
|
{
|
||||||
|
_pendingActions.Add(new DelayedAction(delaySeconds, action, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Documentation(nameof(RunRepeating), "Schedules an action to run repeatedly at specified intervals.")]
|
||||||
|
public void RunRepeating(float intervalSeconds, Action action)
|
||||||
|
{
|
||||||
|
_pendingActions.Add(new DelayedAction(intervalSeconds, action, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Documentation(nameof(RunAfterCancellable), "Schedules an action to run after a delay and returns a handler to cancel it if needed.")]
|
||||||
|
public DelayedActionHandler RunAfterCancellable(float delaySeconds, Action action)
|
||||||
|
{
|
||||||
|
var delayedAction = new DelayedAction(delaySeconds, action, false);
|
||||||
|
_pendingActions.Add(delayedAction);
|
||||||
|
return new DelayedActionHandler(delayedAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update(float deltaTime)
|
||||||
|
{
|
||||||
|
_actionsToRemove.Clear();
|
||||||
|
|
||||||
|
foreach (var action in _pendingActions)
|
||||||
|
{
|
||||||
|
action.TimeRemaining -= deltaTime;
|
||||||
|
|
||||||
|
if (action.TimeRemaining <= 0)
|
||||||
|
{
|
||||||
|
if (!action.IsCancelled)
|
||||||
|
action.Action?.Invoke();
|
||||||
|
|
||||||
|
if (action.IsRepeating && !action.IsCancelled)
|
||||||
|
{
|
||||||
|
action.TimeRemaining = action.InitialDelay;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_actionsToRemove.Add(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var action in _actionsToRemove)
|
||||||
|
{
|
||||||
|
_pendingActions.Remove(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Documentation(nameof(CancelAll), "Cancels all pending delayed actions.")]
|
||||||
|
public void CancelAll()
|
||||||
|
{
|
||||||
|
_pendingActions.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Documentation(nameof(PendingActionsCount), "Gets the count of pending delayed actions.")]
|
||||||
|
public int PendingActionsCount => _pendingActions.Count;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using NetFlow.DocumentationHelper.Library.Attributes;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using OmegaLeo.HelperLib.Game.Interfaces;
|
||||||
|
|
||||||
|
namespace OmegaLeo.HelperLib.Game.Services
|
||||||
|
{
|
||||||
|
public class SaveManager
|
||||||
|
{
|
||||||
|
private readonly ISaveFileProvider _fileProvider;
|
||||||
|
private readonly JsonSerializerSettings _jsonOptions;
|
||||||
|
|
||||||
|
public SaveManager(ISaveFileProvider fileProvider)
|
||||||
|
{
|
||||||
|
_fileProvider = fileProvider ?? throw new ArgumentNullException(nameof(fileProvider));
|
||||||
|
_jsonOptions = new JsonSerializerSettings
|
||||||
|
{
|
||||||
|
TypeNameHandling = TypeNameHandling.Auto,
|
||||||
|
Formatting = Formatting.Indented
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[Documentation(nameof(Save), "Saves data to a JSON file.")]
|
||||||
|
public void Save<T>(string fileName, T data)
|
||||||
|
{
|
||||||
|
string json = JsonConvert.SerializeObject(data, _jsonOptions);
|
||||||
|
string path = _fileProvider.GetSavePath(fileName);
|
||||||
|
string directory = Path.GetDirectoryName(path);
|
||||||
|
|
||||||
|
if (!Directory.Exists(directory))
|
||||||
|
Directory.CreateDirectory(directory);
|
||||||
|
|
||||||
|
File.WriteAllText(path, json);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Documentation(nameof(Load), "Loads data from a JSON file.")]
|
||||||
|
public T Load<T>(string fileName)
|
||||||
|
{
|
||||||
|
string path = _fileProvider.GetSavePath(fileName);
|
||||||
|
|
||||||
|
if (!File.Exists(path))
|
||||||
|
throw new FileNotFoundException($"Save file not found: {fileName}");
|
||||||
|
|
||||||
|
string json = File.ReadAllText(path);
|
||||||
|
return JsonConvert.DeserializeObject<T>(json, _jsonOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Documentation(nameof(TryLoad), "Attempts to load data from a JSON file, returning a success flag.")]
|
||||||
|
public bool TryLoad<T>(string fileName, out T data)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
data = Load<T>(fileName);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
data = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Documentation(nameof(SaveExists), "Checks if a save file exists.")]
|
||||||
|
public bool SaveExists(string fileName)
|
||||||
|
{
|
||||||
|
return File.Exists(_fileProvider.GetSavePath(fileName));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Documentation(nameof(DeleteSave), "Deletes a save file.")]
|
||||||
|
public void DeleteSave(string fileName)
|
||||||
|
{
|
||||||
|
string path = _fileProvider.GetSavePath(fileName);
|
||||||
|
if (File.Exists(path))
|
||||||
|
File.Delete(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Documentation(nameof(GetAllSaveFiles), "Retrieves all save files in the save directory.")]
|
||||||
|
public string[] GetAllSaveFiles()
|
||||||
|
{
|
||||||
|
string savePath = _fileProvider.GetSaveDirectory();
|
||||||
|
if (!Directory.Exists(savePath))
|
||||||
|
return Array.Empty<string>();
|
||||||
|
|
||||||
|
return Directory.GetFiles(savePath, "*.json");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
// File: `OmegaLeo.HelperLib.Game/Commands/GodotConsoleUI.cs`
|
||||||
|
#if GODOT
|
||||||
|
using Godot;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace OmegaLeo.HelperLib.Game.UI
|
||||||
|
{
|
||||||
|
public partial class GodotConsoleUI : Control
|
||||||
|
{
|
||||||
|
private CommandConsole _console;
|
||||||
|
private LineEdit _inputField;
|
||||||
|
private RichTextLabel _outputText;
|
||||||
|
private ScrollContainer _scrollContainer;
|
||||||
|
private int _historyIndex = -1;
|
||||||
|
|
||||||
|
public override void _Ready()
|
||||||
|
{
|
||||||
|
_console = new CommandConsole();
|
||||||
|
_console.RegisterDefaultCommands();
|
||||||
|
_console.OnOutput += AddOutput;
|
||||||
|
_console.OnError += AddError;
|
||||||
|
|
||||||
|
SetupUI();
|
||||||
|
RegisterGodotCommands();
|
||||||
|
Visible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void _Input(InputEvent @event)
|
||||||
|
{
|
||||||
|
if (@event is InputEventKey keyEvent && keyEvent.Pressed)
|
||||||
|
{
|
||||||
|
if (keyEvent.Keycode == Key.Quoteleft)
|
||||||
|
{
|
||||||
|
ToggleConsole();
|
||||||
|
GetViewport().SetInputAsHandled();
|
||||||
|
}
|
||||||
|
else if (Visible && _inputField.HasFocus())
|
||||||
|
{
|
||||||
|
if (keyEvent.Keycode == Key.Enter)
|
||||||
|
{
|
||||||
|
ExecuteInput();
|
||||||
|
GetViewport().SetInputAsHandled();
|
||||||
|
}
|
||||||
|
else if (keyEvent.Keycode == Key.Up)
|
||||||
|
{
|
||||||
|
NavigateHistory(-1);
|
||||||
|
GetViewport().SetInputAsHandled();
|
||||||
|
}
|
||||||
|
else if (keyEvent.Keycode == Key.Down)
|
||||||
|
{
|
||||||
|
NavigateHistory(1);
|
||||||
|
GetViewport().SetInputAsHandled();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetupUI()
|
||||||
|
{
|
||||||
|
// Create a dark panel background
|
||||||
|
var panel = new Panel();
|
||||||
|
AddChild(panel);
|
||||||
|
panel.SetAnchorsPreset(LayoutPreset.FullRect);
|
||||||
|
|
||||||
|
var vbox = new VBoxContainer();
|
||||||
|
AddChild(vbox);
|
||||||
|
vbox.SetAnchorsPreset(LayoutPreset.FullRect);
|
||||||
|
|
||||||
|
_scrollContainer = new ScrollContainer();
|
||||||
|
_scrollContainer.SizeFlagsVertical = SizeFlags.ExpandFill;
|
||||||
|
vbox.AddChild(_scrollContainer);
|
||||||
|
|
||||||
|
_outputText = new RichTextLabel();
|
||||||
|
_outputText.BbcodeEnabled = true;
|
||||||
|
_outputText.ScrollFollowing = true;
|
||||||
|
_scrollContainer.AddChild(_outputText);
|
||||||
|
|
||||||
|
_inputField = new LineEdit();
|
||||||
|
_inputField.PlaceholderText = "Enter command...";
|
||||||
|
vbox.AddChild(_inputField);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ToggleConsole()
|
||||||
|
{
|
||||||
|
Visible = !Visible;
|
||||||
|
if (Visible)
|
||||||
|
{
|
||||||
|
_inputField.GrabFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ExecuteInput()
|
||||||
|
{
|
||||||
|
string input = _inputField.Text;
|
||||||
|
if (!string.IsNullOrWhiteSpace(input))
|
||||||
|
{
|
||||||
|
AddOutput($"> {input}");
|
||||||
|
_console.ExecuteCommand(input);
|
||||||
|
_inputField.Text = "";
|
||||||
|
_historyIndex = -1;
|
||||||
|
}
|
||||||
|
_inputField.GrabFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void NavigateHistory(int direction)
|
||||||
|
{
|
||||||
|
if (_console.History.Count == 0) return;
|
||||||
|
|
||||||
|
_historyIndex = Mathf.Clamp(_historyIndex + direction, 0, _console.History.Count - 1);
|
||||||
|
_inputField.Text = _console.History[_console.History.Count - 1 - _historyIndex];
|
||||||
|
_inputField.CaretColumn = _inputField.Text.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddOutput(string message)
|
||||||
|
{
|
||||||
|
_outputText.Text += message + "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddError(string message)
|
||||||
|
{
|
||||||
|
AddOutput($"[color=red]ERROR: {message}[/color]");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RegisterGodotCommands()
|
||||||
|
{
|
||||||
|
_console.RegisterCommand("timescale", "Sets Engine.TimeScale", args =>
|
||||||
|
{
|
||||||
|
if (float.TryParse(args[0], out float scale))
|
||||||
|
{
|
||||||
|
Engine.TimeScale = scale;
|
||||||
|
_console.Output($"Time scale set to {scale}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_console.Error("Invalid number");
|
||||||
|
}
|
||||||
|
}, 1, 1);
|
||||||
|
|
||||||
|
_console.RegisterCommand("quit", "Quits the application", _ =>
|
||||||
|
{
|
||||||
|
GetTree().Quit();
|
||||||
|
});
|
||||||
|
|
||||||
|
_console.RegisterCommand("scene", "Changes to a scene", args =>
|
||||||
|
{
|
||||||
|
GetTree().ChangeSceneToFile(args[0]);
|
||||||
|
}, 1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandConsole Console => _console;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
#if UNITY_EDITOR || UNITY_STANDALONE
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
using TMPro;
|
||||||
|
|
||||||
|
namespace OmegaLeo.HelperLib.Game.UI
|
||||||
|
{
|
||||||
|
public class UnityConsoleUI : MonoBehaviour
|
||||||
|
{
|
||||||
|
[SerializeField] private GameObject consolePanel;
|
||||||
|
[SerializeField] private TMP_InputField inputField;
|
||||||
|
[SerializeField] private TextMeshProUGUI outputText;
|
||||||
|
[SerializeField] private ScrollRect scrollRect;
|
||||||
|
[SerializeField] private KeyCode toggleKey = KeyCode.BackQuote;
|
||||||
|
|
||||||
|
private CommandConsole _console;
|
||||||
|
private int _historyIndex = -1;
|
||||||
|
|
||||||
|
private void Awake()
|
||||||
|
{
|
||||||
|
_console = new CommandConsole();
|
||||||
|
_console.RegisterDefaultCommands();
|
||||||
|
_console.OnOutput += AddOutput;
|
||||||
|
_console.OnError += AddError;
|
||||||
|
|
||||||
|
RegisterUnityCommands();
|
||||||
|
consolePanel.SetActive(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
if (Input.GetKeyDown(toggleKey))
|
||||||
|
{
|
||||||
|
ToggleConsole();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (consolePanel.activeSelf && inputField.isFocused)
|
||||||
|
{
|
||||||
|
if (Input.GetKeyDown(KeyCode.Return))
|
||||||
|
{
|
||||||
|
ExecuteInput();
|
||||||
|
}
|
||||||
|
else if (Input.GetKeyDown(KeyCode.UpArrow))
|
||||||
|
{
|
||||||
|
NavigateHistory(-1);
|
||||||
|
}
|
||||||
|
else if (Input.GetKeyDown(KeyCode.DownArrow))
|
||||||
|
{
|
||||||
|
NavigateHistory(1);
|
||||||
|
}
|
||||||
|
else if (Input.GetKeyDown(KeyCode.Tab))
|
||||||
|
{
|
||||||
|
AutoComplete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ToggleConsole()
|
||||||
|
{
|
||||||
|
consolePanel.SetActive(!consolePanel.activeSelf);
|
||||||
|
if (consolePanel.activeSelf)
|
||||||
|
{
|
||||||
|
inputField.ActivateInputField();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ExecuteInput()
|
||||||
|
{
|
||||||
|
string input = inputField.text;
|
||||||
|
if (!string.IsNullOrWhiteSpace(input))
|
||||||
|
{
|
||||||
|
AddOutput($"> {input}");
|
||||||
|
_console.ExecuteCommand(input);
|
||||||
|
inputField.text = "";
|
||||||
|
_historyIndex = -1;
|
||||||
|
}
|
||||||
|
inputField.ActivateInputField();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void NavigateHistory(int direction)
|
||||||
|
{
|
||||||
|
if (_console.History.Count == 0) return;
|
||||||
|
|
||||||
|
_historyIndex += direction;
|
||||||
|
_historyIndex = Mathf.Clamp(_historyIndex, 0, _console.History.Count - 1);
|
||||||
|
inputField.text = _console.History[_console.History.Count - 1 - _historyIndex];
|
||||||
|
inputField.caretPosition = inputField.text.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AutoComplete()
|
||||||
|
{
|
||||||
|
string[] suggestions = _console.GetAutoComplete(inputField.text);
|
||||||
|
if (suggestions.Length == 1)
|
||||||
|
{
|
||||||
|
inputField.text = suggestions[0];
|
||||||
|
inputField.caretPosition = inputField.text.Length;
|
||||||
|
}
|
||||||
|
else if (suggestions.Length > 1)
|
||||||
|
{
|
||||||
|
AddOutput("Suggestions: " + string.Join(", ", suggestions));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddOutput(string message)
|
||||||
|
{
|
||||||
|
outputText.text += message + "\n";
|
||||||
|
Canvas.ForceUpdateCanvases();
|
||||||
|
scrollRect.verticalNormalizedPosition = 0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddError(string message)
|
||||||
|
{
|
||||||
|
AddOutput($"<color=red>ERROR: {message}</color>");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RegisterUnityCommands()
|
||||||
|
{
|
||||||
|
_console.RegisterCommand("timescale", "Sets Time.timeScale", args =>
|
||||||
|
{
|
||||||
|
if (float.TryParse(args[0], out float scale))
|
||||||
|
{
|
||||||
|
Time.timeScale = scale;
|
||||||
|
_console.Output($"Time scale set to {scale}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_console.Error("Invalid number");
|
||||||
|
}
|
||||||
|
}, 1, 1);
|
||||||
|
|
||||||
|
_console.RegisterCommand("quit", "Quits the application", _ =>
|
||||||
|
{
|
||||||
|
Application.Quit();
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
UnityEditor.EditorApplication.isPlaying = false;
|
||||||
|
#endif
|
||||||
|
});
|
||||||
|
|
||||||
|
_console.RegisterCommand("scene", "Loads a scene by name", args =>
|
||||||
|
{
|
||||||
|
UnityEngine.SceneManagement.SceneManager.LoadScene(args[0]);
|
||||||
|
}, 1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandConsole Console => _console;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NetFlow.DocumentationHelper.Library.Attributes;
|
||||||
|
using OmegaLeo.HelperLib.Game.Models;
|
||||||
|
|
||||||
|
namespace OmegaLeo.HelperLib.Game.Utilities
|
||||||
|
{
|
||||||
|
public static class WeightedPicker
|
||||||
|
{
|
||||||
|
[Documentation("WeightedPicker.Pick", "Picks a random item from a dictionary of items with associated weights.")]
|
||||||
|
public static T Pick<T>(RandomNumberGenerator rng, Dictionary<T, float> weights)
|
||||||
|
{
|
||||||
|
float total = weights.Values.Sum();
|
||||||
|
float roll = rng.Range(0f, total);
|
||||||
|
|
||||||
|
foreach (var pair in weights)
|
||||||
|
{
|
||||||
|
roll -= pair.Value;
|
||||||
|
if (roll <= 0)
|
||||||
|
return pair.Key;
|
||||||
|
}
|
||||||
|
|
||||||
|
return weights.Keys.Last(); // fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\OmegaLeo.HelperLib.Changelog\OmegaLeo.HelperLib.Changelog.csproj" />
|
||||||
|
<ProjectReference Include="..\OmegaLeo.HelperLib\OmegaLeo.HelperLib.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Markdig" Version="0.40.0" />
|
||||||
|
<PackageReference Include="NetFlow.DocumentationHelper.Library" Version="1.1.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -10,6 +10,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OmegaLeo.HelperLib.Changelo
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OmegaLeo.HelperLib.ChangelogHandler", "OmegaLeo.HelperLib.ChangelogHandler\OmegaLeo.HelperLib.ChangelogHandler.csproj", "{359536F7-02DE-43E9-A383-D83853227B0F}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OmegaLeo.HelperLib.ChangelogHandler", "OmegaLeo.HelperLib.ChangelogHandler\OmegaLeo.HelperLib.ChangelogHandler.csproj", "{359536F7-02DE-43E9-A383-D83853227B0F}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OmegaLeo.HelperLib.Web", "OmegaLeo.HelperLib.Web\OmegaLeo.HelperLib.Web.csproj", "{1EA64C6B-D5D2-47EB-8255-5BAE2EE1385A}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OmegaLeo.HelperLib.Game", "OmegaLeo.HelperLib.Game\OmegaLeo.HelperLib.Game.csproj", "{488BC3EA-9B7A-4251-B802-22EB098D6494}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -36,5 +40,13 @@ Global
|
|||||||
{359536F7-02DE-43E9-A383-D83853227B0F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{359536F7-02DE-43E9-A383-D83853227B0F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{359536F7-02DE-43E9-A383-D83853227B0F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{359536F7-02DE-43E9-A383-D83853227B0F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{359536F7-02DE-43E9-A383-D83853227B0F}.Release|Any CPU.Build.0 = Release|Any CPU
|
{359536F7-02DE-43E9-A383-D83853227B0F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{1EA64C6B-D5D2-47EB-8255-5BAE2EE1385A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{1EA64C6B-D5D2-47EB-8255-5BAE2EE1385A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{1EA64C6B-D5D2-47EB-8255-5BAE2EE1385A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{1EA64C6B-D5D2-47EB-8255-5BAE2EE1385A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{488BC3EA-9B7A-4251-B802-22EB098D6494}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{488BC3EA-9B7A-4251-B802-22EB098D6494}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{488BC3EA-9B7A-4251-B802-22EB098D6494}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{488BC3EA-9B7A-4251-B802-22EB098D6494}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AExceptionDispatchInfo_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003Fhome_003Fomegaleo_003F_002Econfig_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FSourcesCache_003Fbf9021a960b74107a7e141aa06bc9d8a0a53c929178c2fb95b1597be8af8dc_003FExceptionDispatchInfo_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASafeFileHandle_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003Fhome_003Fomegaleo_003F_002Econfig_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fc21672d0d37542808f219d7d1f059dc1b2e000_003Faf_003Fbed3a9b4_003FSafeFileHandle_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASafeFileHandle_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003Fhome_003Fomegaleo_003F_002Econfig_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fc21672d0d37542808f219d7d1f059dc1b2e000_003Faf_003Fbed3a9b4_003FSafeFileHandle_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AThrowHelper_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003Fhome_003Fomegaleo_003F_002Econfig_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fc61cc76f205b4535a5b4f40700c07900b2d200_003Fe5_003F6b3ec67e_003FThrowHelper_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AThrowHelper_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003Fhome_003Fomegaleo_003F_002Econfig_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fc61cc76f205b4535a5b4f40700c07900b2d200_003Fe5_003F6b3ec67e_003FThrowHelper_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ATreeChanges_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003Fhome_003Fomegaleo_003F_002Econfig_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003F7478b0ce6362feb345e793626f5c504bd9d7ffeb748226521658c85fc4e29_003FTreeChanges_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ATreeChanges_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003Fhome_003Fomegaleo_003F_002Econfig_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003F7478b0ce6362feb345e793626f5c504bd9d7ffeb748226521658c85fc4e29_003FTreeChanges_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
|||||||
@@ -11,12 +11,9 @@ namespace GameDevLibrary.Helpers
|
|||||||
|
|
||||||
private static Stopwatch GetStopwatch(string key)
|
private static Stopwatch GetStopwatch(string key)
|
||||||
{
|
{
|
||||||
Stopwatch stopwatch = new Stopwatch();
|
Stopwatch stopwatch;
|
||||||
|
|
||||||
if (_stopwatches.TryGetValue(key, out stopwatch))
|
if (!_stopwatches.TryGetValue(key, out stopwatch))
|
||||||
{
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
stopwatch = new Stopwatch();
|
stopwatch = new Stopwatch();
|
||||||
_stopwatches.Add(key, stopwatch);
|
_stopwatches.Add(key, stopwatch);
|
||||||
|
|||||||
@@ -17,6 +17,11 @@ namespace GameDevLibrary.Models
|
|||||||
? Items.ToDictionary(x => x.Key, x => x.Value)
|
? Items.ToDictionary(x => x.Key, x => x.Value)
|
||||||
: new Dictionary<TKey, TValue>();
|
: new Dictionary<TKey, TValue>();
|
||||||
|
|
||||||
|
public static implicit operator Dictionary<TKey, TValue>(NeoDictionary<TKey, TValue> dict) => (dict.Items.Any())
|
||||||
|
? dict.Items.ToDictionary(x => x.Key, x => x.Value)
|
||||||
|
: new Dictionary<TKey, TValue>();
|
||||||
|
|
||||||
|
[Documentation("TryGetValue", "Tries to get the value from the NeoDictionary for the given key.")]
|
||||||
public bool TryGetValue(TKey key, out TValue value)
|
public bool TryGetValue(TKey key, out TValue value)
|
||||||
{
|
{
|
||||||
var item = Items.FirstOrDefault(x => x.Key.Equals(key));
|
var item = Items.FirstOrDefault(x => x.Key.Equals(key));
|
||||||
@@ -32,6 +37,7 @@ namespace GameDevLibrary.Models
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Documentation("TryGetValueFromIndex", "Tries to get the value from the NeoDictionary at the given index.")]
|
||||||
public bool TryGetValueFromIndex(int index, out TValue value)
|
public bool TryGetValueFromIndex(int index, out TValue value)
|
||||||
{
|
{
|
||||||
if (index < Items.Count)
|
if (index < Items.Count)
|
||||||
@@ -50,44 +56,57 @@ namespace GameDevLibrary.Models
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Documentation("Add", "Adds a new NeoDictionaryItem to the NeoDictionary.")]
|
||||||
public void Add(TKey key, TValue value)
|
public void Add(TKey key, TValue value)
|
||||||
{
|
{
|
||||||
Items.Add(new NeoDictionaryItem<TKey, TValue>(key, value));
|
Items.Add(new NeoDictionaryItem<TKey, TValue>(key, value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Documentation("AddRange", "Adds a range of NeoDictionaryItems from another NeoDictionary to the NeoDictionary.")]
|
||||||
public void AddRange(NeoDictionary<TKey, TValue> values)
|
public void AddRange(NeoDictionary<TKey, TValue> values)
|
||||||
{
|
{
|
||||||
Items.AddRange(values.Items);
|
Items.AddRange(values.Items);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Documentation("AddRange", "Adds a range of NeoDictionaryItems to the NeoDictionary.")]
|
||||||
public void AddRange(IEnumerable<NeoDictionaryItem<TKey, TValue>> values)
|
public void AddRange(IEnumerable<NeoDictionaryItem<TKey, TValue>> values)
|
||||||
{
|
{
|
||||||
Items.AddRange(values);
|
Items.AddRange(values);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Documentation("Any", "Checks if the NeoDictionary has any items.")]
|
||||||
public bool Any() => Items.Any();
|
public bool Any() => Items.Any();
|
||||||
|
|
||||||
|
[Documentation("Any", "Checks if any item in the NeoDictionary satisfies a condition.")]
|
||||||
public bool Any(Func<NeoDictionaryItem<TKey, TValue>, bool> predicate) => Items.Any(predicate);
|
public bool Any(Func<NeoDictionaryItem<TKey, TValue>, bool> predicate) => Items.Any(predicate);
|
||||||
|
|
||||||
|
[Documentation("Where", "Filters the NeoDictionary items based on a predicate.")]
|
||||||
public IEnumerable<NeoDictionaryItem<TKey, TValue>> Where(Func<NeoDictionaryItem<TKey, TValue>, bool> predicate) =>
|
public IEnumerable<NeoDictionaryItem<TKey, TValue>> Where(Func<NeoDictionaryItem<TKey, TValue>, bool> predicate) =>
|
||||||
Items.Where(predicate);
|
Items.Where(predicate);
|
||||||
|
|
||||||
|
[Documentation("FirstOrDefault", "Returns the first item of the NeoDictionary or a default value if the NeoDictionary is empty.")]
|
||||||
public NeoDictionaryItem<TKey, TValue>? FirstOrDefault() => Items.FirstOrDefault();
|
public NeoDictionaryItem<TKey, TValue>? FirstOrDefault() => Items.FirstOrDefault();
|
||||||
|
|
||||||
|
[Documentation("FirstOrDefault", "Returns the first item of the NeoDictionary that satisfies a condition or a default value if no such item is found.")]
|
||||||
public NeoDictionaryItem<TKey, TValue>? FirstOrDefault(Func<NeoDictionaryItem<TKey, TValue>, bool> predicate) =>
|
public NeoDictionaryItem<TKey, TValue>? FirstOrDefault(Func<NeoDictionaryItem<TKey, TValue>, bool> predicate) =>
|
||||||
Items.FirstOrDefault(predicate);
|
Items.FirstOrDefault(predicate);
|
||||||
|
|
||||||
|
[Documentation("LastOrDefault", "Returns the last item of the NeoDictionary or a default value if the NeoDictionary is empty.")]
|
||||||
public NeoDictionaryItem<TKey, TValue>? LastOrDefault() => Items.LastOrDefault();
|
public NeoDictionaryItem<TKey, TValue>? LastOrDefault() => Items.LastOrDefault();
|
||||||
|
|
||||||
|
[Documentation("LastOrDefault", "Returns the last item of the NeoDictionary that satisfies a condition or a default value if no such item is found.")]
|
||||||
public NeoDictionaryItem<TKey, TValue>? LastOrDefault(Func<NeoDictionaryItem<TKey, TValue>, bool> predicate) =>
|
public NeoDictionaryItem<TKey, TValue>? LastOrDefault(Func<NeoDictionaryItem<TKey, TValue>, bool> predicate) =>
|
||||||
Items.LastOrDefault(predicate);
|
Items.LastOrDefault(predicate);
|
||||||
|
|
||||||
|
[Documentation("Select", "Projects each item of the NeoDictionary into a new form.")]
|
||||||
public IEnumerable<NeoDictionaryItem<TKey, TValue>> Select(Func<NeoDictionaryItem<TKey, TValue>, NeoDictionaryItem<TKey, TValue>> selector) =>
|
public IEnumerable<NeoDictionaryItem<TKey, TValue>> Select(Func<NeoDictionaryItem<TKey, TValue>, NeoDictionaryItem<TKey, TValue>> selector) =>
|
||||||
Items.Select(selector);
|
Items.Select(selector);
|
||||||
|
|
||||||
|
[Documentation("IndexOf", "Returns the index of the given NeoDictionaryItem.")]
|
||||||
public int IndexOf(NeoDictionaryItem<TKey, TValue> selector) =>
|
public int IndexOf(NeoDictionaryItem<TKey, TValue> selector) =>
|
||||||
Items.IndexOf(selector);
|
Items.IndexOf(selector);
|
||||||
|
|
||||||
|
[Documentation("Remove", "Removes the item with the given key.")]
|
||||||
public void Remove(TKey key)
|
public void Remove(TKey key)
|
||||||
{
|
{
|
||||||
var index = Items.FindIndex(x => x.Key.Equals(key));
|
var index = Items.FindIndex(x => x.Key.Equals(key));
|
||||||
@@ -95,8 +114,10 @@ namespace GameDevLibrary.Models
|
|||||||
Items.RemoveAt(index);
|
Items.RemoveAt(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Documentation("RemoveAt", "Removes the item at the given index.")]
|
||||||
public void RemoveAt(int index) => Items.RemoveAt(index);
|
public void RemoveAt(int index) => Items.RemoveAt(index);
|
||||||
|
|
||||||
|
[Documentation("Replace", "Replaces the value for the given key if it exists.")]
|
||||||
public void Replace(TKey key, TValue value)
|
public void Replace(TKey key, TValue value)
|
||||||
{
|
{
|
||||||
if (HasKey(key))
|
if (HasKey(key))
|
||||||
@@ -105,6 +126,7 @@ namespace GameDevLibrary.Models
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Documentation("HasKey", "Checks if the NeoDictionary contains the given key.")]
|
||||||
public bool HasKey(TKey key) => Items.Any(x => x.Key.Equals(key));
|
public bool HasKey(TKey key) => Items.Any(x => x.Key.Equals(key));
|
||||||
|
|
||||||
public TValue this[TKey key]
|
public TValue this[TKey key]
|
||||||
@@ -143,7 +165,93 @@ namespace GameDevLibrary.Models
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public NeoDictionaryItem<TKey, TValue> this[int index]
|
||||||
|
{
|
||||||
|
get => Items[index];
|
||||||
|
set => Items[index] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Documentation("Clear", "Clears all items from the NeoDictionary.")]
|
||||||
public void Clear() => Items.Clear();
|
public void Clear() => Items.Clear();
|
||||||
|
|
||||||
|
[Documentation("Sort", "Sorts the NeoDictionary items using the given comparison.")]
|
||||||
|
public void Sort(Comparison<NeoDictionaryItem<TKey, TValue>> comparison) => Items.Sort(comparison);
|
||||||
|
|
||||||
|
[Documentation("Count", "Returns the number of items in the NeoDictionary.")]
|
||||||
|
public int Count() => Items.Count;
|
||||||
|
|
||||||
|
[Documentation("ForEach", "Performs the given action on each item in the NeoDictionary.")]
|
||||||
|
public void ForEach(Action<NeoDictionaryItem<TKey, TValue>> action) => Items.ForEach(action);
|
||||||
|
|
||||||
|
[Documentation("ToList", "Converts the NeoDictionary items to a List.")]
|
||||||
|
public List<NeoDictionaryItem<TKey, TValue>> ToList() => Items.ToList();
|
||||||
|
|
||||||
|
[Documentation("Clone", "Creates a shallow copy of the NeoDictionary.")]
|
||||||
|
public NeoDictionary<TKey, TValue> Clone()
|
||||||
|
{
|
||||||
|
var newDict = new NeoDictionary<TKey, TValue>();
|
||||||
|
newDict.Items = new List<NeoDictionaryItem<TKey, TValue>>(Items);
|
||||||
|
return newDict;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Documentation("Reverse", "Reverses the order of the items in the NeoDictionary.")]
|
||||||
|
public void Reverse() => Items.Reverse();
|
||||||
|
|
||||||
|
[Documentation("Insert", "Inserts an item at the given index.")]
|
||||||
|
public void Insert(int index, TKey key, TValue value) =>
|
||||||
|
Items.Insert(index, new NeoDictionaryItem<TKey, TValue>(key, value));
|
||||||
|
|
||||||
|
[Documentation("InsertRange", "Inserts a range of items starting from the given index.")]
|
||||||
|
public void InsertRange(int index, IEnumerable<NeoDictionaryItem<TKey, TValue>> values) =>
|
||||||
|
Items.InsertRange(index, values);
|
||||||
|
|
||||||
|
[Documentation("RemoveRange", "Removes a range of items starting from the given index.")]
|
||||||
|
public void RemoveRange(int index, int count) =>
|
||||||
|
Items.RemoveRange(index, count);
|
||||||
|
|
||||||
|
[Documentation("RemoveAll", "Removes all items matching the given predicate.")]
|
||||||
|
public void RemoveAll(Func<NeoDictionaryItem<TKey, TValue>, bool> predicate) =>
|
||||||
|
Items.RemoveAll(new Predicate<NeoDictionaryItem<TKey, TValue>>(predicate));
|
||||||
|
|
||||||
|
[Documentation("FindIndex", "Finds the index of the first item matching the given predicate.")]
|
||||||
|
public int FindIndex(Func<NeoDictionaryItem<TKey, TValue>, bool> predicate) =>
|
||||||
|
Items.FindIndex(new Predicate<NeoDictionaryItem<TKey, TValue>>(predicate));
|
||||||
|
|
||||||
|
[Documentation("Find", "Finds the first item matching the given predicate.")]
|
||||||
|
public NeoDictionaryItem<TKey, TValue> Find(Func<NeoDictionaryItem<TKey, TValue>, bool> predicate) =>
|
||||||
|
Items.Find(new Predicate<NeoDictionaryItem<TKey, TValue>>(predicate));
|
||||||
|
|
||||||
|
[Documentation("FindAll", "Finds all items matching the given predicate.")]
|
||||||
|
public IEnumerable<NeoDictionaryItem<TKey, TValue>> FindAll(Func<NeoDictionaryItem<TKey, TValue>, bool> predicate) =>
|
||||||
|
Items.FindAll(new Predicate<NeoDictionaryItem<TKey, TValue>>(predicate));
|
||||||
|
|
||||||
|
[Documentation("Merge", "Merges another NeoDictionary into this one, ignoring duplicate keys.")]
|
||||||
|
public void Merge(NeoDictionary<TKey, TValue> other)
|
||||||
|
{
|
||||||
|
foreach (var item in other.Items)
|
||||||
|
{
|
||||||
|
if (!HasKey(item.Key))
|
||||||
|
{
|
||||||
|
Add(item.Key, item.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Documentation("ToDictionarySafe", "Converts the NeoDictionary to a standard Dictionary while safely handling duplicate keys by ignoring them.")]
|
||||||
|
public Dictionary<TKey, TValue> ToDictionarySafe()
|
||||||
|
{
|
||||||
|
var dict = new Dictionary<TKey, TValue>();
|
||||||
|
|
||||||
|
foreach (var item in Items)
|
||||||
|
{
|
||||||
|
if (!dict.ContainsKey(item.Key))
|
||||||
|
{
|
||||||
|
dict.Add(item.Key, item.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dict;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Serializable]
|
[Serializable]
|
||||||
|
|||||||
Reference in New Issue
Block a user