first commit
This commit is contained in:
commit
4599fe0f95
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
|
35
.sbproj
Normal file
35
.sbproj
Normal file
@ -0,0 +1,35 @@
|
||||
{
|
||||
"Title": "Project 1",
|
||||
"Type": "game",
|
||||
"Org": "local",
|
||||
"Ident": "project_1",
|
||||
"Tags": null,
|
||||
"Schema": 1,
|
||||
"HasAssets": true,
|
||||
"AssetsPath": "",
|
||||
"Resources": null,
|
||||
"MenuResources": null,
|
||||
"HasCode": true,
|
||||
"CodePath": "/code/",
|
||||
"PackageReferences": [],
|
||||
"EditorReferences": null,
|
||||
"Metadata": {
|
||||
"MaxPlayers": 16,
|
||||
"MinPlayers": 1,
|
||||
"GameNetworkType": "Multiplayer",
|
||||
"MapSelect": "Unrestricted",
|
||||
"MapList": [
|
||||
"facepunch.square"
|
||||
],
|
||||
"RankType": "None",
|
||||
"PerMapRanking": false,
|
||||
"LeaderboardType": "None",
|
||||
"GameCategory": "TechDemos",
|
||||
"ProjectTemplate": null,
|
||||
"CsProjName": "",
|
||||
"HttpAllowList": null,
|
||||
"SavedGames": true,
|
||||
"MapDependentSaves": false,
|
||||
"SaveFormatVersion": 1001
|
||||
}
|
||||
}
|
BIN
Maps/rbanhdevmap.vmap
Normal file
BIN
Maps/rbanhdevmap.vmap
Normal file
Binary file not shown.
28
Maps/rbanhdevmap.vmap.meta
Normal file
28
Maps/rbanhdevmap.vmap.meta
Normal file
@ -0,0 +1,28 @@
|
||||
{
|
||||
"publish": {
|
||||
"Allowed": true,
|
||||
"Enabled": false,
|
||||
"ProjectConfig": {
|
||||
"Title": "rbanhdevmap",
|
||||
"Type": "map",
|
||||
"Org": "local",
|
||||
"Ident": "rbanhdevmap",
|
||||
"Tags": null,
|
||||
"Schema": 0,
|
||||
"HasAssets": false,
|
||||
"AssetsPath": null,
|
||||
"Resources": null,
|
||||
"MenuResources": null,
|
||||
"HasCode": false,
|
||||
"CodePath": null,
|
||||
"PackageReferences": [],
|
||||
"EditorReferences": [
|
||||
"sboxmp.stalker_dolg#35122",
|
||||
"kittito.usfishmongertw#9784"
|
||||
],
|
||||
"Metadata": {
|
||||
"SingleAssetSource": "maps/rbanhdevmap.vmap"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
11
README.md
Normal file
11
README.md
Normal file
@ -0,0 +1,11 @@
|
||||
# Shooter Template
|
||||
Designed as a minimal starting point for your projects in s&box.
|
||||
|
||||
## Features
|
||||
- First person pawn
|
||||
- Simple pawn controller
|
||||
- Simple weapon system
|
||||
|
||||
## Controls
|
||||
- Regular movement with WASD
|
||||
- Shoot weapon with LMB
|
BIN
code/.vs/project_1/DesignTimeBuild/.dtbcache.v2
Normal file
BIN
code/.vs/project_1/DesignTimeBuild/.dtbcache.v2
Normal file
Binary file not shown.
Binary file not shown.
0
code/.vs/project_1/FileContentIndex/read.lock
Normal file
0
code/.vs/project_1/FileContentIndex/read.lock
Normal file
BIN
code/.vs/project_1/v17/.futdcache.v2
Normal file
BIN
code/.vs/project_1/v17/.futdcache.v2
Normal file
Binary file not shown.
BIN
code/.vs/project_1/v17/.suo
Normal file
BIN
code/.vs/project_1/v17/.suo
Normal file
Binary file not shown.
59
code/Game.cs
Normal file
59
code/Game.cs
Normal file
@ -0,0 +1,59 @@
|
||||
|
||||
using Sandbox;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
//
|
||||
// You don't need to put things in a namespace, but it doesn't hurt.
|
||||
//
|
||||
namespace MyGame;
|
||||
|
||||
/// <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 MyGame : Sandbox.GameManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Called when the game is created (on both the server and client)
|
||||
/// </summary>
|
||||
public MyGame()
|
||||
{
|
||||
if ( Game.IsClient )
|
||||
{
|
||||
Game.RootPanel = new Hud();
|
||||
}
|
||||
}
|
||||
|
||||
/// <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 pawn = new Pawn();
|
||||
client.Pawn = pawn;
|
||||
pawn.Respawn();
|
||||
pawn.DressFromClient( 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
|
||||
pawn.Transform = tx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
194
code/pawn/Pawn.cs
Normal file
194
code/pawn/Pawn.cs
Normal file
@ -0,0 +1,194 @@
|
||||
using Sandbox;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace MyGame;
|
||||
|
||||
public partial class Pawn : AnimatedEntity
|
||||
{
|
||||
[Net, Predicted]
|
||||
public Weapon ActiveWeapon { get; set; }
|
||||
|
||||
[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 PawnController 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 SetActiveWeapon( Weapon weapon )
|
||||
{
|
||||
ActiveWeapon?.OnHolster();
|
||||
ActiveWeapon = weapon;
|
||||
ActiveWeapon.OnEquip( this );
|
||||
}
|
||||
|
||||
public void Respawn()
|
||||
{
|
||||
Components.Create<PawnController>();
|
||||
Components.Create<PawnAnimator>();
|
||||
|
||||
SetActiveWeapon( new Pistol() );
|
||||
}
|
||||
|
||||
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();
|
||||
ActiveWeapon?.Simulate( cl );
|
||||
EyeLocalPosition = Vector3.Up * (64f * Scale);
|
||||
}
|
||||
|
||||
public override void BuildInput()
|
||||
{
|
||||
InputDirection = Input.AnalogMove;
|
||||
|
||||
if ( Input.StopProcessing )
|
||||
return;
|
||||
|
||||
var look = Input.AnalogLook;
|
||||
|
||||
if ( ViewAngles.pitch > 90f || ViewAngles.pitch < -90f )
|
||||
{
|
||||
look = look.WithYaw( look.yaw * -1f );
|
||||
}
|
||||
|
||||
var viewAngles = ViewAngles;
|
||||
viewAngles += look;
|
||||
viewAngles.pitch = viewAngles.pitch.Clamp( -89f, 89f );
|
||||
viewAngles.roll = 0f;
|
||||
ViewAngles = viewAngles.Normal;
|
||||
}
|
||||
|
||||
bool IsThirdPerson { get; set; } = false;
|
||||
|
||||
public override void FrameSimulate( IClient cl )
|
||||
{
|
||||
SimulateRotation();
|
||||
|
||||
Camera.Rotation = ViewAngles.ToRotation();
|
||||
Camera.FieldOfView = Screen.CreateVerticalFieldOfView( Game.Preferences.FieldOfView );
|
||||
|
||||
if ( Input.Pressed( "view" ) )
|
||||
{
|
||||
IsThirdPerson = !IsThirdPerson;
|
||||
}
|
||||
|
||||
if ( IsThirdPerson )
|
||||
{
|
||||
Vector3 targetPos;
|
||||
var pos = Position + Vector3.Up * 64;
|
||||
var rot = Camera.Rotation * Rotation.FromAxis( Vector3.Up, -16 );
|
||||
|
||||
float distance = 80.0f * Scale;
|
||||
targetPos = pos + rot.Right * ((CollisionBounds.Mins.x + 50) * Scale);
|
||||
targetPos += rot.Forward * -distance;
|
||||
|
||||
var tr = Trace.Ray( pos, targetPos )
|
||||
.WithAnyTags( "solid" )
|
||||
.Ignore( this )
|
||||
.Radius( 8 )
|
||||
.Run();
|
||||
|
||||
Camera.FirstPersonViewer = null;
|
||||
Camera.Position = tr.EndPosition;
|
||||
}
|
||||
else
|
||||
{
|
||||
Camera.FirstPersonViewer = this;
|
||||
Camera.Position = EyePosition;
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
EyeRotation = ViewAngles.ToRotation();
|
||||
Rotation = ViewAngles.WithPitch( 0f ).ToRotation();
|
||||
}
|
||||
}
|
21
code/pawn/PawnAnimator.cs
Normal file
21
code/pawn/PawnAnimator.cs
Normal file
@ -0,0 +1,21 @@
|
||||
using Sandbox;
|
||||
using System;
|
||||
|
||||
namespace MyGame;
|
||||
|
||||
public class PawnAnimator : EntityComponent<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/pawn/PawnController.cs
Normal file
174
code/pawn/PawnController.cs
Normal file
@ -0,0 +1,174 @@
|
||||
using Sandbox;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MyGame;
|
||||
|
||||
public class PawnController : EntityComponent<Pawn>
|
||||
{
|
||||
public int StepSize => 24;
|
||||
public int GroundAngle => 45;
|
||||
public int JumpSpeed => 300;
|
||||
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 = Entity.ViewAngles.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, 100, 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 > 100f )
|
||||
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/ui/Hud.razor
Normal file
20
code/ui/Hud.razor
Normal file
@ -0,0 +1,20 @@
|
||||
@using Sandbox;
|
||||
@using Sandbox.UI;
|
||||
|
||||
@namespace MyGame
|
||||
@inherits RootPanel
|
||||
@attribute [StyleSheet]
|
||||
|
||||
<root>
|
||||
<ChatBox/>
|
||||
<VoiceList/>
|
||||
|
||||
<div class="header">
|
||||
<label>Shooter Game</label>
|
||||
<label class="subtitle">A template to kick start the production of your own games.</label>
|
||||
</div>
|
||||
</root>
|
||||
|
||||
@code
|
||||
{
|
||||
}
|
21
code/ui/Hud.razor.scss
Normal file
21
code/ui/Hud.razor.scss
Normal file
@ -0,0 +1,21 @@
|
||||
Hud
|
||||
{
|
||||
.header
|
||||
{
|
||||
left: 128px;
|
||||
top: 128px;
|
||||
flex-direction: column;
|
||||
|
||||
label
|
||||
{
|
||||
font-family: Roboto;
|
||||
color: white;
|
||||
font-size: 32px;
|
||||
|
||||
&.subtitle
|
||||
{
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
118
code/ui/mainmenu/ActiveLobby.razor
Normal file
118
code/ui/mainmenu/ActiveLobby.razor
Normal file
@ -0,0 +1,118 @@
|
||||
@using Sandbox;
|
||||
@using System;
|
||||
@using System.Linq;
|
||||
@using System.Threading.Tasks;
|
||||
@using Sandbox.Menu;
|
||||
@using Sandbox.UI;
|
||||
|
||||
<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 );
|
||||
}
|
||||
}
|
39
code/ui/mainmenu/FrontPage.razor
Normal file
39
code/ui/mainmenu/FrontPage.razor
Normal file
@ -0,0 +1,39 @@
|
||||
@using Sandbox;
|
||||
@using System.Linq;
|
||||
@using System.Threading.Tasks;
|
||||
@using Sandbox.Menu;
|
||||
@using Sandbox.UI;
|
||||
|
||||
<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" );
|
||||
}
|
||||
}
|
30
code/ui/mainmenu/LoadingScreen.razor
Normal file
30
code/ui/mainmenu/LoadingScreen.razor
Normal file
@ -0,0 +1,30 @@
|
||||
@using Sandbox;
|
||||
@using Sandbox.UI;
|
||||
@using System.Linq;
|
||||
@using System.Threading.Tasks;
|
||||
@using Sandbox.Menu;
|
||||
|
||||
@inherits RootPanel
|
||||
@implements Sandbox.Menu.ILoadingScreenPanel
|
||||
@attribute [StyleSheet]
|
||||
|
||||
<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
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;
|
||||
}
|
||||
}
|
64
code/ui/mainmenu/LobbyBrowser.razor
Normal file
64
code/ui/mainmenu/LobbyBrowser.razor
Normal file
@ -0,0 +1,64 @@
|
||||
@using Sandbox;
|
||||
@using System;
|
||||
@using System.Linq;
|
||||
@using System.Threading.Tasks;
|
||||
@using Sandbox.Menu;
|
||||
@using Sandbox.UI;
|
||||
|
||||
<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() );
|
||||
}
|
||||
}
|
39
code/ui/mainmenu/MainMenu.razor
Normal file
39
code/ui/mainmenu/MainMenu.razor
Normal file
@ -0,0 +1,39 @@
|
||||
@using System;
|
||||
@using Sandbox;
|
||||
@using Sandbox.UI;
|
||||
|
||||
@inherits Sandbox.UI.NavHostPanel
|
||||
@implements Sandbox.Menu.IGameMenuPanel
|
||||
@attribute [StyleSheet]
|
||||
|
||||
<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
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;
|
||||
}
|
||||
}
|
42
code/ui/mainmenu/SavedGames.razor
Normal file
42
code/ui/mainmenu/SavedGames.razor
Normal file
@ -0,0 +1,42 @@
|
||||
@using Sandbox;
|
||||
@using System;
|
||||
@using System.Linq;
|
||||
@using Sandbox.UI;
|
||||
|
||||
<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 );
|
||||
}
|
||||
}
|
76
code/ui/mainmenu/SetupGame.razor
Normal file
76
code/ui/mainmenu/SetupGame.razor
Normal file
@ -0,0 +1,76 @@
|
||||
@using Sandbox;
|
||||
@using System;
|
||||
@using System.Linq;
|
||||
@using System.Threading.Tasks;
|
||||
@using Sandbox.Menu;
|
||||
@using Sandbox.UI;
|
||||
|
||||
<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 );
|
||||
}
|
||||
}
|
30
code/ui/mainmenu/SlimPackageCard.razor
Normal file
30
code/ui/mainmenu/SlimPackageCard.razor
Normal file
@ -0,0 +1,30 @@
|
||||
@using System;
|
||||
@using Sandbox;
|
||||
|
||||
<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 );
|
||||
}
|
||||
}
|
32
code/weapon/Pistol.cs
Normal file
32
code/weapon/Pistol.cs
Normal file
@ -0,0 +1,32 @@
|
||||
using Sandbox;
|
||||
|
||||
namespace MyGame;
|
||||
|
||||
public partial class Pistol : Weapon
|
||||
{
|
||||
public override string ModelPath => "weapons/rust_pistol/rust_pistol.vmdl";
|
||||
public override string ViewModelPath => "weapons/rust_pistol/v_rust_pistol.vmdl";
|
||||
|
||||
[ClientRpc]
|
||||
protected virtual void ShootEffects()
|
||||
{
|
||||
Game.AssertClient();
|
||||
|
||||
Particles.Create( "particles/pistol_muzzleflash.vpcf", EffectEntity, "muzzle" );
|
||||
|
||||
Pawn.SetAnimParameter( "b_attack", true );
|
||||
ViewModelEntity?.SetAnimParameter( "fire", true );
|
||||
}
|
||||
|
||||
public override void PrimaryAttack()
|
||||
{
|
||||
ShootEffects();
|
||||
Pawn.PlaySound( "rust_pistol.shoot" );
|
||||
ShootBullet( 0.1f, 100, 20, 1 );
|
||||
}
|
||||
|
||||
protected override void Animate()
|
||||
{
|
||||
Pawn.SetAnimParameter( "holdtype", (int)CitizenAnimationHelper.HoldTypes.Pistol );
|
||||
}
|
||||
}
|
205
code/weapon/Weapon.cs
Normal file
205
code/weapon/Weapon.cs
Normal file
@ -0,0 +1,205 @@
|
||||
using Sandbox;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MyGame;
|
||||
|
||||
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;
|
||||
|
||||
/// <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; }
|
||||
|
||||
public override void Spawn()
|
||||
{
|
||||
EnableHideInFirstPerson = true;
|
||||
EnableShadowInFirstPerson = true;
|
||||
EnableDrawing = false;
|
||||
|
||||
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;
|
||||
PrimaryAttack();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <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" ) ) return false;
|
||||
|
||||
var rate = PrimaryRate;
|
||||
if ( rate <= 0 ) return true;
|
||||
|
||||
return TimeSincePrimaryAttack > (1 / rate);
|
||||
}
|
||||
|
||||
/// <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/weapon/WeaponViewModel.cs
Normal file
22
code/weapon/WeaponViewModel.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using Sandbox;
|
||||
|
||||
namespace MyGame;
|
||||
|
||||
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 );
|
||||
}
|
||||
}
|
6
untitled.prefab
Normal file
6
untitled.prefab
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"SchemaVersion": 1,
|
||||
"Name": null,
|
||||
"Description": null,
|
||||
"Root": null
|
||||
}
|
Loading…
Reference in New Issue
Block a user