Table of Contents

Class CharacterSystem

Namespace
FishMMO.Server.Implementation.World.SceneServer
Assembly
FishMMO.Server.dll

Instance management: reporting who is in an instance and how long it has left, letting the party leader remove somebody from it, and letting them hide the run from the dungeon finder.

[CreateAssetMenu(fileName = "CharacterSystem", menuName = "FishMMO/Server/SceneServer/Character System", order = 1)]
[RequiresDataContainer(typeof(CharacterMappingData))]
[RequiresDataContainer(typeof(CharacterSystemRuntimeData))]
[RequiresDataContainer(typeof(CharacterSystemMainThreadQueueData))]
[RequiresDataContainer(typeof(AsyncWorkerData))]
public class CharacterSystem : ServerBehaviour, IServerBehaviour<INetworkManagerWrapper, ServerManager, NetworkConnection, IServerBehaviour>, IServerComponent<INetworkManagerWrapper, ServerManager, NetworkConnection, IServerBehaviour>, ICharacterSystem<NetworkConnection, Scene>, IServerBehaviour, IServerComponent
Inheritance
Object
ScriptableObject
CharacterSystem
Implements
IServerBehaviour<INetworkManagerWrapper, ServerManager, NetworkConnection, IServerBehaviour>
IServerComponent<INetworkManagerWrapper, ServerManager, NetworkConnection, IServerBehaviour>
ICharacterSystem<NetworkConnection, Scene>
Inherited Members
ScriptableObject.SetDirty()
ScriptableObject.CreateInstance<T>()
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.ToString()
Object.name
Object.hideFlags

Remarks

Lives on the character system rather than beside the dungeon finder because every operation here is about the characters standing in a scene, not about creating or resolving one — the membership walk, the removal, and the leave path a removal reuses are all already here.

Leadership is the owning party's leader. An instance belongs to a party, not to whoever happened to open it: the scene row records both, and the party is the one that survives its creator leaving, logging out, or handing leadership on. Reading leadership from the party also means it moves the moment the party's does — a promotion is reflected in this panel on its next refresh, with nothing here needing to know a promotion happened.

An instance opened by an ungrouped character has no party, and there its owner is its leader. That is the only case where the two differ, and it is also the case where they cannot disagree: a run of one.

Properties

LingeringCharacterCount

Number of characters whose bodies remain in the world after a combat logout.

public int LingeringCharacterCount { get; }

Property Value

int

Remarks

These have no connection, so they are absent from the connection maps population counts are normally derived from — but they are still resident and still hold a session claim, so anything reporting load or deciding whether a scene is empty has to account for them.

Methods

BeginChannelTransfer(NetworkConnection, long)

Moves a character to another channel (another instance of the same scene) by releasing it here, bound to targetSceneHandle, and dropping the connection so the client re-routes through the world server.

public bool BeginChannelTransfer(NetworkConnection connection, long targetSceneHandle)

Parameters

connection NetworkConnection

Connection whose character is switching channel.

targetSceneHandle long

Scene row ID of the destination channel.

Returns

bool

true when the transfer was started; false if the connection has no character.

Remarks

The ordering this performs is the whole point of the method, and it is why callers must not simply rewrite SceneHandle and disconnect. Scene population is credited and debited by handle, and the disconnect pipeline debits the handle the character carries at that moment — so a handle rewritten first debits the destination instance, which this server may not even host, while the source instance keeps a resident it no longer has. Neither error self-corrects: the source's phantom population makes it advertise capacity it does not have, keeps it from ever being recognised as empty and unloaded, and eventually makes it look full, at which point players queue for a scene instance that is in fact deserted.

The character is released promptly (no combat-logout linger) because the destination scene server has to be able to claim it, exactly as for a teleport.

IPlayerCharacter_OnTeleport(IPlayerCharacter)

Handles character teleport events, validates teleporter and scene, updates position, and saves state.

public void IPlayerCharacter_OnTeleport(IPlayerCharacter character)

Parameters

character IPlayerCharacter

Player character to teleport.

InitializeOnce()

Initializes the character system, registers event handlers, and sets up character authentication and broadcast handling.

public override ServerComponentInitializationStatus InitializeOnce()

Returns

ServerComponentInitializationStatus

OnDeinitialize()

Cleans up the character system, unregisters event handlers, and saves all characters to the database before shutdown.

public override void OnDeinitialize()

OnRemoteConnectionStopped(NetworkConnection)

When a connection disconnects the server removes all known instances of the character and saves it to the database.

protected override void OnRemoteConnectionStopped(NetworkConnection conn)

Parameters

conn NetworkConnection

OnUpdate(float)

Drains the main-thread queue each frame.

protected override void OnUpdate(float deltaTime)

Parameters

deltaTime float

ReturnInstanceOccupantsToWorld(long, string)

Returns everyone standing in an instance to the open world, so the instance can be unloaded without stranding them.

public int ReturnInstanceOccupantsToWorld(long instanceSceneID, string reason)

Parameters

instanceSceneID long

Scene row ID of the instance being closed.

reason string

Why it is closing, for diagnostics.

Returns

int

How many connected characters were moved.

Remarks

An instance is normally emptied by its occupants leaving; this is the path for an instance that ends on the server's terms — a lifetime cap expiring, or a leader closing it — where the characters inside have not asked to go anywhere.

Each character takes the ordinary leave-instance route: announced while it still belongs to the instance so the population is debited correctly, put back at the open-world position it entered from, saved, released, and disconnected to be re-routed. It is not gated on combat or death, because the scene is going away regardless and refusing would leave the character in a scene that is about to be destroyed.

Combat-logout bodies in the instance are finalised as well. They have no connection, so nothing else would notice them — and destroying the scene under one strands that character's session claim until its lease expires, locking the player out of every scene server in the meantime.

SendBroadcastToCharacter<T>(long, T)

Allows sending a broadcast to a specific character by their character ID. Returns true if the broadcast was sent successfully, false otherwise.

public bool SendBroadcastToCharacter<T>(long characterID, T msg) where T : struct, IBroadcast

Parameters

characterID long

ID of the character to send to.

msg T

Broadcast message to send.

Returns

bool

Type Parameters

T

Type of broadcast message.

SendBroadcastToCharacter<T>(string, T)

Allows sending a broadcast to a specific character by their character name. Returns true if the broadcast was sent successfully, false otherwise.

public bool SendBroadcastToCharacter<T>(string characterName, T msg) where T : struct, IBroadcast

Parameters

characterName string

Name of the character to send to.

msg T

Broadcast message to send.

Returns

bool

Type Parameters

T

Type of broadcast message.

SuppressCombatLingerOnDisconnect(NetworkConnection)

Declares that the next disconnect on connection is something the server is doing on purpose, not a player walking out of a fight.

public void SuppressCombatLingerOnDisconnect(NetworkConnection connection)

Parameters

connection NetworkConnection

Connection the server is about to disconnect on purpose.

Remarks

Combat-logout linger exists to stop a player escaping a losing fight by closing the client, so it triggers on a dropped connection — and the server itself drops connections for reasons that have nothing to do with the player quitting.

  • A hand-off to another scene server is implemented as a dropped connection. One that lingered would leave the body — and its session claim — on the source server while the client arrives at the destination, which then cannot claim the character and kicks it, repeatedly, until the linger expires.
  • An administrative kick is the operator removing the player, so there is no escape to deny. Lingering there keeps the kicked player's body in the world and holds their character claim for the length of the linger — which makes a kick the opposite of a remedy for a character that is stuck.

The teleport and bind-point-respawn paths avoid this by releasing the character themselves before disconnecting. Callers that instead rely on the ordinary disconnect pipeline must announce the intent here first. The marker is consumed by that disconnect, so it cannot leak onto a later session on a recycled connection id.

TryEndCombatLingerForAccount(string, string)

Ends the combat-logout linger held for accountName, if there is one, saving the body and handing its session claim back.

public bool TryEndCombatLingerForAccount(string accountName, string reason)

Parameters

accountName string

Account whose lingering body should be removed.

reason string

Why the linger is being ended, for diagnostics.

Returns

bool

true when a lingering body was found and removed.

Remarks

A lingering body has no connection, so it is invisible to everything that acts on a player by finding their connection — including the administrative kick, which therefore did nothing at all to a character that had combat-logged. That is the opposite of what a kick is for: the body stays in the world, still targetable, and goes on holding the character's session claim, so the operator's remedy for a stuck character is precisely the case where it has no effect.

This is the ordinary end of a linger, not a special teardown: the body is persisted and despawned and the claim released, exactly as when its timer runs out. The character keeps whatever happened to it while it stood there.

Events

OnAfterLoadCharacter

public event Action<NetworkConnection, IPlayerCharacter> OnAfterLoadCharacter

Event Type

Action<NetworkConnection, IPlayerCharacter>

OnBeforeLoadCharacter

public event Action<NetworkConnection, long> OnBeforeLoadCharacter

Event Type

Action<NetworkConnection, long>

OnConnect

Triggered immediately after a character is added to their respective cache.

public event Action<NetworkConnection, IPlayerCharacter> OnConnect

Event Type

Action<NetworkConnection, IPlayerCharacter>

OnDespawnCharacter

Triggered immediately after a character is despawned from the scene.

public event Action<NetworkConnection, IPlayerCharacter> OnDespawnCharacter

Event Type

Action<NetworkConnection, IPlayerCharacter>

OnDisconnect

Triggered immediately after a character is removed from their respective cache.

public event Action<NetworkConnection, IPlayerCharacter> OnDisconnect

Event Type

Action<NetworkConnection, IPlayerCharacter>

OnPetKilled

Triggered immediately after a pet is killed.

public event Action<NetworkConnection, IPlayerCharacter> OnPetKilled

Event Type

Action<NetworkConnection, IPlayerCharacter>

OnSpawnCharacter

Triggered immediately after a character is spawned in the scene.

public event Action<NetworkConnection, IPlayerCharacter, Scene> OnSpawnCharacter

Event Type

Action<NetworkConnection, IPlayerCharacter, Scene>