Compare commits
6 Commits
chore/sync
...
docs/runic
| Author | SHA1 | Date | |
|---|---|---|---|
| eb729f7dd4 | |||
| 098c379918 | |||
| 7dade6cce1 | |||
| ae0b8753bb | |||
| c28a688b03 | |||
| f899834453 |
224
runicnpc/PLAN.md
224
runicnpc/PLAN.md
@@ -6,8 +6,9 @@ decided with it. **Stage 1 measured 2026-09-30** (§9): six answers on both rigs
|
||||
**Stage 2's design answered 2026-09-30:** D232–D238 (§0), which reshape stage 2 (§9). **Stage 2 built and tested
|
||||
2026-09-30** on both rigs (§9); its API is [API.md](API.md). **Stage 3's design answered 2026-09-30:** D239–D242
|
||||
(§0). **Stage 3 built and tested 2026-09-30** on both rigs (§9); its in-game walk waits for a player. **Stage 4's
|
||||
design answered 2026-09-30** (D243–D252, §0), **built and walked 2026-09-30** on both rigs (§9). **Stage 4's
|
||||
design answered 2026-09-30:** D243–D252 (§0).
|
||||
design answered 2026-09-30** (D243–D252, §0), **built and walked 2026-09-30** on both rigs (§9). **Stage 5's
|
||||
design answered 2026-09-30 and 2026-10-01** (D253–D266, §0), **its spike measured 2026-10-01** on both rigs (§9),
|
||||
and **its build's own questions answered 2026-10-01** (D267–D272).
|
||||
|
||||
RunicNPC is Runic Gateway's own NPC plugin for Rust servers, in its own repository,
|
||||
[`RunicGateway/runicnpc-rust`](https://gitea.whitlocktech.com/RunicGateway/runicnpc-rust). It runs on Oxide and
|
||||
@@ -69,6 +70,26 @@ architectural or design decision is implemented.
|
||||
| **D250** | **Per-profile kills are shown on the player's public stats ("Warden kills: 3"), in a "kills of a profile" title category, and as a leaderboard the visitor picks a profile for** (stage 4, D225, D247). | The player page and titles only; titles only. |
|
||||
| **D251** | **Where an adopted server profile and a site profile share a name on that server, the site's wins** (stage 4, refines D244). The server's own is still imported, marked "replaced" and kept for an admin to restore, and that server's placements take the site profile's values. | The adopted one winning on its server, with the site's profile skipping it. |
|
||||
| **D252** | **A player's per-profile kills are shown by opening their row in a server's leaderboard** ("Warden 3 · Bandit 12", for the wipe the page shows), and to the player on their own Player → Rust page. The module has no public player page, and stage 4 adds none (stage 4, the "player's public stats" of D250). | A new public player page; the profile leaderboard only. |
|
||||
| **D253** | **Stage 5 is one stage, walked once end to end**, as stage 4 was (D248). It opens with the D231 spike, and its wire changes join protocol 13 while that protocol is unreleased (stage 5). | 5a/5b/5c, each walked and merged before the next; the spike alone first, the split decided afterwards. |
|
||||
| **D254** | **Factions are named, with per-profile overrides.** Each profile names its faction (`bandits`), and a faction table states once how factions treat each other: *hostile*, *neutral* or *allied*. Rust's scientists (`scientists`) and animals (`animals`) are two built-in factions. A profile may also list its own exceptions, which win over its faction's relations. The table is authored on the site and pushed with the profiles; a standalone server edits it from the console (D221) (stage 5). | Named factions only; per-profile lists only, where a sixth bandit means editing the other five. |
|
||||
| **D255** | **A profile with no faction settings fights players only**, as it does today. Fighting Rust's scientists, animals or other profiles is opted into per faction or per profile. Stage 1 measured sensing NPCs at about seven times the cost of fighting players (stage 5). | Hostile to Rust's scientists by default; hostile to everything outside its faction. |
|
||||
| **D256** | **Group alert:** each profile has an alert radius, **40 m by default** and 0 to turn it off. When one of its NPCs is attacked, every ally within that radius learns the attacker and joins the fight, whether or not it can see him. An ally is the same faction, or the same profile when there is no faction (stage 5). | Only the NPCs of the placement that was hit; off unless a profile turns it on. |
|
||||
| **D257** | **An NPC allied to a clan or a Rust team never targets its members, and defends them.** It attacks whoever damages one of those players, or anything the clan or team owns (building blocks, doors, deployables), within its leash. Rust's own clans and teams only, with no Clans-plugin dependency (stage 5). | Never targets them, nothing more; defends the players but not their buildings. |
|
||||
| **D258** | **An escort's target comes from an event step, from the API, or from an admin's `/rnpc follow <placement> <player>`**, the command for trying it out in game. A persistent placement never escorts anyone of its own accord (stage 5). | Persistent placements that escort a named player too; the API alone. |
|
||||
| **D259** | **Turrets treat our NPC as they treat Rust's scientists by default**, and it shoots back at a turret that shoots it. The spike measures exactly what Rust does. Each profile can choose `turrets: default`, `ignore` or `always` (stage 5). | Always targeted by player turrets; never targeted. |
|
||||
| **D260** | **A kit's extra items are used only when the profile opts in, behaviour by behaviour:** healing with syringes, grenades, switching to melee, rockets, flamethrowers. All are off by default; the main weapon is always used. An item used comes out of the NPC's inventory, so the loot matches what is left (stage 5). | Every behaviour on whenever the kit carries the item; the main weapon only, with extras as loot. |
|
||||
| **D261** | **On a PVE server, RunicNPC answers TruePVE's and NextGenPVE's damage hook with "allowed" for its own NPCs, both ways**, so players can fight them and they fight back, as with Rust's scientists. A profile may opt out, for example to make an NPC unkillable (stage 5). | Leaving it to the server's TruePVE rules; a config switch, off by default. |
|
||||
| **D262** | **A patrol that leaves its route to fight returns to the point it was heading to** and carries on from there (stage 5, refines D234). | The nearest point, in the same direction; starting the route again. |
|
||||
| **D263** | **A fight with Rust's scientists stays one-sided, and our NPCs never go hunting.** A scientist one of ours shoots takes cover, as Rust's AI does, and is not made to fire back. A profile hostile to `scientists` fights only scientists that come within its own leash of its spot. It never walks into a monument to look for them, so our NPCs never clear a monument on their own (stage 5 spike; the org lead's words: "our NPCs will not clear monuments on their own"). | RunicNPC driving a hit scientist to fire back; balancing fights so a few cannot clear a monument; refusing such profiles near monuments at placement. |
|
||||
| **D264** | **`turrets: always` means the safe-zone sentries too**: Outpost's and Bandit Camp's turrets shoot that profile's NPCs as they shoot a hostile player. `default` is what Rust does (player turrets shoot it, safe-zone sentries do not), and `ignore` means no turret shoots it (stage 5 spike, refines D259). | Dropping `always`, since `default` already means player turrets. |
|
||||
| **D265** | **The main weapon's ammunition is endless, as in Rust.** An NPC never runs dry, and its loot keeps the kit's ammunition. Only D260's extras (syringes, grenades, rockets and the like) are used up (stage 5 spike, refines D260). | Ammunition used up shot by shot; a per-profile setting. |
|
||||
| **D266** | **Fire burns as it does in Rust.** A flamethrower NPC's flames burn every NPC in them, its own allies included, because Rust filters only bullets between NPCs. Admins place flamethrower NPCs with that in mind (stage 5 spike). | Our NPCs taking no fire damage from their allies. |
|
||||
| **D267** | **`turrets: always` reaches the safe-zone sentries through a Harmony patch.** Rust's sentry (`NPCAutoTurret`) refuses every scientist in `Ignore` and `IsEntityHostile`, before any Oxide or Carbon hook runs. RunicNPC patches those two methods so they answer "hostile" for its own NPCs whose profile says `always`, and as Rust does for everything else, as the zone and dome helpers patch their plugins (D181). It unpatches on unload, and a failed patch is reported at boot. **The org lead's condition: if the walk shows a sentry targeting Rust's own scientists or bandit guards, the sentry half of `always` is dropped** (stage 5 build, refines D264). | RunicNPC handing nearby sentries the target again every 0.25 s; dropping the sentries from `always`. |
|
||||
| **D268** | **A faction pair is one row, both ways.** "bandits ↔ guards: hostile" makes each hunt the other; the table is a grid with one cell per pair. **Any of our NPCs that is shot fights back at whoever shot it**, whatever the table says, as it does a turret (D259) (stage 5 build). | Each direction set separately. |
|
||||
| **D269** | **Escort is an order, not a role.** Any roamer or guard profile can be told to escort, by an event step, the API or `/rnpc follow` (D258). Its NPCs keep within a few metres of the target and fight whoever attacks it. **When the target dies or leaves the server, they walk back to where they were placed** and carry on as their profile does there (stage 5 build). | Holding the spot where the target was lost; an `escort` role whose NPCs stand idle until given someone. |
|
||||
| **D270** | **An event's Place NPCs step names the escort by a player's Steam id** (typed, or a `{placeholder}` filled from the run's start params), **and the ally by a clan from the site's clan list, or as "the team of player <Steam id>"**. Both are optional (stage 5 build). | One Steam id for both; the ally alone, with escort left to the API. |
|
||||
| **D271** | **A profile's PVE opt-out is two checkboxes: "players can hurt it" and "it can hurt players"**, both on by default. Turning one off is enforced by RunicNPC itself on every server, with a PVE plugin or without one, and TruePVE and NextGenPVE are told the same. Turrets, fire and other NPCs are not affected (stage 5 build, refines D261). | One "follow the PVE rules" switch, with unkillable meaning `damageTaken` 0. |
|
||||
| **D272** | **A zone tether is set on a placement (`tether=<zone>`) and on an event step ("inside the zone this event made"), not on a profile**, because a ZoneManager zone belongs to one server and a profile may be pushed to several (stage 5 build, refines §6). | A profile field; a profile default overridden per placement and step. |
|
||||
|
||||
**Borrowing, not copying.** NpcSpawn states no licence at all, so its source grants us nothing and is read only as a
|
||||
description of *what* can be done in Rust. HumanNPC is MIT on uMod, which is GPL-compatible, but §1.2 rules out its
|
||||
@@ -342,17 +363,22 @@ Each profile has a **role**, which sets its AI states and defaults:
|
||||
| **Roamer** | Moves in one of three ways (D233): `wander` within a radius of its spot, `monument` (Rust's AI-zone paths), or `route:<name>`; chases and fights. The profile sets the default, a placement may override it. |
|
||||
| **Sentry** | Stationary: turns and shoots, never walks. The only role allowed off the navmesh. |
|
||||
| **Guard** | Holds a point or an entity; chases only to its leash, then returns. |
|
||||
| **Patrol** | A roamer on `route:<name>` (D233): walks a route recorded in game (`/rnpc path record`); fights and resumes (stage 5). |
|
||||
| **Escort** | Follows an entity or player; defends it. |
|
||||
| **Patrol** | A roamer on `route:<name>` (D233): walks a route recorded in game (`/rnpc path record`); fights, then returns to the point it was heading to (D262, stage 5). |
|
||||
| **Escort** | Follows an entity or player; defends it. Its target comes from an event step, the API, or `/rnpc follow` (D258). |
|
||||
| **Boss** | Any of the above, plus a health bar, phases and announcements (§3.3). |
|
||||
| **Passive** | Never fights; press E to talk. Quest-givers and vendors. |
|
||||
|
||||
**Factions** are a relation table between profiles, plus optional ties to a team or clan: *hostile*, *neutral*
|
||||
or *allied*. Rust's scientists are one more faction, so a profile can be told to leave them alone.
|
||||
**Factions** are named (D254): each profile names one, and a table says how factions treat each other, *hostile*,
|
||||
*neutral* or *allied*. Rust's scientists and animals are two built-in factions. A profile may list exceptions,
|
||||
which win over its faction's relations. With no faction settings a profile fights players only (D255). An
|
||||
NPC can also be allied to a clan or a Rust team: it never targets the members, and it defends them and what they
|
||||
own (D257). Allies within a profile's alert radius join a fight one of them is in (D256).
|
||||
|
||||
**Zone tethering** keeps an NPC inside a ZoneManager zone. At the edge it turns back, and if it ends up outside (it
|
||||
was pushed, or the zone moved) it is returned home. ZoneManager is already required by `module-rust`; to RunicNPC it
|
||||
is optional, and a profile that asks for a tether on a server without it is refused at save.
|
||||
is optional. **The tether is a placement's (`tether=<zone>`) or an event step's, not a profile's (D272)**, because a
|
||||
zone belongs to one server; a placement that names a tether on a server without ZoneManager, or a zone it does not
|
||||
have, is refused when it is set.
|
||||
|
||||
---
|
||||
|
||||
@@ -487,7 +513,7 @@ it. That is the other reason our NPC must never be saved (§2).
|
||||
| The prefab's own values | Sense 30 m, lose target 40 m, vision cone −0.8, listen 10 m, memory 10 s, line-of-sight checks on, hostile-only off, senses players only, health 150, aim cone 2. |
|
||||
| Roaming | **Rust's roam only follows an AI zone's move points** (and so does its chase: stage 2). In a monument's zone (Desert Military Base) ours roamed 36–47 m in 60 s. In an open field it stood still (0 m). **A roamer anywhere but a monument needs our own roam state, so stage 2's roamer role includes one.** |
|
||||
| Sleep | An NPC outside an AI zone is never put to sleep, and `ai_dormant` does not apply to this AI. So an idle NPC keeps thinking with nobody near, which is what the costs below measure. |
|
||||
| **Fighting NPCs** | **Rust's scientist AI never attacks an NPC.** `HumanNPC.IsTarget` is true only for non-NPC players, pets and scarecrows. `IsFriendly` means "same prefab id", which the swap copies, so every stock scientist counts ours as a friend. Re-implementing Rust's internal `IAISenses` puts a scientist in our NPC's target list. But Rust's AI design never runs its attack event for an NPC target. Calling `AttackTick` directly passed line of sight every time and still fired no shot, because the design's cover and facing states win. **NPC-versus-NPC combat needs our own combat state, and sensing NPCs needs our own sensing** (see the cost below). D231 makes it a profile setting, so stage 5 opens with its own spike. |
|
||||
| **Fighting NPCs** | **Rust's scientist AI never attacks an NPC.** `HumanNPC.IsTarget` is true only for non-NPC players, pets and scarecrows. `IsFriendly` means "same prefab id", which the swap copies, so every stock scientist counts ours as a friend. Re-implementing Rust's internal `IAISenses` puts a scientist in our NPC's target list. But Rust's AI design never runs its attack event for an NPC target. Calling `AttackTick` directly passed line of sight every time and still landed no hit. (Stage 5 found why: Rust's bullets skip any NPC an NPC shoots at, unless its faction is `Horror`. It was not the design's cover and facing states, as this row first said.) **NPC-versus-NPC combat needs our own combat state, and sensing NPCs needs our own sensing** (see the cost below). D231 makes it a profile setting, so stage 5 opens with its own spike. |
|
||||
|
||||
**4. Navmesh.** Rust's scientists walk **Rust's own navmesh** (its Gen2 `RustNavMeshAgent`), not Unity's: a Unity
|
||||
`NavMesh` query found nothing in the open world. **The placement check is `Rust.Ai.Gen2.RustNavMeshHelpers.
|
||||
@@ -786,9 +812,191 @@ What building it found:
|
||||
Guard, patrol and escort roles; factions (between profiles, with Rust's scientists, and with teams or clans); group
|
||||
alert; zone tethering; turrets; the weapon behaviours a kit's items imply; TruePVE compatibility.
|
||||
|
||||
The org lead's answers on 2026-09-30 are D253–D262. **It is one stage, walked once end to end (D253)**, with the
|
||||
pieces below. Its wire changes join protocol 13 while it is unreleased.
|
||||
|
||||
- **The spike first (D231, D253).** A harness answers what the rest depends on, on both rigs:
|
||||
- our own sensing and combat state against an NPC target: does it shoot, with the kit weapon, and hit;
|
||||
- the cost of sensing only the factions a profile is hostile to, nearest first, against stage 1's 100² line of
|
||||
sight tests (17.7–20.4 ms per think);
|
||||
- what Rust's auto turrets, flame turrets and shotgun traps do to a stock scientist and to ours (D259);
|
||||
- what TruePVE's and NextGenPVE's hook sees for our NPC (D261);
|
||||
- which kit items Rust's AI already uses on its own (D260).
|
||||
- **RunicNPC (API 4):**
|
||||
- the faction table and each profile's `faction`, exceptions, alert radius, turret mode, PVE opt-out and kit
|
||||
behaviours, in the profile file and through `RunicNpc_SetProfiles` (D254–D261);
|
||||
- the guard role (holds a point or an entity, chases to its leash, returns), escort (D258) and patrol resume
|
||||
(D262);
|
||||
- sensing and combat against NPC and animal targets, for the factions that ask for it (D255);
|
||||
- group alert (D256) and clan or team allies (D257);
|
||||
- zone tethering through ZoneManager, optional;
|
||||
- turret behaviour (D259), the opted-in kit behaviours (D260), and the PVE hooks (D261);
|
||||
- `/rnpc follow` and `rnpc.faction` for standalone servers.
|
||||
- **Rust-Plugins (the bridge):** the faction table pushed with the profiles; `world.place` with an escort target
|
||||
and a clan or team ally for an event's NPCs.
|
||||
- **Module-Rust:** the profile form's new fields, a faction table on the NPC profiles page, and the Place NPCs
|
||||
step's escort target and ally.
|
||||
|
||||
**Tested by** harness fights between profiles on the rig (who shoots whom, with what, and within which leash), and
|
||||
an in-game walk for escort and faction ties to a clan.
|
||||
|
||||
**Measured (2026-10-01, `tools/RunicNpcHarness.cs` 0.2.0: `rnh.npcfight`, `rnh.sense`, `rnh.turrets`, `rnh.items`,
|
||||
`rnh.pve`).** Both rigs, the same 6000 map as stage 1. Costs were measured on one rig at a time, with the other
|
||||
stopped. Nobody was connected; where a player was needed, the harness used stage 1's stand-ins. The prototype combat
|
||||
state lives in the harness, not in RunicNPC. **Every answer held on both frameworks** unless it says otherwise.
|
||||
|
||||
*The prototype.* Our sensing reads only the factions an NPC is hostile to, from a registry the plugin keeps as
|
||||
scientists, animals and our NPCs spawn and die. It costs no physics query, only distances. It sorts the nearest three
|
||||
and tests line of sight on those alone, once a second. Rust's own design still owns every fight with a player. Only when
|
||||
it has none does our state take over: it faces the target, closes in until the target is in range and in sight, and hands
|
||||
Rust's own `TickAttack` the target, so the shot is still Rust's.
|
||||
|
||||
**1. Why stage 1's NPC never hit an NPC, and what fixes it.**
|
||||
|
||||
- **Rust drops the hit, not the shot.** `BaseProjectile.ServerUse` skips every hit where **the shooter is an NPC and
|
||||
the victim is an NPC**, unless the victim's faction is `Horror` (pets excepted). Animals count as NPCs too. Stage 1
|
||||
read the missing hits as "the design's cover and facing states win". That was wrong: our NPC fired 79–96 times in
|
||||
30 s at a stock scientist, and hit it 0 times.
|
||||
- **`faction` is a plain field that nothing else in Rust reads.** `GetFaction()` is not virtual and is used only by that
|
||||
check. So our NPC marks **its own target, and only for the length of its own shot**: it sets the target's faction to
|
||||
`Horror` inside its `ShotTest` and `TriggerDown`, then puts it back. Nothing changes for anyone else's bullets.
|
||||
- **One trap, found and fixed.** `ShotTest` calls `TriggerDown` inside itself, so the mark nests. The first run let the
|
||||
inner call save `Horror` as the value to put back. That left targets `Horror` for good, and allies then hit each other
|
||||
about 475 times in a 50-against-50 fight. A mark is now made only when none is in place. After the fix, no entity was
|
||||
ever left `Horror` (counted after every phase).
|
||||
|
||||
| Fight (15 m, the kit's revolver, 60 s) | Oxide | Carbon |
|
||||
|---|---|---|
|
||||
| Ours → stock scientist, no mark | 0 hits in 30 s | 0 hits in 30 s |
|
||||
| Ours → stock scientist, marked per shot | killed in 44 s, 29 hits | killed in 48 s, 29 hits |
|
||||
| Ours ↔ ours (two profiles) | both hit; one died at 41 s | both hit; one died at 35 s |
|
||||
| Ours → boar | it fought back and killed ours (27 s) | the same (25 s) |
|
||||
| Ours → wolf | 25 hits in 90 s, wolf at 12/150; it never attacked | killed in 43 s, 28 hits; it never attacked |
|
||||
| Ally of ours standing on the line of fire | **0 hits on the ally**; the scientist died at 47 s | **0 hits**; died at 39 s |
|
||||
|
||||
- **A stock scientist never shoots back at ours.** Rust's `HumanNPC.IsTarget` is true only for players, pets and
|
||||
scarecrows, and it is not virtual. Shot by ours, the scientist takes cover and does nothing else. **One-sided, as
|
||||
D263 decided.**
|
||||
- **Fire is not filtered.** Rust's NPC-to-NPC check exists only for bullets. An NPC with a flamethrower burned a
|
||||
polar bear that walked in, and would burn allied NPCs the same way. **It stays that way (D266).**
|
||||
|
||||
**2. What our sensing costs.** Thirty seconds a phase, after five to settle. The frame times include the world's own AI.
|
||||
About 570 brains (Rust's scientists and animals) already queue for Rust's 2 ms AI budget on this map, which is why the
|
||||
empty baseline runs well under stage 1's 50 fps.
|
||||
|
||||
| Phase | Oxide: median frame | Oxide: ms per think / thinks every | Carbon: median frame | Carbon: ms per think / thinks every |
|
||||
|---|---|---|---|---|
|
||||
| Baseline, empty | 22.8 ms | — | 21.2 ms | — |
|
||||
| 100 of ours, hostile to scientists, none near | 23.2 | 0.18 / 0.8 s | 22.7 | 0.12 / 0.6 s |
|
||||
| 100 of ours fighting 100 stock scientists | 27.9 | **2.5** / 7.3 s | 24.6 | **2.4** / 6.3 s |
|
||||
| 50 of ours fighting 50 of ours | 25.9 | **1.9** / 4.8 s | 23.2 | **1.5** / 3.6 s |
|
||||
| Empty again | 24.2 | — | 21.0 | — |
|
||||
|
||||
- **About 7–10× cheaper than Rust's own sensing.** Stage 1 measured 17.7–20.4 ms per think for 100 NPCs sensing NPCs
|
||||
Rust's way. Ours costs 1.5–2.5 ms per think, gunfire included: about what fighting players cost in stage 1 (2.4–2.5 ms).
|
||||
- **One sense costs 0.1 ms with nothing near, and 0.5–1.1 ms in a fight.** Nearly all of that is the two line-of-sight
|
||||
tests; the distance scan over 50–270 candidates is small.
|
||||
- **Rust's AI budget again sets how quickly they react, not the frame rate.** With 100 fighting on top of the world's
|
||||
AI, each NPC thinks every 3.5–7.5 s. The cost warning (D227) gets a line for NPCs that fight NPCs, as stage 1's has one
|
||||
for fighting players.
|
||||
|
||||
**3. Turrets and traps (D259).** Each turret was armed with Rust's own inventory (an AK and ammunition, fuel, shells).
|
||||
It faced a stand-in player as the control, then a stock scientist, then ours. Every target was spawned outside the
|
||||
turret's trigger and moved in, because a flame turret only notices what *enters* its trigger.
|
||||
|
||||
| | Stand-in player | Stock scientist | Ours |
|
||||
|---|---|---|---|
|
||||
| Auto turret | 4 hits, 70 damage in 30 s | the same | the same; ours shot back: 20 hits, **11 damage** to the turret's 1,000 |
|
||||
| Flame turret | burned (196–225 hits) | dead in 7–8 s | dead in 8–10 s; ours shot back, 5–8 hits |
|
||||
| Shotgun trap | 16 pellets a shot | dead in 2–3 s | dead in 2.5 s; it answered with one shot on Oxide, none on Carbon |
|
||||
|
||||
- **Player turrets already treat ours exactly as they treat Rust's scientists**, so D259's `default` needs no code.
|
||||
Outpost and bandit-camp sentries (`NPCAutoTurret`) ignore every `ScientistNPC`, and ours is one, so they ignore ours
|
||||
as they do Rust's.
|
||||
- **Shooting back works, and a revolver barely scratches a turret.** A flame turret's damage arrives through its
|
||||
fireballs, so "who hurt me" follows the fireball's creator back to the turret.
|
||||
- `ignore` has Rust's own hook to use: `CanBeTargeted` for the auto turret, the flame turret and the trap.
|
||||
NextGenPVE uses the same hook. **`always` adds the safe-zone sentries (D264).**
|
||||
|
||||
**4. The kit items Rust's AI uses by itself (D260).** Ours carried the kit's revolver, 3 syringes, 2 grenades and a
|
||||
machete, with Rust's `CanUseHealingItems` switched on. A stand-in "shot" it 20 damage every 4 s, down to 37 health.
|
||||
Then the main weapon was swapped for a rocket launcher, and then for a flamethrower.
|
||||
|
||||
- **Healing: never, in 60 s on either rig.** Rust heals only when it *enters* its cover state at an AI zone's cover
|
||||
point, below a health fraction, on a random chance, and out of its target's sight. In open ground that never
|
||||
happens.
|
||||
- **Grenades and melee: never.** In Rust, only scarecrows throw. No scientist code ever switches to a melee weapon.
|
||||
- **A rocket launcher held as the main weapon: never fired.** Rust's design stood in combat with it and did not call an
|
||||
attack once.
|
||||
- **A flamethrower as the main weapon works** (515–539 hits on the stand-in in 40 s).
|
||||
- **Nothing is consumed.** 151 attempts to fire the revolver (31 hits) and 40 s of flame left the inventory's ammunition and fuel untouched,
|
||||
because an NPC reloads for free.
|
||||
|
||||
So **every behaviour D260 lists is ours to write**: healing, grenades, melee, rockets, and the flamethrower's range.
|
||||
**The main weapon's ammunition stays endless (D265).**
|
||||
|
||||
**5. TruePVE and NextGenPVE (D261).** Each damage direction was probed with a 10-damage hit, each plugin on its default
|
||||
rules. The harness answered `CanEntityTakeDamage` three ways: not at all, "allow", and "deny". The PVE stand-ins
|
||||
carry Steam-range ids, because both plugins treat only Steam ids as players.
|
||||
|
||||
| | Player → player (the control) | Player ↔ ours | Player ↔ stock scientist | Ours ↔ stock scientist |
|
||||
|---|---|---|---|---|
|
||||
| No PVE plugin | hurts | hurts | hurts | hurts |
|
||||
| TruePVE 2.4.4 (uMod) | **blocked** | hurts; "deny" blocks it | hurts | hurts; "deny" blocks it |
|
||||
| NextGenPVE 1.8.2 (Oxide) | **blocked** | hurts; **"deny" ignored** | hurts | hurts; "deny" ignored |
|
||||
| NextGenPVE 1.8.2 (Carbon) | hurts: it failed to start (`SQLite.Interop` missing on this host) | hurts | hurts | hurts |
|
||||
|
||||
- **Both plugins' defaults already treat ours as Rust's scientists, both ways**, so D261's "allowed" changes nothing on
|
||||
default rules. It protects ours from stricter rule sets.
|
||||
- **Both plugins ask `CanEntityTakeDamage` once per hit. TruePVE honours `true` and `false`; NextGenPVE honours only
|
||||
`true`.** D261's per-profile opt-out therefore cannot rely on that hook. RunicNPC does it itself, in places no PVE
|
||||
plugin touches: an unkillable profile zeroes its damage in the NPC's own `Hurt`, and a harmless profile sets the NPC's
|
||||
`damageScale` (Rust's own multiplier on an NPC's shots) to 0.
|
||||
- NextGenPVE is not on uMod. Its source is on GitHub (`Remod-org/NextGenPVE`, GPL-2.0). Both plugins were removed from
|
||||
both rigs afterwards, with their config, data and language files.
|
||||
|
||||
**6. Two notes on the harness itself.** A turret or trap spawned by the harness sometimes vanished the moment its
|
||||
victim's body landed beside it. That looks like Rust's ground check on a deployable that was never placed by a player,
|
||||
not anything an NPC did. The world's own wolves and a polar bear wandered into two scenarios; the tables count only the
|
||||
entities the harness labelled.
|
||||
|
||||
**What the answers left open, and the org lead's answers (2026-10-01):**
|
||||
|
||||
1. **Rust's scientists cannot fight back against ours. → D263:** the fight stays one-sided; the scientist takes cover.
|
||||
A profile hostile to `scientists` fights only the scientists that come within its own leash, and never goes into a
|
||||
monument to look for them.
|
||||
2. **What `turrets: always` means. → D264:** the safe-zone sentries too. Outpost's and Bandit Camp's turrets shoot that
|
||||
profile's NPCs as they shoot a hostile player.
|
||||
3. **Ammunition. → D265:** endless, as in Rust. Only D260's extras are used up.
|
||||
4. **Fire and allies. → D266:** fire burns as it does in Rust, allies included.
|
||||
|
||||
What this adds to the build (D263–D266):
|
||||
|
||||
- **Sensing (D263):** a profile's hunt for `scientists`, `animals` or another profile is limited by its own leash
|
||||
around its spot (its chase range, D233). Our sensing never drives an NPC towards a target beyond it, so it never
|
||||
walks into a monument to look for scientists.
|
||||
- **Turrets (D264):** `default` needs no code. `ignore` answers `CanBeTargeted` with "no" for the auto turret, flame
|
||||
turret and trap. `always` makes Outpost's and Bandit Camp's sentries (`NPCAutoTurret`) target it, which Rust's own
|
||||
`Ignore` and `IsEntityHostile` refuse for every `ScientistNPC`. Stage 5's build finds the hook that reaches those
|
||||
two, or answers for the sentry from RunicNPC's side, and says which in this plan. **No hook reaches them: Rust's
|
||||
`AutoTurret.TargetScan` calls `Ignore` before anything Oxide or Carbon injects, so it is a Harmony patch (D267).**
|
||||
- **Ammunition (D265) and fire (D266):** nothing to build. The NPC's reload stays Rust's own, and fire damage is not
|
||||
touched.
|
||||
|
||||
**The build's own questions, answered by the org lead on 2026-10-01 (D267–D272):**
|
||||
|
||||
1. **How a sentry is made to shoot an `always` NPC. → D267:** a Harmony patch on `NPCAutoTurret.Ignore` and
|
||||
`IsEntityHostile`, for our `always` NPCs only. If the walk shows a sentry targeting Rust's own scientists or
|
||||
bandit guards, the sentry half of `always` is dropped.
|
||||
2. **Whether a faction pair runs both ways. → D268:** one row, both ways; and any of ours shot fights back.
|
||||
3. **Escort as a role or an order, and what happens when the target is gone. → D269:** an order any roamer or guard
|
||||
takes; they walk back to their spot when the target dies or leaves.
|
||||
4. **How an event step names the escort and the ally. → D270:** a Steam id (or `{placeholder}`) for the escort; a clan
|
||||
from the site's list, or the team of a player, for the ally.
|
||||
5. **The PVE opt-out's shape. → D271:** two checkboxes, "players can hurt it" and "it can hurt players", enforced by
|
||||
RunicNPC itself.
|
||||
6. **Where a tether is set. → D272:** on a placement and on an event step, not on a profile.
|
||||
|
||||
### Stage 6 — Roles
|
||||
|
||||
Bosses (a health bar drawn with CUI, phases at thresholds that swap profile values, announcements through the bridge
|
||||
|
||||
Reference in New Issue
Block a user