feat: API 4, factions, escort, allies, tether, turrets, kit extras and PVE (stage 5)
Stage 5's behaviour (docs runicnpc/PLAN.md, D253-D272): - profiles gain faction, relations (exceptions), alertRadius, turrets, hurtByPlayers, hurtsPlayers and kitUse, and the guard role; the faction table rides in profiles.json, one row per pair, both ways (D254, D268); - our own sensing and fight beside Rust's design: only the factions a profile hunts, within its leash of its anchor (D263), the target made Horror for the length of our own shot, and Rust's attack routine; - shooting back at whoever hurt it (D268), the alert radius (D256), clan, team or player allies that are never targeted and are defended with what they own (D257), escort as an order that walks home when its charge is gone (D269), and a placement's tether through ZoneManager (D272); - turrets: ignore through CanBeTargeted, and always through a Harmony patch on the safe-zone sentries for our always NPCs only (D267); - the kit's extras, each opted into and used up: heal, melee, flamethrower, grenades and rockets (D260, D265); - TruePVE/NextGenPVE answered both ways unless a profile turns a direction off, and the opt-out enforced by RunicNPC itself (D261, D271); - API 4: RunicNpc_Factions, _SetFactions, _Escort, _Ally, _Tether and the OnRunicNpcEscortEnded hook; rnpc follow, rnpc.faction and tether=. Compiled and loaded on both rigs (Oxide and Carbon); the sentry patch applies on both. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
12
plugin.toml
12
plugin.toml
@@ -24,9 +24,11 @@
|
||||
#
|
||||
# 1 was stage 0's: `RunicNpc_ApiVersion()` and nothing else.
|
||||
# 2 was stage 2's whole API, PLAN.md §4.
|
||||
# Current: 3 — stage 4 (D249): RunicNpc_AddPlacement, _RenamePlacement, _RespawnPlacement and the
|
||||
# OnRunicNpcPlacementChanged hook, documented in docs/runicnpc/API.md.
|
||||
api = 3
|
||||
# 3 was stage 4 (D249): RunicNpc_AddPlacement, _RenamePlacement, _RespawnPlacement and the
|
||||
# OnRunicNpcPlacementChanged hook.
|
||||
# Current: 4 — stage 5: RunicNpc_Factions, _SetFactions, _Escort, _Ally, _Tether, the profile's stage 5
|
||||
# fields, a placement's tether, and the OnRunicNpcEscortEnded hook, documented in docs/runicnpc/API.md.
|
||||
api = 4
|
||||
|
||||
# ── Framework floors ─────────────────────────────────────────────────────────
|
||||
#
|
||||
@@ -45,6 +47,6 @@ min_carbon_version = "2.0.259"
|
||||
# `doctor`, which reports a missing one rather than installing it. The PR check
|
||||
# holds this list and the plugin's `// Requires:` lines equal.
|
||||
#
|
||||
# ZoneManager is NOT listed: a zone tether is optional (PLAN.md §6), and a
|
||||
# profile that asks for one on a server without it is refused when it is saved.
|
||||
# ZoneManager is NOT listed: a zone tether is optional (PLAN.md §6). A placement
|
||||
# or an event step that asks for one on a server without it is refused (D272).
|
||||
requires_plugins = ["Kits"]
|
||||
|
||||
1904
plugin/RunicNPC.cs
1904
plugin/RunicNPC.cs
File diff suppressed because it is too large
Load Diff
@@ -69,7 +69,17 @@ const PLUGIN_TOML = path.join(ROOT, 'plugin.toml')
|
||||
* That half cannot be read statically; it is the reviewer's to check, which is
|
||||
* why the reason goes here where the reviewer will see it.
|
||||
*/
|
||||
const ANSWERS_DELIBERATELY = Object.create(null)
|
||||
const ANSWERS_DELIBERATELY = Object.assign(Object.create(null), {
|
||||
// Stage 5 (D259, D264): turrets: ignore. Answers false for RunicNPC's own NPCs whose profile says
|
||||
// ignore, so no auto turret, flame turret or shotgun trap targets them; null for everything else.
|
||||
CanBeTargeted: 'turrets: ignore (D259, D264), our own NPCs only',
|
||||
// Stage 5 (D261, D271): TruePVE's and NextGenPVE's question. Answers only for one of ours facing a
|
||||
// real player, with the profile's two PVE checkboxes; null for everything else.
|
||||
CanEntityTakeDamage: 'PVE allowed both ways, or the profile opt-out (D261, D271), our own NPCs only',
|
||||
// Stage 5 (D271): cancels damage only when one of ours whose profile says it cannot hurt players
|
||||
// hits a real player. Its other job, defending an ally or escort (D257, D269), never answers.
|
||||
OnEntityTakeDamage: 'a harmless profile never hurts a player (D271), our own NPCs only',
|
||||
})
|
||||
|
||||
/** Plugins RunicNPC must require, whatever else it requires (D217). */
|
||||
const REQUIRED_PLUGINS = ['Kits']
|
||||
|
||||
@@ -100,13 +100,13 @@ test('returning null is not good enough — the signature is the rule', () => {
|
||||
// `return null` today is one edit away from `return true` tomorrow, and the
|
||||
// edit that breaks it looks harmless in a diff. A void method cannot be turned
|
||||
// into a veto without changing its signature, which is visible.
|
||||
const methods = ` private bool CanBeTargeted(BaseCombatEntity entity, AutoTurret turret)
|
||||
const methods = ` private bool CanLootEntity(BasePlayer player, StorageContainer container)
|
||||
{
|
||||
return true;
|
||||
}`
|
||||
|
||||
const problems = check(source({ expected: ['CanBeTargeted'], methods }), toml())
|
||||
assert.match(problems[0], /CanBeTargeted returns bool, not void/)
|
||||
const problems = check(source({ expected: ['CanLootEntity'], methods }), toml())
|
||||
assert.match(problems[0], /CanLootEntity returns bool, not void/)
|
||||
})
|
||||
|
||||
test('a method that is not shaped like a hook is left alone', () => {
|
||||
|
||||
Reference in New Issue
Block a user