Initial commit
This commit is contained in:
100
.editorconfig
Normal file
100
.editorconfig
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
# Remove the line below if you want to inherit .editorconfig settings from higher directories
|
||||||
|
root = true
|
||||||
|
|
||||||
|
# C# files
|
||||||
|
[*.cs]
|
||||||
|
indent_style = tab
|
||||||
|
indent_size = tab
|
||||||
|
tab_size = 4
|
||||||
|
|
||||||
|
# New line preferences
|
||||||
|
end_of_line = crlf
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
|
||||||
|
#### C# Coding Conventions ####
|
||||||
|
|
||||||
|
# Expression-bodied members
|
||||||
|
csharp_style_expression_bodied_accessors = true:silent
|
||||||
|
csharp_style_expression_bodied_constructors = false:silent
|
||||||
|
csharp_style_expression_bodied_indexers = true:silent
|
||||||
|
csharp_style_expression_bodied_lambdas = true:silent
|
||||||
|
csharp_style_expression_bodied_local_functions = false:silent
|
||||||
|
csharp_style_expression_bodied_methods = false:silent
|
||||||
|
csharp_style_expression_bodied_operators = false:silent
|
||||||
|
csharp_style_expression_bodied_properties = true:silent
|
||||||
|
|
||||||
|
# Pattern matching preferences
|
||||||
|
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
|
||||||
|
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
|
||||||
|
csharp_style_prefer_not_pattern = true:suggestion
|
||||||
|
csharp_style_prefer_pattern_matching = true:silent
|
||||||
|
csharp_style_prefer_switch_expression = true:suggestion
|
||||||
|
|
||||||
|
# Null-checking preferences
|
||||||
|
csharp_style_conditional_delegate_call = true:suggestion
|
||||||
|
|
||||||
|
# Code-block preferences
|
||||||
|
csharp_prefer_braces = true:silent
|
||||||
|
|
||||||
|
# Expression-level preferences
|
||||||
|
csharp_prefer_simple_default_expression = true:suggestion
|
||||||
|
csharp_style_deconstructed_variable_declaration = true:suggestion
|
||||||
|
csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
|
||||||
|
csharp_style_inlined_variable_declaration = true:suggestion
|
||||||
|
csharp_style_pattern_local_over_anonymous_function = true:suggestion
|
||||||
|
csharp_style_prefer_index_operator = true:suggestion
|
||||||
|
csharp_style_prefer_range_operator = true:suggestion
|
||||||
|
csharp_style_throw_expression = true:suggestion
|
||||||
|
csharp_style_unused_value_assignment_preference = discard_variable:suggestion
|
||||||
|
csharp_style_unused_value_expression_statement_preference = discard_variable:silent
|
||||||
|
|
||||||
|
# 'using' directive preferences
|
||||||
|
csharp_using_directive_placement = outside_namespace:silent
|
||||||
|
|
||||||
|
#### C# Formatting Rules ####
|
||||||
|
|
||||||
|
# New line preferences
|
||||||
|
csharp_new_line_before_catch = true
|
||||||
|
csharp_new_line_before_else = true
|
||||||
|
csharp_new_line_before_finally = true
|
||||||
|
csharp_new_line_before_members_in_anonymous_types = true
|
||||||
|
csharp_new_line_before_members_in_object_initializers = true
|
||||||
|
csharp_new_line_before_open_brace = all
|
||||||
|
csharp_new_line_between_query_expression_clauses = true
|
||||||
|
|
||||||
|
# Indentation preferences
|
||||||
|
csharp_indent_block_contents = true
|
||||||
|
csharp_indent_braces = false
|
||||||
|
csharp_indent_case_contents = true
|
||||||
|
csharp_indent_case_contents_when_block = true
|
||||||
|
csharp_indent_labels = no_change
|
||||||
|
csharp_indent_switch_labels = true
|
||||||
|
|
||||||
|
# Space preferences
|
||||||
|
csharp_space_after_cast = false
|
||||||
|
csharp_space_after_colon_in_inheritance_clause = true
|
||||||
|
csharp_space_after_comma = true
|
||||||
|
csharp_space_after_dot = false
|
||||||
|
csharp_space_after_keywords_in_control_flow_statements = true
|
||||||
|
csharp_space_after_semicolon_in_for_statement = true
|
||||||
|
csharp_space_around_binary_operators = before_and_after
|
||||||
|
csharp_space_around_declaration_statements = false
|
||||||
|
csharp_space_before_colon_in_inheritance_clause = true
|
||||||
|
csharp_space_before_comma = false
|
||||||
|
csharp_space_before_dot = false
|
||||||
|
csharp_space_before_open_square_brackets = false
|
||||||
|
csharp_space_before_semicolon_in_for_statement = false
|
||||||
|
csharp_space_between_empty_square_brackets = false
|
||||||
|
csharp_space_between_method_call_empty_parameter_list_parentheses = false
|
||||||
|
csharp_space_between_method_call_name_and_opening_parenthesis = false
|
||||||
|
csharp_space_between_method_call_parameter_list_parentheses = true
|
||||||
|
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
|
||||||
|
csharp_space_between_method_declaration_name_and_open_parenthesis = false
|
||||||
|
csharp_space_between_method_declaration_parameter_list_parentheses = true
|
||||||
|
csharp_space_between_parentheses = control_flow_statements
|
||||||
|
csharp_space_between_square_brackets = false
|
||||||
|
|
||||||
|
# Wrapping preferences
|
||||||
|
csharp_preserve_single_line_blocks = true
|
||||||
|
csharp_preserve_single_line_statements = true
|
||||||
18
.gitignore
vendored
Normal file
18
.gitignore
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
|
||||||
|
# This file describes files and paths that should not be tracked by Git version control
|
||||||
|
# https://git-scm.com/docs/gitignore
|
||||||
|
|
||||||
|
# Auto-generated code editor files
|
||||||
|
.vs/*
|
||||||
|
.vscode/*
|
||||||
|
*.csproj
|
||||||
|
obj/*
|
||||||
|
Properties/*
|
||||||
|
code/obj/*
|
||||||
|
code/Properties/*
|
||||||
|
|
||||||
|
# Auto-generated asset related files
|
||||||
|
*.generated.*
|
||||||
|
*.*_c
|
||||||
|
*.los
|
||||||
|
*.vpk
|
||||||
44
.sbproj
Normal file
44
.sbproj
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
"Title": "Are You \u0026 Lucker",
|
||||||
|
"Type": "game",
|
||||||
|
"Org": "local",
|
||||||
|
"Ident": "are_you___lucker",
|
||||||
|
"Tags": null,
|
||||||
|
"Schema": 1,
|
||||||
|
"HasAssets": true,
|
||||||
|
"AssetsPath": "",
|
||||||
|
"Resources": null,
|
||||||
|
"MenuResources": null,
|
||||||
|
"HasCode": true,
|
||||||
|
"CodePath": "/code/",
|
||||||
|
"PackageReferences": [],
|
||||||
|
"EditorReferences": null,
|
||||||
|
"Metadata": {
|
||||||
|
"MaxPlayers": 8,
|
||||||
|
"MinPlayers": 1,
|
||||||
|
"GameNetworkType": "Multiplayer",
|
||||||
|
"MapSelect": "Unrestricted",
|
||||||
|
"MapList": [
|
||||||
|
"facepunch.square"
|
||||||
|
],
|
||||||
|
"RankType": "None",
|
||||||
|
"PerMapRanking": false,
|
||||||
|
"LeaderboardType": "None",
|
||||||
|
"GameCategory": "TechDemos",
|
||||||
|
"ProjectTemplate": null,
|
||||||
|
"TickRate": 50,
|
||||||
|
"CsProjName": "",
|
||||||
|
"LaunchConfigs": [
|
||||||
|
{
|
||||||
|
"Name": "My New Config",
|
||||||
|
"GameIdent": "local.are_you___lucker#local",
|
||||||
|
"MapName": "voximity.flat_plane",
|
||||||
|
"MaxPlayers": 1,
|
||||||
|
"GameSettings": {},
|
||||||
|
"Addons": "",
|
||||||
|
"PreLaunchCommand": "",
|
||||||
|
"PostLaunchCommand": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
11
README.md
Normal file
11
README.md
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# Top Down Template
|
||||||
|
Designed as a minimal starting point for your projects in s&box.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
- Top down camera
|
||||||
|
- Simple pawn controller
|
||||||
|
|
||||||
|
## Controls
|
||||||
|
- Regular movement with WASD
|
||||||
|
- Rotate the camera with right mouse button
|
||||||
|
- Zoom the camera with mouse wheel
|
||||||
87
code/Entities/Lucker.cs
Normal file
87
code/Entities/Lucker.cs
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
using LuckerGame.Components.Lucker.Cameras;
|
||||||
|
using LuckerGame.Events;
|
||||||
|
using Sandbox;
|
||||||
|
|
||||||
|
namespace LuckerGame.Entities;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a Player.
|
||||||
|
/// This could belong to a Client or a Bot and represents a common entity to operate on for games and keeping score
|
||||||
|
/// </summary>
|
||||||
|
public partial class Lucker : Entity
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The entity this Player currently controls
|
||||||
|
/// </summary>
|
||||||
|
public Entity Pawn { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Before the round has started, this player indicated they were ready
|
||||||
|
/// </summary>
|
||||||
|
[Net] public bool Ready { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This Lucker's camera
|
||||||
|
/// </summary>
|
||||||
|
[BindComponent] public AbstractCamera Camera { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates and properly sets up a Player entity for a given client
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="client">the client to own the player</param>
|
||||||
|
/// <returns>the newly created player</returns>
|
||||||
|
public static Lucker CreateLuckerForClient( IClient client )
|
||||||
|
{
|
||||||
|
var player = new Lucker();
|
||||||
|
client.Pawn = player;
|
||||||
|
player.Owner = client as Entity;
|
||||||
|
player.Name = client.Name;
|
||||||
|
var camera = player.Components.Create<RTSCamera>();
|
||||||
|
|
||||||
|
return player;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Allows clients to request setting their ready state
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="readyState">whether they are readying up or calming down</param>
|
||||||
|
[ConCmd.Server("set_ready")]
|
||||||
|
public static void ReadyUpCommand(bool readyState)
|
||||||
|
{
|
||||||
|
var client = ConsoleSystem.Caller;
|
||||||
|
var player = client.Pawn as Lucker;
|
||||||
|
player.SetReady( readyState );
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets this player's ready state
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ready">the ready state being set</param>
|
||||||
|
public void SetReady(bool ready)
|
||||||
|
{
|
||||||
|
Ready = ready;
|
||||||
|
if ( Game.IsServer )
|
||||||
|
{
|
||||||
|
Event.Run( LuckerEvent.PlayerReady, this, ready );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Simulate( IClient cl )
|
||||||
|
{
|
||||||
|
base.Simulate( cl );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void FrameSimulate( IClient cl )
|
||||||
|
{
|
||||||
|
base.FrameSimulate( cl );
|
||||||
|
Camera?.Update();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void BuildInput()
|
||||||
|
{
|
||||||
|
base.BuildInput();
|
||||||
|
Camera?.BuildInput();
|
||||||
|
}
|
||||||
|
}
|
||||||
49
code/Entities/MinigameManager.cs
Normal file
49
code/Entities/MinigameManager.cs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using LuckerGame.Minigames;
|
||||||
|
using Sandbox;
|
||||||
|
using Sandbox.UI;
|
||||||
|
|
||||||
|
namespace LuckerGame.Entities;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Manages minigames
|
||||||
|
/// </summary>
|
||||||
|
public partial class MinigameManager : Entity
|
||||||
|
{
|
||||||
|
[Net] public Minigame LoadedMinigame { get; private set; }
|
||||||
|
private List<Minigame> AvailableMinigames { get; set; }
|
||||||
|
|
||||||
|
public override void Spawn()
|
||||||
|
{
|
||||||
|
base.Spawn();
|
||||||
|
FindMinigames();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StartRandomMinigame(List<Lucker> players)
|
||||||
|
{
|
||||||
|
if ( (AvailableMinigames?.Count ?? 0) == 0 )
|
||||||
|
{
|
||||||
|
Log.Error( "Attempted to start minigame, but none available" );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LoadedMinigame = AvailableMinigames.OrderBy( _ => Guid.NewGuid() ).FirstOrDefault();
|
||||||
|
ChatBox.AddInformation( To.Everyone, $"Starting {LoadedMinigame.Name}" );
|
||||||
|
LoadedMinigame.Initialize( players );
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FindMinigames()
|
||||||
|
{
|
||||||
|
AvailableMinigames = TypeLibrary.GetTypes<Minigame>()
|
||||||
|
.Where( type => !type.IsAbstract && !type.IsInterface )
|
||||||
|
.Select( td => TypeLibrary.Create<Minigame>( td.TargetType ) ).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Event.Hotload]
|
||||||
|
private void Reload()
|
||||||
|
{
|
||||||
|
FindMinigames();
|
||||||
|
}
|
||||||
|
}
|
||||||
133
code/Entities/Pawn.cs
Normal file
133
code/Entities/Pawn.cs
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
using LuckerGame.Components.Pawn;
|
||||||
|
using Sandbox;
|
||||||
|
|
||||||
|
namespace LuckerGame.Entities;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents an entity in the world. Could be controlled by a Lucker or a minigame
|
||||||
|
/// </summary>
|
||||||
|
public partial class Pawn : AnimatedEntity
|
||||||
|
{
|
||||||
|
[ClientInput]
|
||||||
|
public Vector3 InputDirection { get; set; }
|
||||||
|
|
||||||
|
[ClientInput]
|
||||||
|
public Angles ViewAngles { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Position a player should be looking from in world space.
|
||||||
|
/// </summary>
|
||||||
|
[Browsable( false )]
|
||||||
|
public Vector3 EyePosition
|
||||||
|
{
|
||||||
|
get => Transform.PointToWorld( EyeLocalPosition );
|
||||||
|
set => EyeLocalPosition = Transform.PointToLocal( value );
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Position a player should be looking from in local to the entity coordinates.
|
||||||
|
/// </summary>
|
||||||
|
[Net, Predicted, Browsable( false )]
|
||||||
|
public Vector3 EyeLocalPosition { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Rotation of the entity's "eyes", i.e. rotation for the camera when this entity is used as the view entity.
|
||||||
|
/// </summary>
|
||||||
|
[Browsable( false )]
|
||||||
|
public Rotation EyeRotation
|
||||||
|
{
|
||||||
|
get => Transform.RotationToWorld( EyeLocalRotation );
|
||||||
|
set => EyeLocalRotation = Transform.RotationToLocal( value );
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Rotation of the entity's "eyes", i.e. rotation for the camera when this entity is used as the view entity. In local to the entity coordinates.
|
||||||
|
/// </summary>
|
||||||
|
[Net, Predicted, Browsable( false )]
|
||||||
|
public Rotation EyeLocalRotation { get; set; }
|
||||||
|
|
||||||
|
public BBox Hull
|
||||||
|
{
|
||||||
|
get => new
|
||||||
|
(
|
||||||
|
new Vector3( -16, -16, 0 ),
|
||||||
|
new Vector3( 16, 16, 64 )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[BindComponent] public UserPawnController Controller { get; }
|
||||||
|
[BindComponent] public PawnAnimator Animator { get; }
|
||||||
|
|
||||||
|
public override Ray AimRay => new Ray( EyePosition, EyeRotation.Forward );
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when the entity is first created
|
||||||
|
/// </summary>
|
||||||
|
public override void Spawn()
|
||||||
|
{
|
||||||
|
SetModel( "models/citizen/citizen.vmdl" );
|
||||||
|
|
||||||
|
EnableDrawing = true;
|
||||||
|
EnableHideInFirstPerson = true;
|
||||||
|
EnableShadowInFirstPerson = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Respawn()
|
||||||
|
{
|
||||||
|
Components.Create<UserPawnController>();
|
||||||
|
Components.Create<PawnAnimator>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DressFromClient( IClient cl )
|
||||||
|
{
|
||||||
|
var c = new ClothingContainer();
|
||||||
|
c.LoadFromClient( cl );
|
||||||
|
c.DressEntity( this );
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Simulate( IClient cl )
|
||||||
|
{
|
||||||
|
SimulateRotation();
|
||||||
|
Controller?.Simulate( cl );
|
||||||
|
Animator?.Simulate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void BuildInput()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void FrameSimulate( IClient cl )
|
||||||
|
{
|
||||||
|
SimulateRotation();
|
||||||
|
}
|
||||||
|
|
||||||
|
public TraceResult TraceBBox( Vector3 start, Vector3 end, float liftFeet = 0.0f )
|
||||||
|
{
|
||||||
|
return TraceBBox( start, end, Hull.Mins, Hull.Maxs, liftFeet );
|
||||||
|
}
|
||||||
|
|
||||||
|
public TraceResult TraceBBox( Vector3 start, Vector3 end, Vector3 mins, Vector3 maxs, float liftFeet = 0.0f )
|
||||||
|
{
|
||||||
|
if ( liftFeet > 0 )
|
||||||
|
{
|
||||||
|
start += Vector3.Up * liftFeet;
|
||||||
|
maxs = maxs.WithZ( maxs.z - liftFeet );
|
||||||
|
}
|
||||||
|
|
||||||
|
var tr = Trace.Ray( start, end )
|
||||||
|
.Size( mins, maxs )
|
||||||
|
.WithAnyTags( "solid", "playerclip", "passbullets" )
|
||||||
|
.Ignore( this )
|
||||||
|
.Run();
|
||||||
|
|
||||||
|
return tr;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void SimulateRotation()
|
||||||
|
{
|
||||||
|
var idealRotation = ViewAngles.ToRotation();
|
||||||
|
EyeRotation = Rotation.Slerp( Rotation, idealRotation, Time.Delta * 10f );
|
||||||
|
Rotation = EyeRotation;
|
||||||
|
}
|
||||||
|
}
|
||||||
120
code/Entities/RoundManager.cs
Normal file
120
code/Entities/RoundManager.cs
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using LuckerGame.Enums;
|
||||||
|
using LuckerGame.Events;
|
||||||
|
using Sandbox;
|
||||||
|
using Sandbox.UI;
|
||||||
|
|
||||||
|
namespace LuckerGame.Entities;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Manages rounds. Starting, stopping, triggering minigames from the manager, etc
|
||||||
|
/// </summary>
|
||||||
|
public partial class RoundManager : Entity
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The minigame manager we should be using to manage minigames
|
||||||
|
/// </summary>
|
||||||
|
private MinigameManager MinigameManager { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This percentage of the lobby must be ready to start the countdown for round start
|
||||||
|
/// </summary>
|
||||||
|
private const float RequiredReadyPercent = .5f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The number of seconds from the timer starting before the round starts
|
||||||
|
/// </summary>
|
||||||
|
private const float RoundStartCountdownSeconds = 10f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The state of the current round
|
||||||
|
/// </summary>
|
||||||
|
[Net] public RoundState RoundState { get; private set; }
|
||||||
|
|
||||||
|
#region Countdown State
|
||||||
|
/// <summary>
|
||||||
|
/// How long since we started counting down to game start. Only useful if in the StartCountdown state.
|
||||||
|
/// </summary>
|
||||||
|
[Net] public TimeSince TimeSinceCountdownStarted { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The number of seconds left in the countdown. Only useful if in the StartCountdown state.
|
||||||
|
/// </summary>
|
||||||
|
public int SecondsLeftInCountdown => (int)Math.Ceiling( RoundStartCountdownSeconds - TimeSinceCountdownStarted );
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region In Progress State
|
||||||
|
|
||||||
|
private const int MinigamesPerRound = 1;
|
||||||
|
|
||||||
|
private int MinigamesLeftInRound { get; set; }
|
||||||
|
|
||||||
|
private List<Lucker> Players { get; set; }
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Spawn()
|
||||||
|
{
|
||||||
|
base.Spawn();
|
||||||
|
RoundState = RoundState.NotStarted;
|
||||||
|
MinigameManager = new MinigameManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fires once per server tick
|
||||||
|
/// </summary>
|
||||||
|
[GameEvent.Tick.Server]
|
||||||
|
public void Tick()
|
||||||
|
{
|
||||||
|
if ( RoundState == RoundState.StartCountdown && TimeSinceCountdownStarted > RoundStartCountdownSeconds )
|
||||||
|
{
|
||||||
|
Log.Info( "Starting round" );
|
||||||
|
StartRound();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Is triggered whenever a player readies up
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="readyLucker">the player that readied up, discarded</param>
|
||||||
|
[LuckerEvent.PlayerReady]
|
||||||
|
public void HandlePlayerReady( Lucker readyLucker, bool ready )
|
||||||
|
{
|
||||||
|
if ( RoundState != RoundState.NotStarted && RoundState != RoundState.StartCountdown )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Log.Info( $"{readyLucker.Client.Name} set ready to {ready}" );
|
||||||
|
var message = $"{readyLucker.Client.Name} is {(ready ? "now ready." : "no longer ready.")}";
|
||||||
|
ChatBox.AddInformation( To.Everyone, message );
|
||||||
|
var players = All.OfType<Lucker>().ToList();
|
||||||
|
var readiedCount = players.Count( player => player.Ready );
|
||||||
|
var totalCount = players.Count;
|
||||||
|
if ( (float)readiedCount / totalCount > RequiredReadyPercent && RoundState == RoundState.NotStarted )
|
||||||
|
{
|
||||||
|
Log.Info( "Countdown started" );
|
||||||
|
RoundState = RoundState.StartCountdown;
|
||||||
|
TimeSinceCountdownStarted = 0;
|
||||||
|
}
|
||||||
|
else if ( (float)readiedCount / totalCount <= RequiredReadyPercent && RoundState == RoundState.StartCountdown )
|
||||||
|
{
|
||||||
|
Log.Info( "Countdown ended" );
|
||||||
|
RoundState = RoundState.NotStarted;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StartRound()
|
||||||
|
{
|
||||||
|
if ( RoundState == RoundState.InProgress )
|
||||||
|
{
|
||||||
|
Log.Warning( "Attempted to start round while one was in progress" );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RoundState = RoundState.InProgress;
|
||||||
|
Players = All.OfType<Lucker>().ToList();
|
||||||
|
MinigameManager.StartRandomMinigame( Players );
|
||||||
|
}
|
||||||
|
}
|
||||||
250
code/Entities/Weapons/Weapon.cs
Normal file
250
code/Entities/Weapons/Weapon.cs
Normal file
@@ -0,0 +1,250 @@
|
|||||||
|
using Sandbox;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace LuckerGame.Entities.Weapons;
|
||||||
|
|
||||||
|
public partial class Weapon : AnimatedEntity
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The View Model's entity, only accessible clientside.
|
||||||
|
/// </summary>
|
||||||
|
public WeaponViewModel ViewModelEntity { get; protected set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An accessor to grab our Pawn.
|
||||||
|
/// </summary>
|
||||||
|
public Pawn Pawn => Owner as Pawn;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This'll decide which entity to fire effects from. If we're in first person, the View Model, otherwise, this.
|
||||||
|
/// </summary>
|
||||||
|
public AnimatedEntity EffectEntity => Camera.FirstPersonViewer == Owner ? ViewModelEntity : this;
|
||||||
|
|
||||||
|
public virtual string ViewModelPath => null;
|
||||||
|
public virtual string ModelPath => null;
|
||||||
|
public virtual string ReloadSoundPath => null;
|
||||||
|
public virtual string ReloadAnimPath => "reload";
|
||||||
|
public virtual string WeaponName => "weapon";
|
||||||
|
public virtual float ReloadDuration => 4f;
|
||||||
|
private bool Reloading => TimeSinceReloadStarted < ReloadDuration;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How often you can shoot this gun.
|
||||||
|
/// </summary>
|
||||||
|
public virtual float PrimaryRate => 5.0f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How long since we last shot this gun.
|
||||||
|
/// </summary>
|
||||||
|
[Net, Predicted] public TimeSince TimeSincePrimaryAttack { get; set; }
|
||||||
|
|
||||||
|
[Net, Predicted] public TimeSince TimeSinceReloadStarted { get; set; }
|
||||||
|
|
||||||
|
public virtual int MaxAmmo { get; }
|
||||||
|
|
||||||
|
[Net] public int Ammo { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
public override void Spawn()
|
||||||
|
{
|
||||||
|
EnableHideInFirstPerson = true;
|
||||||
|
EnableShadowInFirstPerson = true;
|
||||||
|
EnableDrawing = false;
|
||||||
|
Ammo = MaxAmmo;
|
||||||
|
|
||||||
|
if ( ModelPath != null )
|
||||||
|
{
|
||||||
|
SetModel( ModelPath );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when <see cref="Pawn.SetActiveWeapon(Weapon)"/> is called for this weapon.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pawn"></param>
|
||||||
|
public void OnEquip( Pawn pawn )
|
||||||
|
{
|
||||||
|
Owner = pawn;
|
||||||
|
SetParent( pawn, true );
|
||||||
|
EnableDrawing = true;
|
||||||
|
CreateViewModel( To.Single( pawn ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when the weapon is either removed from the player, or holstered.
|
||||||
|
/// </summary>
|
||||||
|
public void OnHolster()
|
||||||
|
{
|
||||||
|
EnableDrawing = false;
|
||||||
|
DestroyViewModel( To.Single( Owner ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called from <see cref="Pawn.Simulate(IClient)"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="player"></param>
|
||||||
|
public override void Simulate( IClient player )
|
||||||
|
{
|
||||||
|
Animate();
|
||||||
|
|
||||||
|
if ( CanPrimaryAttack() )
|
||||||
|
{
|
||||||
|
using ( LagCompensation() )
|
||||||
|
{
|
||||||
|
TimeSincePrimaryAttack = 0;
|
||||||
|
ReduceAmmoAndPrimaryAttack();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ( CanReload() )
|
||||||
|
{
|
||||||
|
Reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[ClientRpc]
|
||||||
|
protected virtual void ReloadEffects()
|
||||||
|
{
|
||||||
|
Game.AssertClient();
|
||||||
|
ViewModelEntity?.SetAnimParameter( "reload", true );
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Reload()
|
||||||
|
{
|
||||||
|
ReloadEffects();
|
||||||
|
Pawn.PlaySound( ReloadSoundPath );
|
||||||
|
Ammo = MaxAmmo;
|
||||||
|
Log.Info( $"Reload duration: {ReloadDuration}" );
|
||||||
|
TimeSinceReloadStarted = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CanReload()
|
||||||
|
{
|
||||||
|
return Owner.IsValid && Input.Down( "reload" ) && Ammo < MaxAmmo && !Reloading;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called every <see cref="Simulate(IClient)"/> to see if we can shoot our gun.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public virtual bool CanPrimaryAttack()
|
||||||
|
{
|
||||||
|
if ( !Owner.IsValid() || !Input.Down( "attack1" ) || Ammo == 0 || Reloading ) return false;
|
||||||
|
|
||||||
|
var rate = PrimaryRate;
|
||||||
|
if ( rate <= 0 ) return true;
|
||||||
|
|
||||||
|
return TimeSincePrimaryAttack > (1 / rate);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReduceAmmoAndPrimaryAttack()
|
||||||
|
{
|
||||||
|
Ammo--;
|
||||||
|
PrimaryAttack();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when your gun shoots.
|
||||||
|
/// </summary>
|
||||||
|
public virtual void PrimaryAttack()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Useful for setting anim parameters based off the current weapon.
|
||||||
|
/// </summary>
|
||||||
|
protected virtual void Animate()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Does a trace from start to end, does bullet impact effects. Coded as an IEnumerable so you can return multiple
|
||||||
|
/// hits, like if you're going through layers or ricocheting or something.
|
||||||
|
/// </summary>
|
||||||
|
public virtual IEnumerable<TraceResult> TraceBullet( Vector3 start, Vector3 end, float radius = 2.0f )
|
||||||
|
{
|
||||||
|
bool underWater = Trace.TestPoint( start, "water" );
|
||||||
|
|
||||||
|
var trace = Trace.Ray( start, end )
|
||||||
|
.UseHitboxes()
|
||||||
|
.WithAnyTags( "solid", "player", "npc" )
|
||||||
|
.Ignore( this )
|
||||||
|
.Size( radius );
|
||||||
|
|
||||||
|
//
|
||||||
|
// If we're not underwater then we can hit water
|
||||||
|
//
|
||||||
|
if ( !underWater )
|
||||||
|
trace = trace.WithAnyTags( "water" );
|
||||||
|
|
||||||
|
var tr = trace.Run();
|
||||||
|
|
||||||
|
if ( tr.Hit )
|
||||||
|
yield return tr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Shoot a single bullet
|
||||||
|
/// </summary>
|
||||||
|
public virtual void ShootBullet( Vector3 pos, Vector3 dir, float spread, float force, float damage, float bulletSize )
|
||||||
|
{
|
||||||
|
var forward = dir;
|
||||||
|
forward += (Vector3.Random + Vector3.Random + Vector3.Random + Vector3.Random) * spread * 0.25f;
|
||||||
|
forward = forward.Normal;
|
||||||
|
|
||||||
|
//
|
||||||
|
// ShootBullet is coded in a way where we can have bullets pass through shit
|
||||||
|
// or bounce off shit, in which case it'll return multiple results
|
||||||
|
//
|
||||||
|
foreach ( var tr in TraceBullet( pos, pos + forward * 5000, bulletSize ) )
|
||||||
|
{
|
||||||
|
tr.Surface.DoBulletImpact( tr );
|
||||||
|
|
||||||
|
if ( !Game.IsServer ) continue;
|
||||||
|
if ( !tr.Entity.IsValid() ) continue;
|
||||||
|
|
||||||
|
//
|
||||||
|
// We turn predictiuon off for this, so any exploding effects don't get culled etc
|
||||||
|
//
|
||||||
|
using ( Prediction.Off() )
|
||||||
|
{
|
||||||
|
var damageInfo = DamageInfo.FromBullet( tr.EndPosition, forward * 100 * force, damage )
|
||||||
|
.UsingTraceResult( tr )
|
||||||
|
.WithAttacker( Owner )
|
||||||
|
.WithWeapon( this );
|
||||||
|
|
||||||
|
tr.Entity.TakeDamage( damageInfo );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Shoot a single bullet from owners view point
|
||||||
|
/// </summary>
|
||||||
|
public virtual void ShootBullet( float spread, float force, float damage, float bulletSize )
|
||||||
|
{
|
||||||
|
Game.SetRandomSeed( Time.Tick );
|
||||||
|
|
||||||
|
var ray = Owner.AimRay;
|
||||||
|
ShootBullet( ray.Position, ray.Forward, spread, force, damage, bulletSize );
|
||||||
|
}
|
||||||
|
|
||||||
|
[ClientRpc]
|
||||||
|
public void CreateViewModel()
|
||||||
|
{
|
||||||
|
if ( ViewModelPath == null ) return;
|
||||||
|
|
||||||
|
var vm = new WeaponViewModel( this );
|
||||||
|
vm.Model = Model.Load( ViewModelPath );
|
||||||
|
ViewModelEntity = vm;
|
||||||
|
}
|
||||||
|
|
||||||
|
[ClientRpc]
|
||||||
|
public void DestroyViewModel()
|
||||||
|
{
|
||||||
|
if ( ViewModelEntity.IsValid() )
|
||||||
|
{
|
||||||
|
ViewModelEntity.Delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
22
code/Entities/Weapons/WeaponViewModel.cs
Normal file
22
code/Entities/Weapons/WeaponViewModel.cs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
using Sandbox;
|
||||||
|
|
||||||
|
namespace LuckerGame.Entities.Weapons;
|
||||||
|
|
||||||
|
public partial class WeaponViewModel : BaseViewModel
|
||||||
|
{
|
||||||
|
protected Weapon Weapon { get; init; }
|
||||||
|
|
||||||
|
public WeaponViewModel( Weapon weapon )
|
||||||
|
{
|
||||||
|
Weapon = weapon;
|
||||||
|
EnableShadowCasting = false;
|
||||||
|
EnableViewmodelRendering = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void PlaceViewmodel()
|
||||||
|
{
|
||||||
|
base.PlaceViewmodel();
|
||||||
|
|
||||||
|
Camera.Main.SetViewModelCamera( 80f, 1, 500 );
|
||||||
|
}
|
||||||
|
}
|
||||||
11
code/EntityComponents/Client/ClientComponent.cs
Normal file
11
code/EntityComponents/Client/ClientComponent.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
using Sandbox;
|
||||||
|
|
||||||
|
namespace LuckerGame.Components.Client;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Intended to be a base class for components intended for IClient, as IClient cannot be used as an EntityComponent type param
|
||||||
|
/// </summary>
|
||||||
|
public abstract class ClientComponent : EntityComponent
|
||||||
|
{
|
||||||
|
public IClient Client => Entity as IClient;
|
||||||
|
}
|
||||||
36
code/EntityComponents/Lucker/Cameras/AbstractCamera.cs
Normal file
36
code/EntityComponents/Lucker/Cameras/AbstractCamera.cs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
using LuckerGame.Entities;
|
||||||
|
using Sandbox;
|
||||||
|
|
||||||
|
namespace LuckerGame.Components.Lucker.Cameras;
|
||||||
|
|
||||||
|
public abstract class AbstractCamera : EntityComponent<Entities.Lucker>
|
||||||
|
{
|
||||||
|
protected Vector3 CameraPosition { get; set; }
|
||||||
|
protected Rotation CameraRotation { get; set; }
|
||||||
|
protected float FieldOfView { get; set; }
|
||||||
|
protected IEntity FirstPersonViewer { get; set; }
|
||||||
|
protected Transform SoundSource { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles any input-independent camera updates (ie following a pawn)
|
||||||
|
/// </summary>
|
||||||
|
protected abstract void UpdateCameraParameters();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles any input dependent camera updates (ie moving around a top down cam)
|
||||||
|
/// </summary>
|
||||||
|
public abstract void BuildInput();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Applies Camera parameters to the static Camera
|
||||||
|
/// </summary>
|
||||||
|
public virtual void Update()
|
||||||
|
{
|
||||||
|
UpdateCameraParameters();
|
||||||
|
Camera.Position = CameraPosition;
|
||||||
|
Camera.Rotation = CameraRotation;
|
||||||
|
Camera.FieldOfView = FieldOfView;
|
||||||
|
Camera.FirstPersonViewer = FirstPersonViewer;
|
||||||
|
Sound.Listener = SoundSource;
|
||||||
|
}
|
||||||
|
}
|
||||||
47
code/EntityComponents/Lucker/Cameras/RTSCamera.cs
Normal file
47
code/EntityComponents/Lucker/Cameras/RTSCamera.cs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
using Sandbox;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace LuckerGame.Components.Lucker.Cameras;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A top down camera that can be
|
||||||
|
/// </summary>
|
||||||
|
public partial class RTSCamera : AbstractCamera, ISingletonComponent
|
||||||
|
{
|
||||||
|
private const float MaxDistance = 400f;
|
||||||
|
private const float MinDistance = 200f;
|
||||||
|
private const float Speed = 4f;
|
||||||
|
protected float WheelSpeed => 5f;
|
||||||
|
protected Vector2 DistanceClamp => new( MinDistance, MaxDistance );
|
||||||
|
protected float CameraDistance = MinDistance;
|
||||||
|
protected float TargetDistance = MinDistance;
|
||||||
|
|
||||||
|
private Vector3 CameraPositionOffset = Vector3.Zero;
|
||||||
|
|
||||||
|
protected override void UpdateCameraParameters()
|
||||||
|
{
|
||||||
|
CameraPosition = Entity.Position + CameraPositionOffset;
|
||||||
|
CameraRotation = Rotation.FromPitch( -270 );
|
||||||
|
FieldOfView = 70f;
|
||||||
|
FirstPersonViewer = null;
|
||||||
|
CameraPosition = CameraPosition.WithZ( CameraDistance );
|
||||||
|
SoundSource = new Transform { Position = CameraPosition };
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void BuildInput()
|
||||||
|
{
|
||||||
|
// Zooming in and out
|
||||||
|
var wheel = Input.MouseWheel;
|
||||||
|
if ( wheel != 0 )
|
||||||
|
{
|
||||||
|
TargetDistance -= wheel * WheelSpeed;
|
||||||
|
TargetDistance = TargetDistance.Clamp( DistanceClamp.x, DistanceClamp.y );
|
||||||
|
}
|
||||||
|
|
||||||
|
CameraDistance = CameraDistance.LerpTo( TargetDistance, Time.Delta * 10f );
|
||||||
|
|
||||||
|
// Moving
|
||||||
|
CameraPositionOffset += Input.AnalogMove * Speed;
|
||||||
|
}
|
||||||
|
}
|
||||||
97
code/EntityComponents/Lucker/Cameras/TopDownCamera.cs
Normal file
97
code/EntityComponents/Lucker/Cameras/TopDownCamera.cs
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
/*using Sandbox;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace LuckerGame.Components.Lucker.Cameras;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A top downish camera that follows an entity
|
||||||
|
/// </summary>
|
||||||
|
public partial class TopDownCamera : AbstractCamera, ISingletonComponent
|
||||||
|
{
|
||||||
|
protected float WheelSpeed => 30f;
|
||||||
|
protected Vector2 CameraDistance => new( 125, 1000 );
|
||||||
|
protected Vector2 PitchClamp => new( 30, 60 );
|
||||||
|
|
||||||
|
float OrbitDistance = 400f;
|
||||||
|
float TargetOrbitDistance = 400f;
|
||||||
|
Angles OrbitAngles = Angles.Zero;
|
||||||
|
|
||||||
|
protected static Vector3 IntersectPlane( Vector3 pos, Vector3 dir, float z )
|
||||||
|
{
|
||||||
|
float a = (z - pos.z) / dir.z;
|
||||||
|
return new( dir.x * a + pos.x, dir.y * a + pos.y, z );
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static Rotation LookAt( Vector3 targetPosition, Vector3 position )
|
||||||
|
{
|
||||||
|
var targetDelta = (targetPosition - position);
|
||||||
|
var direction = targetDelta.Normal;
|
||||||
|
|
||||||
|
return Rotation.From( new Angles(
|
||||||
|
((float)Math.Asin( direction.z )).RadianToDegree() * -1.0f,
|
||||||
|
((float)Math.Atan2( direction.y, direction.x )).RadianToDegree(),
|
||||||
|
0.0f ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Update()
|
||||||
|
{
|
||||||
|
var pawn = Entity;
|
||||||
|
if ( !pawn.IsValid() )
|
||||||
|
return;
|
||||||
|
|
||||||
|
Camera.Position = pawn.Position;
|
||||||
|
Vector3 targetPos;
|
||||||
|
|
||||||
|
Camera.Position += Vector3.Up * (pawn.CollisionBounds.Center.z * pawn.Scale);
|
||||||
|
Camera.Rotation = Rotation.From( OrbitAngles );
|
||||||
|
|
||||||
|
targetPos = Camera.Position + Camera.Rotation.Backward * OrbitDistance;
|
||||||
|
|
||||||
|
Camera.Position = targetPos;
|
||||||
|
Camera.FieldOfView = 70f;
|
||||||
|
Camera.FirstPersonViewer = null;
|
||||||
|
|
||||||
|
Sound.Listener = new()
|
||||||
|
{
|
||||||
|
Position = pawn.AimRay.Position,
|
||||||
|
Rotation = pawn.EyeRotation
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void BuildInput()
|
||||||
|
{
|
||||||
|
var wheel = Input.MouseWheel;
|
||||||
|
if ( wheel != 0 )
|
||||||
|
{
|
||||||
|
TargetOrbitDistance -= wheel * WheelSpeed;
|
||||||
|
TargetOrbitDistance = TargetOrbitDistance.Clamp( CameraDistance.x, CameraDistance.y );
|
||||||
|
}
|
||||||
|
|
||||||
|
OrbitDistance = OrbitDistance.LerpTo( TargetOrbitDistance, Time.Delta * 10f );
|
||||||
|
|
||||||
|
// Sets pawn to face where the camera faces
|
||||||
|
if ( Input.UsingController || Input.Down( "attack2" ) )
|
||||||
|
{
|
||||||
|
OrbitAngles.yaw += Input.AnalogLook.yaw;
|
||||||
|
OrbitAngles.pitch += Input.AnalogLook.pitch;
|
||||||
|
OrbitAngles = OrbitAngles.Normal;
|
||||||
|
|
||||||
|
Entity.ViewAngles = OrbitAngles.WithPitch( 0f );
|
||||||
|
}
|
||||||
|
// Sets pawn to face to point that the mouse is over
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Get ray from direction vector from mouse pointer through screen
|
||||||
|
var direction = Screen.GetDirection( Mouse.Position, Camera.FieldOfView, Camera.Rotation, Screen.Size );
|
||||||
|
|
||||||
|
var hitPos = IntersectPlane( Camera.Position, direction, Entity.EyePosition.z );
|
||||||
|
|
||||||
|
Entity.ViewAngles = (hitPos - Entity.EyePosition).EulerAngles;
|
||||||
|
}
|
||||||
|
|
||||||
|
OrbitAngles.pitch = OrbitAngles.pitch.Clamp( PitchClamp.x, PitchClamp.y );
|
||||||
|
|
||||||
|
Entity.InputDirection = Input.AnalogMove;
|
||||||
|
}
|
||||||
|
}*/
|
||||||
15
code/EntityComponents/Lucker/LuckerClientInput.cs
Normal file
15
code/EntityComponents/Lucker/LuckerClientInput.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
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; }
|
||||||
|
}
|
||||||
21
code/EntityComponents/Pawn/PawnAnimator.cs
Normal file
21
code/EntityComponents/Pawn/PawnAnimator.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using Sandbox;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace LuckerGame.Components.Pawn;
|
||||||
|
|
||||||
|
public class PawnAnimator : EntityComponent<Entities.Pawn>, ISingletonComponent
|
||||||
|
{
|
||||||
|
public void Simulate()
|
||||||
|
{
|
||||||
|
var helper = new CitizenAnimationHelper( Entity );
|
||||||
|
helper.WithVelocity( Entity.Velocity );
|
||||||
|
helper.WithLookAt( Entity.EyePosition + Entity.EyeRotation.Forward * 100 );
|
||||||
|
helper.HoldType = CitizenAnimationHelper.HoldTypes.None;
|
||||||
|
helper.IsGrounded = Entity.GroundEntity.IsValid();
|
||||||
|
|
||||||
|
if ( Entity.Controller.HasEvent( "jump" ) )
|
||||||
|
{
|
||||||
|
helper.TriggerJump();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
174
code/EntityComponents/Pawn/UserPawnController.cs
Normal file
174
code/EntityComponents/Pawn/UserPawnController.cs
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
using Sandbox;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace LuckerGame.Components.Pawn;
|
||||||
|
|
||||||
|
public class UserPawnController : EntityComponent<Entities.Pawn>
|
||||||
|
{
|
||||||
|
public int StepSize => 24;
|
||||||
|
public int GroundAngle => 45;
|
||||||
|
public int JumpSpeed => 410;
|
||||||
|
public float Gravity => 800f;
|
||||||
|
|
||||||
|
HashSet<string> ControllerEvents = new( StringComparer.OrdinalIgnoreCase );
|
||||||
|
|
||||||
|
bool Grounded => Entity.GroundEntity.IsValid();
|
||||||
|
|
||||||
|
public void Simulate( IClient cl )
|
||||||
|
{
|
||||||
|
ControllerEvents.Clear();
|
||||||
|
|
||||||
|
var movement = Entity.InputDirection.Normal;
|
||||||
|
var angles = Camera.Rotation.Angles().WithPitch( 0 );
|
||||||
|
var moveVector = Rotation.From( angles ) * movement * 320f;
|
||||||
|
var groundEntity = CheckForGround();
|
||||||
|
|
||||||
|
if ( groundEntity.IsValid() )
|
||||||
|
{
|
||||||
|
if ( !Grounded )
|
||||||
|
{
|
||||||
|
Entity.Velocity = Entity.Velocity.WithZ( 0 );
|
||||||
|
AddEvent( "grounded" );
|
||||||
|
}
|
||||||
|
|
||||||
|
Entity.Velocity = Accelerate( Entity.Velocity, moveVector.Normal, moveVector.Length, 200.0f * ( Input.Down( "run" ) ? 2.5f : 1f ), 7.5f );
|
||||||
|
Entity.Velocity = ApplyFriction( Entity.Velocity, 4.0f );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Entity.Velocity = Accelerate( Entity.Velocity, moveVector.Normal, moveVector.Length, 15, 20f );
|
||||||
|
Entity.Velocity += Vector3.Down * Gravity * Time.Delta;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( Input.Pressed( "jump" ) )
|
||||||
|
{
|
||||||
|
DoJump();
|
||||||
|
}
|
||||||
|
|
||||||
|
var mh = new MoveHelper( Entity.Position, Entity.Velocity );
|
||||||
|
mh.Trace = mh.Trace.Size( Entity.Hull ).Ignore( Entity );
|
||||||
|
|
||||||
|
if ( mh.TryMoveWithStep( Time.Delta, StepSize ) > 0 )
|
||||||
|
{
|
||||||
|
if ( Grounded )
|
||||||
|
{
|
||||||
|
mh.Position = StayOnGround( mh.Position );
|
||||||
|
}
|
||||||
|
Entity.Position = mh.Position;
|
||||||
|
Entity.Velocity = mh.Velocity;
|
||||||
|
}
|
||||||
|
|
||||||
|
Entity.GroundEntity = groundEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DoJump()
|
||||||
|
{
|
||||||
|
if ( Grounded )
|
||||||
|
{
|
||||||
|
Entity.Velocity = ApplyJump( Entity.Velocity, "jump" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Entity CheckForGround()
|
||||||
|
{
|
||||||
|
if ( Entity.Velocity.z > 300f )
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var trace = Entity.TraceBBox( Entity.Position, Entity.Position + Vector3.Down, 2f );
|
||||||
|
|
||||||
|
if ( !trace.Hit )
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if ( trace.Normal.Angle( Vector3.Up ) > GroundAngle )
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return trace.Entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3 ApplyFriction( Vector3 input, float frictionAmount )
|
||||||
|
{
|
||||||
|
float StopSpeed = 100.0f;
|
||||||
|
|
||||||
|
var speed = input.Length;
|
||||||
|
if ( speed < 0.1f ) return input;
|
||||||
|
|
||||||
|
// Bleed off some speed, but if we have less than the bleed
|
||||||
|
// threshold, bleed the threshold amount.
|
||||||
|
float control = (speed < StopSpeed) ? StopSpeed : speed;
|
||||||
|
|
||||||
|
// Add the amount to the drop amount.
|
||||||
|
var drop = control * Time.Delta * frictionAmount;
|
||||||
|
|
||||||
|
// scale the velocity
|
||||||
|
float newspeed = speed - drop;
|
||||||
|
if ( newspeed < 0 ) newspeed = 0;
|
||||||
|
if ( newspeed == speed ) return input;
|
||||||
|
|
||||||
|
newspeed /= speed;
|
||||||
|
input *= newspeed;
|
||||||
|
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3 Accelerate( Vector3 input, Vector3 wishdir, float wishspeed, float speedLimit, float acceleration )
|
||||||
|
{
|
||||||
|
if ( speedLimit > 0 && wishspeed > speedLimit )
|
||||||
|
wishspeed = speedLimit;
|
||||||
|
|
||||||
|
var currentspeed = input.Dot( wishdir );
|
||||||
|
var addspeed = wishspeed - currentspeed;
|
||||||
|
|
||||||
|
if ( addspeed <= 0 )
|
||||||
|
return input;
|
||||||
|
|
||||||
|
var accelspeed = acceleration * Time.Delta * wishspeed;
|
||||||
|
|
||||||
|
if ( accelspeed > addspeed )
|
||||||
|
accelspeed = addspeed;
|
||||||
|
|
||||||
|
input += wishdir * accelspeed;
|
||||||
|
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3 ApplyJump( Vector3 input, string jumpType )
|
||||||
|
{
|
||||||
|
AddEvent( jumpType );
|
||||||
|
|
||||||
|
return input + Vector3.Up * JumpSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3 StayOnGround( Vector3 position )
|
||||||
|
{
|
||||||
|
var start = position + Vector3.Up * 2;
|
||||||
|
var end = position + Vector3.Down * StepSize;
|
||||||
|
|
||||||
|
// See how far up we can go without getting stuck
|
||||||
|
var trace = Entity.TraceBBox( position, start );
|
||||||
|
start = trace.EndPosition;
|
||||||
|
|
||||||
|
// Now trace down from a known safe position
|
||||||
|
trace = Entity.TraceBBox( start, end );
|
||||||
|
|
||||||
|
if ( trace.Fraction <= 0 ) return position;
|
||||||
|
if ( trace.Fraction >= 1 ) return position;
|
||||||
|
if ( trace.StartedSolid ) return position;
|
||||||
|
if ( Vector3.GetAngle( Vector3.Up, trace.Normal ) > GroundAngle ) return position;
|
||||||
|
|
||||||
|
return trace.EndPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool HasEvent( string eventName )
|
||||||
|
{
|
||||||
|
return ControllerEvents.Contains( eventName );
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddEvent( string eventName )
|
||||||
|
{
|
||||||
|
if ( HasEvent( eventName ) )
|
||||||
|
return;
|
||||||
|
|
||||||
|
ControllerEvents.Add( eventName );
|
||||||
|
}
|
||||||
|
}
|
||||||
20
code/Enums/RoundState.cs
Normal file
20
code/Enums/RoundState.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
namespace LuckerGame.Enums;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The state of a current round of minigames
|
||||||
|
/// </summary>
|
||||||
|
public enum RoundState
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This round has not yet started
|
||||||
|
/// </summary>
|
||||||
|
NotStarted,
|
||||||
|
/// <summary>
|
||||||
|
/// This round is counting down to start
|
||||||
|
/// </summary>
|
||||||
|
StartCountdown,
|
||||||
|
/// <summary>
|
||||||
|
/// This round is currently in progress
|
||||||
|
/// </summary>
|
||||||
|
InProgress
|
||||||
|
}
|
||||||
18
code/Events/MinigameEndEvent.cs
Normal file
18
code/Events/MinigameEndEvent.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using Sandbox;
|
||||||
|
|
||||||
|
namespace LuckerGame.Events;
|
||||||
|
|
||||||
|
public static partial class LuckerEvent
|
||||||
|
{
|
||||||
|
public const string MinigameEnd = "lucker.minigameEnd";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event is run on the server whenever a minigame ends
|
||||||
|
/// </summary>
|
||||||
|
public class MinigameEndAttribute : EventAttribute
|
||||||
|
{
|
||||||
|
public MinigameEndAttribute() : base(MinigameEnd)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
20
code/Events/PlayerReadyEvent.cs
Normal file
20
code/Events/PlayerReadyEvent.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
using Sandbox;
|
||||||
|
|
||||||
|
namespace LuckerGame.Events;
|
||||||
|
|
||||||
|
public static partial class LuckerEvent
|
||||||
|
{
|
||||||
|
public const string PlayerReady = "lucker.playerReady";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event is run on the server whenever a player changes ready state
|
||||||
|
/// The event handler is given the player that readied up and their new ready state
|
||||||
|
/// </summary>
|
||||||
|
[MethodArguments(typeof(Entities.Lucker), typeof(bool))]
|
||||||
|
public class PlayerReadyAttribute : EventAttribute
|
||||||
|
{
|
||||||
|
public PlayerReadyAttribute() : base(PlayerReady)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
69
code/Game.cs
Normal file
69
code/Game.cs
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
|
||||||
|
using Sandbox;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using LuckerGame.Components.Client;
|
||||||
|
using LuckerGame.UI;
|
||||||
|
using LuckerGame.Entities;
|
||||||
|
using Sandbox.UI;
|
||||||
|
|
||||||
|
//
|
||||||
|
// You don't need to put things in a namespace, but it doesn't hurt.
|
||||||
|
//
|
||||||
|
namespace LuckerGame;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This is your game class. This is an entity that is created serverside when
|
||||||
|
/// the game starts, and is replicated to the client.
|
||||||
|
///
|
||||||
|
/// You can use this to create things like HUDs and declare which player class
|
||||||
|
/// to use for spawned players.
|
||||||
|
/// </summary>
|
||||||
|
public partial class LuckerGameManager : GameManager
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Called when the game is created (on both the server and client)
|
||||||
|
/// </summary>
|
||||||
|
public LuckerGameManager()
|
||||||
|
{
|
||||||
|
if ( Game.IsClient )
|
||||||
|
{
|
||||||
|
Game.RootPanel = new Hud();
|
||||||
|
}
|
||||||
|
else if ( Game.IsServer )
|
||||||
|
{
|
||||||
|
var roundManager = new RoundManager();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void PostLevelLoaded()
|
||||||
|
{
|
||||||
|
base.PostLevelLoaded();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A client has joined the server. Make them a pawn to play with
|
||||||
|
/// </summary>
|
||||||
|
public override void ClientJoined( IClient client )
|
||||||
|
{
|
||||||
|
base.ClientJoined( client );
|
||||||
|
|
||||||
|
// Create a pawn for this client to play with
|
||||||
|
var lucker = Entities.Lucker.CreateLuckerForClient( client );
|
||||||
|
|
||||||
|
// Get all of the spawnpoints
|
||||||
|
var spawnpoints = Entity.All.OfType<SpawnPoint>();
|
||||||
|
|
||||||
|
// chose a random one
|
||||||
|
var randomSpawnPoint = spawnpoints.OrderBy( x => Guid.NewGuid() ).FirstOrDefault();
|
||||||
|
|
||||||
|
// if it exists, place the pawn there
|
||||||
|
if ( randomSpawnPoint != null )
|
||||||
|
{
|
||||||
|
var tx = randomSpawnPoint.Transform;
|
||||||
|
tx.Position = tx.Position + Vector3.Up * 50.0f; // raise it up
|
||||||
|
lucker.Position = tx.Position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
22
code/Minigames/Minigame.cs
Normal file
22
code/Minigames/Minigame.cs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using LuckerGame.Entities;
|
||||||
|
using Sandbox;
|
||||||
|
|
||||||
|
namespace LuckerGame.Minigames;
|
||||||
|
|
||||||
|
public abstract class Minigame : Entity
|
||||||
|
{
|
||||||
|
public abstract string Name { get; }
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes the minigame with a list of luckers playing it
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="players">the players who made it into the minigame</param>
|
||||||
|
public abstract void Initialize(List<Lucker> players);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cleans up any entities and components created by this minigame.
|
||||||
|
/// It is not necessary to remove the lucker's pawns, the manager will do so if any were assigned.
|
||||||
|
/// </summary>
|
||||||
|
public abstract void Cleanup();
|
||||||
|
}
|
||||||
36
code/Minigames/RussianRoulette/RussianRouletteMinigame.cs
Normal file
36
code/Minigames/RussianRoulette/RussianRouletteMinigame.cs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using LuckerGame.Entities;
|
||||||
|
using Sandbox;
|
||||||
|
|
||||||
|
namespace LuckerGame.Minigames.RussianRoulette;
|
||||||
|
|
||||||
|
public class RussianRouletteMinigame : Minigame
|
||||||
|
{
|
||||||
|
public override string Name => "Russian Roulette";
|
||||||
|
|
||||||
|
public override void Initialize( List<Lucker> players )
|
||||||
|
{
|
||||||
|
var pawn = new Pawn();
|
||||||
|
|
||||||
|
// Get all of the spawnpoints
|
||||||
|
var spawnpoints = Entity.All.OfType<SpawnPoint>();
|
||||||
|
|
||||||
|
// chose a random one
|
||||||
|
var randomSpawnPoint = spawnpoints.OrderBy( x => Guid.NewGuid() ).FirstOrDefault();
|
||||||
|
|
||||||
|
// if it exists, place the pawn there
|
||||||
|
if ( randomSpawnPoint != null )
|
||||||
|
{
|
||||||
|
var tx = randomSpawnPoint.Transform;
|
||||||
|
tx.Position = tx.Position + Vector3.Up * 50.0f; // raise it up
|
||||||
|
pawn.Position = tx.Position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Cleanup()
|
||||||
|
{
|
||||||
|
throw new System.NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
27
code/UI/Hud.razor
Normal file
27
code/UI/Hud.razor
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
@using Sandbox;
|
||||||
|
@using Sandbox.UI;
|
||||||
|
@using LuckerGame.UI.HudComponents
|
||||||
|
@using LuckerGame.UI.Menus
|
||||||
|
|
||||||
|
@namespace LuckerGame.UI
|
||||||
|
@inherits RootPanel
|
||||||
|
@attribute [StyleSheet]
|
||||||
|
|
||||||
|
<root>
|
||||||
|
<ChatBox/>
|
||||||
|
<VoiceList/>
|
||||||
|
<VotingLobby/>
|
||||||
|
<Scoreboard/>
|
||||||
|
|
||||||
|
<div class="pointer-visible" />
|
||||||
|
|
||||||
|
</root>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
public override void Tick()
|
||||||
|
{
|
||||||
|
var devCam = Game.LocalClient.Components.Get<DevCamera>();
|
||||||
|
SetClass( "camera-movement", Input.UsingController || Input.Down( "attack2" ) || devCam is not null );
|
||||||
|
}
|
||||||
|
}
|
||||||
30
code/UI/Hud.razor.scss
Normal file
30
code/UI/Hud.razor.scss
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
$primary-color: 0, 123, 160;
|
||||||
|
$primary-color-translucent: rgba($primary-color, 0.7);
|
||||||
|
Hud
|
||||||
|
{
|
||||||
|
}
|
||||||
|
.primary-color-background {
|
||||||
|
background-color: rgb($primary-color);
|
||||||
|
}
|
||||||
|
.primary-color-translucent-background {
|
||||||
|
background-color: $primary-color-translucent;
|
||||||
|
}
|
||||||
|
label
|
||||||
|
{
|
||||||
|
font-family: Poppins;
|
||||||
|
color: white;
|
||||||
|
font-size: 32px;
|
||||||
|
|
||||||
|
&.subtitle
|
||||||
|
{
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.header {
|
||||||
|
font-size: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.material-icon {
|
||||||
|
font-family: "Material Icons";
|
||||||
|
}
|
||||||
|
}
|
||||||
29
code/UI/HudComponents/Scoreboard.razor
Normal file
29
code/UI/HudComponents/Scoreboard.razor
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
@using System
|
||||||
|
@using System.Collections.Generic
|
||||||
|
@using System.Linq
|
||||||
|
@using LuckerGame.Entities
|
||||||
|
@using Sandbox
|
||||||
|
@using Sandbox.UI
|
||||||
|
@namespace LuckerGame.UI.HudComponents
|
||||||
|
@attribute [StyleSheet]
|
||||||
|
@inherits Panel
|
||||||
|
|
||||||
|
<root>
|
||||||
|
<div class="scoreboard-panel">
|
||||||
|
@foreach (var player in Luckers)
|
||||||
|
{
|
||||||
|
<label>@player.Name</label>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</root>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
|
||||||
|
private IReadOnlyCollection<Lucker> Luckers => Entity.All.OfType<Lucker>().ToList();
|
||||||
|
|
||||||
|
protected override int BuildHash()
|
||||||
|
{
|
||||||
|
return HashCode.Combine(Luckers.Select(player => player.Name).ToList());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
3
code/UI/HudComponents/Scoreboard.razor.scss
Normal file
3
code/UI/HudComponents/Scoreboard.razor.scss
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
Scoreboard {
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
26
code/UI/MenuComponents/LuckerButton.razor
Normal file
26
code/UI/MenuComponents/LuckerButton.razor
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
@using System
|
||||||
|
@using Sandbox.Razor
|
||||||
|
@using Sandbox.UI
|
||||||
|
|
||||||
|
@inherits Panel
|
||||||
|
@attribute [StyleSheet]
|
||||||
|
@namespace LuckerGame.UI.MenuComponents
|
||||||
|
|
||||||
|
<root>
|
||||||
|
<div class="lucker-button">
|
||||||
|
@ChildContent
|
||||||
|
</div>
|
||||||
|
</root>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
|
||||||
|
private RenderFragment _childContent;
|
||||||
|
|
||||||
|
public RenderFragment ChildContent
|
||||||
|
{
|
||||||
|
get { return _childContent; }
|
||||||
|
set { _childContent = value; StateHasChanged(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
15
code/UI/MenuComponents/LuckerButton.razor.scss
Normal file
15
code/UI/MenuComponents/LuckerButton.razor.scss
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
LuckerButton {
|
||||||
|
div {
|
||||||
|
&.lucker-button {
|
||||||
|
z-index: 11;
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: gray;
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 10px;
|
||||||
|
|
||||||
|
:hover {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
9
code/UI/MenuComponents/LuckerSpinner.razor
Normal file
9
code/UI/MenuComponents/LuckerSpinner.razor
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
@using Sandbox.UI
|
||||||
|
|
||||||
|
@namespace LuckerGame.UI.MenuComponents
|
||||||
|
@inherits Panel
|
||||||
|
@attribute [StyleSheet]
|
||||||
|
|
||||||
|
<root>
|
||||||
|
<div class="loader"/>
|
||||||
|
</root>
|
||||||
20
code/UI/MenuComponents/LuckerSpinner.razor.scss
Normal file
20
code/UI/MenuComponents/LuckerSpinner.razor.scss
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
LuckerSpinner {
|
||||||
|
.loader {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
border: 5px solid #FFF;
|
||||||
|
border-bottom-color: transparent;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation-name: rotation;
|
||||||
|
animation-duration: 1s;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes rotation {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
77
code/UI/Menus/VotingLobby.razor
Normal file
77
code/UI/Menus/VotingLobby.razor
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
@using System
|
||||||
|
@using System.Collections.Generic
|
||||||
|
@using System.Linq
|
||||||
|
@using LuckerGame.Entities
|
||||||
|
@using LuckerGame.Enums
|
||||||
|
@using Sandbox.UI;
|
||||||
|
@using Sandbox;
|
||||||
|
@using LuckerGame.UI.MenuComponents
|
||||||
|
|
||||||
|
@inherits Panel
|
||||||
|
@attribute [StyleSheet]
|
||||||
|
@namespace LuckerGame.UI.Menus
|
||||||
|
@if (RoundManager == null)
|
||||||
|
{
|
||||||
|
<root>
|
||||||
|
<div class="voting-panel primary-color-translucent-background">
|
||||||
|
<LuckerSpinner/>
|
||||||
|
</div>
|
||||||
|
</root>
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
@if (RoundManager.RoundState == RoundState.InProgress)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
<root>
|
||||||
|
<div class="voting-panel primary-color-translucent-background">
|
||||||
|
@if (RoundManager.RoundState == RoundState.NotStarted)
|
||||||
|
{
|
||||||
|
<label class="header">Waiting for players...</label>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (@RoundManager.SecondsLeftInCountdown > 0)
|
||||||
|
{
|
||||||
|
<label class="header">@RoundManager.SecondsLeftInCountdown</label>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<label class="header">Good luck!</label>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
<div class="voters">
|
||||||
|
@foreach (var lucker in Luckers)
|
||||||
|
{
|
||||||
|
<div class="voter">
|
||||||
|
<label>@lucker.Name </label><label class="material-icon">@(lucker.Ready ? "done" : "close")</label>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<LuckerButton @onclick=@ReadyButtonPressed>
|
||||||
|
<ChildContent>
|
||||||
|
<label>@(Ready ? "Unready" : "Ready")</label>
|
||||||
|
</ChildContent>
|
||||||
|
</LuckerButton>
|
||||||
|
</div>
|
||||||
|
</root>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
|
||||||
|
private List<Lucker> Luckers => Entity.All.OfType<Lucker>().ToList();
|
||||||
|
private RoundManager RoundManager => Entity.All.OfType<RoundManager>().FirstOrDefault();
|
||||||
|
private Lucker ThisLucker => Game.LocalClient.Pawn as Lucker;
|
||||||
|
private bool Ready => ThisLucker.Ready;
|
||||||
|
|
||||||
|
protected override int BuildHash()
|
||||||
|
{
|
||||||
|
return HashCode.Combine(Luckers.Select(lucker => lucker.Ready), Ready, RoundManager?.SecondsLeftInCountdown);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReadyButtonPressed()
|
||||||
|
{
|
||||||
|
ConsoleSystem.Run("set_ready", !ThisLucker.Ready);
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
43
code/UI/Menus/VotingLobby.razor.scss
Normal file
43
code/UI/Menus/VotingLobby.razor.scss
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
VotingLobby {
|
||||||
|
z-index: 10;
|
||||||
|
.label {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.voting-panel {
|
||||||
|
gap: 10px;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
width: 100vw;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
pointer-events: all;
|
||||||
|
|
||||||
|
.header {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
LuckerButton {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.voters {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.voter {
|
||||||
|
width: 100vw;
|
||||||
|
.label {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user