Website news articles now land in the modern Town Cryer News gump
(TownCryerSystem.NewsEntries), separate from the scrolling-crier lines.
Overlay BridgeNews (new): news.add / news.remove insert/remove a
TownCryerNewsEntry directly in the public NewsEntries list (no stock edit),
tracking our own id->entry map so stock uo.com news is left intact. Title,
HTML body, image, and URL are all supported (the stock gumps already branch on
TextDefinition.Number, so string content renders). On add the article title is
also proclaimed via GlobalTownCrierEntryList (announce defaults on; set
announce:false to suppress). Config caps: NewsMaxTitleLength/BodyLength/
External, NewsAnnounceDurationSec.
Sidecar: POST /news (add/replace, id-correlated), DELETE /news/{id}; news table
stores each article as its news.add command; on shard server.hello the sidecar
replays the stored set with announce:false (the shard rebuilds NewsEntries each
boot and does not persist ours, so the website is the source of truth).
Docs: PROTOCOL_2 §16 (design + verified), INTEGRATION.md /news endpoints.
Verified live: sidecar cargo check clean; overlay compiles in the full ServUO
Scripts tree (0 errors); booted shard + sidecar and exercised add/replace/
remove/error paths and the reconnect replay end-to-end.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
117 lines
5.7 KiB
INI
117 lines
5.7 KiB
INI
|
|
# uo-link bridge settings.
|
|
#
|
|
# Key scope is the filename: Bridge.cfg + StatSweepSeconds => "Bridge.StatSweepSeconds".
|
|
# Read in Configure(), which runs before World.Load.
|
|
|
|
# Loopback only. The socket being local is the trust boundary for inbound commands;
|
|
# if the sidecar ever moves off-host, add a shared secret first.
|
|
Host=127.0.0.1
|
|
Port=7788
|
|
|
|
# Outbound queue cap. On overflow the plugin drops oldest and counts the drops,
|
|
# because a stalled sidecar must never OOM the shard.
|
|
QueueCap=10000
|
|
|
|
# Sweep intervals, seconds. Measured on a 150-character shard: a vitals sweep costs
|
|
# 0.0015 ms/char, so 1000 online players is ~1.5 ms per sweep. See docs/PLAN.md §1.
|
|
StatSweepSeconds=30
|
|
DecaySweepSeconds=60
|
|
EconomySweepSeconds=300
|
|
|
|
# Champion-spawn board poll. ChampionSpawn has no EventSink, so every spawn is diffed on
|
|
# this interval to emit champ.update on any status/level/kills/boss change. The world holds
|
|
# only a handful of spawns, so the pass is trivial; 5-10s is well within site tolerance.
|
|
ChampSweepSeconds=10
|
|
|
|
# Help-page queue poll. The in-game page queue has no EventSink, so it is diffed on this
|
|
# interval to emit page.new / page.closed / page.updated. A few seconds is fine for a
|
|
# support queue; the full open queue is also available on demand via pages.snapshot.
|
|
PageSweepSeconds=5
|
|
|
|
# Guild roster poll (docs/PROTOCOL_2.md Part B). Guilds expose only EventSink.JoinGuild, so
|
|
# create/disband/leave/leader/alliance changes are found by diffing BaseGuild.List on this
|
|
# interval (emit guild.update / guild.remove). Guild membership moves slowly; 60s is ample.
|
|
GuildSweepSeconds=60
|
|
|
|
# Town-governor poll. Each city's Governor / election is diffed on this interval to emit
|
|
# city.update on change. Governors turn over on the order of weeks, so a slow sweep is fine.
|
|
# Idle (emits nothing) unless the City Loyalty system is enabled (CityLoyalty.Enabled).
|
|
CitySweepSeconds=300
|
|
|
|
# Presence poll. Online population (total, per-facet, per-region) is snapshotted on this
|
|
# interval and emitted as presence.online only when it changes. Region transitions come
|
|
# through separately in real time as region.enter (EventSink.OnEnterRegion).
|
|
PresenceSweepSeconds=30
|
|
|
|
# Housing registry poll. Every house is diffed on this interval to emit house.update /
|
|
# house.remove (owner, region, location, decay). Houses change slowly; a few minutes is fine.
|
|
HousingSweepSeconds=300
|
|
|
|
# Shown to a player when they run [link. The website page where they enter the code.
|
|
LinkUrl=https://yoursite/link
|
|
|
|
# Town-crier news pushed from the website. Caps are defense in depth on top of the
|
|
# loopback trust boundary: a buggy or compromised sidecar still cannot flood the criers.
|
|
TownCrierMaxLines=6
|
|
TownCrierMaxLineLength=200
|
|
TownCrierMaxActive=20
|
|
TownCrierMaxDurationSec=86400
|
|
|
|
# Town Cryer news gump. Website articles (news.add) become entries in the modern Town
|
|
# Cryer News gump (TownCryerSystem.NewsEntries), separate from the scrolling-crier lines
|
|
# above. The article title is also proclaimed by the criers (announce defaults on). Caps
|
|
# are defense in depth on top of the loopback trust boundary.
|
|
NewsMaxTitleLength=100
|
|
NewsMaxBodyLength=2000
|
|
NewsMaxExternal=20
|
|
NewsAnnounceDurationSec=300
|
|
|
|
# Admin write plane (staff moderation from the website). OFF by default: the whole
|
|
# feature is opt-in per shard. When enabled, inbound admin.* commands (kick/ban/unban/
|
|
# broadcast) are honored. Authorization is enforced on the website; the shard trusts the
|
|
# loopback socket and applies a hard floor below.
|
|
AdminWriteEnabled=false
|
|
|
|
# The one shard-side safety floor. An admin.* command refuses any target whose AccessLevel
|
|
# is at or above this, so even a compromised sidecar can never touch the Owner. Values are
|
|
# AccessLevel names (Player, VIP, Counselor, Decorator, Spawner, GameMaster, Seer,
|
|
# Administrator, Developer, CoOwner, Owner). Default CoOwner => only Owner/CoOwners shielded.
|
|
AdminAccessFloor=CoOwner
|
|
|
|
# Defense-in-depth caps on admin.* payloads (mirroring the town-crier caps).
|
|
AdminBroadcastMaxLength=300
|
|
AdminReasonMaxLength=400
|
|
# Clamp on a timed ban's duration, seconds. A ban with no/zero duration is indefinite.
|
|
AdminBanMaxDurationSec=31536000
|
|
|
|
# Account provisioning (docs/PROTOCOL_2.md Part A). Which side may mint game accounts:
|
|
# website — the website is the authority; pair with Accounts.AutoCreateAccounts=false
|
|
# (else an in-game login of any new name still mints an account).
|
|
# game — the game server is the authority; website account.create is refused.
|
|
# hybrid — either side may create (the default).
|
|
# The bridge governs only the account.create verb; the in-game first-login auto-create is
|
|
# the core Accounts.AutoCreateAccounts setting, which you pair with the mode above. On boot
|
|
# the bridge warns if the two contradict. An unrecognized value here falls back to 'game'
|
|
# (the safest — no website creation).
|
|
SignupMode=hybrid
|
|
|
|
# Master switch for the account.create verb. Absent, it follows the mode (on unless
|
|
# SignupMode=game). Set explicitly to force it on or off regardless of mode.
|
|
AccountCreateEnabled=true
|
|
|
|
# Fail closed if account.create omits a usable browser IP. The per-IP cap
|
|
# (Accounts.AccountsPerIp) only means something if a missing/loopback IP is refused rather
|
|
# than waved through. Turn off only for a deployment that deliberately does not cap website
|
|
# signups by IP (MaxAccountsPerIP still applies in-game either way).
|
|
RequireIpForCreate=true
|
|
|
|
# Length caps on a website-supplied username / password, checked before the account is made.
|
|
AccountNameMaxLength=16
|
|
AccountPasswordMaxLength=30
|
|
|
|
# The test scaffolding in tools/scaffolding/ reads its own flags from this file
|
|
# (SeedOnStart, CensusOnStart, ProbeOnStart). They are absent here on purpose:
|
|
# Config.Get returns the default of false when a key is missing, so a deployed
|
|
# server never runs the scaffolding even if its .cs files are present.
|