Added new Game logic library to assist with game projects
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user