10 Commits

Author SHA1 Message Date
mccarreon
5647e33dd9 merge with master 2023-08-09 17:56:35 -07:00
mccarreon
bbf0a765d5 added nametags 2023-08-09 17:50:57 -07:00
cc877492d5 Merge pull request 'feature/Lucker-misc_RoundFramework' (#2) from feature/Lucker-misc_RoundFramework into master
Reviewed-on: #2
2023-08-07 05:13:13 +00:00
mccarreon
bce1f8e1f7 got racers animated and racing 2023-08-06 21:12:28 -07:00
mccarreon
3e9d64cb00 making racers 2023-08-06 18:41:00 -07:00
gamer147
90cc343560 Fixes convar 2023-08-05 22:08:57 -04:00
gamer147
5735087889 Updates based on PR feedback 2023-08-05 22:04:44 -04:00
mccarreon
b9dbe07e1f fixed camera with lookat functions 2023-08-04 21:16:49 -07:00
gamer147
1b34af21ee Updates roulette, fixes camera cursor and lucker entity setting along with minigame cleanup 2023-08-04 00:03:51 -04:00
mccarreon
4a338d9502 initial file creation 2023-08-03 19:34:23 -07:00
21 changed files with 425 additions and 95 deletions

View File

@@ -46,7 +46,7 @@
"Choices": [], "Choices": [],
"ConVarName": "lucker_minigames_per_round", "ConVarName": "lucker_minigames_per_round",
"DisplayName": "Minigames Per Round", "DisplayName": "Minigames Per Round",
"DefaultValue": "0", "DefaultValue": "1",
"Description": "The number of minigames played per round", "Description": "The number of minigames played per round",
"Group": "Other", "Group": "Other",
"Minimum": 1, "Minimum": 1,

View File

@@ -6,18 +6,18 @@ using Sandbox;
namespace LuckerGame.Entities; namespace LuckerGame.Entities;
/// <summary> /// <summary>
/// Represents a Player. /// Represents a person playing the game.
/// This could belong to a Client or a Bot and represents a common entity to operate on for games and keeping score /// This could belong to a Client or a Bot and represents a common entity to operate on for games and keeping score
/// </summary> /// </summary>
public partial class Lucker : Entity public partial class Lucker : Entity
{ {
/// <summary> /// <summary>
/// The entity this player controls. This value is networked and should be accessed through <see cref="Pawn"/>. /// The entity this lucker controls. This value is networked and should be accessed through <see cref="Pawn"/>.
/// </summary> /// </summary>
[Net] private Entity InternalPawn { get; set; } [Net] private Entity InternalPawn { get; set; }
/// <summary> /// <summary>
/// Accesses or sets the entity this player current controls /// Accesses or sets the entity this lucker currently controls
/// </summary> /// </summary>
public Entity Pawn public Entity Pawn
{ {
@@ -25,12 +25,15 @@ public partial class Lucker : Entity
set set
{ {
InternalPawn = value; InternalPawn = value;
if ( value != null )
{
value.Owner = this; value.Owner = this;
} }
} }
}
/// <summary> /// <summary>
/// Before the round has started, this player indicated they were ready /// Before the round has started, this lucker indicated they were ready
/// </summary> /// </summary>
[Net] public bool Ready { get; set; } [Net] public bool Ready { get; set; }
@@ -42,20 +45,20 @@ public partial class Lucker : Entity
[BindComponent] public LuckerStats Stats { get; } [BindComponent] public LuckerStats Stats { get; }
/// <summary> /// <summary>
/// Creates and properly sets up a Player entity for a given client /// Creates and properly sets up a <see cref="Lucker"/> entity for a given client
/// </summary> /// </summary>
/// <param name="client">the client to own the player</param> /// <param name="client">the client to own the lucker</param>
/// <returns>the newly created player</returns> /// <returns>the newly created lucker</returns>
public static Lucker CreateLuckerForClient( IClient client ) public static Lucker CreateLuckerForClient( IClient client )
{ {
var player = new Lucker(); var lucker = new Lucker();
client.Pawn = player; client.Pawn = lucker;
player.Owner = client as Entity; lucker.Owner = client as Entity;
player.Name = client.Name; lucker.Name = client.Name;
var camera = player.Components.Create<RTSCamera>(); lucker.Components.Create<RTSCamera>();
var stats = player.Components.Create<LuckerStats>(); lucker.Components.Create<LuckerStats>();
return player; return lucker;
} }
/// <summary> /// <summary>
@@ -66,12 +69,12 @@ public partial class Lucker : Entity
public static void ReadyUpCommand(bool readyState) public static void ReadyUpCommand(bool readyState)
{ {
var client = ConsoleSystem.Caller; var client = ConsoleSystem.Caller;
var player = client.Pawn as Lucker; var lucker = client.Pawn as Lucker;
player.SetReady( readyState ); lucker.SetReady( readyState );
} }
/// <summary> /// <summary>
/// Sets this player's ready state /// Sets this lucker's ready state
/// </summary> /// </summary>
/// <param name="ready">the ready state being set</param> /// <param name="ready">the ready state being set</param>
public void SetReady(bool ready) public void SetReady(bool ready)
@@ -79,7 +82,7 @@ public partial class Lucker : Entity
Ready = ready; Ready = ready;
if ( Game.IsServer ) if ( Game.IsServer )
{ {
Event.Run( LuckerEvent.PlayerReady, this, ready ); Event.Run( LuckerEvent.LuckerReady, this, ready );
} }
} }

View File

@@ -23,9 +23,9 @@ public partial class MinigameManager : Entity
private List<TypeDescription> AvailableMinigames { get; set; } private List<TypeDescription> AvailableMinigames { get; set; }
/// <summary> /// <summary>
/// The players involved in the current minigame /// The luckers involved in the current minigame
/// </summary> /// </summary>
private List<Lucker> InvolvedPlayers { get; set; } private List<Lucker> InvolvedLuckers { get; set; }
public override void Spawn() public override void Spawn()
{ {
@@ -33,14 +33,17 @@ public partial class MinigameManager : Entity
FindMinigames(); FindMinigames();
} }
public void StartMinigame(List<Lucker> players, string minigameName = null) public void StartMinigame(List<Lucker> luckers, string minigameName = null)
{ {
InvolvedPlayers = players.ToList(); InvolvedLuckers = luckers.ToList();
if (CheckForMinigames()) if (CheckForMinigames())
{ {
LoadedMinigame = string.IsNullOrEmpty( minigameName ) ? TypeLibrary.Create<Minigame>(AvailableMinigames.OrderBy( _ => Guid.NewGuid() ).FirstOrDefault().TargetType) : TypeLibrary.Create<Minigame>( minigameName ); LoadedMinigame = string.IsNullOrEmpty( minigameName )
? TypeLibrary.Create<Minigame>( AvailableMinigames.OrderBy( _ => Guid.NewGuid() ).FirstOrDefault()
.TargetType )
: TypeLibrary.Create<Minigame>( minigameName );
ChatBox.AddInformation( To.Everyone, $"Starting {LoadedMinigame.Name}" ); ChatBox.AddInformation( To.Everyone, $"Starting {LoadedMinigame.Name}" );
LoadedMinigame.Initialize( players ); LoadedMinigame.Initialize( luckers );
} }
} }
@@ -69,19 +72,19 @@ public partial class MinigameManager : Entity
} }
/// <summary> /// <summary>
/// Goes through the players included in the loaded minigame and deletes and nulls out any pawns assigned to them /// Goes through the luckers included in the loaded minigame and deletes and nulls out any pawns assigned to them
/// </summary> /// </summary>
private void CleanupPlayerPawns() private void CleanupLuckerPawns()
{ {
if ( LoadedMinigame is not { IsValid: true } || InvolvedPlayers == null) if ( LoadedMinigame is not { IsValid: true } || InvolvedLuckers == null)
{ {
Log.Warning( "Attempted to clean up players without a minigame loaded!" ); Log.Warning( "Attempted to clean up players without a minigame loaded!" );
return; return;
} }
InvolvedPlayers.ForEach( player => InvolvedLuckers.ForEach( lucker =>
{ {
player.Pawn.Delete(); lucker.Pawn?.Delete();
player.Pawn = null; lucker.Pawn = null;
} ); } );
} }
@@ -100,10 +103,16 @@ public partial class MinigameManager : Entity
{ {
return false; return false;
} }
EndMinigame();
LoadedMinigame.Cleanup();
LoadedMinigame = null;
return true; return true;
}
private void EndMinigame()
{
LoadedMinigame.Cleanup();
CleanupLuckerPawns();
LoadedMinigame.Delete();
LoadedMinigame = null;
InvolvedLuckers = null;
} }
} }

