Player spawning logic for PlayerState. Defines a pawn definition struct and spawning state enum, handles requesting character definitions, host RPCs to set character and spawn a PlayerPawn, lifecycle events for spawn and death, and syncs some fields to clients.
using KOTH.UI;
using Sandbox;
using Sandbox.Diagnostics;
using Sandbox.Events;
using System.Runtime.CompilerServices;
using System.Xml.Linq;
namespace KOTH;
public struct FPlayerPawnDefinition
{
public FPlayerPawnDefinition()
{
}
public CharacterDefinition CharacterDefinition { get; init; }
// public PlayerState OwnerPlayerState { get; init; }
public string Name { get; init; } = "UNINITALIZED";
public Team Team { get; init; } = Team.Unassigned;
public bool IsDummy { get; init; } = false;
public readonly bool IsValid()
{
return CharacterDefinition.IsValid();
}
}
//////////////////////////////////////////////////////////////////////////////////
/*
* State of the player pawn
* menu when displaying a menu (true when first joining a lobby)
* waitingforspawn when in a game & requestedcharacterdefinition is not null
* alive when playing in game
*/
public enum EPlayerStateSpawningState
{
MainMenu,
WaitingForSpawn,
InstantSpawn,
Alive,
Spectating,
}
//////////////////////////////////////////////////////////////////////////////////
public partial class PlayerState
{
[Sync(SyncFlags.FromHost)/*, Change(nameof(OnPlayerStateSpawningStateChanged))*/] public EPlayerStateSpawningState PlayerStateSpawningState { get; private set; } = EPlayerStateSpawningState.MainMenu;
//////////////////////////////////////////////////////////////////////////////////
[Sync(SyncFlags.FromHost)] public float TimeTilAttemptedSpawn { get; private set; } = -1;
public void SetTimeTilAttemptedSpawn(float TimeTilSpawn)
{
Assert.True(Networking.IsHost);
TimeTilAttemptedSpawn = TimeTilSpawn;
}
//////////////////////////////////////////////////////////////////////////////////
/*
* NOTE : Sync will only care about the vaule given by an object's host, so bc this player state is owned
* by the server the requested character def will never sync! this is the workaround for now..
*/
public CharacterDefinition RequestedCharacterDefinition { get; private set; } = null;
public void RequestCharacterDefinition(CharacterDefinition CharacterDefintionIn)
{
Log.Info($"Requested Character {CharacterDefintionIn}");
RequestedCharacterDefinition = CharacterDefintionIn;
// TODO : actaully send a request
HostSetCharacterDefinition(CharacterDefintionIn);
}
[Rpc.Host]
private void HostSetCharacterDefinition(CharacterDefinition CharacterDefinition)
{
if (!CharacterDefinition.IsValid())
{
return;
}
RequestedCharacterDefinition = CharacterDefinition;
// if we come from the main menu we want to spawn instantly
if (PlayerStateSpawningState == EPlayerStateSpawningState.MainMenu)
{
PlayerStateSpawningState = EPlayerStateSpawningState.InstantSpawn;
return;
}
PlayerStateSpawningState = EPlayerStateSpawningState.WaitingForSpawn;
}
//////////////////////////////////////////////////////////////////////////////////
[Sync(SyncFlags.FromHost)] public Team Team { get; set; }
//////////////////////////////////////////////////////////////////////////////////
public void SpawnPlayerPawn(TeamSpawnPoint SpawnPoint)
{
Assert.True(Networking.IsHost);
if (PlayerPawn.IsValid())
{
PlayerPawn.GameObject.Root.Destroy();
PlayerPawn = null;
LocalOnPlayerDeath();
GameMode.Instance?.DestroySpawnZoneBlockers();
}
if (!SpawnPoint.IsValid())
{
Log.Warning($"trying to spawn player {this} with invalid spawn point");
return;
}
SpawnPlayerPawn(Connection, SteamName, RequestedCharacterDefinition, SpawnPoint);
}
[Property] public GameObject DefaultPlayerPawnPrefab { get; private set; }
private void OnPlayerPawnSpawn()
{
Assert.True(Networking.IsHost);
Assert.True(PlayerPawn.IsValid());
PlayerPawn.OnDeath += OnPlayerPawnDeath;
PlayerStateSpawningState = EPlayerStateSpawningState.Alive;
BroadcastPlayerSpawn(PlayerPawn);
using (Rpc.FilterInclude(Connection))
{
LocalPlayerSpawn(PlayerPawn);
}
}
[Rpc.Host]
private void SpawnPlayerPawn(Connection OwningConnection, string Name, CharacterDefinition CharacterDefinition, TeamSpawnPoint SpawnPoint)
{
Assert.True(Networking.IsHost);
Assert.IsValid(SpawnPoint);
Log.Info($"attempting to spawn player {RequestedCharacterDefinition} for {OwningConnection}");
var SpawnPlayerPawnPrefab = DefaultPlayerPawnPrefab.Clone(SpawnPoint.GameObject.WorldTransform, null, true);
SpawnPlayerPawnPrefab.Network.SetOrphanedMode(NetworkOrphaned.Destroy);
var SpawnPlayerPawnComponent = SpawnPlayerPawnPrefab.Components.Get<PlayerPawn>();
Assert.NotNull(SpawnPlayerPawnComponent);
var TeamIn = Team; // :P
FPlayerPawnDefinition PlayerPawnDefinition = new()
{
CharacterDefinition = RequestedCharacterDefinition,
Name = Name,
Team = TeamIn,
};
SpawnPlayerPawnComponent.SetPlayerPawnDefinition(PlayerPawnDefinition);
if (!SpawnPlayerPawnPrefab.NetworkSpawn(OwningConnection))
{
SpawnPlayerPawnPrefab.Destroy();
return;
}
PlayerPawn = SpawnPlayerPawnComponent;
OnPlayerPawnSpawn();
}
void OnPlayerPawnDeath()
{
Assert.True(Networking.IsHost);
PlayerStateSpawningState = EPlayerStateSpawningState.WaitingForSpawn;
using (Rpc.FilterInclude(Connection))
{
LocalOnPlayerDeath();
}
}
//////////////////////////////////////////////////////////////////////////////////
[Rpc.Broadcast]
private void BroadcastPlayerSpawn(PlayerPawn PlayerPawn)
{
Scene.Dispatch(new PlayerSpawnedEvent(PlayerPawn));
}
[Rpc.Broadcast] // broadcast filter
private void LocalPlayerSpawn(PlayerPawn PlayerPawn)
{
Scene.Dispatch(new LocalPlayerSpawnedEvent(PlayerPawn));
}
[Rpc.Broadcast] // broadcast filter
public void LocalOnPlayerDeath()
{
Scene.Dispatch(new LocalPlayerDiedEvent());
}
}