Add talk circles with TopicDef, staff/phone chat, and opinion outcomes.

This commit is contained in:
Leonid Pershin
2026-08-20 08:55:55 +03:00
parent 9d96108516
commit 47fc10da02
26 changed files with 1948 additions and 175 deletions
+58 -9
View File
@@ -324,6 +324,23 @@ internal static class PresenceSystem
{
var now = school.Clock.Time;
var walks = school.Walks!;
var slot = SchoolDay.At(school.Catalog!, now, school.SchoolWeekDays);
var weekday = SchoolDay.WeekdayIndex(now);
var lessons = Duty.LessonsToday(person, ClassOf(school, person), school.Timetable, weekday);
var bound = Duty.IsOtherStaff(person)
? slot.Kind != DaySlotKind.Outside
: slot.Kind == DaySlotKind.Lesson && lessons.Any(lesson => lesson.Period == slot.Index);
if (activity.IsActive && TalkActions.IsTalk(activity.ActionId))
{
if (!bound)
{
return null;
}
TalkCircleSystem.Interrupt(school, person.Id);
activity = PersonActivity.Idle;
}
if (!presence.IsOnCampus)
{
intent = Intent.None;
@@ -369,16 +386,11 @@ internal static class PresenceSystem
return null;
}
var slot = SchoolDay.At(school.Catalog!, now, school.SchoolWeekDays);
var weekday = SchoolDay.WeekdayIndex(now);
var lessons = Duty.LessonsToday(person, ClassOf(school, person), school.Timetable, weekday);
var bound = Duty.IsOtherStaff(person)
? slot.Kind != DaySlotKind.Outside
: slot.Kind == DaySlotKind.Lesson && lessons.Any(lesson => lesson.Period == slot.Index);
var frame = school.Catalog!.DayFrame;
var lunchOpen = frame is not null
&& SchoolDay.IsLunchWindow(frame, slot, ClassOf(school, person)?.Year);
var apparel = ApparelPresence.Build(school, person, ClassOf(school, person));
var talk = BuildTalkContext(school, person, bound, lunchOpen, slot);
var state = new ActorState(
presence.NodeId,
presence.DestinationId,
@@ -392,7 +404,8 @@ internal static class PresenceSystem
needs.Values,
intent,
lunchOpen,
apparel);
apparel,
talk);
var decision = DecisionPlanner.Decide(
school.Catalog!,
school.Map!,
@@ -401,17 +414,19 @@ internal static class PresenceSystem
(node, thing) => occupied.GetValueOrDefault((node, thing)));
var changing = activity.IsActive && ActivitySystem.IsChangeClothes(activity.ActionId);
var inTalk = activity.IsActive && TalkActions.IsTalk(activity.ActionId);
if (decision.WalkTo is not null
&& !decision.WalkTo.Equals(presence.NodeId, StringComparison.Ordinal)
&& activity.IsActive
&& !changing)
&& !changing
&& !inTalk)
{
activity = PersonActivity.Idle;
}
intent = decision.Intent;
if (decision.WalkTo is not null && !changing)
if (decision.WalkTo is not null && !changing && !inTalk)
{
presence = PresenceStepper.StartWalk(presence, walks, decision.WalkTo, headingHome: false);
}
@@ -523,6 +538,40 @@ internal static class PresenceSystem
return new Intent(kind, row.GoalId, row.GoalWeight, row.GoalAction);
}
private static TalkPlannerContext BuildTalkContext(
School school,
Person person,
bool boundToLesson,
bool lunchOpen,
DaySlot slot)
{
var opinions = person.Opinions as IReadOnlyDictionary<string, int> ?? new Dictionary<string, int>(StringComparer.Ordinal);
var nodes = SnapshotPersonNodes(school);
var friendPull = !boundToLesson && (slot.Kind == DaySlotKind.Break || lunchOpen);
return new TalkPlannerContext(
person.Id,
opinions,
nodes,
person.ClassId,
TalkCircles.HasPhone(person.Items),
friendPull,
school.Catalog?.BehaviorRules);
}
private static Dictionary<string, string> SnapshotPersonNodes(School school)
{
var nodes = new Dictionary<string, string>(StringComparer.Ordinal);
var world = school.World;
world.Query(in People, (ref PersonIdentity identity, ref Presence presence) =>
{
if (presence.IsOnCampus && presence.NodeId is not null)
{
nodes[identity.Id] = presence.NodeId;
}
});
return nodes;
}
private static SchoolClass? ClassOf(School school, Person person)
{
if (person.ClassId is null)