Compare commits
17 Commits
v0.2.0
...
92915f7de6
| Author | SHA1 | Date | |
|---|---|---|---|
| 92915f7de6 | |||
| 4e83def0dd | |||
| 7b1eabcd8b | |||
| 72f51c3d9e | |||
| 8e8553c589 | |||
| 7c9c8bdacb | |||
| 49a677e3fb | |||
| 491760d817 | |||
| 8fdc374ab3 | |||
| 51de1b492f | |||
| adb9df6a2e | |||
| 6be14c7270 | |||
| e3dc918400 | |||
| 3db35cdd02 | |||
| 66fbbd6b57 | |||
| cc7b2b2a0e | |||
| 6952fbf123 |
38
README.md
38
README.md
@@ -101,6 +101,44 @@ Clans on the website come from Rust's **own** clan system, not from the uMod `Cl
|
|||||||
is optional. The two share no data: a server running the plugin has two unrelated clan systems, and
|
is optional. The two share no data: a server running the plugin has two unrelated clan systems, and
|
||||||
only the game's own clans become Teams on the site.
|
only the game's own clans become Teams on the site.
|
||||||
|
|
||||||
|
Wulf's `Economics` is optional too, and not in `overlay.toml`. Where it is loaded the site can pay
|
||||||
|
coins from an event, show balances and a richest column, and let staff adjust a balance; where it is
|
||||||
|
not, each of those says "Economics is not installed on this server".
|
||||||
|
|
||||||
|
WhiteThunder's `Backpacks` is optional in the same way. Where it is loaded staff can open a player's
|
||||||
|
backpack from the site, a player can see their own, and an event can lend a bigger one for its run;
|
||||||
|
the bridge only ever reads a backpack, and the bigger size is one of Backpacks' own `backpacks.size.N`
|
||||||
|
permissions, pushed like any other grant.
|
||||||
|
|
||||||
|
nivex's `TruePVE` is optional in the same way. Where it is loaded an event's zone can turn off PvP
|
||||||
|
protection inside it, or use one of the operator's own TruePVE rulesets by name; where it is not, the
|
||||||
|
zone step says "TruePVE is not installed on this server". The bridge only ever adds and removes the
|
||||||
|
mappings of zones it opened, and removes each one with its zone, even when TruePVE was away at the time.
|
||||||
|
|
||||||
|
HunterZ's `Dynamic PVP` (which needs ZoneManager and TruePVE) is optional too. Where it is loaded, the
|
||||||
|
PvP zones it opens around a downed helicopter, a Bradley, cargo and the rest reach the site's feed and
|
||||||
|
its map. The bridge only reads them: it never makes, maps or erases one of Dynamic PVP's zones.
|
||||||
|
|
||||||
|
nivex's `Dangerous Treasures` is optional as well. Where it is loaded, its treasure events reach the
|
||||||
|
site's feed, its map and the *Treasure Hunter* title, and an event can open one (`dt.start`) through
|
||||||
|
DT's own `dtevent` console command; DT picks the spot and despawns its own chest. For about ten minutes
|
||||||
|
after DT loads it is still finding its spots, and a start then is answered "not ready yet". A framework
|
||||||
|
that cannot run another plugin's console command from code (`rg.world` says `consolePlugins=False`)
|
||||||
|
cannot open one.
|
||||||
|
|
||||||
|
Bazz3l's `Guarded Crate` is optional too, and needs `NpcSpawn` (a free Codefling download, not shipped
|
||||||
|
here) for its guards. Where both are loaded, a guarded crate landing, being cleared and timing out
|
||||||
|
reach the site's feed, its map and the *Warden* title, and an event can place one (`gc.place`) at a
|
||||||
|
monument or a point through Guarded Crate's own `gcrate position` console command. A placed crate is
|
||||||
|
kept when the event ends: Guarded Crate has no way to end one event alone. Without NpcSpawn a placement
|
||||||
|
is refused, because the crate would land unguarded; and, as for Dangerous Treasures, a framework with
|
||||||
|
`consolePlugins=False` cannot place one.
|
||||||
|
|
||||||
|
Rust's own world events need no plugin at all. The patrol helicopter, Bradley, the cargo ship, the
|
||||||
|
Chinook, airdrops, locked crates, the Travelling Vendor, the satellite and the Deep Sea reach the
|
||||||
|
site's feed and its event triggers on every server, and an event can call one of them (`world.call`).
|
||||||
|
What a call starts is kept when the event ends; `EventsMaxWorldCalls` caps how many one run may call.
|
||||||
|
|
||||||
## Diagnosing it
|
## Diagnosing it
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
14
overlay.toml
14
overlay.toml
@@ -26,15 +26,23 @@
|
|||||||
# the same change that alters the emitters, exactly as the sidecar bumps
|
# the same change that alters the emitters, exactly as the sidecar bumps
|
||||||
# PROTOCOL_VERSION and the module bumps its own constant.
|
# PROTOCOL_VERSION and the module bumps its own constant.
|
||||||
#
|
#
|
||||||
# Current: 13 — the transport, the read path, identity, the permission mirror,
|
# Current: 14 — the transport, the read path, identity, the permission mirror,
|
||||||
# configuration from the site, first-party clans, a raid frame that names who
|
# configuration from the site, first-party clans, a raid frame that names who
|
||||||
# lives there, the event leases, the world verbs events place and give back, and
|
# lives there, the event leases, the world verbs events place and give back, and
|
||||||
# the rewards: a run's participation tally, kit credits and chat (PROTOCOL.md §16),
|
# the rewards: a run's participation tally, kit credits and chat (PROTOCOL.md §16),
|
||||||
# the map: its picture and everything that moves on it (PROTOCOL.md §17), and the
|
# the map: its picture and everything that moves on it (PROTOCOL.md §17), and the
|
||||||
# optional mods: BetterChat titles and group styles, and popups (PROTOCOL.md §18),
|
# optional mods: BetterChat titles and group styles, and popups (PROTOCOL.md §18),
|
||||||
# and the first player walk's fixes (PROTOCOL.md §19) — opened by a configuration
|
# and the first player walk's fixes (PROTOCOL.md §19) — opened by a configuration
|
||||||
# save that answers at once and reports its reload as a `config.outcome` frame.
|
# save that answers at once and reports its reload as a `config.outcome` frame,
|
||||||
protocol = 13
|
# and the step editor (PROTOCOL.md §20): `kits.uses`, and the one-time reward
|
||||||
|
# messages a `perm.sync` carries as `notices`; and Economics (PROTOCOL.md §20.3):
|
||||||
|
# `economics.pay`, `economics.balances`, `economics.adjust`, the tally's
|
||||||
|
# `balance` and the `economics.wiped` event; and Backpacks (PROTOCOL.md §20.4):
|
||||||
|
# `backpacks.read` and hello's `integrations.backpacks.sizes`; and Rust's own world
|
||||||
|
# events (PROTOCOL.md §20.5): `world.event` with its `event`, `world.call`, hello's
|
||||||
|
# `console`, and the tally's `cratesHacked` and `dropsLooted` in place of `apcKills`
|
||||||
|
# and `heliKills`.
|
||||||
|
protocol = 14
|
||||||
|
|
||||||
# ── Oxide compatibility ──────────────────────────────────────────────────────
|
# ── Oxide compatibility ──────────────────────────────────────────────────────
|
||||||
#
|
#
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -113,6 +113,16 @@ function readChatCommands(source) {
|
|||||||
return found
|
return found
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Every `[ConsoleCommand("x")]` the plugin declares. */
|
||||||
|
function readConsoleCommands(source) {
|
||||||
|
return [...source.matchAll(/\[ConsoleCommand\("([^"]+)"\)\]/g)].map((m) => m[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The `rg.*` commands the plugin runs on itself through `ConsoleSystem.Run`. */
|
||||||
|
function readOwnConsoleRuns(source) {
|
||||||
|
return [...source.matchAll(/ConsoleSystem\.Run\([^,]+,\s*"(rg\.[\w.]+)"/g)].map((m) => m[1])
|
||||||
|
}
|
||||||
|
|
||||||
function readMethods(source) {
|
function readMethods(source) {
|
||||||
const found = []
|
const found = []
|
||||||
let m
|
let m
|
||||||
@@ -196,7 +206,20 @@ function check(source, toml) {
|
|||||||
seen.add(cmd.command)
|
seen.add(cmd.command)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. The two declaration sites this repository owns must agree.
|
// 5. Protocol 14 (D363): the console probe runs one of this plugin's own commands
|
||||||
|
// through `ConsoleSystem.Run` to learn whether the framework runs a plugin's
|
||||||
|
// command from code. A probe naming a command the plugin does not declare
|
||||||
|
// answers "no" on every framework, which reads exactly like Carbon's bug.
|
||||||
|
for (const name of readOwnConsoleRuns(source)) {
|
||||||
|
if (!readConsoleCommands(source).includes(name)) {
|
||||||
|
problems.push(
|
||||||
|
`ConsoleSystem.Run runs "${name}", but the plugin declares no [ConsoleCommand("${name}")]: ` +
|
||||||
|
'the console probe would report false on every framework (D363).'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. The two declaration sites this repository owns must agree.
|
||||||
const inCode = /ProtocolVersion\s*=\s*(\d+)\s*;/.exec(source)
|
const inCode = /ProtocolVersion\s*=\s*(\d+)\s*;/.exec(source)
|
||||||
// runicnpc stage 4: the RunicNPC API the bridge needs, declared twice like the protocol.
|
// runicnpc stage 4: the RunicNPC API the bridge needs, declared twice like the protocol.
|
||||||
const npcToml = /^\s*runicnpc_api\s*=\s*(\d+)\s*$/m.exec(toml)
|
const npcToml = /^\s*runicnpc_api\s*=\s*(\d+)\s*$/m.exec(toml)
|
||||||
@@ -242,6 +265,6 @@ function main() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { check, readExpectedHooks, readMethods, readChatCommands, HOOK_NAME }
|
module.exports = { check, readExpectedHooks, readMethods, readChatCommands, readConsoleCommands, readOwnConsoleRuns, HOOK_NAME }
|
||||||
|
|
||||||
if (require.main === module) main()
|
if (require.main === module) main()
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const assert = require('node:assert')
|
|||||||
const fs = require('node:fs')
|
const fs = require('node:fs')
|
||||||
const path = require('node:path')
|
const path = require('node:path')
|
||||||
|
|
||||||
const { check, readExpectedHooks, readMethods, readChatCommands, HOOK_NAME } = require('./checkPlugin')
|
const { check, readExpectedHooks, readMethods, readChatCommands, readConsoleCommands, readOwnConsoleRuns, HOOK_NAME } = require('./checkPlugin')
|
||||||
|
|
||||||
/** A minimal plugin that passes, as the baseline every case below deviates from. */
|
/** A minimal plugin that passes, as the baseline every case below deviates from. */
|
||||||
function source({ expected = ['OnPlayerDeath'], methods, version = 2 } = {}) {
|
function source({ expected = ['OnPlayerDeath'], methods, version = 2 } = {}) {
|
||||||
@@ -210,3 +210,55 @@ test('the chat-command parser reads the real plugin, rather than quietly matchin
|
|||||||
assert.ok(names.has('link'), '/link was not found by the chat-command parser')
|
assert.ok(names.has('link'), '/link was not found by the chat-command parser')
|
||||||
assert.ok(names.has('unlink'), '/unlink was not found by the chat-command parser')
|
assert.ok(names.has('unlink'), '/unlink was not found by the chat-command parser')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// ── The console probe (protocol 14, D363) ──────────────────────────────────
|
||||||
|
//
|
||||||
|
// The plugin runs its own `rg.probe` through `ConsoleSystem.Run` to learn whether
|
||||||
|
// the framework runs a plugin's command from code. A probe that names a command
|
||||||
|
// the plugin does not declare answers false everywhere, and false is exactly what
|
||||||
|
// Carbon's bug looks like, so nobody would ever notice.
|
||||||
|
|
||||||
|
/** A plugin that runs `ran` on itself and declares `declared`. */
|
||||||
|
function withProbe(ran, declared) {
|
||||||
|
return source({
|
||||||
|
expected: [],
|
||||||
|
methods: ` private void StartWorldEvents()
|
||||||
|
{
|
||||||
|
ConsoleSystem.Run(ConsoleSystem.Option.Server.Quiet(), "${ran}");
|
||||||
|
}
|
||||||
|
|
||||||
|
[ConsoleCommand("${declared}")]
|
||||||
|
private void CmdProbe(ConsoleSystem.Arg arg)
|
||||||
|
{
|
||||||
|
}`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
test('a probe that runs a command the plugin declares passes', () => {
|
||||||
|
assert.deepEqual(check(withProbe('rg.probe', 'rg.probe'), toml()), [])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('a probe that runs a command the plugin does not declare is caught', () => {
|
||||||
|
const problems = check(withProbe('rg.probe', 'rg.prob'), toml())
|
||||||
|
assert.equal(problems.length, 1)
|
||||||
|
assert.match(problems[0], /ConsoleSystem\.Run runs "rg\.probe", but the plugin declares no \[ConsoleCommand\("rg\.probe"\)\]/)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('a native command run through the console is not the probe, and is left alone', () => {
|
||||||
|
const methods = ` private void Call()
|
||||||
|
{
|
||||||
|
ConsoleSystem.Run(ConsoleSystem.Option.Server.Quiet(), "satellite.random");
|
||||||
|
}`
|
||||||
|
assert.deepEqual(check(source({ expected: [], methods }), toml()), [])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('the probe parsers read the real plugin, rather than quietly matching nothing', () => {
|
||||||
|
const real = fs.readFileSync(
|
||||||
|
path.resolve(__dirname, '..', 'overlay', 'oxide', 'plugins', 'RunicGateway.cs'),
|
||||||
|
'utf8'
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.ok(readOwnConsoleRuns(real).includes('rg.probe'), 'the console probe was not found by the parser')
|
||||||
|
assert.ok(readConsoleCommands(real).includes('rg.probe'), 'rg.probe was not found among the console commands')
|
||||||
|
assert.ok(readConsoleCommands(real).includes('rg.hooks'), 'rg.hooks was not found among the console commands')
|
||||||
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user