diff options
Diffstat (limited to 'scene')
24 files changed, 770 insertions, 153 deletions
diff --git a/scene/2d/tile_map.h b/scene/2d/tile_map.h index 0ac94b9d45..012bf01df9 100644 --- a/scene/2d/tile_map.h +++ b/scene/2d/tile_map.h @@ -130,7 +130,7 @@ public: } String to_string() const { - return vformat("Constraint {pos:%s, bit:%d, terrain:%d, priotity:%d}", base_cell_coords, bit, terrain, priority); + return vformat("Constraint {pos:%s, bit:%d, terrain:%d, priority:%d}", base_cell_coords, bit, terrain, priority); } Vector2i get_base_cell_coords() const { diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp index 22f968eac7..8968c1cc17 100644 --- a/scene/gui/code_edit.cpp +++ b/scene/gui/code_edit.cpp @@ -407,7 +407,7 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { } /* Ctrl + Hover symbols */ -#ifdef OSX_ENABLED +#ifdef MACOS_ENABLED if (k->get_keycode() == Key::META) { #else if (k->get_keycode() == Key::CTRL) { diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index 20fcdf1136..686045901c 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -3300,7 +3300,7 @@ void Control::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "layout_mode", PROPERTY_HINT_ENUM, "Position,Anchors,Container,Uncontrolled", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_layout_mode", "_get_layout_mode"); ADD_PROPERTY_DEFAULT("layout_mode", LayoutMode::LAYOUT_MODE_POSITION); - const String anchors_presets_options = "Custom:-1,PresetWide:15," + const String anchors_presets_options = "Custom:-1,PresetFullRect:15," "PresetTopLeft:0,PresetTopRight:1,PresetBottomRight:3,PresetBottomLeft:2," "PresetCenterLeft:4,PresetCenterTop:5,PresetCenterRight:6,PresetCenterBottom:7,PresetCenter:8," "PresetLeftWide:9,PresetTopWide:10,PresetRightWide:11,PresetBottomWide:12,PresetVCenterWide:13,PresetHCenterWide:14"; diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp index 84f2ddf700..8094812203 100644 --- a/scene/gui/label.cpp +++ b/scene/gui/label.cpp @@ -31,6 +31,7 @@ #include "label.h" #include "core/config/project_settings.h" +#include "core/core_string_names.h" #include "core/string/print_string.h" #include "core/string/translation.h" @@ -64,7 +65,7 @@ bool Label::is_uppercase() const { } int Label::get_line_height(int p_line) const { - Ref<Font> font = get_theme_font(SNAME("font")); + Ref<Font> font = (settings.is_valid() && settings->get_font().is_valid()) ? settings->get_font() : get_theme_font(SNAME("font")); if (p_line >= 0 && p_line < lines_rid.size()) { return TS->shaped_text_get_size(lines_rid[p_line]).y; } else if (lines_rid.size() > 0) { @@ -74,7 +75,8 @@ int Label::get_line_height(int p_line) const { } return h; } else { - return font->get_height(get_theme_font_size(SNAME("font_size"))); + int font_size = settings.is_valid() ? settings->get_font_size() : get_theme_font_size(SNAME("font_size")); + return font->get_height(font_size); } } @@ -91,8 +93,8 @@ void Label::_shape() { } else { TS->shaped_text_set_direction(text_rid, (TextServer::Direction)text_direction); } - const Ref<Font> &font = get_theme_font(SNAME("font")); - int font_size = get_theme_font_size(SNAME("font_size")); + const Ref<Font> &font = (settings.is_valid() && settings->get_font().is_valid()) ? settings->get_font() : get_theme_font(SNAME("font")); + int font_size = settings.is_valid() ? settings->get_font_size() : get_theme_font_size(SNAME("font_size")); ERR_FAIL_COND(font.is_null()); String text = (uppercase) ? TS->string_to_upper(xl_text, language) : xl_text; if (visible_chars >= 0 && visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING) { @@ -223,9 +225,8 @@ void Label::_shape() { } void Label::_update_visible() { - int line_spacing = get_theme_constant(SNAME("line_spacing"), SNAME("Label")); + int line_spacing = settings.is_valid() ? settings->get_line_spacing() : get_theme_constant(SNAME("line_spacing"), SNAME("Label")); Ref<StyleBox> style = get_theme_stylebox(SNAME("normal"), SNAME("Label")); - Ref<Font> font = get_theme_font(SNAME("font")); int lines_visible = lines_rid.size(); if (max_lines_visible >= 0 && lines_visible > max_lines_visible) { @@ -295,17 +296,19 @@ void Label::_notification(int p_what) { RID ci = get_canvas_item(); + bool has_settings = settings.is_valid(); + Size2 string_size; Size2 size = get_size(); Ref<StyleBox> style = get_theme_stylebox(SNAME("normal")); - Ref<Font> font = get_theme_font(SNAME("font")); - Color font_color = get_theme_color(SNAME("font_color")); - Color font_shadow_color = get_theme_color(SNAME("font_shadow_color")); - Point2 shadow_ofs(get_theme_constant(SNAME("shadow_offset_x")), get_theme_constant(SNAME("shadow_offset_y"))); - int line_spacing = get_theme_constant(SNAME("line_spacing")); - Color font_outline_color = get_theme_color(SNAME("font_outline_color")); - int outline_size = get_theme_constant(SNAME("outline_size")); - int shadow_outline_size = get_theme_constant(SNAME("shadow_outline_size")); + Ref<Font> font = (has_settings && settings->get_font().is_valid()) ? settings->get_font() : get_theme_font(SNAME("font")); + Color font_color = has_settings ? settings->get_font_color() : get_theme_color(SNAME("font_color")); + Color font_shadow_color = has_settings ? settings->get_shadow_color() : get_theme_color(SNAME("font_shadow_color")); + Point2 shadow_ofs = has_settings ? settings->get_shadow_offset() : Point2(get_theme_constant(SNAME("shadow_offset_x")), get_theme_constant(SNAME("shadow_offset_y"))); + int line_spacing = has_settings ? settings->get_line_spacing() : get_theme_constant(SNAME("line_spacing")); + Color font_outline_color = has_settings ? settings->get_outline_color() : get_theme_color(SNAME("font_outline_color")); + int outline_size = has_settings ? settings->get_outline_size() : get_theme_constant(SNAME("outline_size")); + int shadow_outline_size = has_settings ? settings->get_shadow_size() : get_theme_constant(SNAME("shadow_outline_size")); bool rtl = (TS->shaped_text_get_inferred_direction(text_rid) == TextServer::DIRECTION_RTL); bool rtl_layout = is_layout_rtl(); @@ -552,8 +555,10 @@ Size2 Label::get_minimum_size() const { Size2 min_size = minsize; - Ref<Font> font = get_theme_font(SNAME("font")); - min_size.height = MAX(min_size.height, font->get_height(get_theme_font_size(SNAME("font_size")))); + const Ref<Font> &font = (settings.is_valid() && settings->get_font().is_valid()) ? settings->get_font() : get_theme_font(SNAME("font")); + int font_size = settings.is_valid() ? settings->get_font_size() : get_theme_font_size(SNAME("font_size")); + + min_size.height = MAX(min_size.height, font->get_height(font_size) + font->get_spacing(TextServer::SPACING_TOP) + font->get_spacing(TextServer::SPACING_BOTTOM)); Size2 min_style = get_theme_stylebox(SNAME("normal"))->get_minimum_size(); if (autowrap_mode != TextServer::AUTOWRAP_OFF) { @@ -578,9 +583,8 @@ int Label::get_line_count() const { } int Label::get_visible_line_count() const { - Ref<Font> font = get_theme_font(SNAME("font")); Ref<StyleBox> style = get_theme_stylebox(SNAME("normal")); - int line_spacing = get_theme_constant(SNAME("line_spacing")); + int line_spacing = settings.is_valid() ? settings->get_line_spacing() : get_theme_constant(SNAME("line_spacing")); int lines_visible = 0; float total_h = 0.0; for (int64_t i = lines_skipped; i < lines_rid.size(); i++) { @@ -641,6 +645,28 @@ void Label::set_text(const String &p_string) { update_minimum_size(); } +void Label::_invalidate() { + font_dirty = true; + update(); +} + +void Label::set_label_settings(const Ref<LabelSettings> &p_settings) { + if (settings != p_settings) { + if (settings.is_valid()) { + settings->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &Label::_invalidate)); + } + settings = p_settings; + if (settings.is_valid()) { + settings->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &Label::_invalidate), varray(), CONNECT_REFERENCE_COUNTED); + } + _invalidate(); + } +} + +Ref<LabelSettings> Label::get_label_settings() const { + return settings; +} + void Label::set_text_direction(Control::TextDirection p_text_direction) { ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3); if (text_direction != p_text_direction) { @@ -804,6 +830,8 @@ void Label::_bind_methods() { ClassDB::bind_method(D_METHOD("get_vertical_alignment"), &Label::get_vertical_alignment); ClassDB::bind_method(D_METHOD("set_text", "text"), &Label::set_text); ClassDB::bind_method(D_METHOD("get_text"), &Label::get_text); + ClassDB::bind_method(D_METHOD("set_label_settings", "settings"), &Label::set_label_settings); + ClassDB::bind_method(D_METHOD("get_label_settings"), &Label::get_label_settings); ClassDB::bind_method(D_METHOD("set_text_direction", "direction"), &Label::set_text_direction); ClassDB::bind_method(D_METHOD("get_text_direction"), &Label::get_text_direction); ClassDB::bind_method(D_METHOD("set_language", "language"), &Label::set_language); @@ -836,6 +864,7 @@ void Label::_bind_methods() { ClassDB::bind_method(D_METHOD("get_structured_text_bidi_override_options"), &Label::get_structured_text_bidi_override_options); ADD_PROPERTY(PropertyInfo(Variant::STRING, "text", PROPERTY_HINT_MULTILINE_TEXT, "", PROPERTY_USAGE_DEFAULT_INTL), "set_text", "get_text"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "label_settings", PROPERTY_HINT_RESOURCE_TYPE, "LabelSettings"), "set_label_settings", "get_label_settings"); ADD_PROPERTY(PropertyInfo(Variant::INT, "horizontal_alignment", PROPERTY_HINT_ENUM, "Left,Center,Right,Fill"), "set_horizontal_alignment", "get_horizontal_alignment"); ADD_PROPERTY(PropertyInfo(Variant::INT, "vertical_alignment", PROPERTY_HINT_ENUM, "Top,Center,Bottom,Fill"), "set_vertical_alignment", "get_vertical_alignment"); ADD_PROPERTY(PropertyInfo(Variant::INT, "autowrap_mode", PROPERTY_HINT_ENUM, "Off,Arbitrary,Word,Word (Smart)"), "set_autowrap_mode", "get_autowrap_mode"); diff --git a/scene/gui/label.h b/scene/gui/label.h index a59d35950d..3734fce1bb 100644 --- a/scene/gui/label.h +++ b/scene/gui/label.h @@ -32,6 +32,7 @@ #define LABEL_H #include "scene/gui/control.h" +#include "scene/resources/label_settings.h" class Label : public Control { GDCLASS(Label, Control); @@ -65,8 +66,11 @@ private: int lines_skipped = 0; int max_lines_visible = -1; + Ref<LabelSettings> settings; + void _update_visible(); void _shape(); + void _invalidate(); protected: void _notification(int p_what); @@ -85,6 +89,9 @@ public: void set_text(const String &p_string); String get_text() const; + void set_label_settings(const Ref<LabelSettings> &p_settings); + Ref<LabelSettings> get_label_settings() const; + void set_text_direction(TextDirection p_text_direction); TextDirection get_text_direction() const; diff --git a/scene/main/canvas_item.cpp b/scene/main/canvas_item.cpp index 2cd7cf5648..e298805aca 100644 --- a/scene/main/canvas_item.cpp +++ b/scene/main/canvas_item.cpp @@ -230,16 +230,16 @@ void CanvasItem::_enter_canvas() { RenderingServer::get_singleton()->canvas_item_set_parent(canvas_item, canvas); - group = "root_canvas" + itos(canvas.get_id()); + canvas_group = "root_canvas" + itos(canvas.get_id()); - add_to_group(group); + add_to_group(canvas_group); if (canvas_layer) { canvas_layer->reset_sort_index(); } else { get_viewport()->gui_reset_canvas_sort_index(); } - get_tree()->call_group_flags(SceneTree::GROUP_CALL_UNIQUE | SceneTree::GROUP_CALL_DEFERRED, group, SNAME("_top_level_raise_self")); + get_tree()->call_group_flags(SceneTree::GROUP_CALL_UNIQUE | SceneTree::GROUP_CALL_DEFERRED, canvas_group, SNAME("_top_level_raise_self")); } else { CanvasItem *parent = get_parent_item(); @@ -258,7 +258,10 @@ void CanvasItem::_exit_canvas() { notification(NOTIFICATION_EXIT_CANVAS, true); //reverse the notification RenderingServer::get_singleton()->canvas_item_set_parent(canvas_item, RID()); canvas_layer = nullptr; - group = StringName(); + if (canvas_group != StringName()) { + remove_from_group(canvas_group); + canvas_group = StringName(); + } } void CanvasItem::_notification(int p_what) { @@ -319,8 +322,8 @@ void CanvasItem::_notification(int p_what) { break; } - if (group != StringName()) { - get_tree()->call_group_flags(SceneTree::GROUP_CALL_UNIQUE | SceneTree::GROUP_CALL_DEFERRED, group, "_top_level_raise_self"); + if (canvas_group != StringName()) { + get_tree()->call_group_flags(SceneTree::GROUP_CALL_UNIQUE | SceneTree::GROUP_CALL_DEFERRED, canvas_group, "_top_level_raise_self"); } else { CanvasItem *p = get_parent_item(); ERR_FAIL_COND(!p); diff --git a/scene/main/canvas_item.h b/scene/main/canvas_item.h index a4574dce61..f5df6512ee 100644 --- a/scene/main/canvas_item.h +++ b/scene/main/canvas_item.h @@ -70,7 +70,7 @@ private: mutable SelfList<Node> xform_change; RID canvas_item; - StringName group; + StringName canvas_group; CanvasLayer *canvas_layer = nullptr; diff --git a/scene/multiplayer/multiplayer_spawner.cpp b/scene/multiplayer/multiplayer_spawner.cpp index ddd01d0a43..8363d05e54 100644 --- a/scene/multiplayer/multiplayer_spawner.cpp +++ b/scene/multiplayer/multiplayer_spawner.cpp @@ -71,7 +71,7 @@ bool MultiplayerSpawner::_get(const StringName &p_name, Variant &r_ret) const { } void MultiplayerSpawner::_get_property_list(List<PropertyInfo> *p_list) const { - p_list->push_back(PropertyInfo(Variant::INT, "_spawnable_scene_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_ARRAY, "Scenes,scenes/")); + p_list->push_back(PropertyInfo(Variant::INT, "_spawnable_scene_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_ARRAY, "Auto Spawn List,scenes/")); List<String> exts; ResourceLoader::get_recognized_extensions_for_type("PackedScene", &exts); String ext_hint; @@ -144,10 +144,6 @@ void MultiplayerSpawner::_bind_methods() { ClassDB::bind_method(D_METHOD("set_spawn_limit", "limit"), &MultiplayerSpawner::set_spawn_limit); ADD_PROPERTY(PropertyInfo(Variant::INT, "spawn_limit", PROPERTY_HINT_RANGE, "0,1024,1,or_greater"), "set_spawn_limit", "get_spawn_limit"); - ClassDB::bind_method(D_METHOD("set_auto_spawning", "enabled"), &MultiplayerSpawner::set_auto_spawning); - ClassDB::bind_method(D_METHOD("is_auto_spawning"), &MultiplayerSpawner::is_auto_spawning); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_spawn"), "set_auto_spawning", "is_auto_spawning"); - GDVIRTUAL_BIND(_spawn_custom, "data"); ADD_SIGNAL(MethodInfo("despawned", PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); @@ -169,7 +165,7 @@ void MultiplayerSpawner::_update_spawn_node() { Node *node = spawn_path.is_empty() && is_inside_tree() ? nullptr : get_node_or_null(spawn_path); if (node) { spawn_node = node->get_instance_id(); - if (auto_spawn) { + if (get_spawnable_scene_count() && !GDVIRTUAL_IS_OVERRIDDEN(_spawn_custom)) { node->connect("child_entered_tree", callable_mp(this, &MultiplayerSpawner::_node_added)); } } else { @@ -221,15 +217,6 @@ void MultiplayerSpawner::_node_added(Node *p_node) { _track(p_node, Variant(), id); } -void MultiplayerSpawner::set_auto_spawning(bool p_enabled) { - auto_spawn = p_enabled; - _update_spawn_node(); -} - -bool MultiplayerSpawner::is_auto_spawning() const { - return auto_spawn; -} - NodePath MultiplayerSpawner::get_spawn_path() const { return spawn_path; } diff --git a/scene/multiplayer/multiplayer_spawner.h b/scene/multiplayer/multiplayer_spawner.h index e8abe702a0..2c0eb9a2f0 100644 --- a/scene/multiplayer/multiplayer_spawner.h +++ b/scene/multiplayer/multiplayer_spawner.h @@ -69,7 +69,6 @@ private: ObjectID spawn_node; HashMap<ObjectID, SpawnInfo> tracked_nodes; - bool auto_spawn = false; uint32_t spawn_limit = 0; void _update_spawn_node(); @@ -102,8 +101,6 @@ public: void set_spawn_path(const NodePath &p_path); uint32_t get_spawn_limit() const { return spawn_limit; } void set_spawn_limit(uint32_t p_limit) { spawn_limit = p_limit; } - bool is_auto_spawning() const; - void set_auto_spawning(bool p_enabled); const Variant get_spawn_argument(const ObjectID &p_id) const; int find_spawnable_scene_index_from_object(const ObjectID &p_id) const; diff --git a/scene/multiplayer/multiplayer_synchronizer.cpp b/scene/multiplayer/multiplayer_synchronizer.cpp index 68f6e54fa8..e1b7433968 100644 --- a/scene/multiplayer/multiplayer_synchronizer.cpp +++ b/scene/multiplayer/multiplayer_synchronizer.cpp @@ -43,6 +43,11 @@ Object *MultiplayerSynchronizer::_get_prop_target(Object *p_obj, const NodePath } void MultiplayerSynchronizer::_stop() { +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + return; + } +#endif Node *node = is_inside_tree() ? get_node_or_null(root_path) : nullptr; if (node) { get_multiplayer()->replication_stop(node, this); @@ -50,9 +55,42 @@ void MultiplayerSynchronizer::_stop() { } void MultiplayerSynchronizer::_start() { +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + return; + } +#endif Node *node = is_inside_tree() ? get_node_or_null(root_path) : nullptr; if (node) { get_multiplayer()->replication_start(node, this); + _update_process(); + } +} + +void MultiplayerSynchronizer::_update_process() { +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + return; + } +#endif + Node *node = is_inside_tree() ? get_node_or_null(root_path) : nullptr; + if (!node) { + return; + } + set_process_internal(false); + set_physics_process_internal(false); + if (!visibility_filters.size()) { + return; + } + switch (visibility_update_mode) { + case VISIBILITY_PROCESS_IDLE: + set_process_internal(true); + break; + case VISIBILITY_PROCESS_PHYSICS: + set_physics_process_internal(true); + break; + case VISIBILITY_PROCESS_NONE: + break; } } @@ -85,6 +123,66 @@ Error MultiplayerSynchronizer::set_state(const List<NodePath> &p_properties, Obj return OK; } +bool MultiplayerSynchronizer::is_visibility_public() const { + return peer_visibility.has(0); +} + +void MultiplayerSynchronizer::set_visibility_public(bool p_visible) { + set_visibility_for(0, p_visible); +} + +bool MultiplayerSynchronizer::is_visible_to(int p_peer) { + if (visibility_filters.size()) { + Variant arg = p_peer; + const Variant *argv[1] = { &arg }; + for (Callable filter : visibility_filters) { + Variant ret; + Callable::CallError err; + filter.call(argv, 1, ret, err); + ERR_FAIL_COND_V(err.error != Callable::CallError::CALL_OK || ret.get_type() != Variant::BOOL, false); + if (!ret.operator bool()) { + return false; + } + } + } + return peer_visibility.has(0) || peer_visibility.has(p_peer); +} + +void MultiplayerSynchronizer::add_visibility_filter(Callable p_callback) { + visibility_filters.insert(p_callback); + _update_process(); +} + +void MultiplayerSynchronizer::remove_visibility_filter(Callable p_callback) { + visibility_filters.erase(p_callback); + _update_process(); +} + +void MultiplayerSynchronizer::set_visibility_for(int p_peer, bool p_visible) { + if (peer_visibility.has(p_peer) == p_visible) { + return; + } + if (p_visible) { + peer_visibility.insert(p_peer); + } else { + peer_visibility.erase(p_peer); + } + update_visibility(p_peer); +} + +bool MultiplayerSynchronizer::get_visibility_for(int p_peer) const { + return peer_visibility.has(p_peer); +} + +void MultiplayerSynchronizer::set_visibility_update_mode(VisibilityUpdateMode p_mode) { + visibility_update_mode = p_mode; + _update_process(); +} + +MultiplayerSynchronizer::VisibilityUpdateMode MultiplayerSynchronizer::get_visibility_update_mode() const { + return visibility_update_mode; +} + void MultiplayerSynchronizer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_root_path", "path"), &MultiplayerSynchronizer::set_root_path); ClassDB::bind_method(D_METHOD("get_root_path"), &MultiplayerSynchronizer::get_root_path); @@ -95,9 +193,29 @@ void MultiplayerSynchronizer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_replication_config", "config"), &MultiplayerSynchronizer::set_replication_config); ClassDB::bind_method(D_METHOD("get_replication_config"), &MultiplayerSynchronizer::get_replication_config); + ClassDB::bind_method(D_METHOD("set_visibility_update_mode", "mode"), &MultiplayerSynchronizer::set_visibility_update_mode); + ClassDB::bind_method(D_METHOD("get_visibility_update_mode"), &MultiplayerSynchronizer::get_visibility_update_mode); + ClassDB::bind_method(D_METHOD("update_visibility", "for_peer"), &MultiplayerSynchronizer::update_visibility, DEFVAL(0)); + + ClassDB::bind_method(D_METHOD("set_visibility_public", "visible"), &MultiplayerSynchronizer::set_visibility_public); + ClassDB::bind_method(D_METHOD("is_visibility_public"), &MultiplayerSynchronizer::is_visibility_public); + + ClassDB::bind_method(D_METHOD("add_visibility_filter", "filter"), &MultiplayerSynchronizer::add_visibility_filter); + ClassDB::bind_method(D_METHOD("remove_visibility_filter", "filter"), &MultiplayerSynchronizer::remove_visibility_filter); + ClassDB::bind_method(D_METHOD("set_visibility_for", "peer", "visible"), &MultiplayerSynchronizer::set_visibility_for); + ClassDB::bind_method(D_METHOD("get_visibility_for", "peer"), &MultiplayerSynchronizer::get_visibility_for); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "root_path"), "set_root_path", "get_root_path"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "replication_interval", PROPERTY_HINT_RANGE, "0,5,0.001,suffix:s"), "set_replication_interval", "get_replication_interval"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "replication_config", PROPERTY_HINT_RESOURCE_TYPE, "SceneReplicationConfig"), "set_replication_config", "get_replication_config"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "replication_config", PROPERTY_HINT_RESOURCE_TYPE, "SceneReplicationConfig", PROPERTY_USAGE_NO_EDITOR), "set_replication_config", "get_replication_config"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "visibility_update_mode", PROPERTY_HINT_ENUM, "Idle,Physics,None"), "set_visibility_update_mode", "get_visibility_update_mode"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "public_visibility"), "set_visibility_public", "is_visibility_public"); + + BIND_ENUM_CONSTANT(VISIBILITY_PROCESS_IDLE); + BIND_ENUM_CONSTANT(VISIBILITY_PROCESS_PHYSICS); + BIND_ENUM_CONSTANT(VISIBILITY_PROCESS_NONE); + + ADD_SIGNAL(MethodInfo("visibility_changed", PropertyInfo(Variant::INT, "for_peer"))); } void MultiplayerSynchronizer::_notification(int p_what) { @@ -118,6 +236,11 @@ void MultiplayerSynchronizer::_notification(int p_what) { case NOTIFICATION_EXIT_TREE: { _stop(); } break; + + case NOTIFICATION_INTERNAL_PROCESS: + case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { + update_visibility(0); + } break; } } @@ -142,6 +265,18 @@ Ref<SceneReplicationConfig> MultiplayerSynchronizer::get_replication_config() { return replication_config; } +void MultiplayerSynchronizer::update_visibility(int p_for_peer) { +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + return; + } +#endif + Node *node = is_inside_tree() ? get_node_or_null(root_path) : nullptr; + if (node && get_multiplayer()->has_multiplayer_peer() && is_multiplayer_authority()) { + emit_signal(SNAME("visibility_changed"), p_for_peer); + } +} + void MultiplayerSynchronizer::set_root_path(const NodePath &p_path) { _stop(); root_path = p_path; @@ -162,3 +297,8 @@ void MultiplayerSynchronizer::set_multiplayer_authority(int p_peer_id, bool p_re Node::set_multiplayer_authority(p_peer_id, p_recursive); get_multiplayer()->replication_start(node, this); } + +MultiplayerSynchronizer::MultiplayerSynchronizer() { + // Publicly visible by default. + peer_visibility.insert(0); +} diff --git a/scene/multiplayer/multiplayer_synchronizer.h b/scene/multiplayer/multiplayer_synchronizer.h index f61ef459da..59f02b84c1 100644 --- a/scene/multiplayer/multiplayer_synchronizer.h +++ b/scene/multiplayer/multiplayer_synchronizer.h @@ -38,14 +38,25 @@ class MultiplayerSynchronizer : public Node { GDCLASS(MultiplayerSynchronizer, Node); +public: + enum VisibilityUpdateMode { + VISIBILITY_PROCESS_IDLE, + VISIBILITY_PROCESS_PHYSICS, + VISIBILITY_PROCESS_NONE, + }; + private: Ref<SceneReplicationConfig> replication_config; NodePath root_path = NodePath(".."); // Start with parent, like with AnimationPlayer. uint64_t interval_msec = 0; + VisibilityUpdateMode visibility_update_mode = VISIBILITY_PROCESS_IDLE; + HashSet<Callable> visibility_filters; + HashSet<int> peer_visibility; static Object *_get_prop_target(Object *p_obj, const NodePath &p_prop); void _start(); void _stop(); + void _update_process(); protected: static void _bind_methods(); @@ -66,7 +77,19 @@ public: NodePath get_root_path() const; virtual void set_multiplayer_authority(int p_peer_id, bool p_recursive = true) override; - MultiplayerSynchronizer() {} + bool is_visibility_public() const; + void set_visibility_public(bool p_public); + bool is_visible_to(int p_peer); + void set_visibility_for(int p_peer, bool p_visible); + bool get_visibility_for(int p_peer) const; + void update_visibility(int p_for_peer); + void set_visibility_update_mode(VisibilityUpdateMode p_mode); + void add_visibility_filter(Callable p_callback); + void remove_visibility_filter(Callable p_callback); + VisibilityUpdateMode get_visibility_update_mode() const; + + MultiplayerSynchronizer(); }; +VARIANT_ENUM_CAST(MultiplayerSynchronizer::VisibilityUpdateMode); #endif // MULTIPLAYER_SYNCHRONIZER_H diff --git a/scene/multiplayer/scene_cache_interface.cpp b/scene/multiplayer/scene_cache_interface.cpp index 7c271341db..79a7dc2d5a 100644 --- a/scene/multiplayer/scene_cache_interface.cpp +++ b/scene/multiplayer/scene_cache_interface.cpp @@ -187,18 +187,29 @@ bool SceneCacheInterface::is_cache_confirmed(NodePath p_path, int p_peer) { return F->value; } -bool SceneCacheInterface::send_object_cache(Object *p_obj, NodePath p_path, int p_peer_id, int &r_id) { +int SceneCacheInterface::make_object_cache(Object *p_obj) { Node *node = Object::cast_to<Node>(p_obj); - ERR_FAIL_COND_V(!node, false); + ERR_FAIL_COND_V(!node, -1); + NodePath for_path = multiplayer->get_root_path().rel_path_to(node->get_path()); // See if the path is cached. - PathSentCache *psc = path_send_cache.getptr(p_path); + PathSentCache *psc = path_send_cache.getptr(for_path); if (!psc) { // Path is not cached, create. - path_send_cache[p_path] = PathSentCache(); - psc = path_send_cache.getptr(p_path); + path_send_cache[for_path] = PathSentCache(); + psc = path_send_cache.getptr(for_path); psc->id = last_send_cache_id++; } - r_id = psc->id; + return psc->id; +} + +bool SceneCacheInterface::send_object_cache(Object *p_obj, int p_peer_id, int &r_id) { + Node *node = Object::cast_to<Node>(p_obj); + ERR_FAIL_COND_V(!node, false); + + r_id = make_object_cache(p_obj); + ERR_FAIL_COND_V(r_id < 0, false); + NodePath for_path = multiplayer->get_root_path().rel_path_to(node->get_path()); + PathSentCache *psc = path_send_cache.getptr(for_path); bool has_all_peers = true; List<int> peers_to_add; // If one is missing, take note to add it. @@ -233,7 +244,7 @@ bool SceneCacheInterface::send_object_cache(Object *p_obj, NodePath p_path, int } if (peers_to_add.size()) { - _send_confirm_path(node, p_path, psc, peers_to_add); + _send_confirm_path(node, for_path, psc, peers_to_add); } return has_all_peers; diff --git a/scene/multiplayer/scene_cache_interface.h b/scene/multiplayer/scene_cache_interface.h index 3116233b5b..6bfd683cf4 100644 --- a/scene/multiplayer/scene_cache_interface.h +++ b/scene/multiplayer/scene_cache_interface.h @@ -72,7 +72,8 @@ public: virtual void process_confirm_path(int p_from, const uint8_t *p_packet, int p_packet_len) override; // Returns true if all peers have cached path. - virtual bool send_object_cache(Object *p_obj, NodePath p_path, int p_target, int &p_id) override; + virtual bool send_object_cache(Object *p_obj, int p_target, int &p_id) override; + virtual int make_object_cache(Object *p_obj) override; virtual Object *get_cached_object(int p_from, uint32_t p_cache_id) override; virtual bool is_cache_confirmed(NodePath p_path, int p_peer) override; diff --git a/scene/multiplayer/scene_replication_interface.cpp b/scene/multiplayer/scene_replication_interface.cpp index e4715ceb88..c616c5bb85 100644 --- a/scene/multiplayer/scene_replication_interface.cpp +++ b/scene/multiplayer/scene_replication_interface.cpp @@ -60,14 +60,13 @@ void SceneReplicationInterface::on_peer_change(int p_id, bool p_connected) { if (p_connected) { rep_state->on_peer_change(p_id, p_connected); for (const ObjectID &oid : rep_state->get_spawned_nodes()) { - _send_spawn(rep_state->get_node(oid), rep_state->get_spawner(oid), p_id); + _update_spawn_visibility(p_id, oid); } - for (const ObjectID &oid : rep_state->get_path_only_nodes()) { - Node *node = rep_state->get_node(oid); + for (const ObjectID &oid : rep_state->get_synced_nodes()) { MultiplayerSynchronizer *sync = rep_state->get_synchronizer(oid); - ERR_CONTINUE(!node || !sync); + ERR_CONTINUE(!sync); // ERR_BUG if (sync->is_multiplayer_authority()) { - rep_state->peer_add_node(p_id, oid); + _update_sync_visibility(p_id, oid); } } } else { @@ -97,7 +96,13 @@ Error SceneReplicationInterface::on_spawn(Object *p_obj, Variant p_config) { ERR_FAIL_COND_V(!spawner, ERR_INVALID_PARAMETER); Error err = rep_state->config_add_spawn(node, spawner); ERR_FAIL_COND_V(err != OK, err); - return _send_spawn(node, spawner, 0); + const ObjectID oid = node->get_instance_id(); + if (multiplayer->has_multiplayer_peer() && spawner->is_multiplayer_authority()) { + rep_state->ensure_net_id(oid); + _update_spawn_visibility(0, oid); + } + ERR_FAIL_COND_V(err != OK, err); + return OK; } Error SceneReplicationInterface::on_despawn(Object *p_obj, Variant p_config) { @@ -105,9 +110,19 @@ Error SceneReplicationInterface::on_despawn(Object *p_obj, Variant p_config) { ERR_FAIL_COND_V(!node || p_config.get_type() != Variant::OBJECT, ERR_INVALID_PARAMETER); MultiplayerSpawner *spawner = Object::cast_to<MultiplayerSpawner>(p_config.get_validated_object()); ERR_FAIL_COND_V(!p_obj || !spawner, ERR_INVALID_PARAMETER); - Error err = rep_state->config_del_spawn(node, spawner); - ERR_FAIL_COND_V(err != OK, err); - return _send_despawn(node, 0); + // Forcibly despawn to all peers that knowns me. + int len = 0; + Error err = _make_despawn_packet(node, len); + ERR_FAIL_COND_V(err != OK, ERR_BUG); + const ObjectID oid = p_obj->get_instance_id(); + for (int pid : rep_state->get_peers()) { + if (!rep_state->is_peer_spawn(pid, oid)) { + continue; + } + _send_raw(packet_cache.ptr(), len, pid, true); + } + // Also remove spawner tracking from the replication state. + return rep_state->config_del_spawn(node, spawner); } Error SceneReplicationInterface::on_replication_start(Object *p_obj, Variant p_config) { @@ -115,7 +130,15 @@ Error SceneReplicationInterface::on_replication_start(Object *p_obj, Variant p_c ERR_FAIL_COND_V(!node || p_config.get_type() != Variant::OBJECT, ERR_INVALID_PARAMETER); MultiplayerSynchronizer *sync = Object::cast_to<MultiplayerSynchronizer>(p_config.get_validated_object()); ERR_FAIL_COND_V(!sync, ERR_INVALID_PARAMETER); + + // Add to synchronizer list and setup visibility. rep_state->config_add_sync(node, sync); + const ObjectID oid = node->get_instance_id(); + sync->connect("visibility_changed", callable_mp(this, &SceneReplicationInterface::_visibility_changed), varray(oid)); + if (multiplayer->has_multiplayer_peer() && sync->is_multiplayer_authority()) { + _update_sync_visibility(0, oid); + } + // Try to apply initial state if spawning (hack to apply if before ready). if (pending_spawn == p_obj->get_instance_id()) { pending_spawn = ObjectID(); // Make sure this only happens once. @@ -127,9 +150,6 @@ Error SceneReplicationInterface::on_replication_start(Object *p_obj, Variant p_c ERR_FAIL_COND_V(err, err); err = MultiplayerSynchronizer::set_state(props, node, vars); ERR_FAIL_COND_V(err, err); - } else if (multiplayer->has_multiplayer_peer() && sync->is_multiplayer_authority()) { - // Either it's a spawn or a static sync, in any case add it to the list of known nodes. - rep_state->peer_add_node(0, p_obj->get_instance_id()); } return OK; } @@ -138,10 +158,103 @@ Error SceneReplicationInterface::on_replication_stop(Object *p_obj, Variant p_co Node *node = Object::cast_to<Node>(p_obj); ERR_FAIL_COND_V(!node || p_config.get_type() != Variant::OBJECT, ERR_INVALID_PARAMETER); MultiplayerSynchronizer *sync = Object::cast_to<MultiplayerSynchronizer>(p_config.get_validated_object()); - ERR_FAIL_COND_V(!p_obj || !sync, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(!sync, ERR_INVALID_PARAMETER); + sync->disconnect("visibility_changed", callable_mp(this, &SceneReplicationInterface::_visibility_changed)); return rep_state->config_del_sync(node, sync); } +void SceneReplicationInterface::_visibility_changed(int p_peer, ObjectID p_oid) { + if (rep_state->is_spawned_node(p_oid)) { + _update_spawn_visibility(p_peer, p_oid); + } + if (rep_state->is_synced_node(p_oid)) { + _update_sync_visibility(p_peer, p_oid); + } +} + +Error SceneReplicationInterface::_update_sync_visibility(int p_peer, const ObjectID &p_oid) { + MultiplayerSynchronizer *sync = rep_state->get_synchronizer(p_oid); + ERR_FAIL_COND_V(!sync || !sync->is_multiplayer_authority(), ERR_BUG); + bool is_visible = sync->is_visible_to(p_peer); + if (p_peer == 0) { + for (int pid : rep_state->get_peers()) { + // Might be visible to this specific peer. + is_visible = is_visible || sync->is_visible_to(pid); + if (rep_state->is_peer_sync(pid, p_oid) == is_visible) { + continue; + } + if (is_visible) { + rep_state->peer_add_sync(pid, p_oid); + } else { + rep_state->peer_del_sync(pid, p_oid); + } + } + return OK; + } else { + if (is_visible == rep_state->is_peer_sync(p_peer, p_oid)) { + return OK; + } + if (is_visible) { + return rep_state->peer_add_sync(p_peer, p_oid); + } else { + return rep_state->peer_del_sync(p_peer, p_oid); + } + } +} + +Error SceneReplicationInterface::_update_spawn_visibility(int p_peer, const ObjectID &p_oid) { + MultiplayerSpawner *spawner = rep_state->get_spawner(p_oid); + MultiplayerSynchronizer *sync = rep_state->get_synchronizer(p_oid); + Node *node = Object::cast_to<Node>(ObjectDB::get_instance(p_oid)); + ERR_FAIL_COND_V(!node || !spawner || !spawner->is_multiplayer_authority(), ERR_BUG); + bool is_visible = !sync || sync->is_visible_to(p_peer); + // Spawn (and despawn) when needed. + HashSet<int> to_spawn; + HashSet<int> to_despawn; + if (p_peer) { + if (is_visible == rep_state->is_peer_spawn(p_peer, p_oid)) { + return OK; + } + if (is_visible) { + to_spawn.insert(p_peer); + } else { + to_despawn.insert(p_peer); + } + } else { + // Check visibility for each peers. + for (int pid : rep_state->get_peers()) { + bool peer_visible = is_visible || sync->is_visible_to(pid); + if (peer_visible == rep_state->is_peer_spawn(pid, p_oid)) { + continue; + } + if (peer_visible) { + to_spawn.insert(pid); + } else { + to_despawn.insert(pid); + } + } + } + if (to_spawn.size()) { + int len = 0; + _make_spawn_packet(node, len); + for (int pid : to_spawn) { + int path_id; + multiplayer->send_object_cache(spawner, pid, path_id); + _send_raw(packet_cache.ptr(), len, pid, true); + rep_state->peer_add_spawn(pid, p_oid); + } + } + if (to_despawn.size()) { + int len = 0; + _make_despawn_packet(node, len); + for (int pid : to_despawn) { + rep_state->peer_del_spawn(pid, p_oid); + _send_raw(packet_cache.ptr(), len, pid, true); + } + } + return OK; +} + Error SceneReplicationInterface::_send_raw(const uint8_t *p_buffer, int p_size, int p_peer, bool p_reliable) { ERR_FAIL_COND_V(!p_buffer || p_size < 1, ERR_INVALID_PARAMETER); ERR_FAIL_COND_V(!multiplayer, ERR_UNCONFIGURED); @@ -158,18 +271,20 @@ Error SceneReplicationInterface::_send_raw(const uint8_t *p_buffer, int p_size, return peer->put_packet(p_buffer, p_size); } -Error SceneReplicationInterface::_send_spawn(Node *p_node, MultiplayerSpawner *p_spawner, int p_peer) { - ERR_FAIL_COND_V(p_peer < 0, ERR_BUG); +Error SceneReplicationInterface::_make_spawn_packet(Node *p_node, int &r_len) { ERR_FAIL_COND_V(!multiplayer, ERR_BUG); - ERR_FAIL_COND_V(!p_spawner || !p_node, ERR_BUG); const ObjectID oid = p_node->get_instance_id(); - uint32_t nid = rep_state->ensure_net_id(oid); + MultiplayerSpawner *spawner = rep_state->get_spawner(oid); + ERR_FAIL_COND_V(!spawner || !p_node, ERR_BUG); + + uint32_t nid = rep_state->get_net_id(oid); + ERR_FAIL_COND_V(!nid, ERR_UNCONFIGURED); // Prepare custom arg and scene_id - uint8_t scene_id = p_spawner->find_spawnable_scene_index_from_object(oid); + uint8_t scene_id = spawner->find_spawnable_scene_index_from_object(oid); bool is_custom = scene_id == MultiplayerSpawner::INVALID_ID; - Variant spawn_arg = p_spawner->get_spawn_argument(oid); + Variant spawn_arg = spawner->get_spawn_argument(oid); int spawn_arg_size = 0; if (is_custom) { Error err = MultiplayerAPI::encode_and_compress_variant(spawn_arg, nullptr, spawn_arg_size, false); @@ -181,7 +296,8 @@ Error SceneReplicationInterface::_send_spawn(Node *p_node, MultiplayerSpawner *p Vector<Variant> state_vars; Vector<const Variant *> state_varp; MultiplayerSynchronizer *synchronizer = rep_state->get_synchronizer(oid); - if (synchronizer && synchronizer->get_replication_config().is_valid()) { + if (synchronizer) { + ERR_FAIL_COND_V(synchronizer->get_replication_config().is_null(), ERR_BUG); const List<NodePath> props = synchronizer->get_replication_config()->get_spawn_properties(); Error err = MultiplayerSynchronizer::get_state(props, p_node, state_vars, state_varp); ERR_FAIL_COND_V_MSG(err != OK, err, "Unable to retrieve spawn state."); @@ -189,13 +305,8 @@ Error SceneReplicationInterface::_send_spawn(Node *p_node, MultiplayerSpawner *p ERR_FAIL_COND_V_MSG(err != OK, err, "Unable to encode spawn state."); } - // Prepare simplified path. - NodePath rel_path = multiplayer->get_root_path().rel_path_to(p_spawner->get_path()); - - int path_id = 0; - multiplayer->send_object_cache(p_spawner, rel_path, p_peer, path_id); - - // Encode name and parent ID. + // Encode scene ID, path ID, net ID, node name. + int path_id = multiplayer->make_object_cache(spawner); CharString cname = p_node->get_name().operator String().utf8(); int nlen = encode_cstring(cname.get_data(), nullptr); MAKE_ROOM(1 + 1 + 4 + 4 + 4 + nlen + (is_custom ? 4 + spawn_arg_size : 0) + state_size); @@ -220,12 +331,11 @@ Error SceneReplicationInterface::_send_spawn(Node *p_node, MultiplayerSpawner *p ERR_FAIL_COND_V(err, err); ofs += state_size; } - Error err = _send_raw(ptr, ofs, p_peer, true); - ERR_FAIL_COND_V(err, err); - return rep_state->peer_add_node(p_peer, oid); + r_len = ofs; + return OK; } -Error SceneReplicationInterface::_send_despawn(Node *p_node, int p_peer) { +Error SceneReplicationInterface::_make_despawn_packet(Node *p_node, int &r_len) { const ObjectID oid = p_node->get_instance_id(); MAKE_ROOM(5); uint8_t *ptr = packet_cache.ptrw(); @@ -233,9 +343,8 @@ Error SceneReplicationInterface::_send_despawn(Node *p_node, int p_peer) { int ofs = 1; uint32_t nid = rep_state->get_net_id(oid); ofs += encode_uint32(nid, &ptr[ofs]); - Error err = _send_raw(ptr, ofs, p_peer, true); - ERR_FAIL_COND_V(err, err); - return rep_state->peer_del_node(p_peer, oid); + r_len = ofs; + return OK; } Error SceneReplicationInterface::on_spawn_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len) { @@ -316,8 +425,8 @@ Error SceneReplicationInterface::on_despawn_receive(int p_from, const uint8_t *p } void SceneReplicationInterface::_send_sync(int p_peer, uint64_t p_msec) { - const HashSet<ObjectID> &known = rep_state->get_known_nodes(p_peer); - if (known.is_empty()) { + const HashSet<ObjectID> &to_sync = rep_state->get_peer_sync_nodes(p_peer); + if (to_sync.is_empty()) { return; } MAKE_ROOM(sync_mtu); @@ -327,14 +436,29 @@ void SceneReplicationInterface::_send_sync(int p_peer, uint64_t p_msec) { ofs += encode_uint16(rep_state->peer_sync_next(p_peer), &ptr[1]); // Can only send updates for already notified nodes. // This is a lazy implementation, we could optimize much more here with by grouping by replication config. - for (const ObjectID &oid : known) { + for (const ObjectID &oid : to_sync) { if (!rep_state->update_sync_time(oid, p_msec)) { continue; // nothing to sync. } MultiplayerSynchronizer *sync = rep_state->get_synchronizer(oid); - ERR_CONTINUE(!sync); + ERR_CONTINUE(!sync || !sync->get_replication_config().is_valid()); Node *node = rep_state->get_node(oid); ERR_CONTINUE(!node); + uint32_t net_id = rep_state->get_net_id(oid); + if (net_id == 0 || (net_id & 0x80000000)) { + int path_id = 0; + bool verified = multiplayer->send_object_cache(sync, p_peer, path_id); + ERR_CONTINUE_MSG(path_id < 0, "This should never happen!"); + if (net_id == 0) { + // First time path based ID. + net_id = path_id | 0x80000000; + rep_state->set_net_id(oid, net_id | 0x80000000); + } + if (!verified) { + // The path based sync is not yet confirmed, skipping. + continue; + } + } int size; Vector<Variant> vars; Vector<const Variant *> varp; @@ -351,16 +475,6 @@ void SceneReplicationInterface::_send_sync(int p_peer, uint64_t p_msec) { ofs = 3; } if (size) { - uint32_t net_id = rep_state->get_net_id(oid); - if (net_id == 0 || (net_id & 0x80000000)) { - // First time path based ID. - NodePath rel_path = multiplayer->get_root_path().rel_path_to(sync->get_path()); - int path_id = 0; - multiplayer->send_object_cache(sync, rel_path, p_peer, path_id); - ERR_CONTINUE_MSG(net_id && net_id != (uint32_t(path_id) | 0x80000000), "This should never happen!"); - net_id = path_id; - rep_state->set_net_id(oid, net_id | 0x80000000); - } ofs += encode_uint32(rep_state->get_net_id(oid), &ptr[ofs]); ofs += encode_uint32(size, &ptr[ofs]); MultiplayerAPI::encode_and_compress_variants(varp.ptrw(), varp.size(), &ptr[ofs], size); diff --git a/scene/multiplayer/scene_replication_interface.h b/scene/multiplayer/scene_replication_interface.h index 60ac95c93c..ad3a3be979 100644 --- a/scene/multiplayer/scene_replication_interface.h +++ b/scene/multiplayer/scene_replication_interface.h @@ -40,10 +40,13 @@ class SceneReplicationInterface : public MultiplayerReplicationInterface { private: void _send_sync(int p_peer, uint64_t p_msec); - Error _send_spawn(Node *p_node, MultiplayerSpawner *p_spawner, int p_peer); - Error _send_despawn(Node *p_node, int p_peer); + Error _make_spawn_packet(Node *p_node, int &r_len); + Error _make_despawn_packet(Node *p_node, int &r_len); Error _send_raw(const uint8_t *p_buffer, int p_size, int p_peer, bool p_reliable); + void _visibility_changed(int p_peer, ObjectID p_oid); + Error _update_sync_visibility(int p_peer, const ObjectID &p_oid); + Error _update_spawn_visibility(int p_peer, const ObjectID &p_oid); void _free_remotes(int p_peer); Ref<SceneReplicationState> rep_state; diff --git a/scene/multiplayer/scene_replication_state.cpp b/scene/multiplayer/scene_replication_state.cpp index 937b30cb36..f6a51ff9c7 100644 --- a/scene/multiplayer/scene_replication_state.cpp +++ b/scene/multiplayer/scene_replication_state.cpp @@ -56,7 +56,8 @@ void SceneReplicationState::_untrack(const ObjectID &p_id) { // If we spawned or synced it, we need to remove it from any peer it was sent to. if (net_id || peer == 0) { for (KeyValue<int, PeerInfo> &E : peers_info) { - E.value.known_nodes.erase(p_id); + E.value.sync_nodes.erase(p_id); + E.value.spawn_nodes.erase(p_id); } } } @@ -93,11 +94,6 @@ bool SceneReplicationState::update_sync_time(const ObjectID &p_id, uint64_t p_ms return false; } -const HashSet<ObjectID> SceneReplicationState::get_known_nodes(int p_peer) { - ERR_FAIL_COND_V(!peers_info.has(p_peer), HashSet<ObjectID>()); - return peers_info[p_peer].known_nodes; -} - uint32_t SceneReplicationState::get_net_id(const ObjectID &p_id) const { const TrackedNode *tnode = tracked_nodes.getptr(p_id); ERR_FAIL_COND_V(!tnode, 0); @@ -147,8 +143,6 @@ Error SceneReplicationState::config_add_spawn(Node *p_node, MultiplayerSpawner * ERR_FAIL_COND_V(tobj.spawner != ObjectID(), ERR_ALREADY_IN_USE); tobj.spawner = p_spawner->get_instance_id(); spawned_nodes.insert(oid); - // The spawner may be notified after the synchronizer. - path_only_nodes.erase(oid); return OK; } @@ -159,6 +153,9 @@ Error SceneReplicationState::config_del_spawn(Node *p_node, MultiplayerSpawner * ERR_FAIL_COND_V(tobj.spawner != p_spawner->get_instance_id(), ERR_INVALID_PARAMETER); tobj.spawner = ObjectID(); spawned_nodes.erase(oid); + for (KeyValue<int, PeerInfo> &E : peers_info) { + E.value.spawn_nodes.erase(oid); + } return OK; } @@ -167,10 +164,7 @@ Error SceneReplicationState::config_add_sync(Node *p_node, MultiplayerSynchroniz TrackedNode &tobj = _track(oid); ERR_FAIL_COND_V(tobj.synchronizer != ObjectID(), ERR_ALREADY_IN_USE); tobj.synchronizer = p_sync->get_instance_id(); - // If it doesn't have a spawner, we might need to assign ID for this node using it's path. - if (tobj.spawner.is_null()) { - path_only_nodes.insert(oid); - } + synced_nodes.insert(oid); return OK; } @@ -180,38 +174,57 @@ Error SceneReplicationState::config_del_sync(Node *p_node, MultiplayerSynchroniz TrackedNode &tobj = _track(oid); ERR_FAIL_COND_V(tobj.synchronizer != p_sync->get_instance_id(), ERR_INVALID_PARAMETER); tobj.synchronizer = ObjectID(); - if (path_only_nodes.has(oid)) { - p_node->disconnect(SceneStringNames::get_singleton()->tree_exited, callable_mp(this, &SceneReplicationState::_untrack)); - _untrack(oid); - path_only_nodes.erase(oid); + synced_nodes.erase(oid); + for (KeyValue<int, PeerInfo> &E : peers_info) { + E.value.sync_nodes.erase(oid); } return OK; } -Error SceneReplicationState::peer_add_node(int p_peer, const ObjectID &p_id) { - if (p_peer) { - ERR_FAIL_COND_V(!peers_info.has(p_peer), ERR_INVALID_PARAMETER); - peers_info[p_peer].known_nodes.insert(p_id); - } else { - for (KeyValue<int, PeerInfo> &E : peers_info) { - E.value.known_nodes.insert(p_id); - } - } +Error SceneReplicationState::peer_add_sync(int p_peer, const ObjectID &p_id) { + ERR_FAIL_COND_V(!peers_info.has(p_peer), ERR_INVALID_PARAMETER); + peers_info[p_peer].sync_nodes.insert(p_id); return OK; } -Error SceneReplicationState::peer_del_node(int p_peer, const ObjectID &p_id) { - if (p_peer) { - ERR_FAIL_COND_V(!peers_info.has(p_peer), ERR_INVALID_PARAMETER); - peers_info[p_peer].known_nodes.erase(p_id); - } else { - for (KeyValue<int, PeerInfo> &E : peers_info) { - E.value.known_nodes.erase(p_id); - } - } +Error SceneReplicationState::peer_del_sync(int p_peer, const ObjectID &p_id) { + ERR_FAIL_COND_V(!peers_info.has(p_peer), ERR_INVALID_PARAMETER); + peers_info[p_peer].sync_nodes.erase(p_id); return OK; } +const HashSet<ObjectID> SceneReplicationState::get_peer_sync_nodes(int p_peer) { + ERR_FAIL_COND_V(!peers_info.has(p_peer), HashSet<ObjectID>()); + return peers_info[p_peer].sync_nodes; +} + +bool SceneReplicationState::is_peer_sync(int p_peer, const ObjectID &p_id) const { + ERR_FAIL_COND_V(!peers_info.has(p_peer), false); + return peers_info[p_peer].sync_nodes.has(p_id); +} + +Error SceneReplicationState::peer_add_spawn(int p_peer, const ObjectID &p_id) { + ERR_FAIL_COND_V(!peers_info.has(p_peer), ERR_INVALID_PARAMETER); + peers_info[p_peer].spawn_nodes.insert(p_id); + return OK; +} + +Error SceneReplicationState::peer_del_spawn(int p_peer, const ObjectID &p_id) { + ERR_FAIL_COND_V(!peers_info.has(p_peer), ERR_INVALID_PARAMETER); + peers_info[p_peer].spawn_nodes.erase(p_id); + return OK; +} + +const HashSet<ObjectID> SceneReplicationState::get_peer_spawn_nodes(int p_peer) { + ERR_FAIL_COND_V(!peers_info.has(p_peer), HashSet<ObjectID>()); + return peers_info[p_peer].spawn_nodes; +} + +bool SceneReplicationState::is_peer_spawn(int p_peer, const ObjectID &p_id) const { + ERR_FAIL_COND_V(!peers_info.has(p_peer), false); + return peers_info[p_peer].spawn_nodes.has(p_id); +} + Node *SceneReplicationState::peer_get_remote(int p_peer, uint32_t p_net_id) { PeerInfo *info = peers_info.getptr(p_peer); return info && info->recv_nodes.has(p_net_id) ? Object::cast_to<Node>(ObjectDB::get_instance(info->recv_nodes[p_net_id])) : nullptr; diff --git a/scene/multiplayer/scene_replication_state.h b/scene/multiplayer/scene_replication_state.h index 60a6c5d70c..7973b5c904 100644 --- a/scene/multiplayer/scene_replication_state.h +++ b/scene/multiplayer/scene_replication_state.h @@ -62,7 +62,8 @@ private: }; struct PeerInfo { - HashSet<ObjectID> known_nodes; + HashSet<ObjectID> sync_nodes; + HashSet<ObjectID> spawn_nodes; HashMap<uint32_t, ObjectID> recv_nodes; uint16_t last_sent_sync = 0; uint16_t last_recv_sync = 0; @@ -73,7 +74,7 @@ private: HashMap<ObjectID, TrackedNode> tracked_nodes; HashMap<int, PeerInfo> peers_info; HashSet<ObjectID> spawned_nodes; - HashSet<ObjectID> path_only_nodes; + HashSet<ObjectID> synced_nodes; TrackedNode &_track(const ObjectID &p_id); void _untrack(const ObjectID &p_id); @@ -82,7 +83,9 @@ private: public: const HashSet<int> get_peers() const { return known_peers; } const HashSet<ObjectID> &get_spawned_nodes() const { return spawned_nodes; } - const HashSet<ObjectID> &get_path_only_nodes() const { return path_only_nodes; } + bool is_spawned_node(const ObjectID &p_id) const { return spawned_nodes.has(p_id); } + const HashSet<ObjectID> &get_synced_nodes() const { return synced_nodes; } + bool is_synced_node(const ObjectID &p_id) const { return synced_nodes.has(p_id); } MultiplayerSynchronizer *get_synchronizer(const ObjectID &p_id) { return tracked_nodes.has(p_id) ? tracked_nodes[p_id].get_synchronizer() : nullptr; } MultiplayerSpawner *get_spawner(const ObjectID &p_id) { return tracked_nodes.has(p_id) ? tracked_nodes[p_id].get_spawner() : nullptr; } @@ -90,7 +93,6 @@ public: bool update_last_node_sync(const ObjectID &p_id, uint16_t p_time); bool update_sync_time(const ObjectID &p_id, uint64_t p_msec); - const HashSet<ObjectID> get_known_nodes(int p_peer); uint32_t get_net_id(const ObjectID &p_id) const; void set_net_id(const ObjectID &p_id, uint32_t p_net_id); uint32_t ensure_net_id(const ObjectID &p_id); @@ -104,8 +106,17 @@ public: Error config_add_sync(Node *p_node, MultiplayerSynchronizer *p_sync); Error config_del_sync(Node *p_node, MultiplayerSynchronizer *p_sync); - Error peer_add_node(int p_peer, const ObjectID &p_id); - Error peer_del_node(int p_peer, const ObjectID &p_id); + Error peer_add_sync(int p_peer, const ObjectID &p_id); + Error peer_del_sync(int p_peer, const ObjectID &p_id); + + const HashSet<ObjectID> get_peer_sync_nodes(int p_peer); + bool is_peer_sync(int p_peer, const ObjectID &p_id) const; + + Error peer_add_spawn(int p_peer, const ObjectID &p_id); + Error peer_del_spawn(int p_peer, const ObjectID &p_id); + + const HashSet<ObjectID> get_peer_spawn_nodes(int p_peer); + bool is_peer_spawn(int p_peer, const ObjectID &p_id) const; const HashMap<uint32_t, ObjectID> peer_get_remotes(int p_peer) const; Node *peer_get_remote(int p_peer, uint32_t p_net_id); diff --git a/scene/multiplayer/scene_rpc_interface.cpp b/scene/multiplayer/scene_rpc_interface.cpp index 84700a82f3..144a10c665 100644 --- a/scene/multiplayer/scene_rpc_interface.cpp +++ b/scene/multiplayer/scene_rpc_interface.cpp @@ -302,12 +302,9 @@ void SceneRPCInterface::_send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, con ERR_FAIL_MSG("Attempt to call RPC with unknown peer ID: " + itos(p_to) + "."); } - NodePath from_path = multiplayer->get_root_path().rel_path_to(p_from->get_path()); - ERR_FAIL_COND_MSG(from_path.is_empty(), "Unable to send RPC. Relative path is empty. THIS IS LIKELY A BUG IN THE ENGINE!"); - // See if all peers have cached path (if so, call can be fast). int psc_id; - const bool has_all_peers = multiplayer->send_object_cache(p_from, from_path, p_to, psc_id); + const bool has_all_peers = multiplayer->send_object_cache(p_from, p_to, psc_id); // Create base packet, lots of hardcode because it must be tight. @@ -414,6 +411,7 @@ void SceneRPCInterface::_send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, con // Not all verified path, so send one by one. // Append path at the end, since we will need it for some packets. + NodePath from_path = multiplayer->get_root_path().rel_path_to(p_from->get_path()); CharString pname = String(from_path).utf8(); int path_len = encode_cstring(pname.get_data(), nullptr); MAKE_ROOM(ofs + path_len); diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index f462e36758..b1ef3d0f6f 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -159,6 +159,7 @@ #include "scene/resources/gradient.h" #include "scene/resources/height_map_shape_3d.h" #include "scene/resources/immediate_mesh.h" +#include "scene/resources/label_settings.h" #include "scene/resources/material.h" #include "scene/resources/mesh.h" #include "scene/resources/mesh_data_tool.h" @@ -861,6 +862,8 @@ void register_scene_types() { GDREGISTER_CLASS(Curve); + GDREGISTER_CLASS(LabelSettings); + GDREGISTER_CLASS(SceneReplicationConfig); GDREGISTER_CLASS(TextLine); diff --git a/scene/resources/label_settings.cpp b/scene/resources/label_settings.cpp new file mode 100644 index 0000000000..e8b986b431 --- /dev/null +++ b/scene/resources/label_settings.cpp @@ -0,0 +1,187 @@ +/*************************************************************************/ +/* label_settings.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "label_settings.h" + +#include "core/core_string_names.h" + +void LabelSettings::_font_changed() { + emit_changed(); +} + +void LabelSettings::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_line_spacing", "spacing"), &LabelSettings::set_line_spacing); + ClassDB::bind_method(D_METHOD("get_line_spacing"), &LabelSettings::get_line_spacing); + + ClassDB::bind_method(D_METHOD("set_font", "font"), &LabelSettings::set_font); + ClassDB::bind_method(D_METHOD("get_font"), &LabelSettings::get_font); + + ClassDB::bind_method(D_METHOD("set_font_size", "size"), &LabelSettings::set_font_size); + ClassDB::bind_method(D_METHOD("get_font_size"), &LabelSettings::get_font_size); + + ClassDB::bind_method(D_METHOD("set_font_color", "color"), &LabelSettings::set_font_color); + ClassDB::bind_method(D_METHOD("get_font_color"), &LabelSettings::get_font_color); + + ClassDB::bind_method(D_METHOD("set_outline_size", "size"), &LabelSettings::set_outline_size); + ClassDB::bind_method(D_METHOD("get_outline_size"), &LabelSettings::get_outline_size); + + ClassDB::bind_method(D_METHOD("set_outline_color", "color"), &LabelSettings::set_outline_color); + ClassDB::bind_method(D_METHOD("get_outline_color"), &LabelSettings::get_outline_color); + + ClassDB::bind_method(D_METHOD("set_shadow_size", "size"), &LabelSettings::set_shadow_size); + ClassDB::bind_method(D_METHOD("get_shadow_size"), &LabelSettings::get_shadow_size); + + ClassDB::bind_method(D_METHOD("set_shadow_color", "color"), &LabelSettings::set_shadow_color); + ClassDB::bind_method(D_METHOD("get_shadow_color"), &LabelSettings::get_shadow_color); + + ClassDB::bind_method(D_METHOD("set_shadow_offset", "offset"), &LabelSettings::set_shadow_offset); + ClassDB::bind_method(D_METHOD("get_shadow_offset"), &LabelSettings::get_shadow_offset); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "line_spacing", PROPERTY_HINT_NONE, "suffix:px"), "set_line_spacing", "get_line_spacing"); + + ADD_GROUP("Font", "font"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "font", PROPERTY_HINT_RESOURCE_TYPE, "Font"), "set_font", "get_font"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "font_size", PROPERTY_HINT_RANGE, "1,1024,1,or_greater,suffix:px"), "set_font_size", "get_font_size"); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "font_color"), "set_font_color", "get_font_color"); + + ADD_GROUP("Outline", "outline"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "outline_size", PROPERTY_HINT_RANGE, "0,127,1,or_greater,suffix:px"), "set_outline_size", "get_outline_size"); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "outline_color"), "set_outline_color", "get_outline_color"); + + ADD_GROUP("Shadow", "shadow"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "shadow_size", PROPERTY_HINT_RANGE, "0,127,1,or_greater,suffix:px"), "set_shadow_size", "get_shadow_size"); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "shadow_color"), "set_shadow_color", "get_shadow_color"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "shadow_offset", PROPERTY_HINT_NONE, "suffix:px"), "set_shadow_offset", "get_shadow_offset"); +} + +void LabelSettings::set_line_spacing(real_t p_spacing) { + if (line_spacing != p_spacing) { + line_spacing = p_spacing; + emit_changed(); + } +} + +real_t LabelSettings::get_line_spacing() const { + return line_spacing; +} + +void LabelSettings::set_font(const Ref<Font> &p_font) { + if (font != p_font) { + if (font.is_valid()) { + font->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &LabelSettings::_font_changed)); + } + font = p_font; + if (font.is_valid()) { + font->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &LabelSettings::_font_changed), varray(), CONNECT_REFERENCE_COUNTED); + } + emit_changed(); + } +} + +Ref<Font> LabelSettings::get_font() const { + return font; +} + +void LabelSettings::set_font_size(int p_size) { + if (font_size != p_size) { + font_size = p_size; + emit_changed(); + } +} + +int LabelSettings::get_font_size() const { + return font_size; +} + +void LabelSettings::set_font_color(const Color &p_color) { + if (font_color != p_color) { + font_color = p_color; + emit_changed(); + } +} + +Color LabelSettings::get_font_color() const { + return font_color; +} + +void LabelSettings::set_outline_size(int p_size) { + if (outline_size != p_size) { + outline_size = p_size; + emit_changed(); + } +} + +int LabelSettings::get_outline_size() const { + return outline_size; +} + +void LabelSettings::set_outline_color(const Color &p_color) { + if (outline_color != p_color) { + outline_color = p_color; + emit_changed(); + } +} + +Color LabelSettings::get_outline_color() const { + return outline_color; +} + +void LabelSettings::set_shadow_size(int p_size) { + if (shadow_size != p_size) { + shadow_size = p_size; + emit_changed(); + } +} + +int LabelSettings::get_shadow_size() const { + return shadow_size; +} + +void LabelSettings::set_shadow_color(const Color &p_color) { + if (shadow_color != p_color) { + shadow_color = p_color; + emit_changed(); + } +} + +Color LabelSettings::get_shadow_color() const { + return shadow_color; +} + +void LabelSettings::set_shadow_offset(const Vector2 &p_offset) { + if (shadow_offset != p_offset) { + shadow_offset = p_offset; + emit_changed(); + } +} + +Vector2 LabelSettings::get_shadow_offset() const { + return shadow_offset; +} diff --git a/scene/resources/label_settings.h b/scene/resources/label_settings.h new file mode 100644 index 0000000000..d2644a7484 --- /dev/null +++ b/scene/resources/label_settings.h @@ -0,0 +1,89 @@ +/*************************************************************************/ +/* label_settings.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef LABEL_SETTINGS_H +#define LABEL_SETTINGS_H + +#include "core/io/resource.h" +#include "font.h" + +/*************************************************************************/ + +class LabelSettings : public Resource { + GDCLASS(LabelSettings, Resource); + + real_t line_spacing = 0; + + Ref<Font> font; + int font_size = Font::DEFAULT_FONT_SIZE; + Color font_color = Color(0.875, 0.875, 0.875); + + int outline_size = 0; + Color outline_color = Color(1, 1, 1); + + int shadow_size = 0; + Color shadow_color = Color(1, 1, 1); + Vector2 shadow_offset = Vector2(1, 1); + + void _font_changed(); + +protected: + static void _bind_methods(); + +public: + void set_line_spacing(real_t p_spacing); + real_t get_line_spacing() const; + + void set_font(const Ref<Font> &p_font); + Ref<Font> get_font() const; + + void set_font_size(int p_size); + int get_font_size() const; + + void set_font_color(const Color &p_color); + Color get_font_color() const; + + void set_outline_size(int p_size); + int get_outline_size() const; + + void set_outline_color(const Color &p_color); + Color get_outline_color() const; + + void set_shadow_size(int p_size); + int get_shadow_size() const; + + void set_shadow_color(const Color &p_color); + Color get_shadow_color() const; + + void set_shadow_offset(const Vector2 &p_offset); + Vector2 get_shadow_offset() const; +}; + +#endif // LABEL_SETTINGS_H diff --git a/scene/resources/text_line.cpp b/scene/resources/text_line.cpp index 4e7ec9315a..823d742d72 100644 --- a/scene/resources/text_line.cpp +++ b/scene/resources/text_line.cpp @@ -74,7 +74,7 @@ void TextLine::_bind_methods() { ClassDB::bind_method(D_METHOD("set_flags", "flags"), &TextLine::set_flags); ClassDB::bind_method(D_METHOD("get_flags"), &TextLine::get_flags); - ADD_PROPERTY(PropertyInfo(Variant::INT, "flags", PROPERTY_HINT_FLAGS, "Kashida Justification,Word Justication,Trim Edge Spaces After Justication,Justify Only After Last Tab,Constrain Ellipsis"), "set_flags", "get_flags"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "flags", PROPERTY_HINT_FLAGS, "Kashida Justification,Word Justification,Trim Edge Spaces After Justification,Justify Only After Last Tab,Constrain Ellipsis"), "set_flags", "get_flags"); ClassDB::bind_method(D_METHOD("set_text_overrun_behavior", "overrun_behavior"), &TextLine::set_text_overrun_behavior); ClassDB::bind_method(D_METHOD("get_text_overrun_behavior"), &TextLine::get_text_overrun_behavior); diff --git a/scene/resources/text_paragraph.cpp b/scene/resources/text_paragraph.cpp index 2d9e0066e1..43d3f329fa 100644 --- a/scene/resources/text_paragraph.cpp +++ b/scene/resources/text_paragraph.cpp @@ -77,12 +77,12 @@ void TextParagraph::_bind_methods() { ClassDB::bind_method(D_METHOD("set_break_flags", "flags"), &TextParagraph::set_break_flags); ClassDB::bind_method(D_METHOD("get_break_flags"), &TextParagraph::get_break_flags); - ADD_PROPERTY(PropertyInfo(Variant::INT, "break_flags", PROPERTY_HINT_FLAGS, "Mandatory,Word Bouns,Grapheme Bound,Adaptive"), "set_break_flags", "get_break_flags"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "break_flags", PROPERTY_HINT_FLAGS, "Mandatory,Word Bound,Grapheme Bound,Adaptive"), "set_break_flags", "get_break_flags"); ClassDB::bind_method(D_METHOD("set_justification_flags", "flags"), &TextParagraph::set_justification_flags); ClassDB::bind_method(D_METHOD("get_justification_flags"), &TextParagraph::get_justification_flags); - ADD_PROPERTY(PropertyInfo(Variant::INT, "justification_flags", PROPERTY_HINT_FLAGS, "Kashida Justification,Word Justication,Trim Edge Spaces After Justication,Justify Only After Last Tab,Constrain Ellipsis"), "set_justification_flags", "get_justification_flags"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "justification_flags", PROPERTY_HINT_FLAGS, "Kashida Justification,Word Justification,Trim Edge Spaces After Justification,Justify Only After Last Tab,Constrain Ellipsis"), "set_justification_flags", "get_justification_flags"); ClassDB::bind_method(D_METHOD("set_text_overrun_behavior", "overrun_behavior"), &TextParagraph::set_text_overrun_behavior); ClassDB::bind_method(D_METHOD("get_text_overrun_behavior"), &TextParagraph::get_text_overrun_behavior); diff --git a/scene/resources/texture.cpp b/scene/resources/texture.cpp index 21ae62c92e..0aefe34f7d 100644 --- a/scene/resources/texture.cpp +++ b/scene/resources/texture.cpp @@ -323,6 +323,7 @@ void ImageTexture::_bind_methods() { ClassDB::bind_static_method("ImageTexture", D_METHOD("create_from_image", "image"), &ImageTexture::create_from_image); ClassDB::bind_method(D_METHOD("get_format"), &ImageTexture::get_format); + ClassDB::bind_method(D_METHOD("set_image", "image"), &ImageTexture::set_image); ClassDB::bind_method(D_METHOD("update", "image"), &ImageTexture::update); ClassDB::bind_method(D_METHOD("set_size_override", "size"), &ImageTexture::set_size_override); } |