- Updated protocol documentation to include `nativeLanguages` in `nameSets` and added `nativeLanguage` to school creation options. - Enhanced the UI for school creation to allow selection of native languages, improving user experience. - Revised API interfaces to accommodate new native language features, ensuring proper data handling. - Improved localization strings to support new native language functionalities in both English and Russian. - Updated tests to validate the new native language features and ensure robust functionality in staffing scenarios.
148 lines
4.1 KiB
C#
148 lines
4.1 KiB
C#
namespace HSchool.Simulation;
|
|
|
|
/// <summary>Why a school could not be created.</summary>
|
|
public enum SchoolCreationError
|
|
{
|
|
None = 0,
|
|
LimitReached,
|
|
InvalidName,
|
|
InvalidStartDate,
|
|
InvalidMap,
|
|
UnknownMod,
|
|
InvalidCatalog,
|
|
UnknownNameSet,
|
|
UnknownNativeLanguage,
|
|
}
|
|
|
|
/// <summary>Outcome of <see cref="SchoolRegistry.Create"/>: either the school or the reason there is none.</summary>
|
|
public readonly record struct SchoolCreationResult(School? School, SchoolCreationError Error)
|
|
{
|
|
public bool Succeeded => Error == SchoolCreationError.None && School is not null;
|
|
|
|
public static SchoolCreationResult Failed(SchoolCreationError error) => new(null, error);
|
|
}
|
|
|
|
/// <summary>
|
|
/// In-memory set of schools plus the cap from configuration. Not thread-safe — unit tests and
|
|
/// name/limit checks use it; the live server gives each school its own worker instead.
|
|
/// </summary>
|
|
public sealed class SchoolRegistry : IDisposable
|
|
{
|
|
private readonly SimulationOptions _options;
|
|
private readonly List<School> _schools = [];
|
|
|
|
private int _nextId = 1;
|
|
private bool _disposed;
|
|
|
|
public SchoolRegistry(SimulationOptions options)
|
|
{
|
|
_options = options;
|
|
NameGenerator = new SchoolNameGenerator();
|
|
}
|
|
|
|
public SchoolNameGenerator NameGenerator { get; }
|
|
|
|
public int MaxSchools => _options.MaxSchools;
|
|
|
|
public int Count => _schools.Count;
|
|
|
|
public bool IsFull => _schools.Count >= _options.MaxSchools;
|
|
|
|
/// <summary>Schools in creation order — the order the menu lists them in.</summary>
|
|
public IReadOnlyList<School> Schools => _schools;
|
|
|
|
public School? Find(int id) => _schools.Find(school => school.Id == id);
|
|
|
|
public SchoolCreationResult Create(string name, DateTime startDate)
|
|
{
|
|
ObjectDisposedException.ThrowIf(_disposed, this);
|
|
|
|
if (IsFull)
|
|
{
|
|
return SchoolCreationResult.Failed(SchoolCreationError.LimitReached);
|
|
}
|
|
|
|
if (!TryNormalizeName(name, out var normalized))
|
|
{
|
|
return SchoolCreationResult.Failed(SchoolCreationError.InvalidName);
|
|
}
|
|
|
|
if (!GameClock.IsValidStartDate(startDate))
|
|
{
|
|
return SchoolCreationResult.Failed(SchoolCreationError.InvalidStartDate);
|
|
}
|
|
|
|
var school = School.Create(_nextId++, normalized, startDate);
|
|
_schools.Add(school);
|
|
|
|
return new SchoolCreationResult(school, SchoolCreationError.None);
|
|
}
|
|
|
|
public bool Delete(int id)
|
|
{
|
|
ObjectDisposedException.ThrowIf(_disposed, this);
|
|
|
|
var school = Find(id);
|
|
if (school is null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
_schools.Remove(school);
|
|
school.Dispose();
|
|
return true;
|
|
}
|
|
|
|
/// <summary>Advances every running school by one fixed step.</summary>
|
|
public void Tick()
|
|
{
|
|
ObjectDisposedException.ThrowIf(_disposed, this);
|
|
|
|
foreach (var school in _schools)
|
|
{
|
|
school.Tick(_options.FixedDeltaTime, _options.GameMinutesPerRealSecond);
|
|
}
|
|
}
|
|
|
|
/// <summary>A name the player has not used yet, for the "random" button in the creation form.</summary>
|
|
public string SuggestName(SchoolNameLanguage language = SchoolNameLanguage.Russian) =>
|
|
NameGenerator.Next(_schools.Select(school => school.Name), language);
|
|
|
|
/// <summary>Trims, strips control characters and enforces the length limit.</summary>
|
|
public static bool TryNormalizeName(string? name, out string normalized)
|
|
{
|
|
normalized = string.Empty;
|
|
|
|
if (string.IsNullOrWhiteSpace(name))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var cleaned = new string(name.Where(character => !char.IsControl(character)).ToArray()).Trim();
|
|
if (cleaned.Length == 0 || cleaned.Length > School.MaxNameLength)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
normalized = cleaned;
|
|
return true;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (_disposed)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_disposed = true;
|
|
|
|
foreach (var school in _schools)
|
|
{
|
|
school.Dispose();
|
|
}
|
|
|
|
_schools.Clear();
|
|
}
|
|
}
|