Fresh start: replace with naxIO/netfox-cs-sample foundation
Complete replacement of the tactical-shooter project with the netfox-cs-sample (MIT) — a CS 1.6 inspired multiplayer FPS built with Godot 4 and netfox. ## What's new - Full CS-style gameplay: teams (T/CT), rounds, economy, buy menu - 6 weapons: Knife, Glock, USP, AK-47, M4A1, AWP - Bomb plant/defuse with 2 bombsites - Flashbang & smoke grenades - Proper netfox rollback netcode at 64 tick - Network popup UI for host/join - HUD, crosshair, round timer, scoreboard - All netfox singletons registered as autoloads (works in exported builds) ## Architecture - Listen-server (host from client, no dedicated server binary) - Multiplayer-fps game lives at examples/multiplayer-fps/ - Netfox addons registered as autoloads for exported build compat - Godot 4.7 with Forward+ renderer ## Removed - Old headless-server architecture (client_main, server_main, player.gd, etc.) - Custom netfox bootstrap with ENet fallback - Old ChaffGames FPS template (2,420 lines, 844 KB) - SimulationServer GDExtension stub - Godot-jolt physics (netfox sample uses default Godot physics) - Duplicate weapon_data.gd, anti_cheat.gd, round_manager.gd, etc. - Server browser API Python venv (87 MB) - test_range map and modular assets ## Preserved - Git history - Server config at config/default_server_config.cfg - Windows export preset - Build directory (gitignored) Co-authored-by: naxIO <naxIO@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
extends CharacterBody3D
|
||||
class_name BrawlerController
|
||||
|
||||
# Stats
|
||||
@export var speed: float = 5.0
|
||||
@export var jump_velocity: float = 4.5
|
||||
@export var mass: float = 4.0
|
||||
|
||||
# Spawn
|
||||
@export var spawn_point: Vector3 = Vector3(0, 4, 0)
|
||||
@export var death_depth: float = 4.0
|
||||
@export var respawn_time: float = 4.0
|
||||
|
||||
# Dependencies
|
||||
@onready var input := $Input as BrawlerInput
|
||||
@onready var rollback_synchronizer := $RollbackSynchronizer as RollbackSynchronizer
|
||||
@onready var animation_tree := $AnimationTree as AnimationTree
|
||||
@onready var weapon := $Weapon as BrawlerWeapon
|
||||
@onready var mesh := $"bomber-guy/rig/Skeleton3D/Cube_008" as MeshInstance3D
|
||||
@onready var nametag := $Nametag as Label3D
|
||||
@onready var fall_sound := $"Fall Sound" as PlayRandomStream3D
|
||||
|
||||
var player_name: String = "":
|
||||
set(p_name):
|
||||
if p_name.length() > 24:
|
||||
p_name = p_name.substr(0, 21) + "..."
|
||||
player_name = p_name
|
||||
nametag.text = p_name
|
||||
|
||||
var player_id: int = -1
|
||||
var last_hit_player: BrawlerController
|
||||
var last_hit_tick: int = -1
|
||||
var gravity = ProjectSettings.get_setting(&"physics/3d/default_gravity")
|
||||
var respawn_tick: int = -1
|
||||
var respawn_count: int = 0
|
||||
|
||||
func register_hit(from: BrawlerController):
|
||||
if from == self:
|
||||
push_error("Player %s (#%s) trying to register hit on themselves!" % [player_name, player_id])
|
||||
return
|
||||
|
||||
last_hit_player = from
|
||||
last_hit_tick = NetworkRollback.tick if NetworkRollback.is_rollback() else NetworkTime.tick
|
||||
|
||||
func shove(motion: Vector3):
|
||||
move_and_collide(motion / mass)
|
||||
|
||||
func _ready():
|
||||
if not input:
|
||||
input = $Input
|
||||
|
||||
_snap_to_spawn()
|
||||
|
||||
GameEvents.on_brawler_spawn.emit(self)
|
||||
NetworkTime.on_tick.connect(_tick)
|
||||
|
||||
if not player_name:
|
||||
player_name = "Nameless Brawler #%s" % [player_id]
|
||||
|
||||
# Set player color
|
||||
var color = Color.from_hsv((hash(player_id) % 256) / 256.0, 1.0, 1.0)
|
||||
var material: StandardMaterial3D = mesh.get_active_material(0)
|
||||
material = material.duplicate()
|
||||
material.albedo_color = color
|
||||
mesh.set_surface_override_material(0, material)
|
||||
|
||||
rollback_synchronizer.set_schema({
|
||||
":transform": NetworkSchemas.transform3f32(),
|
||||
":velocity": NetworkSchemas.vec3f32(),
|
||||
":speed": NetworkSchemas.float32(),
|
||||
":mass": NetworkSchemas.float32(),
|
||||
|
||||
"Input:movement": NetworkSchemas.vec3f32(),
|
||||
"Input:aim": NetworkSchemas.vec3f32()
|
||||
})
|
||||
|
||||
func _process(delta):
|
||||
# Update animation
|
||||
# Running
|
||||
var movement = Vector3(velocity.x, 0, velocity.z) * speed
|
||||
var relative_velocity = quaternion.inverse() * movement
|
||||
relative_velocity.y = 0
|
||||
relative_velocity /= speed
|
||||
relative_velocity = Vector2(relative_velocity.x, relative_velocity.z)
|
||||
var animated_velocity = animation_tree.get("parameters/Move/blend_position") as Vector2
|
||||
|
||||
animation_tree.set("parameters/Move/blend_position", animated_velocity.move_toward(relative_velocity, delta / 0.2))
|
||||
|
||||
# Float
|
||||
_force_update_is_on_floor()
|
||||
var animated_float = animation_tree.get("parameters/Float/blend_amount") as float
|
||||
var actual_float = 1.0 if not is_on_floor() else 0.0
|
||||
animation_tree.set("parameters/Float/blend_amount", move_toward(animated_float, actual_float, delta / 0.2))
|
||||
|
||||
# Speed
|
||||
animation_tree.set("parameters/MoveScale/scale", speed / 3.75)
|
||||
animation_tree.set("parameters/ThrowScale/scale", min(weapon.fire_cooldown / (10. / 24.), 1.0))
|
||||
|
||||
func _tick(_delta, tick):
|
||||
# Run throw animation if firing
|
||||
if weapon.last_fire == tick:
|
||||
animation_tree.set("parameters/Throw/request", AnimationNodeOneShot.ONE_SHOT_REQUEST_FIRE)
|
||||
|
||||
func _rollback_tick(delta, tick, is_fresh):
|
||||
# Respawn
|
||||
if tick == respawn_tick:
|
||||
_snap_to_spawn()
|
||||
velocity = Vector3.ZERO
|
||||
last_hit_tick = -1
|
||||
|
||||
if is_fresh:
|
||||
GameEvents.on_brawler_respawn.emit(self)
|
||||
|
||||
# Skip predictions
|
||||
if rollback_synchronizer.is_predicting():
|
||||
rollback_synchronizer.ignore_prediction(self)
|
||||
return
|
||||
|
||||
# Apply gravity
|
||||
_force_update_is_on_floor()
|
||||
if not is_on_floor():
|
||||
velocity.y -= gravity * delta
|
||||
|
||||
# Stick to moving platforms
|
||||
var platform_velocity := Vector3.ZERO
|
||||
var collision_result := KinematicCollision3D.new()
|
||||
if test_move(global_transform, Vector3.DOWN * delta, collision_result):
|
||||
var collider := collision_result.get_collider()
|
||||
if collider is MovingPlatform:
|
||||
var platform := collider as MovingPlatform
|
||||
platform_velocity = platform.get_velocity()
|
||||
|
||||
# Jump
|
||||
if input.movement.y > 0 and is_on_floor():
|
||||
velocity.y = jump_velocity * input.movement.y
|
||||
|
||||
# Movement
|
||||
var direction = Vector3(input.movement.x, 0, input.movement.z).normalized()
|
||||
if direction:
|
||||
velocity.x = direction.x * speed
|
||||
velocity.z = direction.z * speed
|
||||
else:
|
||||
velocity.x = move_toward(velocity.x, 0, speed)
|
||||
velocity.z = move_toward(velocity.z, 0, speed)
|
||||
|
||||
# Aim
|
||||
if input.aim:
|
||||
transform = transform.looking_at(position + Vector3(input.aim.x, 0, input.aim.z), Vector3.UP, true).scaled_local(scale)
|
||||
|
||||
# Apply movement
|
||||
velocity += platform_velocity
|
||||
velocity *= NetworkTime.physics_factor
|
||||
move_and_slide()
|
||||
velocity /= NetworkTime.physics_factor
|
||||
velocity -= platform_velocity
|
||||
|
||||
# Death
|
||||
if position.y < -death_depth and tick > respawn_tick and is_fresh:
|
||||
var respawn_cooldown = respawn_time * NetworkTime.tickrate
|
||||
respawn_tick = tick + respawn_cooldown
|
||||
respawn_count += 1
|
||||
|
||||
fall_sound.play_random()
|
||||
|
||||
GameEvents.on_brawler_fall.emit(self)
|
||||
|
||||
func _exit_tree():
|
||||
GameEvents.on_brawler_despawn.emit(self)
|
||||
|
||||
func _snap_to_spawn():
|
||||
var spawns = get_tree().get_nodes_in_group("Spawn Points")
|
||||
var idx = hash(player_id + respawn_count * 39) % spawns.size()
|
||||
var spawn = spawns[idx] as Node3D
|
||||
|
||||
global_transform = spawn.global_transform
|
||||
|
||||
func _force_update_is_on_floor():
|
||||
var old_velocity = velocity
|
||||
velocity *= 0
|
||||
move_and_slide()
|
||||
velocity = old_velocity
|
||||
Reference in New Issue
Block a user