279 lines
9.1 KiB
C#
279 lines
9.1 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Text;
|
|
using UnityEngine;
|
|
|
|
namespace IdleSpectator;
|
|
|
|
public sealed class ActivityStatusAuditResult
|
|
{
|
|
public bool Passed;
|
|
public string Detail = "";
|
|
public int Total;
|
|
public int MissingAuthored;
|
|
public int MissingIcon;
|
|
public int LocaleLeaks;
|
|
public int EmptyPhrase;
|
|
public int OrphanAuthored;
|
|
public int MissingInterest;
|
|
public int OrphanInterest;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Exhaustive live-library audit for status Activity prose, icons, and interest policy.
|
|
/// </summary>
|
|
public static class ActivityStatusHarness
|
|
{
|
|
public static ActivityStatusAuditResult RunAudit(string outputDirectory)
|
|
{
|
|
List<string> liveIds = ActivityAssetCatalog.EnumerateLiveStatusIds();
|
|
var authored = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
|
foreach (string id in ActivityStatusProse.AuthoredIds)
|
|
{
|
|
authored.Add(id);
|
|
}
|
|
|
|
int missingAuthored = 0;
|
|
int missingIcon = 0;
|
|
int localeLeaks = 0;
|
|
int emptyPhrase = 0;
|
|
int orphanAuthored = 0;
|
|
int missingInterest = 0;
|
|
int orphanInterest = 0;
|
|
var tsv = new StringBuilder();
|
|
tsv.AppendLine(
|
|
"id\tgain\tloss\tauthored\tinterest\tevent_strength\tcreates_interest\ticon\tlocale_leak\tduration\tallow_timer_reset\tnotes");
|
|
|
|
var liveSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
|
var interestAuthored = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
|
foreach (string id in EventCatalog.Status.AuthoredIds)
|
|
{
|
|
interestAuthored.Add(id);
|
|
}
|
|
|
|
for (int i = 0; i < liveIds.Count; i++)
|
|
{
|
|
string id = liveIds[i];
|
|
liveSet.Add(id);
|
|
StatusAsset asset = ActivityAssetCatalog.TryGetStatusAsset(id);
|
|
string gain = ActivityStatusProse.Phrase(id, gained: true, out bool authoredGain);
|
|
string loss = ActivityStatusProse.Phrase(id, gained: false, out bool authoredLoss);
|
|
bool hasAuthored = authoredGain && authoredLoss && ActivityStatusProse.HasAuthored(id);
|
|
if (!hasAuthored)
|
|
{
|
|
missingAuthored++;
|
|
}
|
|
|
|
StatusInterestEntry interest = EventCatalog.Status.GetOrFallback(id);
|
|
bool hasInterest = EventCatalog.Status.HasAuthored(id) && !interest.IsFallback;
|
|
if (!hasInterest)
|
|
{
|
|
missingInterest++;
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(gain) || string.IsNullOrEmpty(loss))
|
|
{
|
|
emptyPhrase++;
|
|
}
|
|
|
|
Sprite icon = HudIcons.FromStatusId(id)
|
|
?? HudIcons.FromUiIcon("iconConfused")
|
|
?? HudIcons.Task();
|
|
bool iconOk = icon != null;
|
|
if (!iconOk)
|
|
{
|
|
missingIcon++;
|
|
}
|
|
|
|
bool leak = LooksLikeLocaleLeak(gain, id) || LooksLikeLocaleLeak(loss, id);
|
|
if (leak)
|
|
{
|
|
localeLeaks++;
|
|
}
|
|
|
|
string duration = "";
|
|
string allowReset = "";
|
|
try
|
|
{
|
|
if (asset != null)
|
|
{
|
|
duration = asset.duration.ToString("0.##");
|
|
allowReset = asset.allow_timer_reset ? "1" : "0";
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// ignore
|
|
}
|
|
|
|
string notes = "ok";
|
|
if (!hasAuthored)
|
|
{
|
|
notes = "missing_authored";
|
|
}
|
|
else if (!hasInterest)
|
|
{
|
|
notes = "missing_interest";
|
|
}
|
|
else if (!iconOk)
|
|
{
|
|
notes = "missing_icon";
|
|
}
|
|
else if (leak)
|
|
{
|
|
notes = "locale_leak";
|
|
}
|
|
else if (string.IsNullOrEmpty(gain) || string.IsNullOrEmpty(loss))
|
|
{
|
|
notes = "empty_phrase";
|
|
}
|
|
|
|
tsv.Append(id).Append('\t')
|
|
.Append(Escape(gain)).Append('\t')
|
|
.Append(Escape(loss)).Append('\t')
|
|
.Append(hasAuthored ? "1" : "0").Append('\t')
|
|
.Append(hasInterest ? "1" : "0").Append('\t')
|
|
.Append(interest.EventStrength.ToString("0.#")).Append('\t')
|
|
.Append(interest.CreatesInterest ? "1" : "0").Append('\t')
|
|
.Append(iconOk ? "1" : "0").Append('\t')
|
|
.Append(leak ? "1" : "0").Append('\t')
|
|
.Append(duration).Append('\t')
|
|
.Append(allowReset).Append('\t')
|
|
.Append(notes).AppendLine();
|
|
|
|
string gainKey = ActivityStatusProse.TaskKey(id, true);
|
|
string lossKey = ActivityStatusProse.TaskKey(id, false);
|
|
if (string.IsNullOrEmpty(gainKey) || string.IsNullOrEmpty(lossKey))
|
|
{
|
|
emptyPhrase++;
|
|
}
|
|
}
|
|
|
|
foreach (string id in authored)
|
|
{
|
|
if (!liveSet.Contains(id))
|
|
{
|
|
orphanAuthored++;
|
|
tsv.Append(id).Append("\t\t\t1\t0\t0\t0\t0\t0\t\t\torphan_authored").AppendLine();
|
|
}
|
|
}
|
|
|
|
foreach (string id in interestAuthored)
|
|
{
|
|
if (!liveSet.Contains(id))
|
|
{
|
|
orphanInterest++;
|
|
tsv.Append(id).Append("\t\t\t0\t1\t0\t0\t0\t0\t\t\torphan_interest").AppendLine();
|
|
}
|
|
}
|
|
|
|
try
|
|
{
|
|
if (!string.IsNullOrEmpty(outputDirectory))
|
|
{
|
|
Directory.CreateDirectory(outputDirectory);
|
|
File.WriteAllText(
|
|
Path.Combine(outputDirectory, "activity-status-audit.tsv"),
|
|
tsv.ToString(),
|
|
Encoding.UTF8);
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// Artifact write is best-effort.
|
|
}
|
|
|
|
int cameraDemotionFailures = 0;
|
|
// Brief FX / cooldowns must stay B. Story-readable statuses must stay A.
|
|
string[] bOnlyStatus =
|
|
{
|
|
"afterglow", "cough", "dash", "dodge", "flicked", "just_ate", "on_guard",
|
|
"recovery_combat_action", "recovery_plot", "recovery_social", "recovery_spell",
|
|
"shield", "slowness", "spell_boost", "spell_silence", "egg"
|
|
};
|
|
for (int i = 0; i < bOnlyStatus.Length; i++)
|
|
{
|
|
StatusInterestEntry e = EventCatalog.Status.GetOrFallback(bOnlyStatus[i]);
|
|
if (e.CreatesInterest)
|
|
{
|
|
cameraDemotionFailures++;
|
|
}
|
|
}
|
|
|
|
string[] aStatus =
|
|
{
|
|
"drowning", "burning", "frozen", "poisoned", "possessed", "cursed",
|
|
"pregnant", "crying", "rage", "stunned", "soul_harvested", "tantrum",
|
|
"fell_in_love", "starving", "angry", "sleeping", "inspired", "ash_fever"
|
|
};
|
|
for (int i = 0; i < aStatus.Length; i++)
|
|
{
|
|
StatusInterestEntry e = EventCatalog.Status.GetOrFallback(aStatus[i]);
|
|
if (!e.CreatesInterest)
|
|
{
|
|
cameraDemotionFailures++;
|
|
}
|
|
}
|
|
|
|
bool passed = liveIds.Count > 20
|
|
&& missingAuthored == 0
|
|
&& missingIcon == 0
|
|
&& localeLeaks == 0
|
|
&& emptyPhrase == 0
|
|
&& orphanAuthored == 0
|
|
&& missingInterest == 0
|
|
&& orphanInterest == 0
|
|
&& cameraDemotionFailures == 0;
|
|
|
|
return new ActivityStatusAuditResult
|
|
{
|
|
Passed = passed,
|
|
Total = liveIds.Count,
|
|
MissingAuthored = missingAuthored,
|
|
MissingIcon = missingIcon,
|
|
LocaleLeaks = localeLeaks,
|
|
EmptyPhrase = emptyPhrase,
|
|
OrphanAuthored = orphanAuthored,
|
|
MissingInterest = missingInterest,
|
|
OrphanInterest = orphanInterest,
|
|
Detail =
|
|
$"total={liveIds.Count} missingAuthored={missingAuthored} missingIcon={missingIcon} "
|
|
+ $"localeLeaks={localeLeaks} emptyPhrase={emptyPhrase} orphanAuthored={orphanAuthored} "
|
|
+ $"missingInterest={missingInterest} orphanInterest={orphanInterest} "
|
|
+ $"cameraDemotionFailures={cameraDemotionFailures} passed={passed}"
|
|
};
|
|
}
|
|
|
|
private static bool LooksLikeLocaleLeak(string phrase, string statusId)
|
|
{
|
|
if (string.IsNullOrEmpty(phrase))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (phrase.IndexOf("status_", StringComparison.OrdinalIgnoreCase) >= 0)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(statusId)
|
|
&& statusId.IndexOf('_') >= 0
|
|
&& phrase.IndexOf(statusId, StringComparison.OrdinalIgnoreCase) >= 0)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private static string Escape(string value)
|
|
{
|
|
if (string.IsNullOrEmpty(value))
|
|
{
|
|
return "";
|
|
}
|
|
|
|
return value.Replace('\t', ' ').Replace('\n', ' ').Replace('\r', ' ');
|
|
}
|
|
}
|