Add host-authoritative multiplayer fishing
This commit is contained in:
parent
b0e2e84ba4
commit
d3e3bf4053
17 changed files with 1672 additions and 23 deletions
|
|
@ -11,7 +11,7 @@ func start_host(
|
|||
var enet_peer := ENetMultiplayerPeer.new()
|
||||
if not bind_address.is_empty() and bind_address != "*":
|
||||
enet_peer.set_bind_ip(bind_address)
|
||||
var error: Error = enet_peer.create_server(port, max_clients, 3)
|
||||
var error: Error = enet_peer.create_server(port, max_clients, 5)
|
||||
if error != OK:
|
||||
transport_error.emit("Unable to host UDP port %d." % port)
|
||||
return error
|
||||
|
|
@ -27,7 +27,7 @@ func connect_to_route(route: ConnectionRoute) -> Error:
|
|||
return ERR_INVALID_PARAMETER
|
||||
var endpoint: ConnectionEndpoint = route.direct_endpoint
|
||||
var enet_peer := ENetMultiplayerPeer.new()
|
||||
var error: Error = enet_peer.create_client(endpoint.host, endpoint.port, 3)
|
||||
var error: Error = enet_peer.create_client(endpoint.host, endpoint.port, 5)
|
||||
if error != OK:
|
||||
transport_error.emit(
|
||||
"Unable to connect to %s." % endpoint.normalized_display
|
||||
|
|
|
|||
38
network/network_fishing_attempt.gd
Normal file
38
network/network_fishing_attempt.gd
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
class_name NetworkFishingAttempt
|
||||
extends RefCounted
|
||||
|
||||
enum Phase {
|
||||
CASTING,
|
||||
WAITING_FOR_BITE,
|
||||
FIGHTING,
|
||||
PENDING_CAPACITY,
|
||||
CAUGHT,
|
||||
ESCAPED,
|
||||
CANCELLED,
|
||||
}
|
||||
|
||||
var owner_peer_id: int = 0
|
||||
var request_id: String = ""
|
||||
var attempt_id: String = ""
|
||||
var result_id: String = ""
|
||||
var session_id: String = ""
|
||||
var phase: Phase = Phase.CASTING
|
||||
var origin: Vector3
|
||||
var target: Vector3
|
||||
var bobber_position: Vector3
|
||||
var fish_id: StringName
|
||||
var encounter_seed: int = 0
|
||||
var bite_time_remaining: float = 0.0
|
||||
var withdrawal_progress: float = 0.0
|
||||
var reel_speed: float = 0.0
|
||||
var barrier_damage: int = 1
|
||||
var input_held: bool = false
|
||||
var last_input_sequence: int = 0
|
||||
var catch_payload: Dictionary = {}
|
||||
var capacity_nonce: String = ""
|
||||
var capacity_deadline: float = 0.0
|
||||
var controller: CatchController
|
||||
|
||||
|
||||
func is_terminal() -> bool:
|
||||
return phase in [Phase.CAUGHT, Phase.ESCAPED, Phase.CANCELLED]
|
||||
1
network/network_fishing_attempt.gd.uid
Normal file
1
network/network_fishing_attempt.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://c4c00yp1546af
|
||||
142
network/network_fishing_protocol.gd
Normal file
142
network/network_fishing_protocol.gd
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
class_name NetworkFishingProtocol
|
||||
extends RefCounted
|
||||
|
||||
const MAX_ID_LENGTH: int = 96
|
||||
const MAX_EVIDENCE_FISH_IDS: int = 256
|
||||
const MAX_CAST_DISTANCE: float = 30.0
|
||||
const MAX_REEL_SPEED: float = 10.0
|
||||
const MAX_BARRIER_DAMAGE: int = 100
|
||||
const INPUT_CHANNEL: int = 3
|
||||
const SNAPSHOT_CHANNEL: int = 4
|
||||
const INPUT_RATE: float = 1.0 / 25.0
|
||||
const SNAPSHOT_RATE: float = 1.0 / 15.0
|
||||
|
||||
enum Outcome {
|
||||
CATCH,
|
||||
ESCAPE,
|
||||
CANCELLED,
|
||||
}
|
||||
|
||||
|
||||
static func validate_cast_request(data: Variant) -> String:
|
||||
if typeof(data) != TYPE_DICTIONARY:
|
||||
return "Malformed fishing request."
|
||||
var payload: Dictionary = data
|
||||
for key: String in [
|
||||
"request_id",
|
||||
"session_id",
|
||||
"origin",
|
||||
"target",
|
||||
"charge",
|
||||
"rod_id",
|
||||
"reel_speed",
|
||||
"barrier_damage",
|
||||
"bite_multiplier",
|
||||
"rarity_multipliers",
|
||||
"discovered_fish_ids",
|
||||
"capacity_available",
|
||||
]:
|
||||
if not payload.has(key):
|
||||
return "Malformed fishing request."
|
||||
if (
|
||||
typeof(payload["request_id"]) != TYPE_STRING
|
||||
or typeof(payload["session_id"]) != TYPE_STRING
|
||||
or typeof(payload["origin"]) != TYPE_ARRAY
|
||||
or typeof(payload["target"]) != TYPE_ARRAY
|
||||
or typeof(payload["charge"]) not in [TYPE_FLOAT, TYPE_INT]
|
||||
or typeof(payload["rod_id"]) not in [TYPE_STRING, TYPE_STRING_NAME]
|
||||
or typeof(payload["reel_speed"]) not in [TYPE_FLOAT, TYPE_INT]
|
||||
or typeof(payload["barrier_damage"]) != TYPE_INT
|
||||
or typeof(payload["bite_multiplier"]) not in [TYPE_FLOAT, TYPE_INT]
|
||||
or typeof(payload["rarity_multipliers"]) != TYPE_ARRAY
|
||||
or typeof(payload["discovered_fish_ids"]) != TYPE_ARRAY
|
||||
or typeof(payload["capacity_available"]) != TYPE_BOOL
|
||||
):
|
||||
return "Malformed fishing request."
|
||||
var request_id: String = payload["request_id"]
|
||||
var session_id: String = payload["session_id"]
|
||||
var origin: Array = payload["origin"]
|
||||
var target: Array = payload["target"]
|
||||
var charge: float = float(payload["charge"])
|
||||
var reel_speed: float = float(payload["reel_speed"])
|
||||
var barrier_damage: int = payload["barrier_damage"]
|
||||
var bite_multiplier: float = float(payload["bite_multiplier"])
|
||||
var rarity: Array = payload["rarity_multipliers"]
|
||||
var discovered: Array = payload["discovered_fish_ids"]
|
||||
if (
|
||||
request_id.is_empty()
|
||||
or request_id.length() > MAX_ID_LENGTH
|
||||
or session_id.is_empty()
|
||||
or session_id.length() > MAX_ID_LENGTH
|
||||
or origin.size() != 3
|
||||
or target.size() != 3
|
||||
or not _array_is_finite_vector3(origin)
|
||||
or not _array_is_finite_vector3(target)
|
||||
or not is_finite(charge)
|
||||
or charge < 0.0
|
||||
or charge > 1.01
|
||||
or not is_finite(reel_speed)
|
||||
or reel_speed <= 0.0
|
||||
or reel_speed > MAX_REEL_SPEED
|
||||
or barrier_damage < 1
|
||||
or barrier_damage > MAX_BARRIER_DAMAGE
|
||||
or not is_finite(bite_multiplier)
|
||||
or bite_multiplier < 0.1
|
||||
or bite_multiplier > 10.0
|
||||
or rarity.size() > 16
|
||||
or discovered.size() > MAX_EVIDENCE_FISH_IDS
|
||||
or str(payload["rod_id"]).is_empty()
|
||||
or str(payload["rod_id"]).length() > 96
|
||||
):
|
||||
return "Fishing request values are outside allowed limits."
|
||||
for value: Variant in rarity:
|
||||
if (
|
||||
typeof(value) not in [TYPE_FLOAT, TYPE_INT]
|
||||
or not is_finite(float(value))
|
||||
or float(value) < 0.0
|
||||
or float(value) > 10.0
|
||||
):
|
||||
return "Fishing rarity evidence is invalid."
|
||||
for value: Variant in discovered:
|
||||
if (
|
||||
typeof(value) not in [TYPE_STRING, TYPE_STRING_NAME]
|
||||
or str(value).is_empty()
|
||||
or str(value).length() > 96
|
||||
):
|
||||
return "Fishing discovery evidence is invalid."
|
||||
return ""
|
||||
|
||||
|
||||
static func validate_input(data: Variant) -> bool:
|
||||
if typeof(data) != TYPE_DICTIONARY:
|
||||
return false
|
||||
var payload: Dictionary = data
|
||||
return (
|
||||
typeof(payload.get("attempt_id")) == TYPE_STRING
|
||||
and not str(payload["attempt_id"]).is_empty()
|
||||
and str(payload["attempt_id"]).length() <= MAX_ID_LENGTH
|
||||
and typeof(payload.get("sequence")) == TYPE_INT
|
||||
and int(payload["sequence"]) > 0
|
||||
and typeof(payload.get("held")) == TYPE_BOOL
|
||||
and typeof(payload.get("pressed")) == TYPE_BOOL
|
||||
)
|
||||
|
||||
|
||||
static func vector3_to_array(value: Vector3) -> Array[float]:
|
||||
return [value.x, value.y, value.z]
|
||||
|
||||
|
||||
static func array_to_vector3(value: Array) -> Vector3:
|
||||
if value.size() != 3:
|
||||
return Vector3.INF
|
||||
return Vector3(float(value[0]), float(value[1]), float(value[2]))
|
||||
|
||||
|
||||
static func _array_is_finite_vector3(value: Array) -> bool:
|
||||
for component: Variant in value:
|
||||
if (
|
||||
typeof(component) not in [TYPE_FLOAT, TYPE_INT]
|
||||
or not is_finite(float(component))
|
||||
):
|
||||
return false
|
||||
return true
|
||||
1
network/network_fishing_protocol.gd.uid
Normal file
1
network/network_fishing_protocol.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://cbihol2i6e2iw
|
||||
1048
network/network_fishing_service.gd
Normal file
1048
network/network_fishing_service.gd
Normal file
File diff suppressed because it is too large
Load diff
1
network/network_fishing_service.gd.uid
Normal file
1
network/network_fishing_service.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://b4v56htdn1y2l
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
class_name NetworkProtocol
|
||||
extends RefCounted
|
||||
|
||||
const PROTOCOL_VERSION: int = 1
|
||||
const PROTOCOL_VERSION: int = 2
|
||||
const GAME_BUILD: String = "prealpha"
|
||||
const MAX_DISPLAY_NAME_LENGTH: int = 48
|
||||
const MAX_PROFILE_ID_LENGTH: int = 96
|
||||
|
|
@ -99,7 +99,10 @@ static func make_server_hello(
|
|||
"server_display_name": "NETFISHING",
|
||||
"player_count": player_count,
|
||||
"max_players": max_players,
|
||||
"capability_flags": PackedStringArray(["movement_v1"]),
|
||||
"capability_flags": PackedStringArray([
|
||||
"movement_v1",
|
||||
"fishing_v1",
|
||||
]),
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -254,6 +254,26 @@ func can_use_host_gameplay() -> bool:
|
|||
return is_host()
|
||||
|
||||
|
||||
func is_gameplay_session_active() -> bool:
|
||||
return state in [State.PRIVATE_HOST, State.OPEN_HOST, State.JOINED_CLIENT]
|
||||
|
||||
|
||||
func is_authenticated_peer(peer_id: int) -> bool:
|
||||
return _registry.has_peer(peer_id)
|
||||
|
||||
|
||||
func get_session_id() -> String:
|
||||
return _session_id
|
||||
|
||||
|
||||
func get_operation_generation() -> int:
|
||||
return _operation_generation
|
||||
|
||||
|
||||
func get_local_peer_id() -> int:
|
||||
return multiplayer.get_unique_id() if is_gameplay_session_active() else 0
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
var now: float = Time.get_ticks_msec() / 1000.0
|
||||
if (
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue