Remove obsolete project files for MrGameEng.AI, MrGameEng.Assets, MrGameEng.Atlases, and MrGameEng.Collisions modules. Introduce new AssetManager and related classes for asset loading and management, including support for texture atlases and mod definitions. Enhance mod loading capabilities with DefDatabase and LanguageManager for JSON-based definitions and localization. Implement a shelf packing algorithm for efficient texture atlas creation.

This commit is contained in:
Leonid Pershin
2026-06-12 07:47:04 +03:00
parent 1f87fb0b74
commit c30e2ce764
70 changed files with 0 additions and 233 deletions
+115
View File
@@ -0,0 +1,115 @@
namespace MrGameEng.AI;
/// <summary>
/// A utility reasoner over a fixed set of <see cref="UtilityAction{TContext}"/>s. Each evaluation
/// scores every action for the given context and picks one. Scoring writes into a buffer owned by the
/// reasoner, so repeated evaluations allocate nothing; keep one instance per agent kind (or one per
/// system, reused across agents) and pass each agent's context in. Not thread-safe: the score buffer
/// is shared between calls, so a single instance must not be evaluated from two threads at once.
/// </summary>
public sealed class UtilityAi<TContext>
{
private readonly UtilityAction<TContext>[] _actions;
private readonly float[] _scores;
/// <summary>Creates a reasoner choosing between <paramref name="actions"/> (at least one required).</summary>
/// <exception cref="ArgumentException"><paramref name="actions"/> is empty.</exception>
public UtilityAi(params UtilityAction<TContext>[] actions)
{
if (actions is null || actions.Length == 0)
{
throw new ArgumentException("A UtilityAi needs at least one action.", nameof(actions));
}
_actions = actions;
_scores = new float[actions.Length];
}
/// <summary>The actions this reasoner chooses between, in evaluation order.</summary>
public IReadOnlyList<UtilityAction<TContext>> Actions => _actions;
/// <summary>
/// The scores from the most recent <see cref="Select"/> / <see cref="SelectWeighted"/> call, aligned
/// with <see cref="Actions"/>. Useful for debug overlays and console dumps.
/// </summary>
public ReadOnlySpan<float> LastScores => _scores;
/// <summary>
/// Scores every action for <paramref name="context"/> and returns the highest, or <c>null</c> when no
/// action scores strictly above <paramref name="threshold"/>. Ties resolve to the earliest action,
/// so selection is fully deterministic for identical inputs.
/// </summary>
public UtilityAction<TContext>? Select(TContext context, float threshold = 0f)
{
var best = -1;
var bestScore = threshold;
for (var i = 0; i < _actions.Length; i++)
{
var score = _actions[i].Score(context);
_scores[i] = score;
if (score > bestScore)
{
bestScore = score;
best = i;
}
}
return best >= 0 ? _actions[best] : null;
}
/// <summary>
/// Scores every action and picks one at random in proportion to its score (roulette selection over
/// the actions above <paramref name="threshold"/>), giving believable variety while staying
/// deterministic for a given <paramref name="random"/> sequence. Returns <c>null</c> when nothing
/// qualifies. Pass a seeded <see cref="Random"/> owned by the calling system — never
/// <see cref="Random.Shared"/> — to keep the simulation reproducible.
/// </summary>
public UtilityAction<TContext>? SelectWeighted(
TContext context,
Random random,
float threshold = 0f
)
{
var total = 0f;
for (var i = 0; i < _actions.Length; i++)
{
var score = _actions[i].Score(context);
_scores[i] = score;
if (score > threshold)
{
total += score;
}
}
if (total <= 0f)
{
return null;
}
var roll = (float)random.NextDouble() * total;
for (var i = 0; i < _actions.Length; i++)
{
if (_scores[i] <= threshold)
{
continue;
}
roll -= _scores[i];
if (roll <= 0f)
{
return _actions[i];
}
}
// Floating-point slack can leave roll just above 0; fall back to the last qualifying action.
for (var i = _actions.Length - 1; i >= 0; i--)
{
if (_scores[i] > threshold)
{
return _actions[i];
}
}
return null;
}
}