Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Static typings #277

Closed
2 changes: 1 addition & 1 deletion addons/netfox.extras/plugin.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
name="netfox.extras"
description="Game-specific utilities for Netfox"
author="Tamas Galffy"
version="1.8.3"
version="1.8.4"
script="netfox-extras.gd"
2 changes: 1 addition & 1 deletion addons/netfox.internals/plugin.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
name="netfox.internals"
description="Shared internals for netfox addons"
author="Tamas Galffy"
version="1.8.3"
version="1.8.4"
script="plugin.gd"
2 changes: 1 addition & 1 deletion addons/netfox.noray/plugin.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
name="netfox.noray"
description="Bulletproof your connectivity with noray integration for netfox"
author="Tamas Galffy"
version="1.8.3"
version="1.8.4"
script="netfox-noray.gd"
2 changes: 1 addition & 1 deletion addons/netfox/plugin.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
name="netfox"
description="Shared internals for netfox addons"
author="Tamas Galffy"
version="1.8.3"
version="1.8.4"
script="netfox.gd"
2 changes: 1 addition & 1 deletion addons/netfox/properties/property-cache.gd
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions addons/netfox/properties/property-snapshot.gd
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ 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 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)

Expand Down
10 changes: 5 additions & 5 deletions addons/netfox/rollback/network-rollback.gd
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,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 = {}
Expand All @@ -82,7 +82,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.
##
Expand Down Expand Up @@ -136,14 +136,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):
Expand Down
94 changes: 50 additions & 44 deletions addons/netfox/rollback/rollback-synchronizer.gd
Original file line number Diff line number Diff line change
Expand Up @@ -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

var _record_state_props: Array[PropertyEntry] = []
Expand Down Expand Up @@ -48,7 +48,7 @@ func process_settings():

# 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()
Expand All @@ -71,18 +71,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 = _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():
_record_input_props.push_back(pe)
_auth_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)
Copy link
Contributor Author

@TheYellowArchitect TheYellowArchitect Sep 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that _record_input_props is set here outside authority. Solves #250 and its tested to work locally, as intended in all my other PRs. After all, it is not set anywhere if not here, and before this commit, _record_input_props is the same as _auth_input_props for exclusively the authority player, which makes _record_input_props obsolete to _auth_input_props.

And as mentioned elsewhere, this change is how _auth_state_props and _record_state_props works, so I see this as a bugfix

if picked_property_entry.node.is_multiplayer_authority():
_auth_input_props.push_back(picked_property_entry)


func _ready():
process_settings()
Expand Down Expand Up @@ -112,8 +114,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)
Expand Down Expand Up @@ -142,25 +144,25 @@ 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)

func _record_tick(tick: int):
# Broadcast state we own
if not _auth_state_props.is_empty():
var broadcast = {}
var state_to_broadcast: Dictionary = {}

for property in _auth_state_props:
if _can_simulate(property.node, tick - 1):
# Only broadcast if we've simulated the node
broadcast[property.to_string()] = property.get_value()
state_to_broadcast[property.to_string()] = property.get_value()

if broadcast.size() > 0:
if state_to_broadcast.size() > 0:
# Broadcast as new state
_latest_state = max(_latest_state, tick)
_states[tick] = PropertySnapshot.merge(_states.get(tick, {}), broadcast)
_submit_state.rpc(broadcast, tick)
_states[tick] = PropertySnapshot.merge(_states.get(tick, {}), state_to_broadcast)
_submit_state.rpc(state_to_broadcast, tick)

# Record state for specified tick ( current + 1 )
if not _record_state_props.is_empty() and tick > _latest_state:
Expand All @@ -170,21 +172,21 @@ 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, _tick):
func _after_tick(_delta: float, _tick: int):
if not _auth_input_props.is_empty():
var input = PropertySnapshot.extract(_auth_input_props)
_inputs[NetworkTime.tick] = input

#Send the last n inputs for each property
var inputs = {}
var inputs: Dictionary = {}
for i in range(0, NetworkRollback.input_redundancy):
var tick_input = _inputs.get(NetworkTime.tick - i, {})
for property in tick_input:
Expand All @@ -202,12 +204,13 @@ func _after_tick(_delta, _tick):

_freshness_store.trim()

func _attempt_submit_input(input: Dictionary):
## Sends batched inputs to all other players (not local!)
func _attempt_submit_input(batched_inputs: Dictionary):
# TODO: Default to input broadcast in mesh network setups
if enable_input_broadcast:
_submit_input.rpc(input, NetworkTime.tick)
_submit_input.rpc(batched_inputs, NetworkTime.tick)
elif not multiplayer.is_server():
_submit_input.rpc_id(1, input, NetworkTime.tick)
_submit_input.rpc_id(1, batched_inputs, NetworkTime.tick)

