Table of Contents

Class NPC

Namespace
FishMMO.Shared
Assembly
FishMMO.Shared.dll

Represents a non-player character (NPC) in the game. Handles attribute generation, network payloads, and spawning logic.

[RequireComponent(typeof(AIController))]
[RequireComponent(typeof(CharacterPredictionController))]
[RequireComponent(typeof(AbilityController))]
[RequireComponent(typeof(CooldownController))]
[RequireComponent(typeof(BuffController))]
[RequireComponent(typeof(CharacterAttributeController))]
[RequireComponent(typeof(CharacterDamageController))]
[RequireComponent(typeof(FactionController))]
[RequireComponent(typeof(NetworkTransform))]
[RequireComponent(typeof(NetworkObserver))]
public class NPC : BaseCharacter, ICharacter, ISpawnable, ILootableCorpse, IInteractable, ISceneObject
Inheritance
Object
Component
Behaviour
MonoBehaviour
NetworkBehaviour
NPC
Implements
Derived
Inherited Members
NetworkBehaviour.IsSpawned
NetworkBehaviour.ComponentIndex
NetworkBehaviour.NetworkObject
NetworkBehaviour.MAXIMUM_NETWORKBEHAVIOURS
NetworkBehaviour.UNSET_NETWORKBEHAVIOUR_ID
NetworkBehaviour.ToString()
NetworkBehaviour.Reset()
NetworkBehaviour.IsBehaviourReconciling
NetworkBehaviour.ClearReplicateCache()
NetworkBehaviour.CreateReconcile()
NetworkBehaviour.Reconcile_Reader<T>(PooledReader, ref T)
NetworkBehaviour.OnStartServerCalled
NetworkBehaviour.OnStartClientCalled
NetworkBehaviour.OnStartNetwork()
NetworkBehaviour.OnStopNetwork()
NetworkBehaviour.OnStopServer()
NetworkBehaviour.OnOwnershipServer(NetworkConnection)
NetworkBehaviour.OnSpawnServer(NetworkConnection)
NetworkBehaviour.OnDespawnServer(NetworkConnection)
NetworkBehaviour.OnStartClient()
NetworkBehaviour.OnStopClient()
NetworkBehaviour.OnOwnershipClient(NetworkConnection)
NetworkBehaviour.ClearBuffedRpcs()
NetworkBehaviour.ExcludeOwnerFromUnbufferedObserversRpcs
NetworkBehaviour.IsClientOnly
NetworkBehaviour.IsServerOnly
NetworkBehaviour.IsHost
NetworkBehaviour.IsClient
NetworkBehaviour.IsServer
NetworkBehaviour.IsDeinitializing
NetworkBehaviour.NetworkManager
NetworkBehaviour.ServerManager
NetworkBehaviour.ClientManager
NetworkBehaviour.ObserverManager
NetworkBehaviour.TransportManager
NetworkBehaviour.TimeManager
NetworkBehaviour.SceneManager
NetworkBehaviour.PredictionManager
NetworkBehaviour.RollbackManager
NetworkBehaviour.NetworkObserver
NetworkBehaviour.IsClientInitialized
NetworkBehaviour.IsClientStarted
NetworkBehaviour.IsClientOnlyInitialized
NetworkBehaviour.IsClientOnlyStarted
NetworkBehaviour.IsServerInitialized
NetworkBehaviour.IsServerStarted
NetworkBehaviour.IsServerOnlyInitialized
NetworkBehaviour.IsServerOnlyStarted
NetworkBehaviour.IsHostInitialized
NetworkBehaviour.IsHostStarted
NetworkBehaviour.IsOffline
NetworkBehaviour.IsNetworked
NetworkBehaviour.GetIsNetworked()
NetworkBehaviour.IsManagerReconciling
NetworkBehaviour.Observers
NetworkBehaviour.IsOwner
NetworkBehaviour.IsController
NetworkBehaviour.HasAuthority
NetworkBehaviour.Owner
NetworkBehaviour.OwnerId
NetworkBehaviour.ObjectId
NetworkBehaviour.LocalConnection
NetworkBehaviour.OwnerMatches(NetworkConnection)
NetworkBehaviour.Despawn(GameObject, DespawnType?)
NetworkBehaviour.Despawn(NetworkObject, DespawnType?)
NetworkBehaviour.Despawn(DespawnType?)
NetworkBehaviour.Spawn(GameObject, NetworkConnection, Scene)
NetworkBehaviour.Spawn(NetworkObject, NetworkConnection, Scene)
NetworkBehaviour.RemoveOwnership()
NetworkBehaviour.GiveOwnership(NetworkConnection)
NetworkBehaviour.GetInstance<T>()
NetworkBehaviour.TryRegisterInstance<T>(T)
NetworkBehaviour.UnregisterInstance<T>()
NetworkBehaviour.CanLog(LoggingType)
MonoBehaviour.IsInvoking()
MonoBehaviour.CancelInvoke()
MonoBehaviour.StopCoroutine(Coroutine)
MonoBehaviour.StopAllCoroutines()
MonoBehaviour.destroyCancellationToken
MonoBehaviour.useGUILayout
MonoBehaviour.didStart
MonoBehaviour.didAwake
MonoBehaviour.runInEditMode
Behaviour.enabled
Behaviour.isActiveAndEnabled
Component.GetComponent<T>()
Component.TryGetComponent<T>(out T)
Component.GetComponentInChildren<T>()
Component.GetComponentsInChildren<T>()
Component.GetComponentInParent<T>()
Component.GetComponentsInParent<T>()
Component.GetComponents<T>()
Component.GetComponentIndex()
Component.CompareTag(TagHandle)
Component.transform
Component.transformHandle
Component.gameObject
Component.tag
Object.GetEntityId()
Object.GetInstanceID()
Object.GetHashCode()
Object.InstantiateAsync<T>(T)
Object.InstantiateAsync<T>(T, Transform)
Object.InstantiateAsync<T>(T, Vector3, Quaternion)
Object.InstantiateAsync<T>(T, Transform, Vector3, Quaternion)
Object.Instantiate(Object, Vector3, Quaternion)
Object.Instantiate(Object, Vector3, Quaternion, Transform)
Object.Instantiate(Object)
Object.Instantiate(Object, Scene)
Object.Instantiate<T>(T, InstantiateParameters)
Object.Instantiate<T>(T, Vector3, Quaternion, InstantiateParameters)
Object.Instantiate(Object, Transform)
Object.Instantiate<T>(T)
Object.Instantiate<T>(T, Vector3, Quaternion)
Object.Instantiate<T>(T, Vector3, Quaternion, Transform)
Object.Instantiate<T>(T, Transform)
Object.Destroy(Object)
Object.DestroyImmediate(Object)
Object.DontDestroyOnLoad(Object)
Object.DestroyObject(Object)
Object.FindObjectsOfType<T>()
Object.FindObjectsByType<T>(FindObjectsSortMode)
Object.FindObjectsByType<T>(FindObjectsInactive, FindObjectsSortMode)
Object.FindObjectOfType<T>()
Object.FindFirstObjectByType<T>()
Object.FindAnyObjectByType<T>()
Object.FindFirstObjectByType<T>(FindObjectsInactive)
Object.FindAnyObjectByType<T>(FindObjectsInactive)
Object.FindObjectsByType<T>()
Object.FindObjectsByType<T>(FindObjectsInactive)
Object.name
Object.hideFlags
Extension Methods

Fields

Abilities

Ability templates this NPC can use. Populated in the inspector. Each template is learned as an Ability instance during OnStartServer(), before clients receive the spawn payload.

[Header("Abilities")]
[Tooltip("Ability templates this NPC knows. Learned on server start.")]
public List<AbilityTemplate> Abilities

Field Value

List<AbilityTemplate>

AttributeBonuses

Database of attribute bonuses for this NPC.

public NPCAttributeDatabase AttributeBonuses

Field Value

NPCAttributeDatabase

CorpseDecayDuration

[Header("Corpse Decay")]
[Tooltip("Seconds the corpse remains visible after death before returning to the object pool.")]
public float CorpseDecayDuration

Field Value

float

CorpseInteractionRange

How close a player must stand to loot this NPC's corpse.

[Tooltip("Distance within which a player may loot this NPC's corpse.")]
public float CorpseInteractionRange

Field Value

float

EmptyCorpseDecayDuration

Seconds an empty corpse remains before returning to the object pool.

[Tooltip("Seconds an empty corpse remains before returning to the pool. Only ever shortens the decay, never extends it.")]
[Min(0)]
public float EmptyCorpseDecayDuration

Field Value

float

Remarks

A body with nothing on it is scenery. The full decay duration exists to give the people who earned the kill time to walk over and take their loot, and none of that applies once there is nothing left to take — or once it turns out there never was, which is the case for every NPC killed by another NPC, by the environment, or by a player with no loot table configured. Left on the full timer those bodies accumulate: a busy zone ends up carpeted in corpses that no one can interact with, each still spawned, still observed by every client in range, and still holding its spawner's slot.

