feature/Lucker-misc_RoundFramework #2
15
.sbproj
@@ -37,7 +37,20 @@
|
|||||||
"GameSettings": {},
|
"GameSettings": {},
|
||||||
"Addons": "",
|
"Addons": "",
|
||||||
"PreLaunchCommand": "",
|
"PreLaunchCommand": "",
|
||||||
"PostLaunchCommand": ""
|
"PostLaunchCommand": "lucker_minigames_per_round 1"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"PackageSettings": [
|
||||||
|
{
|
||||||
|
"DisplayType": "Integer",
|
||||||
|
"Choices": [],
|
||||||
|
"ConVarName": "lucker_minigames_per_round",
|
||||||
|
"DisplayName": "Minigames Per Round",
|
||||||
|
"DefaultValue": "1",
|
||||||
|
"Description": "The number of minigames played per round",
|
||||||
|
"Group": "Other",
|
||||||
|
|
|||||||
|
"Minimum": 1,
|
||||||
|
"Maximum": 12
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,39 @@
|
|||||||
using LuckerGame.Components.Lucker.Cameras;
|
using LuckerGame.Components.Lucker.Cameras;
|
||||||
|
using LuckerGame.EntityComponents.Lucker;
|
||||||
using LuckerGame.Events;
|
using LuckerGame.Events;
|
||||||
using Sandbox;
|
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 currently controls
|
/// The entity this lucker controls. This value is networked and should be accessed through <see cref="Pawn"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Entity Pawn { get; set; }
|
[Net] private Entity InternalPawn { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Before the round has started, this player indicated they were ready
|
/// Accesses or sets the entity this lucker currently controls
|
||||||
|
/// </summary>
|
||||||
|
public Entity Pawn
|
||||||
|
{
|
||||||
|
get => InternalPawn;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
InternalPawn = value;
|
||||||
|
if ( value != null )
|
||||||
|
{
|
||||||
|
value.Owner = this;
|
||||||
|
}
|
||||||
|
para
commented
According to Bing Chat this can be According to Bing Chat this can be
```cs
value?.Owner = this
```
conco
commented

Please ask bing chat which version of C# it believes supports this feature
para
commented
Yeah that's what I thought. I asked it and it just confidently lies about it and doesn't admit it's wrong. I found this stack overflow article on the topic and they just suggest a Yeah that's what I thought. I asked it and it just confidently lies about it and doesn't admit it's wrong. [I found this stack overflow article on the topic and they just suggest a `setValue()` method but that's kinda dumb for one-offs.](https://stackoverflow.com/questions/35887106/using-the-null-conditional-operator-on-the-left-hand-side-of-an-assignment)
|
|||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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; }
|
||||||
|
|
||||||
@@ -25,20 +42,23 @@ public partial class Lucker : Entity
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[BindComponent] public AbstractCamera Camera { get; }
|
[BindComponent] public AbstractCamera Camera { 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>();
|
||||||
|
lucker.Components.Create<LuckerStats>();
|
||||||
|
para
commented
nit: unused locals nit: unused locals
|
|||||||
|
|
||||||
return player;
|
return lucker;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -49,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)
|
||||||
@@ -62,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 );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,8 +12,20 @@ namespace LuckerGame.Entities;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class MinigameManager : Entity
|
public partial class MinigameManager : Entity
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The currently loaded minigame
|
||||||
|
/// </summary>
|
||||||
[Net] public Minigame LoadedMinigame { get; private set; }
|
[Net] public Minigame LoadedMinigame { get; private set; }
|
||||||
private List<Minigame> AvailableMinigames { get; set; }
|
|
||||||
|
/// <summary>
|
||||||
|
/// A cached list of available minigames. Gets reloaded on a hotreload
|
||||||
|
/// </summary>
|
||||||
|
private List<TypeDescription> AvailableMinigames { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The luckers involved in the current minigame
|
||||||
|
/// </summary>
|
||||||
|
private List<Lucker> InvolvedLuckers { get; set; }
|
||||||
|
para
commented
I think this correctly caches the Also, I feel like it'd be nice to standardize the naming of these variables to disambiguate references to I think this correctly caches the `Lucker` list when the minigame is created, which is nice. Although I'm wondering how we deal with Luckers who leave before a minigame is ended. If there's other logic that deletes a `Lucker` in response to the client disconnecting, will the logic in `CleanupPlayerPawns()` fail / crash?
Also, I feel like it'd be nice to standardize the naming of these variables to disambiguate references to `Player`s and `Lucker`s. Might as well just name variables after the type to avoid clashing the names.
conco
commented
I agree with the variable naming, and will go through and do that. I think an investigation into handling client disconnects should be done in another ticket however, as I believe there's both an investigation in how S&box handles a client disconnect by default, and also a discussion in how we handle it, such as keeping the player around for the remainder of the round but assigning them to a bot, only a minigame, leave it up to the minigames, etc. I agree with the variable naming, and will go through and do that.
I think an investigation into handling client disconnects should be done in another ticket however, as I believe there's both an investigation in how S&box handles a client disconnect by default, and also a discussion in how we handle it, such as keeping the player around for the remainder of the round but assigning them to a bot, only a minigame, leave it up to the minigames, etc.
conco
commented
Variables should be renamed now based on the naming of 'Lucker' as opposed to player for the most part Variables should be renamed now based on the naming of 'Lucker' as opposed to player for the most part
|
|||||||
|
|
||||||
public override void Spawn()
|
public override void Spawn()
|
||||||
{
|
{
|
||||||
@@ -21,13 +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)
|
||||||
{
|
{
|
||||||
|
InvolvedLuckers = luckers.ToList();
|
||||||
if (CheckForMinigames())
|
if (CheckForMinigames())
|
||||||
{
|
{
|
||||||
LoadedMinigame = string.IsNullOrEmpty( minigameName ) ? AvailableMinigames.OrderBy( _ => Guid.NewGuid() ).FirstOrDefault() : TypeLibrary.Create<Minigame>( minigameName );
|
LoadedMinigame = string.IsNullOrEmpty( minigameName )
|
||||||
|
para
commented
nit: more readable on separate lines nit: more readable on separate lines
|
|||||||
|
? 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 );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +62,7 @@ public partial class MinigameManager : Entity
|
|||||||
{
|
{
|
||||||
AvailableMinigames = TypeLibrary.GetTypes<Minigame>()
|
AvailableMinigames = TypeLibrary.GetTypes<Minigame>()
|
||||||
.Where( type => !type.IsAbstract && !type.IsInterface )
|
.Where( type => !type.IsAbstract && !type.IsInterface )
|
||||||
.Select( td => TypeLibrary.Create<Minigame>( td.TargetType ) ).ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Event.Hotload]
|
[Event.Hotload]
|
||||||
@@ -55,12 +71,48 @@ public partial class MinigameManager : Entity
|
|||||||
FindMinigames();
|
FindMinigames();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Tick()
|
/// <summary>
|
||||||
|
/// Goes through the luckers included in the loaded minigame and deletes and nulls out any pawns assigned to them
|
||||||
|
/// </summary>
|
||||||
|
private void CleanupLuckerPawns()
|
||||||
|
{
|
||||||
|
if ( LoadedMinigame is not { IsValid: true } || InvolvedLuckers == null)
|
||||||
|
{
|
||||||
|
Log.Warning( "Attempted to clean up players without a minigame loaded!" );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
InvolvedLuckers.ForEach( lucker =>
|
||||||
|
{
|
||||||
|
lucker.Pawn?.Delete();
|
||||||
|
lucker.Pawn = null;
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called once per tick by the RoundManager. Ticks any running minigame.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>true if the current minigame has ended, else false</returns>
|
||||||
|
public bool Tick()
|
||||||
{
|
{
|
||||||
if ( LoadedMinigame is not { IsValid: true } )
|
if ( LoadedMinigame is not { IsValid: true } )
|
||||||
{
|
{
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
LoadedMinigame.Tick();
|
var ended = LoadedMinigame.Tick();
|
||||||
|
if ( !ended )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
EndMinigame();
|
||||||
|
para
commented
Should we clear Should we clear `InvolvedPlayers` here?
conco
commented
we can, but I don't believe it would be necessary. Since LoadedMinigame gets nulled out (which actually should be deleted before we do so, need to fix), the manager shouldn't necessarily be doing anything with the list until a new one is loaded (along with a new list), although some additional checks would be nice. we can, but I don't believe it would be necessary. Since LoadedMinigame gets nulled out (which actually should be deleted before we do so, need to fix), the manager shouldn't necessarily be doing anything with the list until a new one is loaded (along with a new list), although some additional checks would be nice.
|
|||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EndMinigame()
|
||||||
|
{
|
||||||
|
LoadedMinigame.Cleanup();
|
||||||
|
CleanupLuckerPawns();
|
||||||
|
LoadedMinigame.Delete();
|
||||||
|
LoadedMinigame = null;
|
||||||
|
InvolvedLuckers = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,11 +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>
|
||||||
|
para
commented
Shouldn't this be on Shouldn't this be on `MinigamesPerRound` and not `MinigamesLeftInRound`?
conco
commented
Yep, should be fixed now Yep, should be fixed now
|
|||||||
|
[ConVar.Replicated("lucker_minigames_per_round")]
|
||||||
|
private static int MinigamesPerRound { get; set; }
|
||||||
|
|
||||||
private int MinigamesLeftInRound { get; set; }
|
/// <summary>
|
||||||
|
/// The number of minigames left in the current round
|
||||||
|
/// </summary>
|
||||||
|
public int MinigamesLeftInRound { get; set; }
|
||||||
|
|
||||||
private List<Lucker> Players { get; set; }
|
/// <summary>
|
||||||
|
/// The luckers playing in the current round
|
||||||
|
/// </summary>
|
||||||
|
private List<Lucker> Luckers { get; set; }
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
@@ -76,16 +86,29 @@ public partial class RoundManager : Entity
|
|||||||
|
|
||||||
if ( RoundState == RoundState.InProgress )
|
if ( RoundState == RoundState.InProgress )
|
||||||
{
|
{
|
||||||
MinigameManager.Tick();
|
var ended = MinigameManager.Tick();
|
||||||
|
if ( ended )
|
||||||
|
para
commented
Can we iron out the definition of A Before a round: settings are configured and the lobby is populated with players waiting to start playing Can we iron out the definition of `Round` now?
A `Round` is not one instance of a `Minigame`, but rather multiple different `Minigame`s, right?
Before a round: settings are configured and the lobby is populated with players waiting to start playing
During a round: multiple different minigames will be played (started and stopped)
After a round: we will show the scoreboard and announce a winner.
conco
commented
I'd prefer setting up a round structure in a separate PR due to it moving logic around, but I agree with the definition, along with the idea that after a round ends a new one begins at the first stage again. I'd prefer setting up a round structure in a separate PR due to it moving logic around, but I agree with the definition, along with the idea that after a round ends a new one begins at the first stage again.
|
|||||||
|
{
|
||||||
|
MinigamesLeftInRound--;
|
||||||
|
if ( MinigamesLeftInRound > 0 )
|
||||||
|
{
|
||||||
|
MinigameManager.StartMinigame( Luckers );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RoundState = RoundState.NotStarted;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <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 )
|
||||||
{
|
{
|
||||||
@@ -94,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" );
|
||||||
@@ -119,8 +142,13 @@ public partial class RoundManager : Entity
|
|||||||
}
|
}
|
||||||
|
|
||||||
RoundState = RoundState.InProgress;
|
RoundState = RoundState.InProgress;
|
||||||
Players = All.OfType<Lucker>().ToList();
|
Luckers = All.OfType<Lucker>().ToList();
|
||||||
MinigameManager.StartMinigame( Players, minigameName );
|
Luckers.ForEach( lucker =>
|
||||||
|
{
|
||||||
|
lucker.Ready = false;
|
||||||
|
} );
|
||||||
|
MinigamesLeftInRound = MinigamesPerRound;
|
||||||
|
MinigameManager.StartMinigame( Luckers, minigameName );
|
||||||
}
|
}
|
||||||
|
|
||||||
[ConCmd.Server( "start_round" )]
|
[ConCmd.Server( "start_round" )]
|
||||||
|
|||||||
@@ -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()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
using Sandbox;
|
|
||||||
|
|
||||||
namespace LuckerGame.EntityComponents.Lucker;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A component for capturing and passing around a client's input for an attached Lucker
|
|
||||||
/// </summary>
|
|
||||||
public class LuckerClientInput : EntityComponent<Entities.Lucker>
|
|
||||||
{
|
|
||||||
[ClientInput]
|
|
||||||
public Vector3 InputDirection { get; set; }
|
|
||||||
|
|
||||||
[ClientInput]
|
|
||||||
public Angles ViewAngles { get; set; }
|
|
||||||
}
|
|
||||||
48
code/EntityComponents/Lucker/LuckerStats.cs
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
using Sandbox;
|
||||||
|
using Sandbox.UI;
|
||||||
|
|
||||||
|
namespace LuckerGame.EntityComponents.Lucker;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles the stats associated with a lucker during a lobby.
|
||||||
|
/// </summary>
|
||||||
|
public partial class LuckerStats : EntityComponent<Entities.Lucker>, ISingletonComponent
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The lucker's current score.
|
||||||
|
/// </summary>
|
||||||
|
[Net] private long Score { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds points to this lucker's score
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="points">points to add (or remove if negative)</param>
|
||||||
|
public void AddScore( long points )
|
||||||
|
{
|
||||||
|
Score += points;
|
||||||
|
if ( points == 0 )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var message = $"{Entity.Name} {(points > 0 ? "gained" : "lost")} {points} points!";
|
||||||
|
ChatBox.AddInformation( To.Everyone, message );
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resets this lucker's score to zero
|
||||||
|
/// </summary>
|
||||||
|
public void ResetScore()
|
||||||
|
{
|
||||||
|
Score = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets this lucker's current score
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>this lucker's current score</returns>
|
||||||
|
public long GetScore()
|
||||||
|
{
|
||||||
|
return Score;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,13 +15,14 @@ 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.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract void Tick();
|
/// <returns>true if the minigame has ended, false otherwise</returns>
|
||||||
|
public abstract bool Tick();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Cleans up any entities and components created by this minigame.
|
/// Cleans up any entities and components created by this minigame.
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ public partial class RussianPistol : Weapon
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Pawn.PlaySound( "denyundo" );
|
Pawn.PlaySound( "player_use_fail" );
|
||||||
}
|
}
|
||||||
Ammo--;
|
Ammo--;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,87 +14,102 @@ 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 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; }
|
||||||
|
|
||||||
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;
|
||||||
|
Taunted = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void 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;
|
||||||
}
|
}
|
||||||
return;
|
else if(TimeSinceDeadVictim > TimeBetweenDeathAndEnd)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
if ( TimeSinceShot > TimeBetweenShots )
|
}
|
||||||
|
else if ( TimeSinceShot > TimeBetweenShots )
|
||||||
{
|
{
|
||||||
TimeSinceShot = 0;
|
TimeSinceShot = 0;
|
||||||
Taunted = 0;
|
Taunted = 0;
|
||||||
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++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Cleanup()
|
public override void Cleanup()
|
||||||
|
|||||||
@@ -5,10 +5,13 @@
|
|||||||
@attribute [StyleSheet]
|
@attribute [StyleSheet]
|
||||||
@inherits Panel
|
@inherits Panel
|
||||||
|
|
||||||
|
<root>
|
||||||
@if (ShouldShowCursor)
|
@if (ShouldShowCursor)
|
||||||
{
|
{
|
||||||
<root/>
|
<div class="camera-cursor"/>
|
||||||
}
|
}
|
||||||
|
</root>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
CameraCursor {
|
CameraCursor {
|
||||||
|
.camera-cursor {
|
||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
121
code/UI/MainMenu/ActiveLobby.razor
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
@using Sandbox;
|
||||||
|
@using System;
|
||||||
|
@using System.Linq;
|
||||||
|
@using System.Threading.Tasks;
|
||||||
|
@using Sandbox.Menu;
|
||||||
|
@using Sandbox.UI;
|
||||||
|
|
||||||
|
@namespace LuckerGame.UI.MainMenu
|
||||||
|
@inherits Panel
|
||||||
|
|
||||||
|
<root>
|
||||||
|
<label class="game-title">
|
||||||
|
@Game.Menu.Package.Title
|
||||||
|
</label>
|
||||||
|
|
||||||
|
@if ( Lobby == null )
|
||||||
|
{
|
||||||
|
<div class="controls">
|
||||||
|
<a class="button">Loading...</a>
|
||||||
|
|
||||||
|
<a class="button" href="/lobby/list">Return</a>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
|
||||||
|
<div class="controls">
|
||||||
|
<div class="col">
|
||||||
|
<label>Members (@Lobby.MemberCount/@Lobby.MaxMembers)</label>
|
||||||
|
|
||||||
|
<div class="span">
|
||||||
|
@foreach (var member in Lobby.Members)
|
||||||
|
{
|
||||||
|
<img class="avatar" src="avatar:@member.Id" tooltip="@member.Name" />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if ( Lobby.Owner.IsMe )
|
||||||
|
{
|
||||||
|
<div class="span">
|
||||||
|
@if ( MaxPlayersSupported > 1 )
|
||||||
|
{
|
||||||
|
<FormGroup class="form-group">
|
||||||
|
<Label>Maximum Players</Label>
|
||||||
|
<Control>
|
||||||
|
<SliderControl ShowRange=@true Min=@(1f) Max=@MaxPlayersSupported Value:bind=@Game.Menu.Lobby.MaxMembers />
|
||||||
|
</Control>
|
||||||
|
</FormGroup>
|
||||||
|
}
|
||||||
|
|
||||||
|
<FormGroup class="form-group">
|
||||||
|
<Label>Map</Label>
|
||||||
|
<Control>
|
||||||
|
<SlimPackageCard OnLaunch=@OnMapClicked Package=@MapPackage />
|
||||||
|
</Control>
|
||||||
|
</FormGroup>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="spacer" />
|
||||||
|
|
||||||
|
|
||||||
|
<a class="button" @onclick=@LeaveLobby>Leave Lobby</a>
|
||||||
|
|
||||||
|
<a class="button" @onclick=@Start>Start</a>
|
||||||
|
|
||||||
|
<a class="button" href="/lobby/list">Return</a>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</root>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
Friend Owner => Lobby.Owner;
|
||||||
|
ILobby Lobby => Game.Menu.Lobby;
|
||||||
|
|
||||||
|
int MaxPlayersSupported { get; set; } = 1;
|
||||||
|
Package MapPackage { get; set; }
|
||||||
|
|
||||||
|
void OnMapClicked()
|
||||||
|
{
|
||||||
|
Game.Overlay.ShowPackageSelector( "type:map sort:popular", OnMapSelected );
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnMapSelected( Package map )
|
||||||
|
{
|
||||||
|
MapPackage = map;
|
||||||
|
Game.Menu.Lobby.Map = map.FullIdent;
|
||||||
|
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LeaveLobby()
|
||||||
|
{
|
||||||
|
Lobby?.Leave();
|
||||||
|
|
||||||
|
this.Navigate( "/lobby/list" );
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task Start()
|
||||||
|
{
|
||||||
|
await Game.Menu.StartServerAsync( Game.Menu.Lobby.MaxMembers, $"{Game.Menu.Lobby.Owner.Name}'s game", Game.Menu.Lobby.Map );
|
||||||
|
}
|
||||||
|
|
||||||
|
async void FetchPackage()
|
||||||
|
{
|
||||||
|
MapPackage = await Package.FetchAsync( Game.Menu.Lobby?.Map ?? "facepunch.square", true );
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnAfterTreeRender( bool firstTime )
|
||||||
|
{
|
||||||
|
FetchPackage();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnParametersSet()
|
||||||
|
{
|
||||||
|
MaxPlayersSupported = Game.Menu.Package.GetMeta<int>( "MaxPlayers", 1 );
|
||||||
|
}
|
||||||
|
}
|
||||||
40
code/UI/MainMenu/FrontPage.razor
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
@using Sandbox;
|
||||||
|
@using System.Linq;
|
||||||
|
@using System.Threading.Tasks;
|
||||||
|
@using Sandbox.Menu;
|
||||||
|
@using Sandbox.UI;
|
||||||
|
@namespace LuckerGame.UI.MainMenu
|
||||||
|
|
||||||
|
<root>
|
||||||
|
<div class="game-title">
|
||||||
|
@Game.Menu.Package.Title
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="controls">
|
||||||
|
@if (Game.InGame)
|
||||||
|
{
|
||||||
|
<a class="button" onclick=@LeaveGame>Leave</a>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<a class="button" href="/setup">Play</a>
|
||||||
|
|
||||||
|
<a class="button" href="/lobby/list">Lobbies</a>
|
||||||
|
|
||||||
|
@if ( Game.Menu.Package.SupportsSavedGames && Game.Menu.SavedGames.Any())
|
||||||
|
{
|
||||||
|
<a class="button" href="/setup/save">Load Save</a>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
<a class="button" @onclick=@Game.Menu.Close>Quit</a>
|
||||||
|
</div>
|
||||||
|
</root>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
void LeaveGame()
|
||||||
|
{
|
||||||
|
Game.Menu.LeaveServer( "Leaving" );
|
||||||
|
}
|
||||||
|
}
|
||||||
31
code/UI/MainMenu/LoadingScreen.razor
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
@using Sandbox;
|
||||||
|
@using Sandbox.UI;
|
||||||
|
@using System.Linq;
|
||||||
|
@using System.Threading.Tasks;
|
||||||
|
@using Sandbox.Menu;
|
||||||
|
|
||||||
|
@inherits RootPanel
|
||||||
|
@implements Sandbox.Menu.ILoadingScreenPanel
|
||||||
|
@attribute [StyleSheet]
|
||||||
|
@namespace LuckerGame.UI.MainMenu
|
||||||
|
|
||||||
|
<root style="flex-direction: column;">
|
||||||
|
<div class="background" />
|
||||||
|
|
||||||
|
<div style="flex-grow: 1;" />
|
||||||
|
|
||||||
|
<div class="controls" style="flex-direction: row; justify-content: center;">
|
||||||
|
<a class="button">@( Progress.Title ?? "Loading..." )</a>
|
||||||
|
</div>
|
||||||
|
</root>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
public LoadingProgress Progress;
|
||||||
|
|
||||||
|
public void OnLoadingProgress( LoadingProgress progress )
|
||||||
|
{
|
||||||
|
Progress = progress;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
50
code/UI/MainMenu/LoadingScreen.razor.scss
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
|
||||||
|
LoadingScreen
|
||||||
|
{
|
||||||
|
background-color: #262934;
|
||||||
|
padding: 128px 128px;
|
||||||
|
opacity: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
font-size: 25px;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
transition: all 0.3s ease-out;
|
||||||
|
color: rgba( white, 0.8 );
|
||||||
|
|
||||||
|
.background
|
||||||
|
{
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-image: url( https://files.facepunch.com/tony/1b1311b1/boxes.webm );
|
||||||
|
opacity: 0.2;
|
||||||
|
background-size: contain;
|
||||||
|
filter: blur( 20px );
|
||||||
|
mask: linear-gradient( 45deg, white, white, black );
|
||||||
|
mask-scope: filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:intro
|
||||||
|
{
|
||||||
|
opacity: 0;
|
||||||
|
transform: scaleX( 1.1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-title
|
||||||
|
{
|
||||||
|
font-family: Roboto;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 70px;
|
||||||
|
color: rgba( white, 1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls
|
||||||
|
{
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 50px;
|
||||||
|
align-items: flex-start;
|
||||||
|
font-family: Roboto;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
}
|
||||||
66
code/UI/MainMenu/LobbyBrowser.razor
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
@using Sandbox;
|
||||||
|
@using System;
|
||||||
|
@using System.Linq;
|
||||||
|
@using System.Threading.Tasks;
|
||||||
|
@using Sandbox.Menu;
|
||||||
|
@using Sandbox.UI;
|
||||||
|
@namespace LuckerGame.UI.MainMenu
|
||||||
|
@inherits Panel
|
||||||
|
|
||||||
|
<root>
|
||||||
|
<label class="game-title">
|
||||||
|
@Game.Menu.Package.Title
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div class="controls">
|
||||||
|
<div class="span">
|
||||||
|
<label>Showing @Game.Menu.Lobbies.Count() @(Game.Menu.Lobbies.Count() == 1 ? "lobby" : "lobbies")</label>
|
||||||
|
<i class="with-click" tooltip="Refresh lobbies" @onclick=@Refresh>refresh</i>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="scroll">
|
||||||
|
@foreach ( var lobby in Game.Menu.Lobbies )
|
||||||
|
{
|
||||||
|
<a class="button" @onclick=@( () => JoinLobby( lobby ) ) >@(lobby.Owner.Name)'s lobby (@lobby.MemberCount/@lobby.MaxMembers)</a>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="spacer" />
|
||||||
|
|
||||||
|
<a class="button" @onclick=@CreateLobbyAsync>Create Lobby</a>
|
||||||
|
<a class="button" href="/">Return</a>
|
||||||
|
</div>
|
||||||
|
</root>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
public async void CreateLobbyAsync()
|
||||||
|
{
|
||||||
|
await Game.Menu.CreateLobbyAsync( 64, "game", true );
|
||||||
|
Game.Menu.Lobby.Map = "facepunch.square";
|
||||||
|
this.Navigate( "/lobby/active" );
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void JoinLobby( ILobby lobby )
|
||||||
|
{
|
||||||
|
if ( lobby == null ) return;
|
||||||
|
|
||||||
|
// don't exist in two lobbies at once
|
||||||
|
Game.Menu.Lobby?.Leave();
|
||||||
|
|
||||||
|
await lobby.JoinAsync();
|
||||||
|
this.Navigate( "/lobby/active" );
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void Refresh()
|
||||||
|
{
|
||||||
|
await Game.Menu.QueryLobbiesAsync( null, 1 );
|
||||||
|
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override int BuildHash()
|
||||||
|
{
|
||||||
|
return HashCode.Combine( Game.Menu.Lobbies.Count() );
|
||||||
|
}
|
||||||
|
}
|
||||||
40
code/UI/MainMenu/MainMenu.razor
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
@using System;
|
||||||
|
@using Sandbox;
|
||||||
|
@using Sandbox.UI;
|
||||||
|
|
||||||
|
@inherits Sandbox.UI.NavHostPanel
|
||||||
|
@implements Sandbox.Menu.IGameMenuPanel
|
||||||
|
@attribute [StyleSheet]
|
||||||
|
@namespace LuckerGame.UI.MainMenu
|
||||||
|
|
||||||
|
<root style="flex-direction: column;">
|
||||||
|
<div class="background" />
|
||||||
|
<div class="navigator-canvas" slot="navigator-canvas" />
|
||||||
|
</root>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
public MainMenu()
|
||||||
|
{
|
||||||
|
DefaultUrl = "/";
|
||||||
|
|
||||||
|
AddDestination( "/", typeof( FrontPage ) );
|
||||||
|
AddDestination( "/setup", typeof( SetupGame ) );
|
||||||
|
|
||||||
|
AddDestination( "/lobby/list", typeof( LobbyBrowser ) );
|
||||||
|
AddDestination( "/lobby/active", typeof( ActiveLobby ) );
|
||||||
|
|
||||||
|
BindClass( "ingame", () => Game.InGame );
|
||||||
|
}
|
||||||
|
|
||||||
|
[GameEvent.Menu.ServerJoined]
|
||||||
|
public void OnServerJoined() => Navigate( "/" );
|
||||||
|
|
||||||
|
[GameEvent.Menu.ServerLeave]
|
||||||
|
public void OnServerLeave() => Navigate ("/" );
|
||||||
|
|
||||||
|
protected override int BuildHash()
|
||||||
|
{
|
||||||
|
return HashCode.Combine( Game.InGame, Game.Menu.Lobby, Game.Menu.Lobby?.Map );
|
||||||
|
}
|
||||||
|
}
|
||||||
195
code/UI/MainMenu/MainMenu.razor.scss
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
|
||||||
|
MainMenu
|
||||||
|
{
|
||||||
|
background-color: #262934;
|
||||||
|
padding: 128px 128px;
|
||||||
|
opacity: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
font-size: 25px;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
transition: all 0.3s ease-out;
|
||||||
|
color: rgba( white, 0.8 );
|
||||||
|
|
||||||
|
.background
|
||||||
|
{
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-image: url( https://files.facepunch.com/tony/1b1311b1/boxes.webm );
|
||||||
|
opacity: 0.2;
|
||||||
|
background-size: cover;
|
||||||
|
filter: blur( 20px );
|
||||||
|
mask: linear-gradient( 45deg, white, white, black );
|
||||||
|
mask-scope: filter;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:intro
|
||||||
|
{
|
||||||
|
opacity: 0;
|
||||||
|
transform: scaleX( 1.1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
&.ingame
|
||||||
|
{
|
||||||
|
background-color: #262934ee;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
|
||||||
|
.background
|
||||||
|
{
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll
|
||||||
|
{
|
||||||
|
flex-direction: column;
|
||||||
|
max-height: 386px;
|
||||||
|
overflow: scroll;
|
||||||
|
gap: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spacer
|
||||||
|
{
|
||||||
|
height: 1px;
|
||||||
|
background-image: linear-gradient( to right, rgba( white, 0.4 ), rgba( white, 0 ) );
|
||||||
|
width: 512px;
|
||||||
|
margin: 16px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-title
|
||||||
|
{
|
||||||
|
font-family: Roboto Condensed;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 70px;
|
||||||
|
color: rgba( white, 1 );
|
||||||
|
padding-bottom: 64px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col
|
||||||
|
{
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls
|
||||||
|
{
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 50px;
|
||||||
|
align-items: flex-start;
|
||||||
|
text-transform: uppercase;
|
||||||
|
|
||||||
|
a, .button
|
||||||
|
{
|
||||||
|
font-family: Roboto;
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
{
|
||||||
|
color: rgba( white, 1 );
|
||||||
|
font-weight: 900;
|
||||||
|
sound-in: ui.button.over;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active
|
||||||
|
{
|
||||||
|
sound-in: ui.button.press;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.span
|
||||||
|
{
|
||||||
|
gap: 128px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.navigator-canvas
|
||||||
|
{
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navigator-body
|
||||||
|
{
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
top: 0;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
transition: opacity 0.15s ease-out;
|
||||||
|
|
||||||
|
&.hidden
|
||||||
|
{
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FormGroup
|
||||||
|
{
|
||||||
|
flex-direction: column;
|
||||||
|
min-width: 300px;
|
||||||
|
gap:16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
SlimPackageCard
|
||||||
|
{
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 12px;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
> i
|
||||||
|
{
|
||||||
|
&:hover
|
||||||
|
{
|
||||||
|
cursor: pointer;
|
||||||
|
color: white;
|
||||||
|
transform: scale( 1.1 );
|
||||||
|
sound-in: ui.button.over;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active
|
||||||
|
{
|
||||||
|
sound-in: ui.button.press;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
i
|
||||||
|
{
|
||||||
|
font-family: Material Icons;
|
||||||
|
text-transform: lowercase;
|
||||||
|
|
||||||
|
&.with-click
|
||||||
|
{
|
||||||
|
&:hover
|
||||||
|
{
|
||||||
|
cursor: pointer;
|
||||||
|
color: white;
|
||||||
|
transform: scale( 1.1 );
|
||||||
|
sound-in: ui.button.over;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active
|
||||||
|
{
|
||||||
|
sound-in: ui.button.press;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar
|
||||||
|
{
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
{
|
||||||
|
border: 1px solid white;
|
||||||
|
}
|
||||||
|
}
|
||||||
44
code/UI/MainMenu/SavedGames.razor
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
@using Sandbox;
|
||||||
|
@using System;
|
||||||
|
@using System.Linq;
|
||||||
|
@using Sandbox.UI;
|
||||||
|
@namespace LuckerGame.UI.MainMenu
|
||||||
|
@inherits Panel
|
||||||
|
|
||||||
|
<root>
|
||||||
|
<label class="game-title">
|
||||||
|
@Game.Menu.Package.Title
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div class="controls">
|
||||||
|
@foreach ( var save in Game.Menu.SavedGames.OrderByDescending( x => x.Time ) )
|
||||||
|
{
|
||||||
|
<a class="button" @onclick=@(() => LoadSavedGame( save ))>@save.Name - @save.Time</a>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="spacer" />
|
||||||
|
|
||||||
|
<a class="button" href="/">Return</a>
|
||||||
|
</div>
|
||||||
|
</root>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
async void LoadSavedGame( SavedGame save )
|
||||||
|
{
|
||||||
|
if ( save != null )
|
||||||
|
{
|
||||||
|
Game.Menu.Lobby.SavedGame = save.Name;
|
||||||
|
|
||||||
|
if ( !string.IsNullOrEmpty( save.Map ) )
|
||||||
|
Game.Menu.Lobby.Map = save.Map;
|
||||||
|
|
||||||
|
await Game.Menu.StartServerAsync( Game.Menu.Lobby.MaxMembers, Game.Menu.Lobby.Title, Game.Menu.Lobby.Map ?? "facepunch.square" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override int BuildHash()
|
||||||
|
{
|
||||||
|
return HashCode.Combine( Game.Menu.Lobby, Game.Menu.Lobby?.Map );
|
||||||
|
}
|
||||||
|
}
|
||||||
78
code/UI/MainMenu/SetupGame.razor
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
@using Sandbox;
|
||||||
|
@using System;
|
||||||
|
@using System.Linq;
|
||||||
|
@using System.Threading.Tasks;
|
||||||
|
@using Sandbox.Menu;
|
||||||
|
@using Sandbox.UI;
|
||||||
|
@namespace LuckerGame.UI.MainMenu
|
||||||
|
@inherits Panel
|
||||||
|
|
||||||
|
<root>
|
||||||
|
<label class="game-title">
|
||||||
|
@Game.Menu.Package.Title
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div class="controls">
|
||||||
|
<div class="span">
|
||||||
|
@if ( MaxPlayersSupported > 1 )
|
||||||
|
{
|
||||||
|
<FormGroup class="form-group">
|
||||||
|
<Label>Maximum Players</Label>
|
||||||
|
<Control>
|
||||||
|
<SliderControl ShowRange=@true Min=@(1f) Max=@MaxPlayersSupported Value:bind=@Game.Menu.Lobby.MaxMembers />
|
||||||
|
</Control>
|
||||||
|
</FormGroup>
|
||||||
|
}
|
||||||
|
|
||||||
|
<FormGroup class="form-group">
|
||||||
|
<Label>Map</Label>
|
||||||
|
<Control>
|
||||||
|
<SlimPackageCard OnLaunch=@OnMapClicked Package=@MapPackage />
|
||||||
|
</Control>
|
||||||
|
</FormGroup>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="spacer" />
|
||||||
|
|
||||||
|
<a class="button" onclick=@Play>Start</a>
|
||||||
|
<a class="button" href="/">Return</a>
|
||||||
|
</div>
|
||||||
|
</root>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
int MaxPlayersSupported { get; set; } = 1;
|
||||||
|
int MaxPlayers { get; set; } = 1;
|
||||||
|
Package MapPackage { get; set; }
|
||||||
|
|
||||||
|
void OnMapClicked()
|
||||||
|
{
|
||||||
|
Game.Overlay.ShowPackageSelector( "type:map sort:popular", OnMapSelected );
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnMapSelected( Package map )
|
||||||
|
{
|
||||||
|
MapPackage = map;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task OnParametersSetAsync()
|
||||||
|
{
|
||||||
|
MaxPlayersSupported = Game.Menu.Package.GetMeta<int>( "MaxPlayers", 1 );
|
||||||
|
MaxPlayers = MaxPlayersSupported;
|
||||||
|
|
||||||
|
MapPackage = await Package.FetchAsync( "facepunch.square", false );
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task Play()
|
||||||
|
{
|
||||||
|
await Game.Menu.StartServerAsync( MaxPlayers, $"My game", MapPackage.FullIdent );
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override int BuildHash()
|
||||||
|
{
|
||||||
|
return HashCode.Combine( MaxPlayers, MapPackage );
|
||||||
|
}
|
||||||
|
}
|
||||||
32
code/UI/MainMenu/SlimPackageCard.razor
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
@using System;
|
||||||
|
@using Sandbox;
|
||||||
|
@namespace LuckerGame.UI.MainMenu
|
||||||
|
@inherits Sandbox.UI.Panel
|
||||||
|
|
||||||
|
<root>
|
||||||
|
@if ( Package == null )
|
||||||
|
{
|
||||||
|
<div class="button" @onclick=@OnCardClicked>Select Package</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="button" @onclick=@OnCardClicked>@Package.Title</div>
|
||||||
|
<i tooltip="See information about this package" @onclick=@( () => Game.Overlay.ShowPackageModal( Package.FullIdent ) )>info</i>
|
||||||
|
}
|
||||||
|
</root>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
public Package Package { get; set; }
|
||||||
|
public System.Action OnLaunch { get; set; }
|
||||||
|
|
||||||
|
void OnCardClicked()
|
||||||
|
{
|
||||||
|
OnLaunch?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override int BuildHash()
|
||||||
|
{
|
||||||
|
return HashCode.Combine( Package );
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
{
|
{
|
||||||
|
|||||||

Why is minimum 1 but default is 0?
Also why can't I comment on multiple lines in Gitea? LAME
Should now be fixed, looks like something weird with S&box convars and setting them via GUI