func _get_history(buffer: Dictionary, tick: int) -> Dictionary:
if buffer.has(tick):
Expand All @@ -216,33 +219,36 @@ func _get_history(buffer: Dictionary, tick: int) -> Dictionary:
if buffer.is_empty():
return {}

var earliest = buffer.keys().min()
var latest = buffer.keys().max()
var earliest: int = buffer.keys().min()
var latest: int = 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()

return buffer[before]

@rpc("any_peer", "unreliable", "call_remote")
func _submit_input(input: Dictionary, tick: int):
var sender = multiplayer.get_remote_sender_id()
var sanitized = {}
var sender_id: int = multiplayer.get_remote_sender_id()
var sanitized: Dictionary = {}
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_id: int = pe.node.get_multiplayer_authority()

if input_owner != sender:
if input_owner_id != sender_id:
_logger.warning("Received input for node owned by %s from %s, sender has no authority!" \
% [input_owner, sender])
% [input_owner_id, sender_id])
continue

sanitized[property] = value
Expand All @@ -260,7 +266,7 @@ func _submit_input(input: Dictionary, tick: int):
_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])
_logger.warning("Received invalid input from %s for tick %s for %s" % [sender_id, tick, root.name])

@rpc("any_peer", "unreliable_ordered", "call_remote")
func _submit_state(state: Dictionary, tick: int):
Expand All @@ -274,16 +280,16 @@ func _submit_state(state: Dictionary, tick: int):
_logger.error("Received state for %s, rejecting because older than %s frames" % [tick, NetworkRollback.history_limit])
return

var sender = multiplayer.get_remote_sender_id()
var sanitized = {}
var sender_id: int = multiplayer.get_remote_sender_id()
var sanitized: Dictionary = {}
for property in state:
var pe = _property_cache.get_entry(property)
var pe: PropertyEntry = _property_cache.get_entry(property)
var value = state[property]
var state_owner = pe.node.get_multiplayer_authority()
var state_owner_id: int = pe.node.get_multiplayer_authority()

if state_owner != sender:
if state_owner_id != sender_id:
_logger.warning("Received state for node owned by %s from %s, sender has no authority!" \
% [state_owner, sender])
% [state_owner_id, sender_id])
continue

sanitized[property] = value
Expand All @@ -293,4 +299,4 @@ func _submit_state(state: Dictionary, tick: int):
# _latest_state = max(_latest_state, tick)
_latest_state = tick
else:
_logger.warning("Received invalid state from %s for tick %s" % [sender, tick])
_logger.warning("Received invalid state from %s for tick %s" % [sender_id, tick])
24 changes: 12 additions & 12 deletions examples/forest-brawl/scripts/brawler-controller.gd
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ extends CharacterBody3D
class_name BrawlerController

# Stats
@export var speed = 5.0
@export var jump_velocity = 4.5
@export var speed: float = 5.0
@export var jump_velocity: float = 4.5

# Spawn
@export var spawn_point: Vector3 = Vector3(0, 4, 0)
Expand Down Expand Up @@ -67,31 +67,31 @@ 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
animation_tree.set("parameters/MoveScale/scale", speed / 3.75)
animation_tree.set("parameters/ThrowScale/scale", min(weapon.fire_cooldown / (10. / 24.), 1.0))

func _tick(_delta, tick):
func _tick(_delta: float, tick: int):
# Run throw animation if firing
if weapon.last_fire == tick:
animation_tree.set("parameters/Throw/request", AnimationNodeOneShot.ONE_SHOT_REQUEST_FIRE)

func _rollback_tick(delta, tick, is_fresh):
func _rollback_tick(delta: float, tick: int, is_fresh: bool):
# Respawn
if tick == respawn_tick:
_snap_to_spawn()
Expand Down Expand Up @@ -142,14 +142,14 @@ func _exit_tree():
GameEvents.on_brawler_despawn.emit(self)

func _snap_to_spawn():
var spawns = get_tree().get_nodes_in_group("Spawn Points")
var idx = hash(player_id + respawn_count * 39) % spawns.size()
var spawn = spawns[idx] as Node3D
var spawns: Array[Node] = get_tree().get_nodes_in_group("Spawn Points")
var idx: int = hash(player_id + respawn_count * 39) % spawns.size()
var spawn: Node3D = spawns[idx] as Node3D

global_transform = spawn.global_transform

func _force_update_is_on_floor():
var old_velocity = velocity
var old_velocity: Vector3 = velocity
velocity *= 0
move_and_slide()
velocity = old_velocity
4 changes: 2 additions & 2 deletions examples/forest-brawl/scripts/brawler-crown.gd
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ func _handle_scores(scores: Dictionary):
if max_players.size() > 1:
return

var player_id = max_players[0]
var player = get_tree().get_nodes_in_group("Brawlers")\
var player_id: int = max_players[0]
var player: BrawlerController = get_tree().get_nodes_in_group("Brawlers")\
.filter(func(it): return it.player_id == player_id)\
.pop_back()

Expand Down
Loading