diff --git a/build-android-apk.sh b/build-android-apk.sh index 760dd10c..f065ac0e 100755 --- a/build-android-apk.sh +++ b/build-android-apk.sh @@ -18,7 +18,7 @@ cargo run -- run --only-build echo "Link export templates" mkdir -p ${HOME}/.local/share/godot/export_templates/ cd ${HOME}/.local/share/godot/export_templates/ -ln -sf ${EXPLORER_PATH}/.bin/godot/templates/templates/ 4.2.stable +ln -sf ${EXPLORER_PATH}/.bin/godot/templates/templates/ 4.2.1.stable echo "Build for Android" cd ${EXPLORER_PATH}/rust/decentraland-godot-lib diff --git a/godot/src/config/config_data.gd b/godot/src/config/config_data.gd index 2c706873..93f952ae 100644 --- a/godot/src/config/config_data.gd +++ b/godot/src/config/config_data.gd @@ -125,7 +125,11 @@ func load_from_default(): self.resolution = "1280 x 720" self.window_size = "1280 x 720" - self.ui_scale = 1 + if Global.is_mobile: + self.ui_scale = 1.75 + else: + self.ui_scale = 1.0 + self.session_account = {} self.last_realm_joined = "https://sdk-team-cdn.decentraland.org/ipfs/goerli-plaza-main" diff --git a/godot/src/decentraland_components/avatar.gd b/godot/src/decentraland_components/avatar.gd index f7d4b618..7a76c758 100644 --- a/godot/src/decentraland_components/avatar.gd +++ b/godot/src/decentraland_components/avatar.gd @@ -62,22 +62,40 @@ func _unset_avatar_modifier_area(): # TODO: Passport (enable functionality) +func async_update_avatar_from_profile(profile: Dictionary): + var profile_content: Dictionary = profile.get("content", {}) + var id = profile_content.get("userId", "unknown") + if id == null: + id = "unknown" + + avatar_id = id + if profile_content.get("name", "") != null: + avatar_name = profile_content.get("name", "") + + await async_update_avatar(profile.get("content", {}).get("avatar", {})) + + func async_update_avatar(avatar: Dictionary): current_content_url = "https://peer.decentraland.org/content/" if not Global.realm.content_base_url.is_empty(): current_content_url = Global.realm.content_base_url + if avatar.is_empty(): + printerr("Trying to update an avatar with an empty object") + return + playing_emote = false set_idle() - avatar_name = avatar.get("name") - avatar_id = avatar.get("userId", "") + if avatar.get("name", "") != null: + avatar_name = avatar.get("name", "") + label_3d_name.text = avatar_name current_wearables = avatar.get("wearables") - current_body_shape = avatar.get("body_shape") - current_eyes_color = avatar.get("eyes") - current_skin_color = avatar.get("skin") - current_hair_color = avatar.get("hair") + current_body_shape = avatar.get("bodyShape") + current_eyes_color = Avatar.from_color_object(avatar.get("eyes", {}).get("color", null)) + current_skin_color = Avatar.from_color_object(avatar.get("skin", {}).get("color", null)) + current_hair_color = Avatar.from_color_object(avatar.get("hair", {}).get("color", null)) var wearable_to_request := PackedStringArray(current_wearables) wearable_to_request.push_back(current_body_shape) @@ -91,6 +109,21 @@ func async_update_avatar(avatar: Dictionary): async_fetch_wearables_dependencies() +static func from_color_object(color: Variant, default: Color = Color.WHITE) -> Color: + if color is Dictionary: + return Color( + color.get("r", default.r), + color.get("g", default.g), + color.get("b", default.b), + color.get("a", default.a) + ) + return default + + +static func to_color_object(color: Color) -> Dictionary: + return {"color": {"r": color.r, "g": color.g, "b": color.b, "a": color.a}} + + func _add_animation(index: int, animation_name: String): var animation = Global.animation_importer.get_animation_from_gltf(animation_name) global_animation_library.add_animation(animation_name, animation) diff --git a/godot/src/decentraland_components/nft_shape.gd b/godot/src/decentraland_components/nft_shape.gd index 2fa8c175..49c0623e 100644 --- a/godot/src/decentraland_components/nft_shape.gd +++ b/godot/src/decentraland_components/nft_shape.gd @@ -87,9 +87,9 @@ func _set_picture_frame_material( func _set_loading_material( - style: NftFrameStyleLoader.NFTFrameStyles, current_frame: Node3D, background_color: Color + style: NftFrameStyleLoader.NFTFrameStyles, new_current_frame: Node3D, background_color: Color ): - var mesh_instance_3d: MeshInstance3D = _get_mesh_instance_3d(current_frame) + var mesh_instance_3d: MeshInstance3D = _get_mesh_instance_3d(new_current_frame) if mesh_instance_3d == null: printerr("set nft mesh_instance_3d is null") return diff --git a/godot/src/global.gd b/godot/src/global.gd index 6529cb51..727f061a 100644 --- a/godot/src/global.gd +++ b/godot/src/global.gd @@ -10,8 +10,10 @@ enum CameraMode { # Only for debugging purpose, Godot editor doesn't include a custom param debugging const FORCE_TEST = false -const FORCE_TEST_ARG = '["52,-52"]' -const FORCE_TEST_REALM = "https://sdilauro.github.io/dae-unit-tests/dae-unit-tests" +const FORCE_TEST_ARG = "[[52,-52],[52,-54],[52,-56],[52,-58],[52,-60],[52,-62],[52,-64],[52,-66],[52,-68],[54,-52],[54,-54],[54,-56],[54,-58],[54,-60],[54,-62]]" +const FORCE_TEST_REALM = "https://decentraland.github.io/scene-explorer-tests/scene-explorer-tests" +#const FORCE_TEST_ARG = "[[52,-64]]" +#const FORCE_TEST_REALM = "http://localhost:8000" ## Global classes (singleton pattern) var content_manager: ContentManager @@ -27,6 +29,7 @@ var nft_fetcher: OpenSeaFetcher = OpenSeaFetcher.new() var nft_frame_loader: NftFrameStyleLoader = NftFrameStyleLoader.new() var standalone = false +var dcl_android_plugin @onready var is_mobile = OS.has_feature("mobile") #@onready var is_mobile = true @@ -56,6 +59,9 @@ func _ready(): if not DirAccess.dir_exists_absolute("user://content/"): DirAccess.make_dir_absolute("user://content/") + if Engine.has_singleton("DclAndroidPlugin"): + dcl_android_plugin = Engine.get_singleton("DclAndroidPlugin") + self.realm = Realm.new() self.realm.set_name("realm") diff --git a/godot/src/logic/player/player.gd b/godot/src/logic/player/player.gd index a9b3382d..017e9e28 100644 --- a/godot/src/logic/player/player.gd +++ b/godot/src/logic/player/player.gd @@ -84,7 +84,7 @@ func _ready(): func _on_player_profile_changed(new_profile: Dictionary): - avatar.async_update_avatar(new_profile) + avatar.async_update_avatar_from_profile(new_profile) func _on_param_changed(_param): diff --git a/godot/src/logic/player/player_identity.gd b/godot/src/logic/player/player_identity.gd index b1cf4938..ada2d018 100644 --- a/godot/src/logic/player/player_identity.gd +++ b/godot/src/logic/player/player_identity.gd @@ -41,8 +41,8 @@ func async_fetch_profile(address: String, lambda_server_base_url: String) -> voi self.set_default_profile() -func _on_wallet_connected(address: String, _chain_id: int, is_guest: bool): - if is_guest: +func _on_wallet_connected(address: String, _chain_id: int, is_guest_value: bool): + if is_guest_value: self.set_default_profile() return diff --git a/godot/src/test/avatar/test_avatar.gd b/godot/src/test/avatar/test_avatar.gd index 87a84f44..0962979e 100644 --- a/godot/src/test/avatar/test_avatar.gd +++ b/godot/src/test/avatar/test_avatar.gd @@ -20,16 +20,17 @@ var emotes: Array = [] # Called when the node enters the scene tree for the first time. func _ready(): - avatar.async_update_avatar( - "https://peer.decentraland.org/content", - "Godot User", - body_shape, - eyes_color, - hair_color, - skin_color, - wearables, - emotes - ) + # TODO: this is outdated + #avatar.async_update_avatar( + #"https://peer.decentraland.org/content", + #"Godot User", + #body_shape, + #eyes_color, + #hair_color, + #skin_color, + #wearables, + #emotes + #) # Called every frame. 'delta' is the elapsed time since the previous frame. diff --git a/godot/src/test/testing_api.gd b/godot/src/test/testing_api.gd index 1164ba74..b6fd3a25 100644 --- a/godot/src/test/testing_api.gd +++ b/godot/src/test/testing_api.gd @@ -68,17 +68,19 @@ func start(): prints("parcels_str=" + str(parcels_str)) var parcels = JSON.parse_string(parcels_str) - for pos_str in parcels: - var pos = pos_str.split(",") - if pos.size() == 2: - var parcel_pos: Vector2i = Vector2i(int(pos[0]), int(pos[1])) + for pos_array in parcels: + if not pos_array is Array: + continue + + if pos_array.size() == 2: + var parcel_pos: Vector2i = Vector2i(int(pos_array[0]), int(pos_array[1])) scene_tests.push_back(SceneTestItem.new(parcel_pos, "")) else: - printerr("Scene to test '" + pos_str + "' not supported for now.") + printerr("Scene to test '" + pos_array + "' not supported for now.") if scene_tests.is_empty(): printerr( - 'Couldn\'t get any scene to test in the scene-test mode. Please try --scene-test ["52,-52"]' + "Couldn't get any scene to test in the scene-test mode. Please try --scene-test [[52,-52]]" ) get_tree().quit(1) return @@ -151,14 +153,17 @@ func async_take_and_compare_snapshot( RenderingServer.set_default_clear_color(Color(0, 0, 0, 0)) var viewport = get_viewport() - var camera = viewport.get_camera_3d() - var previous_camera_position = camera.global_position - var previous_camera_rotation = camera.global_rotation + var previous_camera = viewport.get_camera_3d() + + var test_camera_3d = Camera3D.new() + add_child(test_camera_3d) + test_camera_3d.make_current() + var previous_viewport_size = viewport.size viewport.size = screenshot_size - camera.global_position = camera_position - camera.look_at(camera_target) + test_camera_3d.global_position = camera_position + test_camera_3d.look_at(camera_target) get_node("/root/explorer").set_visible_ui(false) if hide_player: @@ -170,13 +175,16 @@ func async_take_and_compare_snapshot( var viewport_img := viewport.get_texture().get_image() + #await get_tree().create_timer(10.0).timeout + get_node("/root/explorer").set_visible_ui(true) if hide_player: get_node("/root/explorer/Player").show() viewport.size = previous_viewport_size - camera.global_position = previous_camera_position - camera.global_rotation = previous_camera_rotation + previous_camera.make_current() + remove_child(test_camera_3d) + test_camera_3d.queue_free() var existing_snapshot: Image = null var content_mapping = Global.scene_runner.get_scene_content_mapping(scene_id) @@ -224,6 +232,7 @@ func dump_test_result_and_get_ok() -> bool: for scene in scene_tests: if scene.test_result.is_empty(): ok = false + prints("🔴 test result is empty in the scene " + str(scene.parcel_position)) continue prints(scene.test_result.text) @@ -250,7 +259,9 @@ func _process(_delta): scene.already_telep = true scene.reset_timeout() test_player_node.global_position = Vector3( - scene.parcel_position.x * 16.0, 1.0, -scene.parcel_position.y * 16.0 + scene.parcel_position.x * 16.0 + 8.0, + 1.0, + -scene.parcel_position.y * 16.0 - 8.0 ) elif scene.timeout(): printerr( diff --git a/godot/src/ui/components/auth/sign_in.gd b/godot/src/ui/components/auth/sign_in.gd index 4fe51238..1cbec32f 100644 --- a/godot/src/ui/components/auth/sign_in.gd +++ b/godot/src/ui/components/auth/sign_in.gd @@ -1,5 +1,7 @@ extends Control +var cancel_action: Callable = Callable() + @onready var panel_main = $Panel_Main @onready var v_box_container_connect = $Panel_Main/VBoxContainer_Connect @@ -7,6 +9,7 @@ extends Control @onready var v_box_container_guest_confirm = $Panel_Main/VBoxContainer_GuestConfirm @onready var label_waiting = $Panel_Main/VBoxContainer_Waiting/Label_Waiting +@onready var button_waiting_cancel = $Panel_Main/VBoxContainer_Waiting/Button_WaitingCancel func show_panel(child_node: Control): @@ -16,8 +19,16 @@ func show_panel(child_node: Control): child_node.show() -func show_waiting_panel(text: String): +func show_waiting_panel(text: String, new_cancel_action: Variant = null): label_waiting.text = text + + if new_cancel_action != null and new_cancel_action is Callable: + button_waiting_cancel.show() + cancel_action = new_cancel_action + else: + button_waiting_cancel.hide() + cancel_action = Callable() + show_panel(v_box_container_waiting) @@ -34,9 +45,18 @@ func _ready(): show_panel(v_box_container_connect) +func _on_button_sign_in_pressed_abort(): + Global.player_identity.abort_try_connect_account() + show_panel(v_box_container_connect) + + func _on_button_sign_in_pressed(): Global.player_identity.try_connect_account() - show_waiting_panel("Please follow the steps to connect your account and sign the message") + + show_waiting_panel( + "Please follow the steps to connect your account and sign the message", + self._on_button_sign_in_pressed_abort + ) func _on_button_guest_pressed(): @@ -48,7 +68,11 @@ func _on_button_confirm_guest_risk_pressed(): func _on_need_open_url(url: String, _description: String) -> void: - OS.shell_open(url) + if Global.dcl_android_plugin != null: + Global.dcl_android_plugin.showDecentralandMobileToast() + Global.dcl_android_plugin.openUrl(url) + else: + OS.shell_open(url) func _on_wallet_connected(_address: String, _chain_id: int, is_guest: bool) -> void: @@ -63,3 +87,7 @@ func _on_wallet_connected(_address: String, _chain_id: int, is_guest: bool) -> v Global.config.save_to_settings_file() close_sign_in() + + +func _on_button_waiting_cancel_pressed(): + cancel_action.call() diff --git a/godot/src/ui/components/auth/sign_in.tscn b/godot/src/ui/components/auth/sign_in.tscn index bdc3e3a9..bd939736 100644 --- a/godot/src/ui/components/auth/sign_in.tscn +++ b/godot/src/ui/components/auth/sign_in.tscn @@ -64,7 +64,6 @@ theme_override_font_sizes/font_size = 20 text = "Continue as a guest" [node name="VBoxContainer_Waiting" type="VBoxContainer" parent="Panel_Main"] -visible = false layout_mode = 1 anchors_preset = 8 anchor_left = 0.5 @@ -90,6 +89,11 @@ autowrap_mode = 3 layout_mode = 2 size_flags_horizontal = 4 +[node name="Button_WaitingCancel" type="Button" parent="Panel_Main/VBoxContainer_Waiting"] +visible = false +layout_mode = 2 +text = "Cancel" + [node name="VBoxContainer_GuestConfirm" type="VBoxContainer" parent="Panel_Main"] visible = false layout_mode = 1 @@ -121,4 +125,5 @@ text = "I assume the risk" [connection signal="pressed" from="Panel_Main/VBoxContainer_Connect/Button_SignIn" to="." method="_on_button_sign_in_pressed"] [connection signal="pressed" from="Panel_Main/VBoxContainer_Connect/Button_Guest" to="." method="_on_button_guest_pressed"] +[connection signal="pressed" from="Panel_Main/VBoxContainer_Waiting/Button_WaitingCancel" to="." method="_on_button_waiting_cancel_pressed"] [connection signal="pressed" from="Panel_Main/VBoxContainer_GuestConfirm/Button_ConfirmGuestRisk" to="." method="_on_button_confirm_guest_risk_pressed"] diff --git a/godot/src/ui/components/backpack/avatar_preview.gd b/godot/src/ui/components/backpack/avatar_preview.gd index cb73bb58..66c50b93 100644 --- a/godot/src/ui/components/backpack/avatar_preview.gd +++ b/godot/src/ui/components/backpack/avatar_preview.gd @@ -11,7 +11,9 @@ var dirty_is_dragging func _ready(): if Global.standalone: - avatar.async_update_avatar(Global.config.avatar_profile) + pass + # TODO: this config no longer exists + #avatar.async_update_avatar(Global.config.avatar_profile) func focus_camera_on(type): diff --git a/godot/src/ui/components/backpack/backpack.gd b/godot/src/ui/components/backpack/backpack.gd index 8162db74..d4230ef2 100644 --- a/godot/src/ui/components/backpack/backpack.gd +++ b/godot/src/ui/components/backpack/backpack.gd @@ -13,7 +13,7 @@ var avatar_emotes: Array var base_wearable_request_id: int = -1 var wearable_data: Dictionary = {} -var renderer_avatar_dictionary: Dictionary = {} +var primary_player_profile_dictionary: Dictionary = {} var wearable_buttons: Array = [] @@ -66,28 +66,38 @@ func _ready(): func _on_profile_changed(new_profile: Dictionary): - avatar_body_shape = new_profile.body_shape - avatar_wearables = new_profile.wearables - avatar_eyes_color = new_profile.eyes - avatar_hair_color = new_profile.hair - avatar_skin_color = new_profile.skin - avatar_emotes = new_profile.emotes - line_edit_name.text = new_profile.name + var profile_content = new_profile.get("content", {}) + line_edit_name.text = profile_content.get("name") + + var profile_avatar = profile_content.get("avatar", {}) + avatar_body_shape = profile_avatar.bodyShape + avatar_wearables = profile_avatar.wearables + avatar_eyes_color = Avatar.from_color_object(profile_avatar.eyes.color) + avatar_hair_color = Avatar.from_color_object(profile_avatar.hair.color) + avatar_skin_color = Avatar.from_color_object(profile_avatar.skin.color) + + if profile_avatar.emotes != null: + avatar_emotes = profile_avatar.emotes + + if primary_player_profile_dictionary.is_empty(): + primary_player_profile_dictionary = new_profile.duplicate() _update_avatar() func _update_avatar(): - renderer_avatar_dictionary = { - "base_url": "https://peer.decentraland.org/content", - "name": "", - "body_shape": avatar_body_shape, - "eyes": avatar_eyes_color, - "hair": avatar_hair_color, - "skin": avatar_skin_color, - "wearables": avatar_wearables, - "emotes": avatar_emotes - } + if primary_player_profile_dictionary.is_empty(): + return + + var profile_avatar: Dictionary = primary_player_profile_dictionary.get("content", {}).get( + "avatar", {} + ) + profile_avatar["bodyShape"] = avatar_body_shape + profile_avatar["eyes"] = Avatar.to_color_object(avatar_eyes_color) + profile_avatar["hair"] = Avatar.to_color_object(avatar_hair_color) + profile_avatar["skin"] = Avatar.to_color_object(avatar_skin_color) + profile_avatar["wearables"] = avatar_wearables + profile_avatar["emotes"] = avatar_emotes var wearable_body_shape = Global.content_manager.get_wearable(avatar_body_shape) @@ -101,7 +111,7 @@ func _update_avatar(): if wearable_body_shape != null: wearable_button.async_set_wearable(wearable_body_shape) - avatar_preview.avatar.async_update_avatar(renderer_avatar_dictionary) + avatar_preview.avatar.async_update_avatar_from_profile(primary_player_profile_dictionary) button_save_profile.disabled = false @@ -184,8 +194,15 @@ func _on_line_edit_name_text_changed(_new_text): func _on_button_save_profile_pressed(): button_save_profile.disabled = true - renderer_avatar_dictionary["name"] = line_edit_name.text - Global.player_identity.async_deploy_profile(renderer_avatar_dictionary) + + var profile_content = primary_player_profile_dictionary.get("content", {}) + var profile_avatar = profile_content.get("avatar", {}) + + profile_content["name"] = line_edit_name.text + profile_content["hasConnectedWeb3"] = !Global.player_identity.is_guest + profile_avatar["name"] = line_edit_name.text + + Global.player_identity.async_deploy_profile(primary_player_profile_dictionary) func _on_wearable_panel_equip(wearable_id: String): @@ -266,9 +283,9 @@ func _on_color_picker_panel_pick_color(color): skin_color_picker.set_color(color) avatar_preview.avatar.update_colors(avatar_eyes_color, avatar_skin_color, avatar_hair_color) - renderer_avatar_dictionary["eyes"] = avatar_eyes_color - renderer_avatar_dictionary["hair"] = avatar_hair_color - renderer_avatar_dictionary["skin"] = avatar_skin_color + primary_player_profile_dictionary["eyes"] = Avatar.to_color_object(avatar_eyes_color) + primary_player_profile_dictionary["hair"] = Avatar.to_color_object(avatar_hair_color) + primary_player_profile_dictionary["skin"] = Avatar.to_color_object(avatar_skin_color) button_save_profile.disabled = false diff --git a/godot/src/ui/components/debug_panel/debug_panel.gd b/godot/src/ui/components/debug_panel/debug_panel.gd index 19ab293b..2e069d74 100644 --- a/godot/src/ui/components/debug_panel/debug_panel.gd +++ b/godot/src/ui/components/debug_panel/debug_panel.gd @@ -118,6 +118,7 @@ func word_wrap(message: String) -> Array[String]: func _on_button_clear_pressed(): tree_console.clear() + _ready() func _on_line_edit_filter_text_changed(new_text): diff --git a/godot/src/ui/explorer.gd b/godot/src/ui/explorer.gd index eba579fa..9fa46b33 100644 --- a/godot/src/ui/explorer.gd +++ b/godot/src/ui/explorer.gd @@ -10,6 +10,7 @@ var dirty_save_position: bool = false var last_position_sent: Vector3 = Vector3.ZERO var counter: int = 0 +var last_index_scene_ui_root: int = -1 var _last_parcel_position: Vector2i @onready var ui_root: Control = $UI @@ -104,6 +105,7 @@ func _ready(): var start_parcel_position: Vector2i = Vector2i(Global.config.last_parcel_position) if cmd_location != null: start_parcel_position = cmd_location + start_parcel_position = Vector2i() player.position = 16 * Vector3(start_parcel_position.x, 0.1, -start_parcel_position.y) player.look_at(16 * Vector3(start_parcel_position.x + 1, 0, -(start_parcel_position.y + 1))) @@ -134,18 +136,27 @@ func _ready(): Global.player_identity.logout.connect(self._on_player_logout) Global.player_identity.profile_changed.connect(Global.avatars.update_primary_player_profile) + Global.player_identity.need_open_url.connect(self._on_need_open_url) - if not Global.player_identity.try_recover_account(Global.config.session_account): - if Global.testing_scene_mode: - Global.player_identity.create_guest_account() - else: - Global.scene_runner.set_pause(true) - ui_root.add_child(sign_in_resource.instantiate()) + if Global.testing_scene_mode: + Global.player_identity.create_guest_account() + elif not Global.player_identity.try_recover_account(Global.config.session_account): + Global.scene_runner.set_pause(true) + ui_root.add_child(sign_in_resource.instantiate()) # last ui_root.grab_focus.call_deferred() +func _on_need_open_url(url: String, _description: String) -> void: + if not Global.player_identity.get_address_str().is_empty(): + if Global.dcl_android_plugin != null: + Global.dcl_android_plugin.showDecentralandMobileToast() + Global.dcl_android_plugin.openUrl(url) + else: + OS.shell_open(url) + + func _on_player_logout(): # TODO: clean all UI ? control_menu.close() @@ -362,9 +373,16 @@ func release_mouse(): func set_visible_ui(value: bool): + if value == ui_root.visible: + return + if value: ui_root.show() voice_chat_ui.show() + var ui_node = ui_root.get_parent().get_node("scenes_ui") + ui_node.reparent(ui_root) else: ui_root.hide() voice_chat_ui.hide() + var ui_node = ui_root.get_node("scenes_ui") + ui_node.reparent(ui_root.get_parent()) diff --git a/rust/decentraland-godot-lib/Cargo.toml b/rust/decentraland-godot-lib/Cargo.toml index 5b1ade44..911db294 100644 --- a/rust/decentraland-godot-lib/Cargo.toml +++ b/rust/decentraland-godot-lib/Cargo.toml @@ -8,7 +8,7 @@ publish = false crate-type = ["cdylib"] [dependencies] -godot = { git = "https://github.com/godot-rust/gdext", rev = "e9177f22" } +godot = { git = "https://github.com/godot-rust/gdext", rev = "ef5f388def606ee119849e01ac0fafc1e0ec2c2a" } rand = "0.8" serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0.92", features = ["raw_value"] } @@ -62,10 +62,14 @@ multihash-codetable = { version = "0.1.1", features = ["digest", "sha2"] } cid = "0.11.0" multipart = { version = "0.18.0", default-features = false, features = ["client", "lazy_static"] } +ethers-providers = { version = "2.0", features = ["ws","rustls"] } + [target.'cfg(target_os = "android")'.dependencies] ffmpeg-next = { git = "https://github.com/decentraland/rust-ffmpeg/", branch = "audioline-and-mobile-fix", features = ["fix_usize_size_t"] } jni = { version = "0.21.1", features = ["invocation"] } paranoid-android = "0.2.1" +godot = { git = "https://github.com/godot-rust/gdext", rev = "ef5f388def606ee119849e01ac0fafc1e0ec2c2a", features = ["lazy-function-tables"] } + [build-dependencies] webrtc-sys-build = "0.2.0" @@ -73,4 +77,4 @@ prost-build = "0.11.8" [patch."https://github.com/godot-rust/godot4-prebuilt".godot4-prebuilt] git = "https://github.com//godot-rust/godot4-prebuilt" -branch = "4.2" +branch = "4.2.1" diff --git a/rust/decentraland-godot-lib/src/auth/auth_identity.rs b/rust/decentraland-godot-lib/src/auth/auth_identity.rs index 554eeae0..e4ee6ab5 100644 --- a/rust/decentraland-godot-lib/src/auth/auth_identity.rs +++ b/rust/decentraland-godot-lib/src/auth/auth_identity.rs @@ -4,10 +4,7 @@ use super::{ with_browser_and_server::{remote_sign_message, RemoteReportState}, }; use chrono::{DateTime, Utc}; -use ethers::{ - signers::LocalWallet, - types::{Signature, H160}, -}; +use ethers::signers::LocalWallet; use rand::thread_rng; fn get_ephemeral_message(ephemeral_address: &str, expiration: std::time::SystemTime) -> String { @@ -18,23 +15,9 @@ fn get_ephemeral_message(ephemeral_address: &str, expiration: std::time::SystemT ) } -pub async fn try_create_remote_ephemeral_with_account( - signer: H160, - sender: tokio::sync::mpsc::Sender, -) -> Result<(H160, Wallet, Signature, u64), ()> { - let ephemeral_wallet = Wallet::new_local_wallet(); - let ephemeral_address = format!("{:#x}", ephemeral_wallet.address()); - let expiration = std::time::SystemTime::now() + std::time::Duration::from_secs(30 * 24 * 3600); - let message = get_ephemeral_message(ephemeral_address.as_str(), expiration); - - let (signer, signature, chain_id) = - remote_sign_message(message.as_bytes(), Some(signer), sender).await?; - Ok((signer, ephemeral_wallet, signature, chain_id)) -} - pub async fn try_create_remote_ephemeral( sender: tokio::sync::mpsc::Sender, -) -> Result<(EphemeralAuthChain, u64), ()> { +) -> Result<(EphemeralAuthChain, u64), anyhow::Error> { let local_wallet = LocalWallet::new(&mut thread_rng()); let signing_key_bytes = local_wallet.signer().to_bytes().to_vec(); let ephemeral_wallet = Wallet::new_from_inner(Box::new(local_wallet)); diff --git a/rust/decentraland-godot-lib/src/auth/dcl_player_identity.rs b/rust/decentraland-godot-lib/src/auth/dcl_player_identity.rs index 5747c1c3..16e73062 100644 --- a/rust/decentraland-godot-lib/src/auth/dcl_player_identity.rs +++ b/rust/decentraland-godot-lib/src/auth/dcl_player_identity.rs @@ -2,8 +2,10 @@ use ethers::signers::LocalWallet; use ethers::types::H160; use godot::prelude::*; use rand::thread_rng; +use tokio::task::JoinHandle; use crate::comms::profile::{LambdaProfiles, UserProfile}; +use crate::dcl::scene_apis::RpcResultSender; use crate::godot_classes::promise::Promise; use crate::http_request::request_response::{RequestResponse, ResponseEnum}; use crate::scene_runner::tokio_runtime::TokioRuntime; @@ -12,7 +14,7 @@ use super::auth_identity::create_local_ephemeral; use super::ephemeral_auth_chain::EphemeralAuthChain; use super::remote_wallet::RemoteWallet; use super::wallet::{AsH160, Wallet}; -use super::with_browser_and_server::RemoteReportState; +use super::with_browser_and_server::{remote_send_async, RPCSendableMessage, RemoteReportState}; enum CurrentWallet { Remote(RemoteWallet), @@ -30,6 +32,8 @@ pub struct DclPlayerIdentity { profile: Option, + try_connect_account_handle: Option>, + #[var] is_guest: bool, @@ -50,6 +54,7 @@ impl INode for DclPlayerIdentity { profile: None, base, is_guest: false, + try_connect_account_handle: None, } } @@ -195,7 +200,7 @@ impl DclPlayerIdentity { let instance_id = self.base.instance_id(); let sender = self.remote_report_sender.clone(); - handle.spawn(async move { + let try_connect_account_handle = handle.spawn(async move { let wallet = RemoteWallet::with_auth_identity(sender).await; let Ok(mut this) = Gd::::try_from_instance_id(instance_id) else { return; @@ -216,7 +221,8 @@ impl DclPlayerIdentity { ], ); } - Err(_) => { + Err(err) => { + tracing::error!("error getting wallet {:?}", err); this.call_deferred( "_error_getting_wallet".into(), &["Unknown error".to_variant()], @@ -224,6 +230,15 @@ impl DclPlayerIdentity { } } }); + + self.try_connect_account_handle = Some(try_connect_account_handle); + } + + #[func] + fn abort_try_connect_account(&mut self) { + if let Some(handle) = self.try_connect_account_handle.take() { + handle.abort(); + } } #[func] @@ -298,9 +313,7 @@ impl DclPlayerIdentity { #[func] pub fn get_profile_or_empty(&self) -> Dictionary { if let Some(profile) = &self.profile { - profile - .content - .to_godot_dictionary(&self.profile.as_ref().unwrap().base_url) + profile.to_godot_dictionary() } else { Dictionary::default() } @@ -308,6 +321,9 @@ impl DclPlayerIdentity { #[func] pub fn set_default_profile(&mut self) { + let mut profile = UserProfile::default(); + profile.content.user_id = Some(self.get_address_str().to_string()); + self.profile = Some(UserProfile::default()); let dict = self.get_profile_or_empty(); self.base.call_deferred( @@ -329,7 +345,8 @@ impl DclPlayerIdentity { let promise = Promise::new_gd(); let promise_instance_id = promise.instance_id(); - let mut profile = if let Some(profile) = self.profile.clone() { + let mut new_profile = UserProfile::from_godot_dictionary(&dict); + let current_profile = if let Some(profile) = self.profile.clone() { profile } else { UserProfile { @@ -339,10 +356,10 @@ impl DclPlayerIdentity { }; let eth_address = self.get_address_str().to_string(); - profile.content.copy_from_godot_dictionary(&dict); - profile.version += 1; - profile.content.user_id = Some(eth_address.clone()); - profile.content.eth_address = eth_address; + new_profile.version = current_profile.version + 1; + new_profile.content.version = new_profile.version as i64; + new_profile.content.user_id = Some(eth_address.clone()); + new_profile.content.eth_address = eth_address; if let Some(handle) = TokioRuntime::static_clone_handle() { let ephemeral_auth_chain = self @@ -353,7 +370,7 @@ impl DclPlayerIdentity { handle.spawn(async move { let deploy_data = super::deploy_profile::prepare_deploy_profile( ephemeral_auth_chain.clone(), - profile, + new_profile, ) .await; @@ -455,19 +472,11 @@ impl DclPlayerIdentity { pub fn _update_profile_from_dictionary(&mut self, dict: Dictionary) { let eth_address = self.get_address_str().to_string(); - if self.profile.is_none() { - self.profile = Some(UserProfile::default()); - self.profile.as_mut().unwrap().version = 0; - } - - { - let profile = self.profile.as_mut().unwrap(); - profile.content.copy_from_godot_dictionary(&dict); - profile.version += 1; - profile.content.user_id = Some(eth_address.clone()); - profile.content.eth_address = eth_address; - } + let mut new_profile = UserProfile::from_godot_dictionary(&dict); + new_profile.content.user_id = Some(eth_address.clone()); + new_profile.content.eth_address = eth_address; + self.profile = Some(new_profile); self.base.call_deferred( "emit_signal".into(), &["profile_changed".to_variant(), dict.to_variant()], @@ -508,4 +517,18 @@ impl DclPlayerIdentity { self.base .call_deferred("emit_signal".into(), &["logout".to_variant()]); } + + pub fn send_async( + &self, + body: RPCSendableMessage, + response: RpcResultSender>, + ) { + let url_sender = self.remote_report_sender.clone(); + if let Some(handle) = TokioRuntime::static_clone_handle() { + handle.spawn(async move { + let result = remote_send_async(body, None, url_sender).await; + response.send(result.map_err(|err| err.to_string())); + }); + } + } } diff --git a/rust/decentraland-godot-lib/src/auth/ethereum_provider.rs b/rust/decentraland-godot-lib/src/auth/ethereum_provider.rs new file mode 100644 index 00000000..ff3433af --- /dev/null +++ b/rust/decentraland-godot-lib/src/auth/ethereum_provider.rs @@ -0,0 +1,47 @@ +const PROVIDER_URL: &str = "wss://rpc.decentraland.org/mainnet?project=kernel-local"; + +use ethers_providers::{Provider, Ws}; +use tokio::sync::Mutex; + +pub struct EthereumProvider { + provider: Mutex>>, +} + +impl Default for EthereumProvider { + fn default() -> Self { + Self::new() + } +} + +impl EthereumProvider { + pub fn new() -> Self { + Self { + provider: Mutex::new(None), + } + } + + pub async fn send_async( + &self, + method: &str, + params: &[serde_json::Value], + ) -> Result { + let mut this_provider = self.provider.lock().await; + + if this_provider.is_none() { + let provider = Provider::::connect(PROVIDER_URL).await?; + this_provider.replace(provider); + } + + // TODO: check if the connection is missing + let provider = this_provider.as_ref().unwrap(); + let result = provider.request(method, params).await; + + match result { + Err(e) => { + this_provider.take(); + Err(anyhow::Error::new(e)) + } + Ok(result) => Ok(result), + } + } +} diff --git a/rust/decentraland-godot-lib/src/auth/mod.rs b/rust/decentraland-godot-lib/src/auth/mod.rs index 4b2c0600..4951e0db 100644 --- a/rust/decentraland-godot-lib/src/auth/mod.rs +++ b/rust/decentraland-godot-lib/src/auth/mod.rs @@ -2,6 +2,7 @@ pub mod auth_identity; pub mod dcl_player_identity; pub mod deploy_profile; pub mod ephemeral_auth_chain; +pub mod ethereum_provider; pub mod remote_wallet; pub mod wallet; -mod with_browser_and_server; +pub mod with_browser_and_server; diff --git a/rust/decentraland-godot-lib/src/auth/remote_wallet.rs b/rust/decentraland-godot-lib/src/auth/remote_wallet.rs index bb39bcc7..541fb9df 100644 --- a/rust/decentraland-godot-lib/src/auth/remote_wallet.rs +++ b/rust/decentraland-godot-lib/src/auth/remote_wallet.rs @@ -7,7 +7,7 @@ use super::{ auth_identity::try_create_remote_ephemeral, ephemeral_auth_chain::EphemeralAuthChain, wallet::ObjSafeWalletSigner, - with_browser_and_server::{get_account, remote_sign_message, RemoteReportState}, + with_browser_and_server::{remote_sign_message, RemoteReportState}, }; #[derive(Clone)] @@ -29,7 +29,7 @@ impl fmt::Debug for RemoteWallet { impl RemoteWallet { pub async fn with_auth_identity( report_url_sender: tokio::sync::mpsc::Sender, - ) -> Result<(Self, EphemeralAuthChain), ()> { + ) -> Result<(Self, EphemeralAuthChain), anyhow::Error> { let (ephemeral_wallet, chain_id) = try_create_remote_ephemeral(report_url_sender.clone()).await?; @@ -43,18 +43,6 @@ impl RemoteWallet { )) } - pub async fn with( - report_url_sender: tokio::sync::mpsc::Sender, - ) -> Result { - let (address, chain_id) = get_account(report_url_sender.clone()).await?; - - Ok(Self { - address, - report_url_sender, - chain_id, - }) - } - pub fn new( address: H160, chain_id: u64, diff --git a/rust/decentraland-godot-lib/src/auth/with_browser_and_server.rs b/rust/decentraland-godot-lib/src/auth/with_browser_and_server.rs index 1beaee4a..9b4dceee 100644 --- a/rust/decentraland-godot-lib/src/auth/with_browser_and_server.rs +++ b/rust/decentraland-godot-lib/src/auth/with_browser_and_server.rs @@ -3,43 +3,68 @@ use std::{str::FromStr, time::Duration}; use base64::Engine as _; use ethers::types::{Signature, H160}; use rand::Rng; -use reqwest::Url; -use serde::{de::DeserializeOwned, Deserialize}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use crate::auth::wallet::AsH160; #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] -struct GetAccountResponseData { - address: String, +struct SignResponseData { + account: String, + signature: String, chain_id: u64, } #[derive(Deserialize, Debug)] -struct GetAccountResponse { - data: GetAccountResponseData, +struct RemoteWalletResponse { + ok: bool, + reason: Option, + response: Option, } -#[derive(Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -struct SignToServerResponseData { - account: String, - signature: String, - chain_id: u64, +#[derive(Debug, Serialize, Deserialize)] +pub struct RPCSendableMessage { + pub jsonrpc: String, + pub id: u64, + pub method: String, + pub params: Vec, // Using serde_json::Value for unknown[] } -#[derive(Deserialize, Debug)] -struct SignToServerResponse { - data: SignToServerResponseData, +#[derive(Debug, Serialize, Deserialize)] +#[serde(tag = "type")] +pub enum RemoteWalletRequest { + #[serde(rename = "send-async", rename_all = "camelCase")] + SendAsync { + body: RPCSendableMessage, + #[serde(skip_serializing_if = "Option::is_none")] + by_address: Option, + }, + #[serde(rename = "sign", rename_all = "camelCase")] + Sign { + b64_message: String, + #[serde(skip_serializing_if = "Option::is_none")] + by_address: Option, + }, } -const AUTH_FRONT_URL: &str = "https://leanmendoza.github.io/decentraland-auth/"; -const AUTH_SERVER_ENDPOINT_URL: &str = "https://services.aesir-online.net/dcltest/queue/task"; +#[derive(Debug, Serialize, Deserialize)] +struct RegisterRequestBody { + id: String, + request: RemoteWalletRequest, +} + +const AUTH_FRONT_URL: &str = "https://auth.dclexplorer.com/"; +const AUTH_SERVER_ENDPOINT_URL: &str = "https://auth-server.dclexplorer.com/task/"; +// const AUTH_FRONT_URL: &str = "http://localhost:5173/"; +// const AUTH_SERVER_ENDPOINT_URL: &str = "http://localhost:5545/task/"; + const AUTH_SERVER_RETRY_INTERVAL: Duration = Duration::from_secs(1); -const AUTH_SERVER_TIMEOUT: Duration = Duration::from_secs(60); +const AUTH_SERVER_TIMEOUT: Duration = Duration::from_secs(600); const AUTH_SERVER_RETRIES: u64 = AUTH_SERVER_TIMEOUT.as_secs() / AUTH_SERVER_RETRY_INTERVAL.as_secs(); +const AUTH_SERVER_REQUEST_TIMEOUT: Duration = Duration::from_secs(15); + pub enum RemoteReportState { OpenUrl { url: String, description: String }, } @@ -54,33 +79,39 @@ pub fn gen_id() -> String { .collect() } -async fn fetch_server(req_id: String) -> Result +async fn fetch_server(req_id: String) -> Result where T: DeserializeOwned, { + let url = format!("{AUTH_SERVER_ENDPOINT_URL}{req_id}/response"); let mut attempt = 0; loop { tracing::debug!("trying req_id {:?} attempt ${attempt}", req_id); if attempt >= AUTH_SERVER_RETRIES { - return Err(()); + return Err(anyhow::Error::msg("too many atempts")); } attempt += 1; - let url = format!("{AUTH_SERVER_ENDPOINT_URL}/{req_id}"); let response = reqwest::Client::builder() - .timeout(AUTH_SERVER_RETRY_INTERVAL) + .timeout(AUTH_SERVER_REQUEST_TIMEOUT) .build() .expect("reqwest build error") - .get(url) + .get(url.clone()) .send() .await; match response { Ok(response) => { if response.status().is_success() { - match response.json::().await { - Ok(response_data) => { - return Ok(response_data); + match response.json::>().await { + Ok(response) => { + if let Some(response_data) = response.response { + return Ok(response_data); + } else if let Some(reason) = response.reason { + return Err(anyhow::Error::msg(reason)); + } else { + tracing::error!("invalid response ok={:?}", response.ok); + } } Err(error) => { tracing::error!("error while parsing a task {:?}", error); @@ -101,7 +132,7 @@ where tokio::time::sleep(AUTH_SERVER_RETRY_INTERVAL).await; continue; } else { - tracing::error!("Error fetching task: {:?}", error); + tracing::error!("Error fetching task with status: {:?}", error); } } else { tracing::error!("Error fetching task: {:?}", error); @@ -110,86 +141,99 @@ where } break; } - Err(()) + Err(anyhow::Error::msg("couldn't get response")) } -pub async fn get_account( - url_reporter: tokio::sync::mpsc::Sender, -) -> Result<(H160, u64), ()> { - let get_account_req_id = gen_id(); - let open_url = { - let base_url = format!("{AUTH_FRONT_URL}get-account"); - - let mut url = Url::parse(base_url.as_str()).expect("static valid url"); - { - let mut params = url.query_pairs_mut(); - params.append_pair("id", &get_account_req_id); - params.append_pair("server-endpoint", AUTH_SERVER_ENDPOINT_URL); - } - url.to_string() +async fn register_request( + req_id: String, + request: RemoteWalletRequest, +) -> Result<(), anyhow::Error> { + let body = RegisterRequestBody { + id: req_id, + request, }; + let body = serde_json::to_string(&body).expect("valid json"); + let response = reqwest::Client::builder() + .timeout(AUTH_SERVER_REQUEST_TIMEOUT) + .build() + .expect("reqwest build error") + .post(AUTH_SERVER_ENDPOINT_URL) + .header("Content-Type", "application/json") + .body(body) + .send() + .await?; + + if response.status().is_success() { + Ok(()) + } else { + tracing::error!("Error registering request: {:?}", response); + Err(anyhow::Error::msg("couldn't get response")) + } +} + +async fn generate_and_report_request( + request: RemoteWalletRequest, + url_reporter: tokio::sync::mpsc::Sender, +) -> Result { + let req_id = gen_id(); + register_request(req_id.clone(), request).await?; + let open_url = format!("{AUTH_FRONT_URL}remote-wallet/{req_id}"); - tracing::debug!("get_account url {:?}", open_url); + tracing::debug!("sign url {:?}", open_url); url_reporter .send(RemoteReportState::OpenUrl { url: open_url.clone(), - description: "Know your public address account".to_owned(), + description: "Sign a message".to_owned(), }) - .await - .unwrap(); + .await?; - let account = fetch_server::(get_account_req_id).await?; - let Some(address) = account.data.address.as_h160() else { - return Err(()); - }; - Ok((address, account.data.chain_id)) + Ok(req_id) } pub async fn remote_sign_message( payload: &[u8], by_signer: Option, url_reporter: tokio::sync::mpsc::Sender, -) -> Result<(H160, Signature, u64), ()> { - let address = if by_signer.is_some() { - format!("{:#x}", by_signer.unwrap()) - } else { - "".into() - }; - let sign_payload_req_id = gen_id(); - let open_url = { - let base_url = format!("{AUTH_FRONT_URL}sign-to-server"); - let payload = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(payload); - - let mut url = Url::parse(base_url.as_str()).expect("static valid url"); - { - let mut params = url.query_pairs_mut(); - params.append_pair("id", &sign_payload_req_id); - params.append_pair("payload", &payload); - params.append_pair("address", &address); - params.append_pair("server-endpoint", AUTH_SERVER_ENDPOINT_URL); - } - url.to_string() - }; +) -> Result<(H160, Signature, u64), anyhow::Error> { + let by_address = by_signer.map(|s| format!("{:#x}", s)); + let b64_message = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(payload); - tracing::debug!("sign url {:?}", open_url); - url_reporter - .send(RemoteReportState::OpenUrl { - url: open_url.clone(), - description: "Sign a message".to_owned(), - }) - .await - .unwrap(); + let req_id = generate_and_report_request( + RemoteWalletRequest::Sign { + b64_message, + by_address, + }, + url_reporter, + ) + .await?; - let sign_payload = fetch_server::(sign_payload_req_id).await?; - let Some(account) = sign_payload.data.account.as_h160() else { - return Err(()); + let sign_payload = fetch_server::(req_id).await?; + let Some(account) = sign_payload.account.as_h160() else { + return Err(anyhow::Error::msg("invalid account")); }; - let Ok(signature) = Signature::from_str(sign_payload.data.signature.as_str()) else { - tracing::error!("error while parsing signature"); - return Err(()); + let Ok(signature) = Signature::from_str(sign_payload.signature.as_str()) else { + return Err(anyhow::Error::msg("invalid signature")); }; - Ok((account, signature, sign_payload.data.chain_id)) + Ok((account, signature, sign_payload.chain_id)) +} + +pub async fn remote_send_async( + message: RPCSendableMessage, + by_signer: Option, + url_reporter: tokio::sync::mpsc::Sender, +) -> Result { + let by_address = by_signer.map(|s| format!("{:#x}", s)); + let req_id = generate_and_report_request( + RemoteWalletRequest::SendAsync { + body: message, + by_address, + }, + url_reporter, + ) + .await?; + + fetch_server::(req_id).await } #[cfg(test)] @@ -200,7 +244,21 @@ mod test { #[traced_test] #[tokio::test] async fn test_gen_id() { - let (sx, _rx) = tokio::sync::mpsc::channel(100); + let (sx, mut rx) = tokio::sync::mpsc::channel(100); + + tokio::spawn(async move { + loop { + match rx.recv().await { + Some(RemoteReportState::OpenUrl { url, description }) => { + tracing::info!("url {:?} description {:?}", url, description); + } + None => { + break; + } + } + } + }); + let Ok((signer, signature, _chain_id)) = remote_sign_message("hello".as_bytes(), None, sx).await else { @@ -208,4 +266,39 @@ mod test { }; tracing::info!("signer {:?} signature {:?}", signer, signature); } + + #[traced_test] + #[tokio::test] + async fn test_send_async() { + let (sx, mut rx) = tokio::sync::mpsc::channel(100); + + tokio::spawn(async move { + loop { + match rx.recv().await { + Some(RemoteReportState::OpenUrl { url, description }) => { + tracing::info!("url {:?} description {:?}", url, description); + } + None => { + break; + } + } + } + }); + + let Ok(value) = remote_send_async( + RPCSendableMessage { + jsonrpc: "2.0".to_owned(), + id: 1, + method: "eth_chainId".to_owned(), + params: vec![], + }, + None, + sx, + ) + .await + else { + return; + }; + tracing::info!("value {:?} ", value); + } } diff --git a/rust/decentraland-godot-lib/src/avatars/avatar_scene.rs b/rust/decentraland-godot-lib/src/avatars/avatar_scene.rs index 7f12fe7f..bf0decdc 100644 --- a/rust/decentraland-godot-lib/src/avatars/avatar_scene.rs +++ b/rust/decentraland-godot-lib/src/avatars/avatar_scene.rs @@ -5,7 +5,7 @@ use godot::prelude::*; use crate::{ auth::wallet::AsH160, - comms::profile::SerializedProfile, + comms::profile::UserProfile, dcl::{ components::{ proto_components::kernel::comms::rfc4, transform_and_parent::DclTransformAndParent, @@ -36,7 +36,7 @@ pub struct AvatarScene { crdt_state: SceneCrdtState, - last_updated_profile: HashMap, + last_updated_profile: HashMap, } #[godot_api] @@ -76,18 +76,8 @@ macro_rules! sync_crdt_lww_component { impl AvatarScene { #[func] pub fn update_primary_player_profile(&mut self, profile: Dictionary) { - let mut serialized_profile = SerializedProfile::default(); - serialized_profile.copy_from_godot_dictionary(&profile); - let base_url = profile - .get("base_url") - .map(|v| v.to_string()) - .unwrap_or("https://peer.decentraland.org/content".into()); - - self.update_avatar( - SceneEntityId::PLAYER, - &serialized_profile, - base_url.as_str(), - ); + let user_profile = UserProfile::from_godot_dictionary(&profile); + self.update_avatar(SceneEntityId::PLAYER, &user_profile); } #[func] @@ -99,10 +89,10 @@ impl AvatarScene { return; }; - self.avatar_godot_scene - .get_mut(&entity_id) - .unwrap() - .call("async_update_avatar".into(), &[profile.to_variant()]); + self.avatar_godot_scene.get_mut(&entity_id).unwrap().call( + "async_update_avatar_from_profile".into(), + &[profile.to_variant()], + ); } #[func] @@ -341,12 +331,7 @@ impl AvatarScene { self._update_avatar_transform(&entity_id, dcl_transform); } - pub fn update_avatar_by_alias( - &mut self, - alias: u32, - profile: &SerializedProfile, - base_url: &str, - ) { + pub fn update_avatar_by_alias(&mut self, alias: u32, profile: &UserProfile) { let entity_id = if let Some(entity_id) = self.avatar_entity.get(&alias) { *entity_id } else { @@ -354,15 +339,10 @@ impl AvatarScene { return; }; - self.update_avatar(entity_id, profile, base_url); + self.update_avatar(entity_id, profile); } - pub fn update_avatar( - &mut self, - entity_id: SceneEntityId, - profile: &SerializedProfile, - base_url: &str, - ) { + pub fn update_avatar(&mut self, entity_id: SceneEntityId, profile: &UserProfile) { // Avoid updating avatar with the same data if let Some(val) = self.last_updated_profile.get(&entity_id) { if profile.eq(val) { @@ -373,12 +353,12 @@ impl AvatarScene { if let Some(avatar_scene) = self.avatar_godot_scene.get_mut(&entity_id) { avatar_scene.call( - "async_update_avatar".into(), - &[profile.to_godot_dictionary(base_url).to_variant()], + "async_update_avatar_from_profile".into(), + &[profile.to_godot_dictionary().to_variant()], ); } - let new_avatar_base = Some(profile.to_pb_avatar_base()); + let new_avatar_base = Some(profile.content.to_pb_avatar_base()); let avatar_base_component = SceneCrdtStateProtoComponents::get_avatar_base(&self.crdt_state); let avatar_base_component_value = avatar_base_component @@ -398,7 +378,7 @@ impl AvatarScene { .put(entity_id, new_avatar_base); } - let new_avatar_equipped_data = Some(profile.to_pb_avatar_equipped_data()); + let new_avatar_equipped_data = Some(profile.content.to_pb_avatar_equipped_data()); let avatar_equipped_data_component = SceneCrdtStateProtoComponents::get_avatar_equipped_data(&self.crdt_state); let avatar_equipped_data_value = avatar_equipped_data_component @@ -420,7 +400,7 @@ impl AvatarScene { .put(entity_id, new_avatar_equipped_data); } - let new_player_identity_data = Some(profile.to_pb_player_identity_data()); + let new_player_identity_data = Some(profile.content.to_pb_player_identity_data()); let player_identity_data_component = SceneCrdtStateProtoComponents::get_player_identity_data(&self.crdt_state); let player_identity_data_value = player_identity_data_component diff --git a/rust/decentraland-godot-lib/src/comms/adapter/livekit.rs b/rust/decentraland-godot-lib/src/comms/adapter/livekit.rs index 10387446..64863e87 100644 --- a/rust/decentraland-godot-lib/src/comms/adapter/livekit.rs +++ b/rust/decentraland-godot-lib/src/comms/adapter/livekit.rs @@ -215,17 +215,14 @@ impl LivekitRoom { continue; } - avatar_scene.update_avatar_by_alias( - peer.alias, - &serialized_profile, - &profile_response.base_url, - ); - - peer.profile = Some(UserProfile { + let profile = UserProfile { version: incoming_version, - content: serialized_profile, - base_url: profile_response.base_url, - }); + content: serialized_profile.clone(), + base_url: profile_response.base_url.clone(), + }; + + avatar_scene.update_avatar_by_alias(peer.alias, &profile); + peer.profile = Some(profile); } ToSceneMessage::Rfc4(rfc4::packet::Message::Scene(_scene)) => {} ToSceneMessage::Rfc4(rfc4::packet::Message::Voice(_voice)) => {} diff --git a/rust/decentraland-godot-lib/src/comms/adapter/ws_room.rs b/rust/decentraland-godot-lib/src/comms/adapter/ws_room.rs index c1455835..600ceb5d 100644 --- a/rust/decentraland-godot-lib/src/comms/adapter/ws_room.rs +++ b/rust/decentraland-godot-lib/src/comms/adapter/ws_room.rs @@ -476,20 +476,20 @@ impl WebSocketRoom { continue; } - self.avatars.bind_mut().update_avatar_by_alias( - update.from_alias, - &serialized_profile, - &profile_response.base_url, - ); + let profile = UserProfile { + version: incoming_version, + content: serialized_profile.clone(), + base_url: profile_response.base_url.clone(), + }; + + self.avatars + .bind_mut() + .update_avatar_by_alias(update.from_alias, &profile); self.peer_identities .get_mut(&update.from_alias) .unwrap() - .profile = Some(UserProfile { - version: incoming_version, - content: serialized_profile, - base_url: profile_response.base_url, - }); + .profile = Some(profile); } rfc4::packet::Message::Scene(_scene) => {} rfc4::packet::Message::Voice(_voice) => {} diff --git a/rust/decentraland-godot-lib/src/comms/profile.rs b/rust/decentraland-godot-lib/src/comms/profile.rs index cc08b08c..164c8234 100644 --- a/rust/decentraland-godot-lib/src/comms/profile.rs +++ b/rust/decentraland-godot-lib/src/comms/profile.rs @@ -147,154 +147,6 @@ impl Default for SerializedProfile { } impl SerializedProfile { - pub fn to_godot_dictionary(&self, base_url: &str) -> Dictionary { - let mut dictionary = Dictionary::new(); - let name: GString = GString::from(self.name.as_str()); - - let body_shape: GString = self - .avatar - .body_shape - .as_ref() - .unwrap_or(&"default".into()) - .into(); - - let eyes: godot::prelude::Color = self - .avatar - .eyes - .as_ref() - .unwrap_or(&AvatarColor { - color: AvatarColor3 { - r: 0.2, - g: 0.2, - b: 0.5, - }, - }) - .into(); - - let hair: godot::prelude::Color = self - .avatar - .hair - .as_ref() - .unwrap_or(&AvatarColor { - color: AvatarColor3 { - r: 0.2, - g: 0.2, - b: 0.5, - }, - }) - .into(); - - let skin: godot::prelude::Color = self - .avatar - .skin - .as_ref() - .unwrap_or(&AvatarColor { - color: AvatarColor3 { - r: 0.2, - g: 0.2, - b: 0.5, - }, - }) - .into(); - - let wearables = self - .avatar - .wearables - .iter() - .map(GString::from) - .collect::(); - - let emotes = self - .avatar - .emotes - .as_ref() - .unwrap_or(&vec![]) - .iter() - .map(|v| { - let mut arr = VariantArray::new(); - arr.push(GString::from(v.urn.as_str()).to_variant()); - arr.push(v.slot.to_variant()); - arr.to_variant() - }) - .collect::(); - - dictionary.set("name", name); - dictionary.set("body_shape", body_shape); - dictionary.set("eyes", eyes); - dictionary.set("hair", hair); - dictionary.set("skin", skin); - dictionary.set("wearables", wearables); - dictionary.set("emotes", emotes); - dictionary.set("base_url", base_url); - - dictionary - } - - pub fn copy_from_godot_dictionary(&mut self, dictionary: &Dictionary) { - let name = dictionary.get("name").unwrap_or("Noname".to_variant()); - - let body_shape = dictionary - .get("body_shape") - .unwrap_or("default".to_variant()) - .to::(); - let eyes = dictionary - .get("eyes") - .unwrap_or(godot::prelude::Color::from_rgb(0.1, 0.5, 0.8).to_variant()) - .to::(); - let hair = dictionary - .get("hair") - .unwrap_or(godot::prelude::Color::from_rgb(0.1, 0.5, 0.8).to_variant()) - .to::(); - let skin = dictionary - .get("skin") - .unwrap_or(godot::prelude::Color::from_rgb(0.1, 0.5, 0.8).to_variant()) - .to::(); - - let wearables: Vec = { - if let Some(ret) = dictionary.get("wearables") { - if let Ok(ret) = ret.try_to::() { - ret.iter_shared() - .map(|v| v.to_string()) - .collect::>() - } else if let Ok(ret) = ret.try_to::() { - ret.to_vec() - .iter() - .map(|v| v.to_string()) - .collect::>() - } else { - Vec::default() - } - } else { - Vec::default() - } - }; - - let emotes = dictionary - .get("emotes") - .unwrap_or(VariantArray::default().to_variant()) - .to::(); - - self.name = name.to_string(); - self.avatar.body_shape = Some(body_shape.to_string()); - self.avatar.eyes = Some((&eyes).into()); - self.avatar.hair = Some((&hair).into()); - self.avatar.skin = Some((&skin).into()); - self.avatar.wearables = wearables; - self.avatar.emotes = emotes - .iter_shared() - .filter_map(|_v| { - // if !v() { - // return None; - // } - // Some(AvatarEmote { - // slot: 0, - // urn: v.get("urn").unwrap_or("".to_variant()), - // }) - None - }) - .collect(); - } - pub fn to_pb_avatar_base(&self) -> PbAvatarBase { PbAvatarBase { skin_color: self.avatar.skin.map(|c| Color3::from(&c)), @@ -330,7 +182,7 @@ impl SerializedProfile { } } -#[derive(Serialize, Deserialize, Clone, Debug)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] pub struct UserProfile { pub version: u32, pub content: SerializedProfile, @@ -346,3 +198,16 @@ impl Default for UserProfile { } } } + +impl UserProfile { + pub fn from_godot_dictionary(dictionary: &Dictionary) -> Self { + let value = godot::engine::Json::stringify(dictionary.to_variant()); + serde_json::from_str(value.to_string().as_str()).unwrap_or_default() + } + + pub fn to_godot_dictionary(&self) -> Dictionary { + let value = serde_json::to_string(self).unwrap_or_default(); + let value = godot::engine::Json::parse_string(value.into()); + value.to::() + } +} diff --git a/rust/decentraland-godot-lib/src/dcl/js/ethereum_controller.rs b/rust/decentraland-godot-lib/src/dcl/js/ethereum_controller.rs new file mode 100644 index 00000000..3699330a --- /dev/null +++ b/rust/decentraland-godot-lib/src/dcl/js/ethereum_controller.rs @@ -0,0 +1,55 @@ +use deno_core::{ + anyhow::{self, anyhow}, + error::AnyError, + op, Op, OpDecl, OpState, +}; +use std::{cell::RefCell, rc::Rc, sync::Arc}; + +use crate::{ + auth::{ethereum_provider::EthereumProvider, with_browser_and_server::RPCSendableMessage}, + dcl::scene_apis::RpcCall, +}; + +// list of op declarations +pub fn ops() -> Vec { + vec![op_send_async::DECL] +} + +#[op] +async fn op_send_async( + state: Rc>, + method: String, + params: String, +) -> Result { + let params: Vec = serde_json::from_str(¶ms)?; + + match method.as_str() { + "eth_sendTransaction" | "eth_signTypedData_v4" => { + let (sx, rx) = tokio::sync::oneshot::channel::>(); + + state + .borrow_mut() + .borrow_mut::>() + .push(RpcCall::SendAsync { + body: RPCSendableMessage { + jsonrpc: "2.0".into(), + id: 1, + method, + params, + }, + response: sx.into(), + }); + + rx.await + .map_err(|e| anyhow::anyhow!(e))? + .map_err(|e| anyhow!(e)) + } + _ => { + let ethereum_provider = { state.borrow().borrow::>().clone() }; + + ethereum_provider + .send_async(method.as_str(), params.as_slice()) + .await + } + } +} diff --git a/rust/decentraland-godot-lib/src/dcl/js/js_modules/EthereumController.js b/rust/decentraland-godot-lib/src/dcl/js/js_modules/EthereumController.js index ce68876d..59a9be0a 100644 --- a/rust/decentraland-godot-lib/src/dcl/js/js_modules/EthereumController.js +++ b/rust/decentraland-godot-lib/src/dcl/js/js_modules/EthereumController.js @@ -1,5 +1,49 @@ -module.exports.requirePayment = async function (body) { return {} } -module.exports.signMessage = async function (body) { return {} } -module.exports.convertMessageToObject = async function (body) { return {} } -module.exports.sendAsync = async function (body) { return {} } -module.exports.getUserAccount = async function (body) { return {} } \ No newline at end of file +const allowMethodlist = [ + 'eth_sendTransaction', + 'eth_getTransactionReceipt', + 'eth_estimateGas', + 'eth_call', + 'eth_getBalance', + 'eth_getStorageAt', + 'eth_blockNumber', + 'eth_gasPrice', + 'eth_protocolVersion', + 'net_version', + 'web3_sha3', + 'web3_clientVersion', + 'eth_getTransactionCount', + 'eth_getBlockByNumber', + 'eth_requestAccounts', + 'eth_signTypedData_v4', + 'eth_getCode' +] + +module.exports.sendAsync = async function (message) { + if ( + typeof message !== 'object' || + typeof message.id !== 'number' || + typeof message.method !== 'string' || + typeof message.jsonParams !== 'string' + ) { + throw new Error('Invalid JSON-RPC message') + } + + if (!allowMethodlist.includes(message.method)) { + throw new Error(`The Ethereum method "${message.method}" is not allowed on Decentraland Provider`) + } + + return Deno.core.ops.op_send_async(message.method, message.jsonParams) +} + +module.exports.requirePayment = async function (body) { + throw new Error("`requirePayment is not implemented, this method is deprecated in SDK7 APIs, please use sendAsync instead, you can use a library like ethers.js.") +} +module.exports.signMessage = async function (body) { + throw new Error("signMessage is not implemented, this method is deprecated in SDK7 APIs, please use sendAsync instead, you can use a library like ethers.js.") +} +module.exports.convertMessageToObject = async function (body) { + throw new Error("convertMessageToObject is not implemented, this method is deprecated in SDK7 APIs, please use sendAsync instead, you can use a library like ethers.js.") +} +module.exports.getUserAccount = async function (body) { + throw new Error("getUserAccount is not implemented, this method is deprecated in SDK7 APIs, please use sendAsync instead, you can use a library like ethers.js.") +} \ No newline at end of file diff --git a/rust/decentraland-godot-lib/src/dcl/js/js_modules/UserIdentity.js b/rust/decentraland-godot-lib/src/dcl/js/js_modules/UserIdentity.js index 7a62c824..c9104ffe 100644 --- a/rust/decentraland-godot-lib/src/dcl/js/js_modules/UserIdentity.js +++ b/rust/decentraland-godot-lib/src/dcl/js/js_modules/UserIdentity.js @@ -1,2 +1,12 @@ -module.exports.getUserPublicKey = async function (body) { return { address: undefined } } -module.exports.getUserData = async function (body) { return {} } \ No newline at end of file +module.exports.getUserPublicKey = async function (body) { + const res = await Deno.core.ops.op_get_player_data(""); + return { + address: res?.userId + }; +} +module.exports.getUserData = async function (body) { + const res = await Deno.core.ops.op_get_player_data(""); + return { + data: res + }; +} \ No newline at end of file diff --git a/rust/decentraland-godot-lib/src/dcl/js/mod.rs b/rust/decentraland-godot-lib/src/dcl/js/mod.rs index f22916c3..2b8d743d 100644 --- a/rust/decentraland-godot-lib/src/dcl/js/mod.rs +++ b/rust/decentraland-godot-lib/src/dcl/js/mod.rs @@ -1,4 +1,5 @@ pub mod engine; +pub mod ethereum_controller; pub mod events; pub mod fetch; pub mod players; @@ -8,6 +9,7 @@ pub mod runtime; pub mod testing; pub mod websocket; +use crate::auth::ethereum_provider::EthereumProvider; use crate::auth::wallet::Wallet; use crate::dcl::scene_apis::{LocalCall, RpcCall}; @@ -20,6 +22,7 @@ use super::{RendererResponse, SceneId, SceneResponse}; use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; +use std::sync::Arc; use std::time::Duration; use deno_core::error::JsError; @@ -66,7 +69,7 @@ pub fn create_runtime() -> deno_core::JsRuntime { // add core ops ext = ext.ops(vec![op_require::DECL, op_log::DECL, op_error::DECL]); - let op_sets: [Vec; 9] = [ + let op_sets: [Vec; 10] = [ engine::ops(), runtime::ops(), fetch::ops(), @@ -76,6 +79,7 @@ pub fn create_runtime() -> deno_core::JsRuntime { players::ops(), events::ops(), testing::ops(), + ethereum_controller::ops(), ]; let mut op_map = HashMap::new(); @@ -124,6 +128,7 @@ pub(crate) fn scene_thread( scene_crdt: SharedSceneCrdtState, wallet: Wallet, testing_mode: bool, + ethereum_provider: Arc, ) { let mut scene_main_crdt = None; let main_crdt_file_path = scene_definition.main_crdt_path; @@ -189,6 +194,7 @@ pub(crate) fn scene_thread( state.borrow_mut().put(thread_sender_to_main); state.borrow_mut().put(thread_receive_from_main); + state.borrow_mut().put(ethereum_provider); state.borrow_mut().put(scene_id); state.borrow_mut().put(scene_crdt); diff --git a/rust/decentraland-godot-lib/src/dcl/js/players.rs b/rust/decentraland-godot-lib/src/dcl/js/players.rs index aced2455..5fe1bc4c 100644 --- a/rust/decentraland-godot-lib/src/dcl/js/players.rs +++ b/rust/decentraland-godot-lib/src/dcl/js/players.rs @@ -7,7 +7,7 @@ use deno_core::{ }; use crate::dcl::{ - components::proto_components::common::Color3, + components::{proto_components::common::Color3, SceneEntityId}, crdt::{SceneCrdtState, SceneCrdtStateProtoComponents}, scene_apis::{AvatarForUserData, LocalCall, UserData}, }; @@ -107,8 +107,12 @@ pub fn get_player_data(user_id: String, crdt_state: &SceneCrdtState) -> Option, wallet: Wallet, testing_mode: bool, + ethereum_provider: Arc, ) -> Self { let (main_sender_to_thread, thread_receive_from_renderer) = tokio::sync::mpsc::channel::(1); @@ -113,6 +115,7 @@ impl DclScene { thread_scene_crdt, wallet, testing_mode, + ethereum_provider, ) }) .unwrap(); diff --git a/rust/decentraland-godot-lib/src/dcl/scene_apis.rs b/rust/decentraland-godot-lib/src/dcl/scene_apis.rs index 37249cc4..51b06f95 100644 --- a/rust/decentraland-godot-lib/src/dcl/scene_apis.rs +++ b/rust/decentraland-godot-lib/src/dcl/scene_apis.rs @@ -3,6 +3,8 @@ use std::sync::{Arc, RwLock}; use http::Uri; use serde::Serialize; +use crate::auth::with_browser_and_server::RPCSendableMessage; + use super::js::testing::{SceneTestPlan, SceneTestResult}; #[derive(Debug, Clone, PartialEq)] @@ -165,6 +167,10 @@ pub enum RpcCall { SceneTestResult { body: SceneTestResult, }, + SendAsync { + body: RPCSendableMessage, + response: RpcResultSender>, + }, } #[derive(Debug)] diff --git a/rust/decentraland-godot-lib/src/godot_classes/dcl_global.rs b/rust/decentraland-godot-lib/src/godot_classes/dcl_global.rs index 12f24267..e9cf6ebd 100644 --- a/rust/decentraland-godot-lib/src/godot_classes/dcl_global.rs +++ b/rust/decentraland-godot-lib/src/godot_classes/dcl_global.rs @@ -1,10 +1,12 @@ +use std::sync::Arc; + use godot::{ engine::{node::ProcessMode, Engine}, prelude::*, }; use crate::{ - auth::dcl_player_identity::DclPlayerIdentity, + auth::{dcl_player_identity::DclPlayerIdentity, ethereum_provider::EthereumProvider}, avatars::avatar_scene::AvatarScene, comms::communication_manager::CommunicationManager, scene_runner::{scene_manager::SceneManager, tokio_runtime::TokioRuntime}, @@ -55,6 +57,8 @@ pub struct DclGlobal { pub testing_scene_mode: bool, #[var] pub player_identity: Gd, + + pub ethereum_provider: Arc, } #[godot_api] @@ -100,6 +104,7 @@ impl INode for DclGlobal { preview_mode, testing_scene_mode, player_identity: DclPlayerIdentity::alloc_gd(), + ethereum_provider: Arc::new(EthereumProvider::new()), } } } diff --git a/rust/decentraland-godot-lib/src/scene_runner/components/avatar_shape.rs b/rust/decentraland-godot-lib/src/scene_runner/components/avatar_shape.rs index 11a756c8..6f70ff39 100644 --- a/rust/decentraland-godot-lib/src/scene_runner/components/avatar_shape.rs +++ b/rust/decentraland-godot-lib/src/scene_runner/components/avatar_shape.rs @@ -33,6 +33,7 @@ pub fn update_avatar_shape(scene: &mut Scene, crdt_state: &mut SceneCrdtState) { node_3d.remove_child(avatar_node); } } else if let Some(new_value) = new_value { + // TODO: make dictionary from PbAvatarShape as SerializedProfile let mut dictionary = Dictionary::new(); let eyes = new_value.eye_color.as_ref().unwrap_or( &crate::dcl::components::proto_components::common::Color3 { diff --git a/rust/decentraland-godot-lib/src/scene_runner/rpc_calls/mod.rs b/rust/decentraland-godot-lib/src/scene_runner/rpc_calls/mod.rs index 6d0b1c7d..9ee23103 100644 --- a/rust/decentraland-godot-lib/src/scene_runner/rpc_calls/mod.rs +++ b/rust/decentraland-godot-lib/src/scene_runner/rpc_calls/mod.rs @@ -2,7 +2,10 @@ mod handle_restricted_actions; mod handle_runtime; mod portables; -use crate::dcl::{scene_apis::RpcCall, SceneId}; +use crate::{ + dcl::{scene_apis::RpcCall, SceneId}, + godot_classes::dcl_global::DclGlobal, +}; use self::{ handle_restricted_actions::{ @@ -87,6 +90,13 @@ pub fn process_rpcs(scene: &mut Scene, current_parcel_scene_id: &SceneId, rpc_ca *test_entry = Some(body); } } + RpcCall::SendAsync { body, response } => { + DclGlobal::singleton() + .bind() + .get_player_identity() + .bind() + .send_async(body, response); + } } } } diff --git a/rust/decentraland-godot-lib/src/scene_runner/scene_manager.rs b/rust/decentraland-godot-lib/src/scene_runner/scene_manager.rs index 7868e42e..54c14fc9 100644 --- a/rust/decentraland-godot-lib/src/scene_runner/scene_manager.rs +++ b/rust/decentraland-godot-lib/src/scene_runner/scene_manager.rs @@ -120,6 +120,7 @@ impl SceneManager { let new_scene_id = Scene::new_id(); let signal_data = (new_scene_id, scene_definition.entity_id.clone()); let testing_mode_active = DclGlobal::singleton().bind().testing_scene_mode; + let ethereum_provider = DclGlobal::singleton().bind().ethereum_provider.clone(); let dcl_scene = DclScene::spawn_new_js_dcl_scene( new_scene_id, scene_definition.clone(), @@ -128,6 +129,7 @@ impl SceneManager { self.thread_sender_to_main.clone(), wallet, testing_mode_active, + ethereum_provider, ); let new_scene = Scene::new( @@ -325,6 +327,12 @@ impl SceneManager { } if let SceneState::Alive = scene.state { + if scene.dcl_scene.thread_join_handle.is_finished() { + tracing::error!("scene closed without kill signal"); + scene_to_remove.insert(*scene_id); + continue; + } + if _process_scene( scene, end_time_us, @@ -406,6 +414,19 @@ impl SceneManager { self.dying_scene_ids.retain(|x| x != scene_id); self.scenes.remove(scene_id); + if scene.dcl_scene.thread_join_handle.is_finished() { + if let Err(err) = scene.dcl_scene.thread_join_handle.join() { + let msg = if let Some(panic_info) = err.downcast_ref::<&str>() { + format!("Thread panicked with: {}", panic_info) + } else if let Some(panic_info) = err.downcast_ref::() { + format!("Thread panicked with: {}", panic_info) + } else { + "Thread panicked with an unknown payload".to_string() + }; + tracing::error!("scene {} thread result: {:?}", scene_id.0, msg); + } + } + self.base.emit_signal( "scene_killed".into(), &[signal_data.0 .0.to_variant(), signal_data.1.to_variant()], diff --git a/rust/xtask/src/consts.rs b/rust/xtask/src/consts.rs index c6532b53..5bbb74e3 100644 --- a/rust/xtask/src/consts.rs +++ b/rust/xtask/src/consts.rs @@ -7,7 +7,7 @@ pub const PROTOC_BASE_URL: &str = "https://github.com/protocolbuffers/protobuf/releases/download/v23.2/protoc-23.2-"; pub const GODOT4_BIN_BASE_URL: &str = - "https://github.com/godotengine/godot/releases/download/4.2-stable/Godot_v4.2-stable_"; + "https://github.com/godotengine/godot/releases/download/4.2.1-stable/Godot_v4.2.1-stable_"; pub const GODOT4_EXPORT_TEMPLATES_BASE_URL: &str = - "https://github.com/godotengine/godot/releases/download/4.2-stable/Godot_v4.2-stable_export_templates.tpz"; + "https://github.com/godotengine/godot/releases/download/4.2.1-stable/Godot_v4.2.1-stable_export_templates.tpz"; diff --git a/rust/xtask/src/install_dependency.rs b/rust/xtask/src/install_dependency.rs index 6cf7aa98..efc7cfcc 100644 --- a/rust/xtask/src/install_dependency.rs +++ b/rust/xtask/src/install_dependency.rs @@ -11,10 +11,7 @@ use zip::ZipArchive; use crate::download_file::download_file; use crate::export::prepare_templates; -use crate::consts::{ - BIN_FOLDER, GODOT4_BIN_BASE_URL, PROTOC_BASE_URL, RUST_LIB_PROJECT_FOLDER, -}; - +use crate::consts::{BIN_FOLDER, GODOT4_BIN_BASE_URL, PROTOC_BASE_URL, RUST_LIB_PROJECT_FOLDER}; fn create_directory_all(path: &Path) -> io::Result<()> { if let Some(parent) = path.parent() { @@ -147,8 +144,8 @@ pub fn get_godot_executable_path() -> Option { let arch = env::consts::ARCH; let os_url = match (os, arch) { - ("linux", "x86_64") => Some("Godot_v4.2-stable_linux.x86_64".to_string()), - ("windows", "x86_64") => Some("Godot_v4.2-stable_win64.exe".to_string()), + ("linux", "x86_64") => Some("Godot_v4.2.1-stable_linux.x86_64".to_string()), + ("windows", "x86_64") => Some("Godot_v4.2.1-stable_win64.exe".to_string()), ("macos", _) => Some("Godot.app/Contents/MacOS/Godot".to_string()), _ => None, }?; diff --git a/rust/xtask/src/main.rs b/rust/xtask/src/main.rs index 3b507ab7..b9a4e294 100644 --- a/rust/xtask/src/main.rs +++ b/rust/xtask/src/main.rs @@ -196,7 +196,7 @@ pub fn coverage_with_itest(devmode: bool) -> Result<(), anyhow::Error> { "--rendering-driver", "opengl3", "--scene-test", - "['52,-52']", + "[[52,-52],[52,-54],[52,-56],[52,-58],[52,-60],[52,-62],[52,-64],[52,-66],[52,-68],[54,-52],[54,-54],[54,-56],[54,-58],[54,-60]]", "--realm", "https://decentraland.github.io/scene-explorer-tests/scene-explorer-tests", ]