feat: expand progression and multiplayer systems
Add named save slots, progression import/export, and a unified play flow. Add live friend requests, presence, invitations, and relationship controls without durable discovery-server social storage. Advance the network protocol with isolated channels, movement reconciliation, late-join recovery, fishing replication, and animation synchronization. Preserve per-species catch totals, refine generated-world startup and water recovery, and complete the related input and interface improvements.
This commit is contained in:
parent
1db1a5b754
commit
3b84bfe3a0
97 changed files with 7869 additions and 982 deletions
|
|
@ -9,6 +9,15 @@ signal host_state_changed(state: int)
|
|||
signal public_join_prepared(endpoint: String)
|
||||
signal public_join_status_changed(message: String, is_error: bool)
|
||||
signal public_join_state_changed(state: int)
|
||||
signal friend_presence_updated(friends: Array[Dictionary])
|
||||
signal presence_sharing_changed(enabled: bool)
|
||||
signal social_status_changed(message: String, is_error: bool)
|
||||
signal friend_invite_received(
|
||||
fingerprint: String,
|
||||
display_name: String,
|
||||
room: Dictionary,
|
||||
)
|
||||
signal friend_invite_finished(success: bool, message: String)
|
||||
|
||||
const BASE_URL_SETTING: String = "network/discovery/base_url"
|
||||
const BASE_URL_ENVIRONMENT: String = "NETFISHING_DISCOVERY_URL"
|
||||
|
|
@ -28,6 +37,7 @@ const TRAVERSAL_PACKET_PREFIX: String = "NETFISHING_TRAVERSAL_V1 "
|
|||
const UPNP_MAPPING_DURATION_SECONDS: int = 3600
|
||||
const UPNP_RENEW_INTERVAL_SECONDS: float = 2700.0
|
||||
const UPNP_RETRY_INTERVAL_SECONDS: float = 300.0
|
||||
const SOCIAL_POLL_INTERVAL_SECONDS: float = 5.0
|
||||
|
||||
enum HostState {
|
||||
UNAVAILABLE,
|
||||
|
|
@ -55,6 +65,7 @@ enum HostRequestKind {
|
|||
}
|
||||
|
||||
var _session: NetworkSession
|
||||
var _relationships: PlayerRelationshipStore
|
||||
var _base_url: String = ""
|
||||
var _room_name: String = DEFAULT_ROOM_NAME
|
||||
var _room_name_uses_default: bool = true
|
||||
|
|
@ -92,6 +103,18 @@ var _upnp_operation_is_renewal: bool = false
|
|||
var _upnp_renew_timer: Timer
|
||||
var _upnp: UPNP
|
||||
var _upnp_mapped_port: int = 0
|
||||
var _presence_sharing: bool = false
|
||||
var _social_timer: Timer
|
||||
var _presence_request: HTTPRequest
|
||||
var _presence_query_request: HTTPRequest
|
||||
var _invitation_poll_request: HTTPRequest
|
||||
var _invitation_send_request: HTTPRequest
|
||||
var _friend_presence: Dictionary[String, Dictionary] = {}
|
||||
var _published_presence_tokens: PackedStringArray = PackedStringArray()
|
||||
var _pending_presence_online: bool = false
|
||||
var _pending_presence_tokens: PackedStringArray = PackedStringArray()
|
||||
var _pending_invite_fingerprint: String = ""
|
||||
var _joined_public_room_id: String = ""
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
|
|
@ -147,16 +170,48 @@ func _ready() -> void:
|
|||
_join_probe_timer.one_shot = false
|
||||
add_child(_join_probe_timer)
|
||||
_join_probe_timer.timeout.connect(_send_pending_join_probe)
|
||||
|
||||
_presence_request = _make_social_request(
|
||||
"FriendPresencePublish", _on_presence_request_completed
|
||||
)
|
||||
_presence_query_request = _make_social_request(
|
||||
"FriendPresenceQuery", _on_presence_query_completed
|
||||
)
|
||||
_invitation_poll_request = _make_social_request(
|
||||
"FriendInvitationPoll", _on_invitation_poll_completed
|
||||
)
|
||||
_invitation_send_request = _make_social_request(
|
||||
"FriendInvitationSend", _on_invitation_send_completed
|
||||
)
|
||||
_social_timer = Timer.new()
|
||||
_social_timer.name = "FriendPresencePoll"
|
||||
_social_timer.wait_time = SOCIAL_POLL_INTERVAL_SECONDS
|
||||
_social_timer.one_shot = false
|
||||
add_child(_social_timer)
|
||||
_social_timer.timeout.connect(_refresh_social_state)
|
||||
_load_settings()
|
||||
_base_url = _configured_base_url()
|
||||
|
||||
|
||||
func setup(session: NetworkSession) -> void:
|
||||
func setup(
|
||||
session: NetworkSession,
|
||||
relationships: PlayerRelationshipStore = null,
|
||||
) -> void:
|
||||
_session = session
|
||||
_relationships = relationships
|
||||
if _room_name_uses_default:
|
||||
_room_name = _default_room_name(_session.get_local_display_name())
|
||||
_save_settings()
|
||||
_session.set_session_display_name(_room_name)
|
||||
if (
|
||||
_relationships != null
|
||||
and not _relationships.relationship_changed.is_connected(
|
||||
_on_social_relationship_changed
|
||||
)
|
||||
):
|
||||
_relationships.relationship_changed.connect(
|
||||
_on_social_relationship_changed
|
||||
)
|
||||
if not _session.state_changed.is_connected(_on_session_state_changed):
|
||||
_session.state_changed.connect(_on_session_state_changed)
|
||||
if not _session.peer_count_changed.is_connected(_on_peer_count_changed):
|
||||
|
|
@ -173,6 +228,9 @@ func setup(session: NetworkSession) -> void:
|
|||
else:
|
||||
_set_host_state(HostState.CLOSED)
|
||||
_set_host_status("Open the game before listing it publicly.", false)
|
||||
if is_configured() and _relationships != null:
|
||||
_social_timer.start()
|
||||
call_deferred("_refresh_social_state")
|
||||
|
||||
|
||||
func is_configured() -> bool:
|
||||
|
|
@ -183,6 +241,112 @@ func get_base_url() -> String:
|
|||
return _base_url
|
||||
|
||||
|
||||
func is_presence_sharing() -> bool:
|
||||
return _presence_sharing
|
||||
|
||||
|
||||
func set_presence_sharing(enabled: bool) -> bool:
|
||||
if enabled and (not is_configured() or _relationships == null):
|
||||
social_status_changed.emit(
|
||||
"Friend presence is not configured in this build.", true
|
||||
)
|
||||
return false
|
||||
if _presence_sharing == enabled:
|
||||
return true
|
||||
_presence_sharing = enabled
|
||||
_save_settings()
|
||||
presence_sharing_changed.emit(enabled)
|
||||
_refresh_social_state()
|
||||
return true
|
||||
|
||||
|
||||
func get_friend_presence() -> Array[Dictionary]:
|
||||
var result: Array[Dictionary] = []
|
||||
if _relationships == null:
|
||||
return result
|
||||
for friend: Dictionary in _relationships.get_friends():
|
||||
var fingerprint := str(friend.get("fingerprint", ""))
|
||||
var live: Dictionary = _friend_presence.get(fingerprint, {})
|
||||
result.append({
|
||||
"fingerprint": fingerprint,
|
||||
"display_name": str(
|
||||
friend.get("last_known_display_name", "Player")
|
||||
),
|
||||
"online": bool(live.get("online", false)),
|
||||
"room": (
|
||||
live.get("room", {}).duplicate(true)
|
||||
if typeof(live.get("room", {})) == TYPE_DICTIONARY
|
||||
else {}
|
||||
),
|
||||
})
|
||||
result.sort_custom(func(a: Dictionary, b: Dictionary) -> bool:
|
||||
if bool(a["online"]) != bool(b["online"]):
|
||||
return bool(a["online"])
|
||||
return str(a["display_name"]).naturalnocasecmp_to(
|
||||
str(b["display_name"])
|
||||
) < 0
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
func request_friend_presence() -> bool:
|
||||
if not is_configured() or _relationships == null:
|
||||
friend_presence_updated.emit(get_friend_presence())
|
||||
return false
|
||||
_refresh_social_state()
|
||||
return true
|
||||
|
||||
|
||||
func send_friend_invite(fingerprint: String) -> bool:
|
||||
if (
|
||||
_relationships == null
|
||||
or not _relationships.is_friend(fingerprint)
|
||||
or _relationships.is_blocked(fingerprint)
|
||||
or not is_configured()
|
||||
):
|
||||
friend_invite_finished.emit(
|
||||
false, "This person needs to be online to do this."
|
||||
)
|
||||
return false
|
||||
if (
|
||||
_session == null
|
||||
or not _session.is_host()
|
||||
or not _host_verified
|
||||
or _lease_room_id.is_empty()
|
||||
):
|
||||
friend_invite_finished.emit(
|
||||
false,
|
||||
"List your open room in discovery before inviting friends.",
|
||||
)
|
||||
return false
|
||||
if (
|
||||
not _pending_invite_fingerprint.is_empty()
|
||||
or _invitation_send_request.get_http_client_status()
|
||||
!= HTTPClient.STATUS_DISCONNECTED
|
||||
):
|
||||
return false
|
||||
var friend: Dictionary = _relationships.get_friend_record(fingerprint)
|
||||
var inbox_token := str(friend.get("remote_invite_token", ""))
|
||||
if inbox_token.is_empty():
|
||||
friend_invite_finished.emit(false, "Friend invitation is unavailable.")
|
||||
return false
|
||||
var error := _post_social_json(
|
||||
_invitation_send_request,
|
||||
"/v1/invitations",
|
||||
{
|
||||
"inbox_token": inbox_token,
|
||||
"room_id": _lease_room_id,
|
||||
},
|
||||
)
|
||||
if error != OK:
|
||||
friend_invite_finished.emit(
|
||||
false, "Friend invitation could not be sent."
|
||||
)
|
||||
return false
|
||||
_pending_invite_fingerprint = fingerprint
|
||||
return true
|
||||
|
||||
|
||||
func set_base_url_override(value: String) -> bool:
|
||||
var normalized: String = value.strip_edges()
|
||||
while normalized.ends_with("/"):
|
||||
|
|
@ -345,7 +509,7 @@ func prepare_public_join(room: Dictionary) -> bool:
|
|||
|
||||
func request_rooms() -> bool:
|
||||
if not is_configured():
|
||||
rooms_updated.emit([])
|
||||
rooms_updated.emit(_empty_rooms())
|
||||
browse_status_changed.emit(
|
||||
"Public room discovery is not configured in this build.", true
|
||||
)
|
||||
|
|
@ -362,7 +526,7 @@ func request_rooms() -> bool:
|
|||
]
|
||||
var error: Error = _browse_request.request(url)
|
||||
if error != OK:
|
||||
rooms_updated.emit([])
|
||||
rooms_updated.emit(_empty_rooms())
|
||||
browse_status_changed.emit("Could not request public rooms.", true)
|
||||
return false
|
||||
_browse_request_in_flight = true
|
||||
|
|
@ -813,12 +977,12 @@ func _on_browse_request_completed(
|
|||
_browse_request_in_flight = false
|
||||
var response: Dictionary = _parse_response_dictionary(body)
|
||||
if result != HTTPRequest.RESULT_SUCCESS or response_code != HTTPClient.RESPONSE_OK:
|
||||
rooms_updated.emit([])
|
||||
rooms_updated.emit(_empty_rooms())
|
||||
browse_status_changed.emit(_request_failure(response), true)
|
||||
return
|
||||
var raw_rooms: Variant = response.get("rooms", [])
|
||||
if typeof(raw_rooms) != TYPE_ARRAY:
|
||||
rooms_updated.emit([])
|
||||
rooms_updated.emit(_empty_rooms())
|
||||
browse_status_changed.emit("Discovery returned an invalid room list.", true)
|
||||
return
|
||||
var rooms: Array[Dictionary] = []
|
||||
|
|
@ -873,6 +1037,309 @@ func _valid_json_integer(value: Variant, minimum: int, maximum: int) -> bool:
|
|||
)
|
||||
|
||||
|
||||
func _make_social_request(name: String, callback: Callable) -> HTTPRequest:
|
||||
var request := HTTPRequest.new()
|
||||
request.name = name
|
||||
request.timeout = REQUEST_TIMEOUT_SECONDS
|
||||
add_child(request)
|
||||
request.request_completed.connect(callback)
|
||||
return request
|
||||
|
||||
|
||||
func _social_payload(fields: Dictionary = {}) -> Dictionary:
|
||||
var payload: Dictionary = fields.duplicate(true)
|
||||
payload["game_version"] = NetworkProtocol.game_version()
|
||||
payload["protocol_version"] = NetworkProtocol.PROTOCOL_VERSION
|
||||
return payload
|
||||
|
||||
|
||||
func _post_social_json(
|
||||
request: HTTPRequest,
|
||||
path: String,
|
||||
fields: Dictionary,
|
||||
) -> Error:
|
||||
return request.request(
|
||||
_base_url + path,
|
||||
PackedStringArray(["Content-Type: application/json"]),
|
||||
HTTPClient.METHOD_POST,
|
||||
JSON.stringify(_social_payload(fields)),
|
||||
)
|
||||
|
||||
|
||||
func _refresh_social_state() -> void:
|
||||
if not is_configured() or _relationships == null:
|
||||
return
|
||||
_publish_friend_presence()
|
||||
_query_friend_presence()
|
||||
_poll_friend_invitations()
|
||||
|
||||
|
||||
func _publish_friend_presence() -> void:
|
||||
if (
|
||||
_presence_request.get_http_client_status()
|
||||
!= HTTPClient.STATUS_DISCONNECTED
|
||||
):
|
||||
return
|
||||
var current_tokens := _friend_social_values(
|
||||
"local_presence_write_token"
|
||||
)
|
||||
var online := _presence_sharing and not current_tokens.is_empty()
|
||||
var publish_online := online
|
||||
var tokens := PackedStringArray()
|
||||
if online:
|
||||
# Revoke removed or blocked friendships before refreshing the remaining
|
||||
# capabilities. This makes status disappear on the next request instead of
|
||||
# waiting for the server's short presence TTL.
|
||||
tokens = _presence_tokens_not_in(
|
||||
_published_presence_tokens, current_tokens
|
||||
)
|
||||
if not tokens.is_empty():
|
||||
publish_online = false
|
||||
else:
|
||||
tokens = current_tokens
|
||||
else:
|
||||
tokens = _published_presence_tokens
|
||||
if tokens.is_empty():
|
||||
return
|
||||
var error := _post_social_json(
|
||||
_presence_request,
|
||||
"/v1/presence",
|
||||
{
|
||||
"display_name": _session.get_local_display_name(),
|
||||
"room_id": _current_presence_room_id(),
|
||||
"online": publish_online,
|
||||
"write_tokens": Array(tokens),
|
||||
},
|
||||
)
|
||||
if error == OK:
|
||||
_pending_presence_online = publish_online
|
||||
_pending_presence_tokens = tokens.duplicate()
|
||||
else:
|
||||
social_status_changed.emit("Could not update friend presence.", true)
|
||||
|
||||
|
||||
func _query_friend_presence() -> void:
|
||||
if (
|
||||
_presence_query_request.get_http_client_status()
|
||||
!= HTTPClient.STATUS_DISCONNECTED
|
||||
):
|
||||
return
|
||||
var channels := _friend_social_values("remote_presence_channel")
|
||||
if channels.is_empty():
|
||||
var had_presence := not _friend_presence.is_empty()
|
||||
_friend_presence.clear()
|
||||
if had_presence:
|
||||
friend_presence_updated.emit(get_friend_presence())
|
||||
return
|
||||
var error := _post_social_json(
|
||||
_presence_query_request,
|
||||
"/v1/presence/query",
|
||||
{"channels": Array(channels)},
|
||||
)
|
||||
if error != OK:
|
||||
social_status_changed.emit("Could not refresh friend status.", true)
|
||||
|
||||
|
||||
func _poll_friend_invitations() -> void:
|
||||
if (
|
||||
_invitation_poll_request.get_http_client_status()
|
||||
!= HTTPClient.STATUS_DISCONNECTED
|
||||
):
|
||||
return
|
||||
var tokens := _friend_social_values("local_invite_token")
|
||||
if tokens.is_empty():
|
||||
return
|
||||
var error := _post_social_json(
|
||||
_invitation_poll_request,
|
||||
"/v1/invitations/poll",
|
||||
{"inbox_tokens": Array(tokens)},
|
||||
)
|
||||
if error != OK:
|
||||
social_status_changed.emit("Could not check friend invitations.", true)
|
||||
|
||||
|
||||
func _friend_social_values(key: String) -> PackedStringArray:
|
||||
var result := PackedStringArray()
|
||||
if _relationships == null:
|
||||
return result
|
||||
for friend: Dictionary in _relationships.get_friends():
|
||||
var value := str(friend.get(key, ""))
|
||||
if not value.is_empty() and value not in result:
|
||||
result.append(value)
|
||||
return result
|
||||
|
||||
|
||||
func _current_presence_room_id() -> String:
|
||||
if _session == null:
|
||||
return ""
|
||||
if _session.is_host() and _host_verified:
|
||||
return _lease_room_id
|
||||
if _session.is_joined_client():
|
||||
return _joined_public_room_id
|
||||
return ""
|
||||
|
||||
|
||||
func _on_presence_request_completed(
|
||||
result: int,
|
||||
response_code: int,
|
||||
_headers: PackedStringArray,
|
||||
body: PackedByteArray,
|
||||
) -> void:
|
||||
var published_online := _pending_presence_online
|
||||
var request_tokens := _pending_presence_tokens.duplicate()
|
||||
_pending_presence_online = false
|
||||
_pending_presence_tokens = PackedStringArray()
|
||||
var response := _parse_response_dictionary(body)
|
||||
if result != HTTPRequest.RESULT_SUCCESS or response_code != HTTPClient.RESPONSE_OK:
|
||||
social_status_changed.emit(_request_failure(response), true)
|
||||
return
|
||||
if published_online:
|
||||
for token: String in request_tokens:
|
||||
if token not in _published_presence_tokens:
|
||||
_published_presence_tokens.append(token)
|
||||
else:
|
||||
for token: String in request_tokens:
|
||||
var index := _published_presence_tokens.find(token)
|
||||
if index >= 0:
|
||||
_published_presence_tokens.remove_at(index)
|
||||
if _presence_publish_needs_follow_up():
|
||||
call_deferred("_publish_friend_presence")
|
||||
|
||||
|
||||
func _presence_publish_needs_follow_up() -> bool:
|
||||
var desired := (
|
||||
_friend_social_values("local_presence_write_token")
|
||||
if _presence_sharing
|
||||
else PackedStringArray()
|
||||
)
|
||||
return (
|
||||
not _presence_tokens_not_in(
|
||||
_published_presence_tokens, desired
|
||||
).is_empty()
|
||||
or not _presence_tokens_not_in(
|
||||
desired, _published_presence_tokens
|
||||
).is_empty()
|
||||
)
|
||||
|
||||
|
||||
func _presence_tokens_not_in(
|
||||
first: PackedStringArray,
|
||||
second: PackedStringArray,
|
||||
) -> PackedStringArray:
|
||||
var result := PackedStringArray()
|
||||
for token: String in first:
|
||||
if token not in second:
|
||||
result.append(token)
|
||||
return result
|
||||
|
||||
|
||||
func _on_presence_query_completed(
|
||||
result: int,
|
||||
response_code: int,
|
||||
_headers: PackedStringArray,
|
||||
body: PackedByteArray,
|
||||
) -> void:
|
||||
var response := _parse_response_dictionary(body)
|
||||
if result != HTTPRequest.RESULT_SUCCESS or response_code != HTTPClient.RESPONSE_OK:
|
||||
social_status_changed.emit(_request_failure(response), true)
|
||||
return
|
||||
var by_channel: Dictionary[String, String] = {}
|
||||
for friend: Dictionary in _relationships.get_friends():
|
||||
by_channel[str(friend.get("remote_presence_channel", ""))] = str(
|
||||
friend.get("fingerprint", "")
|
||||
)
|
||||
var next_presence: Dictionary[String, Dictionary] = {}
|
||||
var raw_presence: Variant = response.get("presence", [])
|
||||
if typeof(raw_presence) == TYPE_ARRAY:
|
||||
for value: Variant in raw_presence:
|
||||
if typeof(value) != TYPE_DICTIONARY:
|
||||
continue
|
||||
var presence: Dictionary = value
|
||||
var fingerprint := str(
|
||||
by_channel.get(str(presence.get("channel", "")), "")
|
||||
)
|
||||
if fingerprint.is_empty() or _relationships.is_blocked(fingerprint):
|
||||
continue
|
||||
var room: Dictionary = {}
|
||||
var raw_room: Variant = presence.get("room", {})
|
||||
if typeof(raw_room) == TYPE_DICTIONARY and _valid_public_room(raw_room):
|
||||
room = (raw_room as Dictionary).duplicate(true)
|
||||
next_presence[fingerprint] = {
|
||||
"online": true,
|
||||
"room": room,
|
||||
}
|
||||
if next_presence == _friend_presence:
|
||||
return
|
||||
_friend_presence = next_presence
|
||||
friend_presence_updated.emit(get_friend_presence())
|
||||
|
||||
|
||||
func _on_invitation_poll_completed(
|
||||
result: int,
|
||||
response_code: int,
|
||||
_headers: PackedStringArray,
|
||||
body: PackedByteArray,
|
||||
) -> void:
|
||||
var response := _parse_response_dictionary(body)
|
||||
if result != HTTPRequest.RESULT_SUCCESS or response_code != HTTPClient.RESPONSE_OK:
|
||||
return
|
||||
var by_inbox: Dictionary[String, Dictionary] = {}
|
||||
for friend: Dictionary in _relationships.get_friends():
|
||||
var token := str(friend.get("local_invite_token", ""))
|
||||
if not token.is_empty():
|
||||
by_inbox[PlayerRelationshipStore.invite_inbox_id(token)] = friend
|
||||
var invitations: Variant = response.get("invitations", [])
|
||||
if typeof(invitations) != TYPE_ARRAY:
|
||||
return
|
||||
for value: Variant in invitations:
|
||||
if typeof(value) != TYPE_DICTIONARY:
|
||||
continue
|
||||
var invitation: Dictionary = value
|
||||
var friend: Dictionary = by_inbox.get(
|
||||
str(invitation.get("inbox_id", "")), {}
|
||||
)
|
||||
var raw_room: Variant = invitation.get("room", {})
|
||||
if friend.is_empty() or typeof(raw_room) != TYPE_DICTIONARY:
|
||||
continue
|
||||
var room := raw_room as Dictionary
|
||||
if not _valid_public_room(room):
|
||||
continue
|
||||
var fingerprint := str(friend.get("fingerprint", ""))
|
||||
if _relationships.is_blocked(fingerprint):
|
||||
continue
|
||||
friend_invite_received.emit(
|
||||
fingerprint,
|
||||
str(friend.get("last_known_display_name", "Player")),
|
||||
room.duplicate(true),
|
||||
)
|
||||
|
||||
|
||||
func _on_invitation_send_completed(
|
||||
result: int,
|
||||
response_code: int,
|
||||
_headers: PackedStringArray,
|
||||
body: PackedByteArray,
|
||||
) -> void:
|
||||
_pending_invite_fingerprint = ""
|
||||
var response := _parse_response_dictionary(body)
|
||||
var success := (
|
||||
result == HTTPRequest.RESULT_SUCCESS
|
||||
and response_code == HTTPClient.RESPONSE_CREATED
|
||||
)
|
||||
friend_invite_finished.emit(
|
||||
success,
|
||||
"Invitation sent." if success else _request_failure(response),
|
||||
)
|
||||
|
||||
|
||||
func _on_social_relationship_changed(_fingerprint: String) -> void:
|
||||
for fingerprint: String in _friend_presence.keys():
|
||||
if not _relationships.is_friend(fingerprint):
|
||||
_friend_presence.erase(fingerprint)
|
||||
friend_presence_updated.emit(get_friend_presence())
|
||||
call_deferred("_refresh_social_state")
|
||||
|
||||
|
||||
func _parse_response_dictionary(body: PackedByteArray) -> Dictionary:
|
||||
if body.is_empty():
|
||||
return {}
|
||||
|
|
@ -924,6 +1391,21 @@ func _discovery_version_mismatch_message(required_version: String) -> String:
|
|||
|
||||
|
||||
func _on_session_state_changed(state: NetworkSession.State) -> void:
|
||||
if state == NetworkSession.State.JOINED_CLIENT:
|
||||
if not _pending_join_room_id.is_empty():
|
||||
_joined_public_room_id = _pending_join_room_id
|
||||
elif state in [
|
||||
NetworkSession.State.PRIVATE_HOST,
|
||||
NetworkSession.State.OPEN_HOST,
|
||||
NetworkSession.State.SERVER_LOST,
|
||||
NetworkSession.State.CONNECTION_FAILED,
|
||||
]:
|
||||
_joined_public_room_id = ""
|
||||
elif (
|
||||
state == NetworkSession.State.INACTIVE
|
||||
and not _preserve_pending_join_on_inactive
|
||||
):
|
||||
_joined_public_room_id = ""
|
||||
if state == NetworkSession.State.CONNECTING and not _pending_join_token.is_empty():
|
||||
_preserve_pending_join_on_inactive = false
|
||||
_set_public_join_state(PublicJoinState.CONNECTING)
|
||||
|
|
@ -979,6 +1461,7 @@ func _on_session_state_changed(state: NetworkSession.State) -> void:
|
|||
_set_host_status("Open the game before listing it publicly.", false)
|
||||
else:
|
||||
_set_host_state(HostState.CLOSED)
|
||||
call_deferred("_refresh_social_state")
|
||||
|
||||
|
||||
func _on_host_openness_changed(is_open: bool) -> void:
|
||||
|
|
@ -1039,6 +1522,10 @@ func _set_public_join_state(state: PublicJoinState) -> void:
|
|||
public_join_state_changed.emit(int(state))
|
||||
|
||||
|
||||
func _empty_rooms() -> Array[Dictionary]:
|
||||
return []
|
||||
|
||||
|
||||
func _sanitize_room_name(value: String) -> String:
|
||||
var cleaned: String = value.strip_edges().replace("\n", " ").replace("\r", " ")
|
||||
cleaned = cleaned.replace("\t", " ")
|
||||
|
|
@ -1065,7 +1552,7 @@ func _load_settings() -> void:
|
|||
)
|
||||
if not saved_name.is_empty():
|
||||
_room_name = saved_name
|
||||
_room_name_uses_default = bool(config.get_value(
|
||||
_room_name_uses_default = bool(config.get_value(
|
||||
"host",
|
||||
"room_name_uses_default",
|
||||
saved_name in [
|
||||
|
|
@ -1073,6 +1560,9 @@ func _load_settings() -> void:
|
|||
LEGACY_DEFAULT_ROOM_NAME,
|
||||
LEGACY_DEDICATED_DEFAULT_ROOM_NAME,
|
||||
],
|
||||
))
|
||||
_presence_sharing = bool(config.get_value(
|
||||
"social", "share_presence", false
|
||||
))
|
||||
|
||||
|
||||
|
|
@ -1084,6 +1574,7 @@ func _save_settings() -> void:
|
|||
config.set_value(
|
||||
"host", "room_name_uses_default", _room_name_uses_default
|
||||
)
|
||||
config.set_value("social", "share_presence", _presence_sharing)
|
||||
var error: Error = config.save(SETTINGS_PATH)
|
||||
if error != OK:
|
||||
push_warning("Could not save the local NETfishing room name.")
|
||||
push_warning("Could not save local NETfishing discovery settings.")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue