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

doc: Add NetworkWeapon guide #118

Merged
merged 2 commits into from
Nov 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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="0.15.12"
version="0.15.13"
script="netfox-extras.gd"
8 changes: 0 additions & 8 deletions addons/netfox.extras/weapon/network-weapon-2d.gd
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,6 @@ extends Node2D
class_name NetworkWeapon2D

## A 2D-specific implementation of [NetworkWeapon].
##
## Manages reconciliation of projectiles, so the only thing required by you is
## to spawn projectiles.
##
## [i]Note:[/i] Since a single Godot class can't extend two classes, under the
## hood a special [NetworkWeapon] instance is created and added as internal
## child, with calls being proxied to and from it.

## Distance to consider too large during reconciliation checks.
@export var distance_threshold: float = 0.1
Expand All @@ -22,7 +15,6 @@ func fire() -> Node2D:
return _weapon.fire()

func _init():
print("Ready!")
_weapon = _NetworkWeaponProxy.new()
add_child(_weapon, true, INTERNAL_MODE_BACK)
_weapon.owner = self
Expand Down
8 changes: 0 additions & 8 deletions addons/netfox.extras/weapon/network-weapon-3d.gd
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,6 @@ extends Node3D
class_name NetworkWeapon3D

## A 3D-specific implementation of [NetworkWeapon].
##
## Manages reconciliation of projectiles, so the only thing required by you is
## to spawn projectiles.
##
## [i]Note:[/i] Since a single Godot class can't extend two classes, under the
## hood a special [NetworkWeapon] instance is created and added as internal
## child, with calls being proxied to and from it.

## Distance to consider too large during reconciliation checks.
@export var distance_threshold: float = 0.1
Expand All @@ -22,7 +15,6 @@ func fire() -> Node3D:
return _weapon.fire()

func _init():
print("Ready!")
_weapon = _NetworkWeaponProxy.new()
add_child(_weapon, true, INTERNAL_MODE_BACK)
_weapon.owner = self
Expand Down
18 changes: 2 additions & 16 deletions addons/netfox.extras/weapon/network-weapon.gd
Original file line number Diff line number Diff line change
@@ -1,22 +1,8 @@
extends Node
class_name NetworkWeapon

## This class lets you create responsive weapons by creating the projectiles
## as soon as the weapon is fired, but keeping all the control on the server.
##
## To achieve this, both the server and the clients create projectiles which are
## free to implement their behaviour, but should only affect game state on the
## server.
##
## To avoid situations where the server and clients are not on the same page,
## clients spawn their projectiles but also notify the server. From there, the
## server can either reject the projectile ( maybe the weapon was still on
## cooldown ) or accept it. Upon acceptance, the host will broadcast the
## projectile's state. On the firing client, the state will be reconciled with
## the one the client produced and the one from the server - maybe the player
## was in a slightly different position on the server when firing. The other
## clients will simply create the projectile with the state received from the
## server.
## Base class for creating responsive weapons, by spawning projectiles locally,
## but keeping control on the server.

var _projectiles: Dictionary = {}
var _projectile_data: Dictionary = {}
Expand Down
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="0.15.12"
version="0.15.13"
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="A higher-level networking addon with rollback support"
author="Tamas Galffy"
version="0.15.12"
version="0.15.13"
script="netfox.gd"
87 changes: 87 additions & 0 deletions docs/netfox.extras/guides/network-weapon.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# NetworkWeapon

Class to simplify writing networked weapons.

A weapon, in this context, is anything that can be fired and spawn objects (
projectiles ) upon being fired.

## Responsive projectiles

Upon firing, sending a request to the server and waiting for the response with
the projectile would introduce a delay. Doing a full-on state synchronization
with [MultiplayerSynchronizer] or [RollbackSynchronizer] can be unfeasible with
too many projectiles, and unnecessary, since most of the time, projectiles act
and move the same way regardless of their surroundings.

Instead, upon firing, a projectile is spawned instantly. At the same time, a
request is sent to the server. If the server accepts the projectile, it will
spawn it and broadcasts its starting state. Since the server's state is the
source of truth, the projectile's local state will be updated with the
difference. This is called *reconciliation*.

If the client requests a projectile with an unlikely state, it will be
rejected. This is to avoid players cheating, for example by requesting
projectiles at a more advantageous position than they're at.

If the server is too strict with what difference is considered acceptable and
what not, legitimate players may get cases where they fire a projectile which
disappears after a short time period.

## Implementing a weapon

*NetworkWeapon* provides multiple functions to override. Make sure that all
these methods work the same way on every player's game, otherwise players will
experience glitches.

*_can_fire* returns a bool, indicating whether the weapon can be fired. For
example, this method can return false if the weapon was fired recently and is
still on cooldown. **Do not** update state here. Use *_after_fire* instead.

*_can_peer_use* indicates whether a given peer can fire the weapon. Due to the
way RPCs are set up under the hood, any of the players can try to fire a
weapon. Use this method to check if the player trying to fire has permission,
e.g. a player is not trying to use someone else's weapon.

*_after_fire* is called after the weapon is successfully fired. Can be used to
update state ( e.g. last time the weapon was fired ) and play sound effects.

*_spawn* creates the projectile. Make sure to return the created node.

*_get_data* must return the projectile's starting state in a dictionary. This
can contain any property that is relevant to the projectile and must be
synchronized. For example, *global_transform* is important to ensure that the
projectile starts from the right position. On the other hand, projectile speed
does not need to be captured if it's the same for every projectile.

*_apply_data* must apply the captured properties to a projectile.

*_is_reconcilable* checks if the different between two projectile states ( as
captured by *_get_data* ) is close enough to be allowed. Can be used to reject
cheating.

*_reconcile* adjusts the projectile based on the difference between the local
and server state.

## Specializations

*NetworkWeapon* extends [Node]. This also means that anything extending
*NetworkWeapon* is also a node, and thus can't have a position for example.

Two specialized classes are provided - *NetworkWeapon3D*, and *NetworkWeapon2D*
- extending Node3D and Node2D respectively.

This way, weapons can have transforms and have a presence in the game world.
They also take care of reconciliation, implementing *_get_data*, *_apply_data*,
*_is_reconcilable*, and *_reconcile*. These can be overridden, but make sure to
to call the base class with *super(...)*.

Reconciliation is based on distance, and can be configured with the
*distance_threshold* property.

Under the hood, these specializations create a special *NetworkWeapon* node,
that proxies all the method calls back to the specialization. This is a
workaround to build multiple inheritance in a single inheritance language.

[MultiplayerSynchronizer]: https://docs.godotengine.org/en/stable/classes/class_multiplayersynchronizer.html
[RollbackSynchronizer]: ../../netfox/nodes/rollback-synchronizer.md
[Node]: https://docs.godotengine.org/en/stable/classes/class_node.html
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ nav:
- netfox.extras:
- Guides:
- 'netfox.extras/guides/base-net-input.md'
- 'netfox.extras/guides/network-weapon.md'