feat(rust): the rewards — tally, kit reward, chat and the news leg (phase 13b, protocol 10)

Four event verbs and the announce leg, per PLAN.md §29:

- rust.participation.open / .collect: the plugin counts who takes part
  (seconds, kills or both, in a zone this run opened or the whole server)
  and collect files them as the run's participants, keyed by Steam id.
- rust.kit.entitle: the five recipient modes (D101), rows in the new
  rust_perm_run_grants (D84) unioned into the permission push, one extra
  use of the kit per reward as site-held credits on perm.sync (D103),
  and the rust.kit.entitled notice deferred from phase 10 (D64).
- rust.announce: one server or every server (D105).
- rust.chat announce leg, speaking only on servers whose new news switch
  is on (D104) - a card on Admin -> Rust visibility (D106).

Budgets rust.grants and rust.announcements; the kit source and four
fixed-choice sources (core has no enum param type). rust_perm_run_grants
carries core's idempotency key so a revert of a lost answer can find its
rows. Protocol 10.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
2026-09-24 07:00:44 -05:00
parent 9753dda4af
commit cc185db26b
27 changed files with 2096 additions and 45 deletions

View File

@@ -129,6 +129,11 @@ const HEADLINES = Object.freeze({
intro: `The Steam account ${d.player || d.steamId} was linked with an in-game code. `
+ 'If that was not you, unlink it from your Rust account page.',
}),
'rust.kit.entitled': (d) => ({
title: `You earned ${d.kit} on ${d.server}`,
intro: `An event on ${d.server} rewarded you: the ${d.kit} kit is waiting in the Kits menu, `
+ 'with one extra use. Redeem it in game.',
}),
'rust.clan.member.left': (d) => ({
title: `${d.member || 'A member'} left ${d.clan}`,
intro: `${d.member || 'A member'} left ${d.clan} on ${d.server}.`,
@@ -546,12 +551,38 @@ function linked({ userId, steamId, name }) {
}
}
/**
* `rust.kit.entitled` for each user one reward step granted (phase 13b). Never
* throws: a notification that could not be raised must not fail the grant it
* is about. Returns how many were raised.
*/
function entitled({ userIds, kit, server, mode, runId, stepId }) {
let raised = 0
try {
const rewardKey = `${runId}:${stepId}`
for (const userId of new Set(userIds || [])) {
const uid = Number(userId)
if (!Number.isInteger(uid) || uid < 1) continue
const ok = fire(T['rust.kit.entitled'], {
data: { rewardKey, kit: String(kit), ...serverVars(server), ...(mode ? { mode: String(mode) } : {}), accountUrl: PATHS.account },
ownerUserId: uid,
dedupeKey: dedupeKey('entitled', runId, stepId, uid),
})
if (ok) raised++
}
} catch (err) {
log.warn('could not raise the reward notification', { error: err.message })
}
return raised
}
module.exports = {
onEvent,
serverObserved,
checkLeader,
sweepLoginDenied,
linked,
entitled,
reset,
dedupeKey,
headline,