Only ever shortens a corpse's remaining life, never extends it, so setting this longer than CorpseDecayDuration simply has no effect.

Keep it comfortably longer than the death animation, or bodies will pop out of the world mid-collapse.

IsCharmable

If true, this NPC can be charmed by players.

public bool IsCharmable

Field Value

bool

LootTable

[Header("Loot")]
[Tooltip("What this NPC's corpse may hold. Rolled once, on the server, at the moment of death.")]
public LootTableTemplate LootTable

Field Value

LootTableTemplate

MaximumRespawnTime

Longest time in seconds before this NPC respawns after its corpse decays.

[Tooltip("Longest time in seconds before this NPC respawns after its corpse decays. A spawner may override it.")]
[Min(0)]
public float MaximumRespawnTime

Field Value

float

Remarks

The prefab is the right home for these because respawn cadence is a property of the creature, not of the patch of ground it stands on: a wolf should come back at wolf pace wherever it is placed, and a rare spawn should stay rare at every spawner that can produce it. Before this, the values lived only on NPCSpawnableSettings and defaulted to zero — so any spawner whose author did not fill them in respawned its creatures instantly, forever.

A spawner can still override the pair for a specific placement — see NPCSpawnableSettings.MinimumRespawnTime.

MinimumRespawnTime

[Header("Respawn")]
[Tooltip("Shortest time in seconds before this NPC respawns after its corpse decays. A spawner may override it.")]
[Min(0)]
public float MinimumRespawnTime

Field Value

float

Properties

HasLoot

True while the corpse still holds an item or any currency.

public bool HasLoot { get; }

Property Value

bool

InteractRateLimit

Minimum milliseconds between interactions with this object from one character.

public double InteractRateLimit { get; }

Property Value

double

IsCorpse

True while this NPC is a lootable corpse.

public bool IsCorpse { get; }

Property Value

bool

Remarks

Answers from whichever source is authoritative for the peer asking. The server owns FishMMO.Shared.NPC.isCorpse outright; a client has no copy of it and reads the replicated dead flag instead, which arrives live by observers RPC and in the spawn payload for anyone who turns up later. Without the client arm this property is simply false on every client, and the corpse is never offered as an interaction target at all.

LootCurrency

Currency remaining on the corpse.

public long LootCurrency { get; }

Property Value

long

LootItems

The corpse's item slots. Emptied slots read null and keep their index.

public IReadOnlyList<Item> LootItems { get; }

Property Value

IReadOnlyList<Item>

LootViewers

Connections currently viewing this corpse's loot.

public IReadOnlyCollection<NetworkConnection> LootViewers { get; }

Property Value

IReadOnlyCollection<NetworkConnection>

ObjectSpawner

Reference to the spawner that created this NPC.

public ObjectSpawner ObjectSpawner { get; set; }

Property Value

ObjectSpawner

OnInteractTriggers

ECA triggers invoked server-side when a player successfully interacts with this object. Configure these in the Inspector to define per-object interaction behaviour without code.

