Enhance school creation functionality by introducing support for name sets in the API and UI. Update the catalog to include skills, traits, body attributes, needs, and name sets, improving character generation capabilities. Revise localization strings for better user guidance and update tests to validate the new name set functionality and ensure robustness in school creation processes.
ci / server (push) Failing after 3m45s
ci / client (push) Successful in 17s

This commit is contained in:
Leonid Pershin
2026-08-18 18:57:01 +03:00
parent 38afbcad36
commit e6182e0e45
56 changed files with 3706 additions and 9 deletions
+1
View File
@@ -14,6 +14,7 @@ internal abstract record GameCommand
DateTime StartDate,
IReadOnlyList<string>? ExtraModIds,
MapLayout? Map,
string? NameSetId,
TaskCompletionSource<SchoolCreationOutcome> Result) : GameCommand;
internal sealed record DeleteSchool(int SchoolId, TaskCompletionSource<bool> Result) : GameCommand;
+48 -3
View File
@@ -216,10 +216,28 @@ internal sealed class GameLoopService(
return;
}
DefCatalog catalog;
try
{
catalog = mods.LoadCatalog(packIds);
}
catch (Exception ex) when (ex is ContentLoadException or SchoolContentUnavailableException)
{
command.Result.TrySetResult(new SchoolCreationOutcome(null, SchoolCreationError.InvalidCatalog));
return;
}
var nameSetId = ResolveNameSetId(catalog, command.NameSetId);
if (nameSetId is null)
{
command.Result.TrySetResult(new SchoolCreationOutcome(null, SchoolCreationError.UnknownNameSet));
return;
}
var id = _nextId++;
store.WriteNextId(_nextId);
var worker = SpawnWorker(id, normalized, command.StartDate, running: true, ClockSpeed.DefaultIndex, isNew: true, packIds, command.Map);
var worker = SpawnWorker(id, normalized, command.StartDate, running: true, ClockSpeed.DefaultIndex, isNew: true, packIds, command.Map, nameSetId);
Track(worker);
worker.Start();
@@ -385,7 +403,8 @@ internal sealed class GameLoopService(
save.SpeedIndex,
isNew: false,
save.ModIds,
save.Map);
save.Map,
save.NameSetId);
worker.Start();
try
@@ -431,7 +450,8 @@ internal sealed class GameLoopService(
int speedIndex,
bool isNew,
IReadOnlyList<string>? modIds,
MapLayout? map) =>
MapLayout? map,
string? nameSetId) =>
new(
id,
name,
@@ -441,6 +461,7 @@ internal sealed class GameLoopService(
isNew,
modIds,
map,
nameSetId,
_options,
clients,
metrics,
@@ -449,6 +470,30 @@ internal sealed class GameLoopService(
onFailed: schoolId => commands.Enqueue(new GameCommand.WorkerFailed(schoolId)),
loggerFactory.CreateLogger($"HSchool.Server.Game.SchoolWorker.{id}"));
/// <summary>
/// Empty request uses the first placeable set (core's Slavic). A named id must exist in the
/// catalog already loaded for this pack list — unknown extras were rejected above.
/// </summary>
internal static string? ResolveNameSetId(DefCatalog catalog, string? requested)
{
var available = catalog.NameSets.Values
.Where(def => !def.Abstract)
.Select(def => def.DefName)
.OrderBy(name => name, StringComparer.Ordinal)
.ToArray();
if (available.Length == 0)
{
return null;
}
if (string.IsNullOrWhiteSpace(requested))
{
return available[0];
}
return available.Contains(requested, StringComparer.Ordinal) ? requested : null;
}
private void Track(SchoolWorker worker)
{
_workers[worker.Id] = worker;
+3
View File
@@ -23,6 +23,8 @@ internal sealed class SchoolSave
public IReadOnlyList<string>? ModIds { get; init; }
public MapLayout? Map { get; init; }
public string? NameSetId { get; init; }
}
/// <summary>Allocates school ids that survive a process restart.</summary>
@@ -152,6 +154,7 @@ internal sealed class SchoolStore
SpeedIndex = save.SpeedIndex,
ModIds = save.ModIds,
Map = save.Map,
NameSetId = save.NameSetId,
});
}
catch (Exception ex)
+4
View File
@@ -29,6 +29,7 @@ internal sealed class SchoolWorker
private readonly bool _isNew;
private readonly IReadOnlyList<string>? _modIds;
private readonly MapLayout? _savedMap;
private readonly string? _nameSetId;
private readonly Action<int> _onFailed;
private readonly int _id;
@@ -53,6 +54,7 @@ internal sealed class SchoolWorker
bool isNew,
IReadOnlyList<string>? modIds,
MapLayout? savedMap,
string? nameSetId,
SimulationOptions options,
ClientRegistry clients,
GameMetrics metrics,
@@ -69,6 +71,7 @@ internal sealed class SchoolWorker
_isNew = isNew;
_modIds = modIds;
_savedMap = savedMap;
_nameSetId = nameSetId;
_options = options;
_clients = clients;
_metrics = metrics;
@@ -411,6 +414,7 @@ internal sealed class SchoolWorker
SpeedIndex = school.Clock.SpeedIndex,
ModIds = school.Catalog?.PackIds,
Map = school.Map,
NameSetId = _nameSetId,
});
}
catch (Exception ex)