first commit

This commit is contained in:
Rbanh 2023-08-29 02:07:30 -04:00
commit 4599fe0f95
31 changed files with 1629 additions and 0 deletions

100
.editorconfig Normal file
View 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
View 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
View 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

Binary file not shown.

View 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
View 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

Binary file not shown.

Binary file not shown.

BIN
code/.vs/project_1/v17/.suo Normal file

Binary file not shown.

59
code/Game.cs Normal file
View 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
View 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
View 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
View 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
View 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
View 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;
}
}
}
}

View 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 );
}
}

View 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" );
}
}

View 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();
}
}

View 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;
}
}

View 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() );
}
}

View 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 );
}
}

View 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;
}
}

View 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 );
}
}

View 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 );
}
}

View 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
View 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
View 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();
}
}
}

View 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
View File

@ -0,0 +1,6 @@
{
"SchemaVersion": 1,
"Name": null,
"Description": null,
"Root": null
}