diff --git a/modules/rust/PLAN_REDESIGNS.md b/modules/rust/PLAN_REDESIGNS.md index 36eef37..cc31f03 100644 --- a/modules/rust/PLAN_REDESIGNS.md +++ b/modules/rust/PLAN_REDESIGNS.md @@ -1,7 +1,7 @@ # `module-rust` — the redesigns, planned **Status:** plan, written 2026-09-27. **Its eleven questions were answered the same day: D188–D198 (§10).** -On 2026-09-28 the org lead added the first-class optional plugins (§11, D199–D202). +On 2026-09-28 the org lead added the first-class optional plugins (§11, D199–D202), and after the RaidableBases spike, D203–D205 (§11.4). It is step 3 of [`PLAN_FIXES.md`](PLAN_FIXES.md) §6: the six changes the org lead decided during the first player walk, each "planned in detail before code". The org lead asked for all six in one plan (2026-09-27), so they can be read together. No code has been written for any of them yet. @@ -632,6 +632,9 @@ The org lead added §11 on 2026-09-28 and answered its questions the same day: | **D200** | **Economics: all four uses.** Coins as an event reward (withdrawn on revert); balances shown to the player (site and app) and to staff; staff deposit, withdraw and set a balance, with an activity row; and a *richest* leaderboard column and chat-title condition. | — | 11.2 | | **D201** | **Backpacks: staff view any player's backpack, a player views their own (site and app), and an event can grant extra capacity for a time**, taken back at the end. | Showing backpack sizes as named tiers on the permission screen. | 11.3 | | **D202** | **RaidableBases: all four uses, and the paid edition too.** Active bases on the live map and in the feed; an event step that spawns a base at a difficulty and despawns it at the end; bases raided per player on the leaderboard and as a title condition; and the paid edition's extras (five difficulties, lockouts) where it is installed, falling back to what both editions share. | Supporting only what the free edition has. | 11.4 | +| **D203** | **RaidableBases picks where an event's base goes**; the site learns the spot from `OnRaidableBaseStarted` and shows it. The paid edition's placement, if it has one, is offered where installed. | A helper that places a base at a chosen monument. | 11.4.3 | +| **D204** | **At an event's end, a helper (`RunicGatewayRaids.cs`, D168) despawns only that event's base.** Without the helper the base despawns on its own timer, and the run records it as expiring on its own. | Leaving every base to its timer; RaidableBases' "despawn all". | 11.4.3 | +| **D205** | **An event picks a RaidableBases profile and a difficulty, may override a bounded set of settings for that spawn (through the helper), and the site edits RaidableBases' profiles** — a named exception to the editor never walking a data directory. | Profile and difficulty only; overrides without profile editing. | 11.4.3 | ## 11. First-class optional plugins (D199–D202) @@ -702,6 +705,129 @@ Personal GitHub mirrors are not a source: k1lly0u's mirror of Kits is two major - **The spike first:** pull the free source, confirm every hook and API signature above, and find the listing and despawn calls. Then load it on both rigs, with TruePVE, and raid one base. +#### 11.4.1 The free edition, as the spike found it (2026-09-28) + +The source is RaidableBases 3.2.0 (free, `umod.org/plugins/RaidableBases.cs`), and it was loaded on both rigs +with CopyPaste 4.3.0. A throwaway spike plugin made one base file from the walk hut and logged every +lifecycle hook. It was deleted afterwards, and so were the three plugins on both rigs. + +**What it needs.** **CopyPaste is a hard dependency.** A base is a CopyPaste building file in +`data/copypaste/`, and **none ship with the free edition**: the default profile `RaidBases` names +`RaidBase1`–`RaidBase5`, and none of them exist. A base with no storage box is thrown away as soon as it is +pasted ("No usable boxes found"). A PvE plugin (TruePVE, SimplePVE, NextGenPVE, Imperium or AegisPVE) is +optional. + +**What it costs at boot.** `OnServerInitialized` took 6.7 s (Oxide) and 7.9 s (Carbon) on the main thread. +The spawn grid then builds for about a minute: 52 s for 1,172 points on the rigs' 3000 map. **During that +minute a spawn is refused** ("grid is loading"). + +**Its public API** (`[HookMethod]`): + +| Call | Returns | Use to us | +|---|---|---| +| `IsPremium()` | `false` on the free edition | hello's edition field | +| `GetAllDifficulties()` | `[("Normal", 512)]` on the free edition | the step's difficulty list, read from the installed plugin rather than hard-coded | +| `GetAllEventsCount()`, `GetActiveEventCount()` | ints | the servers page | +| `GetAllEvents()` / `GetAllEventsNonAlloc(list)` | an 18-field tuple per base: position, mode, level, PvP, owner, raiders, intruders, entities, base name, spawn and despawn time, radius, loot left | the live map layer. **The plugin warns that `GetAllEvents` changes shape in the next update.** | +| `EventTerritory(pos)`, `…Any`, `…All`, `GetPlayersFrom`, `GetOwnerFrom`, `HasPVPDelay` | | not needed | + +**Nothing in the API spawns, despawns, or lists profiles.** + +**The hooks it raises.** `OnRaidableBaseStarted`, `Ended`, `Completed`, `Despawn`, `Despawned` and +`PrivilegeDestroyed` all carry the same 17 arguments: + +``` +(Vector3 location, int level, bool allowPVP, string id, float, float, float loadTime, ulong ownerId, + BasePlayer owner, List raiders, List intruders, List entities, + string baseName, DateTime spawn, DateTime despawn, float protectionRadius, int lootRemaining) +``` + +A plugin method with exactly that signature binds on both frameworks. `Started` also has a one-argument +overload carrying the plugin's own class, which the bridge cannot name and does not need. Other hooks: +`OnPlayerEnteredRaidableBase` / `Exited` (14 arguments, the player first), `OnRaidableBaseUnlocked`, +`OnRaidableAwardGiven` / `Owner`, the PvP-delay hooks and loot hooks. + +**How another plugin spawns a base: only through the server console.** + +- `rbevent [baseName]` queues a base, and **RaidableBases chooses where**. On the rigs, three spawns landed at + Q6, C13 and C4, and none of them was named by the command. There is no position, no monument and no + difficulty argument, and **nothing comes back** to the caller. +- `rbe` pastes where a player is looking, so it needs a player in the game. +- **Every base's `id` is `"0"`** on the free edition. The only way to know which base the event made is the + next `Started` for that base name after the command. That is safe while events make one base at a time. + +**How another plugin despawns a base: all or nothing.** `rbevent despawnall` removes every base on the +server, including ones the event did not make, and `rbevent despawn_inactive` removes every inactive one. +Despawning **one** base needs a player standing at it. Left alone, a base despawns on its profile's own +timer: 45 minutes in the default profile, as `despawn` in `Started` said. + +**Its options.** Each profile has well over a hundred settings. Among them: NPCs, turrets, loot rules, +despawn timers, the plugins to block, where bases may spawn (beaches, roads, monuments), what players may +do, and backpacks. The global config has its own schedulers (Maintained, Scheduled and Manual events), map +markers, ZoneManager zones, a ranked ladder and messages. **Profiles live in the plugin's data directory**, +which the site's configuration editor deliberately never walks. The global config is in the config +directory, where the editor already reaches it. + +**What that means for D202's event step.** "Spawn a base at a difficulty near a monument, and despawn it at +the end" is not possible on the free edition through anything RaidableBases offers another plugin. What +it does offer is: spawn a named base, somewhere, now; learn where and when it will despawn from the hook; +and remove every base at once. §11.4.3 asks which way to go. + +#### 11.4.2 The paid edition — to be filled when the licensed copy arrives + +The org lead supplies a licensed copy later in the week of 2026-09-28. The same spike runs against it on +both rigs, and this section records: + +- `IsPremium()` and `GetAllDifficulties()` (expected: five tiers); +- whether it adds a spawn at a position, a monument or a difficulty, a despawn of one base, or real base + ids — by API or console command; +- whether the 17-argument hooks and `GetAllEvents` have the same shape (the free edition warns that the + latter is changing), and what `level` carries per difficulty; +- lockouts, buyable events and the ranked ladder: the hooks they raise and what the feed could say; +- anything in it that needs a D168 helper, and anything the free edition's answer in §11.4.3 would get + wrong for it. + +The design in §11.4.3 is written so the paid edition extends it rather than replaces it. The step reads +what the installed edition offers — difficulties, and any placement or despawn it supports — and offers +exactly that. + +#### 11.4.3 Decided (2026-09-28) + +The org lead answered the spike's three questions the same day: + +- **D203: where a base goes — RaidableBases picks.** The step spawns the chosen base with `rbevent`, and + RaidableBases places it wherever its grid allows. The bridge learns the spot from the next + `OnRaidableBaseStarted` for that base name. The site then shows it on the live map and in the run log, and + records it on the run's ledger. If the paid edition offers placement, the step offers it there too + (§11.4.2). +- **D204: at the end — a helper removes only the event's base.** Teardown, a revert, and an operator's stop + each despawn exactly the base the run made, never another. The free edition cannot do that from outside + (§11.4.1), so a D168 helper, **`RunicGatewayRaids.cs`**, finds the run's base by the position and spawn time + `Started` reported, and calls RaidableBases' own despawn on that one base. Like the zone helper + (D181, D182), it is optional and default-installed, and it reports at hello. **Without it, the step still + spawns, and the base despawns on its own timer.** The site says so on the step, and the run records the + base as expiring on its own, like D170's zones. A base the plugin despawns first is marked `expired`. +- **D205: what an event can choose — a profile, a difficulty, and per-event overrides; and the site edits + profiles.** + - **Profile and difficulty.** The step picks a RaidableBases profile, and a difficulty from the installed + plugin's `GetAllDifficulties()`. The free edition offers "Normal"; the paid edition offers its five. + - **Per-event overrides.** The step may override a bounded set of a profile's settings for this spawn + only. The first set is PvP on/off, despawn minutes, NPCs on/off and count, and the lock-to-first-attacker + rule; the build's spike settles which of them the plugin lets a helper change after `Started`. The free + API has no way to do this, so the helper applies them. **Without the helper there are no overrides**, and + the step says so. + - **The site edits profiles.** The configuration page gains RaidableBases' profile files, + `data/RaidableBases/Profiles/*.json`. This is a **named exception** to the rule that the editor never + walks a plugin's data directory, which exists because that directory holds live state and the + permission stores. The exception is that one folder, for that one plugin. It gets the editor's usual + guarantees: the write is recorded, a reload is watched, and a failed load rolls back (D178, D179). The + reload is `rb.reloadprofiles`, which the spike ran on both rigs, not a plugin reload. + +**Still to settle in the build's own spike:** exactly which internal routine the helper calls to despawn one +base, and which settings it can override after `Started`, on both editions. Also the one-at-a-time rule: the +step refuses to spawn a second base of the same name on a server while a first one is waiting to start, +because the free edition's bases have no ids to tell them apart. + ### 11.5 Protocol These are new verbs, events and hello fields. If they are built before the cutover they join the unreleased