Class PlayerCharacter
Represents a player-controlled character in the game world, with inventory, abilities, and networked state. Implements IPlayerCharacter and extends BaseCharacter with player-specific logic, hotkeys, and event-driven behaviour.
Coupling note: This class uses 21 UnityEngine.RequireComponent attributes, creating strong coupling between PlayerCharacter and its component dependencies. All required components are tightly bound to this class and cannot be removed/replaced without modifying this declaration block. If a more modular composition approach is desired in the future (e.g., optional components registered at runtime), this set of RequireComponent attributes would need to be refactored into a dynamic registration system.
[RequireComponent(typeof(CharacterAttributeController))]
[RequireComponent(typeof(TargetController))]
[RequireComponent(typeof(CooldownController))]
[RequireComponent(typeof(InventoryController))]
[RequireComponent(typeof(EquipmentController))]
[RequireComponent(typeof(BankController))]
[RequireComponent(typeof(AbilityController))]
[RequireComponent(typeof(AchievementController))]
[RequireComponent(typeof(BuffController))]
[RequireComponent(typeof(QuestController))]
[RequireComponent(typeof(CharacterDamageController))]
[RequireComponent(typeof(GuildController))]
[RequireComponent(typeof(PartyController))]
[RequireComponent(typeof(FriendController))]
[RequireComponent(typeof(FactionController))]
[RequireComponent(typeof(WaypointController))]
[RequireComponent(typeof(ArchetypeController))]
[RequireComponent(typeof(CharacterPredictionController))]
[RequireComponent(typeof(BodyVisibilityManager))]
[RequireComponent(typeof(CharacterAppearanceManager))]
[RequireComponent(typeof(EquipmentVisualController))]
[RequireComponent(typeof(CharacterAnimationController))]
public class PlayerCharacter : BaseCharacter, IPlayerCharacter, ICharacter
- Inheritance
-
ObjectComponentBehaviourMonoBehaviourNetworkBehaviourPlayerCharacter
- Implements
- Inherited Members
-
NetworkBehaviour.IsSpawnedNetworkBehaviour.ComponentIndexNetworkBehaviour.NetworkObjectNetworkBehaviour.MAXIMUM_NETWORKBEHAVIOURSNetworkBehaviour.UNSET_NETWORKBEHAVIOUR_IDNetworkBehaviour.ToString()NetworkBehaviour.Reset()NetworkBehaviour.OnValidate()NetworkBehaviour.IsBehaviourReconcilingNetworkBehaviour.ClearReplicateCache()NetworkBehaviour.CreateReconcile()NetworkBehaviour.Reconcile_Reader<T>(PooledReader, ref T)NetworkBehaviour.OnStartServerCalledNetworkBehaviour.OnStartClientCalledNetworkBehaviour.OnStartNetwork()NetworkBehaviour.OnStopNetwork()NetworkBehaviour.OnStartServer()NetworkBehaviour.OnStopServer()NetworkBehaviour.OnOwnershipServer(NetworkConnection)NetworkBehaviour.OnSpawnServer(NetworkConnection)NetworkBehaviour.OnDespawnServer(NetworkConnection)NetworkBehaviour.ClearBuffedRpcs()NetworkBehaviour.ExcludeOwnerFromUnbufferedObserversRpcsNetworkBehaviour.IsClientOnlyNetworkBehaviour.IsServerOnlyNetworkBehaviour.IsHostNetworkBehaviour.IsClientNetworkBehaviour.IsServerNetworkBehaviour.IsDeinitializingNetworkBehaviour.NetworkManagerNetworkBehaviour.ServerManagerNetworkBehaviour.ClientManagerNetworkBehaviour.ObserverManagerNetworkBehaviour.TransportManagerNetworkBehaviour.TimeManagerNetworkBehaviour.SceneManagerNetworkBehaviour.PredictionManagerNetworkBehaviour.RollbackManagerNetworkBehaviour.NetworkObserverNetworkBehaviour.IsClientInitializedNetworkBehaviour.IsClientStartedNetworkBehaviour.IsClientOnlyInitializedNetworkBehaviour.IsClientOnlyStartedNetworkBehaviour.IsServerInitializedNetworkBehaviour.IsServerStartedNetworkBehaviour.IsServerOnlyInitializedNetworkBehaviour.IsServerOnlyStartedNetworkBehaviour.IsHostInitializedNetworkBehaviour.IsHostStartedNetworkBehaviour.IsOfflineNetworkBehaviour.IsNetworkedNetworkBehaviour.GetIsNetworked()NetworkBehaviour.IsManagerReconcilingNetworkBehaviour.ObserversNetworkBehaviour.IsOwnerNetworkBehaviour.IsControllerNetworkBehaviour.HasAuthorityNetworkBehaviour.OwnerNetworkBehaviour.OwnerIdNetworkBehaviour.ObjectIdNetworkBehaviour.LocalConnectionNetworkBehaviour.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.destroyCancellationTokenMonoBehaviour.useGUILayoutMonoBehaviour.didStartMonoBehaviour.didAwakeMonoBehaviour.runInEditModeBehaviour.enabledBehaviour.isActiveAndEnabledComponent.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.transformComponent.transformHandleComponent.gameObjectComponent.tagObject.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.nameObject.hideFlags
- Extension Methods
Properties
AccessLevel
The access level of the character (e.g., player, admin).
public AccessLevel AccessLevel { get; set; }
Property Value
Account
The account name associated with this character.
public string Account { get; set; }
Property Value
BindPosition
The position in the bind scene for respawn.
public Vector3 BindPosition { get; set; }
Property Value
- Vector3
BindScene
The name of the bind scene for respawn.
public string BindScene { get; set; }
Property Value
CharacterController
The KCC controller for character movement logic.
public KCCController CharacterController { get; }
Property Value
CharacterName
The character's real name. Use this for referencing by name.
public string CharacterName { get; set; }
Property Value
CharacterNameLower
The lowercase version of the character's name, for case-insensitive operations.
public string CharacterNameLower { get; set; }
Property Value
ChatTokenLastRefillTicks
UTC ticks of the last token bucket refill.
public long ChatTokenLastRefillTicks { get; set; }
Property Value
ChatTokens
Current number of chat tokens available in the token bucket (double for precision on long-uptime servers). Server-side anti-spam: consumed per message, refilled at a configurable rate.
public double ChatTokens { get; set; }
Property Value
EquipmentViewCamera
Gets or sets the equipment view camera.
public Camera EquipmentViewCamera { get; set; }
Property Value
- Camera
Hotkeys
The list of hotkey data for the character.
public List<HotkeyData> Hotkeys { get; set; }
Property Value
InstanceID
The instance ID for instanced content.
public long InstanceID { get; set; }
Property Value
InstancePosition
The position in the instance scene.
public Vector3 InstancePosition { get; set; }
Property Value
- Vector3
InstanceRotation
The rotation in the instance scene.
public Quaternion InstanceRotation { get; set; }
Property Value
- Quaternion
InstanceSceneHandle
Identity of the instance scene the character is inside — the scenes.id of its
row. See SceneHandle.
public long InstanceSceneHandle { get; set; }
Property Value
InstanceSceneName
The name of the instance scene.
public string InstanceSceneName { get; set; }
Property Value
IsChatTokensFull
Indicates whether ChatTokens is in its initial "filled to capacity" state (no refill applied yet).
Used instead of double.MaxValue as a sentinel to avoid overflow to Infinity on first add.
public bool IsChatTokensFull { get; set; }
Property Value
IsTeleporting
Whether the character is currently teleporting (true if TeleporterName is set).
public override bool IsTeleporting { get; }
Property Value
KCCPlayer
The KCC player component for input and control.
public KCCPlayer KCCPlayer { get; }
Property Value
LastChatMessage
The last chat message sent by the character.
public string LastChatMessage { get; set; }
Property Value
LastWorldPosition
Where the character stood in the open world before entering an instance, and where it is put back when it leaves one.
public Vector3 LastWorldPosition { get; set; }
Property Value
- Vector3
Remarks
The character row carries two positions — the open-world one and the instance one — but a spawned character has only one transform, so while it is inside an instance the transform describes the instance and something has to remember the other. Without this, every save taken inside a dungeon overwrote the open-world position with dungeon coordinates, and a character released from a dead instance was placed at those coordinates in the open world: through terrain, or outside the playable bounds entirely, until the out-of-bounds sweep noticed and relocated them.
LastWorldRotation
Rotation to restore alongside LastWorldPosition.
public Quaternion LastWorldRotation { get; set; }
Property Value
- Quaternion
ModelIndex
The model index for the character's race.
public int ModelIndex { get; set; }
Property Value
Motor
The kinematic character motor for movement.
public KinematicCharacterMotor Motor { get; }
Property Value
- KinematicCharacterMotor
NextChatMessageTicks
The next UTC ticks when the character can send a chat message.
public long NextChatMessageTicks { get; set; }
Property Value
NextInteractTime
The next UTC time the character can interact.
public DateTime NextInteractTime { get; set; }
Property Value
RaceID
The Race Template ID for the character object.
public int RaceID { get; set; }
Property Value
RaceName
The character's race name, for display.
public string RaceName { get; }
Property Value
Remarks
Derived from RaceTemplate, and null when that cannot be resolved. This was
once a string field of its own, assigned in ReadPayload — except nothing in the
tree ever assigned it on the way out, so the wire value was always null and every reader
got an empty race. It is derived rather than stored because a name is not identity:
RaceID is, and a name travelling separately from the id it describes is a
name that can disagree with it.
RaceTemplate
The character's race, resolved from RaceID.
public RaceTemplate RaceTemplate { get; }
Property Value
Remarks
A character stores its race as an ID, not as a template reference, and that is the right way round: the ID is what persists, what the spawn payload carries, and what survives a template being reimported. The template is a cache entry that ID names, so this resolves rather than holds — there is no second copy to fall out of step with RaceID.
Not to be confused with IFactionController.RaceTemplate. That is a SEPARATE
holding with its own id, deliberately overridable per spawn
(NPCSpawnableSettings.FactionOverride → SetRaceTemplateOnSpawn) so one NPC
prefab can answer for several factions. For a player the two agree, but the faction
controller's copy answers a different question and must not be read as "what race is
this character".
Null when RaceID is 0 (a character whose race was never assigned) or when no template with that ID is cached on this peer. Callers that display the answer decide what an unknown race reads as; this invents no placeholder.
SceneHandle
Identity of the scene instance the character belongs to: the scenes.id of the
row describing it.
public long SceneHandle { get; set; }
Property Value
Remarks
This used to hold the hosting scene server's local scene-manager handle, which is only meaningful inside that one process. Two scene servers running the same build routinely allocate identical handles, so the value could not tell one server's instance from another's: the world server's routing map collided between them, and a scene server accepted a character routed to a different server's instance whenever the handle and scene name happened to match. The row ID is unique by construction.
SceneName
The name of the current scene.
public string SceneName { get; set; }
Property Value
TeleporterName
The name of the teleporter the character is currently using.
public string TeleporterName { get; set; }
Property Value
TimeCreated
The UTC time when the character was created.
public DateTime TimeCreated { get; set; }
Property Value
Version
The current version of the character data, used for optimistic concurrency control when saving/loading from the database.
public long Version { get; set; }
Property Value
WorldServerID
The world server ID this character belongs to.
public long WorldServerID { get; set; }
Property Value
Methods
CurrentSceneName()
The name of the scene the character is physically standing in.
public string CurrentSceneName()
Returns
Remarks
Not the same thing as SceneName. While a character is inside an instance,
SceneName keeps naming the open-world scene it will return to — that is what makes
the return trip possible — and the scene it is actually in is
InstanceSceneName. Reading SceneName where this was meant has been
the same bug several times over: teleporters inside a dungeon could never fire because
the wrong scene's teleporter list was consulted, and every interactable handler's "is
this a known scene?" check was answered about a scene the character had left.
This names a scene, so it still cannot distinguish one instance of a scene from another —
scene stacking means several share a name. Anything that needs instance identity within
this process must compare GameObject.scene.handle, and anything that needs it
across processes must use the scene row id. See SceneHandle.
IsInInstance()
Returns true if the character is in an instance and the instance scene name is set.
public bool IsInInstance()
Returns
IsLoaded()
Returns true if the character is currently loaded in the scene and active.
public bool IsLoaded()
Returns
OnAwake()
Initializes the player character and its hotkeys. Sets up KCC movement components and resets chat/interact timers.
public override void OnAwake()
OnOwnershipClient(NetworkConnection)
Called on the client after gaining or losing ownership.
public override void OnOwnershipClient(NetworkConnection prevOwner)
Parameters
prevOwnerNetworkConnectionPrevious owner of this object.
OnStartClient()
Called on the client after initializing this object.
public override void OnStartClient()
OnStopClient()
Called when the client stops. Invokes OnStopLocalClient and stops all character behaviours for the local player.
public override void OnStopClient()
ReadPayload(NetworkConnection, Reader)
Reads the character's payload from the network connection and updates core properties. Invokes OnReadPayload event and instantiates the race model on the client.
public override void ReadPayload(NetworkConnection connection, Reader reader)
Parameters
connectionNetworkConnectionThe network connection to read from.
readerReaderThe reader containing serialized data.
ResetHotkeys()
Resets the hotkeys to the default configuration. Initializes each hotkey slot.
public void ResetHotkeys()
ResetState(bool)
Resets the character's state, including teleportation, chat/interact timers, instance data, and hotkeys. This is called when the character is despawned or removed from the world.
public override void ResetState(bool asServer)
Parameters
asServerboolTrue if called on the server, false if on the client.
SetGuildName(string)
Sets the guild row of the character's overhead nameplate (client only).
public void SetGuildName(string guildName)
Parameters
guildNamestringThe guild's name, or blank when the character has left one.
Remarks
A blank name clears the row rather than leaving an empty one: an empty row would still take a line of height on the plate, so every character in the world who is not in a guild would carry a visible gap under their name.
Teleport(string)
Teleports the character to the specified teleporter name and invokes the teleport event.
public void Teleport(string teleporterName)
Parameters
teleporterNamestringThe name of the teleporter to use.
WritePayload(NetworkConnection, Writer)
Writes the character's payload to the network connection for serialization.
public override void WritePayload(NetworkConnection connection, Writer writer)
Parameters
connectionNetworkConnectionThe network connection to write to.
writerWriterThe writer to serialize data into.
Remarks
Two shapes behind a shape byte, chosen by the receiver, in the same style as the ability, buff, attribute and waypoint controllers. Everything an observer needs to DRAW a peer is unconditional; the three fields that describe where that peer is and what it is allowed to do are the owner's alone.
SceneName and InstanceSceneName are read only through
CurrentSceneName(), and the only client that calls it
calls it on the LOCAL character (ClientMapSystem). Every observer of every peer
was being sent two scene-name strings it had no reader for — and an observer is by
definition already in that scene.
AccessLevel has no client reader at all: the only thing that tests it is
ChatHelper.TryParseCommand, whose sole caller is the server's chat system. Sending
it told every observer which characters were game masters, for a byte nobody spent.
RaceName used to travel here and is gone entirely. It was never assigned anywhere
in the tree, so the wire value was always null, and a race name is derivable from
RaceID through the template cache — which is exactly what the guild UI
already does.