BridgeTownCrier handles inbound towncrier.add / towncrier.remove, pushing
website-published news into GlobalTownCrierEntryList so every town crier
announces it until it expires. Both run on the Core thread (required: AddEntry
mutates a shared list and the criers send packets). An id maps to the created
TownCrierEntry so a later remove can pull it, and re-adding an id replaces the
prior entry.
Caps are enforced before touching the shared list -- line count, line length,
active-entry count, duration -- as defense in depth on top of the loopback trust
boundary: a buggy or compromised sidecar still cannot flood the criers or pin a
message forever. Config: Bridge.TownCrierMax{Lines,LineLength,Active,DurationSec}.
Verified with a sending stub and a probe that logs the actual crier list. Replies
and game state agree: add n1 -> towncrier.ok and the entry appears with the exact
lines; add n2 (8 lines over the cap of 6) -> towncrier.error and never enters the
list; remove n1 -> towncrier.ok and the entry is gone; remove unknown ->
towncrier.error. Evidence in docs/PLAN.md §16.
Adds BridgeJson.GetStringList for JSON string arrays, tools/stub_sidecar_crier.ps1,
and tools/scaffolding/BridgeCrierProbe.cs. This closes the pure-plugin inbound
work; only the PlayerVendorSale core edit (Phase 7) remains on the ServUO side.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
59 lines
1.8 KiB
C#
59 lines
1.8 KiB
C#
using System;
|
|
using System.Text;
|
|
|
|
using Server.Mobiles;
|
|
|
|
namespace Server.Custom
|
|
{
|
|
/// <summary>
|
|
/// Logs the global town-crier entry list every few seconds so a towncrier.add / remove
|
|
/// round-trip can be observed landing in the actual game state, not just acknowledged.
|
|
///
|
|
/// Test scaffolding. Never deployed. Read-only.
|
|
/// </summary>
|
|
public static class BridgeCrierProbe
|
|
{
|
|
public static void Initialize()
|
|
{
|
|
if (Config.Get("Bridge.CrierProbeOnStart", false))
|
|
EventSink.ServerStarted += () =>
|
|
Timer.DelayCall(TimeSpan.FromSeconds(3.0), TimeSpan.FromSeconds(3.0), Dump);
|
|
}
|
|
|
|
private static int _tick;
|
|
|
|
private static void Dump()
|
|
{
|
|
try
|
|
{
|
|
var list = GlobalTownCrierEntryList.Instance;
|
|
var entries = list == null ? null : list.Entries;
|
|
int count = entries == null ? 0 : entries.Count;
|
|
|
|
var sb = new StringBuilder();
|
|
sb.AppendFormat("[CrierProbe] tick {0}: {1} entries", ++_tick, count);
|
|
|
|
if (entries != null)
|
|
{
|
|
for (int i = 0; i < entries.Count; i++)
|
|
{
|
|
var e = entries[i];
|
|
if (e == null || e.Lines == null)
|
|
continue;
|
|
sb.AppendFormat(" | [{0}]", String.Join(" / ", e.Lines));
|
|
}
|
|
}
|
|
|
|
Console.WriteLine(sb.ToString());
|
|
|
|
if (_tick >= 6)
|
|
Timer.DelayCall(TimeSpan.Zero, () => { }); // no-op; probe stops being interesting
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine("[CrierProbe] FAILED: " + ex);
|
|
}
|
|
}
|
|
}
|
|
}
|