Dev #7

Merged
omegaleo merged 47 commits from dev into main 2025-12-23 16:46:44 +00:00
25 changed files with 1169 additions and 5 deletions
Showing only changes of commit e0e191dc6b - Show all commits
@@ -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;
}
}
}
+15
View File
@@ -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;
}
}
}
+35
View File
@@ -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>
+12
View File
@@ -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
View File
@@ -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);
+108
View File
@@ -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]