Add multiplayer fish showcases
This commit is contained in:
parent
3f4392fdd3
commit
904a9c2d9e
23 changed files with 966 additions and 46 deletions
253
network/network_fish_showcase_service.gd
Normal file
253
network/network_fish_showcase_service.gd
Normal file
|
|
@ -0,0 +1,253 @@
|
|||
class_name NetworkFishShowcaseService
|
||||
extends Node
|
||||
|
||||
const FishPoolType = preload("res://fish/fish_pool.gd")
|
||||
const FishDataType = preload("res://fish/fish_data.gd")
|
||||
const FishCatchType = preload("res://fish/fish_catch.gd")
|
||||
const FishInventoryType = preload("res://inventory/fish_inventory.gd")
|
||||
const PlayerHotbarType = preload("res://inventory/player_hotbar.gd")
|
||||
|
||||
var _session: NetworkSession
|
||||
var _spawn_service: PlayerSpawnService
|
||||
var _fish_catalog: FishPoolType
|
||||
var _local_inventory: FishInventoryType
|
||||
var _local_hotbar: PlayerHotbarType
|
||||
var _states: Dictionary[int, Dictionary] = {}
|
||||
var _local_revision: int = 0
|
||||
var _local_visible: bool = false
|
||||
var _local_catch_id: StringName
|
||||
|
||||
|
||||
func setup(
|
||||
session: NetworkSession,
|
||||
spawn_service: PlayerSpawnService,
|
||||
fish_catalog: FishPoolType,
|
||||
local_inventory: FishInventoryType,
|
||||
local_hotbar: PlayerHotbarType,
|
||||
) -> void:
|
||||
_session = session
|
||||
_spawn_service = spawn_service
|
||||
_fish_catalog = fish_catalog
|
||||
_local_inventory = local_inventory
|
||||
_local_hotbar = local_hotbar
|
||||
_session.peer_authenticated.connect(_on_peer_authenticated)
|
||||
_session.peer_removed.connect(_on_peer_removed)
|
||||
_session.state_changed.connect(_on_session_state_changed)
|
||||
_spawn_service.avatar_spawned.connect(_on_avatar_spawned)
|
||||
_local_inventory.catches_changed.connect(_on_local_inventory_changed)
|
||||
_local_hotbar.selected_assignment_changed.connect(
|
||||
_on_selected_assignment_changed
|
||||
)
|
||||
|
||||
|
||||
func toggle_selected_fish() -> bool:
|
||||
if _local_hotbar == null or _local_inventory == null:
|
||||
return false
|
||||
var catch_id: StringName = _local_hotbar.get_selected_fish_catch_id()
|
||||
var fish_catch: FishCatchType = _local_inventory.get_catch_by_id(catch_id)
|
||||
if fish_catch == null or not fish_catch.is_valid():
|
||||
return false
|
||||
if _local_visible and _local_catch_id == catch_id:
|
||||
_submit_local_state(null, false)
|
||||
else:
|
||||
_submit_local_state(fish_catch, true)
|
||||
return true
|
||||
|
||||
|
||||
func is_local_showcase_visible() -> bool:
|
||||
return _local_visible
|
||||
|
||||
|
||||
func get_local_showcase_catch_id() -> StringName:
|
||||
return _local_catch_id
|
||||
|
||||
|
||||
func _submit_local_state(fish_catch: FishCatchType, should_show: bool) -> void:
|
||||
_local_revision += 1
|
||||
var local_peer_id: int = (
|
||||
_session.get_local_peer_id() if _session != null else 1
|
||||
)
|
||||
var data: Dictionary = {
|
||||
"session_id": (
|
||||
_session.get_session_id()
|
||||
if _session != null and not _session.get_session_id().is_empty()
|
||||
else "local"
|
||||
),
|
||||
"owner_peer_id": local_peer_id,
|
||||
"visible": should_show and fish_catch != null,
|
||||
"fish_id": String(fish_catch.fish_id) if fish_catch != null else "",
|
||||
"weight_lb": fish_catch.weight_lb if fish_catch != null else 0.0,
|
||||
"display_scale": (
|
||||
fish_catch.display_scale if fish_catch != null else 0.0
|
||||
),
|
||||
"revision": _local_revision,
|
||||
}
|
||||
_local_visible = bool(data["visible"])
|
||||
_local_catch_id = (
|
||||
fish_catch.catch_id if _local_visible else StringName()
|
||||
)
|
||||
_apply_state(data)
|
||||
if _session == null or not _session.is_gameplay_session_active():
|
||||
return
|
||||
data["session_id"] = _session.get_session_id()
|
||||
if _session.is_host():
|
||||
_handle_showcase_state(local_peer_id, data)
|
||||
elif _session.supports_server_capability(
|
||||
NetworkFishShowcaseProtocol.CAPABILITY
|
||||
):
|
||||
submit_showcase_state.rpc_id(1, data)
|
||||
|
||||
|
||||
@rpc(
|
||||
"any_peer",
|
||||
"call_remote",
|
||||
"reliable",
|
||||
NetworkFishShowcaseProtocol.RELIABLE_CHANNEL,
|
||||
)
|
||||
func submit_showcase_state(data: Dictionary) -> void:
|
||||
var sender_id: int = multiplayer.get_remote_sender_id()
|
||||
if _session.is_host() and _session.is_authenticated_peer(sender_id):
|
||||
_handle_showcase_state(sender_id, data)
|
||||
|
||||
|
||||
func _handle_showcase_state(peer_id: int, data: Dictionary) -> void:
|
||||
if (
|
||||
not _session.is_host()
|
||||
or not NetworkFishShowcaseProtocol.validate_state(data)
|
||||
or str(data["session_id"]) != _session.get_session_id()
|
||||
or int(data["owner_peer_id"]) != peer_id
|
||||
or (
|
||||
peer_id != _session.get_local_peer_id()
|
||||
and not _session.is_authenticated_peer(peer_id)
|
||||
)
|
||||
):
|
||||
return
|
||||
var previous: Dictionary = _states.get(peer_id, {})
|
||||
if int(data["revision"]) <= int(previous.get("revision", -1)):
|
||||
return
|
||||
if not _state_matches_catalog(data):
|
||||
return
|
||||
var sanitized: Dictionary = data.duplicate(true)
|
||||
_states[peer_id] = sanitized
|
||||
_apply_state(sanitized)
|
||||
receive_showcase_state.rpc(sanitized)
|
||||
|
||||
|
||||
@rpc(
|
||||
"authority",
|
||||
"call_remote",
|
||||
"reliable",
|
||||
NetworkFishShowcaseProtocol.RELIABLE_CHANNEL,
|
||||
)
|
||||
func receive_showcase_state(data: Dictionary) -> void:
|
||||
if (
|
||||
not NetworkFishShowcaseProtocol.validate_state(data)
|
||||
or str(data["session_id"]) != _session.get_session_id()
|
||||
or not _state_matches_catalog(data)
|
||||
):
|
||||
return
|
||||
var peer_id: int = int(data["owner_peer_id"])
|
||||
var previous: Dictionary = _states.get(peer_id, {})
|
||||
if int(data["revision"]) <= int(previous.get("revision", -1)):
|
||||
return
|
||||
_states[peer_id] = data.duplicate(true)
|
||||
_apply_state(data)
|
||||
|
||||
|
||||
func _state_matches_catalog(data: Dictionary) -> bool:
|
||||
if not bool(data["visible"]):
|
||||
return true
|
||||
var fish_id: StringName = StringName(str(data["fish_id"]))
|
||||
var fish: FishDataType = _fish_catalog.get_fish_by_id(fish_id)
|
||||
if fish == null or not fish.is_selectable():
|
||||
return false
|
||||
var weight_lb: float = float(data["weight_lb"])
|
||||
var display_scale: float = float(data["display_scale"])
|
||||
return (
|
||||
weight_lb >= fish.get_minimum_weight()
|
||||
and weight_lb <= fish.get_maximum_weight()
|
||||
and is_equal_approx(
|
||||
display_scale,
|
||||
fish.get_display_scale_for_weight(weight_lb),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
func _apply_state(data: Dictionary) -> void:
|
||||
var peer_id: int = int(data["owner_peer_id"])
|
||||
var avatar: Player = _spawn_service.get_avatar(peer_id)
|
||||
if avatar == null:
|
||||
return
|
||||
if not bool(data["visible"]):
|
||||
avatar.set_held_fish(null, 1.0, false)
|
||||
return
|
||||
var fish: FishDataType = _fish_catalog.get_fish_by_id(
|
||||
StringName(str(data["fish_id"]))
|
||||
)
|
||||
avatar.set_held_fish(fish, float(data["display_scale"]), true)
|
||||
|
||||
|
||||
func _on_selected_assignment_changed(
|
||||
_slot_index: int,
|
||||
kind: int,
|
||||
identity: StringName,
|
||||
) -> void:
|
||||
if (
|
||||
_local_visible
|
||||
and (
|
||||
kind != PlayerHotbarType.AssignmentKind.FISH
|
||||
or identity != _local_catch_id
|
||||
)
|
||||
):
|
||||
_submit_local_state(null, false)
|
||||
|
||||
|
||||
func _on_local_inventory_changed() -> void:
|
||||
if (
|
||||
_local_visible
|
||||
and not _local_inventory.contains_catch_id(_local_catch_id)
|
||||
):
|
||||
_submit_local_state(null, false)
|
||||
|
||||
|
||||
func _on_peer_authenticated(peer_id: int, _display_name: String) -> void:
|
||||
if not _session.is_host():
|
||||
return
|
||||
for state: Dictionary in _states.values():
|
||||
if bool(state.get("visible", false)):
|
||||
receive_showcase_state.rpc_id(peer_id, state)
|
||||
|
||||
|
||||
func _on_avatar_spawned(peer_id: int, _avatar: Player) -> void:
|
||||
var state: Dictionary = _states.get(peer_id, {})
|
||||
if not state.is_empty():
|
||||
_apply_state(state)
|
||||
|
||||
|
||||
func _on_peer_removed(peer_id: int) -> void:
|
||||
var avatar: Player = _spawn_service.get_avatar(peer_id)
|
||||
if avatar != null:
|
||||
avatar.set_held_fish(null, 1.0, false)
|
||||
_states.erase(peer_id)
|
||||
|
||||
|
||||
func _on_session_state_changed(state: NetworkSession.State) -> void:
|
||||
if state not in [
|
||||
NetworkSession.State.INACTIVE,
|
||||
NetworkSession.State.DISCONNECTING,
|
||||
NetworkSession.State.CONNECTION_FAILED,
|
||||
NetworkSession.State.SERVER_LOST,
|
||||
]:
|
||||
return
|
||||
for peer_id: int in _states:
|
||||
var avatar: Player = _spawn_service.get_avatar(peer_id)
|
||||
if avatar != null:
|
||||
avatar.set_held_fish(null, 1.0, false)
|
||||
_states.clear()
|
||||
_local_visible = false
|
||||
_local_catch_id = StringName()
|
||||
var local_avatar: Player = _spawn_service.get_avatar(
|
||||
_session.get_local_peer_id()
|
||||
)
|
||||
if local_avatar != null:
|
||||
local_avatar.set_held_fish(null, 1.0, false)
|
||||
Loading…
Add table
Add a link
Reference in a new issue