Player/PlayerCharacter.cs
using System;
using Clover.Carriable;
using Clover.Components;
using Clover.Npc;
using Clover.Ui;
namespace Clover.Player;
[Title( "Player Character" )]
[Icon( "face" )]
[Category( "Clover/Player" )]
[Description( "The player character component." )]
public sealed partial class PlayerCharacter : Component
{
private static PlayerCharacter _local;
[ActionGraphNode( "player.local" ), Title( "Local Player" ), Icon( "face" ), Category( "Clover" )]
public static PlayerCharacter Local
{
get
{
if ( _local.IsValid() ) return _local;
_local = Game.ActiveScene.GetAllComponents<PlayerCharacter>().FirstOrDefault( x => !x.IsProxy );
return _local;
}
}
[Sync] public string PlayerId { get; set; }
[Sync] public string PlayerName { get; set; }
[Property] public GameObject Model { get; set; }
[Property] public GameObject InteractPoint { get; set; }
[Property] public SittableNode Seat { get; set; }
public Vector3 EnterPosition { get; set; }
public bool IsSitting => Seat.IsValid();
public World World => WorldLayerObject.World;
public Action<World> OnWorldChanged { get; set; }
[Sync] public bool InCutscene { get; set; }
public Vector3? CutsceneTarget { get; set; }
public string LastEntrance { get; set; }
public bool IsLocalPlayer => Local == this;
protected override void OnStart()
{
if ( IsProxy ) return;
GameObject.BreakFromPrefab();
Load();
OnWorldChanged += ( world ) =>
{
if ( IsProxy ) return;
Save();
};
_ = Fader.Instance.FadeFromBlack();
CameraMan.Instance.AddTarget( GameObject );
MainUi.Instance.LastInput = 0;
}
// TODO: maybe stop using yaw
public void ModelLookAt( Vector3 position )
{
var dir = (position - WorldPosition).Normal;
dir.z = 0;
Model.WorldRotation = Rotation.LookAt( dir, Vector3.Up );
PlayerController.Yaw = Model.WorldRotation.Yaw();
}
public void ModelLook( Rotation rotation )
{
Model.WorldRotation = rotation;
PlayerController.Yaw = Model.WorldRotation.Yaw();
}
protected override void OnFixedUpdate()
{
if ( IsProxy ) return;
base.OnFixedUpdate();
if ( Input.Pressed( "use" ) && IsSitting )
{
Seat.Occupant = null;
Seat = null;
WorldPosition = EnterPosition;
Input.Clear( "use" );
Input.ReleaseAction( "use" );
}
if ( WorldPosition.z < -500f )
{
Log.Error( $"Player fell off the world: {WorldPosition} in world {World}" );
if ( !string.IsNullOrEmpty( LastEntrance ) )
{
TeleportTo( LastEntrance );
}
else
{
Log.Error( "No last entrance found" );
}
}
else if ( World != null && WorldPosition.z < World.WorldPosition.z - 500f )
{
Log.Error( $"Player fell off the world: {WorldPosition} in world {World}" );
if ( !string.IsNullOrEmpty( LastEntrance ) )
{
TeleportTo( LastEntrance );
}
else
{
Log.Error( "No last entrance found" );
}
}
}
public Vector2Int GetAimingGridPosition()
{
var boxPos = InteractPoint.WorldPosition /*+ (Vector3.Down * WorldManager.WorldOffset)*/;
// DebugOverlay.Sphere( new Sphere( boxPos, 8f ), Color.Red, 1f );
var boxPosHeight = boxPos.z;
var world = WorldManager.Instance.ActiveWorld;
var gridPosition = world.WorldToItemGrid( boxPos );
var worldPosition = world.ItemGridToWorld( gridPosition );
// Log.Info( $"Aiming at {boxPos} -> {gridPosition} -> {worldPosition}" );
// DebugOverlay.Sphere( new Sphere( worldPosition, 8f ), Color.Green, 1f );
if ( MathF.Abs( boxPosHeight - worldPosition.z ) > 16f )
{
// throw new System.Exception( $"Aiming at a higher position: {boxPos} -> {worldPosition}" );
Log.Warning(
$"Aiming at a higher position: {boxPos} -> {worldPosition} ({MathF.Abs( boxPosHeight - worldPosition.z )})" );
return default;
}
return gridPosition;
}
/*protected override void OnUpdate()
{
Gizmo.Draw.Arrow( WorldPosition + Vector3.Up * 16f, WorldPosition + Vector3.Up * 16 + Model.WorldRotation.Forward * 32f );
}*/
[Rpc.Owner]
public void TeleportTo( string entrance )
{
Log.Info( $"Teleporting to entrance: {entrance}" );
if ( !World.IsValid() )
{
Log.Error( "World is not valid" );
return;
}
var spawnPoint = World.GetEntrance( entrance );
if ( spawnPoint.IsValid() )
{
TeleportTo( spawnPoint.WorldPosition, spawnPoint.WorldRotation );
LastEntrance = entrance;
spawnPoint.OnTeleportTo( this );
}
else
{
Log.Error( $"No spawn point found in the world for entrance {entrance}" );
}
}
[Rpc.Owner]
public void TeleportTo( Vector3 pos, Rotation rot )
{
Log.Info( $"Teleporting to {pos} {rot}" );
WorldPosition = pos;
// WorldRotation = rot;
Transform.ClearInterpolation();
ModelLookAt( pos + rot.Forward );
GetComponent<CameraController>().SnapCamera();
GetComponent<CharacterController>().Velocity = Vector3.Zero;
}
[Rpc.Owner]
public void SetLayer( int layer )
{
WorldLayerObject.SetLayer( layer, true );
WorldManager.Instance.SetActiveWorld( layer );
}
public bool ShouldMove()
{
if ( IsSitting ) return false;
if ( InCutscene ) return false;
if ( VehicleRider.Vehicle.IsValid() ) return false;
if ( ItemPlacer.IsPlacing || ItemPlacer.IsMoving ) return false;
if ( Equips.TryGetEquippedItem<BaseCarriable>( Equips.EquipSlot.Tool, out var tool ) &&
tool.ShouldDisableMovement() ) return false;
if ( PlayerInteract.InteractionTarget.IsValid() )
{
if ( PlayerInteract.InteractionTarget.GetComponent<BaseNpc>().IsValid() ) return false;
}
if ( Components.TryGet<HideAndSeek>( out var hideAndSeek ) )
{
if ( HideAndSeek.Leader.IsValid() && HideAndSeek.Leader.IsRoundActive && hideAndSeek.IsBlind )
{
return false;
}
}
return true;
}
public void SnapToGrid()
{
WorldPosition = World.ItemGridToWorld( World.WorldToItemGrid( WorldPosition ) );
}
public void SetCollisionEnabled( bool state )
{
// CharacterController.Enabled = state;
}
public void SetCarriableVisibility( bool state )
{
}
[Rpc.Owner]
public void StartCutscene( Vector3 target )
{
Log.Info( $"Starting cutscene to {target}" );
CutsceneTarget = target;
InCutscene = true;
}
[Rpc.Owner]
public void StartCutscene()
{
Log.Info( $"Starting cutscene (empty)" );
CutsceneTarget = null;
InCutscene = true;
}
[Rpc.Owner]
public void EndCutscene()
{
Log.Info( "Ending cutscene" );
CutsceneTarget = null;
InCutscene = false;
}
public void SetVisible( bool state )
{
foreach ( var renderer in Model.GetComponentsInChildren<ModelRenderer>( true ) )
{
renderer.Enabled = state;
}
}
public static PlayerCharacter Get( Connection channel )
{
return Game.ActiveScene.GetAllComponents<PlayerCharacter>().FirstOrDefault( x => x.Network.Owner == channel );
}
[Rpc.Owner]
public void Notify( Notifications.NotificationType type, string text, float duration = 5f )
{
Notifications.Instance.AddNotification( type, text, duration );
}
[Rpc.Broadcast]
public static void NotifyAll( Notifications.NotificationType type, string text, float duration = 5f )
{
foreach ( var player in Game.ActiveScene.GetAllComponents<PlayerCharacter>() )
{
player.Notify( type, text, duration );
}
}
}
public interface IPlayerSaved
{
void PrePlayerSave( PlayerCharacter player );
void PostPlayerSave( PlayerCharacter player );
}