From 2e387686819f72c3035a689aef2dc9b9fe9f341f Mon Sep 17 00:00:00 2001 From: Solomos Date: Fri, 23 Aug 2024 15:10:14 +0300 Subject: [PATCH 1/7] Almost finished but stumbled onto a bug of issue 78219 which is solved in future godot version and makes the code cleaner (saves 2 typecasts) --- addons/netfox/properties/property-cache.gd | 2 +- addons/netfox/properties/property-entry.gd | 2 + addons/netfox/properties/property-snapshot.gd | 15 +- .../netfox/rollback/rollback-synchronizer.gd | 111 ++++++++-- .../netfox/serialization/input-serializer.gd | 1 + addons/netfox/serialization/value-to-bytes.gd | 202 ++++++++++++++++++ 6 files changed, 309 insertions(+), 24 deletions(-) create mode 100644 addons/netfox/serialization/input-serializer.gd create mode 100644 addons/netfox/serialization/value-to-bytes.gd diff --git a/addons/netfox/properties/property-cache.gd b/addons/netfox/properties/property-cache.gd index 82e542f..316edce 100644 --- a/addons/netfox/properties/property-cache.gd +++ b/addons/netfox/properties/property-cache.gd @@ -11,7 +11,7 @@ func _init(p_root: Node): func get_entry(path: String) -> PropertyEntry: if not _cache.has(path): - var parsed = PropertyEntry.parse(root, path) + var parsed: PropertyEntry = PropertyEntry.parse(root, path) if not parsed.is_valid(): _logger.warning("Invalid property path: %s" % path) _cache[path] = parsed diff --git a/addons/netfox/properties/property-entry.gd b/addons/netfox/properties/property-entry.gd index 01fd713..05cbe48 100644 --- a/addons/netfox/properties/property-entry.gd +++ b/addons/netfox/properties/property-entry.gd @@ -4,6 +4,7 @@ class_name PropertyEntry var _path: String var node: Node var property: String +var type: Variant.Type ## See typeof() func get_value() -> Variant: return node.get_indexed(property) @@ -28,4 +29,5 @@ static func parse(root: Node, path: String) -> PropertyEntry: result.node = root.get_node(NodePath(path)) result.property = path.erase(0, path.find(":") + 1) result._path = path + result.type = typeof(result.get_value()) return result diff --git a/addons/netfox/properties/property-snapshot.gd b/addons/netfox/properties/property-snapshot.gd index f811423..261803b 100644 --- a/addons/netfox/properties/property-snapshot.gd +++ b/addons/netfox/properties/property-snapshot.gd @@ -2,12 +2,25 @@ extends Object class_name PropertySnapshot static func extract(properties: Array[PropertyEntry]) -> Dictionary: - var result = {} + var result: Dictionary = {} for property in properties: result[property.to_string()] = property.get_value() result.make_read_only() return result +static func extract_serialized(properties: Array[PropertyEntry], tick_timestamp: int) -> PackedByteArray: + var result: PackedByteArray + result.resize(4) + result.encode_u32(0, tick_timestamp) + + var picked_value: Variant + for picked_property in properties: + picked_value = picked_property.get_value() + result.append_array(ValueToBytes.serialize(picked_value)) + print("Picked property %s of value %s and byte size: %s" % [picked_property._to_string(), picked_value, result.size()]) + + return result + static func apply(properties: Dictionary, cache: PropertyCache): for property in properties: var pe = cache.get_entry(property) diff --git a/addons/netfox/rollback/rollback-synchronizer.gd b/addons/netfox/rollback/rollback-synchronizer.gd index b0d3af0..f776fdc 100644 --- a/addons/netfox/rollback/rollback-synchronizer.gd +++ b/addons/netfox/rollback/rollback-synchronizer.gd @@ -14,6 +14,10 @@ class_name RollbackSynchronizer ## Turning this off is recommended to save bandwith and reduce cheating risks. @export var enable_input_broadcast: bool = true +## This will serialize input before sending it, instead of sending a dictionary of string properties and its values +## Turning this on is recommended to save bandwidth, at the slight cost of CPU. +@export var enable_input_serialization: bool = true + var _record_state_props: Array[PropertyEntry] = [] var _record_input_props: Array[PropertyEntry] = [] var _auth_state_props: Array[PropertyEntry] = [] @@ -22,6 +26,7 @@ var _nodes: Array[Node] = [] var _states: Dictionary = {} var _inputs: Dictionary = {} +var _serialized_inputs: Dictionary = {} # var _latest_state: int = -1 var _earliest_input: int @@ -167,8 +172,6 @@ func _record_tick(tick: int): _states[tick] = PropertySnapshot.extract(_record_state_props) func _after_loop(): - _earliest_input = NetworkTime.tick - # Apply display state var display_state = _get_history(_states, NetworkTime.tick - NetworkRollback.display_offset) PropertySnapshot.apply(display_state, _property_cache) @@ -178,21 +181,34 @@ func _before_tick(_delta, tick): var state = _get_history(_states, tick) PropertySnapshot.apply(state, _property_cache) -func _after_tick(_delta, _tick): +func _after_tick(_delta: float, _tick: int): if not _auth_input_props.is_empty(): + _earliest_input = _tick var input = PropertySnapshot.extract(_auth_input_props) - _inputs[NetworkTime.tick] = input - - #Send the last n inputs for each property - var inputs = {} - for i in range(0, NetworkRollback.input_redundancy): - var tick_input = _inputs.get(NetworkTime.tick - i, {}) - for property in tick_input: - if not inputs.has(property): - inputs[property] = [] - inputs[property].push_back(tick_input[property]) - - _attempt_submit_input(inputs) + _inputs[_tick] = input + + if (enable_input_serialization): + var serialized_current_input: PackedByteArray = PropertySnapshot.extract_serialized(_auth_input_props, _tick) + _serialized_inputs[_tick] = serialized_current_input + + var serialized_inputs_to_send: Array[PackedByteArray] = [] + var first_of_previous_inputs_index: int = max(_serialized_inputs.size() - NetworkRollback.input_redundancy, _earliest_input) + + for picked_tick_index in range(first_of_previous_inputs_index, _serialized_inputs.size()): + serialized_inputs_to_send.append(_serialized_inputs[picked_tick_index]) + + _attempt_submit_serialized_input(serialized_inputs_to_send) + else: + #Send the last n inputs for each property + var inputs = {} + for i in range(0, NetworkRollback.input_redundancy): + var tick_input = _inputs.get(NetworkTime.tick - i, {}) + for property in tick_input: + if not inputs.has(property): + inputs[property] = [] + inputs[property].push_back(tick_input[property]) + + _attempt_submit_raw_input(inputs) while _states.size() > NetworkRollback.history_limit: _states.erase(_states.keys().min()) @@ -200,14 +216,25 @@ func _after_tick(_delta, _tick): while _inputs.size() > NetworkRollback.history_limit: _inputs.erase(_inputs.keys().min()) + if (enable_input_serialization): + while _serialized_inputs.size() > NetworkRollback.history_limit: + _serialized_inputs.erase(_serialized_inputs.keys().min()) + _freshness_store.trim() -func _attempt_submit_input(input: Dictionary): +func _attempt_submit_raw_input(inputs: Dictionary): + # TODO: Default to input broadcast in mesh network setups + if enable_input_broadcast: + _submit_raw_input.rpc( inputs, NetworkTime.tick) + elif not multiplayer.is_server(): + _submit_raw_input.rpc_id(1, inputs, NetworkTime.tick) + +func _attempt_submit_serialized_input(serialized_inputs: Array[PackedByteArray]): # TODO: Default to input broadcast in mesh network setups if enable_input_broadcast: - rpc("_submit_input", input, NetworkTime.tick) + _submit_serialized_input.rpc(serialized_inputs) elif not multiplayer.is_server(): - rpc_id(1, "_submit_input", input, NetworkTime.tick) + _submit_serialized_input.rpc_id(1, serialized_inputs) func _get_history(buffer: Dictionary, tick: int) -> Dictionary: if buffer.has(tick): @@ -232,13 +259,50 @@ func _get_history(buffer: Dictionary, tick: int) -> Dictionary: return buffer[before] @rpc("any_peer", "unreliable", "call_remote") -func _submit_input(input: Dictionary, tick: int): - var sender = multiplayer.get_remote_sender_id() +func _submit_serialized_input(serialized_inputs: Array[PackedByteArray]): + var sender: int = multiplayer.get_remote_sender_id() + + #If clients send input exclusively to server, yet we are a client and received an input + #this is either a serious bug or a hacker sending RPCs. + if (enable_input_broadcast == false && not multiplayer.is_server() && sender != 1): + _logger.error("Received input from %s for %s from a client!" % [sender, root.name]) + + var picked_tick: int + for picked_serialized_input in serialized_inputs: + picked_tick = picked_serialized_input.decode_u32(0) + if (_inputs.has(picked_tick) == false): #New input tick! + _earliest_input = min(_earliest_input, picked_tick) + _inputs[picked_tick] = _inputs.get(picked_tick, {}) + + var reconstructed_property: Dictionary = {} + var picked_type: Variant.Type + var picked_type_byte_size: int + var picked_serialized_property: PackedByteArray + var picked_byte_index: int = 4 #not 0 because above^ we already decoded tick (u_32) + for picked_property_entry in _auth_input_props: + picked_type = picked_property_entry.type + picked_type_byte_size = ValueToBytes.get_byte_size(picked_type) + picked_serialized_property = picked_serialized_input.slice(picked_byte_index, picked_byte_index + picked_type_byte_size + 1) # +1 because end is exclusive + + var picked_value = ValueToBytes.deserialize(picked_serialized_property, picked_type) + picked_byte_index += picked_type_byte_size + + _inputs[picked_tick][picked_property_entry._path] = picked_value + +@rpc("any_peer", "unreliable", "call_remote") +func _submit_raw_input(input: Dictionary, tick: int): + var sender: int = multiplayer.get_remote_sender_id() + + #If clients send input exclusively to server, yet we are a client and received an input + #this is either a serious bug or a hacker sending RPCs. + if (enable_input_broadcast == false && not multiplayer.is_server() && sender != 1): + _logger.error("Received input from %s for tick %s for %s from a client!" % [sender, tick, root.name]) + var sanitized = {} for property in input: - var pe = _property_cache.get_entry(property) + var pe: PropertyEntry = _property_cache.get_entry(property) var value = input[property] - var input_owner = pe.node.get_multiplayer_authority() + var input_owner: int = pe.node.get_multiplayer_authority() if input_owner != sender: _logger.warning("Received input for node owned by %s from %s, sender has no authority!" \ @@ -246,6 +310,7 @@ func _submit_input(input: Dictionary, tick: int): continue sanitized[property] = value + print("Sanitized[property] %s is: %s" % [property, sanitized[property]]) if sanitized.size() > 0: for property in sanitized: @@ -253,12 +318,14 @@ func _submit_input(input: Dictionary, tick: int): var t = tick - i var old_input = _inputs.get(t, {}).get(property) var new_input = sanitized[property][i] + print("property is %s and new_input is %s" % [property, new_input]) if old_input == null: # We received an array of current and previous inputs, merge them into our history. _inputs[t] = _inputs.get(t, {}) _inputs[t][property] = new_input _earliest_input = min(_earliest_input, t) + else: _logger.warning("Received invalid input from %s for tick %s for %s" % [sender, tick, root.name]) diff --git a/addons/netfox/serialization/input-serializer.gd b/addons/netfox/serialization/input-serializer.gd new file mode 100644 index 0000000..05d1e01 --- /dev/null +++ b/addons/netfox/serialization/input-serializer.gd @@ -0,0 +1 @@ +extends Object diff --git a/addons/netfox/serialization/value-to-bytes.gd b/addons/netfox/serialization/value-to-bytes.gd new file mode 100644 index 0000000..d6b30f4 --- /dev/null +++ b/addons/netfox/serialization/value-to-bytes.gd @@ -0,0 +1,202 @@ +extends Node +class_name ValueToBytes ##Mayhaps needs to become Autoload so as to use the logger. + +#var_to_bytes is inefficient, see https://docs.godotengine.org/en/stable/tutorials/io/binary_serialization_api.html +#Contains 4 bytes in the header just for what type it is, should be 1 byte (flag can be last bit of 1st byte) +#With this class, the header is 0 bytes since we know the type beforehand ;) + +static func get_byte_size(type: Variant.Type) -> int: + match(type): + TYPE_BOOL: + return 1 + TYPE_INT, TYPE_FLOAT: + return 4 + TYPE_STRING, TYPE_STRING_NAME: #Dynamic... + return -1 + TYPE_VECTOR2, TYPE_VECTOR2I: + return 8 + TYPE_VECTOR3, TYPE_VECTOR3I: + return 12 + TYPE_VECTOR4, TYPE_VECTOR4I, TYPE_QUATERNION, TYPE_RECT2, TYPE_RECT2I: + return 16 + TYPE_AABB, TYPE_TRANSFORM2D: + return 24 + TYPE_BASIS: + return 36 + TYPE_TRANSFORM3D: + return 48 + TYPE_NIL: + push_error("Null inside get_byte_size!") + _: + push_error("Unknown type size") + + return -1 + +static func deserialize(serialized_value: PackedByteArray, type: Variant.Type) -> Variant: + match(type): + TYPE_BOOL: + return serialized_value.decode_u8(0) + TYPE_INT: + return serialized_value.decode_s32(0) + TYPE_FLOAT: + return serialized_value.decode_float(0) + TYPE_STRING: + serialized_value.get_string_from_ascii() + TYPE_STRING_NAME: + serialized_value.get_string_from_ascii() as StringName + TYPE_VECTOR2: + return Vector2(serialized_value.decode_float(0), serialized_value.decode_float(4)) + TYPE_VECTOR2I: + return Vector2i(serialized_value.decode_s32(0), serialized_value.decode_s32(4)) + TYPE_VECTOR3: + return Vector3(serialized_value.decode_float(0), serialized_value.decode_float(4), serialized_value.decode_float(8)) + TYPE_VECTOR3I: + return Vector3i(serialized_value.decode_s32(0), serialized_value.decode_s32(4), serialized_value.decode_s32(8)) + TYPE_QUATERNION: + return Quaternion(serialized_value.decode_float(0), serialized_value.decode_float(4), serialized_value.decode_float(8), serialized_value.decode_float(12)) + TYPE_VECTOR4: + return Vector4(serialized_value.decode_float(0), serialized_value.decode_float(4), serialized_value.decode_float(8), serialized_value.decode_float(12)) + TYPE_VECTOR4I: + return Vector4i(serialized_value.decode_s32(0), serialized_value.decode_s32(4), serialized_value.decode_s32(8), serialized_value.decode_s32(12)) + TYPE_RECT2: + return Rect2(Vector2(serialized_value.decode_float(0), serialized_value.decode_float(4)), Vector2(serialized_value.decode_float(8), serialized_value.decode_float(12))) + TYPE_RECT2I: + return Rect2i(Vector2i(serialized_value.decode_s32(0), serialized_value.decode_s32(4)), Vector2i(serialized_value.decode_s32(8), serialized_value.decode_s32(12))) + TYPE_AABB: + return AABB(Vector3(serialized_value.decode_float(0), serialized_value.decode_float(4), serialized_value.decode_float(8)), Vector3(serialized_value.decode_float(12), serialized_value.decode_float(16), serialized_value.decode_float(20))) + TYPE_TRANSFORM2D: + return Transform2D(Vector2(serialized_value.decode_float(0), serialized_value.decode_float(4)), Vector2(serialized_value.decode_float(8), serialized_value.decode_float(12)), Vector2(serialized_value.decode_float(16), serialized_value.decode_float(20))) + TYPE_BASIS: + return Basis(Vector3(serialized_value.decode_float(0), serialized_value.decode_float(4), serialized_value.decode_float(8)), Vector3(serialized_value.decode_float(12), serialized_value.decode_float(16), serialized_value.decode_float(20)), Vector3(serialized_value.decode_float(24), serialized_value.decode_float(28), serialized_value.decode_float(32))) + TYPE_TRANSFORM3D: + return Transform3D(Basis(Vector3(serialized_value.decode_float(0), serialized_value.decode_float(4), serialized_value.decode_float(8)), Vector3(serialized_value.decode_float(12), serialized_value.decode_float(16), serialized_value.decode_float(20)), Vector3(serialized_value.decode_float(24), serialized_value.decode_float(28), serialized_value.decode_float(32))), Vector3(serialized_value.decode_float(36), serialized_value.decode_float(40), serialized_value.decode_float(44))) + TYPE_NIL: + push_error("Failed to deserialize, null type!") + _: + push_error("Unknown type to deserialize!") + + return null + + + +#This should be a godot native function imo +static func serialize(value: Variant) -> PackedByteArray: + var serialized_value: PackedByteArray + + match(typeof(value)): + TYPE_BOOL: + serialized_value.resize(1) + serialized_value.encode_u8(0, value) #In the future to expand as bitfield + TYPE_INT: + serialized_value.resize(4) + serialized_value.encode_s32(0, value) + TYPE_FLOAT: + serialized_value.resize(4) + serialized_value.encode_float(0, value) + TYPE_STRING: + return (value as String).to_ascii_buffer() #Note that this is exclusively ASCII. If UTF-8+ (e.g. emojis or non-english, it should give error) + TYPE_STRING_NAME: + return (value as StringName).to_ascii_buffer() #Note that this is exclusively ASCII. If UTF-8+ (e.g. emojis or non-english, it should give error) + TYPE_VECTOR2: + serialized_value.resize(8) + serialized_value.encode_float(0, (value as Vector2).x) + serialized_value.encode_float(4, (value as Vector2).y) + TYPE_VECTOR2I: + serialized_value.resize(8) + serialized_value.encode_s32(0, (value as Vector2).x) + serialized_value.encode_s32(4, (value as Vector2).y) + TYPE_VECTOR3: + serialized_value.resize(12) + serialized_value.encode_float(0, (value as Vector3).x) + serialized_value.encode_float(4, (value as Vector3).y) + serialized_value.encode_float(8, (value as Vector3).z) + TYPE_VECTOR3I: + serialized_value.resize(12) + serialized_value.encode_s32(0, (value as Vector3i).x) + serialized_value.encode_s32(4, (value as Vector3i).y) + serialized_value.encode_s32(8, (value as Vector3i).z) + TYPE_QUATERNION: + serialized_value.resize(16) + serialized_value.encode_float(0, (value as Quaternion).x) + serialized_value.encode_float(4, (value as Quaternion).y) + serialized_value.encode_float(8, (value as Quaternion).z) + serialized_value.encode_float(12, (value as Quaternion).w) + TYPE_VECTOR4: + serialized_value.resize(16) + serialized_value.encode_float(0, (value as Vector4).x) + serialized_value.encode_float(4, (value as Vector4).y) + serialized_value.encode_float(8, (value as Vector4).z) + serialized_value.encode_float(12, (value as Vector4).w) + TYPE_VECTOR4I: + serialized_value.resize(16) + serialized_value.encode_s32(0, (value as Vector4i).x) + serialized_value.encode_s32(4, (value as Vector4i).y) + serialized_value.encode_s32(8, (value as Vector3i).z) + serialized_value.encode_s32(12, (value as Vector4i).w) + TYPE_RECT2: + serialized_value.resize(16) + serialized_value.encode_float(0, (value as Rect2).position.x) + serialized_value.encode_float(4, (value as Rect2).position.y) + serialized_value.encode_float(8, (value as Rect2).size.x) + serialized_value.encode_float(12, (value as Rect2).size.y) + TYPE_RECT2I: + serialized_value.resize(16) + serialized_value.encode_s32(0, (value as Rect2i).position.x) + serialized_value.encode_s32(4, (value as Rect2i).position.y) + serialized_value.encode_s32(8, (value as Rect2i).size.x) + serialized_value.encode_s32(12, (value as Rect2i).size.y) + TYPE_AABB: + serialized_value.resize(24) + serialized_value.encode_float(0, (value as AABB).position.x) + serialized_value.encode_float(4, (value as AABB).position.y) + serialized_value.encode_float(8, (value as AABB).position.z) + serialized_value.encode_float(12, (value as AABB).size.x) + serialized_value.encode_float(16, (value as AABB).size.y) + serialized_value.encode_float(20, (value as AABB).size.z) + TYPE_TRANSFORM2D: + serialized_value.resize(24) + serialized_value.encode_float(0, (value as Transform2D).x.x) + serialized_value.encode_float(4, (value as Transform2D).x.y) + serialized_value.encode_float(8, (value as Transform2D).y.x) + serialized_value.encode_float(12, (value as Transform2D).y.y) + serialized_value.encode_float(16, (value as Transform2D).origin.x) + serialized_value.encode_float(20, (value as Transform2D).origin.y) + TYPE_BASIS: + serialized_value = encode_basis(value) + TYPE_TRANSFORM3D: + serialized_value = encode_basis(value) + serialized_value.resize(48) + serialized_value.encode_float(36, (value as Transform3D).origin.x) + serialized_value.encode_float(40, (value as Transform3D).origin.y) + serialized_value.encode_float(44, (value as Transform3D).origin.z) + #TYPE_RID: # RID is not meant to be serialized, but including it just in case. + #serialized_value.resize(4) + #serialized_value.encode_u32(0, (value as RID).get_id()) + #TYPE_NODE_PATH: #I will open a godot engine issue for this. No straightforward way to convert to string and .to_ascii_buffer?! + #var nodepath: NodePath + #(value as String).to_ascii_buffer() #Note that this is exclusively ASCII. If UTF-8+ (e.g. emojis or non-english, it should give error) + #TYPE_COLOR: # @GDScript.Color8() -> could have RGBA in a single byte (4 bytes) since each value is 0-255 + TYPE_NIL: + push_error("Failed to serialize, null value!") + _: + push_error("Unrecognized type serialized. Either an array/packedarray/dictionary or plane/projection/color") + + + return serialized_value + +static func encode_basis(value: Basis) -> PackedByteArray: + var serialized_value: PackedByteArray + serialized_value.resize(36) + serialized_value.encode_float(0, (value as Basis).x.x) + serialized_value.encode_float(4, (value as Basis).x.y) + serialized_value.encode_float(8, (value as Basis).x.z) + serialized_value.encode_float(12, (value as Basis).y.x) + serialized_value.encode_float(16, (value as Basis).y.y) + serialized_value.encode_float(20, (value as Basis).y.z) + serialized_value.encode_float(24, (value as Basis).z.x) + serialized_value.encode_float(28, (value as Basis).z.y) + serialized_value.encode_float(32, (value as Basis).z.z) + + return serialized_value + +#func deserialize(serialized_properties: PackedByteArray, properties_template: PropertyEntry) -> Variant: From 4fde8ab6186b6856460e413c3345513f1cc2aa87 Mon Sep 17 00:00:00 2001 From: Solomos Date: Mon, 26 Aug 2024 23:01:58 +0300 Subject: [PATCH 2/7] Added serialization for all input properties (so, dynamic serialization) and deserialization for received input properties. --- addons/netfox/netfox.gd | 10 +- addons/netfox/properties/property-snapshot.gd | 19 ++- addons/netfox/rollback/network-rollback.gd | 2 + .../netfox/rollback/rollback-synchronizer.gd | 94 ++++++++------- .../netfox/serialization/input-serializer.gd | 1 - .../serialization/properties-serializer.gd | 41 +++++++ examples/forest-brawl/scenes/brawler.tscn | 110 +++++++++--------- project.godot | 1 + 8 files changed, 173 insertions(+), 105 deletions(-) delete mode 100644 addons/netfox/serialization/input-serializer.gd create mode 100644 addons/netfox/serialization/properties-serializer.gd diff --git a/addons/netfox/netfox.gd b/addons/netfox/netfox.gd index b4b28d9..dd116e8 100644 --- a/addons/netfox/netfox.gd +++ b/addons/netfox/netfox.gd @@ -64,7 +64,8 @@ var SETTINGS = [ { "name": "netfox/rollback/input_redundancy", "value": 3, - "type": TYPE_INT + "type": TYPE_INT, + "hint_string": "When an input is sent, which previous tick inputs should we send with it? By having this above 1, inputs are batched together so if a packet is lost which contains an input, the next packets will provide it" }, { "name": "netfox/rollback/display_offset", @@ -76,6 +77,13 @@ var SETTINGS = [ "name": "netfox/events/enabled", "value": true, "type": TYPE_BOOL + }, + # Serialization + { + "name": "netfox/serialization/enable_input_serialization", + "value": true, + "type": TYPE_BOOL, + "hint_string": "Enabling this, the input is serialized before sending it, instead of sending a dictionary of string properties and its values. Enabling this is recommended to save bandwidth, at the slight cost of CPU." } ] diff --git a/addons/netfox/properties/property-snapshot.gd b/addons/netfox/properties/property-snapshot.gd index 261803b..caa16b9 100644 --- a/addons/netfox/properties/property-snapshot.gd +++ b/addons/netfox/properties/property-snapshot.gd @@ -8,18 +8,25 @@ static func extract(properties: Array[PropertyEntry]) -> Dictionary: result.make_read_only() return result +## Extract but in serialized format! static func extract_serialized(properties: Array[PropertyEntry], tick_timestamp: int) -> PackedByteArray: - var result: PackedByteArray - result.resize(4) - result.encode_u32(0, tick_timestamp) - + var value_bytes: PackedByteArray + value_bytes.resize(0) var picked_value: Variant + var picked_serialized_value: PackedByteArray for picked_property in properties: picked_value = picked_property.get_value() - result.append_array(ValueToBytes.serialize(picked_value)) - print("Picked property %s of value %s and byte size: %s" % [picked_property._to_string(), picked_value, result.size()]) + picked_serialized_value = ValueToBytes.serialize(picked_value) + value_bytes.append_array(picked_serialized_value) + var result: PackedByteArray + result.resize(5) + result.encode_u32(0, tick_timestamp) + result.encode_u8(4, value_bytes.size()) + result.append_array(value_bytes)#now that the header is added, add the values + return result + static func apply(properties: Dictionary, cache: PropertyCache): for property in properties: diff --git a/addons/netfox/rollback/network-rollback.gd b/addons/netfox/rollback/network-rollback.gd index 19d4807..a5aa15c 100644 --- a/addons/netfox/rollback/network-rollback.gd +++ b/addons/netfox/rollback/network-rollback.gd @@ -48,6 +48,8 @@ var tick: int: set(v): push_error("Trying to set read-only variable tick") +var enable_input_serialization: bool = ProjectSettings.get_setting("netfox/serialization/enable_input_serialization", true) + ## Event emitted before running the network rollback loop signal before_loop() diff --git a/addons/netfox/rollback/rollback-synchronizer.gd b/addons/netfox/rollback/rollback-synchronizer.gd index f776fdc..6c5685d 100644 --- a/addons/netfox/rollback/rollback-synchronizer.gd +++ b/addons/netfox/rollback/rollback-synchronizer.gd @@ -14,9 +14,6 @@ class_name RollbackSynchronizer ## Turning this off is recommended to save bandwith and reduce cheating risks. @export var enable_input_broadcast: bool = true -## This will serialize input before sending it, instead of sending a dictionary of string properties and its values -## Turning this on is recommended to save bandwidth, at the slight cost of CPU. -@export var enable_input_serialization: bool = true var _record_state_props: Array[PropertyEntry] = [] var _record_input_props: Array[PropertyEntry] = [] @@ -27,6 +24,7 @@ var _nodes: Array[Node] = [] var _states: Dictionary = {} var _inputs: Dictionary = {} var _serialized_inputs: Dictionary = {} # +var serialized_inputs_to_send: Array[PackedByteArray] = [] var _latest_state: int = -1 var _earliest_input: int @@ -77,7 +75,7 @@ func process_authority(): # Gather state properties that we own # i.e. it's the state of a node that belongs to the local peer for property in state_properties: - var pe = _property_cache.get_entry(property) + var pe: PropertyEntry = _property_cache.get_entry(property) if pe.node.is_multiplayer_authority(): _auth_state_props.push_back(pe) @@ -86,8 +84,9 @@ func process_authority(): for property in input_properties: var pe = _property_cache.get_entry(property) if pe.node.is_multiplayer_authority(): - _record_input_props.push_back(pe) _auth_input_props.push_back(pe) + else: + _record_input_props.push_back(pe) func _ready(): process_settings() @@ -172,6 +171,7 @@ func _record_tick(tick: int): _states[tick] = PropertySnapshot.extract(_record_state_props) func _after_loop(): + _earliest_input = NetworkTime.tick # Apply display state var display_state = _get_history(_states, NetworkTime.tick - NetworkRollback.display_offset) PropertySnapshot.apply(display_state, _property_cache) @@ -183,40 +183,43 @@ func _before_tick(_delta, tick): func _after_tick(_delta: float, _tick: int): if not _auth_input_props.is_empty(): - _earliest_input = _tick var input = PropertySnapshot.extract(_auth_input_props) _inputs[_tick] = input - - if (enable_input_serialization): - var serialized_current_input: PackedByteArray = PropertySnapshot.extract_serialized(_auth_input_props, _tick) + + if (NetworkRollback.enable_input_serialization): + var serialized_current_input: PackedByteArray = PropertiesSerializer.serialize_multiple_properties(_auth_input_props, _tick) _serialized_inputs[_tick] = serialized_current_input - var serialized_inputs_to_send: Array[PackedByteArray] = [] - var first_of_previous_inputs_index: int = max(_serialized_inputs.size() - NetworkRollback.input_redundancy, _earliest_input) + if (serialized_inputs_to_send.size() == NetworkRollback.input_redundancy): + serialized_inputs_to_send.remove_at(0) + serialized_inputs_to_send.append(serialized_current_input) - for picked_tick_index in range(first_of_previous_inputs_index, _serialized_inputs.size()): - serialized_inputs_to_send.append(_serialized_inputs[picked_tick_index]) + if (serialized_inputs_to_send.is_empty() == false): + var merged_serialized_inputs: PackedByteArray + merged_serialized_inputs.resize(0) + for picked_serialized_input in serialized_inputs_to_send: + merged_serialized_inputs.append_array(picked_serialized_input) - _attempt_submit_serialized_input(serialized_inputs_to_send) + _attempt_submit_serialized_inputs(merged_serialized_inputs) else: #Send the last n inputs for each property var inputs = {} for i in range(0, NetworkRollback.input_redundancy): - var tick_input = _inputs.get(NetworkTime.tick - i, {}) + var tick_input: Dictionary = _inputs.get(NetworkTime.tick - i, {}) for property in tick_input: if not inputs.has(property): inputs[property] = [] inputs[property].push_back(tick_input[property]) _attempt_submit_raw_input(inputs) - + while _states.size() > NetworkRollback.history_limit: _states.erase(_states.keys().min()) while _inputs.size() > NetworkRollback.history_limit: _inputs.erase(_inputs.keys().min()) - if (enable_input_serialization): + if (NetworkRollback.enable_input_serialization): while _serialized_inputs.size() > NetworkRollback.history_limit: _serialized_inputs.erase(_serialized_inputs.keys().min()) @@ -229,12 +232,12 @@ func _attempt_submit_raw_input(inputs: Dictionary): elif not multiplayer.is_server(): _submit_raw_input.rpc_id(1, inputs, NetworkTime.tick) -func _attempt_submit_serialized_input(serialized_inputs: Array[PackedByteArray]): +func _attempt_submit_serialized_inputs(serialized_inputs: PackedByteArray): # TODO: Default to input broadcast in mesh network setups if enable_input_broadcast: - _submit_serialized_input.rpc(serialized_inputs) + _submit_serialized_inputs.rpc(serialized_inputs) elif not multiplayer.is_server(): - _submit_serialized_input.rpc_id(1, serialized_inputs) + _submit_serialized_inputs.rpc_id(1, serialized_inputs) func _get_history(buffer: Dictionary, tick: int) -> Dictionary: if buffer.has(tick): @@ -259,35 +262,42 @@ func _get_history(buffer: Dictionary, tick: int) -> Dictionary: return buffer[before] @rpc("any_peer", "unreliable", "call_remote") -func _submit_serialized_input(serialized_inputs: Array[PackedByteArray]): +func _submit_serialized_inputs(serialized_inputs: PackedByteArray): var sender: int = multiplayer.get_remote_sender_id() #If clients send input exclusively to server, yet we are a client and received an input - #this is either a serious bug or a hacker sending RPCs. + #This is a hacker sending RPCs (or someone called this RPC without adding the enable_input_broadcast boolean check) if (enable_input_broadcast == false && not multiplayer.is_server() && sender != 1): _logger.error("Received input from %s for %s from a client!" % [sender, root.name]) var picked_tick: int - for picked_serialized_input in serialized_inputs: - picked_tick = picked_serialized_input.decode_u32(0) - if (_inputs.has(picked_tick) == false): #New input tick! + var picked_input_values_size: int #The size of the serialized input containing all properties (excluding tick timestamp[0,1,2,3] and the size itself on byte[4]) + var picked_single_input: PackedByteArray + var picked_byte_index: int = 0 + while (picked_byte_index < serialized_inputs.size()): + picked_tick = serialized_inputs.decode_u32(picked_byte_index) + picked_byte_index += 4 + picked_input_values_size = serialized_inputs.decode_u8(picked_byte_index) + picked_byte_index += 1 + + if (_inputs.has(picked_tick) == false): #New input! + picked_single_input = serialized_inputs.slice(picked_byte_index, picked_byte_index + picked_input_values_size) + var received_properties: Dictionary + if (_auth_input_props.is_empty()): + received_properties = PropertiesSerializer.deserialize_multiple_properties(picked_single_input, _record_input_props) + else: + received_properties = PropertiesSerializer.deserialize_multiple_properties(picked_single_input, _auth_input_props) + _earliest_input = min(_earliest_input, picked_tick) - _inputs[picked_tick] = _inputs.get(picked_tick, {}) - var reconstructed_property: Dictionary = {} - var picked_type: Variant.Type - var picked_type_byte_size: int - var picked_serialized_property: PackedByteArray - var picked_byte_index: int = 4 #not 0 because above^ we already decoded tick (u_32) - for picked_property_entry in _auth_input_props: - picked_type = picked_property_entry.type - picked_type_byte_size = ValueToBytes.get_byte_size(picked_type) - picked_serialized_property = picked_serialized_input.slice(picked_byte_index, picked_byte_index + picked_type_byte_size + 1) # +1 because end is exclusive - - var picked_value = ValueToBytes.deserialize(picked_serialized_property, picked_type) - picked_byte_index += picked_type_byte_size - - _inputs[picked_tick][picked_property_entry._path] = picked_value + if (_inputs.has(picked_tick) == false): + _inputs[picked_tick] = received_properties + else: + for picked_property_path in received_properties: + _inputs[picked_tick][picked_property_path] = received_properties[picked_property_path] + + + picked_byte_index += picked_input_values_size @rpc("any_peer", "unreliable", "call_remote") func _submit_raw_input(input: Dictionary, tick: int): @@ -310,7 +320,7 @@ func _submit_raw_input(input: Dictionary, tick: int): continue sanitized[property] = value - print("Sanitized[property] %s is: %s" % [property, sanitized[property]]) + #print("Sanitized[property] %s is: %s" % [property, sanitized[property]]) if sanitized.size() > 0: for property in sanitized: @@ -318,7 +328,7 @@ func _submit_raw_input(input: Dictionary, tick: int): var t = tick - i var old_input = _inputs.get(t, {}).get(property) var new_input = sanitized[property][i] - print("property is %s and new_input is %s" % [property, new_input]) + #print("property is %s and new_input is %s" % [property, new_input]) if old_input == null: # We received an array of current and previous inputs, merge them into our history. diff --git a/addons/netfox/serialization/input-serializer.gd b/addons/netfox/serialization/input-serializer.gd deleted file mode 100644 index 05d1e01..0000000 --- a/addons/netfox/serialization/input-serializer.gd +++ /dev/null @@ -1 +0,0 @@ -extends Object diff --git a/addons/netfox/serialization/properties-serializer.gd b/addons/netfox/serialization/properties-serializer.gd new file mode 100644 index 0000000..6722940 --- /dev/null +++ b/addons/netfox/serialization/properties-serializer.gd @@ -0,0 +1,41 @@ +extends Object +class_name PropertiesSerializer + +static func deserialize_multiple_properties(serialized_input: PackedByteArray, auth_input_pros: Array[PropertyEntry]) -> Dictionary: + var reconstructed_dictionary: Dictionary = {} #Exclusively for that tick + + print("Deserializing multiple properties of serialized input %s and array %s" % [serialized_input, auth_input_pros]) + var property_type: Variant.Type + var property_type_byte_size: int + var serialized_property: PackedByteArray + var input_byte_index: int = 0 + for picked_property_entry in auth_input_pros: + property_type = picked_property_entry.type + property_type_byte_size = ValueToBytes.get_byte_size(property_type) + serialized_property = serialized_input.slice(input_byte_index, input_byte_index + property_type_byte_size) + + var picked_value = ValueToBytes.deserialize(serialized_property, property_type) + input_byte_index += property_type_byte_size + + print("path is %s and value is %s" % [picked_property_entry._path, picked_value]) + reconstructed_dictionary[picked_property_entry._path] = picked_value + return reconstructed_dictionary + +## Extract but in serialized format! +static func serialize_multiple_properties(properties: Array[PropertyEntry], tick_timestamp: int) -> PackedByteArray: + var value_bytes: PackedByteArray + value_bytes.resize(0) + var picked_value: Variant + var picked_serialized_value: PackedByteArray + for picked_property in properties: + picked_value = picked_property.get_value() + picked_serialized_value = ValueToBytes.serialize(picked_value) + value_bytes.append_array(picked_serialized_value) + + var result: PackedByteArray + result.resize(5) + result.encode_u32(0, tick_timestamp) + result.encode_u8(4, value_bytes.size()) + result.append_array(value_bytes)#now that the header is added, add the values + + return result diff --git a/examples/forest-brawl/scenes/brawler.tscn b/examples/forest-brawl/scenes/brawler.tscn index 65be7ea..1ce02d0 100644 --- a/examples/forest-brawl/scenes/brawler.tscn +++ b/examples/forest-brawl/scenes/brawler.tscn @@ -104,71 +104,71 @@ respawn_time = 8.0 transform = Transform3D(0.5, 0, 0, 0, 0.5, 0, 0, 0, 0.5, 0, -0.5, 0) [node name="Skeleton3D" parent="bomber-guy/rig" index="0"] -bones/0/position = Vector3(3.20746e-15, 0.583857, -0.0893679) +bones/0/position = Vector3(3.20916e-15, 0.596798, -0.0954073) bones/0/rotation = Quaternion(0.255292, -7.97688e-15, 1.00226e-14, 0.966864) -bones/1/rotation = Quaternion(0.0205548, 1.19184e-07, 2.45031e-09, 0.999789) -bones/1/scale = Vector3(1.01931, 0.96247, 1.01931) -bones/2/rotation = Quaternion(0.096395, -4.59033e-12, 2.30305e-08, 0.995343) -bones/2/scale = Vector3(1.04897, 0.907002, 1.05131) -bones/3/rotation = Quaternion(-0.181044, -1.17239e-07, -2.15821e-08, 0.983475) -bones/3/scale = Vector3(0.935252, 1.1196, 0.963441) -bones/4/rotation = Quaternion(0.376178, 1.10453e-07, 4.48439e-08, 0.926548) -bones/4/scale = Vector3(0.922825, 1.17435, 0.922825) -bones/5/rotation = Quaternion(-0.0765415, -3.93158e-08, -1.58714e-08, 0.997066) -bones/5/scale = Vector3(1.00009, 1.00661, 0.995893) -bones/6/rotation = Quaternion(-0.43687, -4.72473e-08, -2.26897e-08, 0.899525) -bones/6/scale = Vector3(1.08356, 1.00264, 0.945663) -bones/7/position = Vector3(-3.6176e-08, 0.583857, -0.0893679) +bones/1/rotation = Quaternion(0.0305452, 1.19154e-07, 3.64126e-09, 0.999533) +bones/1/scale = Vector3(1.01995, 0.961269, 1.01995) +bones/2/rotation = Quaternion(0.119237, -1.01441e-11, 2.85143e-08, 0.992866) +bones/2/scale = Vector3(1.04566, 0.911712, 1.04933) +bones/3/rotation = Quaternion(-0.199006, -1.16825e-07, -2.37233e-08, 0.979998) +bones/3/scale = Vector3(0.937625, 1.10995, 0.97005) +bones/4/rotation = Quaternion(0.384005, 1.1007e-07, 4.57769e-08, 0.923331) +bones/4/scale = Vector3(0.908312, 1.21211, 0.908312) +bones/5/rotation = Quaternion(-0.0738508, -2.14074e-08, -1.6435e-08, 0.997269) +bones/5/scale = Vector3(1.00011, 1.00753, 0.995709) +bones/6/rotation = Quaternion(-0.458532, -9.77774e-08, -5.05007e-08, 0.888678) +bones/6/scale = Vector3(1.10083, 1.01921, 0.924139) +bones/7/position = Vector3(-3.6176e-08, 0.596798, -0.0954073) bones/7/rotation = Quaternion(-0.135651, -0.571304, -0.59224, 0.551781) -bones/8/position = Vector3(3.6176e-08, 0.583857, -0.0893679) +bones/8/position = Vector3(3.6176e-08, 0.596798, -0.0954073) bones/8/rotation = Quaternion(-0.135651, 0.571304, 0.59224, 0.551781) -bones/9/position = Vector3(0.185305, 0.611893, -0.0339381) -bones/9/rotation = Quaternion(0.905251, -2.21189e-05, -0.000117811, 0.424877) -bones/9/scale = Vector3(1.00629, 0.987535, 1.00629) +bones/9/position = Vector3(0.185305, 0.624835, -0.0399775) +bones/9/rotation = Quaternion(0.915062, -2.60745e-05, -0.00011995, 0.403313) +bones/9/scale = Vector3(1.00538, 0.989337, 1.00538) bones/10/rotation = Quaternion(1.23085e-07, -5.52346e-06, -2.98159e-11, 1) -bones/11/rotation = Quaternion(0.385459, 6.31284e-06, -3.32111e-05, 0.922725) -bones/11/scale = Vector3(1.0012, 0.988229, 1.01087) +bones/11/rotation = Quaternion(0.338319, 8.31479e-06, -3.29737e-05, 0.941032) +bones/11/scale = Vector3(1.00102, 0.991556, 1.00762) bones/12/rotation = Quaternion(-9.67563e-08, 8.29257e-06, -2.16274e-09, 1) -bones/13/rotation = Quaternion(-0.505198, 0.000109451, 2.25149e-05, 0.863003) -bones/13/scale = Vector3(0.992628, 0.997842, 1.00979) +bones/13/rotation = Quaternion(-0.481888, 0.000108836, 2.64277e-05, 0.876233) +bones/13/scale = Vector3(0.993836, 0.998815, 1.00755) bones/14/rotation = Quaternion(5.02433e-08, 0.977548, -0.210714, 2.33145e-07) bones/14/scale = Vector3(0.99948, 1.00078, 0.999746) -bones/15/position = Vector3(-0.185305, 0.611893, -0.0339381) -bones/15/rotation = Quaternion(0.99633, 4.19078e-05, -0.000130651, -0.08559) -bones/15/scale = Vector3(1.00638, 0.987357, 1.00638) -bones/17/rotation = Quaternion(0.389683, 1.45563e-05, -3.68506e-05, 0.920949) -bones/17/scale = Vector3(1.00122, 0.987896, 1.0112) +bones/15/position = Vector3(-0.185305, 0.624835, -0.0399775) +bones/15/rotation = Quaternion(0.996009, 3.94026e-05, -0.000130965, -0.0892538) +bones/15/scale = Vector3(1.00588, 0.988351, 1.00588) +bones/17/rotation = Quaternion(0.363809, 1.46625e-05, -3.60719e-05, 0.931474) +bones/17/scale = Vector3(1.00112, 0.989828, 1.0093) bones/18/rotation = Quaternion(-4.84353e-08, 4.04099e-06, 1.10813e-08, 1) -bones/19/rotation = Quaternion(-0.871762, 8.53352e-05, 7.82363e-05, 0.48993) -bones/19/scale = Vector3(0.992445, 0.99873, 1.0091) +bones/19/rotation = Quaternion(-0.859644, 8.50939e-05, 7.96767e-05, 0.510894) +bones/19/scale = Vector3(0.993011, 0.99798, 1.00924) bones/20/rotation = Quaternion(1.02643e-08, 0.977516, -0.210861, -2.46051e-07) bones/20/scale = Vector3(1.00003, 0.999961, 1.00001) -bones/21/position = Vector3(0.0507796, 1.02886, 0.287226) -bones/21/rotation = Quaternion(-0.472842, -0.270157, -0.416495, 0.727989) -bones/22/position = Vector3(0.242455, 1.04749, 0.191412) -bones/22/rotation = Quaternion(-0.280276, 0.143785, -0.88237, 0.349562) -bones/22/scale = Vector3(1.07211, 0.87008, 1.07211) -bones/23/rotation = Quaternion(3.18531e-08, 0.0853649, 8.32473e-08, 0.99635) -bones/24/rotation = Quaternion(0.421944, 0.147999, -0.308633, 0.839527) -bones/24/scale = Vector3(0.960023, 0.997541, 1.06918) -bones/25/rotation = Quaternion(2.97979e-08, -0.026165, -1.54692e-07, 0.999658) -bones/26/rotation = Quaternion(0.143976, -0.0275267, 0.0637975, 0.987139) -bones/26/scale = Vector3(1.00575, 0.989855, 1.00455) -bones/27/position = Vector3(-0.0507796, 1.02886, 0.287226) -bones/27/rotation = Quaternion(-0.472842, 0.270157, 0.416495, 0.727989) -bones/28/position = Vector3(-0.242455, 1.04749, 0.191412) -bones/28/rotation = Quaternion(-0.425562, -0.363618, 0.693798, 0.453127) -bones/28/scale = Vector3(0.983063, 1.03476, 0.983063) -bones/29/rotation = Quaternion(3.98409e-08, -0.0370571, -2.15209e-07, 0.999313) -bones/30/rotation = Quaternion(0.232389, -0.0893491, 0.253375, 0.93478) -bones/30/scale = Vector3(1.06864, 0.878868, 1.06637) -bones/31/rotation = Quaternion(-3.48398e-08, -0.117463, 1.57868e-07, 0.993077) -bones/32/rotation = Quaternion(0.41303, -0.11386, 0.189505, 0.883476) -bones/32/scale = Vector3(0.954337, 1.01045, 1.05417) -bones/33/position = Vector3(0.1184, 1.00881, 0.15031) -bones/33/rotation = Quaternion(-1.44276e-14, 0.544482, 0.838773, -2.01673e-14) -bones/34/position = Vector3(-0.1184, 1.00881, 0.15031) -bones/34/rotation = Quaternion(-1.44276e-14, 0.544482, 0.838773, -2.01673e-14) +bones/21/position = Vector3(0.0507796, 1.03098, 0.29181) +bones/21/rotation = Quaternion(-0.462282, -0.264116, -0.420352, 0.73474) +bones/22/position = Vector3(0.242455, 1.05236, 0.196574) +bones/22/rotation = Quaternion(-0.257395, 0.121267, -0.902295, 0.323891) +bones/22/scale = Vector3(1.04758, 0.911271, 1.04758) +bones/23/rotation = Quaternion(1.92555e-08, 0.0878316, -2.25235e-07, 0.996135) +bones/24/rotation = Quaternion(0.376765, 0.141985, -0.275946, 0.872779) +bones/24/scale = Vector3(0.982455, 0.988139, 1.04219) +bones/25/rotation = Quaternion(1.89934e-08, -0.0258656, -1.64847e-08, 0.999665) +bones/26/rotation = Quaternion(0.168051, -0.0275768, 0.0685332, 0.983006) +bones/26/scale = Vector3(0.992513, 1.0129, 0.994862) +bones/27/position = Vector3(-0.0507796, 1.03098, 0.29181) +bones/27/rotation = Quaternion(-0.462282, 0.264116, 0.420352, 0.73474) +bones/28/position = Vector3(-0.242455, 1.05236, 0.196574) +bones/28/rotation = Quaternion(-0.408323, -0.352561, 0.721845, 0.433489) +bones/28/scale = Vector3(0.987805, 1.02485, 0.987805) +bones/29/rotation = Quaternion(2.49174e-09, -0.0414146, -1.67266e-07, 0.999142) +bones/30/rotation = Quaternion(0.210336, -0.0832572, 0.233584, 0.945656) +bones/30/scale = Vector3(1.04725, 0.913329, 1.04628) +bones/31/rotation = Quaternion(-4.28044e-08, -0.113463, 1.95445e-07, 0.993542) +bones/32/rotation = Quaternion(0.422021, -0.110196, 0.167438, 0.884149) +bones/32/scale = Vector3(0.966294, 1.0052, 1.03709) +bones/33/position = Vector3(0.1184, 1.01489, 0.154373) +bones/33/rotation = Quaternion(-1.381e-14, 0.532315, 0.846546, -1.94573e-14) +bones/34/position = Vector3(-0.1184, 1.01489, 0.154373) +bones/34/rotation = Quaternion(-1.381e-14, 0.532315, 0.846546, -1.94573e-14) [node name="AnimationTree" type="AnimationTree" parent="."] tree_root = SubResource("AnimationNodeBlendTree_8to0y") diff --git a/project.godot b/project.godot index 388f7de..fef3d28 100644 --- a/project.godot +++ b/project.godot @@ -112,6 +112,7 @@ aim_south={ general/clear_settings=false time/tickrate=24 +serialization/enable_input_serialization=false [rendering] From 705cca6a73e781e3ff342655d048512b08f39683 Mon Sep 17 00:00:00 2001 From: Solomos Date: Mon, 26 Aug 2024 23:39:27 +0300 Subject: [PATCH 3/7] minor cleanup --- addons/netfox/properties/property-snapshot.gd | 20 ---- .../netfox/rollback/rollback-synchronizer.gd | 7 +- .../serialization/properties-serializer.gd | 2 +- examples/forest-brawl/scenes/brawler.tscn | 110 +++++++++--------- 4 files changed, 59 insertions(+), 80 deletions(-) diff --git a/addons/netfox/properties/property-snapshot.gd b/addons/netfox/properties/property-snapshot.gd index caa16b9..05d53f9 100644 --- a/addons/netfox/properties/property-snapshot.gd +++ b/addons/netfox/properties/property-snapshot.gd @@ -8,26 +8,6 @@ static func extract(properties: Array[PropertyEntry]) -> Dictionary: result.make_read_only() return result -## Extract but in serialized format! -static func extract_serialized(properties: Array[PropertyEntry], tick_timestamp: int) -> PackedByteArray: - var value_bytes: PackedByteArray - value_bytes.resize(0) - var picked_value: Variant - var picked_serialized_value: PackedByteArray - for picked_property in properties: - picked_value = picked_property.get_value() - picked_serialized_value = ValueToBytes.serialize(picked_value) - value_bytes.append_array(picked_serialized_value) - - var result: PackedByteArray - result.resize(5) - result.encode_u32(0, tick_timestamp) - result.encode_u8(4, value_bytes.size()) - result.append_array(value_bytes)#now that the header is added, add the values - - return result - - static func apply(properties: Dictionary, cache: PropertyCache): for property in properties: var pe = cache.get_entry(property) diff --git a/addons/netfox/rollback/rollback-synchronizer.gd b/addons/netfox/rollback/rollback-synchronizer.gd index 6c5685d..1b9c9c4 100644 --- a/addons/netfox/rollback/rollback-synchronizer.gd +++ b/addons/netfox/rollback/rollback-synchronizer.gd @@ -11,7 +11,7 @@ class_name RollbackSynchronizer @export var input_properties: Array[String] ## This will broadcast input to all peers, turning this off will limit to sending it to the server only. -## Turning this off is recommended to save bandwith and reduce cheating risks. +## Turning this off is recommended to save bandwidth and reduce cheating risks. @export var enable_input_broadcast: bool = true @@ -269,6 +269,7 @@ func _submit_serialized_inputs(serialized_inputs: PackedByteArray): #This is a hacker sending RPCs (or someone called this RPC without adding the enable_input_broadcast boolean check) if (enable_input_broadcast == false && not multiplayer.is_server() && sender != 1): _logger.error("Received input from %s for %s from a client!" % [sender, root.name]) + return var picked_tick: int var picked_input_values_size: int #The size of the serialized input containing all properties (excluding tick timestamp[0,1,2,3] and the size itself on byte[4]) @@ -307,6 +308,7 @@ func _submit_raw_input(input: Dictionary, tick: int): #this is either a serious bug or a hacker sending RPCs. if (enable_input_broadcast == false && not multiplayer.is_server() && sender != 1): _logger.error("Received input from %s for tick %s for %s from a client!" % [sender, tick, root.name]) + return var sanitized = {} for property in input: @@ -320,7 +322,6 @@ func _submit_raw_input(input: Dictionary, tick: int): continue sanitized[property] = value - #print("Sanitized[property] %s is: %s" % [property, sanitized[property]]) if sanitized.size() > 0: for property in sanitized: @@ -328,14 +329,12 @@ func _submit_raw_input(input: Dictionary, tick: int): var t = tick - i var old_input = _inputs.get(t, {}).get(property) var new_input = sanitized[property][i] - #print("property is %s and new_input is %s" % [property, new_input]) if old_input == null: # We received an array of current and previous inputs, merge them into our history. _inputs[t] = _inputs.get(t, {}) _inputs[t][property] = new_input _earliest_input = min(_earliest_input, t) - else: _logger.warning("Received invalid input from %s for tick %s for %s" % [sender, tick, root.name]) diff --git a/addons/netfox/serialization/properties-serializer.gd b/addons/netfox/serialization/properties-serializer.gd index 6722940..a6bbc3e 100644 --- a/addons/netfox/serialization/properties-serializer.gd +++ b/addons/netfox/serialization/properties-serializer.gd @@ -21,7 +21,7 @@ static func deserialize_multiple_properties(serialized_input: PackedByteArray, a reconstructed_dictionary[picked_property_entry._path] = picked_value return reconstructed_dictionary -## Extract but in serialized format! +## PropertySnapshot.extract() but in serialized format! static func serialize_multiple_properties(properties: Array[PropertyEntry], tick_timestamp: int) -> PackedByteArray: var value_bytes: PackedByteArray value_bytes.resize(0) diff --git a/examples/forest-brawl/scenes/brawler.tscn b/examples/forest-brawl/scenes/brawler.tscn index 1ce02d0..65052f4 100644 --- a/examples/forest-brawl/scenes/brawler.tscn +++ b/examples/forest-brawl/scenes/brawler.tscn @@ -104,71 +104,71 @@ respawn_time = 8.0 transform = Transform3D(0.5, 0, 0, 0, 0.5, 0, 0, 0, 0.5, 0, -0.5, 0) [node name="Skeleton3D" parent="bomber-guy/rig" index="0"] -bones/0/position = Vector3(3.20916e-15, 0.596798, -0.0954073) +bones/0/position = Vector3(3.20746e-15, 0.541784, -0.0697341) bones/0/rotation = Quaternion(0.255292, -7.97688e-15, 1.00226e-14, 0.966864) -bones/1/rotation = Quaternion(0.0305452, 1.19154e-07, 3.64126e-09, 0.999533) -bones/1/scale = Vector3(1.01995, 0.961269, 1.01995) -bones/2/rotation = Quaternion(0.119237, -1.01441e-11, 2.85143e-08, 0.992866) -bones/2/scale = Vector3(1.04566, 0.911712, 1.04933) -bones/3/rotation = Quaternion(-0.199006, -1.16825e-07, -2.37233e-08, 0.979998) -bones/3/scale = Vector3(0.937625, 1.10995, 0.97005) -bones/4/rotation = Quaternion(0.384005, 1.1007e-07, 4.57769e-08, 0.923331) -bones/4/scale = Vector3(0.908312, 1.21211, 0.908312) -bones/5/rotation = Quaternion(-0.0738508, -2.14074e-08, -1.6435e-08, 0.997269) -bones/5/scale = Vector3(1.00011, 1.00753, 0.995709) -bones/6/rotation = Quaternion(-0.458532, -9.77774e-08, -5.05007e-08, 0.888678) -bones/6/scale = Vector3(1.10083, 1.01921, 0.924139) -bones/7/position = Vector3(-3.6176e-08, 0.596798, -0.0954073) +bones/1/rotation = Quaternion(-0.011695, 1.19201e-07, -1.39417e-09, 0.999932) +bones/1/scale = Vector3(1.01588, 0.968983, 1.01588) +bones/2/rotation = Quaternion(0.0208709, -1.97065e-13, 4.977e-09, 0.999782) +bones/2/scale = Vector3(1.04967, 0.907522, 1.04975) +bones/3/rotation = Quaternion(-0.120584, -1.18339e-07, -1.43747e-08, 0.992703) +bones/3/scale = Vector3(0.937799, 1.12689, 0.949998) +bones/4/rotation = Quaternion(0.340518, 1.12085e-07, 4.05929e-08, 0.940238) +bones/4/scale = Vector3(0.965644, 1.07243, 0.965644) +bones/5/rotation = Quaternion(-0.0858146, -1.54712e-09, -2.03464e-08, 0.996311) +bones/5/scale = Vector3(1.00005, 1.00385, 0.996972) +bones/6/rotation = Quaternion(-0.351208, -1.10132e-07, -4.12919e-08, 0.936297) +bones/6/scale = Vector3(1.03553, 0.978445, 0.992288) +bones/7/position = Vector3(-3.6176e-08, 0.541784, -0.0697341) bones/7/rotation = Quaternion(-0.135651, -0.571304, -0.59224, 0.551781) -bones/8/position = Vector3(3.6176e-08, 0.596798, -0.0954073) +bones/8/position = Vector3(3.6176e-08, 0.541784, -0.0697341) bones/8/rotation = Quaternion(-0.135651, 0.571304, 0.59224, 0.551781) -bones/9/position = Vector3(0.185305, 0.624835, -0.0399775) -bones/9/rotation = Quaternion(0.915062, -2.60745e-05, -0.00011995, 0.403313) -bones/9/scale = Vector3(1.00538, 0.989337, 1.00538) +bones/9/position = Vector3(0.185305, 0.569821, -0.0143043) +bones/9/rotation = Quaternion(0.877117, -1.08073e-05, -0.000111774, 0.480277) +bones/9/scale = Vector3(1.00971, 0.980862, 1.00971) bones/10/rotation = Quaternion(1.23085e-07, -5.52346e-06, -2.98159e-11, 1) -bones/11/rotation = Quaternion(0.338319, 8.31479e-06, -3.29737e-05, 0.941032) -bones/11/scale = Vector3(1.00102, 0.991556, 1.00762) +bones/11/rotation = Quaternion(0.503841, 8.94716e-07, -3.31607e-05, 0.863796) +bones/11/scale = Vector3(1.00194, 0.974335, 1.02468) bones/12/rotation = Quaternion(-9.67563e-08, 8.29257e-06, -2.16274e-09, 1) -bones/13/rotation = Quaternion(-0.481888, 0.000108836, 2.64277e-05, 0.876233) -bones/13/scale = Vector3(0.993836, 0.998815, 1.00755) +bones/13/rotation = Quaternion(-0.564447, 0.000110874, 1.18043e-05, 0.825469) +bones/13/scale = Vector3(0.988358, 0.993041, 1.01916) bones/14/rotation = Quaternion(5.02433e-08, 0.977548, -0.210714, 2.33145e-07) bones/14/scale = Vector3(0.99948, 1.00078, 0.999746) -bones/15/position = Vector3(-0.185305, 0.624835, -0.0399775) -bones/15/rotation = Quaternion(0.996009, 3.94026e-05, -0.000130965, -0.0892538) -bones/15/scale = Vector3(1.00588, 0.988351, 1.00588) -bones/17/rotation = Quaternion(0.363809, 1.46625e-05, -3.60719e-05, 0.931474) -bones/17/scale = Vector3(1.00112, 0.989828, 1.0093) +bones/15/position = Vector3(-0.185305, 0.569821, -0.0143043) +bones/15/rotation = Quaternion(0.996522, 4.44183e-05, -0.000130342, -0.0833352) +bones/15/scale = Vector3(1.00775, 0.984683, 1.00775) +bones/17/rotation = Quaternion(0.457802, 1.44218e-05, -3.92301e-05, 0.889054) +bones/17/scale = Vector3(1.00159, 0.980946, 1.01808) bones/18/rotation = Quaternion(-4.84353e-08, 4.04099e-06, 1.10813e-08, 1) -bones/19/rotation = Quaternion(-0.859644, 8.50939e-05, 7.96767e-05, 0.510894) -bones/19/scale = Vector3(0.993011, 0.99798, 1.00924) +bones/19/rotation = Quaternion(-0.904713, 8.56989e-05, 7.44614e-05, 0.426022) +bones/19/scale = Vector3(0.990459, 1.00203, 1.00799) bones/20/rotation = Quaternion(1.02643e-08, 0.977516, -0.210861, -2.46051e-07) bones/20/scale = Vector3(1.00003, 0.999961, 1.00001) -bones/21/position = Vector3(0.0507796, 1.03098, 0.29181) -bones/21/rotation = Quaternion(-0.462282, -0.264116, -0.420352, 0.73474) -bones/22/position = Vector3(0.242455, 1.05236, 0.196574) -bones/22/rotation = Quaternion(-0.257395, 0.121267, -0.902295, 0.323891) -bones/22/scale = Vector3(1.04758, 0.911271, 1.04758) -bones/23/rotation = Quaternion(1.92555e-08, 0.0878316, -2.25235e-07, 0.996135) -bones/24/rotation = Quaternion(0.376765, 0.141985, -0.275946, 0.872779) -bones/24/scale = Vector3(0.982455, 0.988139, 1.04219) -bones/25/rotation = Quaternion(1.89934e-08, -0.0258656, -1.64847e-08, 0.999665) -bones/26/rotation = Quaternion(0.168051, -0.0275768, 0.0685332, 0.983006) -bones/26/scale = Vector3(0.992513, 1.0129, 0.994862) -bones/27/position = Vector3(-0.0507796, 1.03098, 0.29181) -bones/27/rotation = Quaternion(-0.462282, 0.264116, 0.420352, 0.73474) -bones/28/position = Vector3(-0.242455, 1.05236, 0.196574) -bones/28/rotation = Quaternion(-0.408323, -0.352561, 0.721845, 0.433489) -bones/28/scale = Vector3(0.987805, 1.02485, 0.987805) -bones/29/rotation = Quaternion(2.49174e-09, -0.0414146, -1.67266e-07, 0.999142) -bones/30/rotation = Quaternion(0.210336, -0.0832572, 0.233584, 0.945656) -bones/30/scale = Vector3(1.04725, 0.913329, 1.04628) -bones/31/rotation = Quaternion(-4.28044e-08, -0.113463, 1.95445e-07, 0.993542) -bones/32/rotation = Quaternion(0.422021, -0.110196, 0.167438, 0.884149) -bones/32/scale = Vector3(0.966294, 1.0052, 1.03709) -bones/33/position = Vector3(0.1184, 1.01489, 0.154373) -bones/33/rotation = Quaternion(-1.381e-14, 0.532315, 0.846546, -1.94573e-14) -bones/34/position = Vector3(-0.1184, 1.01489, 0.154373) -bones/34/rotation = Quaternion(-1.381e-14, 0.532315, 0.846546, -1.94573e-14) +bones/21/position = Vector3(0.0507796, 1.02088, 0.270898) +bones/21/rotation = Quaternion(-0.506586, -0.289463, -0.403316, 0.704924) +bones/22/position = Vector3(0.242455, 1.03042, 0.173756) +bones/22/rotation = Quaternion(-0.337003, 0.236679, -0.801477, 0.433643) +bones/22/scale = Vector3(1.1264, 0.788156, 1.1264) +bones/23/rotation = Quaternion(-4.53917e-08, 0.0561138, 6.12007e-08, 0.998424) +bones/24/rotation = Quaternion(0.567681, 0.169122, -0.423453, 0.685437) +bones/24/scale = Vector3(0.898858, 0.988916, 1.16062) +bones/25/rotation = Quaternion(-6.91384e-08, -0.0285117, -1.12731e-07, 0.999593) +bones/26/rotation = Quaternion(0.0667088, -0.0312487, 0.0431794, 0.996348) +bones/26/scale = Vector3(1.05288, 0.904574, 1.05123) +bones/27/position = Vector3(-0.0507796, 1.02088, 0.270898) +bones/27/rotation = Quaternion(-0.506586, 0.289463, 0.403316, 0.704924) +bones/28/position = Vector3(-0.242455, 1.03042, 0.173756) +bones/28/rotation = Quaternion(-0.448512, -0.384979, 0.618413, 0.517874) +bones/28/scale = Vector3(0.971869, 1.05873, 0.971869) +bones/29/rotation = Quaternion(9.63622e-08, -0.0084532, -2.07931e-07, 0.999964) +bones/30/rotation = Quaternion(0.246125, -0.0955487, 0.317189, 0.91087) +bones/30/scale = Vector3(1.13478, 0.778569, 1.13713) +bones/31/rotation = Quaternion(8.90478e-08, -0.132603, -4.37424e-08, 0.991169) +bones/32/rotation = Quaternion(0.407007, -0.125294, 0.260653, 0.866434) +bones/32/scale = Vector3(0.93686, 1.02215, 1.12698) +bones/33/position = Vector3(0.1184, 0.98805, 0.136473) +bones/33/rotation = Quaternion(-1.14663e-14, 0.583363, 0.812212, -1.96342e-14) +bones/34/position = Vector3(-0.1184, 0.98805, 0.136473) +bones/34/rotation = Quaternion(-1.14663e-14, 0.583363, 0.812212, -1.96342e-14) [node name="AnimationTree" type="AnimationTree" parent="."] tree_root = SubResource("AnimationNodeBlendTree_8to0y") From e3e240b4e3fd959e7e0c7bc2038d3922a7b65ebd Mon Sep 17 00:00:00 2001 From: Solomos Date: Tue, 27 Aug 2024 01:29:28 +0300 Subject: [PATCH 4/7] Local input is no longer RPC'd, since it is already applied, RPCing locally is bloat. --- .../netfox/rollback/rollback-synchronizer.gd | 34 +++--- examples/forest-brawl/scenes/brawler.tscn | 110 +++++++++--------- project.godot | 2 +- 3 files changed, 71 insertions(+), 75 deletions(-) diff --git a/addons/netfox/rollback/rollback-synchronizer.gd b/addons/netfox/rollback/rollback-synchronizer.gd index 1b9c9c4..cc095dd 100644 --- a/addons/netfox/rollback/rollback-synchronizer.gd +++ b/addons/netfox/rollback/rollback-synchronizer.gd @@ -183,8 +183,8 @@ func _before_tick(_delta, tick): func _after_tick(_delta: float, _tick: int): if not _auth_input_props.is_empty(): - var input = PropertySnapshot.extract(_auth_input_props) - _inputs[_tick] = input + var local_input: Dictionary = PropertySnapshot.extract(_auth_input_props) + _inputs[_tick] = local_input if (NetworkRollback.enable_input_serialization): var serialized_current_input: PackedByteArray = PropertiesSerializer.serialize_multiple_properties(_auth_input_props, _tick) @@ -202,10 +202,10 @@ func _after_tick(_delta: float, _tick: int): _attempt_submit_serialized_inputs(merged_serialized_inputs) else: - #Send the last n inputs for each property + #Send the last n inputs for each property var inputs = {} for i in range(0, NetworkRollback.input_redundancy): - var tick_input: Dictionary = _inputs.get(NetworkTime.tick - i, {}) + var tick_input: Dictionary = _inputs.get(_tick - i, {}) for property in tick_input: if not inputs.has(property): inputs[property] = [] @@ -213,6 +213,8 @@ func _after_tick(_delta: float, _tick: int): _attempt_submit_raw_input(inputs) + history_cleanup() +func history_cleanup() -> void: while _states.size() > NetworkRollback.history_limit: _states.erase(_states.keys().min()) @@ -225,17 +227,21 @@ func _after_tick(_delta: float, _tick: int): _freshness_store.trim() -func _attempt_submit_raw_input(inputs: Dictionary): +## Sends batched inputs to all other players (not local!) +func _attempt_submit_raw_input(batched_inputs: Dictionary): # TODO: Default to input broadcast in mesh network setups if enable_input_broadcast: - _submit_raw_input.rpc( inputs, NetworkTime.tick) + for picked_peer_id in multiplayer.get_peers(): + _submit_raw_input.rpc_id(picked_peer_id, batched_inputs, NetworkTime.tick) elif not multiplayer.is_server(): - _submit_raw_input.rpc_id(1, inputs, NetworkTime.tick) + _submit_raw_input.rpc_id(1, batched_inputs, NetworkTime.tick) +## Sends serialized batched inputs to all other players (not local!) func _attempt_submit_serialized_inputs(serialized_inputs: PackedByteArray): # TODO: Default to input broadcast in mesh network setups if enable_input_broadcast: - _submit_serialized_inputs.rpc(serialized_inputs) + for picked_peer_id in multiplayer.get_peers(): + _submit_serialized_inputs.rpc_id(picked_peer_id, serialized_inputs) elif not multiplayer.is_server(): _submit_serialized_inputs.rpc_id(1, serialized_inputs) @@ -265,11 +271,7 @@ func _get_history(buffer: Dictionary, tick: int) -> Dictionary: func _submit_serialized_inputs(serialized_inputs: PackedByteArray): var sender: int = multiplayer.get_remote_sender_id() - #If clients send input exclusively to server, yet we are a client and received an input - #This is a hacker sending RPCs (or someone called this RPC without adding the enable_input_broadcast boolean check) - if (enable_input_broadcast == false && not multiplayer.is_server() && sender != 1): - _logger.error("Received input from %s for %s from a client!" % [sender, root.name]) - return + #TODO: Security check to ensure no other client sent this (when enable_input_broadcast == false), see sanitization in submit_raw_inputs var picked_tick: int var picked_input_values_size: int #The size of the serialized input containing all properties (excluding tick timestamp[0,1,2,3] and the size itself on byte[4]) @@ -304,12 +306,6 @@ func _submit_serialized_inputs(serialized_inputs: PackedByteArray): func _submit_raw_input(input: Dictionary, tick: int): var sender: int = multiplayer.get_remote_sender_id() - #If clients send input exclusively to server, yet we are a client and received an input - #this is either a serious bug or a hacker sending RPCs. - if (enable_input_broadcast == false && not multiplayer.is_server() && sender != 1): - _logger.error("Received input from %s for tick %s for %s from a client!" % [sender, tick, root.name]) - return - var sanitized = {} for property in input: var pe: PropertyEntry = _property_cache.get_entry(property) diff --git a/examples/forest-brawl/scenes/brawler.tscn b/examples/forest-brawl/scenes/brawler.tscn index 65052f4..78e0f89 100644 --- a/examples/forest-brawl/scenes/brawler.tscn +++ b/examples/forest-brawl/scenes/brawler.tscn @@ -104,71 +104,71 @@ respawn_time = 8.0 transform = Transform3D(0.5, 0, 0, 0, 0.5, 0, 0, 0, 0.5, 0, -0.5, 0) [node name="Skeleton3D" parent="bomber-guy/rig" index="0"] -bones/0/position = Vector3(3.20746e-15, 0.541784, -0.0697341) +bones/0/position = Vector3(3.20746e-15, 0.60503, -0.0992486) bones/0/rotation = Quaternion(0.255292, -7.97688e-15, 1.00226e-14, 0.966864) -bones/1/rotation = Quaternion(-0.011695, 1.19201e-07, -1.39417e-09, 0.999932) -bones/1/scale = Vector3(1.01588, 0.968983, 1.01588) -bones/2/rotation = Quaternion(0.0208709, -1.97065e-13, 4.977e-09, 0.999782) -bones/2/scale = Vector3(1.04967, 0.907522, 1.04975) -bones/3/rotation = Quaternion(-0.120584, -1.18339e-07, -1.43747e-08, 0.992703) -bones/3/scale = Vector3(0.937799, 1.12689, 0.949998) -bones/4/rotation = Quaternion(0.340518, 1.12085e-07, 4.05929e-08, 0.940238) -bones/4/scale = Vector3(0.965644, 1.07243, 0.965644) -bones/5/rotation = Quaternion(-0.0858146, -1.54712e-09, -2.03464e-08, 0.996311) -bones/5/scale = Vector3(1.00005, 1.00385, 0.996972) -bones/6/rotation = Quaternion(-0.351208, -1.10132e-07, -4.12919e-08, 0.936297) -bones/6/scale = Vector3(1.03553, 0.978445, 0.992288) -bones/7/position = Vector3(-3.6176e-08, 0.541784, -0.0697341) +bones/1/rotation = Quaternion(0.0369096, 1.19128e-07, 4.39995e-09, 0.999319) +bones/1/scale = Vector3(1.02023, 0.960733, 1.02023) +bones/2/rotation = Quaternion(0.133518, -1.54454e-11, 3.19502e-08, 0.991046) +bones/2/scale = Vector3(1.04285, 0.915853, 1.0475) +bones/3/rotation = Quaternion(-0.210148, -1.16547e-07, -2.50516e-08, 0.97767) +bones/3/scale = Vector3(0.939869, 1.10246, 0.974404) +bones/4/rotation = Quaternion(0.388282, 1.09856e-07, 4.62868e-08, 0.921541) +bones/4/scale = Vector3(0.898952, 1.23746, 0.898952) +bones/5/rotation = Quaternion(-0.0722243, 5.88418e-15, -1.72434e-08, 0.997388) +bones/5/scale = Vector3(1.00011, 1.00806, 0.995661) +bones/6/rotation = Quaternion(-0.471208, -1.05145e-07, -5.61724e-08, 0.882022) +bones/6/scale = Vector3(1.11228, 1.03153, 0.909103) +bones/7/position = Vector3(-3.6176e-08, 0.60503, -0.0992486) bones/7/rotation = Quaternion(-0.135651, -0.571304, -0.59224, 0.551781) -bones/8/position = Vector3(3.6176e-08, 0.541784, -0.0697341) +bones/8/position = Vector3(3.6176e-08, 0.60503, -0.0992486) bones/8/rotation = Quaternion(-0.135651, 0.571304, 0.59224, 0.551781) -bones/9/position = Vector3(0.185305, 0.569821, -0.0143043) -bones/9/rotation = Quaternion(0.877117, -1.08073e-05, -0.000111774, 0.480277) -bones/9/scale = Vector3(1.00971, 0.980862, 1.00971) +bones/9/position = Vector3(0.185305, 0.633066, -0.0438188) +bones/9/rotation = Quaternion(0.921654, -2.87198e-05, -0.000121398, 0.388014) +bones/9/scale = Vector3(1.00469, 0.990691, 1.00469) bones/10/rotation = Quaternion(1.23085e-07, -5.52346e-06, -2.98159e-11, 1) -bones/11/rotation = Quaternion(0.503841, 8.94716e-07, -3.31607e-05, 0.863796) -bones/11/scale = Vector3(1.00194, 0.974335, 1.02468) +bones/11/rotation = Quaternion(0.304604, 9.69172e-06, -3.272e-05, 0.952479) +bones/11/scale = Vector3(1.00088, 0.993564, 1.00568) bones/12/rotation = Quaternion(-9.67563e-08, 8.29257e-06, -2.16274e-09, 1) -bones/13/rotation = Quaternion(-0.564447, 0.000110874, 1.18043e-05, 0.825469) -bones/13/scale = Vector3(0.988358, 0.993041, 1.01916) +bones/13/rotation = Quaternion(-0.465302, 0.000108395, 2.91561e-05, 0.885152) +bones/13/scale = Vector3(0.994763, 0.999183, 1.00621) bones/14/rotation = Quaternion(5.02433e-08, 0.977548, -0.210714, 2.33145e-07) bones/14/scale = Vector3(0.99948, 1.00078, 0.999746) -bones/15/position = Vector3(-0.185305, 0.569821, -0.0143043) -bones/15/rotation = Quaternion(0.996522, 4.44183e-05, -0.000130342, -0.0833352) -bones/15/scale = Vector3(1.00775, 0.984683, 1.00775) -bones/17/rotation = Quaternion(0.457802, 1.44218e-05, -3.92301e-05, 0.889054) -bones/17/scale = Vector3(1.00159, 0.980946, 1.01808) +bones/15/position = Vector3(-0.185305, 0.633066, -0.0438188) +bones/15/rotation = Quaternion(0.995731, 3.7712e-05, -0.000131178, -0.0923052) +bones/15/scale = Vector3(1.00555, 0.988986, 1.00555) +bones/17/rotation = Quaternion(0.345972, 1.4749e-05, -3.55751e-05, 0.938245) +bones/17/scale = Vector3(1.00105, 0.991033, 1.00813) bones/18/rotation = Quaternion(-4.84353e-08, 4.04099e-06, 1.10813e-08, 1) -bones/19/rotation = Quaternion(-0.904713, 8.56989e-05, 7.44614e-05, 0.426022) -bones/19/scale = Vector3(0.990459, 1.00203, 1.00799) +bones/19/rotation = Quaternion(-0.851383, 8.48038e-05, 8.06454e-05, 0.524545) +bones/19/scale = Vector3(0.993425, 0.997572, 1.0092) bones/20/rotation = Quaternion(1.02643e-08, 0.977516, -0.210861, -2.46051e-07) bones/20/scale = Vector3(1.00003, 0.999961, 1.00001) -bones/21/position = Vector3(0.0507796, 1.02088, 0.270898) -bones/21/rotation = Quaternion(-0.506586, -0.289463, -0.403316, 0.704924) -bones/22/position = Vector3(0.242455, 1.03042, 0.173756) -bones/22/rotation = Quaternion(-0.337003, 0.236679, -0.801477, 0.433643) -bones/22/scale = Vector3(1.1264, 0.788156, 1.1264) -bones/23/rotation = Quaternion(-4.53917e-08, 0.0561138, 6.12007e-08, 0.998424) -bones/24/rotation = Quaternion(0.567681, 0.169122, -0.423453, 0.685437) -bones/24/scale = Vector3(0.898858, 0.988916, 1.16062) -bones/25/rotation = Quaternion(-6.91384e-08, -0.0285117, -1.12731e-07, 0.999593) -bones/26/rotation = Quaternion(0.0667088, -0.0312487, 0.0431794, 0.996348) -bones/26/scale = Vector3(1.05288, 0.904574, 1.05123) -bones/27/position = Vector3(-0.0507796, 1.02088, 0.270898) -bones/27/rotation = Quaternion(-0.506586, 0.289463, 0.403316, 0.704924) -bones/28/position = Vector3(-0.242455, 1.03042, 0.173756) -bones/28/rotation = Quaternion(-0.448512, -0.384979, 0.618413, 0.517874) -bones/28/scale = Vector3(0.971869, 1.05873, 0.971869) -bones/29/rotation = Quaternion(9.63622e-08, -0.0084532, -2.07931e-07, 0.999964) -bones/30/rotation = Quaternion(0.246125, -0.0955487, 0.317189, 0.91087) -bones/30/scale = Vector3(1.13478, 0.778569, 1.13713) -bones/31/rotation = Quaternion(8.90478e-08, -0.132603, -4.37424e-08, 0.991169) -bones/32/rotation = Quaternion(0.407007, -0.125294, 0.260653, 0.866434) -bones/32/scale = Vector3(0.93686, 1.02215, 1.12698) -bones/33/position = Vector3(0.1184, 0.98805, 0.136473) -bones/33/rotation = Quaternion(-1.14663e-14, 0.583363, 0.812212, -1.96342e-14) -bones/34/position = Vector3(-0.1184, 0.98805, 0.136473) -bones/34/rotation = Quaternion(-1.14663e-14, 0.583363, 0.812212, -1.96342e-14) +bones/21/position = Vector3(0.0507796, 1.03223, 0.29458) +bones/21/rotation = Quaternion(-0.455527, -0.260251, -0.422755, 0.738947) +bones/22/position = Vector3(0.242455, 1.05538, 0.199789) +bones/22/rotation = Quaternion(-0.24149, 0.108644, -0.913742, 0.308146) +bones/22/scale = Vector3(1.03123, 0.940376, 1.03123) +bones/23/rotation = Quaternion(5.75631e-08, 0.0889995, 1.07611e-07, 0.996032) +bones/24/rotation = Quaternion(0.348138, 0.137697, -0.25666, 0.891047) +bones/24/scale = Vector3(0.998349, 0.975537, 1.03215) +bones/25/rotation = Quaternion(-3.55473e-08, -0.0265781, -1.32494e-07, 0.999647) +bones/26/rotation = Quaternion(0.183486, -0.0279029, 0.0710922, 0.980051) +bones/26/scale = Vector3(0.984554, 1.02616, 0.990426) +bones/27/position = Vector3(-0.0507796, 1.03223, 0.29458) +bones/27/rotation = Quaternion(-0.455527, 0.260251, 0.422755, 0.738947) +bones/28/position = Vector3(-0.242455, 1.05538, 0.199789) +bones/28/rotation = Quaternion(-0.394256, -0.343475, 0.741158, 0.421036) +bones/28/scale = Vector3(0.991389, 1.01745, 0.991389) +bones/29/rotation = Quaternion(2.26117e-07, -0.0429141, -5.33463e-08, 0.999079) +bones/30/rotation = Quaternion(0.190486, -0.0780955, 0.220591, 0.953392) +bones/30/scale = Vector3(1.03317, 0.937274, 1.03302) +bones/31/rotation = Quaternion(-7.70896e-08, -0.11097, 1.84362e-07, 0.993824) +bones/32/rotation = Quaternion(0.430097, -0.10788, 0.153512, 0.88307) +bones/32/scale = Vector3(0.975338, 1.00231, 1.02647) +bones/33/position = Vector3(0.1184, 1.01869, 0.156907) +bones/33/rotation = Quaternion(-1.42304e-14, 0.524533, 0.85139, -1.94525e-14) +bones/34/position = Vector3(-0.1184, 1.01869, 0.156907) +bones/34/rotation = Quaternion(-1.42304e-14, 0.524533, 0.85139, -1.94525e-14) [node name="AnimationTree" type="AnimationTree" parent="."] tree_root = SubResource("AnimationNodeBlendTree_8to0y") diff --git a/project.godot b/project.godot index fef3d28..76dec3d 100644 --- a/project.godot +++ b/project.godot @@ -112,7 +112,7 @@ aim_south={ general/clear_settings=false time/tickrate=24 -serialization/enable_input_serialization=false +serialization/enable_input_serialization=true [rendering] From 3809b308e3c567ef086702921c597c29a5672c35 Mon Sep 17 00:00:00 2001 From: Solomos Date: Tue, 27 Aug 2024 01:42:22 +0300 Subject: [PATCH 5/7] Applied PR 241 which changes RPC strings to static functions. --- addons/netfox.extras/weapon/network-weapon.gd | 10 +- addons/netfox/network-time-synchronizer.gd | 4 +- addons/netfox/network-time.gd | 2 +- .../netfox/rollback/rollback-synchronizer.gd | 2 +- addons/netfox/state-synchronizer.gd | 2 +- examples/forest-brawl/scenes/brawler.tscn | 110 +++++++++--------- .../forest-brawl/scripts/brawler-spawner.gd | 2 +- examples/forest-brawl/scripts/powerup.gd | 6 +- .../forest-brawl/scripts/score-manager.gd | 2 +- 9 files changed, 70 insertions(+), 70 deletions(-) diff --git a/addons/netfox.extras/weapon/network-weapon.gd b/addons/netfox.extras/weapon/network-weapon.gd index 3bf3753..764f757 100644 --- a/addons/netfox.extras/weapon/network-weapon.gd +++ b/addons/netfox.extras/weapon/network-weapon.gd @@ -30,9 +30,9 @@ func fire() -> Node: var data = _projectile_data[id] if not is_multiplayer_authority(): - rpc_id(get_multiplayer_authority(), "_request_projectile", id, NetworkTime.tick, data) + _request_projectile.rpc_id(get_multiplayer_authority(), id, NetworkTime.tick, data) else: - rpc("_accept_projectile", id, NetworkTime.tick, data) + _accept_projectile.rpc(id, NetworkTime.tick, data) _logger.debug("Calling after fire hook for %s" % [projectile.name]) _after_fire(projectile) @@ -143,7 +143,7 @@ func _request_projectile(id: String, tick: int, request_data: Dictionary): # Reject if sender can't use this input if not _can_peer_use(sender) or not _can_fire(): - rpc_id(sender, "_decline_projectile", id) + _decline_projectile.rpc_id(sender, id) _logger.error("Projectile %s rejected! Peer %s can't use this weapon now" % [id, sender]) return @@ -153,12 +153,12 @@ func _request_projectile(id: String, tick: int, request_data: Dictionary): if not _is_reconcilable(projectile, request_data, local_data): projectile.queue_free() - rpc_id(sender, "_decline_projectile", id) + _decline_projectile.rpc_id(sender, id) _logger.error("Projectile %s rejected! Can't reconcile states: [%s, %s]" % [id, request_data, local_data]) return _save_projectile(projectile, id, local_data) - rpc("_accept_projectile", id, tick, local_data) + _accept_projectile.rpc(id, tick, local_data) _after_fire(projectile) @rpc("authority", "reliable", "call_local") diff --git a/addons/netfox/network-time-synchronizer.gd b/addons/netfox/network-time-synchronizer.gd index 866f167..91706e5 100644 --- a/addons/netfox/network-time-synchronizer.gd +++ b/addons/netfox/network-time-synchronizer.gd @@ -106,7 +106,7 @@ func get_rtt(id: int, sample_id: int = -1) -> float: return 0 var trip_start = get_real_time() - rpc_id(id, "_request_ping") + _request_ping.rpc_id(id) var response = await on_ping var trip_end = get_real_time() var rtt = trip_end - trip_start @@ -138,7 +138,7 @@ func _sync_time_loop(interval: float): @rpc("any_peer", "reliable", "call_remote") func _request_ping(): var sender = multiplayer.get_remote_sender_id() - rpc_id(sender, "_respond_ping", NetworkTime.time, NetworkTime.tick) + _respond_ping.rpc_id(sender, NetworkTime.time, NetworkTime.tick) @rpc("any_peer", "reliable", "call_remote") func _respond_ping(peer_time: float, peer_tick: int): diff --git a/addons/netfox/network-time.gd b/addons/netfox/network-time.gd index ea018a5..aa1756f 100644 --- a/addons/netfox/network-time.gd +++ b/addons/netfox/network-time.gd @@ -307,7 +307,7 @@ func start(): _next_tick_time = _get_os_time() after_sync.emit() - rpc_id(1, "_submit_sync_success") + _submit_sync_success.rpc_id(1) else: _active = true _initial_sync_done = true diff --git a/addons/netfox/rollback/rollback-synchronizer.gd b/addons/netfox/rollback/rollback-synchronizer.gd index cc095dd..592ff6c 100644 --- a/addons/netfox/rollback/rollback-synchronizer.gd +++ b/addons/netfox/rollback/rollback-synchronizer.gd @@ -164,7 +164,7 @@ func _record_tick(tick: int): # Broadcast as new state _latest_state = max(_latest_state, tick) _states[tick] = PropertySnapshot.merge(_states.get(tick, {}), broadcast) - rpc("_submit_state", broadcast, tick) + _submit_state.rpc(broadcast, tick) # Record state for specified tick ( current + 1 ) if not _record_state_props.is_empty() and tick > _latest_state: diff --git a/addons/netfox/state-synchronizer.gd b/addons/netfox/state-synchronizer.gd index f892758..0f60eee 100644 --- a/addons/netfox/state-synchronizer.gd +++ b/addons/netfox/state-synchronizer.gd @@ -31,7 +31,7 @@ func _after_tick(_dt, tick): if is_multiplayer_authority(): # Submit snapshot var state = PropertySnapshot.extract(_props) - rpc("_submit_state", state, tick) + _submit_state.rpc(state, tick) else: # Apply last received state PropertySnapshot.apply(_last_received_state, _property_cache) diff --git a/examples/forest-brawl/scenes/brawler.tscn b/examples/forest-brawl/scenes/brawler.tscn index 78e0f89..a27eda1 100644 --- a/examples/forest-brawl/scenes/brawler.tscn +++ b/examples/forest-brawl/scenes/brawler.tscn @@ -104,71 +104,71 @@ respawn_time = 8.0 transform = Transform3D(0.5, 0, 0, 0, 0.5, 0, 0, 0, 0.5, 0, -0.5, 0) [node name="Skeleton3D" parent="bomber-guy/rig" index="0"] -bones/0/position = Vector3(3.20746e-15, 0.60503, -0.0992486) +bones/0/position = Vector3(3.20746e-15, 0.537386, -0.0676814) bones/0/rotation = Quaternion(0.255292, -7.97688e-15, 1.00226e-14, 0.966864) -bones/1/rotation = Quaternion(0.0369096, 1.19128e-07, 4.39995e-09, 0.999319) -bones/1/scale = Vector3(1.02023, 0.960733, 1.02023) -bones/2/rotation = Quaternion(0.133518, -1.54454e-11, 3.19502e-08, 0.991046) -bones/2/scale = Vector3(1.04285, 0.915853, 1.0475) -bones/3/rotation = Quaternion(-0.210148, -1.16547e-07, -2.50516e-08, 0.97767) -bones/3/scale = Vector3(0.939869, 1.10246, 0.974404) -bones/4/rotation = Quaternion(0.388282, 1.09856e-07, 4.62868e-08, 0.921541) -bones/4/scale = Vector3(0.898952, 1.23746, 0.898952) -bones/5/rotation = Quaternion(-0.0722243, 5.88418e-15, -1.72434e-08, 0.997388) -bones/5/scale = Vector3(1.00011, 1.00806, 0.995661) -bones/6/rotation = Quaternion(-0.471208, -1.05145e-07, -5.61724e-08, 0.882022) -bones/6/scale = Vector3(1.11228, 1.03153, 0.909103) -bones/7/position = Vector3(-3.6176e-08, 0.60503, -0.0992486) +bones/1/rotation = Quaternion(-0.0150382, 1.19196e-07, -1.79271e-09, 0.999887) +bones/1/scale = Vector3(1.01548, 0.969745, 1.01548) +bones/2/rotation = Quaternion(0.0130437, -5.39236e-13, 3.11013e-09, 0.999915) +bones/2/scale = Vector3(1.0489, 0.908889, 1.04892) +bones/3/rotation = Quaternion(-0.114269, -1.18428e-07, -1.36218e-08, 0.99345) +bones/3/scale = Vector3(0.938873, 1.1254, 0.949733) +bones/4/rotation = Quaternion(0.335856, 1.12285e-07, 4.00371e-08, 0.941914) +bones/4/scale = Vector3(0.969513, 1.06388, 0.969513) +bones/5/rotation = Quaternion(-0.086334, -4.83137e-10, -2.05533e-08, 0.996266) +bones/5/scale = Vector3(1.00005, 1.0037, 0.997062) +bones/6/rotation = Quaternion(-0.34101, -7.86727e-08, -2.82993e-08, 0.94006) +bones/6/scale = Vector3(1.0314, 0.978632, 0.994858) +bones/7/position = Vector3(-3.6176e-08, 0.537386, -0.0676814) bones/7/rotation = Quaternion(-0.135651, -0.571304, -0.59224, 0.551781) -bones/8/position = Vector3(3.6176e-08, 0.60503, -0.0992486) +bones/8/position = Vector3(3.6176e-08, 0.537386, -0.0676814) bones/8/rotation = Quaternion(-0.135651, 0.571304, 0.59224, 0.551781) -bones/9/position = Vector3(0.185305, 0.633066, -0.0438188) -bones/9/rotation = Quaternion(0.921654, -2.87198e-05, -0.000121398, 0.388014) -bones/9/scale = Vector3(1.00469, 0.990691, 1.00469) +bones/9/position = Vector3(0.185305, 0.565422, -0.0122516) +bones/9/rotation = Quaternion(0.874355, -9.67259e-06, -0.000111195, 0.485288) +bones/9/scale = Vector3(1.00986, 0.980567, 1.00986) bones/10/rotation = Quaternion(1.23085e-07, -5.52346e-06, -2.98159e-11, 1) -bones/11/rotation = Quaternion(0.304604, 9.69172e-06, -3.272e-05, 0.952479) -bones/11/scale = Vector3(1.00088, 0.993564, 1.00568) +bones/11/rotation = Quaternion(0.514318, 3.81979e-07, -3.31264e-05, 0.8576) +bones/11/scale = Vector3(1.00199, 0.973274, 1.02574) bones/12/rotation = Quaternion(-9.67563e-08, 8.29257e-06, -2.16274e-09, 1) -bones/13/rotation = Quaternion(-0.465302, 0.000108395, 2.91561e-05, 0.885152) -bones/13/scale = Vector3(0.994763, 0.999183, 1.00621) +bones/13/rotation = Quaternion(-0.569761, 0.000110982, 1.07574e-05, 0.821811) +bones/13/scale = Vector3(0.988044, 0.992639, 1.0199) bones/14/rotation = Quaternion(5.02433e-08, 0.977548, -0.210714, 2.33145e-07) bones/14/scale = Vector3(0.99948, 1.00078, 0.999746) -bones/15/position = Vector3(-0.185305, 0.633066, -0.0438188) -bones/15/rotation = Quaternion(0.995731, 3.7712e-05, -0.000131178, -0.0923052) -bones/15/scale = Vector3(1.00555, 0.988986, 1.00555) -bones/17/rotation = Quaternion(0.345972, 1.4749e-05, -3.55751e-05, 0.938245) -bones/17/scale = Vector3(1.00105, 0.991033, 1.00813) +bones/15/position = Vector3(-0.185305, 0.565422, -0.0122516) +bones/15/rotation = Quaternion(0.996533, 4.46413e-05, -0.000130315, -0.0832008) +bones/15/scale = Vector3(1.00783, 0.984523, 1.00783) +bones/17/rotation = Quaternion(0.463856, 1.44308e-05, -3.94679e-05, 0.885911) +bones/17/scale = Vector3(1.0016, 0.980611, 1.01841) bones/18/rotation = Quaternion(-4.84353e-08, 4.04099e-06, 1.10813e-08, 1) -bones/19/rotation = Quaternion(-0.851383, 8.48038e-05, 8.06454e-05, 0.524545) -bones/19/scale = Vector3(0.993425, 0.997572, 1.0092) +bones/19/rotation = Quaternion(-0.907716, 8.58194e-05, 7.42092e-05, 0.419586) +bones/19/scale = Vector3(0.990348, 1.00226, 1.00788) bones/20/rotation = Quaternion(1.02643e-08, 0.977516, -0.210861, -2.46051e-07) bones/20/scale = Vector3(1.00003, 0.999961, 1.00001) -bones/21/position = Vector3(0.0507796, 1.03223, 0.29458) -bones/21/rotation = Quaternion(-0.455527, -0.260251, -0.422755, 0.738947) -bones/22/position = Vector3(0.242455, 1.05538, 0.199789) -bones/22/rotation = Quaternion(-0.24149, 0.108644, -0.913742, 0.308146) -bones/22/scale = Vector3(1.03123, 0.940376, 1.03123) -bones/23/rotation = Quaternion(5.75631e-08, 0.0889995, 1.07611e-07, 0.996032) -bones/24/rotation = Quaternion(0.348138, 0.137697, -0.25666, 0.891047) -bones/24/scale = Vector3(0.998349, 0.975537, 1.03215) -bones/25/rotation = Quaternion(-3.55473e-08, -0.0265781, -1.32494e-07, 0.999647) -bones/26/rotation = Quaternion(0.183486, -0.0279029, 0.0710922, 0.980051) -bones/26/scale = Vector3(0.984554, 1.02616, 0.990426) -bones/27/position = Vector3(-0.0507796, 1.03223, 0.29458) -bones/27/rotation = Quaternion(-0.455527, 0.260251, 0.422755, 0.738947) -bones/28/position = Vector3(-0.242455, 1.05538, 0.199789) -bones/28/rotation = Quaternion(-0.394256, -0.343475, 0.741158, 0.421036) -bones/28/scale = Vector3(0.991389, 1.01745, 0.991389) -bones/29/rotation = Quaternion(2.26117e-07, -0.0429141, -5.33463e-08, 0.999079) -bones/30/rotation = Quaternion(0.190486, -0.0780955, 0.220591, 0.953392) -bones/30/scale = Vector3(1.03317, 0.937274, 1.03302) -bones/31/rotation = Quaternion(-7.70896e-08, -0.11097, 1.84362e-07, 0.993824) -bones/32/rotation = Quaternion(0.430097, -0.10788, 0.153512, 0.88307) -bones/32/scale = Vector3(0.975338, 1.00231, 1.02647) -bones/33/position = Vector3(0.1184, 1.01869, 0.156907) -bones/33/rotation = Quaternion(-1.42304e-14, 0.524533, 0.85139, -1.94525e-14) -bones/34/position = Vector3(-0.1184, 1.01869, 0.156907) -bones/34/rotation = Quaternion(-1.42304e-14, 0.524533, 0.85139, -1.94525e-14) +bones/21/position = Vector3(0.0507796, 1.02, 0.269173) +bones/21/rotation = Quaternion(-0.510056, -0.291449, -0.401883, 0.702417) +bones/22/position = Vector3(0.242455, 1.02872, 0.17204) +bones/22/rotation = Quaternion(-0.34156, 0.247502, -0.791825, 0.441693) +bones/22/scale = Vector3(1.12863, 0.785044, 1.12863) +bones/23/rotation = Quaternion(-4.93594e-08, 0.0514587, 6.53351e-08, 0.998675) +bones/24/rotation = Quaternion(0.582487, 0.171667, -0.434061, 0.665455) +bones/24/scale = Vector3(0.892652, 0.994075, 1.1617) +bones/25/rotation = Quaternion(-4.69706e-08, -0.0287977, -9.51187e-08, 0.999585) +bones/26/rotation = Quaternion(0.0585053, -0.0320021, 0.0405892, 0.996948) +bones/26/scale = Vector3(1.05804, 0.895392, 1.05678) +bones/27/position = Vector3(-0.0507796, 1.02, 0.269173) +bones/27/rotation = Quaternion(-0.510056, 0.291449, 0.401883, 0.702417) +bones/28/position = Vector3(-0.242455, 1.02872, 0.17204) +bones/28/rotation = Quaternion(-0.448703, -0.386923, 0.611271, 0.524694) +bones/28/scale = Vector3(0.970726, 1.06122, 0.970726) +bones/29/rotation = Quaternion(-4.81417e-08, -0.00444605, -2.67754e-07, 0.99999) +bones/30/rotation = Quaternion(0.244666, -0.0954331, 0.323971, 0.908886) +bones/30/scale = Vector3(1.14101, 0.769352, 1.14493) +bones/31/rotation = Quaternion(4.22013e-08, -0.133115, -5.85166e-08, 0.991101) +bones/32/rotation = Quaternion(0.407415, -0.126211, 0.26813, 0.863823) +bones/32/scale = Vector3(0.93742, 1.02232, 1.13593) +bones/33/position = Vector3(0.1184, 0.986027, 0.135132) +bones/33/rotation = Quaternion(-1.26153e-14, 0.587361, 0.809325, -2.04525e-14) +bones/34/position = Vector3(-0.1184, 0.986027, 0.135132) +bones/34/rotation = Quaternion(-1.26153e-14, 0.587361, 0.809325, -2.04525e-14) [node name="AnimationTree" type="AnimationTree" parent="."] tree_root = SubResource("AnimationNodeBlendTree_8to0y") diff --git a/examples/forest-brawl/scripts/brawler-spawner.gd b/examples/forest-brawl/scripts/brawler-spawner.gd index 2e116a0..154fc62 100644 --- a/examples/forest-brawl/scripts/brawler-spawner.gd +++ b/examples/forest-brawl/scripts/brawler-spawner.gd @@ -81,7 +81,7 @@ func _spawn(id: int) -> BrawlerController: # Submit name var player_name = name_input.text print("Submitting player name " + player_name) - rpc("_submit_name", player_name) + _submit_name.rpc(player_name) return avatar diff --git a/examples/forest-brawl/scripts/powerup.gd b/examples/forest-brawl/scripts/powerup.gd index e7c8881..5e5a9fd 100644 --- a/examples/forest-brawl/scripts/powerup.gd +++ b/examples/forest-brawl/scripts/powerup.gd @@ -19,14 +19,14 @@ func _tick(delta, tick): if body.is_in_group("Brawlers") and not _has_powerup(body): _take() # Predict if is_multiplayer_authority(): - rpc("_spawn_effect", randi_range(0, effects.size() - 1), body.get_path()) - rpc("_take") + _spawn_effect.rpc(randi_range(0, effects.size() - 1), body.get_path()) + _take.rpc() else: scale = scale.lerp(Vector3.ONE * 0.0005, fade_speed * delta) if tick == respawn_tick: _respawn() # Predict if is_multiplayer_authority(): - rpc("_respawn") + _respawn.rpc() func _has_powerup(target: Node) -> bool: return target.get_children()\ diff --git a/examples/forest-brawl/scripts/score-manager.gd b/examples/forest-brawl/scripts/score-manager.gd index 92fa19c..1b09f16 100644 --- a/examples/forest-brawl/scripts/score-manager.gd +++ b/examples/forest-brawl/scripts/score-manager.gd @@ -34,7 +34,7 @@ func _handle_fall(brawler: BrawlerController): _scores[id] = _scores.get(id, 0) - 1 GameEvents.on_scores_updated.emit(_scores) - rpc("_submit_scores", _scores) + _submit_scores.rpc(_scores) # Display scoreboard if id == multiplayer.get_unique_id(): From 8175e6b60de8012f6a93cf4669315bf299154e62 Mon Sep 17 00:00:00 2001 From: Solomos Date: Wed, 28 Aug 2024 23:32:52 +0300 Subject: [PATCH 6/7] Added input delay (and added some static typing here and there) --- addons/netfox/properties/property-entry.gd | 8 +++ addons/netfox/properties/property-snapshot.gd | 2 +- addons/netfox/rollback/network-rollback.gd | 10 +-- .../netfox/rollback/rollback-synchronizer.gd | 64 ++++++++++++------- .../scripts/brawler-controller.gd | 10 +-- 5 files changed, 60 insertions(+), 34 deletions(-) diff --git a/addons/netfox/properties/property-entry.gd b/addons/netfox/properties/property-entry.gd index 05cbe48..f90c0db 100644 --- a/addons/netfox/properties/property-entry.gd +++ b/addons/netfox/properties/property-entry.gd @@ -31,3 +31,11 @@ static func parse(root: Node, path: String) -> PropertyEntry: result._path = path result.type = typeof(result.get_value()) return result + +## This is the only property which doesnt have a node. +#static func get_tick_entry() -> PropertyEntry: + #var tick_property_entry: PropertyEntry = PropertyEntry.new() + #tick_property_entry.property = "tick" + #tick_property_entry._path = "tick" + #tick_property_entry.type = TYPE_INT + #return tick_property_entry diff --git a/addons/netfox/properties/property-snapshot.gd b/addons/netfox/properties/property-snapshot.gd index 05d53f9..500332d 100644 --- a/addons/netfox/properties/property-snapshot.gd +++ b/addons/netfox/properties/property-snapshot.gd @@ -10,7 +10,7 @@ static func extract(properties: Array[PropertyEntry]) -> Dictionary: static func apply(properties: Dictionary, cache: PropertyCache): for property in properties: - var pe = cache.get_entry(property) + var pe: PropertyEntry = cache.get_entry(property) var value = properties[property] pe.set_value(value) diff --git a/addons/netfox/rollback/network-rollback.gd b/addons/netfox/rollback/network-rollback.gd index a5aa15c..0b12201 100644 --- a/addons/netfox/rollback/network-rollback.gd +++ b/addons/netfox/rollback/network-rollback.gd @@ -75,7 +75,7 @@ signal on_record_tick(tick: int) signal after_loop() var _tick: int = 0 -var _resim_from: int +var _resimulate_from_tick: int var _is_rollback: bool = false var _simulated_nodes: Dictionary = {} @@ -84,7 +84,7 @@ var _simulated_nodes: Dictionary = {} ## ## This is used to determine the resimulation range during each loop. func notify_resimulation_start(tick: int): - _resim_from = min(_resim_from, tick) + _resimulate_from_tick = min(_resimulate_from_tick, tick) ## Submit node for simulation. ## @@ -138,14 +138,14 @@ func _rollback(): _is_rollback = true # Ask all rewindables to submit their earliest inputs - _resim_from = NetworkTime.tick + _resimulate_from_tick = NetworkTime.tick before_loop.emit() # from = Earliest input amongst all rewindables - var from = _resim_from + var from: int = _resimulate_from_tick # to = Current tick - var to = NetworkTime.tick + var to: int = NetworkTime.tick # for tick in from .. to: for tick in range(from, to): diff --git a/addons/netfox/rollback/rollback-synchronizer.gd b/addons/netfox/rollback/rollback-synchronizer.gd index 592ff6c..52bda6f 100644 --- a/addons/netfox/rollback/rollback-synchronizer.gd +++ b/addons/netfox/rollback/rollback-synchronizer.gd @@ -14,6 +14,11 @@ class_name RollbackSynchronizer ## Turning this off is recommended to save bandwidth and reduce cheating risks. @export var enable_input_broadcast: bool = true +## This is measured in ticks. So if your game's tickrate (project settings > Time) is 60 ticks, 4 ticks are 68 ms (bad but acceptable) +## If your game's tickrate is 30 ticks, 4 ticks are 134 ms (laggy) +## The lesser the value, the tighter the controls feel locally, but the less time for other players to catch up to this node's input +@export var input_delay: int = 0 + var _record_state_props: Array[PropertyEntry] = [] var _record_input_props: Array[PropertyEntry] = [] @@ -48,10 +53,10 @@ func process_settings(): _inputs.clear() _latest_state = NetworkTime.tick - 1 _earliest_input = NetworkTime.tick - + # Gather state props - all state props are recorded for property in state_properties: - var pe = _property_cache.get_entry(property) + var pe: PropertyEntry = _property_cache.get_entry(property) _record_state_props.push_back(pe) process_authority() @@ -74,19 +79,20 @@ func process_authority(): # Gather state properties that we own # i.e. it's the state of a node that belongs to the local peer - for property in state_properties: - var pe: PropertyEntry = _property_cache.get_entry(property) - if pe.node.is_multiplayer_authority(): - _auth_state_props.push_back(pe) + var picked_property_entry: PropertyEntry + for picked_property in state_properties: + picked_property_entry = _property_cache.get_entry(picked_property) + if picked_property_entry.node.is_multiplayer_authority(): + _auth_state_props.push_back(picked_property_entry) # Gather input properties that we own # Only record input that is our own - for property in input_properties: - var pe = _property_cache.get_entry(property) - if pe.node.is_multiplayer_authority(): - _auth_input_props.push_back(pe) - else: - _record_input_props.push_back(pe) + for picked_property in input_properties: + picked_property_entry = _property_cache.get_entry(picked_property) + _record_input_props.push_back(picked_property_entry) + if picked_property_entry.node.is_multiplayer_authority(): + _auth_input_props.push_back(picked_property_entry) + func _ready(): process_settings() @@ -96,8 +102,15 @@ func _ready(): await NetworkTime.after_sync _latest_state = NetworkTime.tick - 1 + #dummy states to parse for first inputs before our first real input + #The code: PropertySnapshot.apply(input, _property_cache) -> does literally nothing if input == {} + #So the default input stays in its default values for the first input delay ticks. + for i in input_delay: + _inputs[NetworkTime.tick + i] = {} + NetworkTime.before_tick.connect(_before_tick) NetworkTime.after_tick.connect(_after_tick) + NetworkRollback.before_loop.connect(_before_loop) NetworkRollback.on_prepare_tick.connect(_prepare_tick) NetworkRollback.on_process_tick.connect(_process_tick) @@ -116,8 +129,8 @@ func _prepare_tick(tick: int): # Prepare state # Done individually by Rewindables ( usually Rollback Synchronizers ) # Restore input and state for tick - var state = _get_history(_states, tick) - var input = _get_history(_inputs, tick) + var state: Dictionary = _get_history(_states, tick) + var input: Dictionary = _get_history(_inputs, tick) PropertySnapshot.apply(state, _property_cache) PropertySnapshot.apply(input, _property_cache) @@ -146,7 +159,7 @@ func _process_tick(tick: int): # If not: Latest input >= tick >= Earliest input for node in _nodes: if NetworkRollback.is_simulated(node): - var is_fresh = _freshness_store.is_fresh(node, tick) + var is_fresh: bool = _freshness_store.is_fresh(node, tick) NetworkRollback.process_rollback(node, NetworkTime.ticktime, tick, is_fresh) _freshness_store.notify_processed(node, tick) @@ -173,22 +186,23 @@ func _record_tick(tick: int): func _after_loop(): _earliest_input = NetworkTime.tick # Apply display state - var display_state = _get_history(_states, NetworkTime.tick - NetworkRollback.display_offset) + var display_state: Dictionary = _get_history(_states, NetworkTime.tick - NetworkRollback.display_offset) PropertySnapshot.apply(display_state, _property_cache) -func _before_tick(_delta, tick): +func _before_tick(_delta: float, tick: int): # Apply state for tick - var state = _get_history(_states, tick) + var state: Dictionary = _get_history(_states, tick) PropertySnapshot.apply(state, _property_cache) func _after_tick(_delta: float, _tick: int): if not _auth_input_props.is_empty(): var local_input: Dictionary = PropertySnapshot.extract(_auth_input_props) - _inputs[_tick] = local_input + var delayed_input_tick: int = _tick + input_delay + _inputs[delayed_input_tick] = local_input if (NetworkRollback.enable_input_serialization): - var serialized_current_input: PackedByteArray = PropertiesSerializer.serialize_multiple_properties(_auth_input_props, _tick) - _serialized_inputs[_tick] = serialized_current_input + var serialized_current_input: PackedByteArray = PropertiesSerializer.serialize_multiple_properties(_auth_input_props, delayed_input_tick) + _serialized_inputs[delayed_input_tick] = serialized_current_input if (serialized_inputs_to_send.size() == NetworkRollback.input_redundancy): serialized_inputs_to_send.remove_at(0) @@ -205,7 +219,7 @@ func _after_tick(_delta: float, _tick: int): #Send the last n inputs for each property var inputs = {} for i in range(0, NetworkRollback.input_redundancy): - var tick_input: Dictionary = _inputs.get(_tick - i, {}) + var tick_input: Dictionary = _inputs.get(delayed_input_tick - i, {}) for property in tick_input: if not inputs.has(property): inputs[property] = [] @@ -255,13 +269,17 @@ func _get_history(buffer: Dictionary, tick: int) -> Dictionary: var earliest = buffer.keys().min() var latest = buffer.keys().max() + if tick < earliest: return buffer[earliest] + #For inputs, this is extrapolation aka prediction + #For example, if you move to the right (at latest) + #at tick, you will still be moving to the right ;) if tick > latest: return buffer[latest] - var before = buffer.keys() \ + var before: int = buffer.keys() \ .filter(func (key): return key < tick) \ .max() diff --git a/examples/forest-brawl/scripts/brawler-controller.gd b/examples/forest-brawl/scripts/brawler-controller.gd index a0e01e6..8b5abde 100644 --- a/examples/forest-brawl/scripts/brawler-controller.gd +++ b/examples/forest-brawl/scripts/brawler-controller.gd @@ -67,19 +67,19 @@ func _ready(): func _process(delta): # Update animation # Running - var movement = Vector3(velocity.x, 0, velocity.z) * speed + var movement: Vector3 = 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 + var animated_velocity: Vector2 = 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 + var animated_float: float = animation_tree.get("parameters/Float/blend_amount") as float + var actual_float: 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 @@ -91,7 +91,7 @@ func _tick(_delta, tick): if weapon.last_fire == tick: animation_tree.set("parameters/Throw/request", AnimationNodeOneShot.ONE_SHOT_REQUEST_FIRE) -func _rollback_tick(delta, tick, is_fresh): +func _rollback_tick(delta: float, tick: int, is_fresh: bool): # Respawn if tick == respawn_tick: _snap_to_spawn() From 6e30029b7c27482c3d6cf2bb42a7e4baea3f85e3 Mon Sep 17 00:00:00 2001 From: Solomos Date: Thu, 29 Aug 2024 01:33:37 +0300 Subject: [PATCH 7/7] fixed input delay for non-serialized input --- addons/netfox/rollback/rollback-synchronizer.gd | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/addons/netfox/rollback/rollback-synchronizer.gd b/addons/netfox/rollback/rollback-synchronizer.gd index 52bda6f..ac8bfdd 100644 --- a/addons/netfox/rollback/rollback-synchronizer.gd +++ b/addons/netfox/rollback/rollback-synchronizer.gd @@ -225,7 +225,7 @@ func _after_tick(_delta: float, _tick: int): inputs[property] = [] inputs[property].push_back(tick_input[property]) - _attempt_submit_raw_input(inputs) + _attempt_submit_raw_input(inputs, delayed_input_tick) history_cleanup() func history_cleanup() -> void: @@ -242,13 +242,13 @@ func history_cleanup() -> void: _freshness_store.trim() ## Sends batched inputs to all other players (not local!) -func _attempt_submit_raw_input(batched_inputs: Dictionary): +func _attempt_submit_raw_input(batched_inputs: Dictionary, tick: int): # TODO: Default to input broadcast in mesh network setups if enable_input_broadcast: for picked_peer_id in multiplayer.get_peers(): - _submit_raw_input.rpc_id(picked_peer_id, batched_inputs, NetworkTime.tick) + _submit_raw_input.rpc_id(picked_peer_id, batched_inputs, tick) elif not multiplayer.is_server(): - _submit_raw_input.rpc_id(1, batched_inputs, NetworkTime.tick) + _submit_raw_input.rpc_id(1, batched_inputs, tick) ## Sends serialized batched inputs to all other players (not local!) func _attempt_submit_serialized_inputs(serialized_inputs: PackedByteArray):