diff --git a/core/io/config_file.cpp b/core/io/config_file.cpp index d3b53b996b..b6da2dadc1 100644 --- a/core/io/config_file.cpp +++ b/core/io/config_file.cpp @@ -184,7 +184,7 @@ Error ConfigFile::_internal_save(FileAccess *file) { for (OrderedHashMap::Element F = E.get().front(); F; F = F.next()) { String vstr; VariantWriter::write_to_string(F.get(), vstr); - file->store_string(F.key() + "=" + vstr + "\n"); + file->store_string(F.key().property_name_encode() + "=" + vstr + "\n"); } } diff --git a/core/io/http_client.cpp b/core/io/http_client.cpp index 581362b51a..e30b59a6fd 100644 --- a/core/io/http_client.cpp +++ b/core/io/http_client.cpp @@ -52,6 +52,8 @@ Error HTTPClient::connect_to_host(const String &p_host, int p_port, bool p_ssl, conn_port = p_port; conn_host = p_host; + ip_candidates.clear(); + ssl = p_ssl; ssl_verify_host = p_verify_host; @@ -306,6 +308,7 @@ void HTTPClient::close() { resolving = IP::RESOLVER_INVALID_ID; } + ip_candidates.clear(); response_headers.clear(); response_str.clear(); body_size = -1; @@ -328,10 +331,17 @@ Error HTTPClient::poll() { return OK; // Still resolving case IP::RESOLVER_STATUS_DONE: { - IP_Address host = IP::get_singleton()->get_resolve_item_address(resolving); - Error err = tcp_connection->connect_to_host(host, conn_port); + ip_candidates = IP::get_singleton()->get_resolve_item_addresses(resolving); IP::get_singleton()->erase_resolve_item(resolving); resolving = IP::RESOLVER_INVALID_ID; + + Error err = ERR_BUG; // Should be at least one entry. + while (ip_candidates.size() > 0) { + err = tcp_connection->connect_to_host(ip_candidates.front(), conn_port); + if (err == OK) { + break; + } + } if (err) { status = STATUS_CANT_CONNECT; return err; @@ -385,6 +395,7 @@ Error HTTPClient::poll() { if (ssl->get_status() == StreamPeerSSL::STATUS_CONNECTED) { // Handshake has been successful handshaking = false; + ip_candidates.clear(); status = STATUS_CONNECTED; return OK; } else if (ssl->get_status() != StreamPeerSSL::STATUS_HANDSHAKING) { @@ -395,15 +406,24 @@ Error HTTPClient::poll() { } // ... we will need to poll more for handshake to finish } else { + ip_candidates.clear(); status = STATUS_CONNECTED; } return OK; } break; case StreamPeerTCP::STATUS_ERROR: case StreamPeerTCP::STATUS_NONE: { + Error err = ERR_CANT_CONNECT; + while (ip_candidates.size() > 0) { + tcp_connection->disconnect_from_host(); + err = tcp_connection->connect_to_host(ip_candidates.pop_front(), conn_port); + if (err == OK) { + return OK; + } + } close(); status = STATUS_CANT_CONNECT; - return ERR_CANT_CONNECT; + return err; } break; } } break; diff --git a/core/io/http_client.h b/core/io/http_client.h index 9a14d72bd6..47ad576f5d 100644 --- a/core/io/http_client.h +++ b/core/io/http_client.h @@ -159,6 +159,7 @@ private: #ifndef JAVASCRIPT_ENABLED Status status; IP::ResolverID resolving; + Array ip_candidates; int conn_port; String conn_host; bool ssl; diff --git a/core/local_vector.h b/core/local_vector.h index f42456ac05..8a571c8a34 100644 --- a/core/local_vector.h +++ b/core/local_vector.h @@ -169,7 +169,7 @@ public: push_back(p_val); } else { resize(count + 1); - for (U i = count; i > p_pos; i--) { + for (U i = count - 1; i > p_pos; i--) { data[i] = data[i - 1]; } data[p_pos] = p_val; diff --git a/doc/classes/GraphEdit.xml b/doc/classes/GraphEdit.xml index 4606151a7d..a4e457a151 100644 --- a/doc/classes/GraphEdit.xml +++ b/doc/classes/GraphEdit.xml @@ -285,7 +285,7 @@ The icon for the zoom in button. - + The horizontal range within which a port can be grabbed (on both sides). diff --git a/doc/classes/Image.xml b/doc/classes/Image.xml index e670cf91a3..60cc13fb62 100644 --- a/doc/classes/Image.xml +++ b/doc/classes/Image.xml @@ -163,6 +163,7 @@ Generates mipmaps for the image. Mipmaps are precalculated lower-resolution copies of the image that are automatically used if the image needs to be scaled down when rendered. They help improve image quality and performance when rendering. This method returns an error if the image is compressed, in a custom format, or if the image's width/height is [code]0[/code]. + [b]Note:[/b] Mipmap generation is done on the CPU, is single-threaded and is [i]always[/i] done on the main thread. This means generating mipmaps will result in noticeable stuttering during gameplay, even if [method generate_mipmaps] is called from a [Thread]. diff --git a/doc/classes/Line2D.xml b/doc/classes/Line2D.xml index b7c362a85b..8c132e762a 100644 --- a/doc/classes/Line2D.xml +++ b/doc/classes/Line2D.xml @@ -59,6 +59,7 @@ If [code]true[/code], the line's border will be anti-aliased. + [b]Note:[/b] Line2D is not accelerated by batching when being anti-aliased. Controls the style of the line's first point. Use [enum LineCapMode] constants. diff --git a/doc/classes/MeshLibrary.xml b/doc/classes/MeshLibrary.xml index 342432d749..06f6551eda 100644 --- a/doc/classes/MeshLibrary.xml +++ b/doc/classes/MeshLibrary.xml @@ -45,6 +45,13 @@ Returns the item's mesh. + + + + + Returns the transform applied to the item's mesh. + + @@ -102,6 +109,14 @@ Sets the item's mesh. + + + + + + Sets the transform to apply to the item's mesh. + + diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 4b59d047fb..7788e6f448 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -1727,7 +1727,7 @@ void EditorNode::_dialog_action(String p_file) { ml = Ref(memnew(MeshLibrary)); } - MeshLibraryEditor::update_library_file(editor_data.get_edited_scene_root(), ml, true); + MeshLibraryEditor::update_library_file(editor_data.get_edited_scene_root(), ml, true, file_export_lib_apply_xforms->is_pressed()); Error err = ResourceSaver::save(p_file, ml); if (err) { @@ -6751,6 +6751,10 @@ EditorNode::EditorNode() { file_export_lib_merge->set_text(TTR("Merge With Existing")); file_export_lib_merge->set_pressed(true); file_export_lib->get_vbox()->add_child(file_export_lib_merge); + file_export_lib_apply_xforms = memnew(CheckBox); + file_export_lib_apply_xforms->set_text(TTR("Apply MeshInstance Transforms")); + file_export_lib_apply_xforms->set_pressed(false); + file_export_lib->get_vbox()->add_child(file_export_lib_apply_xforms); gui_base->add_child(file_export_lib); file_script = memnew(EditorFileDialog); diff --git a/editor/editor_node.h b/editor/editor_node.h index 70809015ee..7ac9a16b07 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -335,6 +335,7 @@ private: EditorFileDialog *file_script; EditorFileDialog *file_android_build_source; CheckBox *file_export_lib_merge; + CheckBox *file_export_lib_apply_xforms; String current_path; MenuButton *update_spinner; diff --git a/editor/plugins/mesh_library_editor_plugin.cpp b/editor/plugins/mesh_library_editor_plugin.cpp index 9caa72d80a..661f13d3c9 100644 --- a/editor/plugins/mesh_library_editor_plugin.cpp +++ b/editor/plugins/mesh_library_editor_plugin.cpp @@ -47,23 +47,25 @@ void MeshLibraryEditor::edit(const Ref &p_mesh_library) { } } -void MeshLibraryEditor::_menu_confirm() { +void MeshLibraryEditor::_menu_remove_confirm() { switch (option) { case MENU_OPTION_REMOVE_ITEM: { mesh_library->remove_item(to_erase); } break; - case MENU_OPTION_UPDATE_FROM_SCENE: { - String existing = mesh_library->get_meta("_editor_source_scene"); - ERR_FAIL_COND(existing == ""); - _import_scene_cbk(existing); - - } break; default: { }; } } -void MeshLibraryEditor::_import_scene(Node *p_scene, Ref p_library, bool p_merge) { +void MeshLibraryEditor::_menu_update_confirm(bool p_apply_xforms) { + cd_update->hide(); + apply_xforms = p_apply_xforms; + String existing = mesh_library->get_meta("_editor_source_scene"); + ERR_FAIL_COND(existing == ""); + _import_scene_cbk(existing); +} + +void MeshLibraryEditor::_import_scene(Node *p_scene, Ref p_library, bool p_merge, bool p_apply_xforms) { if (!p_merge) { p_library->clear(); } @@ -108,6 +110,13 @@ void MeshLibraryEditor::_import_scene(Node *p_scene, Ref p_library, } p_library->set_item_mesh(id, mesh); + + if (p_apply_xforms) { + p_library->set_item_mesh_transform(id, mi->get_transform()); + } else { + p_library->set_item_mesh_transform(id, Transform()); + } + mesh_instances[id] = mi; Vector collisions; @@ -197,15 +206,16 @@ void MeshLibraryEditor::_import_scene_cbk(const String &p_str) { ERR_FAIL_COND_MSG(!scene, "Cannot create an instance from PackedScene '" + p_str + "'."); - _import_scene(scene, mesh_library, option == MENU_OPTION_UPDATE_FROM_SCENE); + _import_scene(scene, mesh_library, option == MENU_OPTION_UPDATE_FROM_SCENE, apply_xforms); memdelete(scene); mesh_library->set_meta("_editor_source_scene", p_str); + menu->get_popup()->set_item_disabled(menu->get_popup()->get_item_index(MENU_OPTION_UPDATE_FROM_SCENE), false); } -Error MeshLibraryEditor::update_library_file(Node *p_base_scene, Ref ml, bool p_merge) { - _import_scene(p_base_scene, ml, p_merge); +Error MeshLibraryEditor::update_library_file(Node *p_base_scene, Ref ml, bool p_merge, bool p_apply_xforms) { + _import_scene(p_base_scene, ml, p_merge, p_apply_xforms); return OK; } @@ -219,23 +229,29 @@ void MeshLibraryEditor::_menu_cbk(int p_option) { String p = editor->get_inspector()->get_selected_path(); if (p.begins_with("/MeshLibrary/item") && p.get_slice_count("/") >= 3) { to_erase = p.get_slice("/", 3).to_int(); - cd->set_text(vformat(TTR("Remove item %d?"), to_erase)); - cd->popup_centered(Size2(300, 60)); + cd_remove->set_text(vformat(TTR("Remove item %d?"), to_erase)); + cd_remove->popup_centered(Size2(300, 60)); } } break; case MENU_OPTION_IMPORT_FROM_SCENE: { + apply_xforms = false; + file->popup_centered_ratio(); + } break; + case MENU_OPTION_IMPORT_FROM_SCENE_APPLY_XFORMS: { + apply_xforms = true; file->popup_centered_ratio(); } break; case MENU_OPTION_UPDATE_FROM_SCENE: { - cd->set_text(vformat(TTR("Update from existing scene?:\n%s"), String(mesh_library->get_meta("_editor_source_scene")))); - cd->popup_centered(Size2(500, 60)); + cd_update->set_text(vformat(TTR("Update from existing scene?:\n%s"), String(mesh_library->get_meta("_editor_source_scene")))); + cd_update->popup_centered(Size2(500, 60)); } break; } } void MeshLibraryEditor::_bind_methods() { ClassDB::bind_method("_menu_cbk", &MeshLibraryEditor::_menu_cbk); - ClassDB::bind_method("_menu_confirm", &MeshLibraryEditor::_menu_confirm); + ClassDB::bind_method("_menu_remove_confirm", &MeshLibraryEditor::_menu_remove_confirm); + ClassDB::bind_method("_menu_update_confirm", &MeshLibraryEditor::_menu_update_confirm); ClassDB::bind_method("_import_scene_cbk", &MeshLibraryEditor::_import_scene_cbk); } @@ -261,16 +277,22 @@ MeshLibraryEditor::MeshLibraryEditor(EditorNode *p_editor) { menu->get_popup()->add_item(TTR("Add Item"), MENU_OPTION_ADD_ITEM); menu->get_popup()->add_item(TTR("Remove Selected Item"), MENU_OPTION_REMOVE_ITEM); menu->get_popup()->add_separator(); - menu->get_popup()->add_item(TTR("Import from Scene"), MENU_OPTION_IMPORT_FROM_SCENE); + menu->get_popup()->add_item(TTR("Import from Scene (Ignore Transforms)"), MENU_OPTION_IMPORT_FROM_SCENE); + menu->get_popup()->add_item(TTR("Import from Scene (Apply Transforms)"), MENU_OPTION_IMPORT_FROM_SCENE_APPLY_XFORMS); menu->get_popup()->add_item(TTR("Update from Scene"), MENU_OPTION_UPDATE_FROM_SCENE); menu->get_popup()->set_item_disabled(menu->get_popup()->get_item_index(MENU_OPTION_UPDATE_FROM_SCENE), true); menu->get_popup()->connect("id_pressed", this, "_menu_cbk"); menu->hide(); editor = p_editor; - cd = memnew(ConfirmationDialog); - add_child(cd); - cd->get_ok()->connect("pressed", this, "_menu_confirm"); + cd_remove = memnew(ConfirmationDialog); + add_child(cd_remove); + cd_remove->get_ok()->connect("pressed", this, "_menu_remove_confirm"); + cd_update = memnew(ConfirmationDialog); + add_child(cd_update); + cd_update->get_ok()->set_text("Apply without Transforms"); + cd_update->get_ok()->connect("pressed", this, "_menu_update_confirm", varray(false)); + cd_update->add_button("Apply with Transforms")->connect("pressed", this, "_menu_update_confirm", varray(true)); } void MeshLibraryEditorPlugin::edit(Object *p_node) { diff --git a/editor/plugins/mesh_library_editor_plugin.h b/editor/plugins/mesh_library_editor_plugin.h index fbcd13575c..17b8ecd82c 100644 --- a/editor/plugins/mesh_library_editor_plugin.h +++ b/editor/plugins/mesh_library_editor_plugin.h @@ -41,8 +41,10 @@ class MeshLibraryEditor : public Control { EditorNode *editor; MenuButton *menu; - ConfirmationDialog *cd; + ConfirmationDialog *cd_remove; + ConfirmationDialog *cd_update; EditorFileDialog *file; + bool apply_xforms; int to_erase; enum { @@ -50,15 +52,17 @@ class MeshLibraryEditor : public Control { MENU_OPTION_ADD_ITEM, MENU_OPTION_REMOVE_ITEM, MENU_OPTION_UPDATE_FROM_SCENE, - MENU_OPTION_IMPORT_FROM_SCENE + MENU_OPTION_IMPORT_FROM_SCENE, + MENU_OPTION_IMPORT_FROM_SCENE_APPLY_XFORMS }; int option; void _import_scene_cbk(const String &p_str); void _menu_cbk(int p_option); - void _menu_confirm(); + void _menu_remove_confirm(); + void _menu_update_confirm(bool p_apply_xforms); - static void _import_scene(Node *p_scene, Ref p_library, bool p_merge); + static void _import_scene(Node *p_scene, Ref p_library, bool p_merge, bool p_apply_xforms); protected: static void _bind_methods(); @@ -67,7 +71,7 @@ public: MenuButton *get_menu_button() const { return menu; } void edit(const Ref &p_mesh_library); - static Error update_library_file(Node *p_base_scene, Ref ml, bool p_merge = true); + static Error update_library_file(Node *p_base_scene, Ref ml, bool p_merge = true, bool p_apply_xforms = false); MeshLibraryEditor(EditorNode *p_editor); }; diff --git a/misc/scripts/black_format.sh b/misc/scripts/black_format.sh index f93e8cbc2a..2ad9a23832 100755 --- a/misc/scripts/black_format.sh +++ b/misc/scripts/black_format.sh @@ -15,7 +15,7 @@ PY_FILES=$(find \( -path "./.git" \ \) -print) black -l 120 $PY_FILES -git diff > patch.patch +git diff --color > patch.patch # If no patch has been generated all is OK, clean up, and exit. if [ ! -s patch.patch ] ; then diff --git a/misc/scripts/clang_format.sh b/misc/scripts/clang_format.sh index 63c66d41c3..bcd63aa73b 100755 --- a/misc/scripts/clang_format.sh +++ b/misc/scripts/clang_format.sh @@ -40,7 +40,7 @@ while IFS= read -rd '' f; do done done -git diff > patch.patch +git diff --color > patch.patch # If no patch has been generated all is OK, clean up, and exit. if [ ! -s patch.patch ] ; then diff --git a/misc/scripts/file_format.sh b/misc/scripts/file_format.sh index 5f4d69a285..be84daa7f4 100755 --- a/misc/scripts/file_format.sh +++ b/misc/scripts/file_format.sh @@ -44,7 +44,7 @@ while IFS= read -rd '' f; do perl -i -pe 's/\x20== true//g' "$f" done -git diff > patch.patch +git diff --color > patch.patch # If no patch has been generated all is OK, clean up, and exit. if [ ! -s patch.patch ] ; then diff --git a/modules/gridmap/grid_map.cpp b/modules/gridmap/grid_map.cpp index b173e743ee..027906a5b8 100644 --- a/modules/gridmap/grid_map.cpp +++ b/modules/gridmap/grid_map.cpp @@ -460,7 +460,7 @@ bool GridMap::_octant_update(const OctantKey &p_key) { } Pair p; - p.first = xform; + p.first = xform * mesh_library->get_item_mesh_transform(c.item); p.second = E->get(); multimesh_items[c.item].push_back(p); } diff --git a/modules/gridmap/grid_map_editor_plugin.cpp b/modules/gridmap/grid_map_editor_plugin.cpp index 8a97d8156a..8a4f078865 100644 --- a/modules/gridmap/grid_map_editor_plugin.cpp +++ b/modules/gridmap/grid_map_editor_plugin.cpp @@ -261,6 +261,12 @@ void GridMapEditor::_update_cursor_transform() { cursor_transform.basis *= node->get_cell_scale(); cursor_transform = node->get_global_transform() * cursor_transform; + if (selected_palette >= 0) { + if (node && !node->get_mesh_library().is_null()) { + cursor_transform *= node->get_mesh_library()->get_item_mesh_transform(selected_palette); + } + } + if (cursor_instance.is_valid()) { VisualServer::get_singleton()->instance_set_transform(cursor_instance, cursor_transform); VisualServer::get_singleton()->instance_set_visible(cursor_instance, cursor_visible); diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 10411f196c..016d07aec5 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -1630,6 +1630,28 @@ Variant::Type CSharpInstance::get_property_type(const StringName &p_name, bool * return Variant::NIL; } +void CSharpInstance::get_method_list(List *p_list) const { + if (!script->is_valid() || !script->script_class) + return; + + GD_MONO_SCOPE_THREAD_ATTACH; + + // TODO: We're filtering out constructors but there may be other methods unsuitable for explicit calls. + GDMonoClass *top = script->script_class; + + while (top && top != script->native) { + const Vector &methods = top->get_all_methods(); + for (int i = 0; i < methods.size(); ++i) { + MethodInfo minfo = methods[i]->get_method_info(); + if (minfo.name != CACHED_STRING_NAME(dotctor)) { + p_list->push_back(minfo); + } + } + + top = top->get_parent_class(); + } +} + bool CSharpInstance::has_method(const StringName &p_method) const { if (!script.is_valid()) return false; @@ -3034,10 +3056,19 @@ void CSharpScript::get_script_method_list(List *p_list) const { GD_MONO_SCOPE_THREAD_ATTACH; - // TODO: Filter out things unsuitable for explicit calls, like constructors. - const Vector &methods = script_class->get_all_methods(); - for (int i = 0; i < methods.size(); ++i) { - p_list->push_back(methods[i]->get_method_info()); + // TODO: We're filtering out constructors but there may be other methods unsuitable for explicit calls. + GDMonoClass *top = script_class; + + while (top && top != native) { + const Vector &methods = top->get_all_methods(); + for (int i = 0; i < methods.size(); ++i) { + MethodInfo minfo = methods[i]->get_method_info(); + if (minfo.name != CACHED_STRING_NAME(dotctor)) { + p_list->push_back(methods[i]->get_method_info()); + } + } + + top = top->get_parent_class(); } } diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h index b19390a7c2..68eeb64216 100644 --- a/modules/mono/csharp_script.h +++ b/modules/mono/csharp_script.h @@ -255,7 +255,7 @@ public: virtual void get_property_list(List *p_properties) const; virtual Variant::Type get_property_type(const StringName &p_name, bool *r_is_valid) const; - /* TODO */ virtual void get_method_list(List *p_list) const {} + virtual void get_method_list(List *p_list) const; virtual bool has_method(const StringName &p_method) const; virtual Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Variant::CallError &r_error); virtual void call_multilevel(const StringName &p_method, const Variant **p_args, int p_argcount); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs index 34da29bdf5..dcb51267be 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs @@ -1126,6 +1126,17 @@ namespace Godot return 2.0f * inter / sum; } + /// + /// Returns a simplified canonical path. + /// + public static string SimplifyPath(this string instance) + { + return godot_icall_String_simplify_path(instance); + } + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static string godot_icall_String_simplify_path(string str); + /// /// Split the string by a divisor string, return an array of the substrings. /// Example "One,Two,Three" will return ["One","Two","Three"] if split by ",". diff --git a/modules/mono/glue/string_glue.cpp b/modules/mono/glue/string_glue.cpp index 08b04d45b5..15addb3be3 100644 --- a/modules/mono/glue/string_glue.cpp +++ b/modules/mono/glue/string_glue.cpp @@ -67,6 +67,11 @@ MonoString *godot_icall_String_sha256_text(MonoString *p_str) { return GDMonoMarshal::mono_string_from_godot(ret); } +MonoString *godot_icall_String_simplify_path(MonoString *p_str) { + String ret = GDMonoMarshal::mono_string_to_godot(p_str).simplify_path(); + return GDMonoMarshal::mono_string_from_godot(ret); +} + void godot_register_string_icalls() { GDMonoUtils::add_internal_call("Godot.StringExtensions::godot_icall_String_md5_buffer", godot_icall_String_md5_buffer); GDMonoUtils::add_internal_call("Godot.StringExtensions::godot_icall_String_md5_text", godot_icall_String_md5_text); @@ -74,6 +79,7 @@ void godot_register_string_icalls() { GDMonoUtils::add_internal_call("Godot.StringExtensions::godot_icall_String_rfindn", godot_icall_String_rfindn); GDMonoUtils::add_internal_call("Godot.StringExtensions::godot_icall_String_sha256_buffer", godot_icall_String_sha256_buffer); GDMonoUtils::add_internal_call("Godot.StringExtensions::godot_icall_String_sha256_text", godot_icall_String_sha256_text); + GDMonoUtils::add_internal_call("Godot.StringExtensions::godot_icall_String_simplify_path", godot_icall_String_simplify_path); } #endif // MONO_GLUE_ENABLED diff --git a/modules/websocket/wsl_client.cpp b/modules/websocket/wsl_client.cpp index 3125d5ab7f..415f49c29c 100644 --- a/modules/websocket/wsl_client.cpp +++ b/modules/websocket/wsl_client.cpp @@ -161,22 +161,28 @@ Error WSLClient::connect_to_host(String p_host, String p_path, uint16_t p_port, ERR_FAIL_COND_V(p_path.empty(), ERR_INVALID_PARAMETER); _peer = Ref(memnew(WSLPeer)); - IP_Address addr; - if (!p_host.is_valid_ip_address()) { - addr = IP::get_singleton()->resolve_hostname(p_host); + if (p_host.is_valid_ip_address()) { + ip_candidates.clear(); + ip_candidates.push_back(IP_Address(p_host)); } else { - addr = p_host; + ip_candidates = IP::get_singleton()->resolve_hostname_addresses(p_host); } - ERR_FAIL_COND_V(!addr.is_valid(), ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(ip_candidates.empty(), ERR_INVALID_PARAMETER); String port = ""; if ((p_port != 80 && !p_ssl) || (p_port != 443 && p_ssl)) { port = ":" + itos(p_port); } - Error err = _tcp->connect_to_host(addr, p_port); + Error err = ERR_BUG; // Should be at least one entry. + while (ip_candidates.size() > 0) { + err = _tcp->connect_to_host(ip_candidates.pop_front(), p_port); + if (err == OK) { + break; + } + } if (err != OK) { _tcp->disconnect_from_host(); _on_error(); @@ -185,6 +191,7 @@ Error WSLClient::connect_to_host(String p_host, String p_path, uint16_t p_port, _connection = _tcp; _use_ssl = p_ssl; _host = p_host; + _port = p_port; // Strip edges from protocols. _protocols.resize(p_protocols.size()); String *pw = _protocols.ptrw(); @@ -244,6 +251,7 @@ void WSLClient::poll() { _on_error(); break; case StreamPeerTCP::STATUS_CONNECTED: { + ip_candidates.clear(); Ref ssl; if (_use_ssl) { if (_connection == _tcp) { @@ -274,6 +282,12 @@ void WSLClient::poll() { _do_handshake(); } break; case StreamPeerTCP::STATUS_ERROR: + while (ip_candidates.size() > 0) { + _tcp->disconnect_from_host(); + if (_tcp->connect_to_host(ip_candidates.pop_front(), _port) == OK) { + return; + } + } disconnect_from_host(); _on_error(); break; @@ -315,6 +329,8 @@ void WSLClient::disconnect_from_host(int p_code, String p_reason) { memset(_resp_buf, 0, sizeof(_resp_buf)); _resp_pos = 0; + + ip_candidates.clear(); } IP_Address WSLClient::get_connected_host() const { diff --git a/modules/websocket/wsl_client.h b/modules/websocket/wsl_client.h index d453c880a4..93fe290b75 100644 --- a/modules/websocket/wsl_client.h +++ b/modules/websocket/wsl_client.h @@ -63,6 +63,8 @@ private: String _key; String _host; + int _port; + Array ip_candidates; Vector _protocols; bool _use_ssl; diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.java b/platform/android/java/lib/src/org/godotengine/godot/Godot.java index 5418f00bb7..ab10d46fbb 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Godot.java +++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.java @@ -419,7 +419,7 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC @SuppressLint("MissingPermission") @Keep private void vibrate(int durationMs) { - if (requestPermission("VIBRATE")) { + if (durationMs > 0 && requestPermission("VIBRATE")) { Vibrator v = (Vibrator)getContext().getSystemService(Context.VIBRATOR_SERVICE); if (v != null) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { diff --git a/platform/osx/crash_handler_osx.mm b/platform/osx/crash_handler_osx.mm index 416d4c93fc..ded3db8b4f 100644 --- a/platform/osx/crash_handler_osx.mm +++ b/platform/osx/crash_handler_osx.mm @@ -32,6 +32,8 @@ #include "core/os/os.h" #include "core/project_settings.h" +#include "core/version.h" +#include "core/version_hash.gen.h" #include "main/main.h" #include @@ -85,11 +87,18 @@ static void handle_crash(int sig) { } // Dump the backtrace to stderr with a message to the user + fprintf(stderr, "\n================================================================\n"); fprintf(stderr, "%s: Program crashed with signal %d\n", __FUNCTION__, sig); if (OS::get_singleton()->get_main_loop()) OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_CRASH); + // Print the engine version just before, so that people are reminded to include the version in backtrace reports. + if (String(VERSION_HASH).length() != 0) { + fprintf(stderr, "Engine version: " VERSION_FULL_NAME " (" VERSION_HASH ")\n"); + } else { + fprintf(stderr, "Engine version: " VERSION_FULL_NAME "\n"); + } fprintf(stderr, "Dumping the backtrace. %ls\n", msg.c_str()); char **strings = backtrace_symbols(bt_buffer, size); if (strings) { @@ -148,6 +157,7 @@ static void handle_crash(int sig) { free(strings); } fprintf(stderr, "-- END OF BACKTRACE --\n"); + fprintf(stderr, "================================================================\n"); // Abort to pass the error to the OS abort(); diff --git a/platform/windows/crash_handler_windows.cpp b/platform/windows/crash_handler_windows.cpp index 3741aaab9a..92541e231e 100644 --- a/platform/windows/crash_handler_windows.cpp +++ b/platform/windows/crash_handler_windows.cpp @@ -32,6 +32,8 @@ #include "core/os/os.h" #include "core/project_settings.h" +#include "core/version.h" +#include "core/version_hash.gen.h" #include "main/main.h" #ifdef CRASH_HANDLER_EXCEPTION @@ -127,6 +129,7 @@ DWORD CrashHandlerException(EXCEPTION_POINTERS *ep) { return EXCEPTION_CONTINUE_SEARCH; } + fprintf(stderr, "\n================================================================\n"); fprintf(stderr, "%s: Program crashed\n", __FUNCTION__); if (OS::get_singleton()->get_main_loop()) @@ -175,6 +178,12 @@ DWORD CrashHandlerException(EXCEPTION_POINTERS *ep) { msg = proj_settings->get("debug/settings/crash_handler/message"); } + // Print the engine version just before, so that people are reminded to include the version in backtrace reports. + if (String(VERSION_HASH).length() != 0) { + fprintf(stderr, "Engine version: " VERSION_FULL_NAME " (" VERSION_HASH ")\n"); + } else { + fprintf(stderr, "Engine version: " VERSION_FULL_NAME "\n"); + } fprintf(stderr, "Dumping the backtrace. %ls\n", msg.c_str()); int n = 0; @@ -200,6 +209,7 @@ DWORD CrashHandlerException(EXCEPTION_POINTERS *ep) { } while (frame.AddrReturn.Offset != 0 && n < 256); fprintf(stderr, "-- END OF BACKTRACE --\n"); + fprintf(stderr, "================================================================\n"); SymCleanup(process); diff --git a/platform/x11/crash_handler_x11.cpp b/platform/x11/crash_handler_x11.cpp index 2bdc11d2c3..d38e527b1f 100644 --- a/platform/x11/crash_handler_x11.cpp +++ b/platform/x11/crash_handler_x11.cpp @@ -32,6 +32,8 @@ #include "core/os/os.h" #include "core/project_settings.h" +#include "core/version.h" +#include "core/version_hash.gen.h" #include "main/main.h" #ifdef DEBUG_ENABLED @@ -61,12 +63,19 @@ static void handle_crash(int sig) { } // Dump the backtrace to stderr with a message to the user + fprintf(stderr, "\n================================================================\n"); fprintf(stderr, "%s: Program crashed with signal %d\n", __FUNCTION__, sig); if (OS::get_singleton()->get_main_loop()) { OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_CRASH); } + // Print the engine version just before, so that people are reminded to include the version in backtrace reports. + if (String(VERSION_HASH).length() != 0) { + fprintf(stderr, "Engine version: " VERSION_FULL_NAME " (" VERSION_HASH ")\n"); + } else { + fprintf(stderr, "Engine version: " VERSION_FULL_NAME "\n"); + } fprintf(stderr, "Dumping the backtrace. %ls\n", msg.c_str()); char **strings = backtrace_symbols(bt_buffer, size); if (strings) { @@ -115,6 +124,7 @@ static void handle_crash(int sig) { free(strings); } fprintf(stderr, "-- END OF BACKTRACE --\n"); + fprintf(stderr, "================================================================\n"); // Abort to pass the error to the OS abort(); diff --git a/scene/3d/sprite_3d.cpp b/scene/3d/sprite_3d.cpp index 8c59aab869..bf4df19fdd 100644 --- a/scene/3d/sprite_3d.cpp +++ b/scene/3d/sprite_3d.cpp @@ -1131,7 +1131,7 @@ void AnimatedSprite3D::stop() { } bool AnimatedSprite3D::is_playing() const { - return is_processing(); + return playing; } void AnimatedSprite3D::_reset_timeout() { diff --git a/scene/animation/skeleton_ik.cpp b/scene/animation/skeleton_ik.cpp index 7bb08a6897..324c2d0f8c 100644 --- a/scene/animation/skeleton_ik.cpp +++ b/scene/animation/skeleton_ik.cpp @@ -549,7 +549,7 @@ Transform SkeletonIK::_get_target_transform() { target_node_override = Object::cast_to(get_node(target_node_path_override)); } - if (target_node_override) { + if (target_node_override && target_node_override->is_inside_tree()) { return target_node_override->get_global_transform(); } else { return target; diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp index 8a18870589..774f667208 100644 --- a/scene/gui/graph_edit.cpp +++ b/scene/gui/graph_edit.cpp @@ -520,6 +520,7 @@ void GraphEdit::_notification(int p_what) { bool GraphEdit::_filter_input(const Point2 &p_point) { Ref port = get_icon("port", "GraphNode"); + Vector2i port_size = Vector2i(port->get_width(), port->get_height()); for (int i = get_child_count() - 1; i >= 0; i--) { GraphNode *gn = Object::cast_to(get_child(i)); @@ -529,14 +530,14 @@ bool GraphEdit::_filter_input(const Point2 &p_point) { for (int j = 0; j < gn->get_connection_output_count(); j++) { Vector2 pos = gn->get_connection_output_position(j) + gn->get_position(); - if (is_in_hot_zone(pos / zoom, p_point / zoom)) { + if (is_in_hot_zone(pos / zoom, p_point / zoom, port_size, false)) { return true; } } for (int j = 0; j < gn->get_connection_input_count(); j++) { Vector2 pos = gn->get_connection_input_position(j) + gn->get_position(); - if (is_in_hot_zone(pos / zoom, p_point / zoom)) { + if (is_in_hot_zone(pos / zoom, p_point / zoom, port_size, true)) { return true; } } @@ -548,8 +549,10 @@ bool GraphEdit::_filter_input(const Point2 &p_point) { void GraphEdit::_top_layer_input(const Ref &p_ev) { Ref mb = p_ev; if (mb.is_valid() && mb->get_button_index() == BUTTON_LEFT && mb->is_pressed()) { - connecting_valid = false; Ref port = get_icon("port", "GraphNode"); + Vector2i port_size = Vector2i(port->get_width(), port->get_height()); + + connecting_valid = false; click_pos = mb->get_position() / zoom; for (int i = get_child_count() - 1; i >= 0; i--) { GraphNode *gn = Object::cast_to(get_child(i)); @@ -559,7 +562,7 @@ void GraphEdit::_top_layer_input(const Ref &p_ev) { for (int j = 0; j < gn->get_connection_output_count(); j++) { Vector2 pos = gn->get_connection_output_position(j) + gn->get_position(); - if (is_in_hot_zone(pos / zoom, click_pos)) { + if (is_in_hot_zone(pos / zoom, click_pos, port_size, false)) { if (valid_left_disconnect_types.has(gn->get_connection_output_type(j))) { //check disconnect for (List::Element *E = connections.front(); E; E = E->next()) { @@ -601,7 +604,7 @@ void GraphEdit::_top_layer_input(const Ref &p_ev) { for (int j = 0; j < gn->get_connection_input_count(); j++) { Vector2 pos = gn->get_connection_input_position(j) + gn->get_position(); - if (is_in_hot_zone(pos / zoom, click_pos)) { + if (is_in_hot_zone(pos / zoom, click_pos, port_size, true)) { if (right_disconnects || valid_right_disconnect_types.has(gn->get_connection_input_type(j))) { //check disconnect for (List::Element *E = connections.front(); E; E = E->next()) { @@ -650,10 +653,12 @@ void GraphEdit::_top_layer_input(const Ref &p_ev) { connecting_target = false; top_layer->update(); minimap->update(); - connecting_valid = just_disconnected || click_pos.distance_to(connecting_to / zoom) > 20.0 * zoom; + connecting_valid = just_disconnected || click_pos.distance_to(connecting_to / zoom) > 20.0; if (connecting_valid) { Ref port = get_icon("port", "GraphNode"); + Vector2i port_size = Vector2i(port->get_width(), port->get_height()); + Vector2 mpos = mm->get_position() / zoom; for (int i = get_child_count() - 1; i >= 0; i--) { GraphNode *gn = Object::cast_to(get_child(i)); @@ -665,7 +670,7 @@ void GraphEdit::_top_layer_input(const Ref &p_ev) { for (int j = 0; j < gn->get_connection_output_count(); j++) { Vector2 pos = gn->get_connection_output_position(j) + gn->get_position(); int type = gn->get_connection_output_type(j); - if ((type == connecting_type || valid_connection_types.has(ConnType(type, connecting_type))) && is_in_hot_zone(pos / zoom, mpos)) { + if ((type == connecting_type || valid_connection_types.has(ConnType(type, connecting_type))) && is_in_hot_zone(pos / zoom, mpos, port_size, false)) { connecting_target = true; connecting_to = pos; connecting_target_to = gn->get_name(); @@ -677,7 +682,7 @@ void GraphEdit::_top_layer_input(const Ref &p_ev) { for (int j = 0; j < gn->get_connection_input_count(); j++) { Vector2 pos = gn->get_connection_input_position(j) + gn->get_position(); int type = gn->get_connection_input_type(j); - if ((type == connecting_type || valid_connection_types.has(ConnType(type, connecting_type))) && is_in_hot_zone(pos / zoom, mpos)) { + if ((type == connecting_type || valid_connection_types.has(ConnType(type, connecting_type))) && is_in_hot_zone(pos / zoom, mpos, port_size, true)) { connecting_target = true; connecting_to = pos; connecting_target_to = gn->get_name(); @@ -748,9 +753,25 @@ bool GraphEdit::_check_clickable_control(Control *p_control, const Vector2 &pos) } } -bool GraphEdit::is_in_hot_zone(const Vector2 &pos, const Vector2 &p_mouse_pos) { - if (!Rect2(pos.x - port_grab_distance_horizontal, pos.y - port_grab_distance_vertical, port_grab_distance_horizontal * 2, port_grab_distance_vertical * 2).has_point(p_mouse_pos)) { - return false; +bool GraphEdit::is_in_hot_zone(const Vector2 &pos, const Vector2 &p_mouse_pos, const Vector2i &p_port_size, bool p_left) { + if (p_left) { + if (!Rect2( + pos.x - p_port_size.x / 2 - port_grab_distance_horizontal, + pos.y - p_port_size.y / 2 - port_grab_distance_vertical / 2, + p_port_size.x + port_grab_distance_horizontal, + p_port_size.y + port_grab_distance_vertical) + .has_point(p_mouse_pos)) { + return false; + } + } else { + if (!Rect2( + pos.x - p_port_size.x / 2, + pos.y - p_port_size.y / 2 - port_grab_distance_vertical / 2, + p_port_size.x + port_grab_distance_horizontal, + p_port_size.y + port_grab_distance_vertical) + .has_point(p_mouse_pos)) { + return false; + } } for (int i = 0; i < get_child_count(); i++) { diff --git a/scene/gui/graph_edit.h b/scene/gui/graph_edit.h index a0f973749c..46f53a9849 100644 --- a/scene/gui/graph_edit.h +++ b/scene/gui/graph_edit.h @@ -181,7 +181,7 @@ private: GraphEditMinimap *minimap; void _top_layer_input(const Ref &p_ev); - bool is_in_hot_zone(const Vector2 &pos, const Vector2 &p_mouse_pos); + bool is_in_hot_zone(const Vector2 &pos, const Vector2 &p_mouse_pos, const Vector2i &p_port_size, bool p_left); void _top_layer_draw(); void _connections_layer_draw(); diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index ca3b2b9e87..8ee96d55f8 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -1090,7 +1090,7 @@ void RichTextLabel::_find_click(ItemFrame *p_frame, const Point2i &p_click, Item Control::CursorShape RichTextLabel::get_cursor_shape(const Point2 &p_pos) const { if (!underline_meta) { - return CURSOR_ARROW; + return get_default_cursor_shape(); } if (selection.click) { @@ -1098,10 +1098,11 @@ Control::CursorShape RichTextLabel::get_cursor_shape(const Point2 &p_pos) const } if (main->first_invalid_line < main->lines.size()) { - return CURSOR_ARROW; //invalid + return get_default_cursor_shape(); //invalid } int line = 0; + Item *item = nullptr; bool outside; ((RichTextLabel *)(this))->_find_click(main, p_pos, &item, &line, &outside); @@ -1110,7 +1111,7 @@ Control::CursorShape RichTextLabel::get_cursor_shape(const Point2 &p_pos) const return CURSOR_POINTING_HAND; } - return CURSOR_ARROW; + return get_default_cursor_shape(); } void RichTextLabel::_gui_input(Ref p_event) { diff --git a/scene/resources/default_theme/default_theme.cpp b/scene/resources/default_theme/default_theme.cpp index be81b0e53d..e84982e1a2 100644 --- a/scene/resources/default_theme/default_theme.cpp +++ b/scene/resources/default_theme/default_theme.cpp @@ -888,7 +888,8 @@ void fill_default_theme(Ref &theme, const Ref &default_font, const theme->set_constant("bezier_len_neg", "GraphEdit", 160 * scale); // Visual Node Ports - theme->set_constant("port_grab_distance_horizontal", "GraphEdit", 48 * scale); + + theme->set_constant("port_grab_distance_horizontal", "GraphEdit", 24 * scale); theme->set_constant("port_grab_distance_vertical", "GraphEdit", 6 * scale); theme->set_stylebox("bg", "GraphEditMinimap", make_flat_stylebox(Color(0.24, 0.24, 0.24), 0, 0, 0, 0)); diff --git a/scene/resources/mesh_library.cpp b/scene/resources/mesh_library.cpp index 6ab15a095c..41144892f3 100644 --- a/scene/resources/mesh_library.cpp +++ b/scene/resources/mesh_library.cpp @@ -43,6 +43,8 @@ bool MeshLibrary::_set(const StringName &p_name, const Variant &p_value) { set_item_name(idx, p_value); } else if (what == "mesh") { set_item_mesh(idx, p_value); + } else if (what == "mesh_transform") { + set_item_mesh_transform(idx, p_value); } else if (what == "shape") { Vector shapes; ShapeData sd; @@ -77,6 +79,8 @@ bool MeshLibrary::_get(const StringName &p_name, Variant &r_ret) const { r_ret = get_item_name(idx); } else if (what == "mesh") { r_ret = get_item_mesh(idx); + } else if (what == "mesh_transform") { + r_ret = get_item_mesh_transform(idx); } else if (what == "shapes") { r_ret = _get_item_shapes(idx); } else if (what == "navmesh") { @@ -127,6 +131,14 @@ void MeshLibrary::set_item_mesh(int p_item, const Ref &p_mesh) { _change_notify(); } +void MeshLibrary::set_item_mesh_transform(int p_item, const Transform &p_transform) { + ERR_FAIL_COND_MSG(!item_map.has(p_item), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); + item_map[p_item].mesh_transform = p_transform; + notify_change_to_owners(); + emit_changed(); + _change_notify(); +} + void MeshLibrary::set_item_shapes(int p_item, const Vector &p_shapes) { ERR_FAIL_COND_MSG(!item_map.has(p_item), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); item_map[p_item].shapes = p_shapes; @@ -170,6 +182,11 @@ Ref MeshLibrary::get_item_mesh(int p_item) const { return item_map[p_item].mesh; } +Transform MeshLibrary::get_item_mesh_transform(int p_item) const { + ERR_FAIL_COND_V_MSG(!item_map.has(p_item), Transform(), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); + return item_map[p_item].mesh_transform; +} + Vector MeshLibrary::get_item_shapes(int p_item) const { ERR_FAIL_COND_V_MSG(!item_map.has(p_item), Vector(), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); return item_map[p_item].shapes; @@ -267,12 +284,14 @@ void MeshLibrary::_bind_methods() { ClassDB::bind_method(D_METHOD("create_item", "id"), &MeshLibrary::create_item); ClassDB::bind_method(D_METHOD("set_item_name", "id", "name"), &MeshLibrary::set_item_name); ClassDB::bind_method(D_METHOD("set_item_mesh", "id", "mesh"), &MeshLibrary::set_item_mesh); + ClassDB::bind_method(D_METHOD("set_item_mesh_transform", "id", "mesh_transform"), &MeshLibrary::set_item_mesh_transform); ClassDB::bind_method(D_METHOD("set_item_navmesh", "id", "navmesh"), &MeshLibrary::set_item_navmesh); ClassDB::bind_method(D_METHOD("set_item_navmesh_transform", "id", "navmesh"), &MeshLibrary::set_item_navmesh_transform); ClassDB::bind_method(D_METHOD("set_item_shapes", "id", "shapes"), &MeshLibrary::_set_item_shapes); ClassDB::bind_method(D_METHOD("set_item_preview", "id", "texture"), &MeshLibrary::set_item_preview); ClassDB::bind_method(D_METHOD("get_item_name", "id"), &MeshLibrary::get_item_name); ClassDB::bind_method(D_METHOD("get_item_mesh", "id"), &MeshLibrary::get_item_mesh); + ClassDB::bind_method(D_METHOD("get_item_mesh_transform", "id"), &MeshLibrary::get_item_mesh_transform); ClassDB::bind_method(D_METHOD("get_item_navmesh", "id"), &MeshLibrary::get_item_navmesh); ClassDB::bind_method(D_METHOD("get_item_navmesh_transform", "id"), &MeshLibrary::get_item_navmesh_transform); ClassDB::bind_method(D_METHOD("get_item_shapes", "id"), &MeshLibrary::_get_item_shapes); diff --git a/scene/resources/mesh_library.h b/scene/resources/mesh_library.h index ddfbb3bbd1..7f8f6346e5 100644 --- a/scene/resources/mesh_library.h +++ b/scene/resources/mesh_library.h @@ -52,6 +52,7 @@ public: Vector shapes; Ref preview; Transform navmesh_transform; + Transform mesh_transform; Ref navmesh; }; @@ -71,12 +72,14 @@ public: void create_item(int p_item); void set_item_name(int p_item, const String &p_name); void set_item_mesh(int p_item, const Ref &p_mesh); + void set_item_mesh_transform(int p_item, const Transform &p_transform); void set_item_navmesh(int p_item, const Ref &p_navmesh); void set_item_navmesh_transform(int p_item, const Transform &p_transform); void set_item_shapes(int p_item, const Vector &p_shapes); void set_item_preview(int p_item, const Ref &p_preview); String get_item_name(int p_item) const; Ref get_item_mesh(int p_item) const; + Transform get_item_mesh_transform(int p_item) const; Ref get_item_navmesh(int p_item) const; Transform get_item_navmesh_transform(int p_item) const; Vector get_item_shapes(int p_item) const; diff --git a/scene/resources/packed_scene.cpp b/scene/resources/packed_scene.cpp index 070b5e61cc..dfc9c9fb37 100644 --- a/scene/resources/packed_scene.cpp +++ b/scene/resources/packed_scene.cpp @@ -377,10 +377,17 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Map return OK; } - // save the child instanced scenes that are chosen as editable, so they can be restored + bool is_editable_instance = false; + + // save the child instantiated scenes that are chosen as editable, so they can be restored // upon load back if (p_node != p_owner && p_node->get_filename() != String() && p_owner->is_editable_instance(p_node)) { editable_instances.push_back(p_owner->get_path_to(p_node)); + // Node is the root of an editable instance. + is_editable_instance = true; + } else if (p_node->get_owner() && p_node->get_owner() != p_owner && p_owner->is_editable_instance(p_node->get_owner())) { + // Node is part of an editable instance. + is_editable_instance = true; } NodeData nd; @@ -610,7 +617,7 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Map // Save the right type. If this node was created by an instance // then flag that the node should not be created but reused - if (pack_state_stack.empty()) { + if (pack_state_stack.empty() && !is_editable_instance) { //this node is not part of an instancing process, so save the type nd.type = _nm_get_string(p_node->get_class(), name_map); } else { diff --git a/scene/resources/primitive_meshes.cpp b/scene/resources/primitive_meshes.cpp index f4e58a21d9..1d6f3e55ce 100644 --- a/scene/resources/primitive_meshes.cpp +++ b/scene/resources/primitive_meshes.cpp @@ -1456,6 +1456,8 @@ void SphereMesh::_create_mesh_array(Array &p_arr) const { int i, j, prevrow, thisrow, point; float x, y, z; + float scale = height * (is_hemisphere ? 1.0 : 0.5); + // set our bounding box PoolVector points; @@ -1479,7 +1481,7 @@ void SphereMesh::_create_mesh_array(Array &p_arr) const { v /= (rings + 1); w = sin(Math_PI * v); - y = height * (is_hemisphere ? 1.0 : 0.5) * cos(Math_PI * v); + y = scale * cos(Math_PI * v); for (i = 0; i <= radial_segments; i++) { float u = i; @@ -1494,7 +1496,8 @@ void SphereMesh::_create_mesh_array(Array &p_arr) const { } else { Vector3 p = Vector3(x * radius * w, y, z * radius * w); points.push_back(p); - normals.push_back(p.normalized()); + Vector3 normal = Vector3(x * radius * w * scale, y / scale, z * radius * w * scale); + normals.push_back(normal.normalized()); }; ADD_TANGENT(z, 0.0, -x, 1.0) uvs.push_back(Vector2(u, v));