public List<Trigger> OnInteractTriggers { get; }

Property Value

List<Trigger>

RNG

Exposes the seeded RNG for deterministic AI decisions. All AI subsystems should use this instead of Shared so that behaviour is reproducible given the same seed.

public DeterministicRNG RNG { get; }

Property Value

DeterministicRNG

SpawnableSettings

Settings used when spawning this NPC.

public SpawnableSettings SpawnableSettings { get; set; }

Property Value

SpawnableSettings

Title

The interaction title. Empty while alive, so a living NPC is not advertised as interactable and its world label is left to the naming components.

public virtual string Title { get; }

Property Value

string

TitleColor

The colour of the corpse title in the world UI.

public virtual Color TitleColor { get; }

Property Value

Color

Methods

AddLootViewer(NetworkConnection)

Records that a connection has the loot window open on this corpse.

public void AddLootViewer(NetworkConnection connection)

Parameters

connection NetworkConnection

The viewing connection.

CanInteract(IPlayerCharacter)

Returns true when the given player may open this NPC's loot window.

public virtual bool CanInteract(IPlayerCharacter character)

Parameters

character IPlayerCharacter

Returns

bool

Remarks

Runs on both sides and answers differently on each, deliberately. The client knows only that the NPC is dead, which is all it needs to decide whether to send a request; the server additionally holds the contributor snapshot and is the only place eligibility is actually enforced. A client that lies here gets a refusal, not loot.

Pure, like every other CanInteract(IPlayerCharacter). The corpse rate limit is spent through TryConsumeInteractRateLimit(IPlayerCharacter) instead.

Despawn()

Enters corpse state or returns to pool. On first call after death, the NPC becomes a corpse (visible, immobile, immortal, and lootable) for CorpseDecayDuration seconds. After the timer expires, the object is returned to FishNet's pool for reuse.

public virtual void Despawn()

Remarks

Called from the server's OnKilled subscriber. Everything it does is server state; clients learn that the NPC is a corpse from IsDead, which travels both live (an observers RPC from the damage controller) and in the spawn payload for anyone who arrives afterwards.

ExecuteOnInteract(EventData)

Fires all OnInteractTriggers with the provided event data. Called by FishMMO.Server.Implementation.World.SceneServer.Interactable.InteractableSystem after server-side validation succeeds.

public bool ExecuteOnInteract(EventData eventData)

Parameters

eventData EventData

Context for this interaction (initiator, interactable reference, etc.).

Returns

bool

True when at least one trigger was fired.

Remarks

Unlike every other interactable, an NPC with no triggers is not a misconfiguration: looting is wired directly into the interaction handler precisely so that a creature with an empty list is still lootable. Triggers here are the optional extras — an achievement, a quest update, a line of dialogue on the body — so an empty list is silent.

InRange(Transform)

Returns true if the specified transform is within interaction range of this object.

public bool InRange(Transform transform)

Parameters

transform Transform

The transform to check range against.

Returns

bool

True if in range, false otherwise.

IsEligibleLooter(long)

Returns true if the given character earned loot rights to this corpse.

public bool IsEligibleLooter(long characterID)

Parameters

characterID long

The character ID to test.

Returns

bool

OnAwake()

Called when the NPC is awakened. Handles name cleanup and registration.

public override void OnAwake()

OnDestroying()

Called when the NPC is destroyed. Unregisters from the scene object registry.

public override void OnDestroying()

OnStartServer()

Called when the server starts for this NPC. Runs on every spawn including pool reuse. Re-rolls the seed, RNG, gender, and name. Then applies attribute bonuses and learns abilities. Spawner overrides (AttributeBonuses, CorpseDecayDuration) are injected before this runs.

public override void OnStartServer()

Remarks

Deliberately not wrapped in #if UNITY_SERVER. FishNet only calls this on a peer that is actually running a server, so the compile-time gate bought nothing — and it cost a great deal: in an editor or host build (where UNITY_SERVER is undefined) the override did not exist, so no NPC ever rolled its RNG, applied its attribute bonuses, or learned a single ability. NPC combat could not be exercised anywhere except a dedicated server build.

OnValidate()

Keeps the respawn range orderable in the inspector.

protected virtual void OnValidate()

Remarks

An inverted range is not merely odd: the RNG returns the minimum whenever min >= max, so a prefab authored 60-to-30 would silently respawn at a fixed 60 seconds and never vary. Clamping here makes that impossible to author by accident.

ReadPayload(NetworkConnection, Reader)

Reads the NPC's payload from the network, including ID and attribute seed. Applies attributes and sets up model.

public override void ReadPayload(NetworkConnection connection, Reader reader)

Parameters

connection NetworkConnection

The network connection.

reader Reader

The network reader.

RemoveLootViewer(NetworkConnection)

Records that a connection has closed the loot window on this corpse.

public void RemoveLootViewer(NetworkConnection connection)

Parameters

connection NetworkConnection

The connection that closed it.

ResetState(bool)

Resets the NPC's state for object pool reuse. Clears RNG, spawner references, and client-side tracking.

public override void ResetState(bool asServer)

Parameters

asServer bool

Whether the reset is performed on the server.

ReturnLootCurrency(long)

Puts currency back onto the corpse after a failed grant.

public void ReturnLootCurrency(long amount)

Parameters

amount long

The amount to restore.

ReturnLootItem(Item, int)

Puts an item back into a slot it was taken from.

public bool ReturnLootItem(Item item, int slot)

Parameters

item Item

The item to restore.

slot int

The slot to restore it to.

Returns

bool

True when the item was restored.

Remarks

The take must be undoable because granting can fail after the corpse has already given the item up — a full inventory being the ordinary case. Without a way back, that item is destroyed rather than left on the corpse.

ReturnToPool()

Returns the NPC to the object pool immediately. Called when the corpse decay timer expires or on server shutdown.

public void ReturnToPool()

Remarks

Routes through the owning ObjectSpawner so a respawn is scheduled, and despawns directly when there is no spawner. The fallback matters: an NPC placed by script or adopted rather than spawned has no spawner, and the null-conditional this replaced meant such an NPC's corpse never decayed at all — it sat in the world as an immortal, AI-disabled, permanently lootable body that nothing would ever collect.

TryConsumeInteractRateLimit(IPlayerCharacter)

Spends the character's interact rate-limit, returning false when it is still cooling.

public bool TryConsumeInteractRateLimit(IPlayerCharacter character)

Parameters

character IPlayerCharacter

The interacting character.

Returns

bool

True when the limiter was free and has now been spent.

Remarks

Separate from CanInteract(IPlayerCharacter) so a caller that only needs to validate — the quest system, which already holds its own ingress guard — does not consume a budget belonging to the interaction path.

TryTakeLootCurrency(long, out long)

Takes up to maximum currency from the corpse.

public bool TryTakeLootCurrency(long maximum, out long amount)

Parameters

maximum long

The most the caller can accept. Must be positive.

amount long

Receives the amount actually taken.

Returns

bool

True when any currency was taken.

Remarks

Capped rather than all-or-nothing because the destination has a ceiling: a character's currency is an int, and a taker already near that ceiling must be able to take what fits and leave the rest on the body rather than either overflowing or being refused.

TryTakeLootItem(int, out Item)

Removes the item in the given slot and hands it to the caller.

public bool TryTakeLootItem(int slot, out Item item)

Parameters

slot int

The slot to empty.

item Item

Receives the item that was removed.

Returns

bool

True when a item was removed.

WritePayload(NetworkConnection, Writer)

Writes the NPC's payload to the network, including ID and attribute seed. Ensures deterministic attribute generation on clients.

public override void WritePayload(NetworkConnection connection, Writer writer)

Parameters

connection NetworkConnection

The network connection.

writer Writer

The network writer.

Events

OnCorpseExpired

Raised on the server when the corpse is about to leave the world, so open loot windows can be closed before the scene object ID they refer to stops resolving.

public event Action<ILootableCorpse> OnCorpseExpired

Event Type

Action<ILootableCorpse>