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
+38 -16
View File
@@ -41,7 +41,8 @@ public readonly record struct ActorState(
IReadOnlyDictionary<string, float> Needs,
Intent Intent,
bool LunchWindowOpen = false,
ApparelActor Apparel = default);
ApparelActor Apparel = default,
TalkPlannerContext Talk = default);
/// <summary>
/// Picks a goal by weight and plans walk-then-do. No world, no clock — a table of inputs to an
@@ -254,6 +255,16 @@ public static class DecisionPlanner
return Intent.None;
}
if (state.BoundToLesson && TalkCircles.BlocksOnLesson(action.DefName))
{
return Intent.None;
}
if (action.DefName.Equals(TalkActions.PhoneChat, StringComparison.Ordinal) && !state.Talk.HasPhone)
{
return Intent.None;
}
if (RoomFor(catalog, map, walks, state, occupied, action) is null)
{
return Intent.None;
@@ -396,8 +407,9 @@ public static class DecisionPlanner
}
string? best = null;
var bestCost = float.PositiveInfinity;
var bestScore = float.NegativeInfinity;
var from = state.NodeId ?? walks.TerritoryId;
var candidates = new List<string>();
foreach (var room in map.Rooms)
{
if (!action.Room.Equals(room.Def, StringComparison.Ordinal))
@@ -410,27 +422,37 @@ public static class DecisionPlanner
continue;
}
var cost = walks.Minutes(from, room.Id);
if (float.IsInfinity(cost) || cost > bestCost)
{
continue;
}
if (cost < bestCost || best is null || string.CompareOrdinal(room.Id, best) < 0)
{
best = room.Id;
bestCost = cost;
}
candidates.Add(room.Id);
}
if (map.Territory is { } territory
&& action.Room.Equals(territory.Def, StringComparison.Ordinal)
&& HasSlot(catalog, map, occupied, territory.Id, action.Thing))
{
var cost = walks.Minutes(from, territory.Id);
if (!float.IsInfinity(cost) && (best is null || cost < bestCost || (cost == bestCost && string.CompareOrdinal(territory.Id, best) < 0)))
candidates.Add(territory.Id);
}
var hasAlternative = candidates.Count > 1;
foreach (var nodeId in candidates)
{
if (action.Lunch
&& TalkCircles.LunchNodeBlockedByEnemy(nodeId, state.Talk.SelfId ?? "", state.Talk, hasAlternative))
{
best = territory.Id;
continue;
}
var cost = walks.Minutes(from, nodeId);
if (float.IsInfinity(cost))
{
continue;
}
var score = -cost + TalkCircles.NodeFriendScore(nodeId, state.Talk.SelfId ?? "", state.Talk);
if (score > bestScore
|| (score == bestScore && (best is null || string.CompareOrdinal(nodeId, best) < 0)))
{
best = nodeId;
bestScore = score;
}
}