worldbox-observer-mod/IdleSpectator/LoreProse.cs
DazedAnon c11b87aa2e Implement gravestone features and enhance Lore interactions.
- Add gravestone management with max stack and lifetime settings
- Introduce new commands for grave interactions in scenarios
- Update Lore handling to include gravestone context and details
- Refactor death cause formatting for improved readability
- Increment version to 0.28.11 in mod.json
2026-07-17 16:32:10 -05:00

276 lines
9.6 KiB
C#

using System.Text.RegularExpressions;
namespace IdleSpectator;
/// <summary>
/// Chronicle-voice templates. Hook = fact (<see cref="ChronicleEntry.Line"/>);
/// sentence = optional poetry (<see cref="ChronicleEntry.LoreLine"/>). Never invents facts.
/// </summary>
public static class LoreProse
{
private static readonly Regex KillPattern =
new Regex(@"^Killed (.+) \(([^)]+)\)$", RegexOptions.CultureInvariant);
private static readonly Regex KilledByPattern =
new Regex(
@"^(?:Killed by|Was cut down by|Was slain by) (.+) \(([^)]+)\)$",
RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
private static readonly Regex EatenByPattern =
new Regex(
@"^(?:Eaten by|Was torn apart and eaten by|Devoured by) (.+)(?: \(([^)]+)\))?$",
RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
private static readonly Regex LoversPattern =
new Regex(@"^Became lovers with (.+)$", RegexOptions.CultureInvariant);
private static readonly Regex FriendPattern =
new Regex(@"^Befriended (.+)$", RegexOptions.CultureInvariant);
private static readonly Regex WarVsPattern =
new Regex(@"^War: (.+) vs (.+)$", RegexOptions.CultureInvariant);
private static readonly Regex TotalWarPattern =
new Regex(@"^Total war: (.+)$", RegexOptions.CultureInvariant);
private static readonly Regex KingdomFellPattern =
new Regex(@"^Kingdom fell: (.+)$", RegexOptions.CultureInvariant);
private static readonly Regex KingdomNewPattern =
new Regex(@"^New kingdom: (.+)$", RegexOptions.CultureInvariant);
private static readonly Regex CityDestroyedPattern =
new Regex(@"^City destroyed: (.+)$", RegexOptions.CultureInvariant);
private static readonly Regex CityNewPattern =
new Regex(@"^New city: (.+)$", RegexOptions.CultureInvariant);
private static readonly Regex NewKingPattern =
new Regex(@"^New king: (.+) \((.+)\)$", RegexOptions.CultureInvariant);
private static readonly Regex KingDiedPattern =
new Regex(@"^King died: (.+) \((.+)\)$", RegexOptions.CultureInvariant);
private static readonly Regex KingKilledPattern =
new Regex(@"^King killed: (.+) \((.+)\)$", RegexOptions.CultureInvariant);
private static readonly Regex AlliancePattern =
new Regex(@"^Alliance: (.+)$", RegexOptions.CultureInvariant);
private static readonly Regex AllianceDissolvedPattern =
new Regex(@"^Alliance dissolved: (.+)$", RegexOptions.CultureInvariant);
private static readonly Regex WarEndedPattern =
new Regex(@"^War ended: (.+)$", RegexOptions.CultureInvariant);
private static readonly Regex RevoltPattern =
new Regex(@"^Revolt: (.+)$", RegexOptions.CultureInvariant);
private static readonly Regex ExtinctPattern =
new Regex(@"^Species extinct: (.+)$", RegexOptions.CultureInvariant);
private static readonly Regex FavoriteFellPattern =
new Regex(@"^Favorite fell: (.+)$", RegexOptions.CultureInvariant);
private static readonly Regex DisasterHint =
new Regex(@"disaster", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
/// <summary>Rewrite a raw history/world fact line into short legendary prose.</summary>
public static string Rewrite(ChronicleKind kind, string rawLine, string assetId = null)
{
if (string.IsNullOrEmpty(rawLine))
{
return rawLine ?? "";
}
// Long free-form harness / player lines stay as written.
if (rawLine.Length > 96)
{
return rawLine;
}
switch (kind)
{
case ChronicleKind.Kill:
return RewriteKill(rawLine);
case ChronicleKind.Death:
return RewriteDeath(rawLine);
case ChronicleKind.Lover:
return RewriteLovers(rawLine);
case ChronicleKind.Friend:
return RewriteFriend(rawLine);
case ChronicleKind.World:
return RewriteWorld(rawLine, assetId);
default:
return rawLine;
}
}
private static string RewriteKill(string raw)
{
Match m = KillPattern.Match(raw);
if (m.Success)
{
return "Slew " + m.Groups[1].Value + " of the " + m.Groups[2].Value + "s";
}
return raw;
}
private static string RewriteDeath(string raw)
{
Match by = KilledByPattern.Match(raw);
if (by.Success)
{
return "Fell at " + by.Groups[1].Value + "'s hand";
}
Match eaten = EatenByPattern.Match(raw);
if (eaten.Success)
{
return "Devoured by " + eaten.Groups[1].Value;
}
// New FormatDeathCause lines are already flavored - keep them.
// Legacy strings still get a soft rewrite.
switch (raw)
{
case "Died of old age":
return "Faded gently into old age";
case "Starved to death":
return "Wasted away from hunger";
case "Burned to death":
return "Burned until only ash remained";
case "Burned in lava":
return "Was swallowed by lava";
case "Drowned":
return "Sank beneath the water and never rose";
case "Died of poison":
return "Succumbed as poison flooded their veins";
case "Died of plague":
return "Was claimed by a spreading plague";
case "Died of infection":
return "Rotted from within as infection took hold";
case "Died of a tumor":
return "Was consumed by a growing tumor";
case "Dissolved in acid":
return "Dissolved screaming in acid";
case "Died of ash fever":
return "Burned out from ash fever";
case "Fell to their death":
return "Fell from a height and broke upon the ground";
case "Died in an explosion":
return "Was torn apart in an explosion";
case "Struck down by divine power":
return "Was smote by divine power";
case "Was eaten":
return "Was torn apart and eaten";
case "Killed in combat":
return "Fell in bitter combat";
case "Transformed away":
return "Transformed away and left no body behind";
case "Died":
return "Met their end";
default:
return raw;
}
}
private static string RewriteLovers(string raw)
{
Match m = LoversPattern.Match(raw);
return m.Success ? "Heartbound to " + m.Groups[1].Value : raw;
}
private static string RewriteFriend(string raw)
{
Match m = FriendPattern.Match(raw);
return m.Success ? "Bound in friendship with " + m.Groups[1].Value : raw;
}
private static string RewriteWorld(string raw, string assetId)
{
Match m;
m = WarVsPattern.Match(raw);
if (m.Success)
{
return "War kindled: " + m.Groups[1].Value + " and " + m.Groups[2].Value;
}
m = TotalWarPattern.Match(raw);
if (m.Success)
{
return "Total war engulfed " + m.Groups[1].Value;
}
m = KingdomFellPattern.Match(raw);
if (m.Success)
{
return "Kingdom fell: " + m.Groups[1].Value;
}
m = KingdomNewPattern.Match(raw);
if (m.Success)
{
return "A kingdom rose: " + m.Groups[1].Value;
}
m = CityDestroyedPattern.Match(raw);
if (m.Success)
{
return "City razed: " + m.Groups[1].Value;
}
m = CityNewPattern.Match(raw);
if (m.Success)
{
return "City founded: " + m.Groups[1].Value;
}
m = NewKingPattern.Match(raw);
if (m.Success)
{
return "Crowned: " + m.Groups[1].Value + " of " + m.Groups[2].Value;
}
m = KingDiedPattern.Match(raw);
if (m.Success)
{
return "A king passed: " + m.Groups[1].Value + " of " + m.Groups[2].Value;
}
m = KingKilledPattern.Match(raw);
if (m.Success)
{
return "A king slain: " + m.Groups[1].Value + " of " + m.Groups[2].Value;
}
m = AlliancePattern.Match(raw);
if (m.Success)
{
return "Alliance forged: " + m.Groups[1].Value;
}
m = AllianceDissolvedPattern.Match(raw);
if (m.Success)
{
return "Alliance broken: " + m.Groups[1].Value;
}
m = WarEndedPattern.Match(raw);
if (m.Success)
{
return "War ended: " + m.Groups[1].Value;
}
m = RevoltPattern.Match(raw);
if (m.Success)
{
return "Revolt in " + m.Groups[1].Value;
}
m = ExtinctPattern.Match(raw);
if (m.Success)
{
return "Extinct: " + m.Groups[1].Value;
}
m = FavoriteFellPattern.Match(raw);
if (m.Success)
{
return "A favorite fell: " + m.Groups[1].Value;
}
if (!string.IsNullOrEmpty(assetId) && DisasterHint.IsMatch(assetId))
{
return "Disaster struck: " + raw;
}
if (!string.IsNullOrEmpty(assetId) && assetId.IndexOf("shattered", System.StringComparison.OrdinalIgnoreCase) >= 0)
{
return raw.Replace("Kingdom shattered:", "Kingdom shattered:");
}
return raw;
}
}