Improve interface accessibility and UI reliability
This commit is contained in:
parent
ba4476511f
commit
3734d4a168
29 changed files with 514 additions and 224 deletions
|
|
@ -216,12 +216,12 @@ func show_bite() -> void:
|
|||
_bite_tween.finished.connect(_on_bite_tween_finished)
|
||||
|
||||
|
||||
func show_withdrawal_position(position: Vector3) -> void:
|
||||
func show_withdrawal_position(world_position: Vector3) -> void:
|
||||
if _mode != VisualMode.FISHING:
|
||||
return
|
||||
|
||||
_kill_active_tween()
|
||||
_bobber.global_position = _with_water_height(position)
|
||||
_bobber.global_position = _with_water_height(world_position)
|
||||
|
||||
|
||||
func begin_reeling() -> void:
|
||||
|
|
@ -232,12 +232,12 @@ func begin_reeling() -> void:
|
|||
_cast_position = _with_water_height(_bobber.global_position)
|
||||
|
||||
|
||||
func show_reel_position(position: Vector3, _input_held: bool) -> void:
|
||||
func show_reel_position(world_position: Vector3, _input_held: bool) -> void:
|
||||
if _mode != VisualMode.FISHING:
|
||||
return
|
||||
|
||||
_kill_active_tween()
|
||||
_bobber.global_position = _with_water_height(position)
|
||||
_bobber.global_position = _with_water_height(world_position)
|
||||
|
||||
|
||||
func play_outcome(outcome: StringName) -> void:
|
||||
|
|
@ -371,8 +371,12 @@ func _calculate_pickup_position(target: Vector3) -> Vector3:
|
|||
return player_on_water + outward * pickup_distance
|
||||
|
||||
|
||||
func _with_water_height(position: Vector3) -> Vector3:
|
||||
return Vector3(position.x, get_water_surface_height(), position.z)
|
||||
func _with_water_height(world_position: Vector3) -> Vector3:
|
||||
return Vector3(
|
||||
world_position.x,
|
||||
get_water_surface_height(),
|
||||
world_position.z,
|
||||
)
|
||||
|
||||
|
||||
func _update_line(delta: float) -> void:
|
||||
|
|
|
|||
|
|
@ -1082,7 +1082,7 @@ func get_fishable_water_region(
|
|||
query,
|
||||
32
|
||||
)
|
||||
var selected_region: FishableWaterRegionType
|
||||
var selected_region: FishableWaterRegionType = null
|
||||
for result: Dictionary in results:
|
||||
var collider: Object = result.get("collider")
|
||||
var region: FishableWaterRegionType = collider as FishableWaterRegionType
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ var _target: Vector3
|
|||
var _active: bool = false
|
||||
|
||||
|
||||
func setup(owner: Player) -> void:
|
||||
_owner = owner
|
||||
func setup(owning_player: Player) -> void:
|
||||
_owner = owning_player
|
||||
_bobber = MeshInstance3D.new()
|
||||
var bobber_mesh := SphereMesh.new()
|
||||
bobber_mesh.radius = 0.12
|
||||
|
|
@ -43,11 +43,11 @@ func show_cast(target: Vector3) -> void:
|
|||
_redraw_line()
|
||||
|
||||
|
||||
func update_bobber(position: Vector3) -> void:
|
||||
if not _active or not position.is_finite():
|
||||
func update_bobber(world_position: Vector3) -> void:
|
||||
if not _active or not world_position.is_finite():
|
||||
return
|
||||
_target = position
|
||||
_bobber.global_position = position
|
||||
_target = world_position
|
||||
_bobber.global_position = world_position
|
||||
_redraw_line()
|
||||
|
||||
|
||||
|
|
|
|||
83
main/main.gd
83
main/main.gd
|
|
@ -88,6 +88,7 @@ const TITLE_MUSIC_SILENCE_DB: float = -80.0
|
|||
@onready var _water_recovery: WaterRecoveryControllerType = %WaterRecovery
|
||||
@onready var _save_manager: PlayerSaveManagerType = %PlayerSaveManager
|
||||
@onready var _settings_manager: PlayerSettingsManagerType = %PlayerSettingsManager
|
||||
@onready var _interface_fonts: InterfaceFontController = %InterfaceFontController
|
||||
@onready var _title_music: AudioStreamPlayer = %TitleMusic
|
||||
@onready var _ui_pixelation: UIPixelationPresenterType = %UIPresentation
|
||||
@onready var _pixelation_reset: PixelationResetOverlayType = (
|
||||
|
|
@ -146,11 +147,21 @@ var _pending_trust_changed: bool = false
|
|||
var _identity_notice_dialog: AcceptDialog
|
||||
var _data_setup_dialog: ConfirmationDialog
|
||||
var _data_folder_dialog: FileDialog
|
||||
var _data_folder_picker_generation: int = 0
|
||||
var _restore_data_setup_after_picker: bool = false
|
||||
var _application_initialized := false
|
||||
var _pending_existing_root_path := ""
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if not _settings_manager.settings_changed.is_connected(
|
||||
_apply_runtime_settings
|
||||
):
|
||||
_settings_manager.settings_changed.connect(_apply_runtime_settings)
|
||||
_settings_manager.load_settings()
|
||||
_interface_fonts.set_readable_font_enabled(
|
||||
_settings_manager.current_settings.use_readable_interface_font
|
||||
)
|
||||
if _data_root.resolve():
|
||||
_configure_portable_stores()
|
||||
_initialize_after_data_root()
|
||||
|
|
@ -377,6 +388,7 @@ func _initialize_after_data_root() -> void:
|
|||
_player_identity,
|
||||
_host_identity,
|
||||
_network_session,
|
||||
_interface_fonts,
|
||||
)
|
||||
_data_root.conflict_detected.connect(_on_portable_conflict)
|
||||
_data_root.status_changed.connect(_on_data_root_status)
|
||||
|
|
@ -400,10 +412,6 @@ func _initialize_after_data_root() -> void:
|
|||
_test_world.get_player_water_triggers(),
|
||||
_test_world.get_safe_respawn_points()
|
||||
)
|
||||
if not _settings_manager.settings_changed.is_connected(
|
||||
_apply_runtime_settings
|
||||
):
|
||||
_settings_manager.settings_changed.connect(_apply_runtime_settings)
|
||||
_ui_pixelation.effective_pixel_size_changed.connect(
|
||||
_on_effective_ui_pixel_size_changed
|
||||
)
|
||||
|
|
@ -420,7 +428,6 @@ func _initialize_after_data_root() -> void:
|
|||
_game_ui.focus_open_settings_back_button
|
||||
)
|
||||
_pixelation_reset.reset_requested.connect(_reset_pixelation)
|
||||
_settings_manager.load_settings()
|
||||
_apply_runtime_settings(_settings_manager.current_settings)
|
||||
_set_gameplay_active(false)
|
||||
var title_screen: TitleScreenType = _game_ui.get_title_screen()
|
||||
|
|
@ -491,9 +498,10 @@ func _show_data_root_setup() -> void:
|
|||
"Keep Current Location", false, "current"
|
||||
)
|
||||
_data_setup_dialog.custom_action.connect(_on_data_setup_action)
|
||||
_interface_fonts.apply_utility_theme(_data_setup_dialog)
|
||||
add_child(_data_setup_dialog)
|
||||
var default_path := _data_root.default_visible_path()
|
||||
var legacy := PortableDataMigration.legacy_files_present()
|
||||
var default_path: String = _data_root.default_visible_path()
|
||||
var legacy: bool = PortableDataMigration.legacy_files_present()
|
||||
_data_setup_dialog.dialog_text = (
|
||||
("Move your existing NETFISHING data to an easy-to-find folder."
|
||||
if legacy else "Choose where NETFISHING stores your player data.")
|
||||
|
|
@ -512,7 +520,7 @@ func _show_data_root_setup() -> void:
|
|||
|
||||
|
||||
func _use_default_data_root() -> void:
|
||||
var path := _data_root.default_visible_path()
|
||||
var path: String = _data_root.default_visible_path()
|
||||
if path.is_empty():
|
||||
_show_folder_picker()
|
||||
return
|
||||
|
|
@ -535,7 +543,29 @@ func _show_folder_picker() -> void:
|
|||
_data_folder_dialog.access = FileDialog.ACCESS_FILESYSTEM
|
||||
_data_folder_dialog.use_native_dialog = false
|
||||
_data_folder_dialog.dir_selected.connect(_activate_selected_data_path)
|
||||
_data_folder_dialog.canceled.connect(_on_data_folder_picker_canceled)
|
||||
_interface_fonts.apply_utility_theme(_data_folder_dialog)
|
||||
add_child(_data_folder_dialog)
|
||||
if _data_folder_dialog.visible:
|
||||
return
|
||||
_data_folder_picker_generation += 1
|
||||
var generation: int = _data_folder_picker_generation
|
||||
_restore_data_setup_after_picker = (
|
||||
_data_setup_dialog != null and _data_setup_dialog.visible
|
||||
)
|
||||
if _data_setup_dialog != null:
|
||||
_data_setup_dialog.hide()
|
||||
_open_folder_picker_after_modal.call_deferred(generation)
|
||||
|
||||
|
||||
func _open_folder_picker_after_modal(generation: int) -> void:
|
||||
await get_tree().process_frame
|
||||
if (
|
||||
generation != _data_folder_picker_generation
|
||||
or _data_folder_dialog == null
|
||||
or _data_folder_dialog.visible
|
||||
):
|
||||
return
|
||||
_data_folder_dialog.current_dir = (
|
||||
_data_root.default_visible_path().get_base_dir()
|
||||
if not _data_root.default_visible_path().is_empty()
|
||||
|
|
@ -544,10 +574,29 @@ func _show_folder_picker() -> void:
|
|||
_data_folder_dialog.popup_centered_ratio(0.75)
|
||||
|
||||
|
||||
func _on_data_folder_picker_canceled() -> void:
|
||||
_data_folder_picker_generation += 1
|
||||
var generation: int = _data_folder_picker_generation
|
||||
var should_restore_setup: bool = _restore_data_setup_after_picker
|
||||
_restore_data_setup_after_picker = false
|
||||
if _data_folder_dialog != null:
|
||||
_data_folder_dialog.hide()
|
||||
if should_restore_setup:
|
||||
_restore_data_setup_after_picker_after_modal.call_deferred(generation)
|
||||
|
||||
|
||||
func _restore_data_setup_after_picker_after_modal(generation: int) -> void:
|
||||
await get_tree().process_frame
|
||||
if generation != _data_folder_picker_generation:
|
||||
return
|
||||
_show_data_root_setup()
|
||||
|
||||
|
||||
func _activate_selected_data_path(path: String, app_data: bool = false) -> void:
|
||||
var ok := false
|
||||
_restore_data_setup_after_picker = false
|
||||
var ok: bool = false
|
||||
if PortableDataMigration.legacy_files_present():
|
||||
var result := (
|
||||
var result: Dictionary = (
|
||||
PortableDataMigration.adopt_legacy_app_data(_data_root)
|
||||
if app_data
|
||||
else PortableDataMigration.migrate_legacy_to(_data_root, path)
|
||||
|
|
@ -571,7 +620,7 @@ func _activate_selected_data_path(path: String, app_data: bool = false) -> void:
|
|||
|
||||
func _show_existing_root_choice(path: String) -> void:
|
||||
_pending_existing_root_path = path
|
||||
var dialog := ConfirmationDialog.new()
|
||||
var dialog: ConfirmationDialog = ConfirmationDialog.new()
|
||||
dialog.title = "Existing NETFISHING data"
|
||||
dialog.ok_button_text = "Use Data Already in Selected Folder"
|
||||
dialog.cancel_button_text = "Cancel"
|
||||
|
|
@ -594,7 +643,7 @@ func _show_existing_root_choice(path: String) -> void:
|
|||
dialog.custom_action.connect(func(action: StringName) -> void:
|
||||
if action != &"replace":
|
||||
return
|
||||
var result := PortableDataMigration.replace_existing_with_legacy(
|
||||
var result: Dictionary = PortableDataMigration.replace_existing_with_legacy(
|
||||
_data_root, _pending_existing_root_path
|
||||
)
|
||||
if bool(result.get("ok", false)):
|
||||
|
|
@ -605,6 +654,7 @@ func _show_existing_root_choice(path: String) -> void:
|
|||
else:
|
||||
_show_data_error(str(result.get("message", "Migration failed.")))
|
||||
)
|
||||
_interface_fonts.apply_utility_theme(dialog)
|
||||
add_child(dialog)
|
||||
dialog.popup_centered(Vector2i(680, 360))
|
||||
|
||||
|
|
@ -616,7 +666,8 @@ func _show_data_error(message: String) -> void:
|
|||
|
||||
|
||||
func _on_portable_conflict(message: String, _path: String) -> void:
|
||||
var dialog := AcceptDialog.new()
|
||||
var dialog: AcceptDialog = AcceptDialog.new()
|
||||
_interface_fonts.apply_utility_theme(dialog)
|
||||
dialog.title = "Player data conflict"
|
||||
dialog.dialog_text = (
|
||||
message
|
||||
|
|
@ -635,7 +686,8 @@ func _on_portable_conflict(message: String, _path: String) -> void:
|
|||
func _on_data_root_status(message: String) -> void:
|
||||
if "Syncthing conflict" not in message:
|
||||
return
|
||||
var dialog := AcceptDialog.new()
|
||||
var dialog: AcceptDialog = AcceptDialog.new()
|
||||
_interface_fonts.apply_utility_theme(dialog)
|
||||
dialog.title = "Synced data needs review"
|
||||
dialog.dialog_text = message
|
||||
dialog.add_button("Open Data Folder", false, "open")
|
||||
|
|
@ -771,6 +823,9 @@ func _process(_delta: float) -> void:
|
|||
func _apply_runtime_settings(settings: PlayerSettingsType) -> void:
|
||||
if settings == null:
|
||||
return
|
||||
_interface_fonts.set_readable_font_enabled(
|
||||
settings.use_readable_interface_font
|
||||
)
|
||||
_apply_world_pixelation(settings.world_pixel_size)
|
||||
_ui_pixelation.set_pixel_size(settings.ui_pixel_size)
|
||||
_game_ui.get_title_screen().set_world_pixelation(
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
[gd_scene load_steps=39 format=3]
|
||||
[gd_scene load_steps=40 format=3]
|
||||
|
||||
[ext_resource type="PackedScene" path="res://world/test_world.tscn" id="1_world"]
|
||||
[ext_resource type="PackedScene" path="res://player/player.tscn" id="2_player"]
|
||||
|
|
@ -38,6 +38,7 @@
|
|||
[ext_resource type="Script" path="res://network/network_player_list_service.gd" id="36_player_list"]
|
||||
[ext_resource type="Script" path="res://network/player_data_root.gd" id="37_data_root"]
|
||||
[ext_resource type="Script" path="res://network/identity_backup_service.gd" id="38_identity_backup"]
|
||||
[ext_resource type="Script" path="res://ui/interface_font_controller.gd" id="39_fonts"]
|
||||
|
||||
[node name="Main" type="Node3D"]
|
||||
script = ExtResource("3_main")
|
||||
|
|
@ -46,6 +47,10 @@ pelican_buyer_profile = ExtResource("7_pelicans")
|
|||
main_shop_buyer_profile = ExtResource("12_main_shop")
|
||||
item_catalog = ExtResource("11_items")
|
||||
|
||||
[node name="InterfaceFontController" type="Node" parent="."]
|
||||
unique_name_in_owner = true
|
||||
script = ExtResource("39_fonts")
|
||||
|
||||
[node name="PlayerDataRoot" type="Node" parent="."]
|
||||
unique_name_in_owner = true
|
||||
script = ExtResource("37_data_root")
|
||||
|
|
|
|||
|
|
@ -24,11 +24,15 @@ func is_banned(host_fingerprint: String, target_fingerprint: String) -> bool:
|
|||
return Dictionary(_namespaces.get(host_fingerprint, {})).has(target_fingerprint)
|
||||
|
||||
|
||||
func ban(host_fingerprint: String, target_fingerprint: String, name: String) -> bool:
|
||||
func ban(
|
||||
host_fingerprint: String,
|
||||
target_fingerprint: String,
|
||||
display_name: String,
|
||||
) -> bool:
|
||||
if (
|
||||
not NetworkIdentityCrypto.valid_fingerprint(host_fingerprint)
|
||||
or not NetworkIdentityCrypto.valid_fingerprint(target_fingerprint)
|
||||
or not NetworkProfilePreferences.is_valid_display_name(name)
|
||||
or not NetworkProfilePreferences.is_valid_display_name(display_name)
|
||||
):
|
||||
return false
|
||||
_ensure_loaded()
|
||||
|
|
@ -41,7 +45,7 @@ func ban(host_fingerprint: String, target_fingerprint: String, name: String) ->
|
|||
records[target_fingerprint] = {
|
||||
"host_fingerprint": host_fingerprint,
|
||||
"target_fingerprint": target_fingerprint,
|
||||
"last_known_display_name": name,
|
||||
"last_known_display_name": display_name,
|
||||
"banned_unix": int(Time.get_unix_time_from_system()),
|
||||
"reason": "host_ban",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,12 +25,12 @@ func setup(
|
|||
|
||||
|
||||
func default_export_path(identity_type: String) -> String:
|
||||
var store := _store(identity_type)
|
||||
var store: LocalSigningIdentityStore = _store(identity_type)
|
||||
if store == null:
|
||||
return ""
|
||||
if not store.is_ready() and not store.load_or_create():
|
||||
return ""
|
||||
var timestamp := Time.get_datetime_string_from_system().replace(":", "-")
|
||||
var timestamp: String = Time.get_datetime_string_from_system().replace(":", "-")
|
||||
return _data_root.identity_backup_directory().path_join(
|
||||
"%s-%s-%s.nfidentity" % [
|
||||
identity_type,
|
||||
|
|
@ -50,14 +50,14 @@ func export_backup(
|
|||
return _finish(false, "Passphrases must match and contain at least 12 characters.")
|
||||
if FileAccess.file_exists(path):
|
||||
return _finish(false, "Choose a new backup filename.")
|
||||
var store := _store(identity_type)
|
||||
var store: LocalSigningIdentityStore = _store(identity_type)
|
||||
if store == null or (not store.is_ready() and not store.load_or_create()):
|
||||
return _finish(false, "The active identity is unavailable.")
|
||||
var material := store.export_identity_material()
|
||||
var proof := store.sign("identity_backup_self_test", [
|
||||
var material: Dictionary = store.export_identity_material()
|
||||
var proof: PackedByteArray = store.sign("identity_backup_self_test", [
|
||||
identity_type, material["fingerprint"],
|
||||
])
|
||||
var envelope := {
|
||||
var envelope: Dictionary = {
|
||||
"magic": MAGIC,
|
||||
"format_version": FORMAT_VERSION,
|
||||
"identity_type": identity_type,
|
||||
|
|
@ -70,16 +70,18 @@ func export_backup(
|
|||
"self_signature": Marshalls.raw_to_base64(proof),
|
||||
}
|
||||
DirAccess.make_dir_recursive_absolute(path.get_base_dir())
|
||||
var file := FileAccess.open_encrypted_with_pass(path, FileAccess.WRITE, passphrase)
|
||||
var file: FileAccess = FileAccess.open_encrypted_with_pass(
|
||||
path, FileAccess.WRITE, passphrase
|
||||
)
|
||||
if file == null:
|
||||
return _finish(false, "Could not write this identity backup.")
|
||||
file.store_string(JSON.stringify(envelope))
|
||||
file.flush()
|
||||
var ok := file.get_error() == OK
|
||||
var ok: bool = file.get_error() == OK
|
||||
file.close()
|
||||
if not ok:
|
||||
return _finish(false, "Could not write this identity backup.")
|
||||
var verified := inspect_backup(path, passphrase, identity_type)
|
||||
var verified: Dictionary = inspect_backup(path, passphrase, identity_type)
|
||||
if not bool(verified.get("ok", false)):
|
||||
DirAccess.remove_absolute(path)
|
||||
return _finish(false, "Could not verify this identity backup.")
|
||||
|
|
@ -97,12 +99,14 @@ func inspect_backup(
|
|||
or FileAccess.get_size(path) > MAX_BACKUP_BYTES
|
||||
):
|
||||
return {"ok": false}
|
||||
var file := FileAccess.open_encrypted_with_pass(path, FileAccess.READ, passphrase)
|
||||
var file: FileAccess = FileAccess.open_encrypted_with_pass(
|
||||
path, FileAccess.READ, passphrase
|
||||
)
|
||||
if file == null:
|
||||
return {"ok": false}
|
||||
var text := file.get_as_text()
|
||||
var text: String = file.get_as_text()
|
||||
file.close()
|
||||
var json := JSON.new()
|
||||
var json: JSON = JSON.new()
|
||||
if json.parse(text) != OK or typeof(json.data) != TYPE_DICTIONARY:
|
||||
return {"ok": false}
|
||||
var data: Dictionary = json.data
|
||||
|
|
@ -113,21 +117,23 @@ func inspect_backup(
|
|||
or data.get("algorithm") != NetworkIdentityCrypto.ALGORITHM
|
||||
):
|
||||
return {"ok": false}
|
||||
var private_pem := str(data.get("private_pem", ""))
|
||||
var public_pem := NetworkIdentityCrypto.normalize_public_pem(
|
||||
var private_pem: String = str(data.get("private_pem", ""))
|
||||
var public_pem: String = NetworkIdentityCrypto.normalize_public_pem(
|
||||
str(data.get("public_pem", ""))
|
||||
)
|
||||
var fingerprint := str(data.get("fingerprint", ""))
|
||||
var fingerprint: String = str(data.get("fingerprint", ""))
|
||||
if (
|
||||
private_pem.length() > 128 * 1024
|
||||
or public_pem.length() > 32 * 1024
|
||||
or NetworkIdentityCrypto.fingerprint_public_pem(public_pem) != fingerprint
|
||||
):
|
||||
return {"ok": false}
|
||||
var key := CryptoKey.new()
|
||||
var key: CryptoKey = CryptoKey.new()
|
||||
if key.load_from_string(private_pem) != OK:
|
||||
return {"ok": false}
|
||||
var signature := Marshalls.base64_to_raw(str(data.get("self_signature", "")))
|
||||
var signature: PackedByteArray = Marshalls.base64_to_raw(
|
||||
str(data.get("self_signature", ""))
|
||||
)
|
||||
if not NetworkIdentityCrypto.verify_fields(
|
||||
NetworkIdentityCrypto.load_public_key(public_pem),
|
||||
"identity_backup_self_test",
|
||||
|
|
@ -135,7 +141,7 @@ func inspect_backup(
|
|||
signature,
|
||||
):
|
||||
return {"ok": false}
|
||||
var fresh := NetworkIdentityCrypto.sign_fields(
|
||||
var fresh: PackedByteArray = NetworkIdentityCrypto.sign_fields(
|
||||
key, "identity_import_self_test", [fingerprint]
|
||||
)
|
||||
if not NetworkIdentityCrypto.verify_fields(
|
||||
|
|
@ -160,12 +166,12 @@ func import_backup(
|
|||
passphrase: String,
|
||||
confirmed_replacement: bool,
|
||||
) -> Dictionary:
|
||||
var inspected := inspect_backup(path, passphrase, identity_type)
|
||||
var inspected: Dictionary = inspect_backup(path, passphrase, identity_type)
|
||||
if not bool(inspected.get("ok", false)):
|
||||
_finish(false, "Could not open this identity backup.")
|
||||
return {"ok": false}
|
||||
var store := _store(identity_type)
|
||||
var incoming := str(inspected["fingerprint"])
|
||||
var store: LocalSigningIdentityStore = _store(identity_type)
|
||||
var incoming: String = str(inspected["fingerprint"])
|
||||
if store.fingerprint == incoming:
|
||||
_finish(true, "This identity is already active.")
|
||||
return {"ok": true, "same": true}
|
||||
|
|
@ -176,7 +182,7 @@ func import_backup(
|
|||
"current_fingerprint": store.fingerprint,
|
||||
"incoming_fingerprint": incoming,
|
||||
}
|
||||
var result := store.install_identity_material(
|
||||
var result: Dictionary = store.install_identity_material(
|
||||
str(inspected["private_pem"]),
|
||||
str(inspected["public_pem"]),
|
||||
incoming,
|
||||
|
|
|
|||
|
|
@ -245,10 +245,10 @@ func _on_peer_removed(peer_id: int) -> void:
|
|||
_rate_times.erase(peer_id)
|
||||
if not _session.is_host():
|
||||
return
|
||||
var name: String = _peer_names.get(peer_id, "Player")
|
||||
var display_name: String = _peer_names.get(peer_id, "Player")
|
||||
_peer_names.erase(peer_id)
|
||||
_broadcast(_make_message(
|
||||
NetworkChatProtocol.Kind.SYSTEM, 0, "", "%s left." % name
|
||||
NetworkChatProtocol.Kind.SYSTEM, 0, "", "%s left." % display_name
|
||||
))
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -144,14 +144,14 @@ func get_recipient_choices() -> Array[Dictionary]:
|
|||
var normalized := str(choice["name"]).to_lower()
|
||||
counts[normalized] = int(counts.get(normalized, 0)) + 1
|
||||
for choice: Dictionary in choices:
|
||||
var name: String = choice["name"]
|
||||
if int(counts.get(name.to_lower(), 0)) > 1:
|
||||
var display_name: String = choice["name"]
|
||||
if int(counts.get(display_name.to_lower(), 0)) > 1:
|
||||
choice["label"] = "%s · %s" % [
|
||||
name,
|
||||
display_name,
|
||||
NetworkIdentityCrypto.compact_suffix(choice["fingerprint"]),
|
||||
]
|
||||
else:
|
||||
choice["label"] = name
|
||||
choice["label"] = display_name
|
||||
return choices
|
||||
|
||||
|
||||
|
|
@ -531,8 +531,8 @@ func prepare_recipient(transfer_id: String, letter: Dictionary) -> void:
|
|||
|
||||
|
||||
func _prepare_recipient(transfer_id: String, letter: Dictionary) -> void:
|
||||
var ready := _can_receive(letter["attachment"])
|
||||
_send_phase_ack("recipient_prepared", transfer_id, ready)
|
||||
var recipient_ready: bool = _can_receive(letter["attachment"])
|
||||
_send_phase_ack("recipient_prepared", transfer_id, recipient_ready)
|
||||
|
||||
|
||||
func _request_sender_commit(peer_id: int, transfer_id: String, letter: Dictionary) -> void:
|
||||
|
|
|
|||
|
|
@ -117,12 +117,17 @@ func kick(peer_id: int, fingerprint: String, revision: int) -> bool:
|
|||
return ok
|
||||
|
||||
|
||||
func ban(peer_id: int, fingerprint: String, name: String, revision: int) -> bool:
|
||||
func ban(
|
||||
peer_id: int,
|
||||
fingerprint: String,
|
||||
display_name: String,
|
||||
revision: int,
|
||||
) -> bool:
|
||||
if not _valid_moderation_target(peer_id, fingerprint, revision):
|
||||
moderation_finished.emit(false, "That player is no longer connected.")
|
||||
return false
|
||||
var host_fingerprint := _session.get_host_identity_fingerprint()
|
||||
if not _bans.ban(host_fingerprint, fingerprint, name):
|
||||
if not _bans.ban(host_fingerprint, fingerprint, display_name):
|
||||
moderation_finished.emit(false, "Ban could not be saved.")
|
||||
return false
|
||||
var ok := _session.kick_authenticated_peer(peer_id, fingerprint, true)
|
||||
|
|
|
|||
|
|
@ -172,10 +172,14 @@ func submit_profile_apply(data: Dictionary) -> void:
|
|||
|
||||
|
||||
func _process_apply_request(peer_id: int, data: Dictionary) -> void:
|
||||
var name := str(data["display_name"]).strip_edges()
|
||||
var conflict := _name_conflicts(peer_id, name)
|
||||
var accepted := not conflict or bool(data["use_anyway"])
|
||||
var suggestions := _make_suggestions(peer_id, name) if conflict else PackedStringArray()
|
||||
var display_name: String = str(data["display_name"]).strip_edges()
|
||||
var conflict: bool = _name_conflicts(peer_id, display_name)
|
||||
var accepted: bool = not conflict or bool(data["use_anyway"])
|
||||
var suggestions: PackedStringArray = (
|
||||
_make_suggestions(peer_id, display_name)
|
||||
if conflict
|
||||
else PackedStringArray()
|
||||
)
|
||||
if peer_id == _session.get_local_peer_id():
|
||||
_apply_local_result(
|
||||
str(data["request_id"]), accepted, "", conflict, suggestions
|
||||
|
|
@ -184,7 +188,7 @@ 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": name,
|
||||
"display_name": display_name,
|
||||
"appearance": Dictionary(data["appearance"]).duplicate(true),
|
||||
"authorization": {
|
||||
"request_id": data["request_id"],
|
||||
|
|
@ -214,10 +218,10 @@ func confirm_profile_saved(request_id: String) -> void:
|
|||
):
|
||||
return
|
||||
_host_pending_apply.erase(request_id)
|
||||
var name := str(pending["display_name"])
|
||||
var appearance := Dictionary(pending["appearance"])
|
||||
_session.apply_canonical_profile(sender_id, name, appearance)
|
||||
var record := _session.get_peer_record(sender_id)
|
||||
var display_name: String = str(pending["display_name"])
|
||||
var appearance: Dictionary = Dictionary(pending["appearance"])
|
||||
_session.apply_canonical_profile(sender_id, display_name, appearance)
|
||||
var record: PeerRegistry.PeerRecord = _session.get_peer_record(sender_id)
|
||||
if record != null:
|
||||
record.profile_authorization = pending.get("authorization", {}).duplicate(true)
|
||||
_apply_to_avatar(sender_id, appearance)
|
||||
|
|
|
|||
|
|
@ -1145,13 +1145,13 @@ func _apply_spawn_entry(entry: Dictionary) -> void:
|
|||
)
|
||||
_registry.update_appearance(peer_id, appearance)
|
||||
var transform: Transform3D = _spawn_service.get_spawn_transform_for_index(0)
|
||||
var position: Array = entry["position"]
|
||||
if position.size() != 3:
|
||||
var spawn_position: Array = entry["position"]
|
||||
if spawn_position.size() != 3:
|
||||
return
|
||||
transform.origin = Vector3(
|
||||
float(position[0]),
|
||||
float(position[1]),
|
||||
float(position[2])
|
||||
float(spawn_position[0]),
|
||||
float(spawn_position[1]),
|
||||
float(spawn_position[2])
|
||||
)
|
||||
transform.basis = Basis(Vector3.UP, float(entry["yaw"]))
|
||||
var avatar := _spawn_service.spawn_remote_player(peer_id, transform, false)
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ var override_active := false
|
|||
|
||||
func resolve() -> bool:
|
||||
_load_bootstrap_identity()
|
||||
var command_line := _command_line_override()
|
||||
var command_line: String = _command_line_override()
|
||||
if not command_line.is_empty():
|
||||
override_active = true
|
||||
mode = Mode.COMMAND_LINE_OVERRIDE
|
||||
|
|
@ -42,18 +42,18 @@ func resolve() -> bool:
|
|||
if OS.has_environment(ENVIRONMENT_VARIABLE):
|
||||
override_active = true
|
||||
mode = Mode.ENVIRONMENT_OVERRIDE
|
||||
var environment_path := OS.get_environment(ENVIRONMENT_VARIABLE)
|
||||
var environment_path: String = OS.get_environment(ENVIRONMENT_VARIABLE)
|
||||
if not environment_path.is_absolute_path():
|
||||
return _fail("NETFISHING_DATA_DIR must be an absolute path.")
|
||||
return _activate_existing(environment_path, "", false)
|
||||
var bootstrap := _read_json(BOOTSTRAP_PATH, 64 * 1024)
|
||||
var bootstrap: Dictionary = _read_json(BOOTSTRAP_PATH, 64 * 1024)
|
||||
if bootstrap.is_empty() and FileAccess.file_exists(BOOTSTRAP_PATH + ".backup"):
|
||||
bootstrap = _read_json(BOOTSTRAP_PATH + ".backup", 64 * 1024)
|
||||
if not bootstrap.is_empty():
|
||||
if bootstrap.get("format_version") != BOOTSTRAP_VERSION:
|
||||
return _fail("The data-folder pointer uses an unsupported version.")
|
||||
var selected := str(bootstrap.get("selected_absolute_path", ""))
|
||||
var expected := str(bootstrap.get("expected_root_id", ""))
|
||||
var selected: String = str(bootstrap.get("selected_absolute_path", ""))
|
||||
var expected: String = str(bootstrap.get("expected_root_id", ""))
|
||||
if selected.is_empty():
|
||||
return _fail("The data-folder pointer is incomplete.")
|
||||
mode = (
|
||||
|
|
@ -68,7 +68,7 @@ func resolve() -> bool:
|
|||
|
||||
|
||||
func default_visible_path() -> String:
|
||||
var documents := OS.get_system_dir(OS.SYSTEM_DIR_DOCUMENTS)
|
||||
var documents: String = OS.get_system_dir(OS.SYSTEM_DIR_DOCUMENTS)
|
||||
return documents.path_join("NETFISHING") if not documents.is_empty() else ""
|
||||
|
||||
|
||||
|
|
@ -77,14 +77,14 @@ func select_new_root(path: String, app_data: bool = false) -> bool:
|
|||
return _fail("The data folder is controlled by a process override.")
|
||||
if device_id.length() != 32:
|
||||
device_id = Crypto.new().generate_random_bytes(16).hex_encode()
|
||||
var normalized := _normalize(path)
|
||||
var normalized: String = _normalize(path)
|
||||
if app_data:
|
||||
normalized = ProjectSettings.globalize_path(APP_DATA_PORTABLE_PATH)
|
||||
if not _validate_candidate(normalized, true):
|
||||
return false
|
||||
var manifest_path := normalized.path_join(MANIFEST_FILENAME)
|
||||
var manifest_path: String = normalized.path_join(MANIFEST_FILENAME)
|
||||
if FileAccess.file_exists(manifest_path):
|
||||
var manifest := _read_json(manifest_path)
|
||||
var manifest: Dictionary = _read_json(manifest_path)
|
||||
if not _valid_manifest(manifest):
|
||||
return _fail("The selected folder has a malformed NETFISHING manifest.")
|
||||
root_id = str(manifest["root_id"])
|
||||
|
|
@ -107,30 +107,30 @@ func select_new_root(path: String, app_data: bool = false) -> bool:
|
|||
|
||||
|
||||
func create_unbound_root(path: String) -> Dictionary:
|
||||
var normalized := _normalize(path)
|
||||
var normalized: String = _normalize(path)
|
||||
if not _validate_candidate(normalized, true):
|
||||
return {"ok": false, "message": error_message}
|
||||
if not _directory_is_empty(normalized):
|
||||
return {"ok": false, "message": "The destination staging folder is not empty."}
|
||||
var id := Crypto.new().generate_random_bytes(16).hex_encode()
|
||||
var id: String = Crypto.new().generate_random_bytes(16).hex_encode()
|
||||
if not _create_layout(normalized, id):
|
||||
return {"ok": false, "message": "The portable layout could not be created."}
|
||||
return {"ok": true, "root_id": id}
|
||||
|
||||
|
||||
func create_app_data_layout_for_migration(path: String) -> Dictionary:
|
||||
var normalized := _normalize(path)
|
||||
var normalized: String = _normalize(path)
|
||||
if not _validate_candidate(normalized, false):
|
||||
return {"ok": false, "message": error_message}
|
||||
var manifest_path := normalized.path_join(MANIFEST_FILENAME)
|
||||
var manifest_path: String = normalized.path_join(MANIFEST_FILENAME)
|
||||
if FileAccess.file_exists(manifest_path):
|
||||
var manifest := _read_json(manifest_path)
|
||||
var manifest: Dictionary = _read_json(manifest_path)
|
||||
return (
|
||||
{"ok": true, "root_id": str(manifest.get("root_id", ""))}
|
||||
if _valid_manifest(manifest)
|
||||
else {"ok": false, "message": "The app-data manifest is malformed."}
|
||||
)
|
||||
var id := Crypto.new().generate_random_bytes(16).hex_encode()
|
||||
var id: String = Crypto.new().generate_random_bytes(16).hex_encode()
|
||||
if not _create_layout(normalized, id):
|
||||
return {"ok": false, "message": "The app-data layout could not be created."}
|
||||
return {"ok": true, "root_id": id}
|
||||
|
|
@ -141,7 +141,7 @@ func use_existing_root(path: String) -> bool:
|
|||
return _fail("The data folder is controlled by a process override.")
|
||||
if device_id.length() != 32:
|
||||
device_id = Crypto.new().generate_random_bytes(16).hex_encode()
|
||||
var normalized := _normalize(path)
|
||||
var normalized: String = _normalize(path)
|
||||
if not _activate_existing(normalized, "", true):
|
||||
return false
|
||||
if not _write_bootstrap(root_path, root_id):
|
||||
|
|
@ -156,7 +156,7 @@ func use_existing_root(path: String) -> bool:
|
|||
return true
|
||||
|
||||
|
||||
func path_for(owner: StringName) -> String:
|
||||
func path_for(store_owner: StringName) -> String:
|
||||
var relative: String = {
|
||||
&"player_save": "player/player_save.json",
|
||||
&"network_profile": "player/network_profile.json",
|
||||
|
|
@ -166,7 +166,7 @@ func path_for(owner: StringName) -> String:
|
|||
&"player_relationships": "social/player_relationships.json",
|
||||
&"server_trust": "social/server_trust.json",
|
||||
&"host_bans": "social/host_bans.json",
|
||||
}.get(owner, "")
|
||||
}.get(store_owner, "")
|
||||
return root_path.path_join(relative) if not relative.is_empty() else ""
|
||||
|
||||
|
||||
|
|
@ -202,16 +202,16 @@ func report_conflict(message: String, conflict_path: String) -> void:
|
|||
|
||||
|
||||
func _activate_existing(path: String, expected_id: String, permit_creation: bool) -> bool:
|
||||
var normalized := _normalize(path)
|
||||
var normalized: String = _normalize(path)
|
||||
if not _validate_candidate(normalized, permit_creation):
|
||||
return false
|
||||
var manifest_path := normalized.path_join(MANIFEST_FILENAME)
|
||||
var manifest_path: String = normalized.path_join(MANIFEST_FILENAME)
|
||||
if not FileAccess.file_exists(manifest_path):
|
||||
return _fail("The selected folder is not a NETFISHING data folder.")
|
||||
var manifest := _read_json(manifest_path)
|
||||
var manifest: Dictionary = _read_json(manifest_path)
|
||||
if not _valid_manifest(manifest):
|
||||
return _fail("The NETFISHING data-folder manifest is malformed.")
|
||||
var found_id := str(manifest["root_id"])
|
||||
var found_id: String = str(manifest["root_id"])
|
||||
if not expected_id.is_empty() and expected_id != found_id:
|
||||
return _fail("The selected data folder does not match this device pointer.")
|
||||
if not _test_writable(normalized):
|
||||
|
|
@ -228,7 +228,7 @@ func _activate_existing(path: String, expected_id: String, permit_creation: bool
|
|||
func _validate_candidate(path: String, create: bool) -> bool:
|
||||
if path.is_empty() or not path.is_absolute_path():
|
||||
return _fail("Choose an absolute filesystem folder.")
|
||||
var project := ProjectSettings.globalize_path("res://").trim_suffix("/")
|
||||
var project: String = ProjectSettings.globalize_path("res://").trim_suffix("/")
|
||||
if path == project or path.begins_with(project + "/"):
|
||||
return _fail("The project folder cannot be used as the player data folder.")
|
||||
if not DirAccess.dir_exists_absolute(path):
|
||||
|
|
@ -238,8 +238,10 @@ func _validate_candidate(path: String, create: bool) -> bool:
|
|||
|
||||
|
||||
func _test_writable(path: String) -> bool:
|
||||
var probe := path.path_join(".netfishing-write-%s.tmp" % device_id.left(12))
|
||||
var file := FileAccess.open(probe, FileAccess.WRITE)
|
||||
var probe: String = path.path_join(
|
||||
".netfishing-write-%s.tmp" % device_id.left(12)
|
||||
)
|
||||
var file: FileAccess = FileAccess.open(probe, FileAccess.WRITE)
|
||||
if file == null:
|
||||
return false
|
||||
file.store_string("probe")
|
||||
|
|
@ -254,8 +256,8 @@ func _create_layout(path: String, id: String) -> bool:
|
|||
]:
|
||||
if DirAccess.make_dir_recursive_absolute(path.path_join(relative)) != OK:
|
||||
return false
|
||||
var now := int(Time.get_unix_time_from_system())
|
||||
var manifest := {
|
||||
var now: int = int(Time.get_unix_time_from_system())
|
||||
var manifest: Dictionary = {
|
||||
"format_version": MANIFEST_VERSION,
|
||||
"layout_version": LAYOUT_VERSION,
|
||||
"application": APPLICATION_ID,
|
||||
|
|
@ -267,7 +269,7 @@ func _create_layout(path: String, id: String) -> bool:
|
|||
path.path_join(MANIFEST_FILENAME), JSON.stringify(manifest, "\t")
|
||||
):
|
||||
return false
|
||||
var readme := (
|
||||
var readme: String = (
|
||||
"NETFISHING player data\n\n"
|
||||
+ "This folder is safe to synchronize with tools such as Syncthing.\n"
|
||||
+ "Active private identity keys remain device-local.\n"
|
||||
|
|
@ -280,8 +282,8 @@ func _create_layout(path: String, id: String) -> bool:
|
|||
|
||||
|
||||
func _write_bootstrap(path: String, id: String) -> bool:
|
||||
var now := int(Time.get_unix_time_from_system())
|
||||
var data := {
|
||||
var now: int = int(Time.get_unix_time_from_system())
|
||||
var data: Dictionary = {
|
||||
"format_version": BOOTSTRAP_VERSION,
|
||||
"selected_absolute_path": path,
|
||||
"expected_root_id": id,
|
||||
|
|
@ -296,7 +298,7 @@ func _write_bootstrap(path: String, id: String) -> bool:
|
|||
|
||||
|
||||
func _load_bootstrap_identity() -> void:
|
||||
var data := _read_json(BOOTSTRAP_PATH, 64 * 1024)
|
||||
var data: Dictionary = _read_json(BOOTSTRAP_PATH, 64 * 1024)
|
||||
if data.is_empty():
|
||||
data = _read_json(BOOTSTRAP_PATH + ".backup", 64 * 1024)
|
||||
device_id = str(data.get("device_id", ""))
|
||||
|
|
@ -305,9 +307,9 @@ func _load_bootstrap_identity() -> void:
|
|||
|
||||
|
||||
func _command_line_override() -> String:
|
||||
var args := OS.get_cmdline_user_args()
|
||||
var args: PackedStringArray = OS.get_cmdline_user_args()
|
||||
for index: int in args.size():
|
||||
var value := args[index]
|
||||
var value: String = args[index]
|
||||
if value.begins_with("--data-dir="):
|
||||
return value.trim_prefix("--data-dir=")
|
||||
if value == "--data-dir" and index + 1 < args.size():
|
||||
|
|
@ -326,15 +328,15 @@ func _valid_manifest(data: Dictionary) -> bool:
|
|||
|
||||
|
||||
func _directory_is_empty(path: String) -> bool:
|
||||
var access := DirAccess.open(path)
|
||||
var access: DirAccess = DirAccess.open(path)
|
||||
if access == null:
|
||||
return true
|
||||
access.list_dir_begin()
|
||||
var name := access.get_next()
|
||||
while name in [".", ".."]:
|
||||
name = access.get_next()
|
||||
var entry_name: String = access.get_next()
|
||||
while entry_name in [".", ".."]:
|
||||
entry_name = access.get_next()
|
||||
access.list_dir_end()
|
||||
return name.is_empty()
|
||||
return entry_name.is_empty()
|
||||
|
||||
|
||||
func _normalize(path: String) -> String:
|
||||
|
|
@ -344,37 +346,37 @@ func _normalize(path: String) -> String:
|
|||
func _read_json(path: String, maximum := 1024 * 1024) -> Dictionary:
|
||||
if not FileAccess.file_exists(path):
|
||||
return {}
|
||||
var file := FileAccess.open(path, FileAccess.READ)
|
||||
var file: FileAccess = FileAccess.open(path, FileAccess.READ)
|
||||
if file == null or file.get_length() > maximum:
|
||||
return {}
|
||||
var json := JSON.new()
|
||||
var error := json.parse(file.get_as_text())
|
||||
var json: JSON = JSON.new()
|
||||
var error: Error = json.parse(file.get_as_text())
|
||||
file.close()
|
||||
return json.data if error == OK and typeof(json.data) == TYPE_DICTIONARY else {}
|
||||
|
||||
|
||||
func _write_text_atomic(path: String, text: String) -> bool:
|
||||
var absolute := (
|
||||
var absolute: String = (
|
||||
ProjectSettings.globalize_path(path)
|
||||
if path.begins_with("user://")
|
||||
else path
|
||||
)
|
||||
if DirAccess.make_dir_recursive_absolute(absolute.get_base_dir()) != OK:
|
||||
return false
|
||||
var temporary := absolute + ".tmp"
|
||||
var file := FileAccess.open(temporary, FileAccess.WRITE)
|
||||
var temporary: String = absolute + ".tmp"
|
||||
var file: FileAccess = FileAccess.open(temporary, FileAccess.WRITE)
|
||||
if file == null:
|
||||
return false
|
||||
file.store_string(text)
|
||||
file.flush()
|
||||
var ok := file.get_error() == OK
|
||||
var ok: bool = file.get_error() == OK
|
||||
file.close()
|
||||
if not ok:
|
||||
return false
|
||||
var backup := absolute + ".backup"
|
||||
var backup: String = absolute + ".backup"
|
||||
if FileAccess.file_exists(backup):
|
||||
DirAccess.remove_absolute(backup)
|
||||
var had_primary := FileAccess.file_exists(absolute)
|
||||
var had_primary: bool = FileAccess.file_exists(absolute)
|
||||
if had_primary and DirAccess.rename_absolute(absolute, backup) != OK:
|
||||
DirAccess.remove_absolute(temporary)
|
||||
return false
|
||||
|
|
|
|||
|
|
@ -78,10 +78,10 @@ func get_avatar(peer_id: int) -> Player:
|
|||
return _avatars.get(peer_id)
|
||||
|
||||
|
||||
func set_peer_presentation_visible(peer_id: int, visible: bool) -> void:
|
||||
func set_peer_presentation_visible(peer_id: int, should_be_visible: bool) -> void:
|
||||
var avatar: Player = _avatars.get(peer_id)
|
||||
if avatar != null and is_instance_valid(avatar):
|
||||
avatar.set_remote_presentation_visible(visible)
|
||||
avatar.set_remote_presentation_visible(should_be_visible)
|
||||
|
||||
|
||||
func get_peer_ids() -> Array[int]:
|
||||
|
|
|
|||
|
|
@ -24,9 +24,9 @@ static func migrate_legacy_to(
|
|||
data_root: PlayerDataRoot,
|
||||
destination: String,
|
||||
) -> Dictionary:
|
||||
var normalized := destination.simplify_path().trim_suffix("/")
|
||||
var normalized: String = destination.simplify_path().trim_suffix("/")
|
||||
if DirAccess.dir_exists_absolute(normalized):
|
||||
var existing_manifest := normalized.path_join(
|
||||
var existing_manifest: String = normalized.path_join(
|
||||
PlayerDataRoot.MANIFEST_FILENAME
|
||||
)
|
||||
if FileAccess.file_exists(existing_manifest):
|
||||
|
|
@ -40,24 +40,24 @@ static func migrate_legacy_to(
|
|||
"ok": false,
|
||||
"message": "Choose an empty folder or create a NETFISHING subfolder.",
|
||||
}
|
||||
var staging := "%s.migration-%s" % [
|
||||
var staging: String = "%s.migration-%s" % [
|
||||
normalized, Crypto.new().generate_random_bytes(8).hex_encode()
|
||||
]
|
||||
if DirAccess.make_dir_recursive_absolute(staging) != OK:
|
||||
return {"ok": false, "message": "Migration staging could not be created."}
|
||||
var created := data_root.create_unbound_root(staging)
|
||||
var created: Dictionary = data_root.create_unbound_root(staging)
|
||||
if not bool(created.get("ok", false)):
|
||||
_remove_tree(staging)
|
||||
return {"ok": false, "message": str(created.get("message", ""))}
|
||||
var copied: Array[String] = []
|
||||
for source_name: String in LEGACY_FILES:
|
||||
var source := ProjectSettings.globalize_path(
|
||||
var source: String = ProjectSettings.globalize_path(
|
||||
"user://".path_join(source_name)
|
||||
)
|
||||
if not FileAccess.file_exists(source):
|
||||
continue
|
||||
var target := staging.path_join(LEGACY_FILES[source_name])
|
||||
var result := _copy_verified_json(source, target)
|
||||
var target: String = staging.path_join(str(LEGACY_FILES[source_name]))
|
||||
var result: Dictionary = _copy_verified_json(source, target)
|
||||
if not bool(result.get("ok", false)):
|
||||
_remove_tree(staging)
|
||||
return {
|
||||
|
|
@ -70,11 +70,15 @@ static func migrate_legacy_to(
|
|||
if DirAccess.rename_absolute(staging, normalized) != OK:
|
||||
_remove_tree(staging)
|
||||
return {"ok": false, "message": "Migration could not activate its destination."}
|
||||
var manifest := _read_json(normalized.path_join(PlayerDataRoot.MANIFEST_FILENAME))
|
||||
var migrated_root_id := str(manifest.get("root_id", ""))
|
||||
var manifest: Dictionary = _read_json(
|
||||
normalized.path_join(PlayerDataRoot.MANIFEST_FILENAME)
|
||||
)
|
||||
var migrated_root_id: String = str(manifest.get("root_id", ""))
|
||||
if migrated_root_id.is_empty() or not data_root.use_existing_root(normalized):
|
||||
return {"ok": false, "message": "Migration completed but could not switch roots."}
|
||||
var recovery := ProjectSettings.globalize_path("user://migration-recovery").path_join(
|
||||
var recovery: String = ProjectSettings.globalize_path(
|
||||
"user://migration-recovery"
|
||||
).path_join(
|
||||
Time.get_datetime_string_from_system().replace(":", "-")
|
||||
)
|
||||
DirAccess.make_dir_recursive_absolute(recovery)
|
||||
|
|
@ -94,7 +98,7 @@ static func migrate_active_to(
|
|||
data_root: PlayerDataRoot,
|
||||
destination: String,
|
||||
) -> Dictionary:
|
||||
var normalized := destination.simplify_path().trim_suffix("/")
|
||||
var normalized: String = destination.simplify_path().trim_suffix("/")
|
||||
if normalized == data_root.root_path:
|
||||
return {"ok": true, "message": "This data folder is already active."}
|
||||
if DirAccess.dir_exists_absolute(normalized) and not _directory_empty(normalized):
|
||||
|
|
@ -108,12 +112,12 @@ static func migrate_active_to(
|
|||
),
|
||||
}
|
||||
return {"ok": false, "message": "Choose an empty folder or a NETFISHING subfolder."}
|
||||
var staging := "%s.migration-%s" % [
|
||||
var staging: String = "%s.migration-%s" % [
|
||||
normalized, Crypto.new().generate_random_bytes(8).hex_encode()
|
||||
]
|
||||
if DirAccess.make_dir_recursive_absolute(staging) != OK:
|
||||
return {"ok": false, "message": "Migration staging could not be created."}
|
||||
var created := data_root.create_unbound_root(staging)
|
||||
var created: Dictionary = data_root.create_unbound_root(staging)
|
||||
if not bool(created.get("ok", false)):
|
||||
_remove_tree(staging)
|
||||
return {"ok": false, "message": str(created.get("message", ""))}
|
||||
|
|
@ -127,7 +131,7 @@ static func migrate_active_to(
|
|||
"social/server_trust.json",
|
||||
"social/host_bans.json",
|
||||
]:
|
||||
var source := data_root.root_path.path_join(relative)
|
||||
var source: String = data_root.root_path.path_join(relative)
|
||||
if not FileAccess.file_exists(source):
|
||||
continue
|
||||
if not bool(_copy_verified_json(source, staging.path_join(relative)).get("ok", false)):
|
||||
|
|
@ -138,7 +142,7 @@ static func migrate_active_to(
|
|||
if DirAccess.rename_absolute(staging, normalized) != OK:
|
||||
_remove_tree(staging)
|
||||
return {"ok": false, "message": "Migration could not activate its destination."}
|
||||
var old_root := data_root.root_path
|
||||
var old_root: String = data_root.root_path
|
||||
if not data_root.use_existing_root(normalized):
|
||||
return {"ok": false, "message": "Migration copied data but did not change the pointer."}
|
||||
return {
|
||||
|
|
@ -149,18 +153,20 @@ static func migrate_active_to(
|
|||
|
||||
|
||||
static func adopt_legacy_app_data(data_root: PlayerDataRoot) -> Dictionary:
|
||||
var source_root := ProjectSettings.globalize_path("user://").trim_suffix("/")
|
||||
var root := ProjectSettings.globalize_path(PlayerDataRoot.APP_DATA_PORTABLE_PATH)
|
||||
var source_root: String = ProjectSettings.globalize_path("user://").trim_suffix("/")
|
||||
var root: String = ProjectSettings.globalize_path(
|
||||
PlayerDataRoot.APP_DATA_PORTABLE_PATH
|
||||
)
|
||||
if DirAccess.make_dir_recursive_absolute(root) != OK:
|
||||
return {"ok": false, "message": "The app-data folder could not be created."}
|
||||
var created := data_root.create_app_data_layout_for_migration(root)
|
||||
var created: Dictionary = data_root.create_app_data_layout_for_migration(root)
|
||||
if not bool(created.get("ok", false)):
|
||||
return created
|
||||
for source_name: String in LEGACY_FILES:
|
||||
var source := source_root.path_join(source_name)
|
||||
var source: String = source_root.path_join(source_name)
|
||||
if not FileAccess.file_exists(source):
|
||||
continue
|
||||
var target := root.path_join(LEGACY_FILES[source_name])
|
||||
var target: String = root.path_join(str(LEGACY_FILES[source_name]))
|
||||
if not bool(_copy_verified_json(source, target).get("ok", false)):
|
||||
return {
|
||||
"ok": false,
|
||||
|
|
@ -179,17 +185,19 @@ static func replace_existing_with_legacy(
|
|||
data_root: PlayerDataRoot,
|
||||
destination: String,
|
||||
) -> Dictionary:
|
||||
var normalized := destination.simplify_path().trim_suffix("/")
|
||||
var manifest := normalized.path_join(PlayerDataRoot.MANIFEST_FILENAME)
|
||||
var normalized: String = destination.simplify_path().trim_suffix("/")
|
||||
var manifest: String = normalized.path_join(PlayerDataRoot.MANIFEST_FILENAME)
|
||||
if not FileAccess.file_exists(manifest):
|
||||
return {"ok": false, "message": "The selected folder is not a NETFISHING data root."}
|
||||
var recovery := ProjectSettings.globalize_path("user://migration-recovery").path_join(
|
||||
var recovery: String = ProjectSettings.globalize_path(
|
||||
"user://migration-recovery"
|
||||
).path_join(
|
||||
"replaced-root-" + Time.get_datetime_string_from_system().replace(":", "-")
|
||||
)
|
||||
if not _copy_tree(normalized, recovery):
|
||||
return {"ok": false, "message": "The selected data could not be backed up."}
|
||||
_remove_tree(normalized)
|
||||
var result := migrate_legacy_to(data_root, normalized)
|
||||
var result: Dictionary = migrate_legacy_to(data_root, normalized)
|
||||
if bool(result.get("ok", false)):
|
||||
result["replaced_root_backup"] = recovery
|
||||
return result
|
||||
|
|
@ -199,26 +207,28 @@ static func replace_existing_with_active(
|
|||
data_root: PlayerDataRoot,
|
||||
destination: String,
|
||||
) -> Dictionary:
|
||||
var normalized := destination.simplify_path().trim_suffix("/")
|
||||
var normalized: String = destination.simplify_path().trim_suffix("/")
|
||||
if not FileAccess.file_exists(normalized.path_join(PlayerDataRoot.MANIFEST_FILENAME)):
|
||||
return {"ok": false, "message": "The selected folder is not a NETFISHING data root."}
|
||||
var recovery := ProjectSettings.globalize_path("user://migration-recovery").path_join(
|
||||
var recovery: String = ProjectSettings.globalize_path(
|
||||
"user://migration-recovery"
|
||||
).path_join(
|
||||
"replaced-root-" + Time.get_datetime_string_from_system().replace(":", "-")
|
||||
)
|
||||
if not _copy_tree(normalized, recovery):
|
||||
return {"ok": false, "message": "The selected data could not be backed up."}
|
||||
_remove_tree(normalized)
|
||||
var result := migrate_active_to(data_root, normalized)
|
||||
var result: Dictionary = migrate_active_to(data_root, normalized)
|
||||
if bool(result.get("ok", false)):
|
||||
result["replaced_root_backup"] = recovery
|
||||
return result
|
||||
|
||||
|
||||
static func _copy_verified_json(source: String, destination: String) -> Dictionary:
|
||||
var bytes := PortableFileGuard.read_bytes(source)
|
||||
var bytes: PackedByteArray = PortableFileGuard.read_bytes(source)
|
||||
if bytes.is_empty() and FileAccess.get_open_error() != OK:
|
||||
return {"ok": false}
|
||||
var json := JSON.new()
|
||||
var json: JSON = JSON.new()
|
||||
if (
|
||||
json.parse(bytes.get_string_from_utf8()) != OK
|
||||
or typeof(json.data) != TYPE_DICTIONARY
|
||||
|
|
@ -229,7 +239,7 @@ static func _copy_verified_json(source: String, destination: String) -> Dictiona
|
|||
return {"ok": false}
|
||||
if not _write_bytes(destination, bytes):
|
||||
return {"ok": false}
|
||||
var copied := PortableFileGuard.read_bytes(destination)
|
||||
var copied: PackedByteArray = PortableFileGuard.read_bytes(destination)
|
||||
return {
|
||||
"ok": (
|
||||
PortableFileGuard.hash_bytes(copied)
|
||||
|
|
@ -240,7 +250,7 @@ static func _copy_verified_json(source: String, destination: String) -> Dictiona
|
|||
|
||||
static func _valid_owned_data(filename: String, data: Dictionary) -> bool:
|
||||
if filename == "player_save.json":
|
||||
var version := int(data.get("save_version", -1))
|
||||
var version: int = int(data.get("save_version", -1))
|
||||
return version >= 1 and version <= PlayerSaveManager.SAVE_VERSION
|
||||
return (
|
||||
filename not in LEGACY_FILES
|
||||
|
|
@ -255,14 +265,14 @@ static func _copy_bytes(source: String, destination: String) -> bool:
|
|||
static func _copy_tree(source: String, destination: String) -> bool:
|
||||
if DirAccess.make_dir_recursive_absolute(destination) != OK:
|
||||
return false
|
||||
var access := DirAccess.open(source)
|
||||
var access: DirAccess = DirAccess.open(source)
|
||||
if access == null:
|
||||
return false
|
||||
access.list_dir_begin()
|
||||
var name := access.get_next()
|
||||
var name: String = access.get_next()
|
||||
while not name.is_empty():
|
||||
var from := source.path_join(name)
|
||||
var to := destination.path_join(name)
|
||||
var from: String = source.path_join(name)
|
||||
var to: String = destination.path_join(name)
|
||||
if access.current_is_dir():
|
||||
if not _copy_tree(from, to):
|
||||
access.list_dir_end()
|
||||
|
|
@ -277,19 +287,19 @@ static func _copy_tree(source: String, destination: String) -> bool:
|
|||
|
||||
static func _write_bytes(path: String, bytes: PackedByteArray) -> bool:
|
||||
DirAccess.make_dir_recursive_absolute(path.get_base_dir())
|
||||
var file := FileAccess.open(path, FileAccess.WRITE)
|
||||
var file: FileAccess = FileAccess.open(path, FileAccess.WRITE)
|
||||
if file == null:
|
||||
return false
|
||||
file.store_buffer(bytes)
|
||||
file.flush()
|
||||
var ok := file.get_error() == OK
|
||||
var ok: bool = file.get_error() == OK
|
||||
file.close()
|
||||
return ok
|
||||
|
||||
|
||||
static func _read_json(path: String) -> Dictionary:
|
||||
var bytes := PortableFileGuard.read_bytes(path)
|
||||
var json := JSON.new()
|
||||
var bytes: PackedByteArray = PortableFileGuard.read_bytes(path)
|
||||
var json: JSON = JSON.new()
|
||||
return (
|
||||
json.data
|
||||
if json.parse(bytes.get_string_from_utf8()) == OK
|
||||
|
|
@ -299,23 +309,23 @@ static func _read_json(path: String) -> Dictionary:
|
|||
|
||||
|
||||
static func _directory_empty(path: String) -> bool:
|
||||
var access := DirAccess.open(path)
|
||||
var access: DirAccess = DirAccess.open(path)
|
||||
if access == null:
|
||||
return true
|
||||
access.list_dir_begin()
|
||||
var name := access.get_next()
|
||||
var name: String = access.get_next()
|
||||
access.list_dir_end()
|
||||
return name.is_empty()
|
||||
|
||||
|
||||
static func _remove_tree(path: String) -> void:
|
||||
var access := DirAccess.open(path)
|
||||
var access: DirAccess = DirAccess.open(path)
|
||||
if access == null:
|
||||
return
|
||||
access.list_dir_begin()
|
||||
var name := access.get_next()
|
||||
var name: String = access.get_next()
|
||||
while not name.is_empty():
|
||||
var child := path.path_join(name)
|
||||
var child: String = path.path_join(name)
|
||||
if access.current_is_dir():
|
||||
_remove_tree(child)
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -7,19 +7,21 @@ const MAX_PORTABLE_FILE_BYTES := 16 * 1024 * 1024
|
|||
static func hash_file(path: String) -> String:
|
||||
if not FileAccess.file_exists(path):
|
||||
return ""
|
||||
var file := FileAccess.open(path, FileAccess.READ)
|
||||
var file: FileAccess = FileAccess.open(path, FileAccess.READ)
|
||||
if file == null or file.get_length() > MAX_PORTABLE_FILE_BYTES:
|
||||
return ""
|
||||
var bytes := file.get_buffer(file.get_length())
|
||||
var bytes: PackedByteArray = file.get_buffer(file.get_length())
|
||||
file.close()
|
||||
return hash_bytes(bytes)
|
||||
|
||||
|
||||
static func hash_bytes(bytes: PackedByteArray) -> String:
|
||||
var context := HashingContext.new()
|
||||
if context.start(HashingContext.HASH_SHA256) != OK:
|
||||
var context: HashingContext = HashingContext.new()
|
||||
var start_error: Error = context.start(HashingContext.HASH_SHA256)
|
||||
if start_error != OK:
|
||||
return ""
|
||||
if context.update(bytes) != OK:
|
||||
var update_error: Error = context.update(bytes)
|
||||
if update_error != OK:
|
||||
return ""
|
||||
return context.finish().hex_encode()
|
||||
|
||||
|
|
@ -27,10 +29,10 @@ static func hash_bytes(bytes: PackedByteArray) -> String:
|
|||
static func read_bytes(path: String, maximum_bytes: int = MAX_PORTABLE_FILE_BYTES) -> PackedByteArray:
|
||||
if not FileAccess.file_exists(path):
|
||||
return PackedByteArray()
|
||||
var file := FileAccess.open(path, FileAccess.READ)
|
||||
var file: FileAccess = FileAccess.open(path, FileAccess.READ)
|
||||
if file == null or file.get_length() > maximum_bytes:
|
||||
return PackedByteArray()
|
||||
var bytes := file.get_buffer(file.get_length())
|
||||
var bytes: PackedByteArray = file.get_buffer(file.get_length())
|
||||
file.close()
|
||||
return bytes
|
||||
|
||||
|
|
@ -42,10 +44,10 @@ static func write_guarded(
|
|||
conflict_directory: String,
|
||||
device_id: String,
|
||||
) -> Dictionary:
|
||||
var current_exists := FileAccess.file_exists(path)
|
||||
var current_hash := hash_file(path) if current_exists else ""
|
||||
var current_exists: bool = FileAccess.file_exists(path)
|
||||
var current_hash: String = hash_file(path) if current_exists else ""
|
||||
if current_hash != expected_hash:
|
||||
var conflict_path := _write_conflict_copy(
|
||||
var conflict_path: String = _write_conflict_copy(
|
||||
path, bytes, conflict_directory, device_id
|
||||
)
|
||||
return {
|
||||
|
|
@ -60,14 +62,14 @@ static func write_guarded(
|
|||
}
|
||||
if not _ensure_parent(path):
|
||||
return {"ok": false, "conflict": false, "hash": expected_hash}
|
||||
var temporary := path + ".tmp"
|
||||
var backup := path + ".backup"
|
||||
var file := FileAccess.open(temporary, FileAccess.WRITE)
|
||||
var temporary: String = path + ".tmp"
|
||||
var backup: String = path + ".backup"
|
||||
var file: FileAccess = FileAccess.open(temporary, FileAccess.WRITE)
|
||||
if file == null:
|
||||
return {"ok": false, "conflict": false, "hash": expected_hash}
|
||||
file.store_buffer(bytes)
|
||||
file.flush()
|
||||
var write_error := file.get_error()
|
||||
var write_error: Error = file.get_error()
|
||||
file.close()
|
||||
if write_error != OK:
|
||||
_remove(temporary)
|
||||
|
|
@ -90,13 +92,13 @@ static func write_guarded(
|
|||
|
||||
|
||||
static func has_syncthing_conflict(directory: String) -> bool:
|
||||
var access := DirAccess.open(directory)
|
||||
var access: DirAccess = DirAccess.open(directory)
|
||||
if access == null:
|
||||
return false
|
||||
access.list_dir_begin()
|
||||
var name := access.get_next()
|
||||
var name: String = access.get_next()
|
||||
while not name.is_empty():
|
||||
var lower := name.to_lower()
|
||||
var lower: String = name.to_lower()
|
||||
if "sync-conflict" in lower or ".syncthing." in lower:
|
||||
access.list_dir_end()
|
||||
return true
|
||||
|
|
@ -113,24 +115,24 @@ static func _write_conflict_copy(
|
|||
) -> String:
|
||||
if DirAccess.make_dir_recursive_absolute(conflict_directory) != OK:
|
||||
return ""
|
||||
var filename := canonical_path.get_file()
|
||||
var safe_device := device_id.left(16)
|
||||
var timestamp := Time.get_datetime_string_from_system().replace(":", "-")
|
||||
var destination := conflict_directory.path_join(
|
||||
var filename: String = canonical_path.get_file()
|
||||
var safe_device: String = device_id.left(16)
|
||||
var timestamp: String = Time.get_datetime_string_from_system().replace(":", "-")
|
||||
var destination: String = conflict_directory.path_join(
|
||||
"%s.local-%s-%s" % [filename, safe_device, timestamp]
|
||||
)
|
||||
var file := FileAccess.open(destination, FileAccess.WRITE)
|
||||
var file: FileAccess = FileAccess.open(destination, FileAccess.WRITE)
|
||||
if file == null:
|
||||
return ""
|
||||
file.store_buffer(bytes)
|
||||
file.flush()
|
||||
var ok := file.get_error() == OK
|
||||
var ok: bool = file.get_error() == OK
|
||||
file.close()
|
||||
return destination if ok else ""
|
||||
|
||||
|
||||
static func _ensure_parent(path: String) -> bool:
|
||||
var parent := path.get_base_dir()
|
||||
var parent: String = path.get_base_dir()
|
||||
return (
|
||||
DirAccess.dir_exists_absolute(parent)
|
||||
or DirAccess.make_dir_recursive_absolute(parent) == OK
|
||||
|
|
|
|||
|
|
@ -323,9 +323,12 @@ func _validate_entry(value: Dictionary, is_saved: bool) -> Dictionary:
|
|||
var endpoint := EndpointParser.parse(endpoint_text)
|
||||
if not endpoint.is_valid():
|
||||
return {}
|
||||
var name: String = str(value.get("display_name", "")).strip_edges()
|
||||
var display_name: String = str(
|
||||
value.get("display_name", "")
|
||||
).strip_edges()
|
||||
if is_saved and (
|
||||
name.is_empty() or name.length() > MAX_DISPLAY_NAME_LENGTH
|
||||
display_name.is_empty()
|
||||
or display_name.length() > MAX_DISPLAY_NAME_LENGTH
|
||||
):
|
||||
return {}
|
||||
var result: Dictionary = _make_entry(
|
||||
|
|
|
|||
|
|
@ -425,19 +425,19 @@ func apply_network_teleport(snapshot: Dictionary) -> void:
|
|||
|
||||
|
||||
func _parse_network_snapshot(snapshot: Dictionary) -> Dictionary:
|
||||
var position: Variant = snapshot.get("position")
|
||||
var snapshot_position: Variant = snapshot.get("position")
|
||||
var network_velocity: Variant = snapshot.get("velocity")
|
||||
if (
|
||||
typeof(position) != TYPE_ARRAY
|
||||
typeof(snapshot_position) != TYPE_ARRAY
|
||||
or typeof(network_velocity) != TYPE_ARRAY
|
||||
or position.size() != 3
|
||||
or snapshot_position.size() != 3
|
||||
or network_velocity.size() != 3
|
||||
):
|
||||
return {}
|
||||
var parsed_position := Vector3(
|
||||
float(position[0]),
|
||||
float(position[1]),
|
||||
float(position[2])
|
||||
float(snapshot_position[0]),
|
||||
float(snapshot_position[1]),
|
||||
float(snapshot_position[2])
|
||||
)
|
||||
var parsed_velocity := Vector3(
|
||||
float(network_velocity[0]),
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ const UI_DESKTOP_RENDER_HEIGHTS: Array[int] = [0, 612, 504, 396, 288]
|
|||
const UI_COMPACT_RENDER_HEIGHTS: Array[int] = [0, 408, 336, 264, 192]
|
||||
|
||||
@export var auto_click_enabled: bool = false
|
||||
@export var use_readable_interface_font: bool = false
|
||||
@export_range(0.10, 0.50, 0.01) var auto_click_interval: float = 0.20
|
||||
@export_range(0.001, 0.012, 0.0005) var mouse_camera_sensitivity: float = 0.005
|
||||
@export_range(0.5, 5.0, 0.1) var controller_camera_sensitivity: float = 2.5
|
||||
|
|
@ -49,6 +50,7 @@ func is_valid() -> bool:
|
|||
func copy() -> PlayerSettings:
|
||||
var result := PlayerSettings.new()
|
||||
result.auto_click_enabled = auto_click_enabled
|
||||
result.use_readable_interface_font = use_readable_interface_font
|
||||
result.auto_click_interval = auto_click_interval
|
||||
result.mouse_camera_sensitivity = mouse_camera_sensitivity
|
||||
result.controller_camera_sensitivity = controller_camera_sensitivity
|
||||
|
|
|
|||
|
|
@ -45,10 +45,17 @@ func load_settings() -> bool:
|
|||
if (
|
||||
typeof(accessibility.get("auto_click_enabled")) != TYPE_BOOL
|
||||
or typeof(camera.get("invert_vertical")) != TYPE_BOOL
|
||||
or (
|
||||
accessibility.has("use_readable_interface_font")
|
||||
and typeof(accessibility["use_readable_interface_font"]) != TYPE_BOOL
|
||||
)
|
||||
):
|
||||
return _use_defaults_after_corruption("Player settings values are invalid.")
|
||||
var loaded := PlayerSettings.new()
|
||||
loaded.auto_click_enabled = accessibility["auto_click_enabled"]
|
||||
loaded.use_readable_interface_font = bool(
|
||||
accessibility.get("use_readable_interface_font", false)
|
||||
)
|
||||
loaded.auto_click_interval = _read_float(
|
||||
accessibility.get("auto_click_interval"),
|
||||
-1.0
|
||||
|
|
@ -124,6 +131,9 @@ func save_now() -> bool:
|
|||
"accessibility": {
|
||||
"auto_click_enabled": current_settings.auto_click_enabled,
|
||||
"auto_click_interval": current_settings.auto_click_interval,
|
||||
"use_readable_interface_font": (
|
||||
current_settings.use_readable_interface_font
|
||||
),
|
||||
},
|
||||
"camera": {
|
||||
"mouse_sensitivity": current_settings.mouse_camera_sensitivity,
|
||||
|
|
|
|||
11
ui/fonts/Tuffy-LICENSE.txt
Normal file
11
ui/fonts/Tuffy-LICENSE.txt
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
We, the copyright holders of this work, hereby release it into the
|
||||
public domain. This applies worldwide.
|
||||
|
||||
In case this is not legally possible,
|
||||
|
||||
We grant any entity the right to use this work for any purpose, without
|
||||
any conditions, unless such conditions are required by law.
|
||||
|
||||
Thatcher Ulrich <tu@tulrich.com> http://tulrich.com
|
||||
Karoly Barta bartakarcsi@gmail.com
|
||||
Michael Evans http://www.evertype.com
|
||||
BIN
ui/fonts/Tuffy_Bold.otf
Normal file
BIN
ui/fonts/Tuffy_Bold.otf
Normal file
Binary file not shown.
36
ui/fonts/Tuffy_Bold.otf.import
Normal file
36
ui/fonts/Tuffy_Bold.otf.import
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
[remap]
|
||||
|
||||
importer="font_data_dynamic"
|
||||
type="FontFile"
|
||||
uid="uid://xhdms1j01k2q"
|
||||
path="res://.godot/imported/Tuffy_Bold.otf-1ed903e9af3906035b0cf09ea141ce25.fontdata"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://ui/fonts/Tuffy_Bold.otf"
|
||||
dest_files=["res://.godot/imported/Tuffy_Bold.otf-1ed903e9af3906035b0cf09ea141ce25.fontdata"]
|
||||
|
||||
[params]
|
||||
|
||||
Rendering=null
|
||||
antialiasing=1
|
||||
generate_mipmaps=false
|
||||
disable_embedded_bitmaps=true
|
||||
multichannel_signed_distance_field=false
|
||||
msdf_pixel_range=8
|
||||
msdf_size=48
|
||||
allow_system_fallback=true
|
||||
force_autohinter=false
|
||||
modulate_color_glyphs=false
|
||||
hinting=3
|
||||
subpixel_positioning=4
|
||||
keep_rounding_remainders=true
|
||||
oversampling=0.0
|
||||
Fallbacks=null
|
||||
fallbacks=[]
|
||||
Compress=null
|
||||
compress=true
|
||||
preload=[]
|
||||
language_support={}
|
||||
script_support={}
|
||||
opentype_features={}
|
||||
|
|
@ -184,6 +184,7 @@ func setup_data_and_identity(
|
|||
player_identity: PlayerIdentityStore,
|
||||
host_identity: HostIdentityStore,
|
||||
network_session: NetworkSession,
|
||||
interface_fonts: InterfaceFontController,
|
||||
) -> void:
|
||||
for panel: SettingsPanelType in [
|
||||
_title_settings_panel, _pause_settings_panel
|
||||
|
|
@ -194,6 +195,7 @@ func setup_data_and_identity(
|
|||
player_identity,
|
||||
host_identity,
|
||||
network_session,
|
||||
interface_fonts,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -219,7 +221,7 @@ func _process(_delta: float) -> void:
|
|||
}.get(item_id, "")
|
||||
parts.append(
|
||||
"%s %d:%02d"
|
||||
% [label, int(remaining) / 60, int(remaining) % 60]
|
||||
% [label, floori(remaining / 60.0), int(remaining) % 60]
|
||||
)
|
||||
_effect_status.text = " ".join(parts)
|
||||
_effect_status.visible = not parts.is_empty()
|
||||
|
|
|
|||
55
ui/interface_font_controller.gd
Normal file
55
ui/interface_font_controller.gd
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
class_name InterfaceFontController
|
||||
extends Node
|
||||
|
||||
signal font_mode_changed(use_readable_font: bool)
|
||||
|
||||
const READABLE_FONT_PATH: String = "res://ui/fonts/Tuffy_Bold.otf"
|
||||
|
||||
var _game_theme: Theme = preload("res://ui/game_theme.tres")
|
||||
var _game_font: Font = preload("res://ui/fonts/seattle_avenue.otf")
|
||||
var _readable_font: Font
|
||||
var _utility_theme: Theme
|
||||
var _use_readable_font: bool = false
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if ResourceLoader.exists(READABLE_FONT_PATH):
|
||||
_readable_font = load(READABLE_FONT_PATH) as Font
|
||||
if _readable_font == null:
|
||||
push_error("Readable interface font resource is unavailable.")
|
||||
else:
|
||||
_readable_font.fallbacks = [_game_font]
|
||||
_utility_theme = _game_theme.duplicate(true)
|
||||
_utility_theme.default_font = readable_font()
|
||||
|
||||
|
||||
func set_readable_font_enabled(enabled: bool) -> void:
|
||||
_use_readable_font = enabled
|
||||
_game_theme.default_font = (
|
||||
_readable_font
|
||||
if enabled and _readable_font != null
|
||||
else _game_font
|
||||
)
|
||||
font_mode_changed.emit(_use_readable_font)
|
||||
|
||||
|
||||
func is_readable_font_enabled() -> bool:
|
||||
return _use_readable_font
|
||||
|
||||
|
||||
func apply_utility_theme(themed_node: Node) -> void:
|
||||
if themed_node == null:
|
||||
return
|
||||
if _utility_theme == null:
|
||||
_utility_theme = _game_theme.duplicate(true)
|
||||
_utility_theme.default_font = (
|
||||
_readable_font if _readable_font != null else _game_font
|
||||
)
|
||||
if themed_node is Control:
|
||||
(themed_node as Control).theme = _utility_theme
|
||||
elif themed_node is Window:
|
||||
(themed_node as Window).theme = _utility_theme
|
||||
|
||||
|
||||
func readable_font() -> Font:
|
||||
return _readable_font if _readable_font != null else _game_font
|
||||
1
ui/interface_font_controller.gd.uid
Normal file
1
ui/interface_font_controller.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://djmxy6jibi5y3
|
||||
|
|
@ -194,8 +194,6 @@ enum CloseReason {
|
|||
@onready var _selection_status: BubbleStatusBubbleType = %SelectionStatus
|
||||
@onready var _offer_status: BubbleStatusBubbleType = %OfferStatus
|
||||
@onready var _inventory_section: Control = %InventorySection
|
||||
@onready var _inventory_body: BoxContainer = %InventoryBody
|
||||
@onready var _inventory_scroll: ScrollContainer = %InventoryScroll
|
||||
@onready var _bag_section: Control = %BagSection
|
||||
@onready var _logbook_section: Control = %LogbookSection
|
||||
@onready var _bag_empty: Label = %BagEmpty
|
||||
|
|
@ -210,9 +208,6 @@ enum CloseReason {
|
|||
@onready var _held_value: Label = %HeldValue
|
||||
@onready var _cooler_count: Label = %CoolerCount
|
||||
@onready var _inventory_empty: Label = %InventoryEmpty
|
||||
@onready var _inventory_grid: GridContainer = %InventoryGrid
|
||||
@onready var _inventory_list: Control = %InventoryList
|
||||
@onready var _detail_panel: Control = %DetailPanel
|
||||
@onready var _detail_texture: TextureRect = %DetailTexture
|
||||
@onready var _detail_name: Label = %DetailName
|
||||
@onready var _detail_data: Label = %DetailData
|
||||
|
|
@ -227,8 +222,6 @@ enum CloseReason {
|
|||
@onready var _cancel_sale_button: Button = %CancelSaleButton
|
||||
@onready var _logbook_empty: Label = %LogbookEmpty
|
||||
@onready var _logbook_grid: GridContainer = %LogbookGrid
|
||||
@onready var _status_shoal: HBoxContainer = %StatusShoal
|
||||
@onready var _sort_bar: HBoxContainer = %SortBar
|
||||
|
||||
var _compact_layout: bool = false
|
||||
var _player: PlayerType
|
||||
|
|
@ -1181,6 +1174,7 @@ func _update_shell_layout() -> void:
|
|||
_logbook_page.size = reference_size
|
||||
_logbook_page.position = Vector2.ZERO
|
||||
_logbook_rest_position = Vector2.ZERO
|
||||
_mail_page.set_anchors_preset(Control.PRESET_TOP_LEFT)
|
||||
_mail_page.size = reference_size
|
||||
_mail_page.position = Vector2.ZERO
|
||||
_mail_rest_position = Vector2.ZERO
|
||||
|
|
@ -1732,7 +1726,10 @@ func _reset_page_transition_visuals() -> void:
|
|||
|
||||
|
||||
func _on_sort_selected(index: int, source: OptionButton) -> void:
|
||||
_sort_mode = source.get_item_id(index)
|
||||
var selected_id: int = source.get_item_id(index)
|
||||
if selected_id < SortMode.CATCH_ORDER or selected_id > SortMode.RARITY:
|
||||
return
|
||||
_sort_mode = selected_id as SortMode
|
||||
_sort_option.select(_sort_mode)
|
||||
_cooler_sort_option.select(_sort_mode)
|
||||
_refresh_inventory()
|
||||
|
|
@ -1891,7 +1888,7 @@ func _layout_bag_items() -> void:
|
|||
var row: int = floori(float(index) / float(columns))
|
||||
var stable_offset := Vector2(
|
||||
float(identity_hash % 13) - 6.0,
|
||||
float((identity_hash / 19) % 11) - 5.0,
|
||||
float(floori(float(identity_hash) / 19.0) % 11) - 5.0,
|
||||
)
|
||||
var lane_offset: float = 18.0 if row % 2 == 1 else 0.0
|
||||
item_node.custom_minimum_size = item_size
|
||||
|
|
@ -2225,7 +2222,7 @@ func _layout_cooler_fish(animate: bool = true) -> void:
|
|||
var offset_center: float = 2.0 if _compact_layout else 3.0
|
||||
var stable_offset := Vector2(
|
||||
float(identity_hash % offset_span) - offset_center,
|
||||
float((identity_hash / 17) % offset_span) - offset_center,
|
||||
float(floori(float(identity_hash) / 17.0) % offset_span) - offset_center,
|
||||
)
|
||||
var lane_offset: float = 5.0 if row % 2 == 1 else 0.0
|
||||
var target := Vector2(
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ enum PresentationMode {
|
|||
@onready var _invert_y_toggle: BubbleButton = %InvertYToggle
|
||||
@onready var _auto_click_toggle: BubbleButton = %AutoClickToggle
|
||||
@onready var _auto_click_interval: BubbleButton = %AutoClickIntervalValue
|
||||
@onready var _readable_font_toggle: BubbleButton = %ReadableFontToggle
|
||||
|
||||
@onready var _world_options: Array[BubbleButton] = [
|
||||
%WorldLegible,
|
||||
|
|
@ -83,6 +84,7 @@ var _data_root: PlayerDataRoot
|
|||
var _identity_backups: IdentityBackupService
|
||||
var _player_identity: PlayerIdentityStore
|
||||
var _host_identity: HostIdentityStore
|
||||
var _interface_fonts: InterfaceFontController
|
||||
var _data_folder_dialog: FileDialog
|
||||
var _backup_file_dialog: FileDialog
|
||||
var _export_file_dialog: FileDialog
|
||||
|
|
@ -148,6 +150,7 @@ func _ready() -> void:
|
|||
)
|
||||
_invert_y_toggle.pressed.connect(_toggle_invert_y)
|
||||
_auto_click_toggle.pressed.connect(_toggle_auto_click)
|
||||
_readable_font_toggle.pressed.connect(_toggle_readable_font)
|
||||
%IntervalDecrease.pressed.connect(_adjust_auto_click_interval.bind(-1))
|
||||
%IntervalIncrease.pressed.connect(_adjust_auto_click_interval.bind(1))
|
||||
_auto_click_interval.pressed.connect(
|
||||
|
|
@ -185,13 +188,16 @@ func setup_data_and_identity(
|
|||
player_identity: PlayerIdentityStore,
|
||||
host_identity: HostIdentityStore,
|
||||
session: NetworkSession,
|
||||
interface_fonts: InterfaceFontController,
|
||||
) -> void:
|
||||
_data_root = data_root
|
||||
_identity_backups = identity_backups
|
||||
_player_identity = player_identity
|
||||
_host_identity = host_identity
|
||||
_network_session = session
|
||||
_interface_fonts = interface_fonts
|
||||
_identity_backups.operation_finished.connect(_on_identity_operation_finished)
|
||||
_interface_fonts.apply_utility_theme(_data_page)
|
||||
_refresh_data_page()
|
||||
|
||||
|
||||
|
|
@ -410,14 +416,18 @@ func _choose_data_folder() -> void:
|
|||
_data_folder_dialog = FileDialog.new()
|
||||
_data_folder_dialog.file_mode = FileDialog.FILE_MODE_OPEN_DIR
|
||||
_data_folder_dialog.access = FileDialog.ACCESS_FILESYSTEM
|
||||
_data_folder_dialog.use_native_dialog = false
|
||||
_data_folder_dialog.dir_selected.connect(_change_data_folder)
|
||||
_interface_fonts.apply_utility_theme(_data_folder_dialog)
|
||||
add_child(_data_folder_dialog)
|
||||
_data_folder_dialog.current_dir = _data_root.root_path.get_base_dir()
|
||||
_data_folder_dialog.popup_centered_ratio(0.75)
|
||||
|
||||
|
||||
func _change_data_folder(path: String) -> void:
|
||||
var result := PortableDataMigration.migrate_active_to(_data_root, path)
|
||||
var result: Dictionary = PortableDataMigration.migrate_active_to(
|
||||
_data_root, path
|
||||
)
|
||||
if bool(result.get("requires_existing_root_decision", false)):
|
||||
_show_existing_data_folder_choice(path)
|
||||
return
|
||||
|
|
@ -430,7 +440,7 @@ func _change_data_folder(path: String) -> void:
|
|||
|
||||
|
||||
func _show_existing_data_folder_choice(path: String) -> void:
|
||||
var dialog := ConfirmationDialog.new()
|
||||
var dialog: ConfirmationDialog = ConfirmationDialog.new()
|
||||
dialog.title = "Existing NETFISHING data"
|
||||
dialog.ok_button_text = "Use Selected Data"
|
||||
dialog.dialog_text = (
|
||||
|
|
@ -449,7 +459,7 @@ func _show_existing_data_folder_choice(path: String) -> void:
|
|||
dialog.custom_action.connect(func(action: StringName) -> void:
|
||||
if action != &"replace":
|
||||
return
|
||||
var result := PortableDataMigration.replace_existing_with_active(
|
||||
var result: Dictionary = PortableDataMigration.replace_existing_with_active(
|
||||
_data_root, path
|
||||
)
|
||||
_feedback.text = str(result.get("message", "Could not replace selected data."))
|
||||
|
|
@ -457,6 +467,7 @@ func _show_existing_data_folder_choice(path: String) -> void:
|
|||
get_tree().call_deferred("quit")
|
||||
dialog.queue_free()
|
||||
)
|
||||
_interface_fonts.apply_utility_theme(dialog)
|
||||
add_child(dialog)
|
||||
dialog.popup_centered(Vector2i(620, 340))
|
||||
|
||||
|
|
@ -466,13 +477,15 @@ func _choose_identity_export(identity_type: String) -> void:
|
|||
return
|
||||
_pending_identity_operation = "export"
|
||||
_pending_identity_type = identity_type
|
||||
var suggested := _identity_backups.default_export_path(identity_type)
|
||||
var suggested: String = _identity_backups.default_export_path(identity_type)
|
||||
if _export_file_dialog == null:
|
||||
_export_file_dialog = FileDialog.new()
|
||||
_export_file_dialog.file_mode = FileDialog.FILE_MODE_SAVE_FILE
|
||||
_export_file_dialog.access = FileDialog.ACCESS_FILESYSTEM
|
||||
_export_file_dialog.use_native_dialog = false
|
||||
_export_file_dialog.filters = PackedStringArray(["*.nfidentity ; NETFISHING identity backup"])
|
||||
_export_file_dialog.file_selected.connect(_identity_export_file_selected)
|
||||
_interface_fonts.apply_utility_theme(_export_file_dialog)
|
||||
add_child(_export_file_dialog)
|
||||
_export_file_dialog.current_dir = suggested.get_base_dir()
|
||||
_export_file_dialog.current_file = suggested.get_file()
|
||||
|
|
@ -495,8 +508,10 @@ func _choose_identity_import(identity_type: String) -> void:
|
|||
_backup_file_dialog = FileDialog.new()
|
||||
_backup_file_dialog.file_mode = FileDialog.FILE_MODE_OPEN_FILE
|
||||
_backup_file_dialog.access = FileDialog.ACCESS_FILESYSTEM
|
||||
_backup_file_dialog.use_native_dialog = false
|
||||
_backup_file_dialog.filters = PackedStringArray(["*.nfidentity ; NETFISHING identity backup"])
|
||||
_backup_file_dialog.file_selected.connect(_identity_import_file_selected)
|
||||
_interface_fonts.apply_utility_theme(_backup_file_dialog)
|
||||
add_child(_backup_file_dialog)
|
||||
_backup_file_dialog.current_dir = _data_root.identity_backup_directory()
|
||||
_backup_file_dialog.popup_centered_ratio(0.75)
|
||||
|
|
@ -512,8 +527,8 @@ func _show_passphrase_dialog(exporting: bool) -> void:
|
|||
_passphrase_dialog = ConfirmationDialog.new()
|
||||
_passphrase_dialog.title = "Encrypted identity backup"
|
||||
_passphrase_dialog.confirmed.connect(_submit_identity_passphrase)
|
||||
var fields := VBoxContainer.new()
|
||||
var warning := Label.new()
|
||||
var fields: VBoxContainer = VBoxContainer.new()
|
||||
var warning: Label = Label.new()
|
||||
warning.text = (
|
||||
"Anyone with this backup and passphrase can use your identity."
|
||||
)
|
||||
|
|
@ -528,6 +543,7 @@ func _show_passphrase_dialog(exporting: bool) -> void:
|
|||
_passphrase_confirm.secret = true
|
||||
fields.add_child(_passphrase_confirm)
|
||||
_passphrase_dialog.add_child(fields)
|
||||
_interface_fonts.apply_utility_theme(_passphrase_dialog)
|
||||
add_child(_passphrase_dialog)
|
||||
_passphrase_confirm.visible = exporting
|
||||
_passphrase_entry.clear()
|
||||
|
|
@ -541,7 +557,7 @@ func _show_passphrase_dialog(exporting: bool) -> void:
|
|||
|
||||
|
||||
func _submit_identity_passphrase() -> void:
|
||||
var passphrase := _passphrase_entry.text
|
||||
var passphrase: String = _passphrase_entry.text
|
||||
if _pending_identity_operation == "export":
|
||||
_identity_backups.export_backup(
|
||||
_pending_identity_type,
|
||||
|
|
@ -550,7 +566,7 @@ func _submit_identity_passphrase() -> void:
|
|||
_passphrase_confirm.text,
|
||||
)
|
||||
return
|
||||
var inspected := _identity_backups.import_backup(
|
||||
var inspected: Dictionary = _identity_backups.import_backup(
|
||||
_pending_identity_type,
|
||||
_pending_identity_path,
|
||||
passphrase,
|
||||
|
|
@ -566,7 +582,7 @@ func _submit_identity_passphrase() -> void:
|
|||
|
||||
|
||||
func _show_identity_replacement_confirmation() -> void:
|
||||
var dialog := ConfirmationDialog.new()
|
||||
var dialog: ConfirmationDialog = ConfirmationDialog.new()
|
||||
dialog.title = "Replace active identity?"
|
||||
dialog.ok_button_text = "Review Replacement"
|
||||
dialog.dialog_text = (
|
||||
|
|
@ -579,6 +595,7 @@ func _show_identity_replacement_confirmation() -> void:
|
|||
dialog.set_meta("confirmation_step", 1)
|
||||
dialog.confirmed.connect(_advance_identity_replacement.bind(dialog))
|
||||
dialog.canceled.connect(dialog.queue_free)
|
||||
_interface_fonts.apply_utility_theme(dialog)
|
||||
add_child(dialog)
|
||||
dialog.popup_centered(Vector2i(620, 360))
|
||||
|
||||
|
|
@ -659,6 +676,9 @@ func _load_controls() -> void:
|
|||
_mouse_sensitivity = settings.mouse_camera_sensitivity
|
||||
_controller_sensitivity = settings.controller_camera_sensitivity
|
||||
_invert_camera_y = settings.invert_camera_y
|
||||
_readable_font_toggle.button_pressed = (
|
||||
settings.use_readable_interface_font
|
||||
)
|
||||
_refresh_value_labels()
|
||||
|
||||
|
||||
|
|
@ -686,6 +706,14 @@ func _refresh_value_labels() -> void:
|
|||
"accessibility\nauto-click\n"
|
||||
+ ("on" if _auto_click_enabled else "off")
|
||||
)
|
||||
_readable_font_toggle.text = (
|
||||
"readable\ninterface font\n"
|
||||
+ (
|
||||
"on"
|
||||
if settings.use_readable_interface_font
|
||||
else "off"
|
||||
)
|
||||
)
|
||||
_auto_click_interval.text = (
|
||||
"auto-click\ninterval\n%.2f s" % _auto_click_interval_value
|
||||
)
|
||||
|
|
@ -709,6 +737,19 @@ func _set_world_pixelation(pixel_size: int) -> void:
|
|||
_refresh_value_labels()
|
||||
|
||||
|
||||
func _toggle_readable_font() -> void:
|
||||
if _settings_manager == null:
|
||||
return
|
||||
var edited: PlayerSettings = _settings_manager.current_settings.copy()
|
||||
edited.use_readable_interface_font = (
|
||||
not edited.use_readable_interface_font
|
||||
)
|
||||
if not _settings_manager.apply_settings(edited):
|
||||
_feedback.text = "failed to save accessibility settings."
|
||||
return
|
||||
_load_controls()
|
||||
|
||||
|
||||
func _set_ui_pixelation(pixel_size: int) -> void:
|
||||
if _settings_manager == null:
|
||||
return
|
||||
|
|
|
|||
|
|
@ -486,8 +486,8 @@ grow_horizontal = 2
|
|||
grow_vertical = 2
|
||||
script = ExtResource("3_page")
|
||||
page_id = &"accessibility"
|
||||
bubble_paths = Array[NodePath]([NodePath("BubbleCluster/AutoClickToggle"), NodePath("BubbleCluster/IntervalDecrease"), NodePath("BubbleCluster/AutoClickIntervalValue"), NodePath("BubbleCluster/IntervalIncrease"), NodePath("BubbleCluster/IntervalHelp"), NodePath("BubbleCluster/AccessibilityBackButton")])
|
||||
focus_paths = Array[NodePath]([NodePath("BubbleCluster/AutoClickToggle"), NodePath("BubbleCluster/IntervalDecrease"), NodePath("BubbleCluster/AutoClickIntervalValue"), NodePath("BubbleCluster/IntervalIncrease"), NodePath("BubbleCluster/AccessibilityBackButton")])
|
||||
bubble_paths = Array[NodePath]([NodePath("BubbleCluster/AutoClickToggle"), NodePath("BubbleCluster/IntervalDecrease"), NodePath("BubbleCluster/AutoClickIntervalValue"), NodePath("BubbleCluster/IntervalIncrease"), NodePath("BubbleCluster/ReadableFontToggle"), NodePath("BubbleCluster/IntervalHelp"), NodePath("BubbleCluster/AccessibilityBackButton")])
|
||||
focus_paths = Array[NodePath]([NodePath("BubbleCluster/AutoClickToggle"), NodePath("BubbleCluster/IntervalDecrease"), NodePath("BubbleCluster/AutoClickIntervalValue"), NodePath("BubbleCluster/IntervalIncrease"), NodePath("BubbleCluster/ReadableFontToggle"), NodePath("BubbleCluster/AccessibilityBackButton")])
|
||||
initial_focus_path = NodePath("BubbleCluster/AutoClickToggle")
|
||||
back_focus_path = NodePath("BubbleCluster/AccessibilityBackButton")
|
||||
|
||||
|
|
@ -562,6 +562,41 @@ minimum_font_size = 13
|
|||
maximum_font_size = 22
|
||||
motion_phase = 4.1
|
||||
|
||||
[node name="ReadableFontToggle" parent="AccessibilityPage/BubbleCluster" instance=ExtResource("4_bubble")]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(0, 0)
|
||||
toggle_mode = true
|
||||
text = "readable\ninterface font\noff"
|
||||
neutral_size = Vector2(180, 132)
|
||||
desktop_anchor = Vector2(92, 405)
|
||||
compact_anchor = Vector2(102, 374)
|
||||
compact_minimum_size = Vector2(156, 118)
|
||||
minimum_font_size = 13
|
||||
maximum_font_size = 21
|
||||
motion_phase = 4.6
|
||||
|
||||
[node name="ReadableFontSample" type="Label" parent="AccessibilityPage/BubbleCluster"]
|
||||
layout_mode = 0
|
||||
offset_left = 250.0
|
||||
offset_top = 20.0
|
||||
offset_right = 700.0
|
||||
offset_bottom = 82.0
|
||||
mouse_filter = 2
|
||||
text = "The quick brown fox jumps over the lazy dog.\n0123456789"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
autowrap_mode = 2
|
||||
|
||||
[node name="ReadableFontHelper" type="Label" parent="AccessibilityPage/BubbleCluster"]
|
||||
layout_mode = 0
|
||||
offset_left = 250.0
|
||||
offset_top = 82.0
|
||||
offset_right = 700.0
|
||||
offset_bottom = 112.0
|
||||
mouse_filter = 2
|
||||
text = "Uses a clearer font for menus and interface text."
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="AccessibilityBackButton" parent="AccessibilityPage/BubbleCluster" instance=ExtResource("4_bubble")]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(0, 0)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue