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
-
ObjectScriptableObjectCharacterSystem
- Implements
- 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.nameObject.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
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
connectionNetworkConnectionConnection whose character is switching channel.
targetSceneHandlelongScene row ID of the destination channel.
Returns
- bool
truewhen the transfer was started;falseif 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
characterIPlayerCharacterPlayer character to teleport.
InitializeOnce()
Initializes the character system, registers event handlers, and sets up character authentication and broadcast handling.
public override ServerComponentInitializationStatus InitializeOnce()
Returns
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
connNetworkConnection
OnUpdate(float)
Drains the main-thread queue each frame.
protected override void OnUpdate(float deltaTime)
Parameters
deltaTimefloat
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
instanceSceneIDlongScene row ID of the instance being closed.
reasonstringWhy 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
characterIDlongID of the character to send to.
msgTBroadcast message to send.
Returns
Type Parameters
TType 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
characterNamestringName of the character to send to.
msgTBroadcast message to send.
Returns
Type Parameters
TType 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
connectionNetworkConnectionConnection 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
accountNamestringAccount whose lingering body should be removed.
reasonstringWhy the linger is being ended, for diagnostics.
Returns
- bool
truewhen 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
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>