Gen2 scientists (scientist2) are invisible / drawn as animals

**Version:** 5.4.3, Rust build 24587531 (Aug 2026 forced update)

## Problem

Facepunch's Gen2 AI migration means vanilla monument/tunnel scientists now spawn as
`scientist2` — class `Rust.Ai.Gen2.ScientistNPC2 : Rust.Ai.Gen2.BaseNPC2 : BaseCombatEntity`.
They are **no longer BasePlayer-derived**, so Admin Radar's NPC pipeline misses them twice:

1. The entity-add path and cache-rebuild coroutine send all `BaseNPC2` to the **Animals**
cache, so Gen2 scientists only draw (unnamed, wrong color) when the animals filter is on.
2. `CacheNpc()` falls back to `CacheAnimal()` for anything that isn't `BasePlayer`.

Net effect: admins see no scientists at monuments/tunnels populated by Gen2 spawns.
(Old-style `ScientistNPC`, `TunnelDweller`, `UnderwaterDweller` still work — they remain
`BasePlayer`-derived.)

## Discriminator

`ScientistNPC2` is currently the only `BaseNPC2` subtype with `IsAnimal => false`
(crocodile/panther/tiger/wolf2 keep the default `true`), so keying on the concrete
`ScientistNPC2` type keeps the new Gen2 animals correctly bucketed as animals.

## Patch (attached, 5 hunks against stock 5.4.3)

1. Entity-add path: route `ScientistNPC2` into the NPC cache under `config.Core.NPC`
(before the `BaseNPC2` → Animals line; mirrors the TravellingVendor precedent).
2. `CacheNpc()`: new branch → `CacheGen2Scientist()` (structured after `CacheVendor`):
scientist hex color, localized "scientist" name (or ShortPrefabName with the
NpcPrefabName option), health from `BaseCombatEntity.health`, NPC distance gate,
fixed 1.8f box height (Gen2 has no `modelState`).
3. Cache-rebuild coroutine: `AddElementsToCache<ScientistNPC2>` into the NPC cache,
ordered before the `BaseNPC2` animal sweep (the shared entity pool is consumed
in order — the animal sweep would otherwise claim them).
4. Distance classifier: `ScientistNPC2` resolves to the NPC distance instead of Animal.

Running in production since 2026-08-07 on a 5000-size map with ~100 live scientist2;
no errors, Gen2 scientists draw identically to gen1.

--- AdminRadar.cs	(5.4.3 stock)
+++ AdminRadar.cs	(gen2 support)
@@ -175,6 +175,12 @@
                 {
                     return true;
                 }
+
+                // route to NPC cache before the BaseNpc/BaseNPC2 -> Animals fallback below.
+                if (config.Core.NPC && Add_Internal<ScientistNPC2, EntityInfo>(NPC, entity, EntityType.Npc))
+                {
+                    return true;
+                }
                 if (config.Core.Animals && Add_Internal<BaseNpc, EntityInfo>(Animals, entity, EntityType.Npc))
                 {
                     return true;
@@ -2420,6 +2426,14 @@
                         return;
                     }
 
+
+                    // branch it falls into the BasePlayer-less CacheAnimal() path below and renders as an animal.
+                    if (ei.entity is ScientistNPC2)
+                    {
+                        CacheGen2Scientist(ei);
+                        return;
+                    }
+
                     if (!(ei.entity is BasePlayer))
                     {
                         CacheAnimal(ei);
@@ -2467,6 +2481,52 @@
                 }
             }
 
+
+            // has no modelState/displayName like a BasePlayer NPC, so it needs its own draw path instead of the
+            // BasePlayer branch in CacheNpc(). Structured after CacheVendor(), the other non-BasePlayer NPC-bucket entity.
+            private void CacheGen2Scientist(EntityInfo ei)
+            {
+                try
+                {
+                    currType = EntityType.Npc;
+
+                    if (IsValid(ei))
+                    {
+                        var obj = SetDataObject(ei);
+
+                        if (SetDistance(ei.from) < config.Distance.NPC)
+                        {
+                            var target = ei.entity as BaseCombatEntity;
+                            var color = __(config.Hex.Scientist);
+                            var displayName = config.Options.NpcPrefabName ? target.ShortPrefabName : instance.m("scientist", userid);
+
+                            CacheText(obj, color, twoUp, () =>
+                            {
+                                ei.info = null;
+
+                                float dist = Distance(ei.from);
+                                if (dist > maxDistance || dist > config.Distance.NPC || !IsAtView(ei))
+                                {
+                                    return;
+                                }
+
+                                ei.info = Format(displayName, $"<color={config.Hex.Health}>{Mathf.CeilToInt(target.health)}</color> <color={config.Hex.Dist}>{dist}</color>");
+                            });
+
+                            CacheBox(obj, color, Vector3.up, 1.8f);
+                            CacheArrow(obj, color, new(0f, ei.from.y + 10), ei.from, 1, false);
+                        }
+                        else CacheBox(obj, Color.blue, Vector3.up, 5f, true);
+                    }
+
+                    checks++;
+                }
+                catch (Exception ex)
+                {
+                    HandleException(ex);
+                }
+            }
+
             private void DrawVictim(BasePlayer victim, Vector3 from, Vector3 offset, Color color)
             {
                 if (victim != null)
@@ -2841,6 +2901,10 @@
                     cached += cache.RidableHorse.Count;
                 }
 
+
+                // matched entities out of the shared _allEntities pool, so scientist2 (a BaseNPC2 subtype) needs to be
+                // claimed into cache.NPC first or the BaseNPC2 sweep will already have swept it into cache.Animals.
+                yield return CreateCoroutine(AddElementsToCache<ScientistNPC2>(_coroutineTimer, cache.NPC, EntityType.Npc));
                 yield return CreateCoroutine(AddElementsToCache<BaseNpc>(_coroutineTimer, cache.Animals, EntityType.Npc));
                 yield return CreateCoroutine(AddElementsToCache<BaseNPC2>(_coroutineTimer, cache.Animals, EntityType.Npc));
                 yield return CreateCoroutine(AddElementsToCache<WildlifeHazard>(_coroutineTimer, cache.Animals, EntityType.Npc));
@@ -5469,7 +5533,8 @@
                     case EntityType.Limit:
                     default:
                         {
-                            if (entity is BaseNpc or BaseNPC2 or FarmableAnimal or WildlifeHazard) return Animal;
+
+                            if (entity is not ScientistNPC2 && entity is BaseNpc or BaseNPC2 or FarmableAnimal or WildlifeHazard) return Animal;
                             if (entity is VendingMachine) return VendingMachine;
                             if (type == EntityType.Box) return Box;
                             if (type == EntityType.Npc) return NPC;
​

heya thank you. I am waiting on a specific hotfix then I will update Admin Radar :)