Integrate per-save identity and interaction UI
This commit is contained in:
parent
ac82a28f3b
commit
4fda6e97f6
58 changed files with 3471 additions and 461 deletions
|
|
@ -278,7 +278,10 @@ func get_friend_presence() -> Array[Dictionary]:
|
|||
result.append({
|
||||
"fingerprint": fingerprint,
|
||||
"display_name": str(
|
||||
friend.get("last_known_display_name", "Player")
|
||||
friend.get(
|
||||
"last_known_display_name",
|
||||
NetworkProfilePreferences.DEFAULT_DISPLAY_NAME,
|
||||
)
|
||||
),
|
||||
"online": bool(live.get("online", false)),
|
||||
"room": (
|
||||
|
|
@ -1444,7 +1447,10 @@ func _on_invitation_poll_completed(
|
|||
continue
|
||||
friend_invite_received.emit(
|
||||
fingerprint,
|
||||
str(friend.get("last_known_display_name", "Player")),
|
||||
str(friend.get(
|
||||
"last_known_display_name",
|
||||
NetworkProfilePreferences.DEFAULT_DISPLAY_NAME,
|
||||
)),
|
||||
room.duplicate(true),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,9 @@ static var _invisible_edge_pattern: RegEx = RegEx.create_from_string(
|
|||
static var _visible_content_pattern: RegEx = RegEx.create_from_string(
|
||||
"[^\\s\\p{Z}\\p{Cf}\\x{2800}]"
|
||||
)
|
||||
static var _roleplay_separator_pattern: RegEx = RegEx.create_from_string(
|
||||
"^[\\s\\p{Z}\\p{Cf}\\x{2800}]"
|
||||
)
|
||||
|
||||
enum Kind { PLAYER, SYSTEM }
|
||||
|
||||
|
|
@ -40,12 +43,40 @@ static func sanitize_body(value: Variant) -> String:
|
|||
return result
|
||||
|
||||
|
||||
static func is_roleplay_body(value: Variant) -> bool:
|
||||
var body: String = sanitize_body(value)
|
||||
if body.length() < 3 or body.left(3).to_lower() != "/me":
|
||||
return false
|
||||
return (
|
||||
body.length() == 3
|
||||
or _roleplay_separator_pattern.search(body.substr(3, 1)) != null
|
||||
)
|
||||
|
||||
|
||||
static func roleplay_action(value: Variant) -> String:
|
||||
var body: String = sanitize_body(value)
|
||||
if not is_roleplay_body(body):
|
||||
return ""
|
||||
return sanitize_body(body.substr(3))
|
||||
|
||||
|
||||
static func valid_user_body(value: Variant) -> bool:
|
||||
var body: String = sanitize_body(value)
|
||||
return (
|
||||
not body.is_empty()
|
||||
and (
|
||||
not is_roleplay_body(body)
|
||||
or not roleplay_action(body).is_empty()
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
static func validate_request(data: Variant) -> bool:
|
||||
return (
|
||||
typeof(data) == TYPE_DICTIONARY
|
||||
and _valid_id(data.get("request_id"))
|
||||
and _valid_id(data.get("session_id"))
|
||||
and not sanitize_body(data.get("body")).is_empty()
|
||||
and valid_user_body(data.get("body"))
|
||||
and NetworkIdentityCrypto.valid_fingerprint(
|
||||
data.get("sender_fingerprint")
|
||||
)
|
||||
|
|
@ -67,7 +98,16 @@ static func validate_message(data: Variant) -> bool:
|
|||
and typeof(data.get("sender_peer_id")) == TYPE_INT
|
||||
and typeof(data.get("sender_display_name")) == TYPE_STRING
|
||||
and str(data["sender_display_name"]).length() <= 24
|
||||
and not sanitize_body(data.get("body")).is_empty()
|
||||
and (
|
||||
(
|
||||
int(data["kind"]) == Kind.SYSTEM
|
||||
and not sanitize_body(data.get("body")).is_empty()
|
||||
)
|
||||
or (
|
||||
int(data["kind"]) == Kind.PLAYER
|
||||
and valid_user_body(data.get("body"))
|
||||
)
|
||||
)
|
||||
and NetworkIdentityCrypto.valid_fingerprint(
|
||||
data.get("sender_fingerprint")
|
||||
)
|
||||
|
|
|
|||
|
|
@ -115,7 +115,10 @@ func send_local_message(
|
|||
send_rejected.emit("Chat is unavailable.")
|
||||
return false
|
||||
var clean := NetworkChatProtocol.sanitize_body(body)
|
||||
if clean.is_empty():
|
||||
if not NetworkChatProtocol.valid_user_body(clean):
|
||||
if NetworkChatProtocol.is_roleplay_body(clean):
|
||||
send_rejected.emit("Usage: /me [action].")
|
||||
return false
|
||||
send_rejected.emit("Message is empty or too long.")
|
||||
return false
|
||||
var request := {
|
||||
|
|
@ -412,6 +415,7 @@ func _apply_message(
|
|||
return
|
||||
var kind := int(data["kind"])
|
||||
var valid_signature := false
|
||||
var canonical_sender_name: String = ""
|
||||
if kind == NetworkChatProtocol.Kind.SYSTEM:
|
||||
valid_signature = _session.verify_host_action(
|
||||
"chat_system",
|
||||
|
|
@ -424,6 +428,7 @@ func _apply_message(
|
|||
if record == null:
|
||||
_queue_pending_peer_message(sender_id, data, emit_live_signals)
|
||||
return
|
||||
canonical_sender_name = record.display_name.left(24)
|
||||
valid_signature = (
|
||||
record.identity_fingerprint == str(data["sender_fingerprint"])
|
||||
and _session.verify_peer_action(
|
||||
|
|
@ -437,6 +442,10 @@ func _apply_message(
|
|||
return
|
||||
_seen_messages[data["message_id"]] = true
|
||||
var stored_message := data.duplicate(true)
|
||||
if kind == NetworkChatProtocol.Kind.PLAYER:
|
||||
# The authenticated peer registry, not sender-controlled message data,
|
||||
# owns the name shown beside chat and roleplay actions.
|
||||
stored_message["sender_display_name"] = canonical_sender_name
|
||||
# Suppression is immutable for this received copy. Relationship changes
|
||||
# later must never resurrect text that was hidden on arrival.
|
||||
stored_message["locally_suppressed"] = (
|
||||
|
|
@ -548,7 +557,9 @@ func _on_peer_removed(peer_id: int) -> void:
|
|||
_pending_peer_messages.erase(peer_id)
|
||||
if not _session.is_host():
|
||||
return
|
||||
var display_name: String = _peer_names.get(peer_id, "Player")
|
||||
var display_name: String = _peer_names.get(
|
||||
peer_id, NetworkProfilePreferences.DEFAULT_DISPLAY_NAME
|
||||
)
|
||||
_peer_names.erase(peer_id)
|
||||
_broadcast(_make_message(
|
||||
NetworkChatProtocol.Kind.SYSTEM, 0, "", "%s left." % display_name
|
||||
|
|
|
|||
|
|
@ -152,7 +152,10 @@ func is_local_recipient(letter: Dictionary) -> bool:
|
|||
|
||||
func get_local_display_name() -> String:
|
||||
var record := _session.get_peer_record(_session.get_local_peer_id())
|
||||
return record.display_name if record != null else "Player"
|
||||
return (
|
||||
record.display_name
|
||||
if record != null else NetworkProfilePreferences.DEFAULT_DISPLAY_NAME
|
||||
)
|
||||
|
||||
|
||||
func get_recipient_choices() -> Array[Dictionary]:
|
||||
|
|
|
|||
|
|
@ -477,7 +477,10 @@ func request_ban(peer_id: int, fingerprint: String) -> void:
|
|||
if not _valid_operator_sender(sender_id):
|
||||
return
|
||||
var record: PeerRegistry.PeerRecord = _session.get_peer_record(peer_id)
|
||||
var display_name: String = record.display_name if record != null else "Player"
|
||||
var display_name: String = (
|
||||
record.display_name
|
||||
if record != null else NetworkProfilePreferences.DEFAULT_DISPLAY_NAME
|
||||
)
|
||||
var ok: bool = _ban_on_host(
|
||||
peer_id, fingerprint, display_name, -1, false
|
||||
)
|
||||
|
|
@ -544,7 +547,10 @@ func receive_ban_snapshot(records: Array) -> void:
|
|||
var record: Dictionary = value
|
||||
var fingerprint: String = str(record.get("target_fingerprint", ""))
|
||||
var display_name: String = str(
|
||||
record.get("last_known_display_name", "Player")
|
||||
record.get(
|
||||
"last_known_display_name",
|
||||
NetworkProfilePreferences.DEFAULT_DISPLAY_NAME,
|
||||
)
|
||||
).strip_edges().left(NetworkProtocol.MAX_DISPLAY_NAME_LENGTH)
|
||||
if (
|
||||
not NetworkIdentityCrypto.valid_fingerprint(fingerprint)
|
||||
|
|
|
|||
|
|
@ -1,12 +1,17 @@
|
|||
class_name NetworkProfilePreferences
|
||||
extends Node
|
||||
|
||||
signal profile_changed
|
||||
|
||||
const FORMAT_VERSION: int = 1
|
||||
const DEFAULT_DISPLAY_NAME: String = "stray"
|
||||
const DEFAULT_TITLE: String = "newbie"
|
||||
const VoiceProfilesType = preload(
|
||||
"res://player/animalese_voice_profiles.gd"
|
||||
)
|
||||
var profile_id: String = ""
|
||||
var display_name: String = "Player"
|
||||
var display_name: String = DEFAULT_DISPLAY_NAME
|
||||
var title: String = DEFAULT_TITLE
|
||||
var voice_id: String = VoiceProfilesType.DEFAULT_ID
|
||||
var speech_speed_id: String = VoiceProfilesType.DEFAULT_SPEED_ID
|
||||
var call_id: String = VoiceProfilesType.DEFAULT_CALL_ID
|
||||
|
|
@ -56,7 +61,8 @@ func load_or_create() -> bool:
|
|||
int(Time.get_unix_time_from_system()),
|
||||
Time.get_ticks_usec(),
|
||||
]
|
||||
display_name = "Player"
|
||||
display_name = DEFAULT_DISPLAY_NAME
|
||||
title = DEFAULT_TITLE
|
||||
voice_id = VoiceProfilesType.DEFAULT_ID
|
||||
speech_speed_id = VoiceProfilesType.DEFAULT_SPEED_ID
|
||||
call_id = VoiceProfilesType.DEFAULT_CALL_ID
|
||||
|
|
@ -65,6 +71,113 @@ func load_or_create() -> bool:
|
|||
return _save_atomic()
|
||||
|
||||
|
||||
func to_save_data() -> Dictionary:
|
||||
return {
|
||||
"profile_id": profile_id,
|
||||
"display_name": display_name,
|
||||
"title": title,
|
||||
"voice_id": voice_id,
|
||||
"speech_speed_id": speech_speed_id,
|
||||
"call_id": call_id,
|
||||
"sample_set_id": sample_set_id,
|
||||
"created_at_unix": created_at_unix,
|
||||
}
|
||||
|
||||
|
||||
static func validate_save_data(value: Variant) -> bool:
|
||||
if typeof(value) != TYPE_DICTIONARY:
|
||||
return false
|
||||
var data: Dictionary = value
|
||||
if (
|
||||
typeof(data.get("profile_id")) != TYPE_STRING
|
||||
or typeof(data.get("display_name")) != TYPE_STRING
|
||||
or (
|
||||
data.has("title")
|
||||
and typeof(data.get("title")) != TYPE_STRING
|
||||
)
|
||||
or typeof(data.get("created_at_unix")) not in [TYPE_INT, TYPE_FLOAT]
|
||||
):
|
||||
return false
|
||||
var saved_profile_id := str(data.get("profile_id", ""))
|
||||
var saved_name := str(data.get("display_name", ""))
|
||||
var saved_title := str(data.get("title", DEFAULT_TITLE))
|
||||
return (
|
||||
not saved_profile_id.is_empty()
|
||||
and saved_profile_id.length() <= NetworkProtocol.MAX_PROFILE_ID_LENGTH
|
||||
and is_valid_display_name(saved_name)
|
||||
and is_valid_title(saved_title)
|
||||
and VoiceProfilesType.is_valid(str(data.get(
|
||||
"voice_id",
|
||||
VoiceProfilesType.DEFAULT_ID,
|
||||
)))
|
||||
and VoiceProfilesType.is_valid_speed(str(data.get(
|
||||
"speech_speed_id",
|
||||
VoiceProfilesType.DEFAULT_SPEED_ID,
|
||||
)))
|
||||
and VoiceProfilesType.is_valid_call(str(data.get(
|
||||
"call_id",
|
||||
VoiceProfilesType.DEFAULT_CALL_ID,
|
||||
)))
|
||||
and VoiceProfilesType.is_valid_sample_set(str(data.get(
|
||||
"sample_set_id",
|
||||
VoiceProfilesType.DEFAULT_SAMPLE_SET_ID,
|
||||
)))
|
||||
and int(data.get("created_at_unix", -1)) >= 0
|
||||
)
|
||||
|
||||
|
||||
func restore_from_save_data(
|
||||
value: Variant,
|
||||
persist_to_profile_file: bool = true,
|
||||
) -> bool:
|
||||
if not validate_save_data(value):
|
||||
return false
|
||||
var data: Dictionary = value
|
||||
var previous: Dictionary = to_save_data()
|
||||
profile_id = str(data["profile_id"])
|
||||
display_name = str(data["display_name"]).strip_edges()
|
||||
title = str(data.get("title", DEFAULT_TITLE)).strip_edges()
|
||||
voice_id = str(data.get("voice_id", VoiceProfilesType.DEFAULT_ID))
|
||||
speech_speed_id = str(data.get(
|
||||
"speech_speed_id",
|
||||
VoiceProfilesType.DEFAULT_SPEED_ID,
|
||||
))
|
||||
call_id = str(data.get("call_id", VoiceProfilesType.DEFAULT_CALL_ID))
|
||||
sample_set_id = str(data.get(
|
||||
"sample_set_id",
|
||||
VoiceProfilesType.DEFAULT_SAMPLE_SET_ID,
|
||||
))
|
||||
created_at_unix = int(data["created_at_unix"])
|
||||
if persist_to_profile_file and not _save_atomic():
|
||||
_assign_save_data(previous)
|
||||
return false
|
||||
if to_save_data() != previous:
|
||||
profile_changed.emit()
|
||||
return true
|
||||
|
||||
|
||||
func reset_to_defaults(persist_to_profile_file: bool = true) -> bool:
|
||||
var previous: Dictionary = to_save_data()
|
||||
profile_id = Crypto.new().generate_random_bytes(16).hex_encode()
|
||||
if profile_id.is_empty():
|
||||
profile_id = "%d-%d" % [
|
||||
int(Time.get_unix_time_from_system()),
|
||||
Time.get_ticks_usec(),
|
||||
]
|
||||
display_name = DEFAULT_DISPLAY_NAME
|
||||
title = DEFAULT_TITLE
|
||||
voice_id = VoiceProfilesType.DEFAULT_ID
|
||||
speech_speed_id = VoiceProfilesType.DEFAULT_SPEED_ID
|
||||
call_id = VoiceProfilesType.DEFAULT_CALL_ID
|
||||
sample_set_id = VoiceProfilesType.DEFAULT_SAMPLE_SET_ID
|
||||
created_at_unix = int(Time.get_unix_time_from_system())
|
||||
if persist_to_profile_file and not _save_atomic():
|
||||
_assign_save_data(previous)
|
||||
return false
|
||||
profile_changed.emit()
|
||||
return true
|
||||
|
||||
|
||||
func set_display_name(value: String) -> bool:
|
||||
return set_profile_identity(
|
||||
value,
|
||||
|
|
@ -72,6 +185,7 @@ func set_display_name(value: String) -> bool:
|
|||
speech_speed_id,
|
||||
call_id,
|
||||
sample_set_id,
|
||||
title,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -81,10 +195,15 @@ func set_profile_identity(
|
|||
selected_speech_speed_id: String,
|
||||
selected_call_id: String,
|
||||
selected_sample_set_id: String = VoiceProfilesType.DEFAULT_SAMPLE_SET_ID,
|
||||
selected_title: String = "",
|
||||
) -> bool:
|
||||
var clean_name: String = value.strip_edges()
|
||||
var clean_title: String = (
|
||||
title if selected_title.is_empty() else selected_title.strip_edges()
|
||||
)
|
||||
if (
|
||||
not is_valid_display_name(clean_name)
|
||||
or not is_valid_title(clean_title)
|
||||
or not VoiceProfilesType.is_valid(selected_voice_id)
|
||||
or not VoiceProfilesType.is_valid_speed(selected_speech_speed_id)
|
||||
or not VoiceProfilesType.is_valid_call(selected_call_id)
|
||||
|
|
@ -92,18 +211,30 @@ func set_profile_identity(
|
|||
):
|
||||
return false
|
||||
var previous_name: String = display_name
|
||||
var previous_title: String = title
|
||||
var previous_voice_id: String = voice_id
|
||||
var previous_speech_speed_id: String = speech_speed_id
|
||||
var previous_call_id: String = call_id
|
||||
var previous_sample_set_id: String = sample_set_id
|
||||
display_name = clean_name
|
||||
title = clean_title
|
||||
voice_id = selected_voice_id
|
||||
speech_speed_id = selected_speech_speed_id
|
||||
call_id = selected_call_id
|
||||
sample_set_id = selected_sample_set_id
|
||||
if _save_atomic():
|
||||
if (
|
||||
display_name != previous_name
|
||||
or voice_id != previous_voice_id
|
||||
or title != previous_title
|
||||
or speech_speed_id != previous_speech_speed_id
|
||||
or call_id != previous_call_id
|
||||
or sample_set_id != previous_sample_set_id
|
||||
):
|
||||
profile_changed.emit()
|
||||
return true
|
||||
display_name = previous_name
|
||||
title = previous_title
|
||||
voice_id = previous_voice_id
|
||||
speech_speed_id = previous_speech_speed_id
|
||||
call_id = previous_call_id
|
||||
|
|
@ -111,6 +242,23 @@ func set_profile_identity(
|
|||
return false
|
||||
|
||||
|
||||
func _assign_save_data(data: Dictionary) -> void:
|
||||
profile_id = str(data.get("profile_id", ""))
|
||||
display_name = str(data.get("display_name", DEFAULT_DISPLAY_NAME))
|
||||
title = str(data.get("title", DEFAULT_TITLE))
|
||||
voice_id = str(data.get("voice_id", VoiceProfilesType.DEFAULT_ID))
|
||||
speech_speed_id = str(data.get(
|
||||
"speech_speed_id",
|
||||
VoiceProfilesType.DEFAULT_SPEED_ID,
|
||||
))
|
||||
call_id = str(data.get("call_id", VoiceProfilesType.DEFAULT_CALL_ID))
|
||||
sample_set_id = str(data.get(
|
||||
"sample_set_id",
|
||||
VoiceProfilesType.DEFAULT_SAMPLE_SET_ID,
|
||||
))
|
||||
created_at_unix = int(data.get("created_at_unix", 0))
|
||||
|
||||
|
||||
static func is_valid_display_name(value: String) -> bool:
|
||||
var clean_name := value.strip_edges()
|
||||
if clean_name.is_empty() or clean_name.length() > 24:
|
||||
|
|
@ -122,6 +270,17 @@ static func is_valid_display_name(value: String) -> bool:
|
|||
return true
|
||||
|
||||
|
||||
static func is_valid_title(value: String) -> bool:
|
||||
var clean_title := value.strip_edges()
|
||||
if clean_title.is_empty() or clean_title.length() > NetworkProtocol.MAX_PLAYER_TITLE_LENGTH:
|
||||
return false
|
||||
for index: int in clean_title.length():
|
||||
var codepoint: int = clean_title.unicode_at(index)
|
||||
if codepoint < 32 or codepoint == 127:
|
||||
return false
|
||||
return true
|
||||
|
||||
|
||||
func _load_existing() -> bool:
|
||||
var file := FileAccess.open(_profile_path, FileAccess.READ)
|
||||
if file == null:
|
||||
|
|
@ -142,20 +301,27 @@ func _load_existing() -> bool:
|
|||
data.get("format_version") != FORMAT_VERSION
|
||||
or typeof(data.get("profile_id")) != TYPE_STRING
|
||||
or typeof(data.get("display_name")) != TYPE_STRING
|
||||
or (
|
||||
data.has("title")
|
||||
and typeof(data.get("title")) != TYPE_STRING
|
||||
)
|
||||
or typeof(data.get("created_at_unix")) not in [TYPE_INT, TYPE_FLOAT]
|
||||
):
|
||||
return false
|
||||
var loaded_id: String = data["profile_id"]
|
||||
var loaded_name: String = data["display_name"]
|
||||
var loaded_title := str(data.get("title", DEFAULT_TITLE))
|
||||
if (
|
||||
loaded_id.is_empty()
|
||||
or loaded_id.length() > NetworkProtocol.MAX_PROFILE_ID_LENGTH
|
||||
or loaded_name.is_empty()
|
||||
or loaded_name.length() > NetworkProtocol.MAX_DISPLAY_NAME_LENGTH
|
||||
or not is_valid_title(loaded_title)
|
||||
):
|
||||
return false
|
||||
profile_id = loaded_id
|
||||
display_name = loaded_name
|
||||
title = loaded_title.strip_edges()
|
||||
voice_id = VoiceProfilesType.sanitized_id(
|
||||
str(data.get("voice_id", VoiceProfilesType.DEFAULT_ID))
|
||||
)
|
||||
|
|
@ -180,6 +346,7 @@ func _save_atomic() -> bool:
|
|||
"format_version": FORMAT_VERSION,
|
||||
"profile_id": profile_id,
|
||||
"display_name": display_name,
|
||||
"title": title,
|
||||
"voice_id": voice_id,
|
||||
"speech_speed_id": speech_speed_id,
|
||||
"call_id": call_id,
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@ static func valid_check_request(data: Variant) -> bool:
|
|||
static func valid_apply_request(data: Variant) -> bool:
|
||||
return (
|
||||
valid_check_request(data)
|
||||
and typeof(data.get("player_title")) == TYPE_STRING
|
||||
and NetworkProfilePreferences.is_valid_title(data["player_title"])
|
||||
and typeof(data.get("appearance")) == TYPE_DICTIONARY
|
||||
and valid_snapshot(data["appearance"])
|
||||
and typeof(data.get("use_anyway")) == TYPE_BOOL
|
||||
|
|
@ -63,6 +65,7 @@ static func signature_fields(data: Dictionary) -> Array:
|
|||
str(data.get("request_id", "")),
|
||||
str(data.get("sender_fingerprint", "")),
|
||||
str(data.get("display_name", "")),
|
||||
str(data.get("player_title", "")),
|
||||
]
|
||||
result.append_array(
|
||||
CharacterCustomizationCatalog.appearance_signature_values(appearance)
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ signal profile_snapshot_changed(
|
|||
display_name: String,
|
||||
appearance: Dictionary,
|
||||
)
|
||||
signal profile_title_changed(peer_id: int, title: String)
|
||||
|
||||
var _session: NetworkSession
|
||||
var _preferences: NetworkProfilePreferences
|
||||
|
|
@ -26,6 +27,7 @@ var _pending_voice_ids: Dictionary[String, String] = {}
|
|||
var _pending_speech_speed_ids: Dictionary[String, String] = {}
|
||||
var _pending_call_ids: Dictionary[String, String] = {}
|
||||
var _pending_sample_set_ids: Dictionary[String, String] = {}
|
||||
var _pending_titles: Dictionary[String, String] = {}
|
||||
var _host_pending_apply: Dictionary[String, Dictionary] = {}
|
||||
var _latest_check_id: String = ""
|
||||
var _latest_check_name: String = ""
|
||||
|
|
@ -42,6 +44,16 @@ func setup(
|
|||
_appearance_store = appearance_store
|
||||
_spawn_service = spawn_service
|
||||
_appearance_store.load_preferences()
|
||||
if not _preferences.profile_changed.is_connected(
|
||||
_on_local_profile_store_changed
|
||||
):
|
||||
_preferences.profile_changed.connect(_on_local_profile_store_changed)
|
||||
if not _appearance_store.appearance_changed.is_connected(
|
||||
_on_local_appearance_store_changed
|
||||
):
|
||||
_appearance_store.appearance_changed.connect(
|
||||
_on_local_appearance_store_changed
|
||||
)
|
||||
_session.peer_authenticated.connect(_on_peer_authenticated)
|
||||
_session.peer_removed.connect(_on_peer_removed)
|
||||
_session.peer_display_name_changed.connect(_on_peer_display_name_changed)
|
||||
|
|
@ -51,6 +63,20 @@ func setup(
|
|||
_apply_local_voice_to_avatar()
|
||||
|
||||
|
||||
func _on_local_profile_store_changed() -> void:
|
||||
_apply_local_voice_to_avatar()
|
||||
|
||||
|
||||
func _on_local_appearance_store_changed() -> void:
|
||||
var snapshot: Dictionary = _appearance_store.get_snapshot()
|
||||
var local_peer_id: int = (
|
||||
_session.get_local_peer_id() if _session != null else 1
|
||||
)
|
||||
_apply_to_avatar(local_peer_id, snapshot)
|
||||
if _session != null:
|
||||
_session.set_local_appearance_snapshot(snapshot)
|
||||
|
||||
|
||||
func reload_persisted_profile() -> bool:
|
||||
if (
|
||||
not _preferences.load_or_create()
|
||||
|
|
@ -66,6 +92,10 @@ func get_persisted_name() -> String:
|
|||
return _preferences.display_name
|
||||
|
||||
|
||||
func get_persisted_title() -> String:
|
||||
return _preferences.title
|
||||
|
||||
|
||||
func get_persisted_appearance() -> Dictionary:
|
||||
return _appearance_store.get_snapshot()
|
||||
|
||||
|
|
@ -107,6 +137,7 @@ func preview_appearance(appearance: Dictionary) -> bool:
|
|||
local_peer_id,
|
||||
_preferences.display_name,
|
||||
snapshot,
|
||||
_preferences.title,
|
||||
)
|
||||
var authorization := _make_appearance_preview_authorization(snapshot)
|
||||
var local_record := _session.get_peer_record(local_peer_id)
|
||||
|
|
@ -179,10 +210,16 @@ func apply_profile(
|
|||
speech_speed_id: String = VoiceProfilesType.DEFAULT_SPEED_ID,
|
||||
call_id: String = VoiceProfilesType.DEFAULT_CALL_ID,
|
||||
sample_set_id: String = VoiceProfilesType.DEFAULT_SAMPLE_SET_ID,
|
||||
player_title: String = "",
|
||||
) -> bool:
|
||||
var clean_name := display_name.strip_edges()
|
||||
var clean_title := (
|
||||
_preferences.title
|
||||
if player_title.is_empty() else player_title.strip_edges()
|
||||
)
|
||||
if (
|
||||
not NetworkProfilePreferences.is_valid_display_name(clean_name)
|
||||
or not NetworkProfilePreferences.is_valid_title(clean_title)
|
||||
or not CharacterCustomizationCatalog.validate_snapshot(appearance)
|
||||
or not VoiceProfilesType.is_valid(voice_id)
|
||||
or not VoiceProfilesType.is_valid_speed(speech_speed_id)
|
||||
|
|
@ -196,6 +233,7 @@ func apply_profile(
|
|||
"request_id": request_id,
|
||||
"session_id": _session.get_session_id() if _session != null else "",
|
||||
"display_name": clean_name,
|
||||
"player_title": clean_title,
|
||||
"appearance": appearance.duplicate(true),
|
||||
"use_anyway": use_anyway,
|
||||
"sender_fingerprint": _session.get_local_identity_fingerprint(),
|
||||
|
|
@ -208,6 +246,7 @@ func apply_profile(
|
|||
_pending_speech_speed_ids[request_id] = speech_speed_id
|
||||
_pending_call_ids[request_id] = call_id
|
||||
_pending_sample_set_ids[request_id] = sample_set_id
|
||||
_pending_titles[request_id] = clean_title
|
||||
if _session == null or not _session.is_gameplay_session_active():
|
||||
_apply_local_result(request_id, true, "", false, PackedStringArray())
|
||||
elif _session.is_host():
|
||||
|
|
@ -303,6 +342,7 @@ func submit_appearance_preview(data: Dictionary) -> void:
|
|||
sender_id,
|
||||
record.display_name,
|
||||
appearance,
|
||||
record.title,
|
||||
)
|
||||
_apply_to_avatar(sender_id, appearance)
|
||||
_broadcast_appearance_preview_to_supported_peers(sender_id, appearance)
|
||||
|
|
@ -310,6 +350,7 @@ func submit_appearance_preview(data: Dictionary) -> void:
|
|||
|
||||
func _process_apply_request(peer_id: int, data: Dictionary) -> void:
|
||||
var display_name: String = str(data["display_name"]).strip_edges()
|
||||
var player_title: String = str(data["player_title"]).strip_edges()
|
||||
var conflict: bool = _name_conflicts(peer_id, display_name)
|
||||
var accepted: bool = not conflict or bool(data["use_anyway"])
|
||||
var suggestions: PackedStringArray = (
|
||||
|
|
@ -325,7 +366,8 @@ func _process_apply_request(peer_id: int, data: Dictionary) -> void:
|
|||
if accepted:
|
||||
_host_pending_apply[str(data["request_id"])] = {
|
||||
"peer_id": peer_id,
|
||||
"display_name": display_name,
|
||||
"display_name": display_name,
|
||||
"player_title": player_title,
|
||||
"appearance": Dictionary(data["appearance"]).duplicate(true),
|
||||
"authorization": {
|
||||
"request_id": data["request_id"],
|
||||
|
|
@ -356,8 +398,11 @@ func confirm_profile_saved(request_id: String) -> void:
|
|||
return
|
||||
_host_pending_apply.erase(request_id)
|
||||
var display_name: String = str(pending["display_name"])
|
||||
var player_title: String = str(pending["player_title"])
|
||||
var appearance: Dictionary = Dictionary(pending["appearance"])
|
||||
_session.apply_canonical_profile(sender_id, display_name, appearance)
|
||||
_session.apply_canonical_profile(
|
||||
sender_id, display_name, appearance, player_title
|
||||
)
|
||||
var record: PeerRegistry.PeerRecord = _session.get_peer_record(sender_id)
|
||||
if record != null:
|
||||
record.profile_authorization = pending.get("authorization", {}).duplicate(true)
|
||||
|
|
@ -365,6 +410,7 @@ func confirm_profile_saved(request_id: String) -> void:
|
|||
broadcast_profile_snapshot.rpc(
|
||||
sender_id,
|
||||
display_name,
|
||||
player_title,
|
||||
appearance,
|
||||
pending.get("authorization", {}),
|
||||
)
|
||||
|
|
@ -405,10 +451,12 @@ func _apply_local_result(
|
|||
_pending_speech_speed_ids.erase(request_id)
|
||||
_pending_call_ids.erase(request_id)
|
||||
_pending_sample_set_ids.erase(request_id)
|
||||
_pending_titles.erase(request_id)
|
||||
conflict_result.emit(request_id, conflict, suggestions)
|
||||
apply_finished.emit(false, message)
|
||||
return
|
||||
var previous_name := _preferences.display_name
|
||||
var previous_title := _preferences.title
|
||||
var previous_voice_id := _preferences.voice_id
|
||||
var previous_speech_speed_id := _preferences.speech_speed_id
|
||||
var previous_call_id := _preferences.call_id
|
||||
|
|
@ -429,18 +477,24 @@ func _apply_local_result(
|
|||
request_id,
|
||||
VoiceProfilesType.DEFAULT_SAMPLE_SET_ID,
|
||||
)
|
||||
var requested_title: String = _pending_titles.get(
|
||||
request_id,
|
||||
NetworkProfilePreferences.DEFAULT_TITLE,
|
||||
)
|
||||
if not _preferences.set_profile_identity(
|
||||
str(request["display_name"]),
|
||||
requested_voice_id,
|
||||
requested_speech_speed_id,
|
||||
requested_call_id,
|
||||
requested_sample_set_id,
|
||||
requested_title,
|
||||
):
|
||||
_pending_apply.erase(request_id)
|
||||
_pending_voice_ids.erase(request_id)
|
||||
_pending_speech_speed_ids.erase(request_id)
|
||||
_pending_call_ids.erase(request_id)
|
||||
_pending_sample_set_ids.erase(request_id)
|
||||
_pending_titles.erase(request_id)
|
||||
apply_finished.emit(false, "Profile could not be saved.")
|
||||
return
|
||||
if not _appearance_store.save_snapshot(request["appearance"]):
|
||||
|
|
@ -450,12 +504,14 @@ func _apply_local_result(
|
|||
previous_speech_speed_id,
|
||||
previous_call_id,
|
||||
previous_sample_set_id,
|
||||
previous_title,
|
||||
)
|
||||
_pending_apply.erase(request_id)
|
||||
_pending_voice_ids.erase(request_id)
|
||||
_pending_speech_speed_ids.erase(request_id)
|
||||
_pending_call_ids.erase(request_id)
|
||||
_pending_sample_set_ids.erase(request_id)
|
||||
_pending_titles.erase(request_id)
|
||||
apply_finished.emit(false, "Profile could not be saved.")
|
||||
return
|
||||
_pending_apply.erase(request_id)
|
||||
|
|
@ -463,6 +519,7 @@ func _apply_local_result(
|
|||
_pending_speech_speed_ids.erase(request_id)
|
||||
_pending_call_ids.erase(request_id)
|
||||
_pending_sample_set_ids.erase(request_id)
|
||||
_pending_titles.erase(request_id)
|
||||
if _session != null:
|
||||
_session.set_local_appearance_snapshot(_appearance_store.get_snapshot())
|
||||
if _session != null and _session.is_gameplay_session_active():
|
||||
|
|
@ -471,6 +528,7 @@ func _apply_local_result(
|
|||
_session.get_local_peer_id(),
|
||||
_preferences.display_name,
|
||||
_appearance_store.get_snapshot(),
|
||||
_preferences.title,
|
||||
)
|
||||
var record := _session.get_peer_record(
|
||||
_session.get_local_peer_id()
|
||||
|
|
@ -480,6 +538,7 @@ func _apply_local_result(
|
|||
broadcast_profile_snapshot.rpc(
|
||||
_session.get_local_peer_id(),
|
||||
_preferences.display_name,
|
||||
_preferences.title,
|
||||
_appearance_store.get_snapshot(),
|
||||
request,
|
||||
)
|
||||
|
|
@ -499,11 +558,13 @@ func _apply_local_result(
|
|||
func broadcast_profile_snapshot(
|
||||
peer_id: int,
|
||||
display_name: String,
|
||||
player_title: String,
|
||||
appearance: Dictionary,
|
||||
authorization: Dictionary = {},
|
||||
) -> void:
|
||||
if (
|
||||
not NetworkProfilePreferences.is_valid_display_name(display_name)
|
||||
or not NetworkProfilePreferences.is_valid_title(player_title)
|
||||
or not CharacterCustomizationCatalog.validate_snapshot(appearance)
|
||||
):
|
||||
return
|
||||
|
|
@ -514,12 +575,16 @@ func broadcast_profile_snapshot(
|
|||
display_name,
|
||||
appearance,
|
||||
authorization,
|
||||
player_title,
|
||||
)
|
||||
):
|
||||
return
|
||||
_session.apply_canonical_profile(peer_id, display_name, appearance)
|
||||
_session.apply_canonical_profile(
|
||||
peer_id, display_name, appearance, player_title
|
||||
)
|
||||
_apply_to_avatar(peer_id, appearance)
|
||||
profile_snapshot_changed.emit(peer_id, display_name, appearance.duplicate(true))
|
||||
profile_title_changed.emit(peer_id, player_title)
|
||||
|
||||
|
||||
@rpc("authority", "call_remote", "reliable", NetworkProfileProtocol.RELIABLE_CHANNEL)
|
||||
|
|
@ -533,7 +598,9 @@ func broadcast_appearance_preview(
|
|||
if record == null:
|
||||
return
|
||||
var snapshot := CharacterCustomizationCatalog.sanitized_snapshot(appearance)
|
||||
_session.apply_canonical_profile(peer_id, record.display_name, snapshot)
|
||||
_session.apply_canonical_profile(
|
||||
peer_id, record.display_name, snapshot, record.title
|
||||
)
|
||||
_apply_to_avatar(peer_id, snapshot)
|
||||
profile_snapshot_changed.emit(
|
||||
peer_id,
|
||||
|
|
@ -575,6 +642,7 @@ func _on_peer_authenticated(peer_id: int, _display_name: String) -> void:
|
|||
peer_id,
|
||||
existing_id,
|
||||
record.display_name,
|
||||
record.title,
|
||||
record.appearance_snapshot,
|
||||
record.profile_authorization,
|
||||
)
|
||||
|
|
@ -638,6 +706,7 @@ func _on_session_state_changed(state: NetworkSession.State) -> void:
|
|||
_pending_speech_speed_ids.clear()
|
||||
_pending_call_ids.clear()
|
||||
_pending_sample_set_ids.clear()
|
||||
_pending_titles.clear()
|
||||
_host_pending_apply.clear()
|
||||
_latest_check_id = ""
|
||||
_latest_check_name = ""
|
||||
|
|
|
|||
|
|
@ -3,10 +3,11 @@ extends RefCounted
|
|||
|
||||
const WorldLayoutType = preload("res://world/world_layout.gd")
|
||||
|
||||
const PROTOCOL_VERSION: int = 12
|
||||
const PROTOCOL_VERSION: int = 13
|
||||
const GAME_BUILD: String = "prealpha"
|
||||
const MAX_GAME_VERSION_LENGTH: int = 64
|
||||
const MAX_DISPLAY_NAME_LENGTH: int = 24
|
||||
const MAX_PLAYER_TITLE_LENGTH: int = 24
|
||||
const MAX_PROFILE_ID_LENGTH: int = 96
|
||||
const MAX_NONCE_LENGTH: int = 96
|
||||
const MAX_PUBLIC_KEY_LENGTH: int = 8192
|
||||
|
|
@ -183,6 +184,7 @@ static func make_client_hello(
|
|||
cosmetic_snapshot: Dictionary = {},
|
||||
identity_fingerprint: String = "",
|
||||
identity_signature: PackedByteArray = PackedByteArray(),
|
||||
player_title: String = NetworkProfilePreferences.DEFAULT_TITLE,
|
||||
) -> Dictionary:
|
||||
return {
|
||||
"protocol_version": PROTOCOL_VERSION,
|
||||
|
|
@ -190,6 +192,7 @@ static func make_client_hello(
|
|||
"game_build": GAME_BUILD,
|
||||
"local_profile_id": profile_id,
|
||||
"display_name": display_name,
|
||||
"player_title": player_title,
|
||||
"client_nonce": client_nonce,
|
||||
"capability_flags": PackedStringArray([
|
||||
FISH_QUALITY_CAPABILITY,
|
||||
|
|
@ -224,6 +227,7 @@ static func validate_client_hello(data: Variant) -> String:
|
|||
"game_build",
|
||||
"local_profile_id",
|
||||
"display_name",
|
||||
"player_title",
|
||||
"client_nonce",
|
||||
"capability_flags",
|
||||
"cosmetic_snapshot",
|
||||
|
|
@ -246,6 +250,11 @@ static func validate_client_hello(data: Variant) -> String:
|
|||
return "Profile ID is invalid."
|
||||
if typeof(payload["display_name"]) != TYPE_STRING:
|
||||
return "Display name is invalid."
|
||||
if (
|
||||
typeof(payload["player_title"]) != TYPE_STRING
|
||||
or not NetworkProfilePreferences.is_valid_title(payload["player_title"])
|
||||
):
|
||||
return "Player title is invalid."
|
||||
if typeof(payload["client_nonce"]) != TYPE_STRING:
|
||||
return "Client nonce is invalid."
|
||||
if typeof(payload["capability_flags"]) not in [
|
||||
|
|
@ -295,6 +304,7 @@ static func client_profile_fields(data: Dictionary) -> Array:
|
|||
str(data.get("identity_fingerprint", "")),
|
||||
str(data.get("local_profile_id", "")),
|
||||
str(data.get("display_name", "")),
|
||||
str(data.get("player_title", "")),
|
||||
]
|
||||
result.append_array(
|
||||
CharacterCustomizationCatalog.appearance_signature_values(appearance)
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ signal host_openness_changed(is_open: bool)
|
|||
signal session_display_name_changed(display_name: String)
|
||||
signal peer_count_changed(player_count: int, max_players: int)
|
||||
signal peer_display_name_changed(peer_id: int, display_name: String)
|
||||
signal peer_title_changed(peer_id: int, title: String)
|
||||
signal peer_profile_changed(
|
||||
peer_id: int,
|
||||
display_name: String,
|
||||
|
|
@ -354,6 +355,7 @@ func _register_player_host() -> void:
|
|||
NetworkProtocol.HOME_CAPABILITY,
|
||||
NetworkProtocol.HOME_UPGRADE_CAPABILITY,
|
||||
]),
|
||||
_profile.title,
|
||||
)
|
||||
_registry.update_appearance(1, _local_appearance_snapshot)
|
||||
var host_profile_hello := NetworkProtocol.make_client_hello(
|
||||
|
|
@ -362,6 +364,8 @@ func _register_player_host() -> void:
|
|||
NetworkIdentityCrypto.secure_id(32),
|
||||
_local_appearance_snapshot,
|
||||
_player_identity.fingerprint,
|
||||
PackedByteArray(),
|
||||
_profile.title,
|
||||
)
|
||||
host_profile_hello["identity_signature"] = _player_identity.sign(
|
||||
"handshake_client_profile",
|
||||
|
|
@ -931,13 +935,16 @@ func apply_canonical_profile(
|
|||
peer_id: int,
|
||||
display_name: String,
|
||||
appearance: Dictionary,
|
||||
player_title: String = NetworkProfilePreferences.DEFAULT_TITLE,
|
||||
) -> bool:
|
||||
if (
|
||||
not NetworkProfilePreferences.is_valid_display_name(display_name)
|
||||
or not NetworkProfilePreferences.is_valid_title(player_title)
|
||||
or not CharacterCustomizationCatalog.validate_snapshot(appearance)
|
||||
):
|
||||
return false
|
||||
var name_changed := _registry.update_display_name(peer_id, display_name)
|
||||
var title_changed: bool = _registry.update_title(peer_id, player_title)
|
||||
var appearance_changed := _registry.update_appearance(peer_id, appearance)
|
||||
if name_changed:
|
||||
peer_display_name_changed.emit(peer_id, display_name)
|
||||
|
|
@ -945,9 +952,11 @@ func apply_canonical_profile(
|
|||
var avatar: Player = _spawn_service.get_avatar(peer_id)
|
||||
if avatar != null:
|
||||
avatar.apply_appearance_snapshot(appearance)
|
||||
if title_changed:
|
||||
peer_title_changed.emit(peer_id, player_title)
|
||||
if name_changed or appearance_changed:
|
||||
peer_profile_changed.emit(peer_id, display_name, appearance.duplicate(true))
|
||||
return name_changed or appearance_changed
|
||||
return name_changed or title_changed or appearance_changed
|
||||
|
||||
|
||||
@rpc("any_peer", "call_remote", "reliable", 0)
|
||||
|
|
@ -1376,6 +1385,8 @@ func request_client_profile() -> void:
|
|||
_client_nonce,
|
||||
_local_appearance_snapshot,
|
||||
_player_identity.fingerprint,
|
||||
PackedByteArray(),
|
||||
_profile.title,
|
||||
)
|
||||
hello["identity_signature"] = _player_identity.sign(
|
||||
"handshake_client_profile",
|
||||
|
|
@ -1459,6 +1470,7 @@ func submit_client_hello(data: Dictionary) -> void:
|
|||
return
|
||||
var profile_id: String = data["local_profile_id"]
|
||||
var display_name: String = data["display_name"]
|
||||
var player_title: String = data["player_title"]
|
||||
if not NetworkProfilePreferences.is_valid_display_name(display_name):
|
||||
_reject_peer(
|
||||
sender_id,
|
||||
|
|
@ -1473,6 +1485,7 @@ func submit_client_hello(data: Dictionary) -> void:
|
|||
identity["fingerprint"],
|
||||
identity["public_key"],
|
||||
client_capabilities,
|
||||
player_title,
|
||||
):
|
||||
_reject_peer(
|
||||
sender_id,
|
||||
|
|
@ -1515,6 +1528,7 @@ func submit_client_hello(data: Dictionary) -> void:
|
|||
display_name,
|
||||
submitted_appearance.duplicate(true),
|
||||
)
|
||||
peer_title_changed.emit(sender_id, player_title)
|
||||
receive_server_hello.rpc_id(
|
||||
sender_id,
|
||||
NetworkProtocol.make_server_hello(
|
||||
|
|
@ -1691,6 +1705,7 @@ func receive_server_hello(data: Dictionary) -> void:
|
|||
NetworkProtocol.HOME_CAPABILITY,
|
||||
NetworkProtocol.HOME_UPGRADE_CAPABILITY,
|
||||
]),
|
||||
_profile.title,
|
||||
)
|
||||
_registry.update_appearance(local_peer_id, _local_appearance_snapshot)
|
||||
var local_record := _registry.get_peer(local_peer_id)
|
||||
|
|
@ -1777,6 +1792,10 @@ func _apply_spawn_entry(entry: Dictionary) -> void:
|
|||
typeof(entry.get("peer_id")) != TYPE_INT
|
||||
or typeof(entry.get("profile_id")) != TYPE_STRING
|
||||
or typeof(entry.get("display_name")) != TYPE_STRING
|
||||
or typeof(entry.get("player_title")) != TYPE_STRING
|
||||
or not NetworkProfilePreferences.is_valid_title(
|
||||
str(entry["player_title"])
|
||||
)
|
||||
or typeof(entry.get("position")) != TYPE_ARRAY
|
||||
or typeof(entry.get("yaw")) not in [TYPE_FLOAT, TYPE_INT]
|
||||
or typeof(entry.get("sitting")) != TYPE_BOOL
|
||||
|
|
@ -1811,6 +1830,7 @@ func _apply_spawn_entry(entry: Dictionary) -> void:
|
|||
str(entry.get("identity_fingerprint", "")),
|
||||
str(entry.get("identity_public_key", "")),
|
||||
_sanitized_capabilities(entry.get("capability_flags", [])),
|
||||
str(entry["player_title"]),
|
||||
):
|
||||
return
|
||||
peer_was_added = true
|
||||
|
|
@ -1894,6 +1914,10 @@ func _make_spawn_entry(
|
|||
"peer_id": peer_id,
|
||||
"profile_id": record.profile_id if record != null else "",
|
||||
"display_name": display_name,
|
||||
"player_title": (
|
||||
record.title
|
||||
if record != null else NetworkProfilePreferences.DEFAULT_TITLE
|
||||
),
|
||||
"position": [
|
||||
transform.origin.x,
|
||||
transform.origin.y,
|
||||
|
|
@ -1978,6 +2002,7 @@ func verify_profile_authorization(
|
|||
display_name: String,
|
||||
appearance: Dictionary,
|
||||
authorization: Dictionary,
|
||||
player_title: String = NetworkProfilePreferences.DEFAULT_TITLE,
|
||||
) -> bool:
|
||||
var record := _registry.get_peer(peer_id)
|
||||
if record == null:
|
||||
|
|
@ -1986,6 +2011,7 @@ func verify_profile_authorization(
|
|||
peer_id,
|
||||
record.profile_id,
|
||||
display_name,
|
||||
player_title,
|
||||
appearance,
|
||||
record.identity_fingerprint,
|
||||
record.identity_public_key,
|
||||
|
|
@ -2000,6 +2026,7 @@ func _verify_spawn_identity(entry: Dictionary) -> bool:
|
|||
int(entry.get("peer_id", 0)),
|
||||
str(entry.get("profile_id", "")),
|
||||
str(entry.get("display_name", "")),
|
||||
str(entry.get("player_title", NetworkProfilePreferences.DEFAULT_TITLE)),
|
||||
Dictionary(entry.get("appearance", {})),
|
||||
str(entry.get("identity_fingerprint", "")),
|
||||
str(entry.get("identity_public_key", "")),
|
||||
|
|
@ -2011,6 +2038,7 @@ func _verify_profile_authorization_fields(
|
|||
peer_id: int,
|
||||
profile_id: String,
|
||||
display_name: String,
|
||||
player_title: String,
|
||||
appearance: Dictionary,
|
||||
fingerprint: String,
|
||||
identity_public_key: String,
|
||||
|
|
@ -2021,6 +2049,8 @@ func _verify_profile_authorization_fields(
|
|||
)
|
||||
if NetworkIdentityCrypto.fingerprint_public_pem(public_pem) != fingerprint:
|
||||
return false
|
||||
if not NetworkProfilePreferences.is_valid_title(player_title):
|
||||
return false
|
||||
if authorization.is_empty():
|
||||
return peer_id == 1
|
||||
if authorization.get("domain") == "handshake_client_profile":
|
||||
|
|
@ -2031,6 +2061,7 @@ func _verify_profile_authorization_fields(
|
|||
appearance,
|
||||
fingerprint,
|
||||
authorization.get("signature", PackedByteArray()),
|
||||
player_title,
|
||||
)
|
||||
return NetworkIdentityCrypto.verify_fields(
|
||||
NetworkIdentityCrypto.load_public_key(public_pem),
|
||||
|
|
@ -2054,6 +2085,7 @@ func _verify_profile_authorization_fields(
|
|||
return false
|
||||
var signed := authorization.duplicate(true)
|
||||
signed["display_name"] = display_name
|
||||
signed["player_title"] = player_title
|
||||
signed["appearance"] = appearance
|
||||
return NetworkIdentityCrypto.verify_fields(
|
||||
NetworkIdentityCrypto.load_public_key(public_pem),
|
||||
|
|
@ -2182,7 +2214,6 @@ func _maybe_send_local_animation_action() -> void:
|
|||
str(action["id"]),
|
||||
int(action["sequence"]),
|
||||
bool(action.get("paused", false)),
|
||||
avatar.get_network_sitting_intent(),
|
||||
]
|
||||
if signature == _last_local_animation_action_signature:
|
||||
return
|
||||
|
|
@ -2237,9 +2268,9 @@ func submit_movement_animation_action(encoded: Array) -> void:
|
|||
avatar.apply_authoritative_network_animation_action(
|
||||
animation_state["action"]
|
||||
)
|
||||
avatar.apply_authoritative_network_sitting_state(
|
||||
bool(animation_state["sitting"])
|
||||
)
|
||||
# Sitting is authoritative only on sequenced movement input. The legacy bit
|
||||
# remains in this packet's wire shape for protocol compatibility, but acting
|
||||
# on it here would race jump input across two independent ENet channels.
|
||||
|
||||
|
||||
static func _encode_movement_input(data: Dictionary) -> Array:
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ class PeerRecord:
|
|||
var peer_id: int = 0
|
||||
var profile_id: String = ""
|
||||
var display_name: String = ""
|
||||
var title: String = NetworkProfilePreferences.DEFAULT_TITLE
|
||||
var protocol_version: int = 0
|
||||
var joined_at_unix: int = 0
|
||||
var appearance_snapshot: Dictionary = (
|
||||
|
|
@ -30,10 +31,12 @@ func add_peer(
|
|||
identity_fingerprint: String = "",
|
||||
identity_public_key: String = "",
|
||||
capability_flags: PackedStringArray = PackedStringArray(),
|
||||
player_title: String = NetworkProfilePreferences.DEFAULT_TITLE,
|
||||
) -> bool:
|
||||
if (
|
||||
peer_id <= 0
|
||||
or profile_id.is_empty()
|
||||
or not NetworkProfilePreferences.is_valid_title(player_title)
|
||||
or (
|
||||
not identity_fingerprint.is_empty()
|
||||
and has_fingerprint(identity_fingerprint)
|
||||
|
|
@ -44,6 +47,7 @@ func add_peer(
|
|||
record.peer_id = peer_id
|
||||
record.profile_id = profile_id
|
||||
record.display_name = display_name
|
||||
record.title = player_title
|
||||
record.protocol_version = protocol_version
|
||||
record.joined_at_unix = int(Time.get_unix_time_from_system())
|
||||
record.identity_fingerprint = identity_fingerprint
|
||||
|
|
@ -74,6 +78,14 @@ func update_display_name(peer_id: int, display_name: String) -> bool:
|
|||
return true
|
||||
|
||||
|
||||
func update_title(peer_id: int, player_title: String) -> bool:
|
||||
var record: PeerRecord = _records.get(peer_id)
|
||||
if record == null or not NetworkProfilePreferences.is_valid_title(player_title):
|
||||
return false
|
||||
record.title = player_title.strip_edges()
|
||||
return true
|
||||
|
||||
|
||||
func update_appearance(peer_id: int, snapshot: Dictionary) -> bool:
|
||||
var record: PeerRecord = _records.get(peer_id)
|
||||
if record == null or not CharacterCustomizationCatalog.validate_snapshot(snapshot):
|
||||
|
|
|
|||
|
|
@ -16,6 +16,10 @@ const ENVIRONMENT_VARIABLE := "straywild_DATA_DIR"
|
|||
const CREATE_ENVIRONMENT_VARIABLE := "straywild_CREATE_DATA_DIR"
|
||||
const LEGACY_ENVIRONMENT_VARIABLE := "NETFISHING_DATA_DIR"
|
||||
const LEGACY_CREATE_ENVIRONMENT_VARIABLE := "NETFISHING_CREATE_DATA_DIR"
|
||||
const ISOLATED_VALIDATION_ENVIRONMENT := (
|
||||
"straywild_ISOLATED_VALIDATION_STORAGE"
|
||||
)
|
||||
const ISOLATED_VALIDATION_DIRECTORY := "isolated-validation-data"
|
||||
const APPLICATION_ID := "straywild"
|
||||
const LEGACY_APPLICATION_ID := "netfishing"
|
||||
const APP_DATA_PORTABLE_PATH := "user://portable-data"
|
||||
|
|
@ -57,6 +61,9 @@ enum Mode {
|
|||
COMMAND_LINE_OVERRIDE,
|
||||
}
|
||||
|
||||
static var _process_validation_root := ""
|
||||
static var _process_validation_device_id := ""
|
||||
|
||||
var mode := Mode.UNRESOLVED
|
||||
var root_path := ""
|
||||
var root_id := ""
|
||||
|
|
@ -67,6 +74,11 @@ var override_active := false
|
|||
|
||||
|
||||
func resolve() -> bool:
|
||||
# Validation scripts may be launched directly instead of through the
|
||||
# repository runner. Keep the opt-in flag fail-safe: it must never follow a
|
||||
# real user's bootstrap pointer merely because XDG isolation was omitted.
|
||||
if _isolated_validation_requested():
|
||||
return _resolve_isolated_validation_root()
|
||||
prepare_legacy_user_data()
|
||||
_load_bootstrap_identity()
|
||||
var command_line: String = _command_line_override()
|
||||
|
|
@ -116,6 +128,44 @@ func resolve() -> bool:
|
|||
return false
|
||||
|
||||
|
||||
func _resolve_isolated_validation_root() -> bool:
|
||||
# Behave like app-data storage after choosing the isolated root so migration
|
||||
# tests can still exercise live root switching within their sandbox.
|
||||
override_active = false
|
||||
mode = Mode.APP_DATA
|
||||
if _process_validation_device_id.is_empty():
|
||||
_process_validation_device_id = (
|
||||
Crypto.new().generate_random_bytes(16).hex_encode()
|
||||
)
|
||||
device_id = _process_validation_device_id
|
||||
if _process_validation_root.is_empty():
|
||||
var parent: String
|
||||
if OS.has_environment("XDG_DATA_HOME"):
|
||||
parent = ProjectSettings.globalize_path("user://")
|
||||
else:
|
||||
parent = OS.get_temp_dir().path_join(
|
||||
"straywild-validation-%d-%d"
|
||||
% [OS.get_process_id(), Time.get_ticks_usec()]
|
||||
)
|
||||
_process_validation_root = parent.path_join(
|
||||
ISOLATED_VALIDATION_DIRECTORY
|
||||
)
|
||||
if not has_data_manifest(_process_validation_root):
|
||||
var created: Dictionary = create_unbound_root(
|
||||
_process_validation_root
|
||||
)
|
||||
if not bool(created.get("ok", false)):
|
||||
return false
|
||||
return _activate_existing(_process_validation_root, "", false)
|
||||
|
||||
|
||||
static func _isolated_validation_requested() -> bool:
|
||||
return (
|
||||
DisplayServer.get_name() == "headless"
|
||||
and OS.get_environment(ISOLATED_VALIDATION_ENVIRONMENT) == "1"
|
||||
)
|
||||
|
||||
|
||||
func prepare_legacy_user_data() -> void:
|
||||
var current_root: String = _normalize(
|
||||
ProjectSettings.globalize_path("user://")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue