Social play and the rules of PvP
Decision #131. Chat channels, parties, guilds, murder counts and young colonists: the group play the vassal game (#77) grows from, and the consequences that keep full-loot PvP (#121) from emptying the alpha.
Code: crates/uo-proto/src/social.rs (rules, views, the chat filter), crates/uo-world/src/law.rs
(where the law reaches), crates/uo-server/src/social/ (records and their store operations),
crates/uo-server/src/gateway/social.rs (chat routing, invitations, views),
crates/uo-server/src/sim/social.rs and sim/region/social.rs (the rules in the world),
crates/uo-client/src/online/social/ (windows), crates/uo-sdk/src/bot/social.rs (bots).
1. Chat
| Channel | How | Who hears it |
|---|---|---|
| local speech | Say (Enter, or the local tab) | everyone in the area of interest, over the speaker's head |
| private | Tell { to, text }, /t name … | that character, wherever it is |
| party | Chat { channel: party }, /p | the party |
| guild | /g | the guild |
| alliance | /a | the guild and its allies |
| region | /r | everyone within 192 tiles of the speaker |
| trade | /trade | the whole world |
Every line goes through the gateway first, which is the one choke point of all client input:
- The filter (
uo_proto::social::clean): trims, drops control characters, cuts to 240 characters, masks every word containing a listed word with asterisks. The gateway adds words fromUO_CHAT_FILTER=a,b,c. - Flood guard: at most 6 lines per 10 seconds per character, all channels together.
- Mute (the hook for the GM tools, #128):
uo_server::social::mute(store, net, gateways, char, until_ms, reason)storesmute/<char>and tells every gateway (SocialNet::Muted); a muted character's speech and chat are refused until then. Gateways read the stored mute at login.GwSocial::muted(char, now)is the check. - Ghosts: the living hear a ghost's speech as moaning ("oOooOo"), and the dead can't use the party, guild or alliance channels (no "ghosting": a dead scout telling the living where everyone is).
- Report:
Report { name, reason }storesreport/<ms>/<char>for the game masters.
Routing across nodes. Gateways broadcast who is online at each of them (Presence, sent
again to a gateway that joins, dropped with a gateway that dies). A private message goes
straight to the recipient's gateway; channel lines go to every gateway, which delivers them to
its sessions on that channel (party, guild, guilds, within reach, all). Sims are not involved,
so handoffs and failovers don't touch chat. Lines arrive as GameEvent::Message(ChatLine);
local speech stays GameEvent::Chat.
2. Parties
PartyInvite { name }: the leader (or anyone in no party) invites an online character; the invitation lasts a minute (PartyAnswer { yes }). Accepting founds the party with the inviter as leader if there was none. At most 8 members.PartyLeave,PartyKick { name }(leader only). The leader leaving passes the lead to the next member; a party of one breaks up. A character whose connection drops leaves its party unless it is back within 90 seconds.- One battle side. Party members fight as one side of the region's battle (
party_side), so they can't attack or target each other with harmful spells, mobs' AI is unchanged, and a party member hit by accident doesn't hit back. - Friendly fire is real but innocent: an explosion, a sweep or a keg that catches a party member hurts them, but is never a crime nor a murder count, whoever the member is (the young included).
- Loot rights: the body of a mob or animal belongs to its killer and the killer's party for 2 minutes; anyone else taking from it in that time commits a crime. Party members loot each other's corpses without a crime.
- Bars: every sim reports its partied players' place, hits and mana once a second to every
gateway (
SocialNet::Pulse); each client getsGameEvent::PartyStatuswith its members, wherever on the map they are. The client draws them under the status panel with distance and direction.
Party records live in the store (p/<id>), so any gateway can change them.
3. Guilds
GuildFound { name, tag }burns 2,500 coins from the founder's pack (the sim takes them only after the name and tag are reserved; if it can't, the reservation is given up). Names are 3–24 letters, digits, spaces or apostrophes; tags 2–4 letters or digits; both unique, ignoring case.UO_GUILD_COSToverrides the price (tests, tuning).- Ranks: recruit, member, officer, leader. Officers and the leader invite
(
GuildInvite/GuildAnswer), officers rank and kick recruits and members, the leader ranks and kicks anyone but itself (GuildRank,GuildKick). The leader leaving passes the lead to the best-ranked member; the last one out disbands the guild, frees its name and tag and ends its relations. - The tag shows over every member's name (
EntityKind::Player::guild). - Relations (
GuildStance { guild, stance }, officers and the leader, by name or tag):- war is declared by one side and mutual at once: the members of both guilds may attack each other anywhere but Aquais, towns included, with no crime and no murder counts;
- alliance needs both guilds to propose it; allies are kin (no crime between them), and the alliance channel joins them;
- neutral ends either.
- Guilds are durable (
g/<id>,gn/<name>,gt/<tag>, the member indexm/<char>in the store'ssocialtable, compare-and-set), the seed of the lord and vassal layer.
4. The law
Where it happens (uo_world::law::ground): sanctuary (Aquais, #112: nobody fights),
guarded (a town's guard zone), open land, lawless (the isles and the wilds, #97:
their towns, and outside towns every region whose nearest settlement belongs to them). A harm
is judged on the more peaceful of the two tiles (attacker, victim); it is lawless only where
the victim stands lawless.
uo_proto::social::judge(attacker, victim, ground), in order:
- the sanctuary: refused;
- one's own party: allowed, never a crime;
- lawless land: allowed, never a crime, nobody protected;
- a young colonist or one just raised: refused;
- the same guild, an ally, a guild at war (either side declared): allowed, no crime;
- a grey or red character: allowed, no crime;
- an innocent (or a hero): refused in a guard zone, else a crime.
Harmful orders (attacks, harmful spells, kegs) are checked before they are given, including whoever stands where an area spell would land. Every hit between players is judged again when it lands (a keg's blast, a sweep): a criminal hit makes the attacker grey for 2 minutes; a hit that the order checks couldn't foresee on someone protected is given back to them and counts as a crime.
Murder counts
A player who dies was murdered by everyone who hurt them criminally in the 2 minutes before
(AGGRESSION_MS): each gets a murder count (Citizen::murders). Five counts make a
murderer (red):
- anyone may attack them anywhere, with no crime (they are not innocent);
- guards attack them (the NPC guards, #123; the
Guardbot already does); - no exchange within the law trades with them (lawless towns' fences still do);
- Aquais turns them away: a murderer who steps into the free port is put back where they came from.
A count wears off per 8 hours of online play (COUNT_DECAY_MS, game time: sped up with the test clock of #127, like hunger), so a murderer stays red
while they keep killing and has to play it off.
Young colonists
Every new character is young for its first 10 hours of play (YOUNG_PLAY_MS): outside
lawless land other players can't attack them or catch them in a harmful spell, and nobody but
their party can loot their corpse. Youth ends early on their first criminal act, the first time
they harm another player (outside their party), or when they renounce it (RenounceYoung).
Characters saved before #131 are not young. Youth protects only the innocent: a young colonist
carrying stolen goods is grey and fair game like anyone else. Ten hours cover the first evenings of the alpha:
learning to gather, trade and fight camps without being farmed.
Corpses and ghosts
- Ghosts can't be harmed (they never could).
- No killing at the healer: a character raised from a ghost is shielded from players for a
minute (
RAISE_SHIELD_MS), unless it harms someone first. - Full loot stays (#121): robbing the innocent dead is a crime, the young dead can't be robbed, a party loots its own, and on the isles and in the wilds there is no crime at all.
- The dead can't use party or guild chat, and moan to the living.
What the player sees
- Names over players:
[TAG] Name, red for murderers, grey for criminals, green for the party, the guild and its allies, orange for guilds at war with yours, "(young)" for young colonists. - The target card says why: murderer, grey, young, in your party, at war.
- The status line under the vitals: young, innocent, GREY, hero, MURDERER (counts).
- The social window (P) has a standing tab: counts, when the next wears off, youth left, the rules.
GameEvent::Law { reason, text }tells the player what the law decided (reasons:criminal,murder,murderer,murdered,young_ended,count_decayed,turned_away,looted); the client puts it in the log and the chat's law tab.
5. Bots
uo_sdk::bot::Behaviour::Murderer renounces youth and kills grown innocent players standing on
open land (never in a guard zone); it turns grey, then red. Guard hunts grey players and
murderers. Behaviour::Party raids camps in a party: the first name in Tuning::party invites
the rest, everyone accepts, members keep with their leader and talk on the party channel.
Duelist renounces youth and fights only grown players. In the load-test swarm:
uo-loadtest scenario --mix "party=8,murderer=2,guard=2,combat=20" (party bots group in fours).
6. Open
- Murder counts are given by the region that saw the death; an attacker handed to another region before the victim died escapes the count (battle pinning keeps fighters together for 10 seconds after a hit, so it is rare).
- A keg's blast on a young colonist is refunded, not prevented; a blast that kills one outright still kills.
- Long-term counts (UO's) and bounties, the report queue's GM view, ignore lists, guild halls and guildstones, the lord and vassal layer (#77) are later work.