extends Node class_name _NetworkCommandServer ## Transmits commands over the network ## ## Commands are a simpler, lightweight alternative to RPCs. Commands consist of ## a single byte for ID, and the raw binary data. The ID lets the receiving peer ## decide what to execute, with the binary data serving as the input. ## [br][br] ## Being a simpler construct makes commands a good fit for regular, fundamental ## operations. ## [br][br] ## Commands are, by default, transmitted over regular RPCs. To use less data, ## commands can also be transmitted as raw packets, using ## [method SceneMultiplayer.send_bytes]. This is an opt-in feature - if the game ## is already using [method SceneMultiplayer.send_bytes], it needs to be aware ## of commands, and must check each packet whether it's a command or one of its ## own packets. To check if a packet is a command, use [method ## is_command_packet]. var _packet_prefix := PackedByteArray([0, 78, 70]) # "\0nf" var _next_idx := 0 var _rpc_transport := _RPCTransport.new() var _packet_transport := _PacketTransport.new(_packet_prefix) var _commands := {} # id to `Command` var _use_raw := ProjectSettings.get_setting("netfox/general/use_raw_commands", false) as bool static var _logger := NetfoxLogger._for_netfox("NetworkCommandServer") func _ready(): add_child(_rpc_transport, true) add_child(_packet_transport, true) _rpc_transport.on_receive.connect(_handle_command) _packet_transport.on_receive.connect(_handle_command) ## Register a command at the next available ID func register_command(handler: Callable, mode: MultiplayerPeer.TransferMode = MultiplayerPeer.TRANSFER_MODE_RELIABLE, channel: int = 0) -> Command: var idx := _next_idx _next_idx += 1 return register_command_at(idx, handler) ## Register a command at a specific index ## [br][br] ## A specific ID should only be registered once. Doing otherwise will trigger an ## assert in the editor, but will overwrite the previous command in release. func register_command_at(idx: int, handler: Callable, mode: MultiplayerPeer.TransferMode = MultiplayerPeer.TRANSFER_MODE_RELIABLE, channel: int = 0) -> Command: assert(not _commands.has(idx), "Command #%d is already taken!" % idx) var command := Command.new(self, idx, handler, mode, channel) _commands[idx] = command _next_idx = maxi(_next_idx, idx + 1) return command ## Send a command with index and data func send_command(idx: int, data: PackedByteArray, target_peer: int = 0, mode: MultiplayerPeer.TransferMode = MultiplayerPeer.TRANSFER_MODE_RELIABLE, channel: int = 0) -> void: if _use_raw: _packet_transport.send(idx, data, target_peer, mode, channel) else: _rpc_transport.send(idx, data, target_peer, mode, channel) ## Return true if [param packet] is a command packet ## [br][br] ## Always returns [code]true[/code] if RPCs are used for transmitting commands. func is_command_packet(packet: PackedByteArray) -> bool: if not _use_raw: return true return _packet_transport.is_command_packet(packet) ## Return the prefix bytes for command packets ## [br][br] ## Can be used to avoid conflicts between command packets and game packets. func get_command_packet_prefix() -> PackedByteArray: return _packet_prefix func _handle_command(sender: int, idx: int, data: PackedByteArray) -> void: var command := _commands.get(idx) as Command if not command: _logger.error("Received unknown command #%d!", [idx]) return command._handle(sender, data) ## Networked command ## ## Provides a convenient interface for sending and managing networked commands. ## [br][br] ## Should not be instantiated manually. class Command: var _command_server: _NetworkCommandServer var _idx: int var _handler: Callable var _mode: MultiplayerPeer.TransferMode var _channel: int func _init(p_command_server: _NetworkCommandServer, p_idx: int, p_handler: Callable, p_mode: MultiplayerPeer.TransferMode, p_channel: int): _command_server = p_command_server _idx = p_idx _handler = p_handler _mode = p_mode _channel = p_channel ## Send command func send(data: PackedByteArray, target_peer: int = 0) -> void: _command_server.send_command(_idx, data, target_peer, _mode, _channel) func _handle(sender: int, data: PackedByteArray) -> void: _handler.call(sender, data) class _Transport extends Node: signal on_receive(idx: int, data: PackedByteArray) func send(idx: int, data: PackedByteArray, target_peer: int, mode: MultiplayerPeer.TransferMode, channel: int) -> void: pass class _PacketTransport extends _Transport: var _packet_prefix: PackedByteArray func _init(p_packet_prefix: PackedByteArray): _packet_prefix = p_packet_prefix func _ready(): (multiplayer as SceneMultiplayer).peer_packet.connect(_handle_packet) func send(idx: int, data: PackedByteArray, target_peer: int, mode: MultiplayerPeer.TransferMode, channel: int) -> void: var buffer := StreamPeerBuffer.new() buffer.put_data(_packet_prefix) buffer.put_u8(idx) buffer.put_data(data) (multiplayer as SceneMultiplayer).send_bytes(buffer.data_array, target_peer, mode, channel) func is_command_packet(packet: PackedByteArray) -> bool: if packet.size() < _packet_prefix.size(): return false for i in _packet_prefix.size(): if packet[i] != _packet_prefix[i]: return false return true func _handle_packet(peer: int, packet: PackedByteArray) -> void: var buffer := StreamPeerBuffer.new() buffer.data_array = packet # Check header if not is_command_packet(packet): return # Grab data buffer.seek(_packet_prefix.size()) var idx := buffer.get_u8() var data := buffer.get_partial_data(buffer.get_available_bytes())[1] as PackedByteArray on_receive.emit(peer, idx, data) class _RPCTransport extends _Transport: func send(idx: int, data: PackedByteArray, target_peer: int, mode: MultiplayerPeer.TransferMode, _channel: int) -> void: match mode: MultiplayerPeer.TRANSFER_MODE_UNRELIABLE: _submit_unreliable.rpc_id(target_peer, idx, data) MultiplayerPeer.TRANSFER_MODE_UNRELIABLE_ORDERED: _submit_unreliable_ordered.rpc_id(target_peer, idx, data) MultiplayerPeer.TRANSFER_MODE_RELIABLE: _submit_reliable.rpc_id(target_peer, idx, data) @rpc("any_peer", "call_remote", "unreliable") func _submit_unreliable(idx: int, data: PackedByteArray): var sender := multiplayer.get_remote_sender_id() on_receive.emit(sender, idx, data) @rpc("any_peer", "call_remote", "unreliable_ordered") func _submit_unreliable_ordered(idx: int, data: PackedByteArray): var sender := multiplayer.get_remote_sender_id() on_receive.emit(sender, idx, data) @rpc("any_peer", "call_remote", "reliable") func _submit_reliable(idx: int, data: PackedByteArray): var sender := multiplayer.get_remote_sender_id() on_receive.emit(sender, idx, data)