chore(release): cut edge over to main — protocol 14 #44
@@ -110,6 +110,11 @@ backpack from the site, a player can see their own, and an event can lend a bigg
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
```
|
||||
|
||||
@@ -38,7 +38,10 @@
|
||||
# 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`.
|
||||
# `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 ──────────────────────────────────────────────────────
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -113,6 +113,16 @@ function readChatCommands(source) {
|
||||
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) {
|
||||
const found = []
|
||||
let m
|
||||
@@ -196,7 +206,20 @@ function check(source, toml) {
|
||||
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)
|
||||
// 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)
|
||||
@@ -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()
|
||||
|
||||
@@ -16,7 +16,7 @@ const assert = require('node:assert')
|
||||
const fs = require('node:fs')
|
||||
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. */
|
||||
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('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