feat(bridge): Rust's own world events on protocol 14 (PLAN_REDESIGNS §14.3.1) #35
@@ -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`
|
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.
|
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
|
## Diagnosing it
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -38,7 +38,10 @@
|
|||||||
# messages a `perm.sync` carries as `notices`; and Economics (PROTOCOL.md §20.3):
|
# messages a `perm.sync` carries as `notices`; and Economics (PROTOCOL.md §20.3):
|
||||||
# `economics.pay`, `economics.balances`, `economics.adjust`, the tally's
|
# `economics.pay`, `economics.balances`, `economics.adjust`, the tally's
|
||||||
# `balance` and the `economics.wiped` event; and Backpacks (PROTOCOL.md §20.4):
|
# `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
|
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