Phase 7: netfox + godot-jolt stack upgrade

Stack installed:
- netfox v1.35.3 (core + extras + noray + internals)
- godot-jolt v0.16.0-stable

Architecture:
- Server: ENet transport (works headless, no netfox deps)
- Client/Editor: netfox rollback (RollbackSynchronizer, TickInterpolator)

New/modified:
- docs/migration-netfox-plan.md — migration architecture
- scripts/network/network_manager.gd — netfox-aware ENet fallback
- scripts/network/player.gd — clean base player
- client/characters/player_netfox.gd — rollback player w/ WeaponManager
- client/characters/input/player_net_input.gd — BaseNetInput subclass
- client/characters/character/fps_character_controller.gd — netfox input feed
- client/weapons/ — weapon data, registry, TacticalWeaponHitscan, WeaponManager
- client/scripts/round_replicator.gd — client-side round state bridge
- server/scripts/round_manager.gd — improved state machine
- server/scripts/plugin_api/plugin_manager.gd — refined plugin system
- config: enemy_tag, ally_tag for meatball targeting

Removed: old C++ SimulationServer GDExtension (replaced by netfox rollback)
This commit is contained in:
2026-07-02 17:38:50 -04:00
parent e2dc429caa
commit e7299b17e9
3237 changed files with 523530 additions and 18 deletions
+51
View File
@@ -0,0 +1,51 @@
## WeaponData — resource containing all stats for a weapon.
##
## Used by the weapon system to configure damage, fire rate, recoil,
## economy pricing, and team restrictions.
##
## This is a pure Resource (no Node dependency) so it loads clean
## in both client and server contexts.
extends Resource
class_name WeaponData
enum FireMode { SEMI, AUTO, MELEE }
# Identification
@export var weapon_id: int = 0
@export var weapon_name: String = ""
@export var slot: int = 0 # 0=melee, 1=pistol, 2=SMG, 3=rifle, 4=sniper
# Damage
@export var damage: int = 30
@export var headshot_multiplier: float = 2.0
@export var chest_multiplier: float = 1.0
@export var waist_multiplier: float = 0.75
@export var leg_multiplier: float = 0.5
@export var armor_penetration: float = 0.5 # 0.0-1.0, % damage through armor
# Firing
@export var fire_mode: FireMode = FireMode.SEMI
@export var fire_rate: float = 10.0 # rounds per second
@export var reload_time: float = 2.0 # seconds
@export var max_distance: float = 1000.0
# Magazine
@export var magazine_size: int = 30
@export var reserve_size: int = 90
# Recoil & Spread
@export var recoil_per_shot: float = 0.5 # degrees
@export var recoil_recovery: float = 8.0 # degrees/sec
@export var spread_idle: float = 0.0 # degrees while standing still
@export var spread_walking: float = 2.0 # degrees while moving
@export var spread_sprinting: float = 6.0 # degrees while sprinting
# Movement
@export var move_speed_multiplier: float = 1.0 # multiplier on base move speed
@export var aim_speed_multiplier: float = 0.5 # move speed while aiming
# Economy
@export var price: int = 0
@export var kill_reward: int = 300
@export var team: int = -1 # -1=both, 0=T, 1=CT
+175
View File
@@ -0,0 +1,175 @@
## WeaponRegistry — central registry of all weapon definitions.
##
## All weapon data is defined here as static constants. Systems that need
## weapon stats (WeaponManager, BuyMenu, RoundManager) reference this.
##
## To add a new weapon: create a WeaponData resource in client/weapons/data/
## and register it in the ALL array below.
extends Node
class_name WeaponRegistry
# Define all weapons as convenient static getters
static var KNIFE: WeaponData:
get: return _knife
static var GLOCK: WeaponData:
get: return _glock
static var USP: WeaponData:
get: return _usp
static var AK47: WeaponData:
get: return _ak47
static var M4A1: WeaponData:
get: return _m4a1
static var AWP: WeaponData:
get: return _awp
# Weapon ID constants
const ID_KNIFE: int = 0
const ID_GLOCK: int = 1
const ID_USP: int = 2
const ID_AK47: int = 3
const ID_M4A1: int = 4
const ID_AWP: int = 5
## Array of all weapons for iteration
static var ALL: Array[WeaponData] = []
## Map of weapon_id → WeaponData for fast lookup
static var BY_ID: Dictionary = {}
## Static initializer
static func _static_init() -> void:
ALL = [_knife, _glock, _usp, _ak47, _m4a1, _awp]
for w in ALL:
BY_ID[w.weapon_id] = w
# ---------------------------------------------------------------------------
# Weapon definitions
# ---------------------------------------------------------------------------
static var _knife: WeaponData = _make_weapon_data({
weapon_id = ID_KNIFE,
weapon_name = "Knife",
slot = 0,
damage = 34,
fire_mode = WeaponData.FireMode.MELEE,
fire_rate = 0.0,
magazine_size = 0,
reserve_size = 0,
move_speed_multiplier = 1.0,
price = 0,
kill_reward = 1500,
armor_penetration = 0.0,
max_distance = 3.0,
})
static var _glock: WeaponData = _make_weapon_data({
weapon_id = ID_GLOCK,
weapon_name = "Glock-18",
slot = 1,
damage = 25,
fire_rate = 6.0,
magazine_size = 20,
reserve_size = 120,
recoil_per_shot = 0.4,
move_speed_multiplier = 0.86,
price = 200,
kill_reward = 300,
armor_penetration = 0.35,
team = 0, # Terrorist default
})
static var _usp: WeaponData = _make_weapon_data({
weapon_id = ID_USP,
weapon_name = "USP-S",
slot = 1,
damage = 28,
fire_rate = 5.0,
magazine_size = 12,
reserve_size = 24,
recoil_per_shot = 0.3,
move_speed_multiplier = 0.86,
price = 200,
kill_reward = 300,
armor_penetration = 0.50,
team = 1, # CT default
})
static var _ak47: WeaponData = _make_weapon_data({
weapon_id = ID_AK47,
weapon_name = "AK-47",
slot = 3,
damage = 36,
fire_mode = WeaponData.FireMode.AUTO,
fire_rate = 10.0,
magazine_size = 30,
reserve_size = 90,
recoil_per_shot = 0.8,
recoil_recovery = 6.0,
spread_walking = 3.0,
spread_sprinting = 8.0,
move_speed_multiplier = 0.78,
price = 2700,
kill_reward = 300,
armor_penetration = 0.77,
team = 0,
})
static var _m4a1: WeaponData = _make_weapon_data({
weapon_id = ID_M4A1,
weapon_name = "M4A1-S",
slot = 3,
damage = 33,
fire_mode = WeaponData.FireMode.AUTO,
fire_rate = 10.0,
magazine_size = 25,
reserve_size = 75,
recoil_per_shot = 0.7,
recoil_recovery = 7.0,
spread_walking = 2.5,
spread_sprinting = 7.0,
move_speed_multiplier = 0.78,
price = 2900,
kill_reward = 300,
armor_penetration = 0.77,
team = 1,
})
static var _awp: WeaponData = _make_weapon_data({
weapon_id = ID_AWP,
weapon_name = "AWP",
slot = 4,
damage = 115,
fire_rate = 1.5,
magazine_size = 10,
reserve_size = 30,
recoil_per_shot = 4.0,
recoil_recovery = 1.0,
spread_idle = 0.0,
spread_walking = 10.0,
spread_sprinting = 20.0,
move_speed_multiplier = 0.57,
price = 4750,
kill_reward = 100,
armor_penetration = 0.87,
max_distance = 2000.0,
headshot_multiplier = 5.0, # AWP one-shots through armor
})
# ---------------------------------------------------------------------------
# Factory
# ---------------------------------------------------------------------------
static func _make_weapon_data(dict: Dictionary) -> WeaponData:
var w := WeaponData.new()
for key in dict:
w.set(key, dict[key])
return w
## Look up a weapon by its ID.
static func get_by_id(id: int) -> WeaponData:
return BY_ID.get(id, _knife)
## Get the default weapon for a team (0=T, 1=CT).
static func get_default_weapon(team_id: int) -> WeaponData:
return _glock if team_id == 0 else _usp