Files
h-school/src/HSchool.Server/Game/PersonCardReader.cs
T

288 lines
9.7 KiB
C#

using Arch.Core;
using HSchool.Ai;
using HSchool.Content;
using HSchool.People;
using HSchool.Server.Api;
using HSchool.Simulation;
namespace HSchool.Server.Game;
/// <summary>
/// Builds a person card on the school's worker thread so live need values come from the World.
/// </summary>
internal static partial class PersonCardReader
{
private static readonly QueryDescription IdentityAndNeeds =
new QueryDescription().WithAll<PersonIdentity, PersonNeeds>();
private static readonly QueryDescription IdentityAndSkills =
new QueryDescription().WithAll<PersonIdentity, PersonSkills>();
private static readonly QueryDescription IdentityAndActivity =
new QueryDescription().WithAll<PersonIdentity, PersonActivity>();
private static readonly QueryDescription IdentityAndPresence =
new QueryDescription().WithAll<PersonIdentity, Presence>();
public static PersonCardResponse? Read(School school, string personId, string locale)
{
var roster = school.Roster;
if (roster is null)
{
return null;
}
var person = roster.People.FirstOrDefault(candidate => candidate.Id.Equals(personId, StringComparison.Ordinal));
person ??= school.Applicants?.Applicants
.FirstOrDefault(applicant => applicant.Person.Id.Equals(personId, StringComparison.Ordinal))
?.Person;
if (person is null)
{
return null;
}
var catalog = school.Catalog;
var classes = roster.Classes.ToDictionary(schoolClass => schoolClass.Id, StringComparer.Ordinal);
var people = roster.People.ToDictionary(person => person.Id, StringComparer.Ordinal);
int? year = null;
string? letter = null;
string? classTeacherId = null;
string? classTeacherName = null;
if (person.ClassId is { } classId && classes.TryGetValue(classId, out var schoolClass))
{
year = schoolClass.Year;
letter = schoolClass.Letter;
classTeacherId = schoolClass.ClassTeacherId;
if (classTeacherId is { } teacherId && people.TryGetValue(teacherId, out var teacher))
{
classTeacherName = teacher.Name.Full;
}
}
var needs = LiveNeeds(school.World, personId) ?? person.Needs;
var skills = LiveSkills(school.World, personId);
var activityId = LiveActivity(school.World, personId)
?? school.DirectorSummonPresenceActionId(personId);
string? activityLabel = null;
if (activityId is not null && catalog is not null && catalog.Actions.TryGetValue(activityId, out var action))
{
activityLabel = catalog.Label(locale, action);
}
else if (activityId is not null)
{
activityLabel = activityId;
}
var circle = school.TalkCircleOf(personId);
return new PersonCardResponse(
person.Id,
person.Name.Full,
person.Name.Surname,
person.Name.Given,
person.Name.Patronymic,
person.Female,
person.AgeOn(school.Clock.Time),
person.BirthDate,
PeopleListMapper.RolesOf(person),
year,
letter,
person.ClassId,
person.Position,
PeopleListMapper.PositionLabel(catalog, locale, person.Position),
Body(person, catalog, locale),
Skills(person, skills, catalog, locale),
Traits(person, catalog, locale),
Needs(needs, catalog, locale),
activityId,
activityLabel,
Family(roster, person),
Worn(person, catalog, locale),
Carried(person, catalog, locale),
catalog is null ? 0f : CarryMass.Held(catalog, person.Items),
Capacity(person, skills, catalog),
person.LockerRoomId is not null
|| person.Items.Any(item => item.Location.Equals(ItemLocations.Locker, StringComparison.Ordinal)),
person.Items.Count(item => item.Location.Equals(ItemLocations.Home, StringComparison.Ordinal)),
Connections: Connections(roster, person, catalog, locale),
Orientation: OrientationOf(person, catalog, locale),
TalkCircleMemberIds: circle?.MemberIds ?? [],
TalkTopicId: circle?.TopicId,
ClassTeacherId: classTeacherId,
ClassTeacherName: classTeacherName,
Offenses: Offenses(roster, person, catalog, locale),
LessonMarks: LessonMarks(person, catalog, locale),
Attendance: Attendance(person, catalog, locale));
}
private static IReadOnlyList<LessonMarkEntryResponse> LessonMarks(
Person person,
DefCatalog? catalog,
string locale)
{
var rows = person.LessonMarks;
if (rows is null || rows.Count == 0)
{
return [];
}
var result = new LessonMarkEntryResponse[rows.Count];
for (var i = 0; i < rows.Count; i++)
{
var row = rows[i];
result[i] = new LessonMarkEntryResponse(
row.Subject,
SubjectLabel(catalog, locale, row.Subject),
row.Value,
row.Time,
row.Period);
}
return result;
}
private static IReadOnlyList<AttendanceEntryResponse> Attendance(
Person person,
DefCatalog? catalog,
string locale)
{
var rows = person.Attendance;
if (rows is null || rows.Count == 0)
{
return [];
}
var result = new AttendanceEntryResponse[rows.Count];
for (var i = 0; i < rows.Count; i++)
{
var row = rows[i];
result[i] = new AttendanceEntryResponse(
row.Subject,
SubjectLabel(catalog, locale, row.Subject),
row.Status,
StatusLabel(catalog, locale, row.Status),
row.Time,
row.Period,
row.AbsenceReason);
}
return result;
}
private static string SubjectLabel(DefCatalog? catalog, string locale, string subject)
{
if (catalog is not null && catalog.Subjects.TryGetValue(subject, out var def))
{
return catalog.Label(locale, def);
}
return subject;
}
private static string StatusLabel(DefCatalog? catalog, string locale, string status)
{
var key = AttendanceStatuses.LocaleKey(status);
if (catalog is not null && catalog.HasText(locale, key))
{
return catalog.Text(locale, key);
}
return status;
}
private static IReadOnlyList<OffenseEntryResponse> Offenses(
Roster roster,
Person person,
DefCatalog? catalog,
string locale)
{
var rows = person.Offenses;
if (rows is null || rows.Count == 0)
{
return [];
}
var byId = roster.People.ToDictionary(candidate => candidate.Id, StringComparer.Ordinal);
var result = new OffenseEntryResponse[rows.Count];
for (var i = 0; i < rows.Count; i++)
{
var row = rows[i];
string? otherName = null;
if (row.OtherPersonId is { } otherId && byId.TryGetValue(otherId, out var other))
{
otherName = other.Name.Full;
}
var label = KindLabel(catalog, locale, row.Kind);
result[i] = new OffenseEntryResponse(row.Kind, label, row.Time, row.OtherPersonId, otherName);
}
return result;
}
private static string KindLabel(DefCatalog? catalog, string locale, string kind)
{
var key = OffenseKinds.LocaleKey(kind);
if (catalog is not null && catalog.HasText(locale, key))
{
return catalog.Text(locale, key);
}
return kind;
}
private static IReadOnlyDictionary<string, float>? LiveNeeds(World world, string personId)
{
Dictionary<string, float>? found = null;
world.Query(in IdentityAndNeeds, (ref PersonIdentity identity, ref PersonNeeds needs) =>
{
if (identity.Id.Equals(personId, StringComparison.Ordinal))
{
found = new Dictionary<string, float>(needs.Values, StringComparer.Ordinal);
}
});
return found;
}
private static IReadOnlyDictionary<string, float>? LiveSkills(World world, string personId)
{
Dictionary<string, float>? found = null;
world.Query(in IdentityAndSkills, (ref PersonIdentity identity, ref PersonSkills skills) =>
{
if (identity.Id.Equals(personId, StringComparison.Ordinal))
{
found = new Dictionary<string, float>(skills.Values, StringComparer.Ordinal);
}
});
return found;
}
private static string? LiveActivity(World world, string personId)
{
string? found = null;
var query = IdentityAndActivity;
world.Query(in query, (ref PersonIdentity identity, ref PersonActivity activity) =>
{
if (identity.Id.Equals(personId, StringComparison.Ordinal) && activity.IsActive)
{
found = activity.ActionId;
}
});
return found;
}
public static string? NodeId(School school, string personId)
{
string? found = null;
var query = IdentityAndPresence;
school.World.Query(in query, (ref PersonIdentity identity, ref Presence presence) =>
{
if (identity.Id.Equals(personId, StringComparison.Ordinal))
{
found = presence.NodeId;
}
});
return found;
}
}