Compare commits
3 Commits
main
...
docs/rust-
| Author | SHA1 | Date | |
|---|---|---|---|
| 664d2b2144 | |||
| a34b5fdbf0 | |||
| 6684ea5c60 |
@@ -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 spikes, D203–D208 (§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,12 @@ 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 |
|
||||
| **D206** | **Lock-to-first-attacker is set per event.** RaidableBases keeps it only in its main config, so the raids helper patches the base's `BypassUseOwners()` to answer from the run's own setting for the event's base, and leaves every other base to the config. | One server-wide switch the config page edits; no lock control at all. | 11.4.5 |
|
||||
| **D207** | **A step places its base either where RaidableBases picks (the default, D203) or at an admin-given spot:** x and z, plus either a height adjustment or an exact height. The spot needs the raids helper; without it the step offers only the plugin's pick. | Only an admin-given spot; keeping D203 alone. | 11.4.5 |
|
||||
| **D208** | **A blocked spot is refused, with the reason.** The step does not spawn when the plugin's area check fails or the helper's water and terrain test fails, and the run log says why ("player building", "raid base", "in water"). The editor runs the same check when the step is saved. | Falling back to the plugin's pick; spawning anyway. | 11.4.5 |
|
||||
|
||||
## 11. First-class optional plugins (D199–D202)
|
||||
|
||||
@@ -702,6 +708,209 @@ 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<BasePlayer> raiders, List<BasePlayer> intruders, List<BaseEntity> 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:** the same helper questions on Carbon and on the paid edition.
|
||||
Also the one-at-a-time rule: while a first base of the same name is still waiting to start on a server, the
|
||||
step refuses to spawn a second one, because the free edition's bases have no ids to tell them apart.
|
||||
§11.4.4 may retire that rule.
|
||||
|
||||
#### 11.4.4 The helper spike (2026-09-28)
|
||||
|
||||
The org lead asked for two tests of a D168 helper before the build: can it despawn one base, and which
|
||||
settings can it override? A third question came with them: can an admin give the spot, meaning x, z and a
|
||||
height adjustment? The spike ran on the rust-oxide rig against the free edition, 3.2.0. Carbon and the paid
|
||||
edition are still to run (§11.4.2). Every finding below reaches RaidableBases' internals by reflection and a
|
||||
Harmony patch. None of it is public API, so the helper has to check the plugin's version at hello, as the
|
||||
zone helper does.
|
||||
|
||||
- **Despawning one base works.** The plugin keeps its live bases in a list on the plugin, and each base has
|
||||
its own `Despawn()`. Calling it on one base removed that base and no other. It fired the usual
|
||||
`OnRaidableBaseDespawn` and `OnRaidableBaseDespawned` hooks, so the bridge's feed sees nothing new.
|
||||
- **The helper can know exactly which base is the event's.** A Harmony postfix on the plugin's `OpenEvent`
|
||||
receives the new base object itself. The helper can tag it for the run at birth, instead of matching the
|
||||
position and spawn time `Started` reports afterwards (D204). The one-at-a-time rule above may then go,
|
||||
if the build confirms it on Carbon and the paid edition.
|
||||
- **Overrides go in at `OpenEvent`, not after `Started`.** At `OpenEvent` the base holds a reference to its
|
||||
profile's settings object, which every base of that profile shares. The helper swaps in a deep copy (a
|
||||
JSON round trip), changed for this spawn. The shared profile was checked afterwards and was untouched.
|
||||
Proven on the rig:
|
||||
- PvP on/off;
|
||||
- despawn minutes (inactive and total, via the profile's own "override config" switch);
|
||||
- NPCs on/off, and the scientist and murderer counts.
|
||||
- **Lock-to-first-attacker is not a profile setting.** It lives in RaidableBases' main config, in two
|
||||
pairs of switches (PvP and PvE): one pair for "Manual" bases, which is what `rbevent` and the helper
|
||||
spawn, and one pair for scheduled bases. A per-event lock would need a second Harmony patch, on the
|
||||
base's `BypassUseOwners()`. Not built; a question for the org lead.
|
||||
- **An admin-given spot works.** The helper builds the same spawn request the plugin's own "spawn where I
|
||||
look" command builds, from numbers instead of a player's view. It then calls the plugin's paste. Bases
|
||||
landed at the given x and z every time.
|
||||
- **Height has to go through a copy of the profile.** The plugin recomputes y at paste time from the
|
||||
ground (or water), plus the building's own height, plus the profile's paste height adjustment. It
|
||||
ignores any y it is handed, unless the profile forces a height. So the helper copies the profile:
|
||||
- an adjustment is added to the copy's paste height adjustment;
|
||||
- an absolute y turns on the copy's forced height.
|
||||
|
||||
Results, measured as the height of the base's building blocks:
|
||||
|
||||
| Request | Blocks at | Ground there |
|
||||
| --- | --- | --- |
|
||||
| no adjustment | 1–4 | −0.2 |
|
||||
| +5 | 6–9 | −1.1 |
|
||||
| absolute y = 30 | 31–34 | 0.5 |
|
||||
| a plain `rbevent` spawned right after | 1–4 | −0.2 |
|
||||
|
||||
**Trap:** the plugin's own `BuildingOptions.Clone()` is a shallow `MemberwiseClone`. The first run changed
|
||||
the height through that clone, and the change leaked into the shared profile: the next plain `rbevent`
|
||||
base spawned 35 m up. The height settings sit in a nested object, so the helper must copy that too, or
|
||||
use the same deep copy as the overrides. Nothing reached the file on disk, and `rb.reloadprofiles` reset
|
||||
the leak.
|
||||
- **The plugin's area check catches buildings, but not water.** Its safety check, run at an admin's spot,
|
||||
refused a player's building and refused an existing raid base. At 25 m away from the building it passed. A
|
||||
spot on the seabed 50 m under water also passed: the plugin tests water in its own location picker, not
|
||||
in this check. So an admin-given spot needs the helper's own water and terrain test as well as the
|
||||
plugin's check. The spike reported the check but did not enforce it; whether to enforce is a question
|
||||
for the org lead.
|
||||
- **A RaidableBases reload drops the helper's Harmony patches**, because the patched methods belong to the
|
||||
old assembly. The helper must re-patch in `OnPluginLoaded`, as the zone helper does for ZoneManager.
|
||||
- **After a server boot the plugin refuses every spawn for about 50 s**, until its log says "Grid
|
||||
initialization completed". This includes the helper's spawns. The step has to wait for that, not fail
|
||||
on it.
|
||||
|
||||
#### 11.4.5 Decided after the helper spike (2026-09-28)
|
||||
|
||||
The org lead answered §11.4.4's three questions the same day:
|
||||
|
||||
- **D206: lock-to-first-attacker is per event.** The helper adds a second Harmony patch, on each base's
|
||||
`BypassUseOwners()`. For a base the helper tagged for a run, it answers from that run's setting. Every
|
||||
other base, including one an admin spawns by hand, still follows RaidableBases' config. Without the
|
||||
helper the lock follows the config, and the step says so.
|
||||
- **D207: a step places its base one of two ways.** The default is D203, where RaidableBases picks. The
|
||||
other is an admin-given spot: x and z, plus either a height adjustment (added to the profile's own) or
|
||||
an exact height (the profile's forced height). Both go through the helper's deep copy of the profile
|
||||
(§11.4.4). The spot needs the helper; without it the step offers only the plugin's pick.
|
||||
- **D208: a blocked spot is refused, and the run log says why.** At spawn time the helper runs
|
||||
RaidableBases' own area check and its own water and terrain test. If either fails, the step does not
|
||||
spawn, and the run records the reason ("player building", "raid base", "in water"). The editor
|
||||
runs the same check through the bridge when the step is saved. That is advice only, since the world
|
||||
can change before the event runs.
|
||||
|
||||
### 11.5 Protocol
|
||||
|
||||
These are new verbs, events and hello fields. If they are built before the cutover they join the unreleased
|
||||
|
||||
Reference in New Issue
Block a user