b0c83af092
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>
191 lines
6.2 KiB
GDScript
191 lines
6.2 KiB
GDScript
extends Node
|
|
class_name _NetworkIdentityServer
|
|
|
|
## Tracks identities for use over the network
|
|
##
|
|
## Internally, netfox sends packets that refer to specific nodes, e.g. to
|
|
## synchronize their state. Nodes are referenced based on their path in the
|
|
## scene tree. To save on bandwidth, node paths are replaced with numeric IDs
|
|
## when possible.
|
|
## [br][br]
|
|
## This class manages these numerical IDs and their broadcast among peers.
|
|
## [br][br]
|
|
## Each node that is to be referred to over the network must be registered using
|
|
## [method register_node]. It is best to deregister freed nodes using [method
|
|
## deregister_node].
|
|
## [br][br]
|
|
## Node registration and deregistration are done automatically by the high-level
|
|
## nodes ( like RollbackSynchronizer ), and usually doesn't need to be done
|
|
## manually.
|
|
|
|
var _command_server: _NetworkCommandServer
|
|
|
|
var _next_id := 0
|
|
var _identifiers := {} # object to NetworkIdentifier
|
|
var _push_queue := {} # peer to (full name to id)
|
|
|
|
var _identifier_by_name := {} # full name to NetworkIdentifier
|
|
var _identifier_by_id := {} # peer to (id to NetworkIdentifier)
|
|
|
|
var _cmd_ids: _NetworkCommandServer.Command
|
|
|
|
static var _logger := NetfoxLogger._for_netfox("NetworkIdentityServer")
|
|
|
|
func _init(p_command_server: _NetworkCommandServer = null):
|
|
_command_server = p_command_server
|
|
|
|
func _ready():
|
|
if not _command_server:
|
|
_command_server = NetworkCommandServer
|
|
|
|
_cmd_ids = _command_server.register_command(_handle_ids, MultiplayerPeer.TRANSFER_MODE_RELIABLE)
|
|
|
|
# TODO(#561): Handle peer disconnect by clearing up data
|
|
|
|
## Register a node
|
|
## [br][br]
|
|
## Once a node is registered, it can be referred to over the network. The same
|
|
## node must be registered with the same node path on all peers.
|
|
func register_node(node: Node) -> void:
|
|
if not node.is_inside_tree():
|
|
_logger.error("Can't register node %s that is not inside tree!", [node])
|
|
return
|
|
_register(node, node.get_path())
|
|
|
|
## Deregister a node, freeing all identity data associated with it
|
|
func deregister_node(node: Node) -> void:
|
|
_deregister(node)
|
|
|
|
## Clear all identities
|
|
func clear() -> void:
|
|
_identifiers.clear()
|
|
_identifier_by_name.clear()
|
|
_identifier_by_id.clear()
|
|
_next_id = 0
|
|
|
|
## Queue sending the numeric ID of [param node] to [param peer]
|
|
## [br][br]
|
|
## This happens automatically when the [param node] is first referred to in a
|
|
## packet. This method allows frontloading that, at a more feasible moment.
|
|
## [br][br]
|
|
## This method does not send any data. To force sending identifiers, use [method
|
|
## flush_queue].
|
|
func queue_identifier_for(node: Node, peer: int) -> bool:
|
|
var identifier := _get_identifier_of(node)
|
|
if not identifier:
|
|
return false
|
|
_queue_for(identifier, peer)
|
|
return true
|
|
|
|
## Broadcast all identities queued for synchronization
|
|
## [br][br]
|
|
## Flushed automatically by default
|
|
func flush_queue() -> void:
|
|
for peer in _push_queue:
|
|
_cmd_ids.send(_serialize_ids(_push_queue[peer]), peer)
|
|
_push_queue.clear()
|
|
|
|
func _get_identifier_of(what: Object) -> _NetworkIdentifier:
|
|
return _identifiers.get(what)
|
|
|
|
func _resolve_reference(peer: int, identity_reference: _NetworkIdentityReference, allow_queue: bool = true) -> _NetworkIdentifier:
|
|
if identity_reference.has_id():
|
|
return _get_identifier_by_id(peer, identity_reference.get_id())
|
|
else:
|
|
var identifier := _get_identifier_by_name(identity_reference.get_full_name())
|
|
if allow_queue and identifier:
|
|
_queue_for(identifier, peer)
|
|
return identifier
|
|
|
|
func _queue_for(identifier: _NetworkIdentifier, peer: int) -> void:
|
|
if not _push_queue.has(peer):
|
|
_push_queue[peer] = { identifier.get_full_name(): identifier.get_id_for(peer) }
|
|
else:
|
|
_push_queue[peer][identifier.get_full_name()] = identifier.get_id_for(peer)
|
|
|
|
func _register(what: Object, path: String) -> void:
|
|
if _identifiers.has(what):
|
|
return
|
|
|
|
var identifier := _NetworkIdentifier.new(what, path, _make_id(), multiplayer.get_unique_id())
|
|
_identifiers[what] = identifier
|
|
_identifier_by_name[identifier.get_full_name()] = identifier
|
|
|
|
identifier.on_id.connect(func(peer: int, id: int): _update_id_cache(identifier, peer, id))
|
|
_update_id_cache(identifier, multiplayer.get_unique_id(), identifier.get_local_id())
|
|
|
|
func _deregister(what: Object) -> void:
|
|
if not _identifiers.has(what):
|
|
return
|
|
|
|
var identifier := _identifiers[what] as _NetworkIdentifier
|
|
_identifiers.erase(what)
|
|
_identifier_by_name.erase(identifier.get_full_name())
|
|
_erase_from_id_cache(identifier)
|
|
|
|
|
|
func _get_identifier_by_name(full_name: String) -> _NetworkIdentifier:
|
|
return _identifier_by_name.get(full_name)
|
|
|
|
func _get_identifier_by_id(peer: int, id: int) -> _NetworkIdentifier:
|
|
return _identifier_by_id.get(peer, {}).get(id, null)
|
|
|
|
func _make_id() -> int:
|
|
_next_id += 1
|
|
return _next_id
|
|
|
|
func _update_id_cache(identifier: _NetworkIdentifier, peer: int, id: int) -> void:
|
|
if not _identifier_by_id.has(peer):
|
|
_identifier_by_id[peer] = { id: identifier }
|
|
else:
|
|
_identifier_by_id[peer][id] = identifier
|
|
|
|
func _erase_from_id_cache(identifier: _NetworkIdentifier) -> void:
|
|
for peer in identifier.get_known_peers():
|
|
if not _identifier_by_id.has(peer):
|
|
# nani?
|
|
continue
|
|
|
|
var cache := _identifier_by_id.get(peer) as Dictionary
|
|
cache.erase(identifier.get_id_for(peer))
|
|
if cache.is_empty():
|
|
_identifier_by_id.erase(peer)
|
|
|
|
func _handle_ids(sender: int, data: PackedByteArray) -> void:
|
|
var ids := _deserialize_ids(data)
|
|
|
|
for full_name in ids:
|
|
var id := ids[full_name] as int
|
|
var identifier := _get_identifier_by_name(full_name)
|
|
if not identifier:
|
|
# Deleted since then
|
|
_logger.debug("Received identifier for unknown object with full name %s, id #%d", [full_name, id])
|
|
continue
|
|
identifier.set_id_for(sender, id)
|
|
|
|
func _serialize_ids(ids: Dictionary) -> PackedByteArray:
|
|
var buffer := StreamPeerBuffer.new()
|
|
var varuint := NetworkSchemas.varuint()
|
|
|
|
for full_name in ids.keys():
|
|
var id := ids[full_name] as int
|
|
|
|
buffer.put_utf8_string(full_name)
|
|
varuint.encode(ids[full_name], buffer)
|
|
|
|
return buffer.data_array
|
|
|
|
func _deserialize_ids(data: PackedByteArray) -> Dictionary:
|
|
var ids := {}
|
|
var varuint := NetworkSchemas.varuint()
|
|
var buffer := StreamPeerBuffer.new()
|
|
buffer.data_array = data
|
|
|
|
while buffer.get_available_bytes() > 0:
|
|
var full_name := buffer.get_utf8_string()
|
|
var id := varuint.decode(buffer) as int
|
|
|
|
ids[full_name] = id
|
|
|
|
return ids
|