View File

@@ -47,12 +47,21 @@ public partial class RoundManager : Entity
#region In Progress State #region In Progress State
private const int MinigamesPerRound = 1; /// <summary>
/// The number of minigames that should be played per round, settable via a convar
/// </summary>
[ConVar.Replicated("lucker_minigames_per_round")] [ConVar.Replicated("lucker_minigames_per_round")]
public static int MinigamesLeftInRound { get; set; } private static int MinigamesPerRound { get; set; }
private List<Lucker> Players { get; set; } /// <summary>
/// The number of minigames left in the current round
/// </summary>
public int MinigamesLeftInRound { get; set; }
/// <summary>
/// The luckers playing in the current round
/// </summary>
private List<Lucker> Luckers { get; set; }
#endregion #endregion
/// <inheritdoc/> /// <inheritdoc/>
@@ -83,7 +92,7 @@ public partial class RoundManager : Entity
MinigamesLeftInRound--; MinigamesLeftInRound--;
if ( MinigamesLeftInRound > 0 ) if ( MinigamesLeftInRound > 0 )
{ {
MinigameManager.StartMinigame( Players ); MinigameManager.StartMinigame( Luckers );
} }
else else
{ {
@@ -94,11 +103,12 @@ public partial class RoundManager : Entity
} }
/// <summary> /// <summary>
/// Is triggered whenever a player readies up /// Is triggered whenever a lucker readies up or readies down
/// </summary> /// </summary>
/// <param name="readyLucker">the player that readied up, discarded</param> /// <param name="readyLucker">the lucker that readied up</param>
[LuckerEvent.PlayerReady] /// <param name="ready">the lucker's ready state</param>
public void HandlePlayerReady( Lucker readyLucker, bool ready ) [LuckerEvent.LuckerReady]
public void HandleLuckerReady( Lucker readyLucker, bool ready )
{ {
if ( RoundState != RoundState.NotStarted && RoundState != RoundState.StartCountdown ) if ( RoundState != RoundState.NotStarted && RoundState != RoundState.StartCountdown )
{ {
@@ -107,9 +117,9 @@ public partial class RoundManager : Entity
Log.Info( $"{readyLucker.Client.Name} set ready to {ready}" ); Log.Info( $"{readyLucker.Client.Name} set ready to {ready}" );
var message = $"{readyLucker.Client.Name} is {(ready ? "now ready." : "no longer ready.")}"; var message = $"{readyLucker.Client.Name} is {(ready ? "now ready." : "no longer ready.")}";
ChatBox.AddInformation( To.Everyone, message ); ChatBox.AddInformation( To.Everyone, message );
var players = All.OfType<Lucker>().ToList(); var luckers = All.OfType<Lucker>().ToList();
var readiedCount = players.Count( player => player.Ready ); var readiedCount = luckers.Count( lucker => lucker.Ready );
var totalCount = players.Count; var totalCount = luckers.Count;
if ( (float)readiedCount / totalCount > RequiredReadyPercent && RoundState == RoundState.NotStarted ) if ( (float)readiedCount / totalCount > RequiredReadyPercent && RoundState == RoundState.NotStarted )
{ {
Log.Info( "Countdown started" ); Log.Info( "Countdown started" );
@@ -132,13 +142,13 @@ public partial class RoundManager : Entity
} }
RoundState = RoundState.InProgress; RoundState = RoundState.InProgress;
Players = All.OfType<Lucker>().ToList(); Luckers = All.OfType<Lucker>().ToList();
Players.ForEach( player => Luckers.ForEach( lucker =>
{ {
player.Ready = false; lucker.Ready = false;
} ); } );
MinigamesLeftInRound = MinigamesPerRound; MinigamesLeftInRound = MinigamesPerRound;
MinigameManager.StartMinigame( Players, minigameName ); MinigameManager.StartMinigame( Luckers, minigameName );
} }
[ConCmd.Server( "start_round" )] [ConCmd.Server( "start_round" )]

View File

@@ -76,7 +76,7 @@ public partial class Weapon : AnimatedEntity
} }
/// <summary> /// <summary>
/// Called when the weapon is either removed from the player, or holstered. /// Called when the weapon is either removed from the pawn, or holstered.
/// </summary> /// </summary>
public void OnHolster() public void OnHolster()
{ {

View File

@@ -3,10 +3,10 @@ using Sandbox;
namespace LuckerGame.Components.Lucker.Cameras; namespace LuckerGame.Components.Lucker.Cameras;
public abstract class AbstractCamera : EntityComponent<Entities.Lucker>, ISingletonComponent public abstract partial class AbstractCamera : EntityComponent<Entities.Lucker>, ISingletonComponent
{ {
public virtual bool ShouldShowCursor => false; public virtual bool ShouldShowCursor => false;
protected Vector3 CameraPosition { get; set; } public Vector3 CameraPosition { get; set; }
protected Rotation CameraRotation { get; set; } protected Rotation CameraRotation { get; set; }
protected float FieldOfView { get; set; } protected float FieldOfView { get; set; }
protected IEntity FirstPersonViewer { get; set; } protected IEntity FirstPersonViewer { get; set; }
@@ -20,7 +20,7 @@ public abstract class AbstractCamera : EntityComponent<Entities.Lucker>, ISingle
/// <summary> /// <summary>
/// Handles any input dependent camera updates (ie moving around a top down cam) /// Handles any input dependent camera updates (ie moving around a top down cam)
/// </summary> /// </summary>
public abstract void BuildInput(); public virtual void BuildInput() { }
/// <summary> /// <summary>
/// Applies Camera parameters to the static Camera /// Applies Camera parameters to the static Camera

View File

@@ -0,0 +1,41 @@
using Sandbox;
namespace LuckerGame.Components.Lucker.Cameras;
public partial class FixedCamera : AbstractCamera, ISingletonComponent
{
[Net] public float FieldOfViewValue { get; set; } = 70f;
public override bool ShouldShowCursor => true;
protected override void UpdateCameraParameters()
{
FieldOfView = FieldOfViewValue;
FirstPersonViewer = null;
SoundSource = new Transform { Position = CameraPosition };
}
/// <summary>
/// ClientRpc doesn't allow for nullable versions of non-nullable types
/// so here we are
/// </summary>
public void UpdateCameraPositionRotation( Vector3? position = null, Rotation? rotation = null )
{
CameraPosition = (Vector3)(position.Equals( null ) ? CameraPosition : position);
CameraRotation = (Rotation)(rotation.Equals( null ) ? CameraRotation : rotation);
}
[ClientRpc]
public void LookAt( Vector3 position )
{
UpdateCameraPositionRotation( position );
}
[ClientRpc]
public void LookAt( Rotation rotation )
{
UpdateCameraPositionRotation( rotation: rotation );
}
[ClientRpc]
public void LookAt( Vector3 position, Rotation rotation )
{
UpdateCameraPositionRotation( position, rotation );
}
}

View File

@@ -4,16 +4,16 @@ namespace LuckerGame.Events;
public static partial class LuckerEvent public static partial class LuckerEvent
{ {
public const string PlayerReady = "lucker.playerReady"; public const string LuckerReady = "lucker.luckerReady";
/// <summary> /// <summary>
/// Event is run on the server whenever a player changes ready state /// Event is run on the server whenever a lucker changes ready state
/// The event handler is given the player that readied up and their new ready state /// The event handler is given the lucker that readied up and their new ready state
/// </summary> /// </summary>
[MethodArguments(typeof(Entities.Lucker), typeof(bool))] [MethodArguments(typeof(Entities.Lucker), typeof(bool))]
public class PlayerReadyAttribute : EventAttribute public class LuckerReadyAttribute : EventAttribute
{ {
public PlayerReadyAttribute() : base(PlayerReady) public LuckerReadyAttribute() : base(LuckerReady)
{ {
} }
} }

View File

@@ -15,8 +15,8 @@ public abstract class Minigame : Entity
/// <summary> /// <summary>
/// Initializes the minigame with a list of luckers playing it. /// Initializes the minigame with a list of luckers playing it.
/// </summary> /// </summary>
/// <param name="players">the players who made it into the minigame</param> /// <param name="luckers">the luckers who made it into the minigame</param>
public abstract void Initialize(List<Lucker> players); public abstract void Initialize(List<Lucker> luckers);
/// <summary> /// <summary>
/// Once a minigame is loaded and initialized, this method is called once per server tick. /// Once a minigame is loaded and initialized, this method is called once per server tick.

View File

@@ -14,50 +14,53 @@ namespace LuckerGame.Minigames.RussianRoulette;
public class RussianRouletteMinigame : Minigame public class RussianRouletteMinigame : Minigame
{ {
public override string Name => "Russian Roulette"; public override string Name => "Russian Roulette";
private List<Lucker> Players { get; set; } private List<Lucker> Luckers { get; set; }
private Pawn Shooter { get; set; } private Pawn Shooter { get; set; }
private const float ShooterDistance = 80f; private const float ShooterDistance = 80f;
private const float TimeBetweenShots = 7f; private const float TimeBetweenShots = 7f;
private const float TimeBetweenDeathAndEnd = 5f; private const float TimeBetweenDeathAndEnd = 5f;
private const string ShooterName = "The Russian";
private int Taunted = 0; private int Taunted = 0;
private Pawn ShooterTarget;
private List<Pawn> DeadVictims => Players private List<Pawn> DeadVictims => Luckers
.Select( player => player.Pawn as Pawn ) .Select( lucker => lucker.Pawn as Pawn )
.Where( pawn => !pawn.IsValid || pawn.LifeState != LifeState.Alive ) .Where( pawn => pawn is not { IsValid: true } || pawn.LifeState != LifeState.Alive )
.ToList(); .ToList();
private TimeSince TimeSinceShot { get; set; } private TimeSince TimeSinceShot { get; set; }
private TimeSince TimeSinceDeadVictim { get; set; } private TimeSince TimeSinceDeadVictim { get; set; }
public override void Initialize( List<Lucker> players ) public override void Initialize( List<Lucker> luckers )
{ {
Players = players; Luckers = luckers;
Shooter = new Pawn(); Shooter = new Pawn();
Shooter.Name = ShooterName;
var shooterInventory = Shooter.Components.Create<PawnInventory>(); var shooterInventory = Shooter.Components.Create<PawnInventory>();
shooterInventory.AddWeapon( new RussianPistol() ); shooterInventory.AddWeapon( new RussianPistol() );
// Setup cameras for players // Setup cameras for luckers
Players.ForEach( player => Luckers.ForEach( lucker =>
{ {
player.Components.Create<RTSCamera>(); lucker.Components.Create<RTSCamera>();
player.Position = Shooter.Position; lucker.Position = Shooter.Position;
} ); } );
Players.Select((player, i) => (Player: player, Index: i) ).ToList().ForEach( pair => Luckers.Select((lucker, i) => (Lucker: lucker, Index: i) ).ToList().ForEach( pair =>
{ {
var player = pair.Player; var lucker = pair.Lucker;
var index = pair.Index; var index = pair.Index;
var pawn = new Pawn(); var pawn = new Pawn();
pawn.Name = player.Name; pawn.Name = lucker.Name;
pawn.Tags.Add( "victim" ); pawn.Tags.Add( "victim" );
pawn.Health = 1; pawn.Health = 1;
player.Pawn = pawn; lucker.Pawn = pawn;
pawn.DressFromClient( player.Client ); pawn.DressFromClient( lucker.Client );
var pawnOffset = ShooterDistance * (index % 2 == 0 ? Vector3.Forward : Vector3.Right) * (index % 4 >= 2 ? -1 : 1); var pawnOffset = ShooterDistance * (index % 2 == 0 ? Vector3.Forward : Vector3.Right) * (index % 4 >= 2 ? -1 : 1);
player.Pawn.Position = Shooter.Position + pawnOffset; lucker.Pawn.Position = Shooter.Position + pawnOffset;
pawn.LookAt(Shooter.Position); pawn.LookAt(Shooter.Position);
} ); } );
TimeSinceShot = 0; TimeSinceShot = 0;
@@ -66,11 +69,12 @@ public class RussianRouletteMinigame : Minigame
public override bool Tick() public override bool Tick()
{ {
// Someone is dead, we're getting ready to end
if ( DeadVictims.Any() ) if ( DeadVictims.Any() )
{ {
if ( Taunted != int.MaxValue ) if ( Taunted != int.MaxValue )
{ {
ChatBox.AddChatEntry( To.Everyone, "Shooter", "Heh, nothing personnel, kid." ); ChatBox.AddChatEntry( To.Everyone, Shooter.Name, "Heh, nothing personnel, kid." );
Taunted = int.MaxValue; Taunted = int.MaxValue;
TimeSinceDeadVictim = 0; TimeSinceDeadVictim = 0;
} }
@@ -86,21 +90,22 @@ public class RussianRouletteMinigame : Minigame
Shooter.Inventory.ActiveWeapon.PrimaryAttack(); Shooter.Inventory.ActiveWeapon.PrimaryAttack();
if ( !DeadVictims.Any() ) if ( !DeadVictims.Any() )
{ {
ChatBox.AddChatEntry( To.Everyone, "Shooter", "Fucking lag..." ); ChatBox.AddChatEntry( To.Everyone, Shooter.Name, "Fucking lag..." );
} }
} }
else if ( TimeSinceShot > TimeBetweenShots * .8f && Taunted == 1) else if ( TimeSinceShot > TimeBetweenShots * .8f && Taunted == 1)
{ {
var victim = Players.Select( player => player.Pawn as Pawn ) ShooterTarget = Luckers.Select( lucker => lucker.Pawn as Pawn )
.OrderBy( _ => Guid.NewGuid() ) .OrderBy( _ => Guid.NewGuid() )
.FirstOrDefault(); .FirstOrDefault();
Shooter.LookAt( victim.Position ); Shooter.LookAt( ShooterTarget.Position );
ChatBox.AddChatEntry( To.Everyone, "Shooter", $"I'm gonna eat you up, {victim.Name}" ); var chance = 1f / Shooter.Inventory.ActiveWeapon.Ammo;
ChatBox.AddChatEntry( To.Everyone, Shooter.Name, $"Good luck, {ShooterTarget.Name}! You have a {chance:P0} chance to die!" );
Taunted++; Taunted++;
} }
else if ( TimeSinceShot > TimeBetweenShots / 2 && Taunted == 0) else if ( TimeSinceShot > TimeBetweenShots / 2 && Taunted == 0)
{ {
ChatBox.AddChatEntry( To.Everyone, "Shooter", "Im gettin' ready!" ); ChatBox.AddChatEntry( To.Everyone, Shooter.Name, "Im gettin' ready!" );
Taunted++; Taunted++;
} }

View File

@@ -0,0 +1,23 @@
using LuckerGame.UI;
using Sandbox;
public class HoveringTextCreator : Entity
{
private string Text { get; set; }
private Entity TargetEntity { get; set; }
public HoveringTextCreator()
{
}
public HoveringTextCreator(string text, Entity targetEntity)
{
Text = text;
TargetEntity = targetEntity;
}
public override void ClientSpawn()
{
base.ClientSpawn();
var hoveringText = new HoveringText(Text, TargetEntity);
}
}

View File

@@ -0,0 +1,64 @@
using LuckerGame.Entities;
using LuckerGame.UI;
using Sandbox;
using System;
using System.IO;
using System.Linq;
using System.Numerics;
using System.Reflection.Metadata.Ecma335;
public class Racer : Pawn
{
public float Speed;
private float SpeedModifier = 1f;
private Random Random { get; set; }
private HoveringText NameTag { get; set; }
public Racer()
{
}
public Racer( Random random )
{
Random = random;
}
public override void ClientSpawn()
{
base.ClientSpawn();
NameTag = new( Name, this );
}
public void ContinueRacing()
{
SetAnimParameter( "move_x", Speed * 500f * SpeedModifier );
ModifySpeed();
Position = Position.WithY( Position.y - (Speed * SpeedModifier) );
}
private void ModifySpeed()
{
var roll = Random.NextDouble();
switch ( roll )
{
case >= .90 and < .95:
SpeedModifier = 2f;
break;
case >= .95:
SpeedModifier = 0.5f;
break;
}
}
public void StopRacing()
{
SetAnimParameter( "move_x", 0 );
}
public void GenerateSpeed( double minSpeed, double maxSpeed )
{
Speed = (float)(RandomExtensions.NextDouble( Random, minSpeed, maxSpeed ));
}
}

View File

@@ -0,0 +1,123 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Reflection.Metadata.Ecma335;
using LuckerGame.Components.Lucker.Cameras;
using LuckerGame.Components.Pawn;
using LuckerGame.Entities;
using Sandbox;
using Sandbox.UI;
namespace LuckerGame.Minigames.TerryRaces;
[Library( "mg_terry_races" )]
public class TerryRaces : Minigame
{
public override string Name => "Terry Races";
private Random random = new Random();
private HoveringTextCreator NameTag { get; set; }
private Racer WinningRacer = null;
private FixedCamera Camera;
private List<Lucker> Players { get; set; }
private List<Racer> Racers { get; set; } = new List<Racer>();
private List<string> RacerNames = new List<string>
{
"Terrance",
"Terrie",
"TearBear",
"Theresa"
};
private float StartingY = 290f;
private float FinishLineY = -300f;
private float StartingX = 90f;
private float RacerXOffset = 60f;
private float MinimumSpeed = .90f;
private float MaximumSpeed = 1.1f;
public override void Initialize( List<Lucker> players )
{
Players = players;
// Setup cameras for players
Players.ForEach( player =>
{
Camera = player.Components.Create<FixedCamera>();
Camera.LookAt( new Vector3(0f, 0f, 400f), Rotation.FromPitch( -270 ) );
Camera.FieldOfViewValue = 100f;
} );
SpawnRacers();
}
public override void Tick()
{
Racers.ForEach( racer =>
{
if (WinningRacer == null)
{
GetWinningRacer();
racer.ContinueRacing();
}
else
{
racer.StopRacing();
}
} );
}
public override void Cleanup()
{
}
private void SpawnRacers()
{
for ( int i = 0; i < RacerNames.Count; i++ )
{
var clothing = new ClothingContainer();
var racer = new Racer(random);
var x = StartingX - RacerXOffset * i;
clothing.Toggle( GetRandomBottomClothing() );
clothing.Toggle( GetRandomHatClothing() );
clothing.DressEntity( racer );
racer.Name = RacerNames[i];
racer.Position = new Vector3( x, StartingY, 0 );
racer.Rotation = Rotation.FromYaw( -90 );
racer.GenerateSpeed(MinimumSpeed, MaximumSpeed );
Racers.Add( racer );
}
}
private Clothing GetRandomBottomClothing()
{
return ResourceLibrary
.GetAll<Clothing>()
.Where( c => c.Category == Clothing.ClothingCategory.Bottoms )
.OrderBy( _ => Guid.NewGuid() )
.FirstOrDefault();
}
private Clothing GetRandomHatClothing()
{
return ResourceLibrary
.GetAll<Clothing>()
.Where( c => c.Category == Clothing.ClothingCategory.Hat )
.OrderBy( _ => Guid.NewGuid() )
.FirstOrDefault();
}
private void GetWinningRacer()
{
foreach ( Racer racer in Racers )
{
if ( racer.Position.y <= FinishLineY )
{
WinningRacer = racer;
Log.Info( $"Winning racer: {racer.Name}" );
}
}
}
}

11
code/RandomExtensions.cs Normal file
View File

@@ -0,0 +1,11 @@
using System;
public static class RandomExtensions
{
public static double NextDouble(
this Random random,
double minValue,
double maxValue )
{
return random.NextDouble() * (maxValue - minValue) + minValue;
}
}

View File

@@ -0,0 +1,30 @@
@using Sandbox;
@using Sandbox.UI;
@namespace LuckerGame.UI
@attribute [StyleSheet]
@inherits WorldPanel
<root class="card">
<label class="text">@Text</label>
</root>
@code {
private Entity TargetEntity;
private string Text;
public HoveringText(string text, Entity targetEntity)
{
Text = text;
TargetEntity = targetEntity;
}
[GameEvent.Client.Frame]
private void OnFrame()
{
if (!TargetEntity.IsValid()) return;
Log.Info($"{Text} to {TargetEntity.Position}");
Position = TargetEntity.Position + Vector3.Up * 65f;
Rotation = Rotation.LookAt(-Screen.GetDirection(new Vector2(Screen.Width * 0.5f, Screen.Height * 0.5f)));
}
}

View File

@@ -0,0 +1,7 @@
hoveringtext {
white-space: nowrap;
.text {
font-size: 150px;
color: white;
}
}

View File

@@ -12,7 +12,6 @@
<VoiceList/> <VoiceList/>
<VotingLobby/> <VotingLobby/>
<Scoreboard/> <Scoreboard/>
<CameraCursor/>
</root> </root>
@code @code

View File

@@ -5,10 +5,13 @@
@attribute [StyleSheet] @attribute [StyleSheet]
@inherits Panel @inherits Panel
@if (ShouldShowCursor) <root>
{ @if (ShouldShowCursor)
<root/> {
} <div class="camera-cursor"/>
}
</root>
@code { @code {

View File

@@ -1,3 +1,5 @@
CameraCursor { CameraCursor {
.camera-cursor {
pointer-events: all; pointer-events: all;
}
} }

View File

@@ -10,9 +10,9 @@
<root> <root>
<div class="scoreboard-panel"> <div class="scoreboard-panel">
@foreach (var player in Luckers) @foreach (var lucker in Luckers)
{ {
<label>@player.Name</label> <label>@lucker.Name</label>
} }
</div> </div>
</root> </root>
@@ -23,7 +23,7 @@
protected override int BuildHash() protected override int BuildHash()
{ {
return HashCode.Combine(Luckers.Select(player => player.Name).ToList()); return HashCode.Combine(Luckers.Select(lucker => lucker.Name).ToList());
} }
} }

View File

@@ -27,7 +27,7 @@
<div class="voting-panel primary-color-translucent-background"> <div class="voting-panel primary-color-translucent-background">
@if (RoundManager.RoundState == RoundState.NotStarted) @if (RoundManager.RoundState == RoundState.NotStarted)
{ {
<label class="header">Waiting for players...</label> <label class="header">Waiting for luckers...</label>
} }
else else
{ {