swb_player/PlayerBase.cs
using Sandbox.Events;
using Sandbox.Sboku;
using Sandbox.Sboku.Arena;
using Sandbox.Sboku.Shared;
using SWB.Shared;
using System;
using System.Collections.Generic;
using System.Linq;
namespace SWB.Player;
[Group( "SWB" )]
[Title( "PlayerBase" )]
public partial class PlayerBase : Component, Component.INetworkSpawn, IPlayerBase, ISbokuTarget
{
[Property] public GameObject Head { get; set; }
[Property] public GameObject Body { get; set; }
[Property] public SkinnedModelRenderer BodyRenderer { get; set; }
[Property] public CameraComponent Camera { get; set; }
[Property] public CameraComponent ViewModelCamera { get; set; }
[Property] public PanelComponent RootDisplay { get; set; }
[Property] public Voice Voice { get; set; }
[RequireComponent]
public UpgradeHolder UpgradeHolder { get; set; }
[Sync] public bool IsBot { get; set; }
public IInventory Inventory { get; set; }
public ICameraMovement CameraMovement { get; set; }
public bool IsFirstPerson => CameraMovement.IsFirstPerson;
public string DisplayName => !IsBot ? (Network.Owner?.DisplayName ?? "Disconnected") : GameObject.Name;
public SteamId SteamId => !IsBot ? Network.Owner.SteamId : new( 0 );
public bool IsHost => !IsBot && Network.Owner.IsHost;
public bool IsSpeaking => Voice.Amplitude > 0;
public float InputSensitivity
{
get { return CameraMovement.InputSensitivity; }
set { CameraMovement.InputSensitivity = value; }
}
public Angles EyeAnglesOffset
{
get { return CameraMovement.EyeAnglesOffset; }
set { CameraMovement.EyeAnglesOffset = value; }
}
Guid IPlayerBase.Id { get => GameObject.Id; }
protected override void OnAwake()
{
Inventory = Components.Create<Inventory>();
CameraMovement = Components.GetInChildren<CameraMovement>();
Voice = Components.GetInChildren<Voice>();
if ( IsBot ) return;
// Hack: Hide client until fully loaded in OnStart
if ( !IsProxy )
{
WorldPosition = new( 0, 0, -999999 );
Network.ClearInterpolation();
}
OnMovementAwake();
}
public void OnNetworkSpawn( Connection connection )
{
//ApplyClothes( connection );
}
protected override void OnStart()
{
if (!IsProxy && !IsBot)
{
InitClothingFromConnection();
}
if ( IsProxy || IsBot )
{
if ( Camera is not null )
Camera.Enabled = false;
if ( ViewModelCamera is not null )
ViewModelCamera.Enabled = false;
}
if ( IsBot )
{
var screenPanel = Components.GetInChildrenOrSelf<ScreenPanel>();
if ( screenPanel is not null )
screenPanel.Enabled = false;
}
if ( !IsProxy )
Respawn();
var movement = Components.Get<CameraMovement>();
if (movement is not null)
{
movement.Enabled = false;
}
}
[Rpc.Owner]
public void Kill()
{
if ( !IsAlive ) return;
Health = 0;
OnDeath( new() { AttackerId = GameObject.Id } );
}
[Rpc.Owner]
public void Kick( string reason )
{
Log.Info( reason );
Game.Disconnect();
}
[Rpc.Broadcast]
public virtual void OnDeath( Shared.DamageInfo info )
{
if ( !IsValid ) return;
var attackerGO = Scene.Directory.FindByGuid( info.AttackerId );
if ( attackerGO is not null && !attackerGO.IsProxy )
{
var attacker = attackerGO.Components.Get<IPlayerBase>();
if ( attacker is not null && attacker != this )
attacker.Kills++;
}
if ( IsProxy ) return;
Deaths++;
CharacterController.Velocity = 0;
Ragdoll( info.Force, info.Origin );
Inventory.Clear();
RespawnWithDelay( 2 );
}
public async virtual void RespawnWithDelay( float delay )
{
await GameTask.DelaySeconds( delay );
// 🔑 Pelaaja spawnaa uudelleen omasta spawnistaan — ei Game Overia, ei elämärajoja.
// Game Over dispatchaus poistettu: yksittäisen pelaajan kuolema ei lopeta koko peliä.
// Scene.Dispatch<RoundManager.GameOver>(new());
Respawn();
}
[Rpc.Broadcast]
public void RespawnWithDelayBroadCast( float delay )
{
RespawnWithDelay( delay );
}
[Rpc.Broadcast]
public void RespawnBroadCast()
{
Respawn();
}
public virtual void Respawn()
{
// Hack: Delaying with just 1ms fixes the 1 frame body at death position.
async void UnragdollWithDelay()
{
await GameTask.Delay( 1 );
Unragdoll();
}
UnragdollWithDelay();
Inventory.Clear();
dmgTable.Clear();
Health = MaxHealth;
var spawnLocation = GetSpawnLocation();
WorldPosition = spawnLocation.Position;
EyeAngles = spawnLocation.Rotation.Angles();
Network.ClearInterpolation();
if ( IsBot )
{
Body.WorldRotation = new Angles( 0, EyeAngles.ToRotation().Yaw(), 0 ).ToRotation();
AnimationHelper.WithLook( EyeAngles.ToRotation().Forward, 1f, 0.75f, 0.5f );
}
}
protected void ResetPlayerState()
{
Inventory.Clear();
dmgTable.Clear();
Health = MaxHealth;
}
public virtual Transform GetSpawnLocation()
{
var prisonMarkers = Scene.GetAllComponents<PrisonSpawnMarker>()
.Where( marker => marker.IsValid && marker.PreferForPlayers )
.Select( marker => new Transform(
marker.GameObject.Transform.World.Position + Vector3.Up * 64f,
marker.GameObject.Transform.World.Rotation
) )
.ToList();
if ( prisonMarkers.Any() )
{
var rand = new Random();
return prisonMarkers.ElementAt( rand.Next( 0, prisonMarkers.Count ) );
}
var spawnPoints = Scene.Components.GetAll<SpawnPoint>();
if ( !spawnPoints.Any() )
return new Transform();
var randFallback = new Random();
var randomSpawnPoint = spawnPoints.ElementAt( randFallback.Next( 0, spawnPoints.Count() ) );
return randomSpawnPoint.Transform.World;
}
protected override void OnUpdate()
{
if ( IsBot ) return;
if ( !IsProxy ) ViewModelCamera.Enabled = IsFirstPerson && IsAlive;
// 🔑 TÄMÄ on se hetki kun client saa vaatteet
if (IsProxy && !clothesApplied && !string.IsNullOrEmpty(clothingJSON))
{
ApplyClothesJson(clothingJSON);
clothesApplied = true;
}
if ( IsAlive )
OnMovementUpdate();
HandleFlinch();
HandleScreenShake();
UpdateClothes();
}
protected override void OnFixedUpdate()
{
if (IsRagdolled) return;
if ( !IsAlive || IsBot ) return;
OnMovementFixedUpdate();
HandleDamage();
}
public static PlayerBase GetLocal()
{
var players = GetAll();
return players.FirstOrDefault( ( player ) => !player.IsProxy && !player.IsBot, null );
}
public static IEnumerable<PlayerBase> GetAll()
{
return Game.ActiveScene.GetAllComponents<PlayerBase>();
}
public bool IsEnemy => true;
public bool IsAttackPressed(string type)
=> Input.Pressed(type);
public bool IsAttackDown(string type)
=> Input.Down(type);
public bool IsReloadDown()
=> Input.Down(InputButtonHelper.Reload);
}