Add selectable startup world layouts

This commit is contained in:
Alexander Sellite 2026-08-20 21:43:04 -04:00
parent 5d55afcc26
commit 3d34ed0bee
24 changed files with 1139 additions and 78 deletions

View file

@ -1,6 +1,8 @@
class_name NetworkProtocol
extends RefCounted
const WorldLayoutType = preload("res://world/world_layout.gd")
const PROTOCOL_VERSION: int = 9
const GAME_BUILD: String = "prealpha"
const MAX_GAME_VERSION_LENGTH: int = 64
@ -29,6 +31,7 @@ const JOBS_CAPABILITY: String = "jobs_v1"
const WORLD_SPAWN_CAPABILITY: String = "world_spawn_envelope_v1"
const APPEARANCE_PREVIEW_CAPABILITY: String = "appearance_preview_v1"
const WORLD_GENERATION_CAPABILITY: String = "world_generation_v1"
const WORLD_LAYOUT_CAPABILITY: String = "world_layout_v1"
const DEFAULT_WORLD_SEED: int = 13001
const MAX_WORLD_SEED: int = 2147483646
@ -182,6 +185,7 @@ static func make_client_hello(
APPEARANCE_PREVIEW_CAPABILITY,
BACKPACK_SHOP_CAPABILITY,
WORLD_GENERATION_CAPABILITY,
WORLD_LAYOUT_CAPABILITY,
]),
"cosmetic_snapshot": cosmetic_snapshot,
"identity_fingerprint": identity_fingerprint,
@ -286,6 +290,7 @@ static func make_server_hello(
max_players: int,
server_display_name: String = "NETfishing",
world_seed: int = DEFAULT_WORLD_SEED,
world_layout: StringName = WorldLayoutType.GENERATED,
) -> Dictionary:
return {
"accepted": accepted,
@ -298,6 +303,7 @@ static func make_server_hello(
"player_count": player_count,
"max_players": max_players,
"world_seed": world_seed,
"world_layout": String(world_layout),
"capability_flags": PackedStringArray([
"movement_v1",
"fishing_v1",
@ -316,6 +322,7 @@ static func make_server_hello(
WORLD_SPAWN_CAPABILITY,
APPEARANCE_PREVIEW_CAPABILITY,
WORLD_GENERATION_CAPABILITY,
WORLD_LAYOUT_CAPABILITY,
"chat_v1",
"mail_v1",
"profile_v1",

View file

@ -91,6 +91,10 @@ func setup(
_session.state_changed.connect(_on_session_state_changed)
func set_shop_interaction(interaction: FishingShopInteraction) -> void:
_shop_interaction = interaction
func is_local_sale_pending() -> bool:
return not _pending_local_request_id.is_empty()

View file

@ -86,6 +86,7 @@ var _last_server_player_count: int = 0
var _last_server_display_name: String = ""
var _last_server_protocol_version: int = 0
var _last_server_world_seed: int = NetworkProtocol.DEFAULT_WORLD_SEED
var _last_server_world_layout: StringName = WorldLayout.GENERATED
var _server_capabilities: PackedStringArray = PackedStringArray()
var _profile_ready: bool = false
var _player_identity: PlayerIdentityStore
@ -113,6 +114,7 @@ var _session_operator_fingerprints: Dictionary[String, bool] = {}
var _operator_peer_ids: Dictionary[int, bool] = {}
var _local_operator: bool = false
var _host_world_seed: int = NetworkProtocol.DEFAULT_WORLD_SEED
var _host_world_layout: StringName = WorldLayout.GENERATED
func _ready() -> void:
@ -275,6 +277,7 @@ func _register_player_host() -> void:
NetworkProtocol.WORLD_SPAWN_CAPABILITY,
NetworkProtocol.BACKPACK_SHOP_CAPABILITY,
NetworkProtocol.WORLD_GENERATION_CAPABILITY,
NetworkProtocol.WORLD_LAYOUT_CAPABILITY,
]),
)
_registry.update_appearance(1, _local_appearance_snapshot)
@ -531,20 +534,35 @@ func get_last_server_metadata() -> Dictionary:
"player_count": _last_server_player_count,
"max_players": _last_server_max_players,
"world_seed": _last_server_world_seed,
"world_layout": String(_last_server_world_layout),
}
func set_host_world_seed(seed: int) -> bool:
return set_host_world(WorldLayout.GENERATED, seed)
func set_host_world(world_layout: StringName, seed: int) -> bool:
if (
state != State.INACTIVE
or not WorldLayout.is_valid(world_layout)
or seed <= 0
or seed > NetworkProtocol.MAX_WORLD_SEED
):
return false
_host_world_layout = world_layout
_host_world_seed = seed
return true
func get_authoritative_world_layout() -> StringName:
return (
_last_server_world_layout
if is_joined_client()
else _host_world_layout
)
func get_authoritative_world_seed() -> int:
return _last_server_world_seed if is_joined_client() else _host_world_seed
@ -583,6 +601,7 @@ func supports_server_capability(capability: StringName) -> bool:
NetworkProtocol.WORLD_SPAWN_CAPABILITY,
NetworkProtocol.APPEARANCE_PREVIEW_CAPABILITY,
NetworkProtocol.WORLD_GENERATION_CAPABILITY,
NetworkProtocol.WORLD_LAYOUT_CAPABILITY,
"chat_v1",
"mail_v1",
"profile_v1",
@ -1190,6 +1209,15 @@ func submit_client_hello(data: Dictionary) -> void:
NetworkProtocol.RejectionCode.UNSUPPORTED_CLIENT,
)
return
if (
_host_world_layout == WorldLayout.STARTER_ISLAND
and NetworkProtocol.WORLD_LAYOUT_CAPABILITY not in client_capabilities
):
_reject_peer(
sender_id,
NetworkProtocol.RejectionCode.UNSUPPORTED_CLIENT,
)
return
var identity: Dictionary = _authenticated_identity_cache.get(sender_id, {})
if identity.is_empty():
_reject_peer(sender_id, NetworkProtocol.RejectionCode.INVALID_IDENTITY_PROOF)
@ -1278,6 +1306,7 @@ func submit_client_hello(data: Dictionary) -> void:
session_max_players,
_session_display_name,
_host_world_seed,
_host_world_layout,
)
)
receive_spawn_list.rpc_id(sender_id, _build_spawn_list())
@ -1332,6 +1361,9 @@ func receive_server_hello(data: Dictionary) -> void:
or typeof(data.get("game_version")) != TYPE_STRING
or typeof(data.get("rejection_code")) != TYPE_INT
or typeof(data.get("world_seed")) != TYPE_INT
or typeof(
data.get("world_layout", String(WorldLayout.GENERATED))
) not in [TYPE_STRING, TYPE_STRING_NAME]
):
_teardown_peer()
_fail("The server sent an invalid handshake response.")
@ -1362,6 +1394,14 @@ func receive_server_hello(data: Dictionary) -> void:
_teardown_peer()
_fail("The server sent an invalid world seed.")
return
var received_world_layout := WorldLayout.normalized(
data.get("world_layout", String(WorldLayout.GENERATED)),
&"",
)
if received_world_layout.is_empty():
_teardown_peer()
_fail("The server sent an invalid world layout.")
return
_session_id = str(data.get("session_id", ""))
_last_server_max_players = int(data.get(
"max_players",
@ -1373,6 +1413,7 @@ func receive_server_hello(data: Dictionary) -> void:
"protocol_version", NetworkProtocol.PROTOCOL_VERSION
))
_last_server_world_seed = received_world_seed
_last_server_world_layout = received_world_layout
_server_capabilities = PackedStringArray()
var advertised_capabilities: Variant = data.get(
"capability_flags", PackedStringArray()
@ -1391,6 +1432,13 @@ func receive_server_hello(data: Dictionary) -> void:
_teardown_peer()
_fail("This server does not support generated worlds.")
return
if (
received_world_layout == WorldLayout.STARTER_ISLAND
and NetworkProtocol.WORLD_LAYOUT_CAPABILITY not in _server_capabilities
):
_teardown_peer()
_fail("This server does not support selectable world layouts.")
return
var local_peer_id: int = multiplayer.get_unique_id()
_registry.clear()
_registry.add_peer(
@ -1407,6 +1455,7 @@ func receive_server_hello(data: Dictionary) -> void:
NetworkProtocol.WORLD_WEATHER_CAPABILITY,
NetworkProtocol.BACKPACK_SHOP_CAPABILITY,
NetworkProtocol.WORLD_GENERATION_CAPABILITY,
NetworkProtocol.WORLD_LAYOUT_CAPABILITY,
]),
)
_registry.update_appearance(local_peer_id, _local_appearance_snapshot)

View file

@ -83,6 +83,10 @@ func setup(
_session.state_changed.connect(_on_session_state_changed)
func set_shop_interaction(interaction: FishingShopInteraction) -> void:
_interaction = interaction
func can_request_purchase() -> bool:
return (
_session != null