Components/HideAndSeek.cs
using System;
using Clover.Interactable;
using Clover.Player;
using Clover.Ui;
namespace Clover.Components;
public class HideAndSeek : Component, IInteract
{
public static HideAndSeek Leader =>
Game.ActiveScene?.GetAllComponents<HideAndSeek>()
.FirstOrDefault( x => x.Network.Owner != null && x.Network.Owner.IsHost );
[Sync, HostSync] public bool IsSeeker { get; set; }
[Sync, Property] public TimeSince RoundStart { get; set; }
[Sync, Property] public bool IsRoundActive { get; set; }
[Property] public SoundEvent RoundStartSound { get; set; }
[Property] public SoundEvent RoundEndSound { get; set; }
[Property] public SoundEvent CatchSound { get; set; }
[Property] public SoundEvent BlindCountdownSound { get; set; }
[Property] public SoundEvent BlindEndSound { get; set; }
private bool IsLeader => Networking.IsHost;
private const int RoundDuration = 120;
private const int BlindDuration = 20;
public bool IsBlind => IsSeeker && Leader.RoundStart < BlindDuration;
public float BlindSecondsLeft => BlindDuration - Leader.RoundStart;
public float RoundSecondsLeft => RoundDuration - Leader.RoundStart;
[ConCmd( "clover_hns_start" )]
public static void Start()
{
if ( !Networking.IsHost )
{
PlayerCharacter.Local.Notify( Notifications.NotificationType.Error,
"Only the host can start a game of hide and seek." );
return;
}
if ( !Leader.IsValid() )
{
PlayerCharacter.Local.Notify( Notifications.NotificationType.Error,
"Only the host can start a game of hide and seek." );
return;
}
var players = Game.ActiveScene.GetAllComponents<HideAndSeek>().ToList();
if ( players.Count < 2 )
{
PlayerCharacter.Local.Notify( Notifications.NotificationType.Error,
"Not enough players to start a game of hide and seek." );
return;
}
// reset all players
foreach ( var player in players )
{
player.SetSeeker( false );
}
// pick a random seeker
var seeker = Random.Shared.FromList( players );
seeker.SetSeeker( true );
// start the round
Leader.RoundStart = 0;
Leader.IsRoundActive = true;
Log.Info( "Started a game of hide and seek" );
foreach ( var player in players )
{
player.GetComponent<PlayerCharacter>()
.Notify( Notifications.NotificationType.Info, "A new round of hide and seek has started!" );
}
SoundEx.Play( Leader.RoundStartSound );
}
[Rpc.Owner]
public void SetSeeker( bool isSeeker )
{
IsSeeker = isSeeker;
Log.Info( $"Player {GameObject.Name} is now a {(isSeeker ? "seeker" : "hider")}" );
}
private TimeSince _lastBlindCountdownSound;
protected override void OnFixedUpdate()
{
if ( !IsRoundActive )
return;
if ( IsLeader && RoundStart > RoundDuration )
{
IsRoundActive = false;
foreach ( var player in Scene.GetAllComponents<HideAndSeek>() )
{
player.GetComponent<PlayerCharacter>()
.Notify( Notifications.NotificationType.Info, "The round has ended." );
}
SoundEx.Play( RoundEndSound );
return;
}
if ( IsSeeker )
{
if ( IsBlind && _lastBlindCountdownSound > 1 )
{
Sound.Play( BlindCountdownSound );
_lastBlindCountdownSound = 0;
}
}
else
{
}
}
public bool CanInteract( PlayerCharacter player )
{
var hns = player.GetComponent<HideAndSeek>();
// Log.Info(
// $"HnS:{GameObject.Name} - IsSeeker: {IsSeeker}, IsRoundActive: {Leader.IsRoundActive}, IsSelf: {player != Components.Get<PlayerCharacter>()}/{IsProxy}" );
return hns.IsSeeker && Leader.IsValid() && Leader.IsRoundActive && IsProxy;
}
public void StartInteract( PlayerCharacter player )
{
Log.Info( $"{player.GameObject.Name} is trying to catch {GameObject.Name}" );
using ( Rpc.FilterInclude( Connection.Host ) )
{
player.GetComponent<HideAndSeek>().Catch( this );
}
}
private bool AreAllCaught => Scene.GetAllComponents<HideAndSeek>().All( x => x.IsSeeker );
// Broadcasted but only called on the host
[Rpc.Broadcast]
public void Catch( HideAndSeek target )
{
var catcher = PlayerCharacter.Get( Rpc.Caller );
var caught = target.Components.Get<PlayerCharacter>();
caught.Notify( Notifications.NotificationType.Warning, "You have been caught! You are now a seeker." );
catcher.Notify( Notifications.NotificationType.Success, $"You caught {caught.GameObject.Name}!" );
Log.Info( $"{catcher.GameObject.Name} caught {caught.GameObject.Name}" );
target.IsSeeker = true;
target.SetSeeker( true );
SoundEx.Play( CatchSound, target.WorldPosition );
CheckRoundEnd();
}
private void CheckRoundEnd()
{
if ( !AreAllCaught )
{
return;
}
Leader.RoundStart = 0;
Leader.IsRoundActive = false;
Log.Info( "All players have been caught. Round ended." );
foreach ( var player in Scene.GetAllComponents<HideAndSeek>() )
{
player.GetComponent<PlayerCharacter>()
.Notify( Notifications.NotificationType.Info,
"All players have been caught. The round has ended." );
}
SoundEx.Play( RoundEndSound );
}
public string GetInteractName()
{
return "Catch";
}
}