Enhance ActivityLog and ActivityInterestTable with detailed target resolution and activity tracking. Implemented new methods for resolving actor targets and formatting activity statistics, including colored display options. Updated HUD interactions to reflect these changes and added test scenarios for improved activity logging and display functionality.
This commit is contained in:
parent
beb08265ec
commit
63bf44cae5
8 changed files with 1208 additions and 230 deletions
|
|
@ -213,13 +213,47 @@ public static class ActivityInterestTable
|
||||||
|
|
||||||
public static string TargetLabel(Actor actor)
|
public static string TargetLabel(Actor actor)
|
||||||
{
|
{
|
||||||
|
ResolveTarget(actor, out string name, out _, out string place);
|
||||||
|
return !string.IsNullOrEmpty(name) ? name : place;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Prefer live actor targets (by name). Places/buildings stay as type labels.
|
||||||
|
/// </summary>
|
||||||
|
public static void ResolveTarget(
|
||||||
|
Actor actor,
|
||||||
|
out string targetName,
|
||||||
|
out bool targetIsActor,
|
||||||
|
out string placeLabel)
|
||||||
|
{
|
||||||
|
targetName = "";
|
||||||
|
targetIsActor = false;
|
||||||
|
placeLabel = "";
|
||||||
if (actor == null)
|
if (actor == null)
|
||||||
{
|
{
|
||||||
return "";
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
if (actor.has_attack_target && actor.attack_target != null && actor.attack_target.isAlive())
|
||||||
|
{
|
||||||
|
if (actor.attack_target.isActor())
|
||||||
|
{
|
||||||
|
Actor foe = actor.attack_target.a;
|
||||||
|
if (foe != null)
|
||||||
|
{
|
||||||
|
string n = foe.getName();
|
||||||
|
if (!string.IsNullOrEmpty(n))
|
||||||
|
{
|
||||||
|
targetName = n;
|
||||||
|
targetIsActor = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (actor.beh_actor_target != null && actor.beh_actor_target.isAlive())
|
if (actor.beh_actor_target != null && actor.beh_actor_target.isAlive())
|
||||||
{
|
{
|
||||||
if (actor.beh_actor_target.isActor())
|
if (actor.beh_actor_target.isActor())
|
||||||
|
|
@ -228,11 +262,17 @@ public static class ActivityInterestTable
|
||||||
if (other != null)
|
if (other != null)
|
||||||
{
|
{
|
||||||
string n = other.getName();
|
string n = other.getName();
|
||||||
return string.IsNullOrEmpty(n) ? "foe" : n;
|
if (!string.IsNullOrEmpty(n))
|
||||||
|
{
|
||||||
|
targetName = n;
|
||||||
|
targetIsActor = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return "target";
|
placeLabel = "their mark";
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (actor.beh_building_target != null && actor.beh_building_target.isAlive())
|
if (actor.beh_building_target != null && actor.beh_building_target.isAlive())
|
||||||
|
|
@ -242,23 +282,41 @@ public static class ActivityInterestTable
|
||||||
string id = b.asset != null ? b.asset.id : "";
|
string id = b.asset != null ? b.asset.id : "";
|
||||||
if (!string.IsNullOrEmpty(type))
|
if (!string.IsNullOrEmpty(type))
|
||||||
{
|
{
|
||||||
return type.Replace("type_", "");
|
placeLabel = type.Replace("type_", "");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return string.IsNullOrEmpty(id) ? "building" : id;
|
placeLabel = string.IsNullOrEmpty(id) ? "a building" : id;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (actor.beh_tile_target != null)
|
if (actor.beh_tile_target != null)
|
||||||
{
|
{
|
||||||
return "tile";
|
placeLabel = "the wilds";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return "";
|
public static string ActorName(Actor actor)
|
||||||
|
{
|
||||||
|
if (actor == null)
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string n = actor.getName();
|
||||||
|
return string.IsNullOrEmpty(n) ? "" : n;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool ContainsAny(string hay, params string[] needles)
|
private static bool ContainsAny(string hay, params string[] needles)
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,10 @@ public sealed class ActivityEntry
|
||||||
public string TargetLabel = "";
|
public string TargetLabel = "";
|
||||||
/// <summary>Stable fact line for asserts.</summary>
|
/// <summary>Stable fact line for asserts.</summary>
|
||||||
public string Line = "";
|
public string Line = "";
|
||||||
/// <summary>Varied display prose for HUD.</summary>
|
/// <summary>Varied display prose for HUD (plain text).</summary>
|
||||||
public string DisplayLine = "";
|
public string DisplayLine = "";
|
||||||
|
/// <summary>Display prose with colored names (Unity rich text).</summary>
|
||||||
|
public string DisplayLineRich = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -38,7 +40,17 @@ public static class ActivityLog
|
||||||
private static readonly Dictionary<long, float> LastBeatAt = new Dictionary<long, float>();
|
private static readonly Dictionary<long, float> LastBeatAt = new Dictionary<long, float>();
|
||||||
private static readonly Dictionary<long, int> LineIndex = new Dictionary<long, int>();
|
private static readonly Dictionary<long, int> LineIndex = new Dictionary<long, int>();
|
||||||
|
|
||||||
|
private static int _notesSinceClear;
|
||||||
|
private static int _tasksSinceClear;
|
||||||
|
private static int _beatsSinceClear;
|
||||||
|
private static float _clearedAt = -1f;
|
||||||
|
private static readonly Dictionary<string, int> TaskHitsSinceClear =
|
||||||
|
new Dictionary<string, int>();
|
||||||
|
|
||||||
public static int SubjectCount => BySubject.Count;
|
public static int SubjectCount => BySubject.Count;
|
||||||
|
public static int NotesSinceClear => _notesSinceClear;
|
||||||
|
public static int TasksSinceClear => _tasksSinceClear;
|
||||||
|
public static int BeatsSinceClear => _beatsSinceClear;
|
||||||
|
|
||||||
public static void ClearSession()
|
public static void ClearSession()
|
||||||
{
|
{
|
||||||
|
|
@ -46,6 +58,52 @@ public static class ActivityLog
|
||||||
LastTaskId.Clear();
|
LastTaskId.Clear();
|
||||||
LastBeatAt.Clear();
|
LastBeatAt.Clear();
|
||||||
LineIndex.Clear();
|
LineIndex.Clear();
|
||||||
|
ResetSampleCounters();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Harness: reset rate counters without wiping the ring.</summary>
|
||||||
|
public static void ResetSampleCounters()
|
||||||
|
{
|
||||||
|
_notesSinceClear = 0;
|
||||||
|
_tasksSinceClear = 0;
|
||||||
|
_beatsSinceClear = 0;
|
||||||
|
_clearedAt = Time.unscaledTime;
|
||||||
|
TaskHitsSinceClear.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string FormatSampleStats()
|
||||||
|
{
|
||||||
|
float elapsed = _clearedAt < 0f ? 0f : Mathf.Max(0.01f, Time.unscaledTime - _clearedAt);
|
||||||
|
float perSec = _notesSinceClear / elapsed;
|
||||||
|
float perMin = perSec * 60f;
|
||||||
|
int lines = 0;
|
||||||
|
foreach (var kv in BySubject)
|
||||||
|
{
|
||||||
|
lines += kv.Value != null ? kv.Value.Count : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
string top = "";
|
||||||
|
if (TaskHitsSinceClear.Count > 0)
|
||||||
|
{
|
||||||
|
var ranked = new List<KeyValuePair<string, int>>(TaskHitsSinceClear);
|
||||||
|
ranked.Sort((a, b) => b.Value.CompareTo(a.Value));
|
||||||
|
int n = Mathf.Min(8, ranked.Count);
|
||||||
|
for (int i = 0; i < n; i++)
|
||||||
|
{
|
||||||
|
if (i > 0)
|
||||||
|
{
|
||||||
|
top += ", ";
|
||||||
|
}
|
||||||
|
|
||||||
|
top += ranked[i].Key + "=" + ranked[i].Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float perSubjectPerMin = BySubject.Count > 0 ? perMin / BySubject.Count : 0f;
|
||||||
|
return
|
||||||
|
$"elapsed={elapsed:0.0}s notes={_notesSinceClear} tasks={_tasksSinceClear} beats={_beatsSinceClear} "
|
||||||
|
+ $"per_sec={perSec:0.00} per_min={perMin:0.0} per_subject_per_min={perSubjectPerMin:0.00} "
|
||||||
|
+ $"subjects={BySubject.Count} ring_lines={lines} top=[{top}]";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int CountFor(long subjectId)
|
public static int CountFor(long subjectId)
|
||||||
|
|
@ -110,7 +168,9 @@ public static class ActivityLog
|
||||||
|
|
||||||
LastTaskId[id] = taskId;
|
LastTaskId[id] = taskId;
|
||||||
string jobId = ActivityInterestTable.SafeJobId(actor);
|
string jobId = ActivityInterestTable.SafeJobId(actor);
|
||||||
string target = ActivityInterestTable.TargetLabel(actor);
|
ActivityInterestTable.ResolveTarget(
|
||||||
|
actor, out string targetName, out bool targetIsActor, out string place);
|
||||||
|
string actorName = ActivityInterestTable.ActorName(actor);
|
||||||
string loc = "";
|
string loc = "";
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -124,15 +184,27 @@ public static class ActivityLog
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string targetLabel = !string.IsNullOrEmpty(targetName) ? targetName : place;
|
||||||
string raw = string.IsNullOrEmpty(loc) ? ("Task: " + taskId) : loc;
|
string raw = string.IsNullOrEmpty(loc) ? ("Task: " + taskId) : loc;
|
||||||
if (!string.IsNullOrEmpty(target))
|
if (!string.IsNullOrEmpty(targetLabel))
|
||||||
{
|
{
|
||||||
raw = raw + " → " + target;
|
raw = raw + " → " + targetLabel;
|
||||||
}
|
}
|
||||||
|
|
||||||
int idx = NextIndex(id);
|
int idx = NextIndex(id);
|
||||||
string display = ActivityProse.Format(
|
ActivityProse.Format(
|
||||||
ActivityKind.TaskStart, taskId, raw, target, id, idx);
|
ActivityKind.TaskStart,
|
||||||
|
taskId,
|
||||||
|
actorName,
|
||||||
|
targetName,
|
||||||
|
targetIsActor,
|
||||||
|
place,
|
||||||
|
raw,
|
||||||
|
id,
|
||||||
|
idx,
|
||||||
|
out string display,
|
||||||
|
out string displayRich);
|
||||||
|
RecordSample(taskId, isBeat: false);
|
||||||
Append(id, new ActivityEntry
|
Append(id, new ActivityEntry
|
||||||
{
|
{
|
||||||
CreatedAt = Time.unscaledTime,
|
CreatedAt = Time.unscaledTime,
|
||||||
|
|
@ -140,12 +212,32 @@ public static class ActivityLog
|
||||||
Kind = ActivityKind.TaskStart,
|
Kind = ActivityKind.TaskStart,
|
||||||
TaskId = taskId,
|
TaskId = taskId,
|
||||||
JobId = jobId,
|
JobId = jobId,
|
||||||
TargetLabel = target,
|
TargetLabel = targetLabel,
|
||||||
Line = raw,
|
Line = raw,
|
||||||
DisplayLine = display
|
DisplayLine = display,
|
||||||
|
DisplayLineRich = displayRich
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Capture safety net: if the live task id differs from the last logged id, note it.
|
||||||
|
/// </summary>
|
||||||
|
public static void EnsureCurrentTask(Actor actor)
|
||||||
|
{
|
||||||
|
if (actor == null || !actor.isAlive())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string taskId = ActivityInterestTable.SafeTaskId(actor);
|
||||||
|
if (string.IsNullOrEmpty(taskId))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NoteTask(actor, taskId);
|
||||||
|
}
|
||||||
|
|
||||||
public static void NoteActionBeat(Actor actor, string actionKey, string rawFact, string target)
|
public static void NoteActionBeat(Actor actor, string actionKey, string rawFact, string target)
|
||||||
{
|
{
|
||||||
if (actor == null || !actor.isAlive() || string.IsNullOrEmpty(actionKey))
|
if (actor == null || !actor.isAlive() || string.IsNullOrEmpty(actionKey))
|
||||||
|
|
@ -177,10 +269,30 @@ public static class ActivityLog
|
||||||
LastBeatAt[id] = now;
|
LastBeatAt[id] = now;
|
||||||
string taskId = ActivityInterestTable.SafeTaskId(actor);
|
string taskId = ActivityInterestTable.SafeTaskId(actor);
|
||||||
string jobId = ActivityInterestTable.SafeJobId(actor);
|
string jobId = ActivityInterestTable.SafeJobId(actor);
|
||||||
|
string actorName = ActivityInterestTable.ActorName(actor);
|
||||||
|
ActivityInterestTable.ResolveTarget(
|
||||||
|
actor, out string resolvedName, out bool targetIsActor, out string place);
|
||||||
|
if (string.IsNullOrEmpty(resolvedName) && !string.IsNullOrEmpty(target))
|
||||||
|
{
|
||||||
|
resolvedName = target;
|
||||||
|
targetIsActor = true;
|
||||||
|
}
|
||||||
|
|
||||||
string fact = string.IsNullOrEmpty(rawFact) ? actionKey : rawFact;
|
string fact = string.IsNullOrEmpty(rawFact) ? actionKey : rawFact;
|
||||||
int idx = NextIndex(id);
|
int idx = NextIndex(id);
|
||||||
string display = ActivityProse.Format(
|
ActivityProse.Format(
|
||||||
ActivityKind.ActionBeat, actionKey, fact, target ?? "", id, idx);
|
ActivityKind.ActionBeat,
|
||||||
|
actionKey,
|
||||||
|
actorName,
|
||||||
|
resolvedName,
|
||||||
|
targetIsActor,
|
||||||
|
place,
|
||||||
|
fact,
|
||||||
|
id,
|
||||||
|
idx,
|
||||||
|
out string display,
|
||||||
|
out string displayRich);
|
||||||
|
RecordSample(actionKey, isBeat: true);
|
||||||
Append(id, new ActivityEntry
|
Append(id, new ActivityEntry
|
||||||
{
|
{
|
||||||
CreatedAt = now,
|
CreatedAt = now,
|
||||||
|
|
@ -188,14 +300,21 @@ public static class ActivityLog
|
||||||
Kind = ActivityKind.ActionBeat,
|
Kind = ActivityKind.ActionBeat,
|
||||||
TaskId = string.IsNullOrEmpty(taskId) ? actionKey : taskId,
|
TaskId = string.IsNullOrEmpty(taskId) ? actionKey : taskId,
|
||||||
JobId = jobId,
|
JobId = jobId,
|
||||||
TargetLabel = target ?? "",
|
TargetLabel = !string.IsNullOrEmpty(resolvedName) ? resolvedName : (place ?? ""),
|
||||||
Line = fact,
|
Line = fact,
|
||||||
DisplayLine = display
|
DisplayLine = display,
|
||||||
|
DisplayLineRich = displayRich
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Harness: inject activity without a live AI transition.</summary>
|
/// <summary>Harness: inject activity without a live AI transition.</summary>
|
||||||
public static bool ForceNote(long subjectId, string taskOrAction, string rawLine, bool asBeat = false)
|
public static bool ForceNote(
|
||||||
|
long subjectId,
|
||||||
|
string taskOrAction,
|
||||||
|
string rawLine,
|
||||||
|
bool asBeat = false,
|
||||||
|
string actorName = "",
|
||||||
|
string targetName = "")
|
||||||
{
|
{
|
||||||
if (subjectId == 0 || string.IsNullOrEmpty(taskOrAction))
|
if (subjectId == 0 || string.IsNullOrEmpty(taskOrAction))
|
||||||
{
|
{
|
||||||
|
|
@ -205,19 +324,54 @@ public static class ActivityLog
|
||||||
int idx = NextIndex(subjectId);
|
int idx = NextIndex(subjectId);
|
||||||
string fact = string.IsNullOrEmpty(rawLine) ? taskOrAction : rawLine;
|
string fact = string.IsNullOrEmpty(rawLine) ? taskOrAction : rawLine;
|
||||||
ActivityKind kind = asBeat ? ActivityKind.ActionBeat : ActivityKind.TaskStart;
|
ActivityKind kind = asBeat ? ActivityKind.ActionBeat : ActivityKind.TaskStart;
|
||||||
string display = ActivityProse.Format(kind, taskOrAction, fact, "", subjectId, idx);
|
ActivityProse.Format(
|
||||||
|
kind,
|
||||||
|
taskOrAction,
|
||||||
|
actorName,
|
||||||
|
targetName,
|
||||||
|
!string.IsNullOrEmpty(targetName),
|
||||||
|
"",
|
||||||
|
fact,
|
||||||
|
subjectId,
|
||||||
|
idx,
|
||||||
|
out string display,
|
||||||
|
out string displayRich);
|
||||||
Append(subjectId, new ActivityEntry
|
Append(subjectId, new ActivityEntry
|
||||||
{
|
{
|
||||||
CreatedAt = Time.unscaledTime,
|
CreatedAt = Time.unscaledTime,
|
||||||
SubjectId = subjectId,
|
SubjectId = subjectId,
|
||||||
Kind = kind,
|
Kind = kind,
|
||||||
TaskId = taskOrAction,
|
TaskId = taskOrAction,
|
||||||
|
TargetLabel = targetName ?? "",
|
||||||
Line = fact,
|
Line = fact,
|
||||||
DisplayLine = display
|
DisplayLine = display,
|
||||||
|
DisplayLineRich = displayRich
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void RecordSample(string key, bool isBeat)
|
||||||
|
{
|
||||||
|
if (_clearedAt < 0f)
|
||||||
|
{
|
||||||
|
_clearedAt = Time.unscaledTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
_notesSinceClear++;
|
||||||
|
if (isBeat)
|
||||||
|
{
|
||||||
|
_beatsSinceClear++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_tasksSinceClear++;
|
||||||
|
}
|
||||||
|
|
||||||
|
string k = string.IsNullOrEmpty(key) ? "?" : key;
|
||||||
|
TaskHitsSinceClear.TryGetValue(k, out int n);
|
||||||
|
TaskHitsSinceClear[k] = n + 1;
|
||||||
|
}
|
||||||
|
|
||||||
private static int NextIndex(long id)
|
private static int NextIndex(long id)
|
||||||
{
|
{
|
||||||
LineIndex.TryGetValue(id, out int idx);
|
LineIndex.TryGetValue(id, out int idx);
|
||||||
|
|
|
||||||
|
|
@ -3,196 +3,347 @@ using System.Collections.Generic;
|
||||||
namespace IdleSpectator;
|
namespace IdleSpectator;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fact-preserving multi-variant activity lines (anti-stale dossier feed).
|
/// Fact-preserving activity sentences with colored actor/target names.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class ActivityProse
|
public static class ActivityProse
|
||||||
{
|
{
|
||||||
|
public const string NameColorHex = "#F0C14A";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Templates use {actor} and optional {target}. Never invent outcomes.
|
||||||
|
/// </summary>
|
||||||
private static readonly Dictionary<string, string[]> Variants = new Dictionary<string, string[]>
|
private static readonly Dictionary<string, string[]> Variants = new Dictionary<string, string[]>
|
||||||
{
|
{
|
||||||
["fighting"] = new[]
|
["attack"] = new[]
|
||||||
{
|
{
|
||||||
"Locked in combat",
|
"{actor} strikes at {target}",
|
||||||
"Trading blows",
|
"{actor} presses the attack on {target}",
|
||||||
"In the fray",
|
"{actor} lunges toward {target}"
|
||||||
"Clash of steel"
|
|
||||||
},
|
},
|
||||||
["fight"] = new[]
|
["fight"] = new[]
|
||||||
{
|
{
|
||||||
"Locked in combat",
|
"{actor} fights {target}",
|
||||||
"Trading blows",
|
"{actor} clashes with {target}",
|
||||||
"In the fray"
|
"{actor} trades blows with {target}"
|
||||||
},
|
},
|
||||||
["attack"] = new[]
|
["fighting"] = new[]
|
||||||
{
|
{
|
||||||
"Strikes at {target}",
|
"{actor} fights {target}",
|
||||||
"Presses the attack on {target}",
|
"{actor} is locked in combat with {target}",
|
||||||
"Lunges toward {target}"
|
"{actor} clashes with {target}"
|
||||||
},
|
},
|
||||||
["hunt"] = new[]
|
["hunt"] = new[]
|
||||||
{
|
{
|
||||||
"Hunts {target}",
|
"{actor} hunts {target}",
|
||||||
"Stalks {target}",
|
"{actor} stalks {target}",
|
||||||
"On the trail of {target}"
|
"{actor} closes on {target}"
|
||||||
},
|
|
||||||
["eating"] = new[]
|
|
||||||
{
|
|
||||||
"Taking a meal",
|
|
||||||
"Eating",
|
|
||||||
"Breaking hunger"
|
|
||||||
},
|
|
||||||
["eat"] = new[]
|
|
||||||
{
|
|
||||||
"Taking a meal",
|
|
||||||
"Eating",
|
|
||||||
"Breaking hunger"
|
|
||||||
},
|
|
||||||
["sleep"] = new[]
|
|
||||||
{
|
|
||||||
"Sleeping",
|
|
||||||
"Resting",
|
|
||||||
"Deep in slumber"
|
|
||||||
},
|
|
||||||
["random_move"] = new[]
|
|
||||||
{
|
|
||||||
"Wandering",
|
|
||||||
"Roaming aimlessly",
|
|
||||||
"Drifting along"
|
|
||||||
},
|
|
||||||
["pollinate"] = new[]
|
|
||||||
{
|
|
||||||
"Dusts the blossom",
|
|
||||||
"Works the flower",
|
|
||||||
"Leaves pollen behind",
|
|
||||||
"Humming over petals"
|
|
||||||
},
|
|
||||||
["farm"] = new[]
|
|
||||||
{
|
|
||||||
"Tends the fields",
|
|
||||||
"Works the soil",
|
|
||||||
"Farming the plot"
|
|
||||||
},
|
|
||||||
["harvest"] = new[]
|
|
||||||
{
|
|
||||||
"Harvests the crop",
|
|
||||||
"Gathers the yield",
|
|
||||||
"Cuts the ripe fields"
|
|
||||||
},
|
|
||||||
["mine"] = new[]
|
|
||||||
{
|
|
||||||
"Delves the mine",
|
|
||||||
"Chipping ore",
|
|
||||||
"Deep in the dig"
|
|
||||||
},
|
|
||||||
["build"] = new[]
|
|
||||||
{
|
|
||||||
"Building",
|
|
||||||
"Raising walls",
|
|
||||||
"At the worksite"
|
|
||||||
},
|
|
||||||
["gather"] = new[]
|
|
||||||
{
|
|
||||||
"Gathering stores",
|
|
||||||
"Foraging supplies",
|
|
||||||
"Collecting goods"
|
|
||||||
},
|
|
||||||
["woodcut"] = new[]
|
|
||||||
{
|
|
||||||
"Felling timber",
|
|
||||||
"Cutting wood",
|
|
||||||
"Axe to the trees"
|
|
||||||
},
|
|
||||||
["fire"] = new[]
|
|
||||||
{
|
|
||||||
"Fighting the blaze",
|
|
||||||
"Dousing flames",
|
|
||||||
"Racing the fire"
|
|
||||||
},
|
|
||||||
["fireman"] = new[]
|
|
||||||
{
|
|
||||||
"Fighting the blaze",
|
|
||||||
"Dousing flames",
|
|
||||||
"Racing the fire"
|
|
||||||
},
|
|
||||||
["social"] = new[]
|
|
||||||
{
|
|
||||||
"Socializing",
|
|
||||||
"In conversation",
|
|
||||||
"Among companions"
|
|
||||||
},
|
|
||||||
["lover"] = new[]
|
|
||||||
{
|
|
||||||
"Seeking a lover",
|
|
||||||
"Drawn to {target}",
|
|
||||||
"Heart on the path"
|
|
||||||
},
|
|
||||||
["unload"] = new[]
|
|
||||||
{
|
|
||||||
"Unloads goods in town",
|
|
||||||
"Delivers the haul",
|
|
||||||
"Empties the pack at home"
|
|
||||||
},
|
|
||||||
["store"] = new[]
|
|
||||||
{
|
|
||||||
"Stores supplies",
|
|
||||||
"Stocking the village",
|
|
||||||
"Puts goods away"
|
|
||||||
},
|
|
||||||
["fish"] = new[]
|
|
||||||
{
|
|
||||||
"Fishing",
|
|
||||||
"Casting for fish",
|
|
||||||
"Working the waters"
|
|
||||||
},
|
|
||||||
["trade"] = new[]
|
|
||||||
{
|
|
||||||
"Trading",
|
|
||||||
"Bartering goods",
|
|
||||||
"On a trade run"
|
|
||||||
},
|
|
||||||
["run_away"] = new[]
|
|
||||||
{
|
|
||||||
"Fleeing",
|
|
||||||
"Running for safety",
|
|
||||||
"In retreat"
|
|
||||||
},
|
|
||||||
["make_decision"] = new[]
|
|
||||||
{
|
|
||||||
"Considering next steps",
|
|
||||||
"Weighing a choice",
|
|
||||||
"Paused in thought"
|
|
||||||
},
|
|
||||||
["wait"] = new[]
|
|
||||||
{
|
|
||||||
"Waiting",
|
|
||||||
"Holding still",
|
|
||||||
"Biding time"
|
|
||||||
},
|
|
||||||
["BehPollinate"] = new[]
|
|
||||||
{
|
|
||||||
"Dusts the blossom",
|
|
||||||
"Works the flower",
|
|
||||||
"Leaves pollen behind"
|
|
||||||
},
|
|
||||||
["BehUnloadResources"] = new[]
|
|
||||||
{
|
|
||||||
"Unloads goods in town",
|
|
||||||
"Delivers the haul",
|
|
||||||
"Empties the pack at home"
|
|
||||||
},
|
},
|
||||||
["BehAttackActorHuntingTarget"] = new[]
|
["BehAttackActorHuntingTarget"] = new[]
|
||||||
{
|
{
|
||||||
"Hunts {target}",
|
"{actor} hunts {target}",
|
||||||
"Strikes prey",
|
"{actor} strikes at {target}",
|
||||||
"Closes on the quarry"
|
"{actor} closes on {target}"
|
||||||
|
},
|
||||||
|
["heal"] = new[]
|
||||||
|
{
|
||||||
|
"{actor} tends to {target}",
|
||||||
|
"{actor} heals {target}",
|
||||||
|
"{actor} binds {target}'s wounds"
|
||||||
|
},
|
||||||
|
["check_heal"] = new[]
|
||||||
|
{
|
||||||
|
"{actor} checks on wounds",
|
||||||
|
"{actor} looks over injuries",
|
||||||
|
"{actor} takes stock of pain"
|
||||||
|
},
|
||||||
|
["cure"] = new[]
|
||||||
|
{
|
||||||
|
"{actor} seeks a cure",
|
||||||
|
"{actor} works to recover",
|
||||||
|
"{actor} fights sickness"
|
||||||
|
},
|
||||||
|
["eat"] = new[]
|
||||||
|
{
|
||||||
|
"{actor} takes a meal",
|
||||||
|
"{actor} eats",
|
||||||
|
"{actor} breaks hunger"
|
||||||
|
},
|
||||||
|
["eating"] = new[]
|
||||||
|
{
|
||||||
|
"{actor} takes a meal",
|
||||||
|
"{actor} eats",
|
||||||
|
"{actor} breaks hunger"
|
||||||
|
},
|
||||||
|
["sleep"] = new[]
|
||||||
|
{
|
||||||
|
"{actor} sleeps",
|
||||||
|
"{actor} rests",
|
||||||
|
"{actor} settles in to sleep"
|
||||||
|
},
|
||||||
|
["decide_where_to_sleep"] = new[]
|
||||||
|
{
|
||||||
|
"{actor} looks for a place to sleep",
|
||||||
|
"{actor} decides where to rest",
|
||||||
|
"{actor} seeks shelter for the night"
|
||||||
|
},
|
||||||
|
["random_move"] = new[]
|
||||||
|
{
|
||||||
|
"{actor} wanders",
|
||||||
|
"{actor} roams nearby",
|
||||||
|
"{actor} drifts along"
|
||||||
|
},
|
||||||
|
["city_idle_walking"] = new[]
|
||||||
|
{
|
||||||
|
"{actor} walks the streets",
|
||||||
|
"{actor} strolls through town",
|
||||||
|
"{actor} paces the settlement"
|
||||||
|
},
|
||||||
|
["move"] = new[]
|
||||||
|
{
|
||||||
|
"{actor} is on the move",
|
||||||
|
"{actor} travels onward",
|
||||||
|
"{actor} heads out"
|
||||||
|
},
|
||||||
|
["pollinate"] = new[]
|
||||||
|
{
|
||||||
|
"{actor} dusts the blossom",
|
||||||
|
"{actor} works the flower",
|
||||||
|
"{actor} leaves pollen behind"
|
||||||
|
},
|
||||||
|
["BehPollinate"] = new[]
|
||||||
|
{
|
||||||
|
"{actor} dusts the blossom",
|
||||||
|
"{actor} works the flower",
|
||||||
|
"{actor} leaves pollen behind"
|
||||||
|
},
|
||||||
|
["farm"] = new[]
|
||||||
|
{
|
||||||
|
"{actor} tends the fields",
|
||||||
|
"{actor} works the soil",
|
||||||
|
"{actor} farms the plot"
|
||||||
|
},
|
||||||
|
["harvest"] = new[]
|
||||||
|
{
|
||||||
|
"{actor} harvests the crop",
|
||||||
|
"{actor} gathers the yield",
|
||||||
|
"{actor} cuts the ripe fields"
|
||||||
|
},
|
||||||
|
["forag"] = new[]
|
||||||
|
{
|
||||||
|
"{actor} forages",
|
||||||
|
"{actor} gathers wild food",
|
||||||
|
"{actor} searches for forage"
|
||||||
|
},
|
||||||
|
["gather"] = new[]
|
||||||
|
{
|
||||||
|
"{actor} gathers stores",
|
||||||
|
"{actor} collects goods",
|
||||||
|
"{actor} forages supplies"
|
||||||
|
},
|
||||||
|
["mine"] = new[]
|
||||||
|
{
|
||||||
|
"{actor} delves the mine",
|
||||||
|
"{actor} chips at ore",
|
||||||
|
"{actor} works the dig"
|
||||||
|
},
|
||||||
|
["build"] = new[]
|
||||||
|
{
|
||||||
|
"{actor} builds",
|
||||||
|
"{actor} raises walls",
|
||||||
|
"{actor} works the construction"
|
||||||
|
},
|
||||||
|
["woodcut"] = new[]
|
||||||
|
{
|
||||||
|
"{actor} fells timber",
|
||||||
|
"{actor} cuts wood",
|
||||||
|
"{actor} takes an axe to the trees"
|
||||||
|
},
|
||||||
|
["fire"] = new[]
|
||||||
|
{
|
||||||
|
"{actor} fights the blaze",
|
||||||
|
"{actor} douses flames",
|
||||||
|
"{actor} races the fire"
|
||||||
},
|
},
|
||||||
["BehCityActorRemoveFire"] = new[]
|
["BehCityActorRemoveFire"] = new[]
|
||||||
{
|
{
|
||||||
"Dousing flames",
|
"{actor} fights the blaze",
|
||||||
"Fighting the blaze",
|
"{actor} douses flames",
|
||||||
"Beats back the fire"
|
"{actor} beats back the fire"
|
||||||
|
},
|
||||||
|
["social"] = new[]
|
||||||
|
{
|
||||||
|
"{actor} talks with {target}",
|
||||||
|
"{actor} socializes with {target}",
|
||||||
|
"{actor} converses with {target}"
|
||||||
|
},
|
||||||
|
["socialize"] = new[]
|
||||||
|
{
|
||||||
|
"{actor} talks with {target}",
|
||||||
|
"{actor} socializes with {target}",
|
||||||
|
"{actor} joins company near {target}"
|
||||||
|
},
|
||||||
|
["lover"] = new[]
|
||||||
|
{
|
||||||
|
"{actor} seeks out {target}",
|
||||||
|
"{actor} is drawn to {target}",
|
||||||
|
"{actor} pursues {target}'s affection"
|
||||||
|
},
|
||||||
|
["breed"] = new[]
|
||||||
|
{
|
||||||
|
"{actor} tries to start a family with {target}",
|
||||||
|
"{actor} courts {target}",
|
||||||
|
"{actor} seeks offspring with {target}"
|
||||||
|
},
|
||||||
|
["sexual_reproduction"] = new[]
|
||||||
|
{
|
||||||
|
"{actor} tries to start a family with {target}",
|
||||||
|
"{actor} courts {target}",
|
||||||
|
"{actor} seeks offspring with {target}"
|
||||||
|
},
|
||||||
|
["unload"] = new[]
|
||||||
|
{
|
||||||
|
"{actor} unloads goods in town",
|
||||||
|
"{actor} delivers the haul",
|
||||||
|
"{actor} empties the pack at home"
|
||||||
|
},
|
||||||
|
["BehUnloadResources"] = new[]
|
||||||
|
{
|
||||||
|
"{actor} unloads goods in town",
|
||||||
|
"{actor} delivers the haul",
|
||||||
|
"{actor} empties the pack at home"
|
||||||
|
},
|
||||||
|
["job"] = new[]
|
||||||
|
{
|
||||||
|
"{actor} seeks work",
|
||||||
|
"{actor} looks for a job",
|
||||||
|
"{actor} searches for employment"
|
||||||
|
},
|
||||||
|
["find_city_job"] = new[]
|
||||||
|
{
|
||||||
|
"{actor} seeks work in town",
|
||||||
|
"{actor} looks for a city job",
|
||||||
|
"{actor} searches for employment"
|
||||||
|
},
|
||||||
|
["find_house"] = new[]
|
||||||
|
{
|
||||||
|
"{actor} looks for a house",
|
||||||
|
"{actor} seeks a home",
|
||||||
|
"{actor} searches for shelter"
|
||||||
|
},
|
||||||
|
["claim"] = new[]
|
||||||
|
{
|
||||||
|
"{actor} claims land",
|
||||||
|
"{actor} stakes a claim",
|
||||||
|
"{actor} takes claim of ground"
|
||||||
|
},
|
||||||
|
["warrior"] = new[]
|
||||||
|
{
|
||||||
|
"{actor} follows the army",
|
||||||
|
"{actor} marches with the warband",
|
||||||
|
"{actor} keeps formation"
|
||||||
|
},
|
||||||
|
["army"] = new[]
|
||||||
|
{
|
||||||
|
"{actor} follows the army",
|
||||||
|
"{actor} marches with the warband",
|
||||||
|
"{actor} keeps formation"
|
||||||
|
},
|
||||||
|
["wait"] = new[]
|
||||||
|
{
|
||||||
|
"{actor} waits",
|
||||||
|
"{actor} holds still",
|
||||||
|
"{actor} bides time"
|
||||||
|
},
|
||||||
|
["make_decision"] = new[]
|
||||||
|
{
|
||||||
|
"{actor} considers next steps",
|
||||||
|
"{actor} weighs a choice",
|
||||||
|
"{actor} pauses in thought"
|
||||||
|
},
|
||||||
|
["reflection"] = new[]
|
||||||
|
{
|
||||||
|
"{actor} reflects",
|
||||||
|
"{actor} takes a quiet moment",
|
||||||
|
"{actor} thinks things over"
|
||||||
|
},
|
||||||
|
["run_away"] = new[]
|
||||||
|
{
|
||||||
|
"{actor} flees",
|
||||||
|
"{actor} runs for safety",
|
||||||
|
"{actor} retreats"
|
||||||
|
},
|
||||||
|
["trade"] = new[]
|
||||||
|
{
|
||||||
|
"{actor} trades",
|
||||||
|
"{actor} barters goods",
|
||||||
|
"{actor} is on a trade run"
|
||||||
|
},
|
||||||
|
["fish"] = new[]
|
||||||
|
{
|
||||||
|
"{actor} fishes",
|
||||||
|
"{actor} casts for fish",
|
||||||
|
"{actor} works the waters"
|
||||||
|
},
|
||||||
|
["family_group"] = new[]
|
||||||
|
{
|
||||||
|
"{actor} joins the herd",
|
||||||
|
"{actor} seeks the family group",
|
||||||
|
"{actor} gathers with kin"
|
||||||
|
},
|
||||||
|
["generate_loot"] = new[]
|
||||||
|
{
|
||||||
|
"{actor} gathers loot",
|
||||||
|
"{actor} claims spoils",
|
||||||
|
"{actor} picks through loot"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public static string ColorName(string name)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(name))
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "<color=" + NameColorHex + ">" + name + "</color>";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Format(
|
||||||
|
ActivityKind kind,
|
||||||
|
string key,
|
||||||
|
string actorName,
|
||||||
|
string targetName,
|
||||||
|
bool targetIsActor,
|
||||||
|
string placeOrFallback,
|
||||||
|
string rawFact,
|
||||||
|
long subjectId,
|
||||||
|
int lineIndex,
|
||||||
|
out string plain,
|
||||||
|
out string rich)
|
||||||
|
{
|
||||||
|
plain = "";
|
||||||
|
rich = "";
|
||||||
|
string actor = string.IsNullOrEmpty(actorName) ? "Someone" : actorName;
|
||||||
|
string targetPlain = ResolveTargetPlain(targetName, targetIsActor, placeOrFallback);
|
||||||
|
string template = PickTemplate(key, subjectId, lineIndex, !string.IsNullOrEmpty(targetPlain));
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(template))
|
||||||
|
{
|
||||||
|
plain = BuildFallbackPlain(actor, targetPlain, rawFact, key);
|
||||||
|
rich = BuildFallbackRich(actor, targetPlain, targetIsActor, rawFact, key);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
plain = Apply(template, actor, targetPlain);
|
||||||
|
rich = Apply(
|
||||||
|
template,
|
||||||
|
ColorName(actor),
|
||||||
|
targetIsActor && !string.IsNullOrEmpty(targetName)
|
||||||
|
? ColorName(targetName)
|
||||||
|
: targetPlain);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Legacy helper used by older call sites / harness.</summary>
|
||||||
public static string Format(
|
public static string Format(
|
||||||
ActivityKind kind,
|
ActivityKind kind,
|
||||||
string key,
|
string key,
|
||||||
|
|
@ -201,22 +352,123 @@ public static class ActivityProse
|
||||||
long subjectId,
|
long subjectId,
|
||||||
int lineIndex)
|
int lineIndex)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(rawFact) && string.IsNullOrEmpty(key))
|
Format(
|
||||||
|
kind,
|
||||||
|
key,
|
||||||
|
actorName: "",
|
||||||
|
targetName: target,
|
||||||
|
targetIsActor: !string.IsNullOrEmpty(target),
|
||||||
|
placeOrFallback: target,
|
||||||
|
rawFact: rawFact,
|
||||||
|
subjectId: subjectId,
|
||||||
|
lineIndex: lineIndex,
|
||||||
|
out string plain,
|
||||||
|
out _);
|
||||||
|
return plain;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ResolveTargetPlain(string targetName, bool targetIsActor, string placeOrFallback)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(targetName))
|
||||||
|
{
|
||||||
|
return targetName;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(placeOrFallback))
|
||||||
|
{
|
||||||
|
return placeOrFallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string Apply(string template, string actor, string target)
|
||||||
|
{
|
||||||
|
string t = template.Replace("{actor}", actor ?? "");
|
||||||
|
if (string.IsNullOrEmpty(target))
|
||||||
|
{
|
||||||
|
// Drop dangling " with {target}" style if target missing - templates should
|
||||||
|
// prefer no-target variants via PickTemplate, but scrub leftovers.
|
||||||
|
t = t.Replace(" with {target}", "")
|
||||||
|
.Replace(" on {target}", "")
|
||||||
|
.Replace(" toward {target}", "")
|
||||||
|
.Replace(" at {target}", "")
|
||||||
|
.Replace(" {target}", "")
|
||||||
|
.Replace("{target}", "someone");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
t = t.Replace("{target}", target);
|
||||||
|
}
|
||||||
|
|
||||||
|
return t.Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string BuildFallbackPlain(string actor, string target, string rawFact, string key)
|
||||||
|
{
|
||||||
|
string action = !string.IsNullOrEmpty(rawFact) ? rawFact : HumanizeKey(key);
|
||||||
|
if (string.IsNullOrEmpty(action))
|
||||||
|
{
|
||||||
|
return actor;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(target) && action.IndexOf(target, System.StringComparison.OrdinalIgnoreCase) < 0)
|
||||||
|
{
|
||||||
|
return actor + " " + LowerFirst(action) + " → " + target;
|
||||||
|
}
|
||||||
|
|
||||||
|
return actor + " " + LowerFirst(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string BuildFallbackRich(
|
||||||
|
string actor,
|
||||||
|
string target,
|
||||||
|
bool targetIsActor,
|
||||||
|
string rawFact,
|
||||||
|
string key)
|
||||||
|
{
|
||||||
|
string action = !string.IsNullOrEmpty(rawFact) ? rawFact : HumanizeKey(key);
|
||||||
|
string a = ColorName(actor);
|
||||||
|
if (string.IsNullOrEmpty(action))
|
||||||
|
{
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(target) && action.IndexOf(target, System.StringComparison.OrdinalIgnoreCase) < 0)
|
||||||
|
{
|
||||||
|
string t = targetIsActor ? ColorName(target) : target;
|
||||||
|
return a + " " + LowerFirst(action) + " → " + t;
|
||||||
|
}
|
||||||
|
|
||||||
|
return a + " " + LowerFirst(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string LowerFirst(string s)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(s))
|
||||||
|
{
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s.Length == 1)
|
||||||
|
{
|
||||||
|
return s.ToLowerInvariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
return char.ToLowerInvariant(s[0]) + s.Substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string HumanizeKey(string key)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(key))
|
||||||
{
|
{
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
string template = PickTemplate(key, subjectId, lineIndex);
|
return key.Replace('_', ' ').Replace("Beh", "").Trim();
|
||||||
if (string.IsNullOrEmpty(template))
|
|
||||||
{
|
|
||||||
return rawFact ?? key ?? "";
|
|
||||||
}
|
|
||||||
|
|
||||||
string t = string.IsNullOrEmpty(target) ? "their mark" : target;
|
|
||||||
return template.Replace("{target}", t);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string PickTemplate(string key, long subjectId, int lineIndex)
|
private static string PickTemplate(string key, long subjectId, int lineIndex, bool hasTarget)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(key))
|
if (string.IsNullOrEmpty(key))
|
||||||
{
|
{
|
||||||
|
|
@ -226,21 +478,61 @@ public static class ActivityProse
|
||||||
string[] bag = null;
|
string[] bag = null;
|
||||||
if (Variants.TryGetValue(key, out bag) && bag != null && bag.Length > 0)
|
if (Variants.TryGetValue(key, out bag) && bag != null && bag.Length > 0)
|
||||||
{
|
{
|
||||||
return bag[PickIndex(subjectId, key, lineIndex, bag.Length)];
|
return PreferTargetAware(bag, hasTarget, subjectId, key, lineIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
string lower = key.ToLowerInvariant();
|
string lower = key.ToLowerInvariant();
|
||||||
|
string bestKey = null;
|
||||||
|
string[] bestBag = null;
|
||||||
|
int bestLen = -1;
|
||||||
foreach (KeyValuePair<string, string[]> kv in Variants)
|
foreach (KeyValuePair<string, string[]> kv in Variants)
|
||||||
{
|
{
|
||||||
if (lower.IndexOf(kv.Key, System.StringComparison.Ordinal) >= 0
|
if (kv.Value == null || kv.Value.Length == 0)
|
||||||
&& kv.Value != null
|
|
||||||
&& kv.Value.Length > 0)
|
|
||||||
{
|
{
|
||||||
return kv.Value[PickIndex(subjectId, kv.Key, lineIndex, kv.Value.Length)];
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lower.IndexOf(kv.Key, System.StringComparison.Ordinal) >= 0 && kv.Key.Length > bestLen)
|
||||||
|
{
|
||||||
|
bestLen = kv.Key.Length;
|
||||||
|
bestKey = kv.Key;
|
||||||
|
bestBag = kv.Value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
if (bestBag == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return PreferTargetAware(bestBag, hasTarget, subjectId, bestKey, lineIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string PreferTargetAware(
|
||||||
|
string[] bag,
|
||||||
|
bool hasTarget,
|
||||||
|
long subjectId,
|
||||||
|
string key,
|
||||||
|
int lineIndex)
|
||||||
|
{
|
||||||
|
var suited = new List<string>(bag.Length);
|
||||||
|
for (int i = 0; i < bag.Length; i++)
|
||||||
|
{
|
||||||
|
string line = bag[i];
|
||||||
|
bool needsTarget = line.IndexOf("{target}", System.StringComparison.Ordinal) >= 0;
|
||||||
|
if (hasTarget || !needsTarget)
|
||||||
|
{
|
||||||
|
suited.Add(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (suited.Count == 0)
|
||||||
|
{
|
||||||
|
// Fall back to any template; Apply() will scrub missing targets.
|
||||||
|
suited.AddRange(bag);
|
||||||
|
}
|
||||||
|
|
||||||
|
return suited[PickIndex(subjectId, key, lineIndex, suited.Count)];
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int PickIndex(long subjectId, string key, int lineIndex, int count)
|
private static int PickIndex(long subjectId, string key, int lineIndex, int count)
|
||||||
|
|
@ -253,7 +545,6 @@ public static class ActivityProse
|
||||||
unchecked
|
unchecked
|
||||||
{
|
{
|
||||||
int h = (int)(subjectId * 397) ^ (key?.GetHashCode() ?? 0) ^ (lineIndex * 31);
|
int h = (int)(subjectId * 397) ^ (key?.GetHashCode() ?? 0) ^ (lineIndex * 31);
|
||||||
// Extra jitter so back-to-back repeats of the same task shift.
|
|
||||||
h ^= (int)(UnityEngine.Time.unscaledTime * 10f);
|
h ^= (int)(UnityEngine.Time.unscaledTime * 10f);
|
||||||
if (h < 0)
|
if (h < 0)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -394,12 +394,26 @@ public static class AgentHarness
|
||||||
: (!string.IsNullOrEmpty(cmd.value) ? cmd.value : "farm");
|
: (!string.IsNullOrEmpty(cmd.value) ? cmd.value : "farm");
|
||||||
string line = !string.IsNullOrEmpty(cmd.label) ? cmd.label : key;
|
string line = !string.IsNullOrEmpty(cmd.label) ? cmd.label : key;
|
||||||
bool asBeat = (cmd.tier ?? "").IndexOf("beat", System.StringComparison.OrdinalIgnoreCase) >= 0;
|
bool asBeat = (cmd.tier ?? "").IndexOf("beat", System.StringComparison.OrdinalIgnoreCase) >= 0;
|
||||||
|
string actorName = "";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (MoveCamera.hasFocusUnit() && MoveCamera._focus_unit != null)
|
||||||
|
{
|
||||||
|
actorName = MoveCamera._focus_unit.getName() ?? "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
actorName = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
string targetName = cmd.expect ?? "";
|
||||||
int n = Math.Max(1, cmd.count);
|
int n = Math.Max(1, cmd.count);
|
||||||
int okN = 0;
|
int okN = 0;
|
||||||
for (int i = 0; i < n; i++)
|
for (int i = 0; i < n; i++)
|
||||||
{
|
{
|
||||||
string one = n > 1 ? $"{line} #{i + 1}" : line;
|
string one = n > 1 ? $"{line} #{i + 1}" : line;
|
||||||
if (ActivityLog.ForceNote(id, key, one, asBeat))
|
if (ActivityLog.ForceNote(id, key, one, asBeat, actorName, targetName))
|
||||||
{
|
{
|
||||||
okN++;
|
okN++;
|
||||||
}
|
}
|
||||||
|
|
@ -430,6 +444,46 @@ public static class AgentHarness
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "activity_sample_reset":
|
||||||
|
{
|
||||||
|
ActivityLog.ResetSampleCounters();
|
||||||
|
_cmdOk++;
|
||||||
|
Emit(cmd, ok: true, detail: ActivityLog.FormatSampleStats());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "activity_stats":
|
||||||
|
{
|
||||||
|
string stats = ActivityLog.FormatSampleStats();
|
||||||
|
_cmdOk++;
|
||||||
|
Emit(cmd, ok: true, detail: stats);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "lore_detail_pane":
|
||||||
|
{
|
||||||
|
string raw = (cmd.value ?? cmd.label ?? "life").Trim().ToLowerInvariant();
|
||||||
|
ChronicleHud.DetailPane pane = raw.StartsWith("act")
|
||||||
|
? ChronicleHud.DetailPane.Activity
|
||||||
|
: ChronicleHud.DetailPane.Life;
|
||||||
|
ChronicleHud.SetDetailPane(pane);
|
||||||
|
bool ok = ChronicleHud.DetailUnitId != 0
|
||||||
|
&& ChronicleHud.ActiveDetailPane == pane;
|
||||||
|
if (ok)
|
||||||
|
{
|
||||||
|
_cmdOk++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_cmdFail++;
|
||||||
|
}
|
||||||
|
|
||||||
|
Emit(cmd, ok, detail:
|
||||||
|
$"pane={ChronicleHud.ActiveDetailPane} detailId={ChronicleHud.DetailUnitId} "
|
||||||
|
+ $"actRows={ChronicleHud.LastDetailActivityRows} lifeRows={ChronicleHud.LastDetailHistoryRows}");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case "chronicle_world_force":
|
case "chronicle_world_force":
|
||||||
{
|
{
|
||||||
string label = !string.IsNullOrEmpty(cmd.label)
|
string label = !string.IsNullOrEmpty(cmd.label)
|
||||||
|
|
@ -1803,6 +1857,90 @@ public static class AgentHarness
|
||||||
detail = scoreDetail;
|
detail = scoreDetail;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "activity_names_colored":
|
||||||
|
{
|
||||||
|
long id = WatchCaption.CurrentUnitId;
|
||||||
|
IReadOnlyList<ActivityEntry> entries = ActivityLog.LatestForSubject(id, 8);
|
||||||
|
bool any = false;
|
||||||
|
bool colored = false;
|
||||||
|
for (int i = 0; i < entries.Count; i++)
|
||||||
|
{
|
||||||
|
ActivityEntry e = entries[i];
|
||||||
|
if (e == null || string.IsNullOrEmpty(e.DisplayLineRich))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
any = true;
|
||||||
|
if (e.DisplayLineRich.IndexOf("<color=", System.StringComparison.OrdinalIgnoreCase) >= 0)
|
||||||
|
{
|
||||||
|
colored = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pass = any && colored;
|
||||||
|
detail = $"any={any} colored={colored} preview='{WatchCaption.LastHistoryPreview}'";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "activity_log_contains":
|
||||||
|
{
|
||||||
|
long id = WatchCaption.CurrentUnitId;
|
||||||
|
string needle = string.IsNullOrEmpty(cmd.value) ? cmd.label : cmd.value;
|
||||||
|
IReadOnlyList<ActivityEntry> entries =
|
||||||
|
ActivityLog.LatestForSubject(id, ActivityLog.MaxLinesPerSubject);
|
||||||
|
bool hit = false;
|
||||||
|
string sample = "";
|
||||||
|
for (int i = 0; i < entries.Count; i++)
|
||||||
|
{
|
||||||
|
ActivityEntry e = entries[i];
|
||||||
|
if (e == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
string line = e.DisplayLine ?? e.Line ?? "";
|
||||||
|
if (string.IsNullOrEmpty(sample))
|
||||||
|
{
|
||||||
|
sample = line;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(needle)
|
||||||
|
&& line.IndexOf(needle, System.StringComparison.OrdinalIgnoreCase) >= 0)
|
||||||
|
{
|
||||||
|
hit = true;
|
||||||
|
sample = line;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pass = hit;
|
||||||
|
detail = $"hit={hit} needle='{needle}' sample='{sample}' count={entries.Count}";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "lore_detail_pane":
|
||||||
|
{
|
||||||
|
string want = (cmd.value ?? "life").Trim().ToLowerInvariant();
|
||||||
|
ChronicleHud.DetailPane have = ChronicleHud.ActiveDetailPane;
|
||||||
|
bool match = want.StartsWith("act")
|
||||||
|
? have == ChronicleHud.DetailPane.Activity
|
||||||
|
: have == ChronicleHud.DetailPane.Life;
|
||||||
|
pass = Chronicle.HudVisible && ChronicleHud.DetailUnitId != 0 && match;
|
||||||
|
detail =
|
||||||
|
$"pane={have} want={want} actRows={ChronicleHud.LastDetailActivityRows} lifeRows={ChronicleHud.LastDetailHistoryRows}";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "lore_activity_shown":
|
||||||
|
{
|
||||||
|
int want = ParseCountExpect(cmd, defaultValue: 1);
|
||||||
|
int have = ChronicleHud.LastDetailActivityRows;
|
||||||
|
string cmp = (cmd.label ?? "min").Trim().ToLowerInvariant();
|
||||||
|
pass = Chronicle.HudVisible
|
||||||
|
&& ChronicleHud.ActiveDetailPane == ChronicleHud.DetailPane.Activity
|
||||||
|
&& (cmp == "exact" || cmp == "==" ? have == want : have >= want);
|
||||||
|
detail = $"actRows={have} want={want} pane={ChronicleHud.ActiveDetailPane}";
|
||||||
|
break;
|
||||||
|
}
|
||||||
case "activity_band_cold_focus":
|
case "activity_band_cold_focus":
|
||||||
{
|
{
|
||||||
Actor focus = MoveCamera.hasFocusUnit() ? MoveCamera._focus_unit : null;
|
Actor focus = MoveCamera.hasFocusUnit() ? MoveCamera._focus_unit : null;
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,12 @@ public static class ChronicleHud
|
||||||
Fallen = 2
|
Fallen = 2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum DetailPane
|
||||||
|
{
|
||||||
|
Life = 0,
|
||||||
|
Activity = 1
|
||||||
|
}
|
||||||
|
|
||||||
private const float PanelWidth = 268f;
|
private const float PanelWidth = 268f;
|
||||||
private const float PanelHeight = 188f;
|
private const float PanelHeight = 188f;
|
||||||
private const float ScreenInset = 12f;
|
private const float ScreenInset = 12f;
|
||||||
|
|
@ -33,7 +39,7 @@ public static class ChronicleHud
|
||||||
private const float TabH = 15f;
|
private const float TabH = 15f;
|
||||||
private const float CharToolsH = 17f;
|
private const float CharToolsH = 17f;
|
||||||
private const float ScrollSensitivity = 55f;
|
private const float ScrollSensitivity = 55f;
|
||||||
private const string ChromeMarker = "LoreV5";
|
private const string ChromeMarker = "LoreV6";
|
||||||
|
|
||||||
private static GameObject _root;
|
private static GameObject _root;
|
||||||
private static RectTransform _rootRt;
|
private static RectTransform _rootRt;
|
||||||
|
|
@ -68,10 +74,20 @@ public static class ChronicleHud
|
||||||
private static bool _followFocus;
|
private static bool _followFocus;
|
||||||
private static GameObject _backBtn;
|
private static GameObject _backBtn;
|
||||||
private static Text _backBtnLabel;
|
private static Text _backBtnLabel;
|
||||||
|
private static GameObject _detailPaneBar;
|
||||||
|
private static GameObject _detailActivityBtn;
|
||||||
|
private static GameObject _detailLifeBtn;
|
||||||
|
private static DetailPane _detailPane = DetailPane.Life;
|
||||||
|
|
||||||
/// <summary>Rows actually built in the last character detail rebuild (harness).</summary>
|
/// <summary>Rows actually built in the last character detail rebuild (harness).</summary>
|
||||||
public static int LastDetailHistoryRows { get; private set; }
|
public static int LastDetailHistoryRows { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>Activity rows in the last Activity-pane rebuild (harness).</summary>
|
||||||
|
public static int LastDetailActivityRows { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>Active detail sub-pane when a unit history is open.</summary>
|
||||||
|
public static DetailPane ActiveDetailPane => _detailPane;
|
||||||
|
|
||||||
/// <summary>Title of the first row in the last Living/Fallen list rebuild (harness).</summary>
|
/// <summary>Title of the first row in the last Living/Fallen list rebuild (harness).</summary>
|
||||||
public static string LastListTopTitle { get; private set; } = "";
|
public static string LastListTopTitle { get; private set; } = "";
|
||||||
|
|
||||||
|
|
@ -128,8 +144,12 @@ public static class ChronicleHud
|
||||||
_searchField = null;
|
_searchField = null;
|
||||||
_backBtn = null;
|
_backBtn = null;
|
||||||
_backBtnLabel = null;
|
_backBtnLabel = null;
|
||||||
|
_detailPaneBar = null;
|
||||||
|
_detailActivityBtn = null;
|
||||||
|
_detailLifeBtn = null;
|
||||||
_detailUnitId = 0;
|
_detailUnitId = 0;
|
||||||
_followFocus = false;
|
_followFocus = false;
|
||||||
|
_detailPane = DetailPane.Life;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_root != null)
|
if (_root != null)
|
||||||
|
|
@ -337,16 +357,46 @@ public static class ChronicleHud
|
||||||
_backBtnLabel = _backBtn.transform.Find("Label")?.GetComponent<Text>();
|
_backBtnLabel = _backBtn.transform.Find("Label")?.GetComponent<Text>();
|
||||||
RectTransform backRt = _backBtn.GetComponent<RectTransform>();
|
RectTransform backRt = _backBtn.GetComponent<RectTransform>();
|
||||||
backRt.anchorMin = new Vector2(0f, 0f);
|
backRt.anchorMin = new Vector2(0f, 0f);
|
||||||
backRt.anchorMax = new Vector2(0.42f, 1f);
|
backRt.anchorMax = new Vector2(0.36f, 1f);
|
||||||
backRt.offsetMin = Vector2.zero;
|
backRt.offsetMin = Vector2.zero;
|
||||||
backRt.offsetMax = Vector2.zero;
|
backRt.offsetMax = Vector2.zero;
|
||||||
_backBtn.SetActive(false);
|
_backBtn.SetActive(false);
|
||||||
|
|
||||||
|
_detailPaneBar = new GameObject("DetailPanes", typeof(RectTransform));
|
||||||
|
_detailPaneBar.transform.SetParent(_charTools.transform, false);
|
||||||
|
RectTransform paneRt = _detailPaneBar.GetComponent<RectTransform>();
|
||||||
|
paneRt.anchorMin = new Vector2(0.38f, 0f);
|
||||||
|
paneRt.anchorMax = new Vector2(1f, 1f);
|
||||||
|
paneRt.offsetMin = Vector2.zero;
|
||||||
|
paneRt.offsetMax = Vector2.zero;
|
||||||
|
_detailActivityBtn = BuildTabButton(
|
||||||
|
_detailPaneBar.transform, "ActivityPane", "Activity", () => SetDetailPane(DetailPane.Activity));
|
||||||
|
_detailLifeBtn = BuildTabButton(
|
||||||
|
_detailPaneBar.transform, "LifePane", "Life", () => SetDetailPane(DetailPane.Life));
|
||||||
|
PlaceDetailPane(_detailActivityBtn, 0f);
|
||||||
|
PlaceDetailPane(_detailLifeBtn, 1f);
|
||||||
|
_detailPaneBar.SetActive(false);
|
||||||
|
|
||||||
_charTools.SetActive(false);
|
_charTools.SetActive(false);
|
||||||
RefreshFavFilterVisual();
|
RefreshFavFilterVisual();
|
||||||
RefreshStaleVisual();
|
RefreshStaleVisual();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void PlaceDetailPane(GameObject tab, float index)
|
||||||
|
{
|
||||||
|
if (tab == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RectTransform rt = tab.GetComponent<RectTransform>();
|
||||||
|
float w = 0.5f;
|
||||||
|
rt.anchorMin = new Vector2(index * w, 0f);
|
||||||
|
rt.anchorMax = new Vector2(index * w + w, 1f);
|
||||||
|
rt.offsetMin = new Vector2(1f, 1f);
|
||||||
|
rt.offsetMax = new Vector2(-1f, -1f);
|
||||||
|
}
|
||||||
|
|
||||||
private static Button BuildTextTool(Transform parent, string name, string label, UnityAction onClick)
|
private static Button BuildTextTool(Transform parent, string name, string label, UnityAction onClick)
|
||||||
{
|
{
|
||||||
GameObject go = new GameObject(name, typeof(RectTransform), typeof(Image), typeof(Button));
|
GameObject go = new GameObject(name, typeof(RectTransform), typeof(Image), typeof(Button));
|
||||||
|
|
@ -671,6 +721,7 @@ public static class ChronicleHud
|
||||||
EnsureBuilt();
|
EnsureBuilt();
|
||||||
_detailUnitId = unitId;
|
_detailUnitId = unitId;
|
||||||
_followFocus = followFocus;
|
_followFocus = followFocus;
|
||||||
|
_detailPane = DetailPane.Life;
|
||||||
|
|
||||||
if (pauseIdle && SpectatorMode.Active)
|
if (pauseIdle && SpectatorMode.Active)
|
||||||
{
|
{
|
||||||
|
|
@ -705,6 +756,7 @@ public static class ChronicleHud
|
||||||
{
|
{
|
||||||
_detailUnitId = 0;
|
_detailUnitId = 0;
|
||||||
_followFocus = false;
|
_followFocus = false;
|
||||||
|
_detailPane = DetailPane.Life;
|
||||||
_lastCharFingerprint = int.MinValue;
|
_lastCharFingerprint = int.MinValue;
|
||||||
ApplyTabChrome();
|
ApplyTabChrome();
|
||||||
if (Visible && IsCastTab)
|
if (Visible && IsCastTab)
|
||||||
|
|
@ -713,6 +765,24 @@ public static class ChronicleHud
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void SetDetailPane(DetailPane pane)
|
||||||
|
{
|
||||||
|
if (_detailUnitId == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_detailPane == pane)
|
||||||
|
{
|
||||||
|
StyleDetailPanes();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_detailPane = pane;
|
||||||
|
StyleDetailPanes();
|
||||||
|
Rebuild(force: true);
|
||||||
|
}
|
||||||
|
|
||||||
public static void SetFavoritesOnly(bool on)
|
public static void SetFavoritesOnly(bool on)
|
||||||
{
|
{
|
||||||
if (_favoritesOnly == on)
|
if (_favoritesOnly == on)
|
||||||
|
|
@ -871,6 +941,12 @@ public static class ChronicleHud
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_detailPaneBar != null)
|
||||||
|
{
|
||||||
|
_detailPaneBar.SetActive(detail);
|
||||||
|
StyleDetailPanes();
|
||||||
|
}
|
||||||
|
|
||||||
StyleTab(_worldTabBtn, _tab == LoreTab.World);
|
StyleTab(_worldTabBtn, _tab == LoreTab.World);
|
||||||
StyleTab(_livingTabBtn, _tab == LoreTab.Living);
|
StyleTab(_livingTabBtn, _tab == LoreTab.Living);
|
||||||
StyleTab(_fallenTabBtn, _tab == LoreTab.Fallen);
|
StyleTab(_fallenTabBtn, _tab == LoreTab.Fallen);
|
||||||
|
|
@ -879,6 +955,12 @@ public static class ChronicleHud
|
||||||
RefreshTitle();
|
RefreshTitle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void StyleDetailPanes()
|
||||||
|
{
|
||||||
|
StyleTab(_detailActivityBtn, _detailPane == DetailPane.Activity);
|
||||||
|
StyleTab(_detailLifeBtn, _detailPane == DetailPane.Life);
|
||||||
|
}
|
||||||
|
|
||||||
private static void RefreshTitle()
|
private static void RefreshTitle()
|
||||||
{
|
{
|
||||||
if (_titleText == null)
|
if (_titleText == null)
|
||||||
|
|
@ -1086,6 +1168,8 @@ public static class ChronicleHud
|
||||||
if (_detailUnitId != 0)
|
if (_detailUnitId != 0)
|
||||||
{
|
{
|
||||||
hash = (hash * 31) + Chronicle.HistoryCountFor(_detailUnitId);
|
hash = (hash * 31) + Chronicle.HistoryCountFor(_detailUnitId);
|
||||||
|
hash = (hash * 31) + ActivityLog.CountFor(_detailUnitId);
|
||||||
|
hash = (hash * 31) + (int)_detailPane;
|
||||||
}
|
}
|
||||||
|
|
||||||
return hash;
|
return hash;
|
||||||
|
|
@ -1155,7 +1239,15 @@ public static class ChronicleHud
|
||||||
LastListTopTitle = "";
|
LastListTopTitle = "";
|
||||||
if (_detailUnitId != 0)
|
if (_detailUnitId != 0)
|
||||||
{
|
{
|
||||||
RebuildUnitHistoryDetail(_detailUnitId);
|
if (_detailPane == DetailPane.Activity)
|
||||||
|
{
|
||||||
|
RebuildActivityDetail(_detailUnitId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RebuildUnitHistoryDetail(_detailUnitId);
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1211,6 +1303,103 @@ public static class ChronicleHud
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void RebuildActivityDetail(long unitId)
|
||||||
|
{
|
||||||
|
IReadOnlyList<ActivityEntry> entries =
|
||||||
|
ActivityLog.LatestForSubject(unitId, ActivityLog.MaxLinesPerSubject);
|
||||||
|
_lastCharFingerprint = CharacterFingerprint();
|
||||||
|
_builtAtRevision = Chronicle.HistoryRevision;
|
||||||
|
RefreshStaleVisual();
|
||||||
|
LastDetailHistoryRows = 0;
|
||||||
|
if (entries == null || entries.Count == 0)
|
||||||
|
{
|
||||||
|
LastDetailActivityRows = 0;
|
||||||
|
AddEmpty("No activity yet");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AddSection(entries.Count + " actions");
|
||||||
|
int shown = 0;
|
||||||
|
for (int i = 0; i < entries.Count; i++)
|
||||||
|
{
|
||||||
|
ActivityEntry entry = entries[i];
|
||||||
|
if (entry == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
GameObject row = BuildActivityDetailRow(entry, shown);
|
||||||
|
row.transform.SetParent(_content, false);
|
||||||
|
Rows.Add(row);
|
||||||
|
shown++;
|
||||||
|
}
|
||||||
|
|
||||||
|
LastDetailActivityRows = shown;
|
||||||
|
if (_scroll != null)
|
||||||
|
{
|
||||||
|
_scroll.verticalNormalizedPosition = 1f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GameObject BuildActivityDetailRow(ActivityEntry entry, int index)
|
||||||
|
{
|
||||||
|
GameObject go = new GameObject(
|
||||||
|
$"ActDetail_{index}",
|
||||||
|
typeof(RectTransform),
|
||||||
|
typeof(Image),
|
||||||
|
typeof(LayoutElement),
|
||||||
|
typeof(ContentSizeFitter));
|
||||||
|
RectTransform rt = go.GetComponent<RectTransform>();
|
||||||
|
rt.sizeDelta = new Vector2(PanelWidth - 14f, RowHeightMin);
|
||||||
|
LayoutElement le = go.GetComponent<LayoutElement>();
|
||||||
|
le.minHeight = RowHeightMin;
|
||||||
|
le.preferredHeight = -1f;
|
||||||
|
ContentSizeFitter fit = go.GetComponent<ContentSizeFitter>();
|
||||||
|
fit.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
|
||||||
|
fit.horizontalFit = ContentSizeFitter.FitMode.Unconstrained;
|
||||||
|
|
||||||
|
Image bg = go.GetComponent<Image>();
|
||||||
|
bg.color = index % 2 == 0
|
||||||
|
? new Color(1f, 1f, 1f, 0.04f)
|
||||||
|
: new Color(1f, 1f, 1f, 0.015f);
|
||||||
|
bg.raycastTarget = false;
|
||||||
|
|
||||||
|
Image kindIcon = HudCanvas.MakeIcon(go.transform, "Kind", KindIconSize);
|
||||||
|
RectTransform kindRt = kindIcon.GetComponent<RectTransform>();
|
||||||
|
kindRt.anchorMin = new Vector2(0f, 1f);
|
||||||
|
kindRt.anchorMax = new Vector2(0f, 1f);
|
||||||
|
kindRt.pivot = new Vector2(0.5f, 1f);
|
||||||
|
kindRt.anchoredPosition = new Vector2(9f, -5f);
|
||||||
|
kindIcon.raycastTarget = false;
|
||||||
|
HudIcons.Apply(
|
||||||
|
kindIcon,
|
||||||
|
HudIcons.FromUiIcon("iconClock")
|
||||||
|
?? HudIcons.FromUiIcon("iconTask")
|
||||||
|
?? HudIcons.ForChronicleKind(ChronicleKind.Other));
|
||||||
|
|
||||||
|
string text = !string.IsNullOrEmpty(entry.DisplayLineRich)
|
||||||
|
? entry.DisplayLineRich
|
||||||
|
: (!string.IsNullOrEmpty(entry.DisplayLine) ? entry.DisplayLine : entry.Line);
|
||||||
|
Text label = HudCanvas.MakeText(go.transform, "Text", text ?? "", 8);
|
||||||
|
RectTransform labelRt = label.GetComponent<RectTransform>();
|
||||||
|
labelRt.anchorMin = new Vector2(0f, 0f);
|
||||||
|
labelRt.anchorMax = new Vector2(1f, 1f);
|
||||||
|
labelRt.offsetMin = new Vector2(18f, 2f);
|
||||||
|
labelRt.offsetMax = new Vector2(-4f, -2f);
|
||||||
|
label.alignment = TextAnchor.UpperLeft;
|
||||||
|
label.color = new Color(0.88f, 0.9f, 0.78f, 1f);
|
||||||
|
label.supportRichText = true;
|
||||||
|
label.resizeTextForBestFit = false;
|
||||||
|
label.horizontalOverflow = HorizontalWrapMode.Wrap;
|
||||||
|
label.verticalOverflow = VerticalWrapMode.Overflow;
|
||||||
|
label.raycastTarget = false;
|
||||||
|
|
||||||
|
ContentSizeFitter textFit = label.gameObject.AddComponent<ContentSizeFitter>();
|
||||||
|
textFit.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
|
||||||
|
textFit.horizontalFit = ContentSizeFitter.FitMode.Unconstrained;
|
||||||
|
return go;
|
||||||
|
}
|
||||||
|
|
||||||
private static void RebuildUnitHistoryDetail(long unitId)
|
private static void RebuildUnitHistoryDetail(long unitId)
|
||||||
{
|
{
|
||||||
IReadOnlyList<ChronicleEntry> entries = Chronicle.LatestForSubject(unitId, Chronicle.MaxHistoryPerUnit);
|
IReadOnlyList<ChronicleEntry> entries = Chronicle.LatestForSubject(unitId, Chronicle.MaxHistoryPerUnit);
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,9 @@ internal static class HarnessScenarios
|
||||||
case "activity":
|
case "activity":
|
||||||
case "activity_idle_smoke":
|
case "activity_idle_smoke":
|
||||||
return ActivityIdleSmoke();
|
return ActivityIdleSmoke();
|
||||||
|
case "activity_rate":
|
||||||
|
case "activity_rate_sample":
|
||||||
|
return ActivityRateSample();
|
||||||
case "chronicle_subject_bench":
|
case "chronicle_subject_bench":
|
||||||
case "subject_bench":
|
case "subject_bench":
|
||||||
return ChronicleSubjectBench();
|
return ChronicleSubjectBench();
|
||||||
|
|
@ -419,6 +422,18 @@ internal static class HarnessScenarios
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static List<HarnessCommand> ActivityRateSample()
|
||||||
|
{
|
||||||
|
return new List<HarnessCommand>
|
||||||
|
{
|
||||||
|
Step("ar0", "wait_world"),
|
||||||
|
Step("ar1", "activity_sample_reset"),
|
||||||
|
Step("ar2", "wait", wait: 20f),
|
||||||
|
Step("ar3", "activity_stats"),
|
||||||
|
Step("ar4", "snapshot"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private static List<HarnessCommand> ActivityIdleSmoke()
|
private static List<HarnessCommand> ActivityIdleSmoke()
|
||||||
{
|
{
|
||||||
return new List<HarnessCommand>
|
return new List<HarnessCommand>
|
||||||
|
|
@ -439,12 +454,14 @@ internal static class HarnessScenarios
|
||||||
Step("act13", "assert", expect: "activity_first_scoring"),
|
Step("act13", "assert", expect: "activity_first_scoring"),
|
||||||
Step("act14", "activity_force", asset: "eating", label: "Taking a meal", count: 1),
|
Step("act14", "activity_force", asset: "eating", label: "Taking a meal", count: 1),
|
||||||
Step("act15", "activity_force", asset: "farm", label: "Tends the fields", count: 1),
|
Step("act15", "activity_force", asset: "farm", label: "Tends the fields", count: 1),
|
||||||
Step("act16", "activity_force", asset: "pollinate", label: "Dusts the blossom", count: 1),
|
Step("act16", "activity_force", asset: "hunt", label: "Hunts", count: 1, expect: "Barkley"),
|
||||||
Step("act17", "activity_force", asset: "BehPollinate", label: "Works the flower", count: 4, tier: "beat"),
|
Step("act17", "activity_force", asset: "BehPollinate", label: "Works the flower", count: 3, tier: "beat"),
|
||||||
Step("act17b", "activity_force", asset: "zzz_harness_act", label: "Harness pollen trail", count: 1),
|
Step("act17b", "activity_force", asset: "zzz_harness_act", label: "Harness pollen trail", count: 1),
|
||||||
Step("act18", "wait", wait: 0.25f),
|
Step("act18", "wait", wait: 0.25f),
|
||||||
Step("act19", "assert", expect: "activity_count", value: "4", label: "min"),
|
Step("act19", "assert", expect: "activity_count", value: "4", label: "min"),
|
||||||
Step("act20", "assert", expect: "activity_prose_variety", value: "2"),
|
Step("act20", "assert", expect: "activity_prose_variety", value: "2"),
|
||||||
|
Step("act20b", "assert", expect: "activity_names_colored"),
|
||||||
|
Step("act20c", "assert", expect: "activity_log_contains", value: "Barkley"),
|
||||||
Step("act21", "assert", expect: "dossier_history_shown", value: "1", label: "min", tier: "activity"),
|
Step("act21", "assert", expect: "dossier_history_shown", value: "1", label: "min", tier: "activity"),
|
||||||
Step("act22", "assert", expect: "dossier_history_contains", value: "Harness pollen"),
|
Step("act22", "assert", expect: "dossier_history_contains", value: "Harness pollen"),
|
||||||
Step("act23", "assert", expect: "caption_layout_ok"),
|
Step("act23", "assert", expect: "caption_layout_ok"),
|
||||||
|
|
@ -455,9 +472,23 @@ internal static class HarnessScenarios
|
||||||
Step("act27", "wait", wait: 0.2f),
|
Step("act27", "wait", wait: 0.2f),
|
||||||
Step("act28", "assert", expect: "dossier_history_contains", value: "serpent"),
|
Step("act28", "assert", expect: "dossier_history_contains", value: "serpent"),
|
||||||
Step("act29", "assert", expect: "dossier_history_shown", value: "1", label: "min", tier: "life"),
|
Step("act29", "assert", expect: "dossier_history_shown", value: "1", label: "min", tier: "life"),
|
||||||
Step("act30", "assert", expect: "caption_layout_ok"),
|
Step("act29b", "screenshot", value: "hud-activity-life-split.png"),
|
||||||
Step("act31", "assert", expect: "health"),
|
Step("act29c", "wait", wait: 0.45f),
|
||||||
Step("act32", "assert", expect: "no_bad"),
|
// Books: Activity / Life tabs.
|
||||||
|
Step("act40", "lore_open_focus"),
|
||||||
|
Step("act41", "wait", wait: 0.25f),
|
||||||
|
Step("act42", "assert", expect: "lore_detail_pane", value: "life"),
|
||||||
|
Step("act43", "lore_detail_pane", value: "activity"),
|
||||||
|
Step("act44", "wait", wait: 0.2f),
|
||||||
|
Step("act45", "assert", expect: "lore_detail_pane", value: "activity"),
|
||||||
|
Step("act46", "assert", expect: "lore_activity_shown", value: "4", label: "min"),
|
||||||
|
Step("act47", "screenshot", value: "hud-lore-activity-tab.png"),
|
||||||
|
Step("act48", "wait", wait: 0.45f),
|
||||||
|
Step("act49", "lore_detail_pane", value: "life"),
|
||||||
|
Step("act50", "assert", expect: "lore_history_shown", value: "1", label: "min"),
|
||||||
|
Step("act51", "assert", expect: "idle", value: "false"),
|
||||||
|
Step("act52", "assert", expect: "caption_layout_ok"),
|
||||||
|
Step("act53", "assert", expect: "no_bad"),
|
||||||
Step("act99", "snapshot"),
|
Step("act99", "snapshot"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -64,11 +64,15 @@ public static class WatchCaption
|
||||||
private static GameObject _traitsRow;
|
private static GameObject _traitsRow;
|
||||||
|
|
||||||
private static GameObject _historyCol;
|
private static GameObject _historyCol;
|
||||||
|
private static Image _activityBoxBg;
|
||||||
|
private static RectTransform _lifeSep;
|
||||||
private static readonly HistorySlot[] _historySlots = new HistorySlot[HistoryPeekMax];
|
private static readonly HistorySlot[] _historySlots = new HistorySlot[HistoryPeekMax];
|
||||||
private static readonly float[] _historySlotHeights = new float[HistoryPeekMax];
|
private static readonly float[] _historySlotHeights = new float[HistoryPeekMax];
|
||||||
|
private static readonly bool[] _historySlotIsActivity = new bool[HistoryPeekMax];
|
||||||
private static float _historyColW = HistoryColMinW;
|
private static float _historyColW = HistoryColMinW;
|
||||||
private static int _lastHistoryCount = -1;
|
private static int _lastHistoryCount = -1;
|
||||||
private static long _lastHistorySubjectId;
|
private static long _lastHistorySubjectId;
|
||||||
|
private const float LifeSepH = 3f;
|
||||||
|
|
||||||
private static Button _favoriteBtn;
|
private static Button _favoriteBtn;
|
||||||
private static Image _favoriteIcon;
|
private static Image _favoriteIcon;
|
||||||
|
|
@ -773,6 +777,7 @@ public static class WatchCaption
|
||||||
if (hasLive)
|
if (hasLive)
|
||||||
{
|
{
|
||||||
DossierAvatar.Show(actor);
|
DossierAvatar.Show(actor);
|
||||||
|
ActivityLog.EnsureCurrentTask(actor);
|
||||||
}
|
}
|
||||||
else if (hasBody)
|
else if (hasBody)
|
||||||
{
|
{
|
||||||
|
|
@ -983,7 +988,7 @@ public static class WatchCaption
|
||||||
|
|
||||||
_historyCol.SetActive(true);
|
_historyCol.SetActive(true);
|
||||||
|
|
||||||
var lines = new List<(string text, bool isActivity, ChronicleKind? kind)>(HistoryPeekMax);
|
var lines = new List<(string rich, string plain, bool isActivity, ChronicleKind? kind)>(HistoryPeekMax);
|
||||||
if (activity != null)
|
if (activity != null)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < activity.Count && lines.Count < HistoryPeekMax; i++)
|
for (int i = 0; i < activity.Count && lines.Count < HistoryPeekMax; i++)
|
||||||
|
|
@ -994,13 +999,14 @@ public static class WatchCaption
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
string t = !string.IsNullOrEmpty(e.DisplayLine) ? e.DisplayLine : e.Line;
|
string plain = !string.IsNullOrEmpty(e.DisplayLine) ? e.DisplayLine : e.Line;
|
||||||
if (string.IsNullOrEmpty(t))
|
if (string.IsNullOrEmpty(plain))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
lines.Add((t, true, null));
|
string rich = !string.IsNullOrEmpty(e.DisplayLineRich) ? e.DisplayLineRich : plain;
|
||||||
|
lines.Add((rich, plain, true, null));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1014,13 +1020,14 @@ public static class WatchCaption
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
string t = e.DisplayLineRich ?? e.DisplayLine ?? "";
|
string plain = e.DisplayLine ?? e.HudLine ?? "";
|
||||||
if (string.IsNullOrEmpty(t))
|
string rich = e.DisplayLineRich ?? plain;
|
||||||
|
if (string.IsNullOrEmpty(plain) && string.IsNullOrEmpty(rich))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
lines.Add((t, false, e.Kind));
|
lines.Add((rich, plain, false, e.Kind));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1039,7 +1046,7 @@ public static class WatchCaption
|
||||||
label.horizontalOverflow = HorizontalWrapMode.Overflow;
|
label.horizontalOverflow = HorizontalWrapMode.Overflow;
|
||||||
label.verticalOverflow = VerticalWrapMode.Overflow;
|
label.verticalOverflow = VerticalWrapMode.Overflow;
|
||||||
label.alignment = TextAnchor.UpperLeft;
|
label.alignment = TextAnchor.UpperLeft;
|
||||||
label.text = lines[i].text;
|
label.text = lines[i].plain;
|
||||||
Canvas.ForceUpdateCanvases();
|
Canvas.ForceUpdateCanvases();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -1047,7 +1054,7 @@ public static class WatchCaption
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
widestLabel = Mathf.Max(widestLabel, lines[i].text.Length * 6.2f);
|
widestLabel = Mathf.Max(widestLabel, lines[i].plain.Length * 6.2f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1061,7 +1068,7 @@ public static class WatchCaption
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ApplyCombinedSlotContents(
|
private static void ApplyCombinedSlotContents(
|
||||||
List<(string text, bool isActivity, ChronicleKind? kind)> lines,
|
List<(string rich, string plain, bool isActivity, ChronicleKind? kind)> lines,
|
||||||
float colW)
|
float colW)
|
||||||
{
|
{
|
||||||
float labelW = Mathf.Max(24f, colW - HistoryIcon - 2f);
|
float labelW = Mathf.Max(24f, colW - HistoryIcon - 2f);
|
||||||
|
|
@ -1083,10 +1090,12 @@ public static class WatchCaption
|
||||||
{
|
{
|
||||||
slot.Root.SetActive(false);
|
slot.Root.SetActive(false);
|
||||||
_historySlotHeights[i] = 0f;
|
_historySlotHeights[i] = 0f;
|
||||||
|
_historySlotIsActivity[i] = false;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var row = lines[i];
|
var row = lines[i];
|
||||||
|
_historySlotIsActivity[i] = row.isActivity;
|
||||||
slot.Root.SetActive(true);
|
slot.Root.SetActive(true);
|
||||||
Sprite icon = row.isActivity
|
Sprite icon = row.isActivity
|
||||||
? (HudIcons.FromUiIcon("iconClock")
|
? (HudIcons.FromUiIcon("iconClock")
|
||||||
|
|
@ -1103,7 +1112,7 @@ public static class WatchCaption
|
||||||
slot.Label.color = row.isActivity
|
slot.Label.color = row.isActivity
|
||||||
? new Color(0.88f, 0.9f, 0.78f, 1f)
|
? new Color(0.88f, 0.9f, 0.78f, 1f)
|
||||||
: HistoryTextColor;
|
: HistoryTextColor;
|
||||||
slot.Label.text = row.text ?? "";
|
slot.Label.text = row.rich ?? row.plain ?? "";
|
||||||
RectTransform lrt = slot.Label.GetComponent<RectTransform>();
|
RectTransform lrt = slot.Label.GetComponent<RectTransform>();
|
||||||
lrt.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, labelW);
|
lrt.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, labelW);
|
||||||
Canvas.ForceUpdateCanvases();
|
Canvas.ForceUpdateCanvases();
|
||||||
|
|
@ -1114,7 +1123,7 @@ public static class WatchCaption
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
int chars = (row.text ?? "").Length;
|
int chars = (row.plain ?? "").Length;
|
||||||
int perLine = Mathf.Max(12, (int)(labelW / 6.2f));
|
int perLine = Mathf.Max(12, (int)(labelW / 6.2f));
|
||||||
int wrapLines = Mathf.Max(1, (chars + perLine - 1) / perLine);
|
int wrapLines = Mathf.Max(1, (chars + perLine - 1) / perLine);
|
||||||
needed = HistoryLineMinH * wrapLines;
|
needed = HistoryLineMinH * wrapLines;
|
||||||
|
|
@ -1136,7 +1145,7 @@ public static class WatchCaption
|
||||||
LastLifeShown++;
|
LastLifeShown++;
|
||||||
}
|
}
|
||||||
|
|
||||||
string plain = (row.text ?? "").Replace("\n", " ");
|
string plain = (row.plain ?? "").Replace("\n", " ");
|
||||||
joined.Add(plain);
|
joined.Add(plain);
|
||||||
if (string.IsNullOrEmpty(LastHistoryPreview))
|
if (string.IsNullOrEmpty(LastHistoryPreview))
|
||||||
{
|
{
|
||||||
|
|
@ -1185,6 +1194,8 @@ public static class WatchCaption
|
||||||
|
|
||||||
float total = 0f;
|
float total = 0f;
|
||||||
int used = 0;
|
int used = 0;
|
||||||
|
bool hasAct = false;
|
||||||
|
bool hasLife = false;
|
||||||
for (int i = 0; i < _historySlotHeights.Length && used < historyCount; i++)
|
for (int i = 0; i < _historySlotHeights.Length && used < historyCount; i++)
|
||||||
{
|
{
|
||||||
if (_historySlotHeights[i] <= 0.01f)
|
if (_historySlotHeights[i] <= 0.01f)
|
||||||
|
|
@ -1193,9 +1204,23 @@ public static class WatchCaption
|
||||||
}
|
}
|
||||||
|
|
||||||
total += _historySlotHeights[i];
|
total += _historySlotHeights[i];
|
||||||
|
if (_historySlotIsActivity[i])
|
||||||
|
{
|
||||||
|
hasAct = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
hasLife = true;
|
||||||
|
}
|
||||||
|
|
||||||
used++;
|
used++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hasAct && hasLife)
|
||||||
|
{
|
||||||
|
total += LifeSepH;
|
||||||
|
}
|
||||||
|
|
||||||
return Mathf.Max(BodyH, total);
|
return Mathf.Max(BodyH, total);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1521,6 +1546,16 @@ public static class WatchCaption
|
||||||
if (historyCount <= 0)
|
if (historyCount <= 0)
|
||||||
{
|
{
|
||||||
_historyCol.SetActive(false);
|
_historyCol.SetActive(false);
|
||||||
|
if (_activityBoxBg != null)
|
||||||
|
{
|
||||||
|
_activityBoxBg.gameObject.SetActive(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_lifeSep != null)
|
||||||
|
{
|
||||||
|
_lifeSep.gameObject.SetActive(false);
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1533,6 +1568,11 @@ public static class WatchCaption
|
||||||
col.sizeDelta = new Vector2(_historyColW, bodyH);
|
col.sizeDelta = new Vector2(_historyColW, bodyH);
|
||||||
|
|
||||||
float top = 0f;
|
float top = 0f;
|
||||||
|
float activityTop = -1f;
|
||||||
|
float activityBottom = 0f;
|
||||||
|
bool sawLife = false;
|
||||||
|
bool placedSep = false;
|
||||||
|
|
||||||
for (int i = 0; i < _historySlots.Length; i++)
|
for (int i = 0; i < _historySlots.Length; i++)
|
||||||
{
|
{
|
||||||
HistorySlot slot = _historySlots[i];
|
HistorySlot slot = _historySlots[i];
|
||||||
|
|
@ -1541,7 +1581,41 @@ public static class WatchCaption
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isAct = _historySlotIsActivity[i];
|
||||||
|
if (!isAct && !placedSep && activityTop >= 0f)
|
||||||
|
{
|
||||||
|
// Thin rule between Activity box and Life.
|
||||||
|
if (_lifeSep != null)
|
||||||
|
{
|
||||||
|
_lifeSep.gameObject.SetActive(true);
|
||||||
|
_lifeSep.SetParent(_historyCol.transform, false);
|
||||||
|
_lifeSep.anchorMin = new Vector2(0f, 1f);
|
||||||
|
_lifeSep.anchorMax = new Vector2(1f, 1f);
|
||||||
|
_lifeSep.pivot = new Vector2(0.5f, 1f);
|
||||||
|
_lifeSep.offsetMin = new Vector2(2f, -(top + LifeSepH));
|
||||||
|
_lifeSep.offsetMax = new Vector2(-2f, -top);
|
||||||
|
}
|
||||||
|
|
||||||
|
top += LifeSepH;
|
||||||
|
placedSep = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isAct)
|
||||||
|
{
|
||||||
|
sawLife = true;
|
||||||
|
}
|
||||||
|
|
||||||
float lineH = _historySlotHeights[i] > 0.01f ? _historySlotHeights[i] : HistoryLineMinH;
|
float lineH = _historySlotHeights[i] > 0.01f ? _historySlotHeights[i] : HistoryLineMinH;
|
||||||
|
if (isAct)
|
||||||
|
{
|
||||||
|
if (activityTop < 0f)
|
||||||
|
{
|
||||||
|
activityTop = top;
|
||||||
|
}
|
||||||
|
|
||||||
|
activityBottom = top + lineH;
|
||||||
|
}
|
||||||
|
|
||||||
RectTransform rt = slot.Root.GetComponent<RectTransform>();
|
RectTransform rt = slot.Root.GetComponent<RectTransform>();
|
||||||
rt.anchorMin = new Vector2(0f, 1f);
|
rt.anchorMin = new Vector2(0f, 1f);
|
||||||
rt.anchorMax = new Vector2(1f, 1f);
|
rt.anchorMax = new Vector2(1f, 1f);
|
||||||
|
|
@ -1569,6 +1643,31 @@ public static class WatchCaption
|
||||||
lrt.offsetMax = new Vector2(0f, -1f);
|
lrt.offsetMax = new Vector2(0f, -1f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_lifeSep != null && (!sawLife || activityTop < 0f))
|
||||||
|
{
|
||||||
|
_lifeSep.gameObject.SetActive(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_activityBoxBg != null)
|
||||||
|
{
|
||||||
|
if (activityTop >= 0f && activityBottom > activityTop)
|
||||||
|
{
|
||||||
|
_activityBoxBg.gameObject.SetActive(true);
|
||||||
|
RectTransform box = _activityBoxBg.rectTransform;
|
||||||
|
box.SetParent(_historyCol.transform, false);
|
||||||
|
box.SetAsFirstSibling();
|
||||||
|
box.anchorMin = new Vector2(0f, 1f);
|
||||||
|
box.anchorMax = new Vector2(1f, 1f);
|
||||||
|
box.pivot = new Vector2(0.5f, 1f);
|
||||||
|
box.offsetMin = new Vector2(0f, -activityBottom);
|
||||||
|
box.offsetMax = new Vector2(0f, -activityTop);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_activityBoxBg.gameObject.SetActive(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static float PlaceTraits(float yFromTop, int traitCount)
|
private static float PlaceTraits(float yFromTop, int traitCount)
|
||||||
|
|
@ -1768,6 +1867,8 @@ public static class WatchCaption
|
||||||
_taskText = null;
|
_taskText = null;
|
||||||
_traitsRow = null;
|
_traitsRow = null;
|
||||||
_historyCol = null;
|
_historyCol = null;
|
||||||
|
_activityBoxBg = null;
|
||||||
|
_lifeSep = null;
|
||||||
_favoriteBtn = null;
|
_favoriteBtn = null;
|
||||||
_favoriteIcon = null;
|
_favoriteIcon = null;
|
||||||
_historyBtn = null;
|
_historyBtn = null;
|
||||||
|
|
@ -1808,6 +1909,21 @@ public static class WatchCaption
|
||||||
_historyCol = new GameObject("HistoryCol", typeof(RectTransform));
|
_historyCol = new GameObject("HistoryCol", typeof(RectTransform));
|
||||||
_historyCol.transform.SetParent(_root.transform, false);
|
_historyCol.transform.SetParent(_root.transform, false);
|
||||||
|
|
||||||
|
GameObject boxGo = new GameObject("ActivityBox", typeof(RectTransform), typeof(Image));
|
||||||
|
boxGo.transform.SetParent(_historyCol.transform, false);
|
||||||
|
_activityBoxBg = boxGo.GetComponent<Image>();
|
||||||
|
_activityBoxBg.color = new Color(0.14f, 0.16f, 0.14f, 0.55f);
|
||||||
|
_activityBoxBg.raycastTarget = false;
|
||||||
|
boxGo.SetActive(false);
|
||||||
|
|
||||||
|
GameObject sepGo = new GameObject("LifeSep", typeof(RectTransform), typeof(Image));
|
||||||
|
sepGo.transform.SetParent(_historyCol.transform, false);
|
||||||
|
_lifeSep = sepGo.GetComponent<RectTransform>();
|
||||||
|
Image sepImg = sepGo.GetComponent<Image>();
|
||||||
|
sepImg.color = new Color(0.55f, 0.58f, 0.62f, 0.55f);
|
||||||
|
sepImg.raycastTarget = false;
|
||||||
|
sepGo.SetActive(false);
|
||||||
|
|
||||||
for (int i = 0; i < _historySlots.Length; i++)
|
for (int i = 0; i < _historySlots.Length; i++)
|
||||||
{
|
{
|
||||||
GameObject slotGo = new GameObject("Hist" + i, typeof(RectTransform));
|
GameObject slotGo = new GameObject("Hist" + i, typeof(RectTransform));
|
||||||
|
|
@ -1817,6 +1933,7 @@ public static class WatchCaption
|
||||||
label.color = HistoryTextColor;
|
label.color = HistoryTextColor;
|
||||||
label.alignment = TextAnchor.MiddleLeft;
|
label.alignment = TextAnchor.MiddleLeft;
|
||||||
label.horizontalOverflow = HorizontalWrapMode.Overflow;
|
label.horizontalOverflow = HorizontalWrapMode.Overflow;
|
||||||
|
label.supportRichText = true;
|
||||||
label.resizeTextMinSize = 6;
|
label.resizeTextMinSize = 6;
|
||||||
label.resizeTextMaxSize = 8;
|
label.resizeTextMaxSize = 8;
|
||||||
_historySlots[i] = new HistorySlot { Root = slotGo, Icon = icon, Label = label };
|
_historySlots[i] = new HistorySlot { Root = slotGo, Icon = icon, Label = label };
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "IdleSpectator",
|
"name": "IdleSpectator",
|
||||||
"author": "dazed",
|
"author": "dazed",
|
||||||
"version": "0.12.25",
|
"version": "0.12.29",
|
||||||
"description": "AFK Idle Spectator (I) + Lore (L). Activity feed + activity-first idle scoring.",
|
"description": "AFK Idle Spectator (I) + Lore (L). Detailed activity prose, Activity/Life tabs.",
|
||||||
"GUID": "com.dazed.idlespectator"
|
"GUID": "com.dazed.idlespectator"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue