Split the person card into overview, clothes, carry and now tabs.

Today's history is a paged HTTP log on the worker, not on the card or in people.json.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Leonid Pershin
2026-08-20 04:00:38 +03:00
co-authored by Cursor
parent 69106cb48b
commit 6be4fa75ab
27 changed files with 1551 additions and 109 deletions
+8
View File
@@ -59,6 +59,14 @@ internal abstract record GameCommand
string Locale,
TaskCompletionSource<PersonCardResult> Result) : GameCommand;
/// <summary>Today's history for one person. Completes on that school's worker; not a snapshot.</summary>
internal sealed record GetPersonLog(
int SchoolId,
string PersonId,
string Locale,
PersonLogQuery Query,
TaskCompletionSource<PersonLogResult> Result) : GameCommand;
internal sealed record HireStaff(
int SchoolId,
string PersonId,
@@ -187,6 +187,10 @@ internal sealed class GameLoopService(
HandleGetPerson(getPerson);
break;
case GameCommand.GetPersonLog getLog:
HandleGetPersonLog(getLog);
break;
case GameCommand.HireStaff hire:
HandleStaffing(
hire.SchoolId,
@@ -242,6 +246,15 @@ internal sealed class GameLoopService(
}
}
private void HandleGetPersonLog(GameCommand.GetPersonLog command)
{
if (!_workers.TryGetValue(command.SchoolId, out var worker)
|| !worker.Post(new WorkerCommand.GetPersonLog(command.PersonId, command.Locale, command.Query, command.Result)))
{
command.Result.TrySetResult(new PersonLogResult(null, PersonLookupError.UnknownSchool));
}
}
private void HandleStaffing(int schoolId, WorkerCommand command, TaskCompletionSource<StaffingOutcome> result)
{
if (!_workers.TryGetValue(schoolId, out var worker) || !worker.Post(command))
+6 -2
View File
@@ -85,7 +85,9 @@ internal static class PersonCardReader
Worn(person, catalog, locale),
Carried(person, catalog, locale),
catalog is null ? 0f : CarryMass.Held(catalog, person.Items),
Capacity(person, skills, catalog));
Capacity(person, skills, catalog),
person.Items.Any(item => item.Location.Equals(ItemLocations.Locker, StringComparison.Ordinal)),
person.Items.Count(item => item.Location.Equals(ItemLocations.Home, StringComparison.Ordinal)));
}
private static IReadOnlyDictionary<string, float>? LiveNeeds(World world, string personId)
@@ -288,7 +290,9 @@ internal static class PersonCardReader
ThingLabel(catalog, locale, item.Def),
item.Color,
ColorLabel(catalog, locale, item.Color),
layers));
layers,
item.Condition,
ConditionLabel: null));
}
return rows;
@@ -0,0 +1,30 @@
using HSchool.Server.Api;
using HSchool.Simulation;
namespace HSchool.Server.Game;
/// <summary>Pages today's log on the worker thread. The list is not a published snapshot.</summary>
internal static class PersonLogReader
{
public static PersonLogResponse? Read(School school, string personId, PersonLogQuery query, string locale)
{
var roster = school.Roster;
if (roster is null)
{
return null;
}
var known = roster.People.Any(person => person.Id.Equals(personId, StringComparison.Ordinal))
|| (school.Applicants?.Applicants.Any(applicant => applicant.Person.Id.Equals(personId, StringComparison.Ordinal)) ?? false);
if (!known)
{
return null;
}
var page = PersonLogBrowser.Apply(school.DayLog, personId, query, school.Catalog, locale);
var entries = page.Entries
.Select(row => new PersonLogEntryResponse(row.Time, row.Type, row.Label, row.ThingDef))
.ToArray();
return new PersonLogResponse(page.Total, page.Page, page.PageSize, entries);
}
}
+14
View File
@@ -429,6 +429,14 @@ internal sealed class SchoolWorker
: new PersonCardResult(card, PersonLookupError.None));
break;
case WorkerCommand.GetPersonLog getLog:
var log = PersonLogReader.Read(school, getLog.PersonId, getLog.Query, getLog.Locale);
getLog.Result.TrySetResult(
log is null
? new PersonLogResult(null, PersonLookupError.UnknownPerson)
: new PersonLogResult(log, PersonLookupError.None));
break;
case WorkerCommand.HireStaff hire:
hire.Result.TrySetResult(ApplyHire(school, hire.PersonId, hire.Position));
break;
@@ -485,6 +493,9 @@ internal sealed class SchoolWorker
case WorkerCommand.GetPerson getPerson:
getPerson.Result.TrySetResult(new PersonCardResult(null, PersonLookupError.UnknownSchool));
break;
case WorkerCommand.GetPersonLog getLog:
getLog.Result.TrySetResult(new PersonLogResult(null, PersonLookupError.UnknownSchool));
break;
case WorkerCommand.HireStaff hire:
hire.Result.TrySetResult(Staffing.UnknownSchool());
break;
@@ -513,6 +524,9 @@ internal sealed class SchoolWorker
case WorkerCommand.GetPerson getPerson:
getPerson.Result.TrySetException(exception);
break;
case WorkerCommand.GetPersonLog getLog:
getLog.Result.TrySetException(exception);
break;
case WorkerCommand.HireStaff hire:
hire.Result.TrySetException(exception);
break;
+7
View File
@@ -1,6 +1,7 @@
using HSchool.People;
using HSchool.Server.Api;
using HSchool.Server.Net;
using HSchool.Simulation;
namespace HSchool.Server.Game;
@@ -27,6 +28,12 @@ internal abstract record WorkerCommand
string Locale,
TaskCompletionSource<PersonCardResult> Result) : WorkerCommand;
internal sealed record GetPersonLog(
string PersonId,
string Locale,
PersonLogQuery Query,
TaskCompletionSource<PersonLogResult> Result) : WorkerCommand;
internal sealed record HireStaff(
string PersonId,
string Position,