Whispering cuts lesson skill gain from BehaviorDef; a teacher in the room may interrupt into a discipline circle, and pedagogy scales the pupil's opinion of the teacher. Co-authored-by: Cursor <cursoragent@cursor.com>
674 lines
23 KiB
C#
674 lines
23 KiB
C#
using Arch.Core;
|
||
using HSchool.Ai;
|
||
using HSchool.Content;
|
||
using HSchool.People;
|
||
|
||
namespace HSchool.Simulation;
|
||
|
||
internal sealed class ActiveTalkCircle
|
||
{
|
||
public required string Id { get; init; }
|
||
|
||
public required string ActionId { get; init; }
|
||
|
||
public required string TopicId { get; init; }
|
||
|
||
public required string NodeId { get; init; }
|
||
|
||
public required List<string> Members { get; init; }
|
||
|
||
public float RemainingMinutes { get; set; }
|
||
|
||
/// <summary>Set once a teacher in the room has rolled to notice this whisper.</summary>
|
||
public bool CatchChecked { get; set; }
|
||
}
|
||
|
||
/// <summary>Forms 2–4 person talk circles, applies outcomes, writes topic log lines.</summary>
|
||
internal static class TalkCircleSystem
|
||
{
|
||
private static readonly QueryDescription People =
|
||
new QueryDescription().WithAll<PersonIdentity, PersonRoles, PersonTraits, PersonSkills, PersonNeeds, Presence, PersonActivity>();
|
||
|
||
public static bool IsInCircle(School school, string personId) =>
|
||
school.TalkCircleByPerson.ContainsKey(personId);
|
||
|
||
/// <summary>Ends an active circle when duty overrides break talk (bell rang).</summary>
|
||
public static void Interrupt(School school, string personId)
|
||
{
|
||
if (!school.TalkCircleByPerson.TryGetValue(personId, out var circleId)
|
||
|| !school.TalkCirclesById.TryGetValue(circleId, out var circle))
|
||
{
|
||
return;
|
||
}
|
||
|
||
Finish(school, circle);
|
||
}
|
||
|
||
public static bool TryStart(School school, string personId, string actionId)
|
||
{
|
||
if (school.Catalog is null || school.Map is null || school.Roster is null)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
if (!TalkActions.IsTalk(actionId) || !school.Catalog.Actions.TryGetValue(actionId, out var action) || action.Abstract)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
if (school.TalkCircleByPerson.ContainsKey(personId))
|
||
{
|
||
return true;
|
||
}
|
||
|
||
var person = school.Roster.People.FirstOrDefault(row => row.Id.Equals(personId, StringComparison.Ordinal));
|
||
if (person is null || !RoleFits(action, person))
|
||
{
|
||
return false;
|
||
}
|
||
|
||
string? nodeId = null;
|
||
var busy = false;
|
||
school.World.Query(in People, (ref PersonIdentity identity, ref Presence presence, ref PersonActivity activity) =>
|
||
{
|
||
if (!identity.Id.Equals(personId, StringComparison.Ordinal))
|
||
{
|
||
return;
|
||
}
|
||
|
||
nodeId = presence.NodeId;
|
||
busy = activity.IsActive;
|
||
});
|
||
|
||
if (nodeId is null || busy)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
var location = school.Map.NodeDef(nodeId);
|
||
if (location is null || !location.Equals(action.Room, StringComparison.Ordinal))
|
||
{
|
||
return false;
|
||
}
|
||
|
||
if (action.DefName.Equals(TalkActions.PhoneChat, StringComparison.Ordinal))
|
||
{
|
||
return TryStartPhone(school, person, nodeId, action);
|
||
}
|
||
|
||
var existing = FindOpenCircle(school, nodeId, actionId);
|
||
if (existing is not null)
|
||
{
|
||
return JoinCircle(school, existing, personId, action);
|
||
}
|
||
|
||
return TryStartGroup(school, person, nodeId, action);
|
||
}
|
||
|
||
public static IReadOnlyList<string> Apply(School school, double gameMinutes)
|
||
{
|
||
if (gameMinutes <= 0 || school.Catalog is null || school.Roster is null)
|
||
{
|
||
return [];
|
||
}
|
||
|
||
var minutes = (float)gameMinutes;
|
||
var completed = new List<string>();
|
||
foreach (var circle in school.TalkCirclesById.Values.ToArray())
|
||
{
|
||
circle.RemainingMinutes -= minutes;
|
||
SyncActivity(school, circle);
|
||
if (TalkActions.IsWhisper(circle.ActionId) && TryCatchWhisper(school, circle))
|
||
{
|
||
continue;
|
||
}
|
||
|
||
if (circle.RemainingMinutes > 0)
|
||
{
|
||
continue;
|
||
}
|
||
|
||
Finish(school, circle);
|
||
completed.AddRange(circle.Members);
|
||
}
|
||
|
||
completed.Sort(StringComparer.Ordinal);
|
||
return completed;
|
||
}
|
||
|
||
private static bool TryStartPhone(School school, Person initiator, string nodeId, ActionDef action)
|
||
{
|
||
if (!TalkCircles.HasPhone(initiator.Items))
|
||
{
|
||
return false;
|
||
}
|
||
|
||
var candidates = GatherCandidates(school, initiator.Id, nodeId, requirePhone: true);
|
||
var ranked = TalkCircles.RankInvitees(initiator, candidates, school.Catalog!.BehaviorRules);
|
||
if (ranked.Count == 0)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
var partnerId = ranked[0];
|
||
var partner = school.Roster!.People.First(row => row.Id.Equals(partnerId, StringComparison.Ordinal));
|
||
if (!TalkCircles.HasPhone(partner.Items))
|
||
{
|
||
return false;
|
||
}
|
||
|
||
var topicId = TalkCircles.PickTopic(
|
||
school.Catalog!,
|
||
initiator,
|
||
initiator.AgeOn(school.Clock.Time),
|
||
Seed.Mix(school.PeopleSeed, initiator.Id, DateOnly.FromDateTime(school.Clock.Time).DayNumber, Seed.ApparelSalt + 20))
|
||
?? school.Catalog!.Topics.Values.First(topic => !topic.Abstract).DefName;
|
||
|
||
var circle = new ActiveTalkCircle
|
||
{
|
||
Id = $"phone-{initiator.Id}-{partnerId}",
|
||
ActionId = action.DefName,
|
||
TopicId = topicId,
|
||
NodeId = nodeId,
|
||
Members = [initiator.Id, partnerId],
|
||
RemainingMinutes = action.Minutes,
|
||
};
|
||
Register(school, circle, action);
|
||
return true;
|
||
}
|
||
|
||
private static bool TryStartGroup(School school, Person initiator, string nodeId, ActionDef action)
|
||
{
|
||
var rules = school.Catalog!.BehaviorRules;
|
||
var max = TalkCircles.MaxSize(action.DefName, rules, initiator.Traits, school.Catalog);
|
||
var studentsOnly = TalkActions.IsWhisper(action.DefName) || TalkActions.IsTeacherTalk(action.DefName);
|
||
var candidates = GatherCandidates(school, initiator.Id, nodeId, requirePhone: false, studentsOnly);
|
||
var ranked = TalkCircles.RankInvitees(initiator, candidates, rules);
|
||
var members = new List<string> { initiator.Id };
|
||
foreach (var id in ranked)
|
||
{
|
||
if (members.Count >= max)
|
||
{
|
||
break;
|
||
}
|
||
|
||
members.Add(id);
|
||
}
|
||
|
||
var min = TalkActions.IsTeacherTalk(action.DefName) ? 3 : rules?.TalkCircleMin ?? 2;
|
||
if (members.Count < min)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
var topicId = TalkCircles.PickTopic(
|
||
school.Catalog,
|
||
initiator,
|
||
initiator.AgeOn(school.Clock.Time),
|
||
Seed.Mix(school.PeopleSeed, initiator.Id, DateOnly.FromDateTime(school.Clock.Time).DayNumber, Seed.ApparelSalt + 21),
|
||
whisperOnLesson: TalkActions.IsWhisper(action.DefName));
|
||
if (TalkActions.IsTeacherTalk(action.DefName))
|
||
{
|
||
topicId = TalkCircles.PickTeacherTopic(
|
||
school.Catalog,
|
||
rules,
|
||
Seed.Mix(school.PeopleSeed, initiator.Id, DateOnly.FromDateTime(school.Clock.Time).DayNumber, Seed.WhisperSalt));
|
||
}
|
||
|
||
topicId ??= school.Catalog.Topics.Values.First(topic => !topic.Abstract).DefName;
|
||
|
||
var circle = new ActiveTalkCircle
|
||
{
|
||
Id = $"talk-{initiator.Id}-{nodeId}-{school.Clock.Time.Ticks}",
|
||
ActionId = action.DefName,
|
||
TopicId = topicId,
|
||
NodeId = nodeId,
|
||
Members = members,
|
||
RemainingMinutes = action.Minutes,
|
||
};
|
||
Register(school, circle, action);
|
||
return true;
|
||
}
|
||
|
||
private static bool JoinCircle(School school, ActiveTalkCircle circle, string personId, ActionDef action)
|
||
{
|
||
var rules = school.Catalog!.BehaviorRules;
|
||
var person = school.Roster!.People.First(row => row.Id.Equals(personId, StringComparison.Ordinal));
|
||
if ((TalkActions.IsWhisper(action.DefName) || TalkActions.IsTeacherTalk(action.DefName)) && !person.IsStudent)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
var max = TalkCircles.MaxSize(action.DefName, rules, person.Traits, school.Catalog);
|
||
if (circle.Members.Count >= max || circle.Members.Contains(personId))
|
||
{
|
||
return false;
|
||
}
|
||
|
||
circle.Members.Add(personId);
|
||
circle.Members.Sort(StringComparer.Ordinal);
|
||
school.TalkCircleByPerson[personId] = circle.Id;
|
||
SetActivity(school, personId, action.DefName, circle.TopicId, circle.RemainingMinutes);
|
||
return true;
|
||
}
|
||
|
||
private static ActiveTalkCircle? FindOpenCircle(School school, string nodeId, string actionId)
|
||
{
|
||
foreach (var circle in school.TalkCirclesById.Values)
|
||
{
|
||
if (circle.NodeId.Equals(nodeId, StringComparison.Ordinal)
|
||
&& circle.ActionId.Equals(actionId, StringComparison.Ordinal)
|
||
&& circle.RemainingMinutes > 0)
|
||
{
|
||
var rules = school.Catalog!.BehaviorRules;
|
||
var max = TalkCircles.MaxSize(actionId, rules, [], school.Catalog);
|
||
if (circle.Members.Count < max)
|
||
{
|
||
return circle;
|
||
}
|
||
}
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
private static IReadOnlyList<TalkCircles.Candidate> GatherCandidates(
|
||
School school,
|
||
string exceptId,
|
||
string nodeId,
|
||
bool requirePhone,
|
||
bool studentsOnly = false)
|
||
{
|
||
var roster = school.Roster!.People.ToDictionary(person => person.Id, StringComparer.Ordinal);
|
||
var list = new List<TalkCircles.Candidate>();
|
||
school.World.Query(
|
||
in People,
|
||
(ref PersonIdentity identity, ref PersonRoles roles, ref Presence presence, ref PersonActivity activity) =>
|
||
{
|
||
if (identity.Id.Equals(exceptId, StringComparison.Ordinal)
|
||
|| presence.NodeId is null
|
||
|| !presence.NodeId.Equals(nodeId, StringComparison.Ordinal))
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (studentsOnly && !roles.IsStudent)
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (!roster.TryGetValue(identity.Id, out var person))
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (requirePhone && !TalkCircles.HasPhone(person.Items))
|
||
{
|
||
return;
|
||
}
|
||
|
||
list.Add(new TalkCircles.Candidate(
|
||
identity.Id,
|
||
roles.ClassId,
|
||
!activity.IsActive && !school.TalkCircleByPerson.ContainsKey(identity.Id),
|
||
school.TalkCircleByPerson.ContainsKey(identity.Id),
|
||
TalkCircles.HasPhone(person.Items)));
|
||
});
|
||
|
||
return list.Where(candidate => candidate.IsIdle).ToArray();
|
||
}
|
||
|
||
private static void Register(School school, ActiveTalkCircle circle, ActionDef action)
|
||
{
|
||
school.TalkCirclesById[circle.Id] = circle;
|
||
foreach (var member in circle.Members)
|
||
{
|
||
school.TalkCircleByPerson[member] = circle.Id;
|
||
SetActivity(school, member, action.DefName, circle.TopicId, circle.RemainingMinutes);
|
||
if (TalkActions.IsWhisper(action.DefName))
|
||
{
|
||
school.AppendDayLog(new PersonLogEvent(
|
||
member,
|
||
school.Clock.Time,
|
||
PersonLogTypes.Whispered,
|
||
circle.TopicId));
|
||
}
|
||
}
|
||
}
|
||
|
||
private static void SetActivity(School school, string personId, string actionId, string topicId, float remaining)
|
||
{
|
||
school.World.Query(
|
||
in People,
|
||
(ref PersonIdentity identity, ref PersonActivity activity) =>
|
||
{
|
||
if (identity.Id.Equals(personId, StringComparison.Ordinal))
|
||
{
|
||
activity = new PersonActivity(actionId, topicId, remaining);
|
||
}
|
||
});
|
||
}
|
||
|
||
private static void SyncActivity(School school, ActiveTalkCircle circle)
|
||
{
|
||
foreach (var member in circle.Members)
|
||
{
|
||
SetActivity(school, member, circle.ActionId, circle.TopicId, Math.Max(0f, circle.RemainingMinutes));
|
||
}
|
||
}
|
||
|
||
private static void Finish(School school, ActiveTalkCircle circle)
|
||
{
|
||
var catalog = school.Catalog!;
|
||
var rules = catalog.BehaviorRules;
|
||
var roster = school.Roster!;
|
||
var people = circle.Members
|
||
.Select(id => roster.People.First(person => person.Id.Equals(id, StringComparison.Ordinal)))
|
||
.ToArray();
|
||
if (!catalog.Topics.TryGetValue(circle.TopicId, out var topic))
|
||
{
|
||
topic = catalog.Topics.Values.First(def => !def.Abstract);
|
||
}
|
||
|
||
if (!catalog.Actions.TryGetValue(circle.ActionId, out var action))
|
||
{
|
||
action = catalog.Actions[TalkActions.Chat];
|
||
}
|
||
|
||
var hours = action.Minutes / 60f;
|
||
var sharedLanguage = TalkCircles.SharedLanguage(catalog, people, rules) is not null;
|
||
var language = TalkCircles.SharedLanguage(catalog, people, rules);
|
||
|
||
foreach (var person in people)
|
||
{
|
||
if (!string.IsNullOrWhiteSpace(action.Need)
|
||
&& catalog.Needs.TryGetValue(action.Need, out var need))
|
||
{
|
||
var current = NeedOf(school, person.Id, action.Need);
|
||
if (float.IsNaN(current))
|
||
{
|
||
current = need.Min;
|
||
}
|
||
|
||
var next = ActionStepper.ApplyNeedGain(current, action, need);
|
||
MutateNeed(school, person.Id, action.Need, next);
|
||
}
|
||
|
||
if (catalog.Skills.TryGetValue("Communication", out var communicationSkill))
|
||
{
|
||
var level = SkillOf(school, person.Id, "Communication");
|
||
var next = TalkCircles.CommunicationGain(level, communicationSkill, hours, rules);
|
||
MutateSkill(school, person.Id, "Communication", next);
|
||
}
|
||
|
||
if (language is not null
|
||
&& catalog.Skills.TryGetValue(language, out var languageSkill)
|
||
&& SkillOf(school, person.Id, language) < languageSkill.Range.Max)
|
||
{
|
||
var level = SkillOf(school, person.Id, language);
|
||
var next = TalkCircles.LanguageGain(level, languageSkill, hours, rules);
|
||
MutateSkill(school, person.Id, language, next);
|
||
}
|
||
|
||
foreach (var other in people)
|
||
{
|
||
if (other.Id.Equals(person.Id, StringComparison.Ordinal))
|
||
{
|
||
continue;
|
||
}
|
||
|
||
var communication = SkillOf(school, person.Id, "Communication");
|
||
var appearance = AppearanceOf(school, other);
|
||
var delta = TalkCircles.OpinionDelta(
|
||
person,
|
||
other,
|
||
topic,
|
||
communication,
|
||
sharedLanguage,
|
||
circle.ActionId,
|
||
appearance,
|
||
rules,
|
||
catalog);
|
||
if (person.IsStudent && other.IsStaff)
|
||
{
|
||
delta = TalkCircles.ScaleByPedagogy(delta, SkillOf(school, other.Id, "Pedagogy"), rules);
|
||
}
|
||
|
||
if (delta == 0)
|
||
{
|
||
continue;
|
||
}
|
||
|
||
var current = OpinionStore.Get(person, other.Id) ?? 0;
|
||
OpinionStore.Set(person, other.Id, current + delta);
|
||
}
|
||
|
||
school.AppendDayLog(new PersonLogEvent(
|
||
person.Id,
|
||
school.Clock.Time,
|
||
PersonLogTypes.TalkEnded,
|
||
circle.TopicId));
|
||
}
|
||
|
||
foreach (var member in circle.Members)
|
||
{
|
||
school.TalkCircleByPerson.Remove(member);
|
||
ClearActivity(school, member);
|
||
}
|
||
|
||
school.TalkCirclesById.Remove(circle.Id);
|
||
}
|
||
|
||
private static bool TryCatchWhisper(School school, ActiveTalkCircle circle)
|
||
{
|
||
if (circle.CatchChecked)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
var teacherId = StaffStandingIn(school, circle.NodeId);
|
||
if (teacherId is null)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
circle.CatchChecked = true;
|
||
var rules = school.Catalog!.BehaviorRules;
|
||
var traits = new List<string>();
|
||
foreach (var memberId in circle.Members)
|
||
{
|
||
var member = school.Roster!.People.FirstOrDefault(person => person.Id.Equals(memberId, StringComparison.Ordinal));
|
||
if (member is not null)
|
||
{
|
||
traits.AddRange(member.Traits);
|
||
}
|
||
}
|
||
|
||
var roll = TalkCircles.Roll01(
|
||
Seed.Mix(school.PeopleSeed, circle.Id, DateOnly.FromDateTime(school.Clock.Time).DayNumber, Seed.WhisperSalt));
|
||
var multiplier = TalkCircles.WhisperCatchMultiplier(traits, school.Catalog);
|
||
if (!TalkCircles.WhisperCaught(
|
||
roll,
|
||
rules?.WhisperCatchChance ?? 0.4f,
|
||
rules?.WhisperCatchMax ?? 0.85f,
|
||
multiplier))
|
||
{
|
||
return false;
|
||
}
|
||
|
||
ConvertToReprimand(school, circle, teacherId);
|
||
return true;
|
||
}
|
||
|
||
private static void ConvertToReprimand(School school, ActiveTalkCircle circle, string teacherId)
|
||
{
|
||
var pupils = circle.Members.ToArray();
|
||
foreach (var member in pupils)
|
||
{
|
||
school.AppendDayLog(new PersonLogEvent(
|
||
member,
|
||
school.Clock.Time,
|
||
PersonLogTypes.TeacherInterrupted,
|
||
circle.TopicId));
|
||
school.TalkCircleByPerson.Remove(member);
|
||
ClearActivity(school, member);
|
||
}
|
||
|
||
school.TalkCirclesById.Remove(circle.Id);
|
||
if (!school.Catalog!.Actions.TryGetValue(TalkActions.TeacherTalk, out var action))
|
||
{
|
||
return;
|
||
}
|
||
|
||
var members = new List<string> { teacherId };
|
||
var max = TalkCircles.MaxSize(TalkActions.TeacherTalk, school.Catalog.BehaviorRules, [], school.Catalog);
|
||
foreach (var id in pupils.OrderBy(value => value, StringComparer.Ordinal))
|
||
{
|
||
if (members.Count >= max)
|
||
{
|
||
break;
|
||
}
|
||
|
||
if (!members.Contains(id, StringComparer.Ordinal))
|
||
{
|
||
members.Add(id);
|
||
}
|
||
}
|
||
|
||
if (members.Count < 3)
|
||
{
|
||
return;
|
||
}
|
||
|
||
var reprimand = new ActiveTalkCircle
|
||
{
|
||
Id = $"reprimand-{teacherId}-{circle.NodeId}-{school.Clock.Time.Ticks}",
|
||
ActionId = TalkActions.TeacherTalk,
|
||
TopicId = TeacherTopics.Discipline,
|
||
NodeId = circle.NodeId,
|
||
Members = members,
|
||
RemainingMinutes = action.Minutes,
|
||
CatchChecked = true,
|
||
};
|
||
Register(school, reprimand, action);
|
||
}
|
||
|
||
private static string? StaffStandingIn(School school, string nodeId)
|
||
{
|
||
string? found = null;
|
||
school.World.Query(
|
||
in People,
|
||
(ref PersonIdentity identity, ref PersonRoles roles, ref Presence presence, ref PersonActivity activity) =>
|
||
{
|
||
if (found is not null
|
||
|| !roles.IsStaff
|
||
|| presence.NodeId is null
|
||
|| !presence.NodeId.Equals(nodeId, StringComparison.Ordinal)
|
||
|| presence.Path.Length > 0
|
||
|| presence.RemainingMinutes > 0)
|
||
{
|
||
return;
|
||
}
|
||
|
||
found = identity.Id;
|
||
});
|
||
return found;
|
||
}
|
||
|
||
private static bool RoleFits(ActionDef action, Person person)
|
||
{
|
||
if (action.Roles.Count == 0)
|
||
{
|
||
return true;
|
||
}
|
||
|
||
foreach (var role in action.Roles)
|
||
{
|
||
if (role.Equals(HSchool.Content.PersonRoles.Student, StringComparison.OrdinalIgnoreCase) && person.IsStudent)
|
||
{
|
||
return true;
|
||
}
|
||
|
||
if (role.Equals(HSchool.Content.PersonRoles.Staff, StringComparison.OrdinalIgnoreCase) && person.IsStaff)
|
||
{
|
||
return true;
|
||
}
|
||
|
||
if (role.Equals(HSchool.Content.PersonRoles.Parent, StringComparison.OrdinalIgnoreCase) && person.IsParent)
|
||
{
|
||
return true;
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
private static ApparelIssue AppearanceOf(School school, Person person)
|
||
{
|
||
var schoolClass = person.ClassId is { } classId
|
||
? school.Roster!.Classes.FirstOrDefault(row => row.Id.Equals(classId, StringComparison.Ordinal))
|
||
: null;
|
||
var mode = ApparelPresence.ModeAt(school, person, schoolClass);
|
||
return ApparelDresser.CurrentIssues(school, person, mode);
|
||
}
|
||
|
||
private static float NeedOf(School school, string personId, string need)
|
||
{
|
||
var value = float.NaN;
|
||
school.World.Query(in People, (ref PersonIdentity identity, ref PersonNeeds needs) =>
|
||
{
|
||
if (identity.Id.Equals(personId, StringComparison.Ordinal))
|
||
{
|
||
value = needs.Values.GetValueOrDefault(need, float.NaN);
|
||
}
|
||
});
|
||
return value;
|
||
}
|
||
|
||
private static float SkillOf(School school, string personId, string skill)
|
||
{
|
||
var value = 0f;
|
||
school.World.Query(in People, (ref PersonIdentity identity, ref PersonSkills skills) =>
|
||
{
|
||
if (identity.Id.Equals(personId, StringComparison.Ordinal))
|
||
{
|
||
value = skills.Values.GetValueOrDefault(skill);
|
||
}
|
||
});
|
||
return value;
|
||
}
|
||
|
||
private static void MutateSkill(School school, string personId, string skill, float value)
|
||
{
|
||
school.World.Query(in People, (ref PersonIdentity identity, ref PersonSkills skills) =>
|
||
{
|
||
if (identity.Id.Equals(personId, StringComparison.Ordinal))
|
||
{
|
||
skills.Values[skill] = value;
|
||
}
|
||
});
|
||
}
|
||
|
||
private static void MutateNeed(School school, string personId, string need, float value)
|
||
{
|
||
school.World.Query(in People, (ref PersonIdentity identity, ref PersonNeeds needs) =>
|
||
{
|
||
if (identity.Id.Equals(personId, StringComparison.Ordinal))
|
||
{
|
||
needs.Values[need] = value;
|
||
}
|
||
});
|
||
}
|
||
|
||
private static void ClearActivity(School school, string personId)
|
||
{
|
||
school.World.Query(in People, (ref PersonIdentity identity, ref PersonActivity activity) =>
|
||
{
|
||
if (identity.Id.Equals(personId, StringComparison.Ordinal))
|
||
{
|
||
activity = PersonActivity.Idle;
|
||
}
|
||
});
|
||
}
|
||
}
|