summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/extension/gdextension.cpp4
-rw-r--r--core/extension/gdextension_interface.h20
-rw-r--r--core/io/image.cpp1
-rw-r--r--core/math/a_star_grid_2d.cpp76
-rw-r--r--core/math/a_star_grid_2d.h19
-rw-r--r--core/object/make_virtuals.py27
-rw-r--r--core/object/object.h2
-rw-r--r--core/templates/cowdata.h7
-rw-r--r--doc/classes/Image.xml9
-rw-r--r--doc/classes/PhysicsServer3DRenderingServerHandler.xml32
-rw-r--r--drivers/vulkan/rendering_device_vulkan.cpp11
-rw-r--r--editor/animation_track_editor.cpp2
-rw-r--r--editor/editor_audio_buses.cpp4
-rw-r--r--editor/editor_command_palette.cpp10
-rw-r--r--editor/editor_command_palette.h1
-rw-r--r--editor/editor_node.cpp4
-rw-r--r--editor/filesystem_dock.cpp2
-rw-r--r--editor/gui/editor_spin_slider.cpp2
-rw-r--r--editor/plugins/abstract_polygon_2d_editor.cpp2
-rw-r--r--editor/plugins/animation_state_machine_editor.cpp4
-rw-r--r--editor/plugins/canvas_item_editor_plugin.cpp12
-rw-r--r--editor/plugins/curve_editor_plugin.cpp4
-rw-r--r--editor/plugins/gradient_editor.cpp2
-rw-r--r--editor/plugins/gradient_texture_2d_editor_plugin.cpp4
-rw-r--r--editor/plugins/node_3d_editor_plugin.cpp5
-rw-r--r--editor/plugins/path_3d_editor_plugin.cpp2
-rw-r--r--editor/plugins/polygon_2d_editor_plugin.cpp3
-rw-r--r--editor/plugins/polygon_3d_editor_plugin.cpp4
-rw-r--r--editor/plugins/script_text_editor.cpp19
-rw-r--r--editor/plugins/sprite_frames_editor_plugin.cpp5
-rw-r--r--editor/plugins/tiles/tile_map_editor.cpp7
-rw-r--r--editor/plugins/tiles/tile_set_atlas_source_editor.cpp4
-rw-r--r--editor/project_manager.cpp4
-rw-r--r--editor/scene_tree_dock.cpp2
-rw-r--r--misc/extension_api_validation/4.1-stable.expected8
-rw-r--r--modules/gdscript/editor/gdscript_highlighter.cpp43
-rw-r--r--modules/gdscript/editor/gdscript_highlighter.h1
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp6
-rw-r--r--modules/gdscript/gdscript_editor.cpp1
-rw-r--r--modules/gdscript/gdscript_parser.cpp223
-rw-r--r--modules/gdscript/gdscript_parser.h5
-rw-r--r--modules/gdscript/gdscript_tokenizer.cpp280
-rw-r--r--modules/gdscript/gdscript_tokenizer.h2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.gd17
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.out11
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/typed_array_dont_make_literal_typed_with_weak_type.gd22
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/typed_array_dont_make_literal_typed_with_weak_type.out7
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/bad_r_string_1.gd2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/bad_r_string_1.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/bad_r_string_2.gd2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/bad_r_string_2.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/bad_r_string_3.gd3
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/bad_r_string_3.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/r_strings.gd22
-rw-r--r--modules/gdscript/tests/scripts/parser/features/r_strings.out22
-rw-r--r--modules/gdscript/tests/scripts/utils.notest.gd144
-rw-r--r--modules/gridmap/grid_map.cpp1
-rw-r--r--modules/lightmapper_rd/lightmapper_rd.cpp16
-rw-r--r--modules/mono/csharp_script.cpp14
-rw-r--r--modules/mono/csharp_script.h1
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs34
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs4
-rw-r--r--modules/mono/glue/GodotSharp/.editorconfig7
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs54
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj3
-rw-r--r--modules/mono/mono_gd/gd_mono_cache.cpp1
-rw-r--r--modules/mono/mono_gd/gd_mono_cache.h2
-rw-r--r--modules/regex/doc_classes/RegEx.xml2
-rw-r--r--scene/3d/soft_body_3d.cpp9
-rw-r--r--scene/3d/soft_body_3d.h4
-rw-r--r--scene/gui/code_edit.cpp2
-rw-r--r--scene/gui/graph_edit.cpp8
-rw-r--r--scene/gui/line_edit.cpp8
-rw-r--r--scene/gui/text_edit.cpp12
-rw-r--r--scene/gui/text_edit.h4
-rw-r--r--scene/gui/tree.cpp2
-rw-r--r--scene/resources/syntax_highlighter.cpp2
-rw-r--r--servers/audio/effects/audio_stream_generator.cpp11
-rw-r--r--servers/physics_3d/godot_soft_body_3d.cpp6
-rw-r--r--servers/physics_server_3d.cpp16
-rw-r--r--servers/physics_server_3d.h8
-rw-r--r--servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl11
-rw-r--r--servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl11
84 files changed, 953 insertions, 445 deletions
diff --git a/core/extension/gdextension.cpp b/core/extension/gdextension.cpp
index 3bc7dde10a..bffa0e251f 100644
--- a/core/extension/gdextension.cpp
+++ b/core/extension/gdextension.cpp
@@ -301,6 +301,8 @@ void GDExtension::_register_extension_class(GDExtensionClassLibraryPtr p_library
p_extension_funcs->create_instance_func, // GDExtensionClassCreateInstance create_instance_func; /* this one is mandatory */
p_extension_funcs->free_instance_func, // GDExtensionClassFreeInstance free_instance_func; /* this one is mandatory */
p_extension_funcs->get_virtual_func, // GDExtensionClassGetVirtual get_virtual_func;
+ nullptr, // GDExtensionClassGetVirtualCallData get_virtual_call_data_func;
+ nullptr, // GDExtensionClassCallVirtualWithData call_virtual_func;
p_extension_funcs->get_rid_func, // GDExtensionClassGetRID get_rid;
p_extension_funcs->class_userdata, // void *class_userdata;
};
@@ -375,6 +377,8 @@ void GDExtension::_register_extension_class_internal(GDExtensionClassLibraryPtr
extension->gdextension.create_instance = p_extension_funcs->create_instance_func;
extension->gdextension.free_instance = p_extension_funcs->free_instance_func;
extension->gdextension.get_virtual = p_extension_funcs->get_virtual_func;
+ extension->gdextension.get_virtual_call_data = p_extension_funcs->get_virtual_call_data_func;
+ extension->gdextension.call_virtual_with_data = p_extension_funcs->call_virtual_with_data_func;
extension->gdextension.get_rid = p_extension_funcs->get_rid_func;
ClassDB::register_extension_class(&extension->gdextension);
diff --git a/core/extension/gdextension_interface.h b/core/extension/gdextension_interface.h
index 4214fce5f9..48cb28832a 100644
--- a/core/extension/gdextension_interface.h
+++ b/core/extension/gdextension_interface.h
@@ -265,9 +265,11 @@ typedef void (*GDExtensionClassToString)(GDExtensionClassInstancePtr p_instance,
typedef void (*GDExtensionClassReference)(GDExtensionClassInstancePtr p_instance);
typedef void (*GDExtensionClassUnreference)(GDExtensionClassInstancePtr p_instance);
typedef void (*GDExtensionClassCallVirtual)(GDExtensionClassInstancePtr p_instance, const GDExtensionConstTypePtr *p_args, GDExtensionTypePtr r_ret);
-typedef GDExtensionObjectPtr (*GDExtensionClassCreateInstance)(void *p_userdata);
-typedef void (*GDExtensionClassFreeInstance)(void *p_userdata, GDExtensionClassInstancePtr p_instance);
-typedef GDExtensionClassCallVirtual (*GDExtensionClassGetVirtual)(void *p_userdata, GDExtensionConstStringNamePtr p_name);
+typedef GDExtensionObjectPtr (*GDExtensionClassCreateInstance)(void *p_class_userdata);
+typedef void (*GDExtensionClassFreeInstance)(void *p_class_userdata, GDExtensionClassInstancePtr p_instance);
+typedef GDExtensionClassCallVirtual (*GDExtensionClassGetVirtual)(void *p_class_userdata, GDExtensionConstStringNamePtr p_name);
+typedef void *(*GDExtensionClassGetVirtualCallData)(void *p_class_userdata, GDExtensionConstStringNamePtr p_name);
+typedef void (*GDExtensionClassCallVirtualWithData)(GDExtensionClassInstancePtr p_instance, GDExtensionConstStringNamePtr p_name, void *p_virtual_call_userdata, const GDExtensionConstTypePtr *p_args, GDExtensionTypePtr r_ret);
typedef struct {
GDExtensionBool is_virtual;
@@ -306,7 +308,17 @@ typedef struct {
GDExtensionClassUnreference unreference_func;
GDExtensionClassCreateInstance create_instance_func; // (Default) constructor; mandatory. If the class is not instantiable, consider making it virtual or abstract.
GDExtensionClassFreeInstance free_instance_func; // Destructor; mandatory.
- GDExtensionClassGetVirtual get_virtual_func; // Queries a virtual function by name and returns a callback to invoke the requested virtual function.
+ // Queries a virtual function by name and returns a callback to invoke the requested virtual function.
+ GDExtensionClassGetVirtual get_virtual_func;
+ // Paired with `call_virtual_with_data_func`, this is an alternative to `get_virtual_func` for extensions that
+ // need or benefit from extra data when calling virtual functions.
+ // Returns user data that will be passed to `call_virtual_with_data_func`.
+ // Returning `NULL` from this function signals to Godot that the virtual function is not overridden.
+ // Data returned from this function should be managed by the extension and must be valid until the extension is deinitialized.
+ // You should supply either `get_virtual_func`, or `get_virtual_call_data_func` with `call_virtual_with_data_func`.
+ GDExtensionClassGetVirtualCallData get_virtual_call_data_func;
+ // Used to call virtual functions when `get_virtual_call_data_func` is not null.
+ GDExtensionClassCallVirtualWithData call_virtual_with_data_func;
GDExtensionClassGetRID get_rid_func;
void *class_userdata; // Per-class user data, later accessible in instance bindings.
} GDExtensionClassCreationInfo2;
diff --git a/core/io/image.cpp b/core/io/image.cpp
index 204db687d8..674af6b0a6 100644
--- a/core/io/image.cpp
+++ b/core/io/image.cpp
@@ -3412,6 +3412,7 @@ void Image::_bind_methods() {
ClassDB::bind_method(D_METHOD("convert", "format"), &Image::convert);
+ ClassDB::bind_method(D_METHOD("get_mipmap_count"), &Image::get_mipmap_count);
ClassDB::bind_method(D_METHOD("get_mipmap_offset", "mipmap"), &Image::get_mipmap_offset);
ClassDB::bind_method(D_METHOD("resize_to_po2", "square", "interpolation"), &Image::resize_to_po2, DEFVAL(false), DEFVAL(INTERPOLATE_BILINEAR));
diff --git a/core/math/a_star_grid_2d.cpp b/core/math/a_star_grid_2d.cpp
index 9ba4c2ff9a..9a3744fef8 100644
--- a/core/math/a_star_grid_2d.cpp
+++ b/core/math/a_star_grid_2d.cpp
@@ -32,8 +32,6 @@
#include "core/variant/typed_array.h"
-#define GET_POINT_UNCHECKED(m_id) points[m_id.y - region.position.y][m_id.x - region.position.x]
-
static real_t heuristic_euclidian(const Vector2i &p_from, const Vector2i &p_to) {
real_t dx = (real_t)ABS(p_to.x - p_from.x);
real_t dy = (real_t)ABS(p_to.y - p_from.y);
@@ -110,19 +108,22 @@ Size2 AStarGrid2D::get_cell_size() const {
void AStarGrid2D::update() {
points.clear();
- const int64_t end_x = region.position.x + region.size.width;
- const int64_t end_y = region.position.y + region.size.height;
- for (int64_t y = region.position.y; y < end_y; y++) {
+
+ const int32_t end_x = region.get_end().x;
+ const int32_t end_y = region.get_end().y;
+
+ for (int32_t y = region.position.y; y < end_y; y++) {
LocalVector<Point> line;
- for (int64_t x = region.position.x; x < end_x; x++) {
+ for (int32_t x = region.position.x; x < end_x; x++) {
line.push_back(Point(Vector2i(x, y), offset + Vector2(x, y) * cell_size));
}
points.push_back(line);
}
+
dirty = false;
}
-bool AStarGrid2D::is_in_bounds(int p_x, int p_y) const {
+bool AStarGrid2D::is_in_bounds(int32_t p_x, int32_t p_y) const {
return region.has_point(Vector2i(p_x, p_y));
}
@@ -172,40 +173,38 @@ AStarGrid2D::Heuristic AStarGrid2D::get_default_estimate_heuristic() const {
void AStarGrid2D::set_point_solid(const Vector2i &p_id, bool p_solid) {
ERR_FAIL_COND_MSG(dirty, "Grid is not initialized. Call the update method.");
ERR_FAIL_COND_MSG(!is_in_boundsv(p_id), vformat("Can't set if point is disabled. Point %s out of bounds %s.", p_id, region));
- GET_POINT_UNCHECKED(p_id).solid = p_solid;
+ _get_point_unchecked(p_id)->solid = p_solid;
}
bool AStarGrid2D::is_point_solid(const Vector2i &p_id) const {
ERR_FAIL_COND_V_MSG(dirty, false, "Grid is not initialized. Call the update method.");
ERR_FAIL_COND_V_MSG(!is_in_boundsv(p_id), false, vformat("Can't get if point is disabled. Point %s out of bounds %s.", p_id, region));
- return GET_POINT_UNCHECKED(p_id).solid;
+ return _get_point_unchecked(p_id)->solid;
}
void AStarGrid2D::set_point_weight_scale(const Vector2i &p_id, real_t p_weight_scale) {
ERR_FAIL_COND_MSG(dirty, "Grid is not initialized. Call the update method.");
ERR_FAIL_COND_MSG(!is_in_boundsv(p_id), vformat("Can't set point's weight scale. Point %s out of bounds %s.", p_id, region));
ERR_FAIL_COND_MSG(p_weight_scale < 0.0, vformat("Can't set point's weight scale less than 0.0: %f.", p_weight_scale));
- GET_POINT_UNCHECKED(p_id).weight_scale = p_weight_scale;
+ _get_point_unchecked(p_id)->weight_scale = p_weight_scale;
}
real_t AStarGrid2D::get_point_weight_scale(const Vector2i &p_id) const {
ERR_FAIL_COND_V_MSG(dirty, 0, "Grid is not initialized. Call the update method.");
ERR_FAIL_COND_V_MSG(!is_in_boundsv(p_id), 0, vformat("Can't get point's weight scale. Point %s out of bounds %s.", p_id, region));
- return GET_POINT_UNCHECKED(p_id).weight_scale;
+ return _get_point_unchecked(p_id)->weight_scale;
}
void AStarGrid2D::fill_solid_region(const Rect2i &p_region, bool p_solid) {
ERR_FAIL_COND_MSG(dirty, "Grid is not initialized. Call the update method.");
- Rect2i safe_region = p_region.intersection(region);
- int from_x = safe_region.get_position().x;
- int from_y = safe_region.get_position().y;
- int end_x = safe_region.get_end().x;
- int end_y = safe_region.get_end().y;
+ const Rect2i safe_region = p_region.intersection(region);
+ const int32_t end_x = safe_region.get_end().x;
+ const int32_t end_y = safe_region.get_end().y;
- for (int x = from_x; x < end_x; x++) {
- for (int y = from_y; y < end_y; y++) {
- GET_POINT_UNCHECKED(Vector2i(x, y)).solid = p_solid;
+ for (int32_t y = safe_region.position.y; y < end_y; y++) {
+ for (int32_t x = safe_region.position.x; x < end_x; x++) {
+ _get_point_unchecked(x, y)->solid = p_solid;
}
}
}
@@ -214,14 +213,13 @@ void AStarGrid2D::fill_weight_scale_region(const Rect2i &p_region, real_t p_weig
ERR_FAIL_COND_MSG(dirty, "Grid is not initialized. Call the update method.");
ERR_FAIL_COND_MSG(p_weight_scale < 0.0, vformat("Can't set point's weight scale less than 0.0: %f.", p_weight_scale));
- Rect2i safe_region = p_region.intersection(region);
- int from_x = safe_region.get_position().x;
- int from_y = safe_region.get_position().y;
- int end_x = safe_region.get_end().x;
- int end_y = safe_region.get_end().y;
- for (int x = from_x; x < end_x; x++) {
- for (int y = from_y; y < end_y; y++) {
- GET_POINT_UNCHECKED(Vector2i(x, y)).weight_scale = p_weight_scale;
+ const Rect2i safe_region = p_region.intersection(region);
+ const int32_t end_x = safe_region.get_end().x;
+ const int32_t end_y = safe_region.get_end().y;
+
+ for (int32_t y = safe_region.position.y; y < end_y; y++) {
+ for (int32_t x = safe_region.position.x; x < end_x; x++) {
+ _get_point_unchecked(x, y)->weight_scale = p_weight_scale;
}
}
}
@@ -234,14 +232,14 @@ AStarGrid2D::Point *AStarGrid2D::_jump(Point *p_from, Point *p_to) {
return p_to;
}
- int64_t from_x = p_from->id.x;
- int64_t from_y = p_from->id.y;
+ int32_t from_x = p_from->id.x;
+ int32_t from_y = p_from->id.y;
- int64_t to_x = p_to->id.x;
- int64_t to_y = p_to->id.y;
+ int32_t to_x = p_to->id.x;
+ int32_t to_y = p_to->id.y;
- int64_t dx = to_x - from_x;
- int64_t dy = to_y - from_y;
+ int32_t dx = to_x - from_x;
+ int32_t dy = to_y - from_y;
if (diagonal_mode == DIAGONAL_MODE_ALWAYS || diagonal_mode == DIAGONAL_MODE_AT_LEAST_ONE_WALKABLE) {
if (dx != 0 && dy != 0) {
@@ -516,7 +514,7 @@ void AStarGrid2D::clear() {
Vector2 AStarGrid2D::get_point_position(const Vector2i &p_id) const {
ERR_FAIL_COND_V_MSG(dirty, Vector2(), "Grid is not initialized. Call the update method.");
ERR_FAIL_COND_V_MSG(!is_in_boundsv(p_id), Vector2(), vformat("Can't get point's position. Point %s out of bounds %s.", p_id, region));
- return GET_POINT_UNCHECKED(p_id).pos;
+ return _get_point_unchecked(p_id)->pos;
}
Vector<Vector2> AStarGrid2D::get_point_path(const Vector2i &p_from_id, const Vector2i &p_to_id) {
@@ -542,7 +540,7 @@ Vector<Vector2> AStarGrid2D::get_point_path(const Vector2i &p_from_id, const Vec
}
Point *p = end_point;
- int64_t pc = 1;
+ int32_t pc = 1;
while (p != begin_point) {
pc++;
p = p->prev_point;
@@ -555,7 +553,7 @@ Vector<Vector2> AStarGrid2D::get_point_path(const Vector2i &p_from_id, const Vec
Vector2 *w = path.ptrw();
p = end_point;
- int64_t idx = pc - 1;
+ int32_t idx = pc - 1;
while (p != begin_point) {
w[idx--] = p->pos;
p = p->prev_point;
@@ -590,7 +588,7 @@ TypedArray<Vector2i> AStarGrid2D::get_id_path(const Vector2i &p_from_id, const V
}
Point *p = end_point;
- int64_t pc = 1;
+ int32_t pc = 1;
while (p != begin_point) {
pc++;
p = p->prev_point;
@@ -601,7 +599,7 @@ TypedArray<Vector2i> AStarGrid2D::get_id_path(const Vector2i &p_from_id, const V
{
p = end_point;
- int64_t idx = pc - 1;
+ int32_t idx = pc - 1;
while (p != begin_point) {
path[idx--] = p->id;
p = p->prev_point;
@@ -671,5 +669,3 @@ void AStarGrid2D::_bind_methods() {
BIND_ENUM_CONSTANT(DIAGONAL_MODE_ONLY_IF_NO_OBSTACLES);
BIND_ENUM_CONSTANT(DIAGONAL_MODE_MAX);
}
-
-#undef GET_POINT_UNCHECKED
diff --git a/core/math/a_star_grid_2d.h b/core/math/a_star_grid_2d.h
index ecc9bb01f9..619551b754 100644
--- a/core/math/a_star_grid_2d.h
+++ b/core/math/a_star_grid_2d.h
@@ -105,24 +105,32 @@ private:
uint64_t pass = 1;
private: // Internal routines.
- _FORCE_INLINE_ bool _is_walkable(int64_t p_x, int64_t p_y) const {
+ _FORCE_INLINE_ bool _is_walkable(int32_t p_x, int32_t p_y) const {
if (region.has_point(Vector2i(p_x, p_y))) {
return !points[p_y - region.position.y][p_x - region.position.x].solid;
}
return false;
}
- _FORCE_INLINE_ Point *_get_point(int64_t p_x, int64_t p_y) {
+ _FORCE_INLINE_ Point *_get_point(int32_t p_x, int32_t p_y) {
if (region.has_point(Vector2i(p_x, p_y))) {
return &points[p_y - region.position.y][p_x - region.position.x];
}
return nullptr;
}
- _FORCE_INLINE_ Point *_get_point_unchecked(int64_t p_x, int64_t p_y) {
+ _FORCE_INLINE_ Point *_get_point_unchecked(int32_t p_x, int32_t p_y) {
return &points[p_y - region.position.y][p_x - region.position.x];
}
+ _FORCE_INLINE_ Point *_get_point_unchecked(const Vector2i &p_id) {
+ return &points[p_id.y - region.position.y][p_id.x - region.position.x];
+ }
+
+ _FORCE_INLINE_ const Point *_get_point_unchecked(const Vector2i &p_id) const {
+ return &points[p_id.y - region.position.y][p_id.x - region.position.x];
+ }
+
void _get_nbors(Point *p_point, LocalVector<Point *> &r_nbors);
Point *_jump(Point *p_from, Point *p_to);
bool _solve(Point *p_begin_point, Point *p_end_point);
@@ -151,10 +159,7 @@ public:
void update();
- int get_width() const;
- int get_height() const;
-
- bool is_in_bounds(int p_x, int p_y) const;
+ bool is_in_bounds(int32_t p_x, int32_t p_y) const;
bool is_in_boundsv(const Vector2i &p_id) const;
bool is_dirty() const;
diff --git a/core/object/make_virtuals.py b/core/object/make_virtuals.py
index 38682d6d92..c2e69dc8e2 100644
--- a/core/object/make_virtuals.py
+++ b/core/object/make_virtuals.py
@@ -2,7 +2,7 @@ proto = """
#define GDVIRTUAL$VER($RET m_name $ARG) \\
StringName _gdvirtual_##m_name##_sn = #m_name;\\
mutable bool _gdvirtual_##m_name##_initialized = false;\\
-mutable GDExtensionClassCallVirtual _gdvirtual_##m_name = nullptr;\\
+mutable void* _gdvirtual_##m_name = nullptr;\\
template<bool required>\\
_FORCE_INLINE_ bool _gdvirtual_##m_name##_call($CALLARGS) $CONST { \\
ScriptInstance *_script_instance = ((Object*)(this))->get_script_instance();\\
@@ -16,15 +16,24 @@ _FORCE_INLINE_ bool _gdvirtual_##m_name##_call($CALLARGS) $CONST { \\
} \\
}\\
if (unlikely(_get_extension() && !_gdvirtual_##m_name##_initialized)) {\\
- /* TODO: C-style cast because GDExtensionStringNamePtr's const qualifier is broken (see https://github.com/godotengine/godot/pull/67751) */\\
- _gdvirtual_##m_name = (_get_extension() && _get_extension()->get_virtual) ? _get_extension()->get_virtual(_get_extension()->class_userdata, (GDExtensionStringNamePtr)&_gdvirtual_##m_name##_sn) : (GDExtensionClassCallVirtual) nullptr;\\
+ _gdvirtual_##m_name = nullptr;\\
+ if (_get_extension()->get_virtual_call_data && _get_extension()->call_virtual_with_data) {\\
+ _gdvirtual_##m_name = _get_extension()->get_virtual_call_data(_get_extension()->class_userdata, &_gdvirtual_##m_name##_sn);\\
+ } else if (_get_extension()->get_virtual) {\\
+ _gdvirtual_##m_name = (void *)_get_extension()->get_virtual(_get_extension()->class_userdata, &_gdvirtual_##m_name##_sn);\\
+ }\\
_gdvirtual_##m_name##_initialized = true;\\
}\\
if (_gdvirtual_##m_name) {\\
$CALLPTRARGS\\
$CALLPTRRETDEF\\
- _gdvirtual_##m_name(_get_extension_instance(),$CALLPTRARGPASS,$CALLPTRRETPASS);\\
- $CALLPTRRET\\
+ if (_get_extension()->get_virtual_call_data && _get_extension()->call_virtual_with_data) {\\
+ _get_extension()->call_virtual_with_data(_get_extension_instance(), &_gdvirtual_##m_name##_sn, _gdvirtual_##m_name, $CALLPTRARGPASS,$CALLPTRRETPASS);\\
+ $CALLPTRRET\\
+ } else {\\
+ ((GDExtensionClassCallVirtual)_gdvirtual_##m_name)(_get_extension_instance(),$CALLPTRARGPASS,$CALLPTRRETPASS);\\
+ $CALLPTRRET\\
+ }\\
return true;\\
}\\
\\
@@ -41,8 +50,12 @@ _FORCE_INLINE_ bool _gdvirtual_##m_name##_overridden() const { \\
return _script_instance->has_method(_gdvirtual_##m_name##_sn);\\
}\\
if (unlikely(_get_extension() && !_gdvirtual_##m_name##_initialized)) {\\
- /* TODO: C-style cast because GDExtensionStringNamePtr's const qualifier is broken (see https://github.com/godotengine/godot/pull/67751) */\\
- _gdvirtual_##m_name = (_get_extension() && _get_extension()->get_virtual) ? _get_extension()->get_virtual(_get_extension()->class_userdata, (GDExtensionStringNamePtr)&_gdvirtual_##m_name##_sn) : (GDExtensionClassCallVirtual) nullptr;\\
+ _gdvirtual_##m_name = nullptr;\\
+ if (_get_extension()->get_virtual_call_data && _get_extension()->call_virtual_with_data) {\\
+ _gdvirtual_##m_name = _get_extension()->get_virtual_call_data(_get_extension()->class_userdata, &_gdvirtual_##m_name##_sn);\\
+ } else if (_get_extension()->get_virtual) {\\
+ _gdvirtual_##m_name = (void *)_get_extension()->get_virtual(_get_extension()->class_userdata, &_gdvirtual_##m_name##_sn);\\
+ }\\
_gdvirtual_##m_name##_initialized = true;\\
}\\
if (_gdvirtual_##m_name) {\\
diff --git a/core/object/object.h b/core/object/object.h
index 3a698f7526..7da1c68edf 100644
--- a/core/object/object.h
+++ b/core/object/object.h
@@ -347,6 +347,8 @@ struct ObjectGDExtension {
GDExtensionClassCreateInstance create_instance;
GDExtensionClassFreeInstance free_instance;
GDExtensionClassGetVirtual get_virtual;
+ GDExtensionClassGetVirtualCallData get_virtual_call_data;
+ GDExtensionClassCallVirtualWithData call_virtual_with_data;
};
#define GDVIRTUAL_CALL(m_name, ...) _gdvirtual_##m_name##_call<false>(__VA_ARGS__)
diff --git a/core/templates/cowdata.h b/core/templates/cowdata.h
index d446c81721..46d9797d6c 100644
--- a/core/templates/cowdata.h
+++ b/core/templates/cowdata.h
@@ -90,6 +90,10 @@ private:
}
_FORCE_INLINE_ bool _get_alloc_size_checked(size_t p_elements, size_t *out) const {
+ if (unlikely(p_elements == 0)) {
+ *out = 0;
+ return true;
+ }
#if defined(__GNUC__)
size_t o;
size_t p;
@@ -101,13 +105,12 @@ private:
if (__builtin_add_overflow(o, static_cast<size_t>(32), &p)) {
return false; // No longer allocated here.
}
- return true;
#else
// Speed is more important than correctness here, do the operations unchecked
// and hope for the best.
*out = _get_alloc_size(p_elements);
- return true;
#endif
+ return *out;
}
void _unref(void *p_data);
diff --git a/doc/classes/Image.xml b/doc/classes/Image.xml
index 5f1f9e6c18..2782e877a9 100644
--- a/doc/classes/Image.xml
+++ b/doc/classes/Image.xml
@@ -201,7 +201,8 @@
<return type="int" enum="Error" />
<param index="0" name="renormalize" type="bool" default="false" />
<description>
- 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].
+ 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]. Enabling [param renormalize] when generating mipmaps for normal textures will make sure all resulting vector values are normalized.
+ It is possible to check if the image has mipmaps by calling [method has_mipmaps] or [method get_mipmap_count].
</description>
</method>
<method name="get_data" qualifiers="const">
@@ -222,6 +223,12 @@
Returns the image's height.
</description>
</method>
+ <method name="get_mipmap_count" qualifiers="const">
+ <return type="int" />
+ <description>
+ Returns the number of mipmap levels or 0 if the image has no mipmaps. The largest main level image is not counted as a mipmap level by this method, so if you want to include it you can add 1 to this count.
+ </description>
+ </method>
<method name="get_mipmap_offset" qualifiers="const">
<return type="int" />
<param index="0" name="mipmap" type="int" />
diff --git a/doc/classes/PhysicsServer3DRenderingServerHandler.xml b/doc/classes/PhysicsServer3DRenderingServerHandler.xml
index da04cd918c..c04dbf2bd7 100644
--- a/doc/classes/PhysicsServer3DRenderingServerHandler.xml
+++ b/doc/classes/PhysicsServer3DRenderingServerHandler.xml
@@ -12,20 +12,48 @@
<return type="void" />
<param index="0" name="aabb" type="AABB" />
<description>
+ Called by the [PhysicsServer3D] to set the bounding box for the [SoftBody3D].
</description>
</method>
<method name="_set_normal" qualifiers="virtual">
<return type="void" />
<param index="0" name="vertex_id" type="int" />
- <param index="1" name="normals" type="const void*" />
+ <param index="1" name="normal" type="Vector3" />
<description>
+ Called by the [PhysicsServer3D] to set the normal for the [SoftBody3D] vertex at the index specified by [param vertex_id].
+ [b]Note:[/b] The [param normal] parameter used to be of type [code]const void*[/code] prior to Godot 4.2.
</description>
</method>
<method name="_set_vertex" qualifiers="virtual">
<return type="void" />
<param index="0" name="vertex_id" type="int" />
- <param index="1" name="vertices" type="const void*" />
+ <param index="1" name="vertex" type="Vector3" />
<description>
+ Called by the [PhysicsServer3D] to set the position for the [SoftBody3D] vertex at the index specified by [param vertex_id].
+ [b]Note:[/b] The [param vertex] parameter used to be of type [code]const void*[/code] prior to Godot 4.2.
+ </description>
+ </method>
+ <method name="set_aabb">
+ <return type="void" />
+ <param index="0" name="aabb" type="AABB" />
+ <description>
+ Sets the bounding box for the [SoftBody3D].
+ </description>
+ </method>
+ <method name="set_normal">
+ <return type="void" />
+ <param index="0" name="vertex_id" type="int" />
+ <param index="1" name="normal" type="Vector3" />
+ <description>
+ Sets the normal for the [SoftBody3D] vertex at the index specified by [param vertex_id].
+ </description>
+ </method>
+ <method name="set_vertex">
+ <return type="void" />
+ <param index="0" name="vertex_id" type="int" />
+ <param index="1" name="vertex" type="Vector3" />
+ <description>
+ Sets the position for the [SoftBody3D] vertex at the index specified by [param vertex_id].
</description>
</method>
</methods>
diff --git a/drivers/vulkan/rendering_device_vulkan.cpp b/drivers/vulkan/rendering_device_vulkan.cpp
index bd4d76a1b6..29841a6be1 100644
--- a/drivers/vulkan/rendering_device_vulkan.cpp
+++ b/drivers/vulkan/rendering_device_vulkan.cpp
@@ -3112,7 +3112,7 @@ Error RenderingDeviceVulkan::texture_copy(RID p_from_texture, RID p_to_texture,
image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_memory_barrier.image = dst_tex->image;
- image_memory_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ image_memory_barrier.subresourceRange.aspectMask = dst_tex->read_aspect_mask;
image_memory_barrier.subresourceRange.baseMipLevel = p_src_mipmap;
image_memory_barrier.subresourceRange.levelCount = 1;
image_memory_barrier.subresourceRange.baseArrayLayer = p_src_layer;
@@ -3294,7 +3294,7 @@ Error RenderingDeviceVulkan::texture_resolve_multisample(RID p_from_texture, RID
image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_memory_barrier.image = dst_tex->image;
- image_memory_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ image_memory_barrier.subresourceRange.aspectMask = dst_tex->read_aspect_mask;
image_memory_barrier.subresourceRange.baseMipLevel = dst_tex->base_mipmap;
image_memory_barrier.subresourceRange.levelCount = 1;
image_memory_barrier.subresourceRange.baseArrayLayer = dst_tex->base_layer;
@@ -5918,7 +5918,7 @@ Error RenderingDeviceVulkan::buffer_copy(RID p_src_buffer, RID p_dst_buffer, uin
// This method assumes the barriers have been pushed prior to being called, therefore no barriers are pushed
// for the source or destination buffers before performing the copy. These masks are effectively ignored.
- VkPipelineShaderStageCreateFlags src_stage_mask = 0;
+ VkPipelineStageFlags src_stage_mask = 0;
VkAccessFlags src_access_mask = 0;
Buffer *src_buffer = _get_buffer_from_owner(p_src_buffer, src_stage_mask, src_access_mask, BARRIER_MASK_NO_BARRIER);
if (!src_buffer) {
@@ -6038,9 +6038,6 @@ Error RenderingDeviceVulkan::buffer_clear(RID p_buffer, uint32_t p_offset, uint3
ERR_FAIL_COND_V_MSG(p_offset + p_size > buffer->size, ERR_INVALID_PARAMETER,
"Attempted to write buffer (" + itos((p_offset + p_size) - buffer->size) + " bytes) past the end.");
- // Should not be needed.
- // _buffer_memory_barrier(buffer->buffer, p_offset, p_size, dst_stage_mask, VK_PIPELINE_STAGE_TRANSFER_BIT, dst_access, VK_ACCESS_TRANSFER_WRITE_BIT, p_post_barrier);
-
vkCmdFillBuffer(frames[frame].draw_command_buffer, buffer->buffer, p_offset, p_size, 0);
#ifdef FORCE_FULL_BARRIER
@@ -6050,7 +6047,7 @@ Error RenderingDeviceVulkan::buffer_clear(RID p_buffer, uint32_t p_offset, uint3
dst_stage_mask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
}
- _buffer_memory_barrier(buffer->buffer, p_offset, p_size, VK_PIPELINE_STAGE_TRANSFER_BIT, dst_stage_mask, VK_ACCESS_TRANSFER_WRITE_BIT, dst_access, dst_stage_mask);
+ _buffer_memory_barrier(buffer->buffer, p_offset, p_size, VK_PIPELINE_STAGE_TRANSFER_BIT, dst_stage_mask, VK_ACCESS_TRANSFER_WRITE_BIT, dst_access, true);
#endif
return OK;
diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp
index 920d94081e..5b87dc4f46 100644
--- a/editor/animation_track_editor.cpp
+++ b/editor/animation_track_editor.cpp
@@ -4302,7 +4302,7 @@ bool AnimationTrackEditor::is_selection_active() const {
}
bool AnimationTrackEditor::is_snap_enabled() const {
- return snap->is_pressed() ^ Input::get_singleton()->is_key_pressed(Key::CTRL);
+ return snap->is_pressed() ^ Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL);
}
void AnimationTrackEditor::_update_tracks() {
diff --git a/editor/editor_audio_buses.cpp b/editor/editor_audio_buses.cpp
index 6116ad8e4c..109a10750f 100644
--- a/editor/editor_audio_buses.cpp
+++ b/editor/editor_audio_buses.cpp
@@ -329,7 +329,7 @@ void EditorAudioBus::_volume_changed(float p_normalized) {
const float p_db = this->_normalized_volume_to_scaled_db(p_normalized);
- if (Input::get_singleton()->is_key_pressed(Key::CTRL)) {
+ if (Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) {
// Snap the value when holding Ctrl for easier editing.
// To do so, it needs to be converted back to normalized volume (as the slider uses that unit).
slider->set_value(_scaled_db_to_normalized_volume(Math::round(p_db)));
@@ -389,7 +389,7 @@ float EditorAudioBus::_scaled_db_to_normalized_volume(float db) {
void EditorAudioBus::_show_value(float slider_value) {
float db;
- if (Input::get_singleton()->is_key_pressed(Key::CTRL)) {
+ if (Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) {
// Display the correct (snapped) value when holding Ctrl
db = Math::round(_normalized_volume_to_scaled_db(slider_value));
} else {
diff --git a/editor/editor_command_palette.cpp b/editor/editor_command_palette.cpp
index 168fe5a7ac..18a251a306 100644
--- a/editor/editor_command_palette.cpp
+++ b/editor/editor_command_palette.cpp
@@ -357,3 +357,13 @@ Ref<Shortcut> ED_SHORTCUT_AND_COMMAND(const String &p_path, const String &p_name
EditorCommandPalette::get_singleton()->add_shortcut_command(p_command_name, p_path, shortcut);
return shortcut;
}
+
+Ref<Shortcut> ED_SHORTCUT_ARRAY_AND_COMMAND(const String &p_path, const String &p_name, const PackedInt32Array &p_keycodes, String p_command_name) {
+ if (p_command_name.is_empty()) {
+ p_command_name = p_name;
+ }
+
+ Ref<Shortcut> shortcut = ED_SHORTCUT_ARRAY(p_path, p_name, p_keycodes);
+ EditorCommandPalette::get_singleton()->add_shortcut_command(p_command_name, p_path, shortcut);
+ return shortcut;
+}
diff --git a/editor/editor_command_palette.h b/editor/editor_command_palette.h
index 7eb9ff7404..b34c4ddf97 100644
--- a/editor/editor_command_palette.h
+++ b/editor/editor_command_palette.h
@@ -105,5 +105,6 @@ public:
};
Ref<Shortcut> ED_SHORTCUT_AND_COMMAND(const String &p_path, const String &p_name, Key p_keycode = Key::NONE, String p_command = "");
+Ref<Shortcut> ED_SHORTCUT_ARRAY_AND_COMMAND(const String &p_path, const String &p_name, const PackedInt32Array &p_keycodes, String p_command = "");
#endif // EDITOR_COMMAND_PALETTE_H
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index 5341d73fe4..223e50f557 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -7226,8 +7226,8 @@ EditorNode::EditorNode() {
file_menu->add_separator();
- file_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/quick_open", TTR("Quick Open..."), KeyModifierMask::SHIFT + KeyModifierMask::ALT + Key::O), FILE_QUICK_OPEN);
- ED_SHORTCUT_OVERRIDE("editor/quick_open", "macos", KeyModifierMask::META + KeyModifierMask::CTRL + Key::O);
+ file_menu->add_shortcut(ED_SHORTCUT_ARRAY_AND_COMMAND("editor/quick_open", TTR("Quick Open..."), { int32_t(KeyModifierMask::SHIFT + KeyModifierMask::ALT + Key::O), int32_t(KeyModifierMask::CMD_OR_CTRL + Key::P) }), FILE_QUICK_OPEN);
+ ED_SHORTCUT_OVERRIDE_ARRAY("editor/quick_open", "macos", { int32_t(KeyModifierMask::META + KeyModifierMask::CTRL + Key::O), int32_t(KeyModifierMask::CMD_OR_CTRL + Key::P) });
file_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/quick_open_scene", TTR("Quick Open Scene..."), KeyModifierMask::CMD_OR_CTRL + KeyModifierMask::SHIFT + Key::O), FILE_QUICK_OPEN_SCENE);
file_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/quick_open_script", TTR("Quick Open Script..."), KeyModifierMask::CMD_OR_CTRL + KeyModifierMask::ALT + Key::O), FILE_QUICK_OPEN_SCRIPT);
diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp
index 1a518f7d03..338376c724 100644
--- a/editor/filesystem_dock.cpp
+++ b/editor/filesystem_dock.cpp
@@ -2682,7 +2682,7 @@ void FileSystemDock::drop_data_fw(const Point2 &p_point, const Variant &p_data,
}
}
if (!to_move.is_empty()) {
- if (Input::get_singleton()->is_key_pressed(Key::CTRL)) {
+ if (Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) {
_move_operation_confirm(to_dir, true);
} else {
_move_operation_confirm(to_dir);
diff --git a/editor/gui/editor_spin_slider.cpp b/editor/gui/editor_spin_slider.cpp
index fa94a107f0..c59822ba55 100644
--- a/editor/gui/editor_spin_slider.cpp
+++ b/editor/gui/editor_spin_slider.cpp
@@ -212,7 +212,7 @@ void EditorSpinSlider::_value_input_gui_input(const Ref<InputEvent> &p_event) {
}
}
- if (k->is_ctrl_pressed()) {
+ if (k->is_command_or_control_pressed()) {
step *= 100.0;
} else if (k->is_shift_pressed()) {
step *= 10.0;
diff --git a/editor/plugins/abstract_polygon_2d_editor.cpp b/editor/plugins/abstract_polygon_2d_editor.cpp
index fd15735e65..fd5ebc423e 100644
--- a/editor/plugins/abstract_polygon_2d_editor.cpp
+++ b/editor/plugins/abstract_polygon_2d_editor.cpp
@@ -285,7 +285,7 @@ bool AbstractPolygon2DEditor::forward_gui_input(const Ref<InputEvent> &p_event)
if (mode == MODE_EDIT || (_is_line() && mode == MODE_CREATE)) {
if (mb->get_button_index() == MouseButton::LEFT) {
if (mb->is_pressed()) {
- if (mb->is_ctrl_pressed() || mb->is_shift_pressed() || mb->is_alt_pressed()) {
+ if (mb->is_meta_pressed() || mb->is_ctrl_pressed() || mb->is_shift_pressed() || mb->is_alt_pressed()) {
return false;
}
diff --git a/editor/plugins/animation_state_machine_editor.cpp b/editor/plugins/animation_state_machine_editor.cpp
index 638838bcca..a16d689e3d 100644
--- a/editor/plugins/animation_state_machine_editor.cpp
+++ b/editor/plugins/animation_state_machine_editor.cpp
@@ -158,7 +158,7 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv
}
// Select node or push a field inside
- if (mb.is_valid() && !mb->is_shift_pressed() && !mb->is_ctrl_pressed() && mb->is_pressed() && tool_select->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
+ if (mb.is_valid() && !mb->is_shift_pressed() && !mb->is_command_or_control_pressed() && mb->is_pressed() && tool_select->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
selected_transition_from = StringName();
selected_transition_to = StringName();
selected_transition_index = -1;
@@ -337,7 +337,7 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv
ABS(box_selecting_from.x - box_selecting_to.x),
ABS(box_selecting_from.y - box_selecting_to.y));
- if (mb->is_ctrl_pressed() || mb->is_shift_pressed()) {
+ if (mb->is_command_or_control_pressed() || mb->is_shift_pressed()) {
previous_selected = selected_nodes;
} else {
selected_nodes.clear();
diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp
index 91f403bc49..f62eaee96e 100644
--- a/editor/plugins/canvas_item_editor_plugin.cpp
+++ b/editor/plugins/canvas_item_editor_plugin.cpp
@@ -354,7 +354,7 @@ Point2 CanvasItemEditor::snap_point(Point2 p_target, unsigned int p_modes, unsig
snap_target[0] = SNAP_TARGET_NONE;
snap_target[1] = SNAP_TARGET_NONE;
- bool is_snap_active = smart_snap_active ^ Input::get_singleton()->is_key_pressed(Key::CTRL);
+ bool is_snap_active = smart_snap_active ^ Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL);
// Smart snap using the canvas position
Vector2 output = p_target;
@@ -480,7 +480,7 @@ Point2 CanvasItemEditor::snap_point(Point2 p_target, unsigned int p_modes, unsig
}
real_t CanvasItemEditor::snap_angle(real_t p_target, real_t p_start) const {
- if (((smart_snap_active || snap_rotation) ^ Input::get_singleton()->is_key_pressed(Key::CTRL)) && snap_rotation_step != 0) {
+ if (((smart_snap_active || snap_rotation) ^ Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) && snap_rotation_step != 0) {
if (snap_relative) {
return Math::snapped(p_target - snap_rotation_offset, snap_rotation_step) + snap_rotation_offset + (p_start - (int)(p_start / snap_rotation_step) * snap_rotation_step);
} else {
@@ -505,7 +505,7 @@ void CanvasItemEditor::shortcut_input(const Ref<InputEvent> &p_ev) {
viewport->queue_redraw();
}
- if (k->is_pressed() && !k->is_ctrl_pressed() && !k->is_echo() && (grid_snap_active || _is_grid_visible())) {
+ if (k->is_pressed() && !k->is_command_or_control_pressed() && !k->is_echo() && (grid_snap_active || _is_grid_visible())) {
if (multiply_grid_step_shortcut.is_valid() && multiply_grid_step_shortcut->matches_event(p_ev)) {
// Multiply the grid size
grid_step_multiplier = MIN(grid_step_multiplier + 1, 12);
@@ -1924,7 +1924,7 @@ bool CanvasItemEditor::_gui_input_scale(const Ref<InputEvent> &p_event) {
Transform2D simple_xform = (viewport->get_transform() * unscaled_transform).affine_inverse() * transform;
bool uniform = m->is_shift_pressed();
- bool is_ctrl = m->is_ctrl_pressed();
+ bool is_ctrl = m->is_command_or_control_pressed();
Point2 drag_from_local = simple_xform.xform(drag_from);
Point2 drag_to_local = simple_xform.xform(drag_to);
@@ -5609,7 +5609,7 @@ void CanvasItemEditorViewport::_create_preview(const Vector<String> &files) cons
Ref<PackedScene> scene = Ref<PackedScene>(Object::cast_to<PackedScene>(*res));
if (texture != nullptr || scene != nullptr) {
bool root_node_selected = EditorNode::get_singleton()->get_editor_selection()->is_selected(EditorNode::get_singleton()->get_edited_scene());
- String desc = TTR("Drag and drop to add as child of current scene's root node.") + "\n" + TTR("Hold Ctrl when dropping to add as child of selected node.");
+ String desc = TTR("Drag and drop to add as child of current scene's root node.") + "\n" + vformat(TTR("Hold %s when dropping to add as child of selected node."), keycode_get_string((Key)KeyModifierMask::CMD_OR_CTRL));
if (!root_node_selected) {
desc += "\n" + TTR("Hold Shift when dropping to add as sibling of selected node.");
}
@@ -5913,7 +5913,7 @@ bool CanvasItemEditorViewport::_only_packed_scenes_selected() const {
void CanvasItemEditorViewport::drop_data(const Point2 &p_point, const Variant &p_data) {
bool is_shift = Input::get_singleton()->is_key_pressed(Key::SHIFT);
- bool is_ctrl = Input::get_singleton()->is_key_pressed(Key::CTRL);
+ bool is_ctrl = Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL);
bool is_alt = Input::get_singleton()->is_key_pressed(Key::ALT);
selected_files.clear();
diff --git a/editor/plugins/curve_editor_plugin.cpp b/editor/plugins/curve_editor_plugin.cpp
index 1d3ffc0e3a..37f0ca449d 100644
--- a/editor/plugins/curve_editor_plugin.cpp
+++ b/editor/plugins/curve_editor_plugin.cpp
@@ -225,7 +225,7 @@ void CurveEdit::gui_input(const Ref<InputEvent> &p_event) {
} else if (grabbing == GRAB_NONE) {
// Adding a new point. Insert a temporary point for the user to adjust, so it's not in the undo/redo.
Vector2 new_pos = get_world_pos(mpos).clamp(Vector2(0.0, curve->get_min_value()), Vector2(1.0, curve->get_max_value()));
- if (snap_enabled || mb->is_ctrl_pressed()) {
+ if (snap_enabled || mb->is_command_or_control_pressed()) {
new_pos.x = Math::snapped(new_pos.x, 1.0 / snap_count);
new_pos.y = Math::snapped(new_pos.y - curve->get_min_value(), curve->get_range() / snap_count) + curve->get_min_value();
}
@@ -276,7 +276,7 @@ void CurveEdit::gui_input(const Ref<InputEvent> &p_event) {
// Drag point.
Vector2 new_pos = get_world_pos(mpos).clamp(Vector2(0.0, curve->get_min_value()), Vector2(1.0, curve->get_max_value()));
- if (snap_enabled || mm->is_ctrl_pressed()) {
+ if (snap_enabled || mm->is_command_or_control_pressed()) {
new_pos.x = Math::snapped(new_pos.x, 1.0 / snap_count);
new_pos.y = Math::snapped(new_pos.y - curve->get_min_value(), curve->get_range() / snap_count) + curve->get_min_value();
}
diff --git a/editor/plugins/gradient_editor.cpp b/editor/plugins/gradient_editor.cpp
index 63dede4850..bcc7d2a004 100644
--- a/editor/plugins/gradient_editor.cpp
+++ b/editor/plugins/gradient_editor.cpp
@@ -312,7 +312,7 @@ void GradientEditor::gui_input(const Ref<InputEvent> &p_event) {
// Snap to "round" coordinates if holding Ctrl.
// Be more precise if holding Shift as well.
- if (mm->is_ctrl_pressed()) {
+ if (mm->is_command_or_control_pressed()) {
newofs = Math::snapped(newofs, mm->is_shift_pressed() ? 0.025 : 0.1);
} else if (mm->is_shift_pressed()) {
// Snap to nearest point if holding just Shift.
diff --git a/editor/plugins/gradient_texture_2d_editor_plugin.cpp b/editor/plugins/gradient_texture_2d_editor_plugin.cpp
index 494d97c45c..5952185cc0 100644
--- a/editor/plugins/gradient_texture_2d_editor_plugin.cpp
+++ b/editor/plugins/gradient_texture_2d_editor_plugin.cpp
@@ -113,7 +113,7 @@ void GradientTexture2DEdit::gui_input(const Ref<InputEvent> &p_event) {
}
Vector2 new_pos = (mpos / size).clamp(Vector2(0, 0), Vector2(1, 1));
- if (snap_enabled || mm->is_ctrl_pressed()) {
+ if (snap_enabled || mm->is_command_or_control_pressed()) {
new_pos = new_pos.snapped(Vector2(1.0 / snap_count, 1.0 / snap_count));
}
@@ -201,7 +201,7 @@ void GradientTexture2DEdit::_draw() {
draw_texture_rect(texture, Rect2(Point2(), size));
// Draw grid snap lines.
- if (snap_enabled || (Input::get_singleton()->is_key_pressed(Key::CTRL) && grabbed != HANDLE_NONE)) {
+ if (snap_enabled || (Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL) && grabbed != HANDLE_NONE)) {
const Color line_color = Color(0.5, 0.5, 0.5, 0.5);
for (int idx = 0; idx < snap_count + 1; idx++) {
diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp
index c05a6d1392..2215c371d0 100644
--- a/editor/plugins/node_3d_editor_plugin.cpp
+++ b/editor/plugins/node_3d_editor_plugin.cpp
@@ -1977,7 +1977,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
nav_mode = NAVIGATION_ORBIT;
} else if (nav_scheme == NAVIGATION_MODO && m->is_alt_pressed() && m->is_shift_pressed()) {
nav_mode = NAVIGATION_PAN;
- } else if (nav_scheme == NAVIGATION_MODO && m->is_alt_pressed() && m->is_ctrl_pressed()) {
+ } else if (nav_scheme == NAVIGATION_MODO && m->is_alt_pressed() && m->is_command_or_control_pressed()) {
nav_mode = NAVIGATION_ZOOM;
} else if (nav_scheme == NAVIGATION_MODO && m->is_alt_pressed()) {
nav_mode = NAVIGATION_ORBIT;
@@ -5244,7 +5244,8 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, int p
preview_material_label_desc = memnew(Label);
preview_material_label_desc->set_anchors_and_offsets_preset(LayoutPreset::PRESET_BOTTOM_LEFT);
preview_material_label_desc->set_offset(Side::SIDE_TOP, -50 * EDSCALE);
- preview_material_label_desc->set_text(TTR("Drag and drop to override the material of any geometry node.\nHold Ctrl when dropping to override a specific surface."));
+ Key key = (OS::get_singleton()->has_feature("macos") || OS::get_singleton()->has_feature("web_macos") || OS::get_singleton()->has_feature("web_ios")) ? Key::META : Key::CTRL;
+ preview_material_label_desc->set_text(vformat(TTR("Drag and drop to override the material of any geometry node.\nHold %s when dropping to override a specific surface."), find_keycode_name(key)));
preview_material_label_desc->add_theme_color_override("font_color", Color(0.8, 0.8, 0.8, 1));
preview_material_label_desc->add_theme_constant_override("line_spacing", 0);
preview_material_label_desc->hide();
diff --git a/editor/plugins/path_3d_editor_plugin.cpp b/editor/plugins/path_3d_editor_plugin.cpp
index 40e8482a0b..e1b402475a 100644
--- a/editor/plugins/path_3d_editor_plugin.cpp
+++ b/editor/plugins/path_3d_editor_plugin.cpp
@@ -455,7 +455,7 @@ EditorPlugin::AfterGUIInput Path3DEditorPlugin::forward_3d_gui_input(Camera3D *p
set_handle_clicked(false);
}
- if (mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT && (curve_create->is_pressed() || (curve_edit->is_pressed() && mb->is_ctrl_pressed()))) {
+ if (mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT && (curve_create->is_pressed() || (curve_edit->is_pressed() && mb->is_command_or_control_pressed()))) {
//click into curve, break it down
Vector<Vector3> v3a = c->tessellate();
int rc = v3a.size();
diff --git a/editor/plugins/polygon_2d_editor_plugin.cpp b/editor/plugins/polygon_2d_editor_plugin.cpp
index 5ed9f4946d..e700d28afb 100644
--- a/editor/plugins/polygon_2d_editor_plugin.cpp
+++ b/editor/plugins/polygon_2d_editor_plugin.cpp
@@ -1304,7 +1304,8 @@ Polygon2DEditor::Polygon2DEditor() {
uv_button[UV_MODE_CREATE]->set_tooltip_text(TTR("Create Polygon"));
uv_button[UV_MODE_CREATE_INTERNAL]->set_tooltip_text(TTR("Create Internal Vertex"));
uv_button[UV_MODE_REMOVE_INTERNAL]->set_tooltip_text(TTR("Remove Internal Vertex"));
- uv_button[UV_MODE_EDIT_POINT]->set_tooltip_text(TTR("Move Points") + "\n" + TTR("Ctrl: Rotate") + "\n" + TTR("Shift: Move All") + "\n" + TTR("Shift+Ctrl: Scale"));
+ Key key = (OS::get_singleton()->has_feature("macos") || OS::get_singleton()->has_feature("web_macos") || OS::get_singleton()->has_feature("web_ios")) ? Key::META : Key::CTRL;
+ uv_button[UV_MODE_EDIT_POINT]->set_tooltip_text(TTR("Move Points") + "\n" + find_keycode_name(key) + TTR(": Rotate") + "\n" + TTR("Shift: Move All") + "\n" + keycode_get_string((Key)KeyModifierMask::CMD_OR_CTRL) + TTR("Shift: Scale"));
uv_button[UV_MODE_MOVE]->set_tooltip_text(TTR("Move Polygon"));
uv_button[UV_MODE_ROTATE]->set_tooltip_text(TTR("Rotate Polygon"));
uv_button[UV_MODE_SCALE]->set_tooltip_text(TTR("Scale Polygon"));
diff --git a/editor/plugins/polygon_3d_editor_plugin.cpp b/editor/plugins/polygon_3d_editor_plugin.cpp
index 41672822b2..fa5413787c 100644
--- a/editor/plugins/polygon_3d_editor_plugin.cpp
+++ b/editor/plugins/polygon_3d_editor_plugin.cpp
@@ -184,7 +184,7 @@ EditorPlugin::AfterGUIInput Polygon3DEditor::forward_3d_gui_input(Camera3D *p_ca
case MODE_EDIT: {
if (mb->get_button_index() == MouseButton::LEFT) {
if (mb->is_pressed()) {
- if (mb->is_ctrl_pressed()) {
+ if (mb->is_command_or_control_pressed()) {
if (poly.size() < 3) {
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action(TTR("Edit Poly"));
@@ -329,7 +329,7 @@ EditorPlugin::AfterGUIInput Polygon3DEditor::forward_3d_gui_input(Camera3D *p_ca
Vector2 cpoint(spoint.x, spoint.y);
- if (snap_ignore && !Input::get_singleton()->is_key_pressed(Key::CTRL)) {
+ if (snap_ignore && !Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) {
snap_ignore = false;
}
diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp
index 5aaa3365b3..d5ad21e346 100644
--- a/editor/plugins/script_text_editor.cpp
+++ b/editor/plugins/script_text_editor.cpp
@@ -890,14 +890,19 @@ void ScriptTextEditor::_lookup_symbol(const String &p_symbol, int p_row, int p_c
Error lc_error = script->get_language()->lookup_code(code_text, p_symbol, script->get_path(), base, result);
if (ScriptServer::is_global_class(p_symbol)) {
EditorNode::get_singleton()->load_resource(ScriptServer::get_global_class_path(p_symbol));
- } else if (p_symbol.is_resource_file()) {
+ } else if (p_symbol.is_resource_file() || p_symbol.begins_with("uid://")) {
+ String symbol = p_symbol;
+ if (symbol.begins_with("uid://")) {
+ symbol = ResourceUID::get_singleton()->get_id_path(ResourceUID::get_singleton()->text_to_id(symbol));
+ }
+
List<String> scene_extensions;
ResourceLoader::get_recognized_extensions_for_type("PackedScene", &scene_extensions);
- if (scene_extensions.find(p_symbol.get_extension())) {
- EditorNode::get_singleton()->load_scene(p_symbol);
+ if (scene_extensions.find(symbol.get_extension())) {
+ EditorNode::get_singleton()->load_scene(symbol);
} else {
- EditorNode::get_singleton()->load_resource(p_symbol);
+ EditorNode::get_singleton()->load_resource(symbol);
}
} else if (lc_error == OK) {
@@ -1024,7 +1029,7 @@ void ScriptTextEditor::_validate_symbol(const String &p_symbol) {
String lc_text = code_editor->get_text_editor()->get_text_for_symbol_lookup();
Error lc_error = script->get_language()->lookup_code(lc_text, p_symbol, script->get_path(), base, result);
bool is_singleton = ProjectSettings::get_singleton()->has_autoload(p_symbol) && ProjectSettings::get_singleton()->get_autoload(p_symbol).is_singleton;
- if (ScriptServer::is_global_class(p_symbol) || p_symbol.is_resource_file() || lc_error == OK || is_singleton) {
+ if (lc_error == OK || is_singleton || ScriptServer::is_global_class(p_symbol) || p_symbol.is_resource_file() || p_symbol.begins_with("uid://")) {
text_edit->set_symbol_lookup_word_as_valid(true);
} else if (p_symbol.is_relative_path()) {
String path = _get_absolute_path(p_symbol);
@@ -1747,7 +1752,7 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data
Array files = d["files"];
String text_to_drop;
- bool preload = Input::get_singleton()->is_key_pressed(Key::CTRL);
+ bool preload = Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL);
for (int i = 0; i < files.size(); i++) {
if (i > 0) {
text_to_drop += ", ";
@@ -1787,7 +1792,7 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data
Array nodes = d["nodes"];
String text_to_drop;
- if (Input::get_singleton()->is_key_pressed(Key::CTRL)) {
+ if (Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) {
bool use_type = EDITOR_GET("text_editor/completion/add_type_hints");
for (int i = 0; i < nodes.size(); i++) {
NodePath np = nodes[i];
diff --git a/editor/plugins/sprite_frames_editor_plugin.cpp b/editor/plugins/sprite_frames_editor_plugin.cpp
index 3134c0b951..1844358069 100644
--- a/editor/plugins/sprite_frames_editor_plugin.cpp
+++ b/editor/plugins/sprite_frames_editor_plugin.cpp
@@ -188,7 +188,7 @@ void SpriteFramesEditor::_sheet_preview_input(const Ref<InputEvent> &p_event) {
// Prevent double-toggling the same frame when moving the mouse when the mouse button is still held.
frames_toggled_by_mouse_hover.insert(this_idx);
- if (mb->is_ctrl_pressed()) {
+ if (mb->is_command_or_control_pressed()) {
frames_selected.erase(this_idx);
} else if (!frames_selected.has(this_idx)) {
frames_selected.insert(this_idx, selected_count);
@@ -255,6 +255,7 @@ void SpriteFramesEditor::_sheet_scroll_input(const Ref<InputEvent> &p_event) {
// Zoom in/out using Ctrl + mouse wheel. This is done on the ScrollContainer
// to allow performing this action anywhere, even if the cursor isn't
// hovering the texture in the workspace.
+ // keep CTRL and not CMD_OR_CTRL as CTRL is expected even on MacOS.
if (mb->get_button_index() == MouseButton::WHEEL_UP && mb->is_pressed() && mb->is_ctrl_pressed()) {
_sheet_zoom_on_position(scale_ratio, mb->get_position());
// Don't scroll up after zooming in.
@@ -1485,7 +1486,7 @@ void SpriteFramesEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da
if (String(d["type"]) == "files") {
Vector<String> files = d["files"];
- if (Input::get_singleton()->is_key_pressed(Key::CTRL)) {
+ if (Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) {
_prepare_sprite_sheet(files[0]);
} else {
_file_load_request(files, at_pos);
diff --git a/editor/plugins/tiles/tile_map_editor.cpp b/editor/plugins/tiles/tile_map_editor.cpp
index d5011380d3..f5f405d576 100644
--- a/editor/plugins/tiles/tile_map_editor.cpp
+++ b/editor/plugins/tiles/tile_map_editor.cpp
@@ -675,7 +675,7 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p
}
} else if (tool_buttons_group->get_pressed_button() == select_tool_button) {
drag_start_mouse_pos = mpos;
- if (tile_map_selection.has(tile_map->local_to_map(drag_start_mouse_pos)) && !mb->is_shift_pressed()) {
+ if (tile_map_selection.has(tile_map->local_to_map(drag_start_mouse_pos)) && !mb->is_shift_pressed() && !mb->is_command_or_control_pressed()) {
// Move the selection
_update_selection_pattern_from_tilemap_selection(); // Make sure the pattern is up to date before moving.
drag_type = DRAG_TYPE_MOVE;
@@ -2217,7 +2217,7 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() {
paint_tool_button->set_toggle_mode(true);
paint_tool_button->set_button_group(tool_buttons_group);
paint_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/paint_tool", TTR("Paint"), Key::D));
- paint_tool_button->set_tooltip_text(TTR("Shift: Draw line.") + "\n" + TTR("Shift+Ctrl: Draw rectangle."));
+ paint_tool_button->set_tooltip_text(TTR("Shift: Draw line.") + "\n" + keycode_get_string((Key)KeyModifierMask::CMD_OR_CTRL) + TTR("Shift: Draw rectangle."));
paint_tool_button->connect("pressed", callable_mp(this, &TileMapEditorTilesPlugin::_update_toolbar));
tilemap_tiles_tools_buttons->add_child(paint_tool_button);
viewport_shortcut_buttons.push_back(paint_tool_button);
@@ -2263,7 +2263,8 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() {
picker_button->set_flat(true);
picker_button->set_toggle_mode(true);
picker_button->set_shortcut(ED_SHORTCUT("tiles_editor/picker", TTR("Picker"), Key::P));
- picker_button->set_tooltip_text(TTR("Alternatively hold Ctrl with other tools to pick tile."));
+ Key key = (OS::get_singleton()->has_feature("macos") || OS::get_singleton()->has_feature("web_macos") || OS::get_singleton()->has_feature("web_ios")) ? Key::META : Key::CTRL;
+ picker_button->set_tooltip_text(vformat(TTR("Alternatively hold %s with other tools to pick tile."), find_keycode_name(key)));
picker_button->connect("pressed", callable_mp(CanvasItemEditor::get_singleton(), &CanvasItemEditor::update_viewport));
tools_settings->add_child(picker_button);
viewport_shortcut_buttons.push_back(picker_button);
diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp
index 1485ee9115..4037655e2c 100644
--- a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp
+++ b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp
@@ -1202,7 +1202,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven
if (tools_button_group->get_pressed_button() == tool_setup_atlas_source_button) {
if (tools_settings_erase_button->is_pressed()) {
// Erasing
- if (mb->is_ctrl_pressed() || mb->is_shift_pressed()) {
+ if (mb->is_command_or_control_pressed() || mb->is_shift_pressed()) {
// Remove tiles using rect.
// Setup the dragging info.
@@ -1241,7 +1241,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven
// Create a tile.
tile_set_atlas_source->create_tile(coords);
}
- } else if (mb->is_ctrl_pressed()) {
+ } else if (mb->is_command_or_control_pressed()) {
// Create tiles using rect.
drag_type = DRAG_TYPE_CREATE_TILES_USING_RECT;
drag_start_mouse_pos = mouse_local_pos;
diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp
index feb3d7fa14..e7fe9a353c 100644
--- a/editor/project_manager.cpp
+++ b/editor/project_manager.cpp
@@ -1863,7 +1863,7 @@ void ProjectList::_panel_input(const Ref<InputEvent> &p_ev, Node *p_hb) {
CRASH_COND(anchor_index == -1);
_select_project_range(anchor_index, clicked_index);
- } else if (mb->is_ctrl_pressed()) {
+ } else if (mb->is_command_or_control_pressed()) {
_toggle_project(clicked_index);
} else {
@@ -1875,7 +1875,7 @@ void ProjectList::_panel_input(const Ref<InputEvent> &p_ev, Node *p_hb) {
// Do not allow opening a project more than once using a single project manager instance.
// Opening the same project in several editor instances at once can lead to various issues.
- if (!mb->is_ctrl_pressed() && mb->is_double_click() && !project_opening_initiated) {
+ if (!mb->is_command_or_control_pressed() && mb->is_double_click() && !project_opening_initiated) {
emit_signal(SNAME(SIGNAL_PROJECT_ASK_OPEN));
}
}
diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp
index 966cba6348..c91f1599ed 100644
--- a/editor/scene_tree_dock.cpp
+++ b/editor/scene_tree_dock.cpp
@@ -2818,7 +2818,7 @@ void SceneTreeDock::_script_dropped(String p_file, NodePath p_to) {
}
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
- if (Input::get_singleton()->is_key_pressed(Key::CTRL)) {
+ if (Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) {
Object *obj = ClassDB::instantiate(scr->get_instance_base_type());
ERR_FAIL_NULL(obj);
diff --git a/misc/extension_api_validation/4.1-stable.expected b/misc/extension_api_validation/4.1-stable.expected
index 39eba9642b..19c9a28c09 100644
--- a/misc/extension_api_validation/4.1-stable.expected
+++ b/misc/extension_api_validation/4.1-stable.expected
@@ -176,3 +176,11 @@ Validate extension JSON: API was removed: classes/TileMap/methods/set_quadrant_s
Validate extension JSON: API was removed: classes/TileMap/properties/cell_quadrant_size
cell_quadrant_size/quadrant_size of the TileMap API was renamed to rendering_quadrant_size.
+
+
+GH-81298
+--------
+Validate extension JSON: Error: Field 'classes/PhysicsServer3DRenderingServerHandler/methods/_set_vertex/arguments/1': type changed value in new API, from "const void*" to "Vector3".
+Validate extension JSON: Error: Field 'classes/PhysicsServer3DRenderingServerHandler/methods/_set_normal/arguments/1': type changed value in new API, from "const void*" to "Vector3".
+
+Intentional compatibility breakage to be consistent with the new non-virtual set_vertex/set_normal.
diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp
index e488d6e266..1be690d894 100644
--- a/modules/gdscript/editor/gdscript_highlighter.cpp
+++ b/modules/gdscript/editor/gdscript_highlighter.cpp
@@ -52,6 +52,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
bool in_keyword = false;
bool in_word = false;
bool in_number = false;
+ bool in_raw_string = false;
bool in_node_path = false;
bool in_node_ref = false;
bool in_annotation = false;
@@ -234,15 +235,33 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
}
if (str[from] == '\\') {
- Dictionary escape_char_highlighter_info;
- escape_char_highlighter_info["color"] = symbol_color;
- color_map[from] = escape_char_highlighter_info;
+ if (!in_raw_string) {
+ Dictionary escape_char_highlighter_info;
+ escape_char_highlighter_info["color"] = symbol_color;
+ color_map[from] = escape_char_highlighter_info;
+ }
from++;
- Dictionary region_continue_highlighter_info;
- region_continue_highlighter_info["color"] = region_color;
- color_map[from + 1] = region_continue_highlighter_info;
+ if (!in_raw_string) {
+ int esc_len = 0;
+ if (str[from] == 'u') {
+ esc_len = 4;
+ } else if (str[from] == 'U') {
+ esc_len = 6;
+ }
+ for (int k = 0; k < esc_len && from < line_length - 1; k++) {
+ if (!is_hex_digit(str[from + 1])) {
+ break;
+ }
+ from++;
+ }
+
+ Dictionary region_continue_highlighter_info;
+ region_continue_highlighter_info["color"] = region_color;
+ color_map[from + 1] = region_continue_highlighter_info;
+ }
+
continue;
}
@@ -489,6 +508,12 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
in_member_variable = false;
}
+ if (!in_raw_string && in_region == -1 && str[j] == 'r' && j < line_length - 1 && (str[j + 1] == '"' || str[j + 1] == '\'')) {
+ in_raw_string = true;
+ } else if (in_raw_string && in_region == -1) {
+ in_raw_string = false;
+ }
+
// Keep symbol color for binary '&&'. In the case of '&&&' use StringName color for the last ampersand.
if (!in_string_name && in_region == -1 && str[j] == '&' && !is_binary_op) {
if (j >= 2 && str[j - 1] == '&' && str[j - 2] != '&' && prev_is_binary_op) {
@@ -520,7 +545,9 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
in_annotation = false;
}
- if (in_node_ref) {
+ if (in_raw_string) {
+ color = string_color;
+ } else if (in_node_ref) {
next_type = NODE_REF;
color = node_ref_color;
} else if (in_annotation) {
@@ -692,7 +719,7 @@ void GDScriptSyntaxHighlighter::_update_cache() {
}
/* Strings */
- const Color string_color = EDITOR_GET("text_editor/theme/highlighting/string_color");
+ string_color = EDITOR_GET("text_editor/theme/highlighting/string_color");
List<String> strings;
gdscript->get_string_delimiters(&strings);
for (const String &string : strings) {
diff --git a/modules/gdscript/editor/gdscript_highlighter.h b/modules/gdscript/editor/gdscript_highlighter.h
index fe3b63d713..090857f397 100644
--- a/modules/gdscript/editor/gdscript_highlighter.h
+++ b/modules/gdscript/editor/gdscript_highlighter.h
@@ -78,6 +78,7 @@ private:
Color built_in_type_color;
Color number_color;
Color member_color;
+ Color string_color;
Color node_path_color;
Color node_ref_color;
Color annotation_color;
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index 077b30a7fe..04c86d60a8 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -2609,7 +2609,7 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig
}
// Check if assigned value is an array literal, so we can make it a typed array too if appropriate.
- if (p_assignment->assigned_value->type == GDScriptParser::Node::ARRAY && assignee_type.has_container_element_type()) {
+ if (p_assignment->assigned_value->type == GDScriptParser::Node::ARRAY && assignee_type.is_hard_type() && assignee_type.has_container_element_type()) {
update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_assignment->assigned_value), assignee_type.get_container_element_type());
}
@@ -3213,7 +3213,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
// If the function requires typed arrays we must make literals be typed.
for (const KeyValue<int, GDScriptParser::ArrayNode *> &E : arrays) {
int index = E.key;
- if (index < par_types.size() && par_types[index].has_container_element_type()) {
+ if (index < par_types.size() && par_types[index].is_hard_type() && par_types[index].has_container_element_type()) {
update_array_literal_element_type(E.value, par_types[index].get_container_element_type());
}
}
@@ -4099,7 +4099,7 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
GDScriptParser::DataType base_type = p_subscript->base->get_datatype();
bool valid = false;
// If the base is a metatype, use the analyzer instead.
- if (p_subscript->base->is_constant && !base_type.is_meta_type) {
+ if (p_subscript->base->is_constant && !base_type.is_meta_type && base_type.kind != GDScriptParser::DataType::CLASS) {
// Just try to get it.
Variant value = p_subscript->base->reduced_value.get_named(p_subscript->attribute->name, valid);
if (valid) {
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index aec8f56516..00d3df8fd0 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -59,6 +59,7 @@ void GDScriptLanguage::get_string_delimiters(List<String> *p_delimiters) const {
p_delimiters->push_back("' '");
p_delimiters->push_back("\"\"\" \"\"\"");
p_delimiters->push_back("''' '''");
+ // NOTE: StringName, NodePath and r-strings are not listed here.
}
bool GDScriptLanguage::is_using_templates() {
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index 52c1a5b141..1202e7e235 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -383,8 +383,10 @@ GDScriptTokenizer::Token GDScriptParser::advance() {
push_error(current.literal);
current = tokenizer.scan();
}
- for (Node *n : nodes_in_progress) {
- update_extents(n);
+ if (previous.type != GDScriptTokenizer::Token::DEDENT) { // `DEDENT` belongs to the next non-empty line.
+ for (Node *n : nodes_in_progress) {
+ update_extents(n);
+ }
}
return previous;
}
@@ -579,13 +581,14 @@ void GDScriptParser::parse_program() {
complete_extents(head);
#ifdef TOOLS_ENABLED
- for (const KeyValue<int, GDScriptTokenizer::CommentData> &E : tokenizer.get_comments()) {
- if (E.value.new_line && E.value.comment.begins_with("##")) {
- class_doc_line = MIN(class_doc_line, E.key);
+ const HashMap<int, GDScriptTokenizer::CommentData> &comments = tokenizer.get_comments();
+ int line = MIN(max_script_doc_line, head->end_line);
+ while (line > 0) {
+ if (comments.has(line) && comments[line].new_line && comments[line].comment.begins_with("##")) {
+ head->doc_data = parse_class_doc_comment(line);
+ break;
}
- }
- if (has_comment(class_doc_line, true)) {
- head->doc_data = parse_class_doc_comment(class_doc_line, false);
+ line--;
}
#endif // TOOLS_ENABLED
@@ -747,10 +750,6 @@ template <class T>
void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(bool), AnnotationInfo::TargetKind p_target, const String &p_member_kind, bool p_is_static) {
advance();
-#ifdef TOOLS_ENABLED
- int doc_comment_line = previous.start_line - 1;
-#endif // TOOLS_ENABLED
-
// Consume annotations.
List<AnnotationNode *> annotations;
while (!annotation_stack.is_empty()) {
@@ -762,11 +761,6 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(b
push_error(vformat(R"(Annotation "%s" cannot be applied to a %s.)", last_annotation->name, p_member_kind));
clear_unused_annotations();
}
-#ifdef TOOLS_ENABLED
- if (last_annotation->start_line == doc_comment_line) {
- doc_comment_line--;
- }
-#endif // TOOLS_ENABLED
}
T *member = (this->*p_parse_function)(p_is_static);
@@ -774,28 +768,40 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(b
return;
}
+#ifdef TOOLS_ENABLED
+ int doc_comment_line = member->start_line - 1;
+#endif // TOOLS_ENABLED
+
for (AnnotationNode *&annotation : annotations) {
member->annotations.push_back(annotation);
+#ifdef TOOLS_ENABLED
+ if (annotation->start_line <= doc_comment_line) {
+ doc_comment_line = annotation->start_line - 1;
+ }
+#endif // TOOLS_ENABLED
}
#ifdef TOOLS_ENABLED
- // Consume doc comments.
- class_doc_line = MIN(class_doc_line, doc_comment_line - 1);
-
- // Check whether current line has a doc comment
- if (has_comment(previous.start_line, true)) {
- if constexpr (std::is_same_v<T, ClassNode>) {
- member->doc_data = parse_class_doc_comment(previous.start_line, true, true);
- } else {
- member->doc_data = parse_doc_comment(previous.start_line, true);
+ if constexpr (std::is_same_v<T, ClassNode>) {
+ if (has_comment(member->start_line, true)) {
+ // Inline doc comment.
+ member->doc_data = parse_class_doc_comment(member->start_line, true);
+ } else if (has_comment(doc_comment_line, true) && tokenizer.get_comments()[doc_comment_line].new_line) {
+ // Normal doc comment. Don't check `min_member_doc_line` because a class ends parsing after its members.
+ // This may not work correctly for cases like `var a; class B`, but it doesn't matter in practice.
+ member->doc_data = parse_class_doc_comment(doc_comment_line);
}
- } else if (has_comment(doc_comment_line, true)) {
- if constexpr (std::is_same_v<T, ClassNode>) {
- member->doc_data = parse_class_doc_comment(doc_comment_line, true);
- } else {
+ } else {
+ if (has_comment(member->start_line, true)) {
+ // Inline doc comment.
+ member->doc_data = parse_doc_comment(member->start_line, true);
+ } else if (doc_comment_line >= min_member_doc_line && has_comment(doc_comment_line, true) && tokenizer.get_comments()[doc_comment_line].new_line) {
+ // Normal doc comment.
member->doc_data = parse_doc_comment(doc_comment_line);
}
}
+
+ min_member_doc_line = member->end_line + 1; // Prevent multiple members from using the same doc comment.
#endif // TOOLS_ENABLED
if (member->identifier != nullptr) {
@@ -1263,6 +1269,9 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum(bool p_is_static) {
push_multiline(true);
consume(GDScriptTokenizer::Token::BRACE_OPEN, vformat(R"(Expected "{" after %s.)", named ? "enum name" : R"("enum")"));
+#ifdef TOOLS_ENABLED
+ int min_enum_value_doc_line = previous.end_line + 1;
+#endif
HashMap<StringName, int> elements;
@@ -1325,43 +1334,35 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum(bool p_is_static) {
}
} while (match(GDScriptTokenizer::Token::COMMA));
- pop_multiline();
- consume(GDScriptTokenizer::Token::BRACE_CLOSE, R"(Expected closing "}" for enum.)");
-
#ifdef TOOLS_ENABLED
// Enum values documentation.
for (int i = 0; i < enum_node->values.size(); i++) {
- int doc_comment_line = enum_node->values[i].line;
- bool single_line = false;
-
- if (has_comment(doc_comment_line, true)) {
- single_line = true;
- } else if (has_comment(doc_comment_line - 1, true)) {
- doc_comment_line--;
- } else {
- continue;
- }
-
- if (i == enum_node->values.size() - 1) {
- // If close bracket is same line as last value.
- if (doc_comment_line == previous.start_line) {
- break;
- }
- } else {
- // If two values are same line.
- if (doc_comment_line == enum_node->values[i + 1].line) {
- continue;
+ int enum_value_line = enum_node->values[i].line;
+ int doc_comment_line = enum_value_line - 1;
+
+ MemberDocData doc_data;
+ if (has_comment(enum_value_line, true)) {
+ // Inline doc comment.
+ if (i == enum_node->values.size() - 1 || enum_node->values[i + 1].line > enum_value_line) {
+ doc_data = parse_doc_comment(enum_value_line, true);
}
+ } else if (doc_comment_line >= min_enum_value_doc_line && has_comment(doc_comment_line, true) && tokenizer.get_comments()[doc_comment_line].new_line) {
+ // Normal doc comment.
+ doc_data = parse_doc_comment(doc_comment_line);
}
if (named) {
- enum_node->values.write[i].doc_data = parse_doc_comment(doc_comment_line, single_line);
+ enum_node->values.write[i].doc_data = doc_data;
} else {
- current_class->set_enum_value_doc_data(enum_node->values[i].identifier->name, parse_doc_comment(doc_comment_line, single_line));
+ current_class->set_enum_value_doc_data(enum_node->values[i].identifier->name, doc_data);
}
+
+ min_enum_value_doc_line = enum_value_line + 1; // Prevent multiple enum values from using the same doc comment.
}
#endif // TOOLS_ENABLED
+ pop_multiline();
+ consume(GDScriptTokenizer::Token::BRACE_CLOSE, R"(Expected closing "}" for enum.)");
complete_extents(enum_node);
end_statement("enum");
@@ -3454,31 +3455,21 @@ bool GDScriptParser::has_comment(int p_line, bool p_must_be_doc) {
}
GDScriptParser::MemberDocData GDScriptParser::parse_doc_comment(int p_line, bool p_single_line) {
- MemberDocData result;
+ ERR_FAIL_COND_V(!has_comment(p_line, true), MemberDocData());
const HashMap<int, GDScriptTokenizer::CommentData> &comments = tokenizer.get_comments();
- ERR_FAIL_COND_V(!comments.has(p_line), result);
-
- if (p_single_line) {
- if (comments[p_line].comment.begins_with("##")) {
- result.description = comments[p_line].comment.trim_prefix("##").strip_edges();
- return result;
- }
- return result;
- }
-
int line = p_line;
- DocLineState state = DOC_LINE_NORMAL;
- while (comments.has(line - 1)) {
- if (!comments[line - 1].new_line || !comments[line - 1].comment.begins_with("##")) {
- break;
+ if (!p_single_line) {
+ while (comments.has(line - 1) && comments[line - 1].new_line && comments[line - 1].comment.begins_with("##")) {
+ line--;
}
- line--;
}
+ max_script_doc_line = MIN(max_script_doc_line, line - 1);
+
String space_prefix;
- if (comments.has(line) && comments[line].comment.begins_with("##")) {
+ {
int i = 2;
for (; i < comments[line].comment.length(); i++) {
if (comments[line].comment[i] != ' ') {
@@ -3488,11 +3479,10 @@ GDScriptParser::MemberDocData GDScriptParser::parse_doc_comment(int p_line, bool
space_prefix = String(" ").repeat(i - 2);
}
- while (comments.has(line)) {
- if (!comments[line].new_line || !comments[line].comment.begins_with("##")) {
- break;
- }
+ DocLineState state = DOC_LINE_NORMAL;
+ MemberDocData result;
+ while (line <= p_line) {
String doc_line = comments[line].comment.trim_prefix("##");
line++;
@@ -3513,35 +3503,22 @@ GDScriptParser::MemberDocData GDScriptParser::parse_doc_comment(int p_line, bool
return result;
}
-GDScriptParser::ClassDocData GDScriptParser::parse_class_doc_comment(int p_line, bool p_inner_class, bool p_single_line) {
- ClassDocData result;
+GDScriptParser::ClassDocData GDScriptParser::parse_class_doc_comment(int p_line, bool p_single_line) {
+ ERR_FAIL_COND_V(!has_comment(p_line, true), ClassDocData());
const HashMap<int, GDScriptTokenizer::CommentData> &comments = tokenizer.get_comments();
- ERR_FAIL_COND_V(!comments.has(p_line), result);
-
- if (p_single_line) {
- if (comments[p_line].comment.begins_with("##")) {
- result.brief = comments[p_line].comment.trim_prefix("##").strip_edges();
- return result;
- }
- return result;
- }
-
int line = p_line;
- DocLineState state = DOC_LINE_NORMAL;
- bool is_in_brief = true;
- if (p_inner_class) {
- while (comments.has(line - 1)) {
- if (!comments[line - 1].new_line || !comments[line - 1].comment.begins_with("##")) {
- break;
- }
+ if (!p_single_line) {
+ while (comments.has(line - 1) && comments[line - 1].new_line && comments[line - 1].comment.begins_with("##")) {
line--;
}
}
+ max_script_doc_line = MIN(max_script_doc_line, line - 1);
+
String space_prefix;
- if (comments.has(line) && comments[line].comment.begins_with("##")) {
+ {
int i = 2;
for (; i < comments[line].comment.length(); i++) {
if (comments[line].comment[i] != ' ') {
@@ -3551,11 +3528,11 @@ GDScriptParser::ClassDocData GDScriptParser::parse_class_doc_comment(int p_line,
space_prefix = String(" ").repeat(i - 2);
}
- while (comments.has(line)) {
- if (!comments[line].new_line || !comments[line].comment.begins_with("##")) {
- break;
- }
+ DocLineState state = DOC_LINE_NORMAL;
+ bool is_in_brief = true;
+ ClassDocData result;
+ while (line <= p_line) {
String doc_line = comments[line].comment.trim_prefix("##");
line++;
@@ -3630,14 +3607,6 @@ GDScriptParser::ClassDocData GDScriptParser::parse_class_doc_comment(int p_line,
}
}
- if (current_class->members.size() > 0) {
- const ClassNode::Member &m = current_class->members[0];
- int first_member_line = m.get_line();
- if (first_member_line == line) {
- result = ClassDocData(); // Clear result.
- }
- }
-
return result;
}
#endif // TOOLS_ENABLED
@@ -4095,25 +4064,29 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
}
} break;
case GDScriptParser::DataType::ENUM: {
- variable->export_info.type = Variant::INT;
- variable->export_info.hint = PROPERTY_HINT_ENUM;
-
- String enum_hint_string;
- bool first = true;
- for (const KeyValue<StringName, int64_t> &E : export_type.enum_values) {
- if (!first) {
- enum_hint_string += ",";
- } else {
- first = false;
+ if (export_type.is_meta_type) {
+ variable->export_info.type = Variant::DICTIONARY;
+ } else {
+ variable->export_info.type = Variant::INT;
+ variable->export_info.hint = PROPERTY_HINT_ENUM;
+
+ String enum_hint_string;
+ bool first = true;
+ for (const KeyValue<StringName, int64_t> &E : export_type.enum_values) {
+ if (!first) {
+ enum_hint_string += ",";
+ } else {
+ first = false;
+ }
+ enum_hint_string += E.key.operator String().capitalize().xml_escape();
+ enum_hint_string += ":";
+ enum_hint_string += String::num_int64(E.value).xml_escape();
}
- enum_hint_string += E.key.operator String().capitalize().xml_escape();
- enum_hint_string += ":";
- enum_hint_string += String::num_int64(E.value).xml_escape();
- }
- variable->export_info.hint_string = enum_hint_string;
- variable->export_info.usage |= PROPERTY_USAGE_CLASS_IS_ENUM;
- variable->export_info.class_name = String(export_type.native_type).replace("::", ".");
+ variable->export_info.hint_string = enum_hint_string;
+ variable->export_info.usage |= PROPERTY_USAGE_CLASS_IS_ENUM;
+ variable->export_info.class_name = String(export_type.native_type).replace("::", ".");
+ }
} break;
default:
push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)", variable);
diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h
index 27d4d0fb47..988524d058 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.h
@@ -1517,10 +1517,11 @@ private:
TypeNode *parse_type(bool p_allow_void = false);
#ifdef TOOLS_ENABLED
- int class_doc_line = 0x7FFFFFFF;
+ int max_script_doc_line = INT_MAX;
+ int min_member_doc_line = 1;
bool has_comment(int p_line, bool p_must_be_doc = false);
MemberDocData parse_doc_comment(int p_line, bool p_single_line = false);
- ClassDocData parse_class_doc_comment(int p_line, bool p_inner_class, bool p_single_line = false);
+ ClassDocData parse_class_doc_comment(int p_line, bool p_single_line = false);
#endif // TOOLS_ENABLED
public:
diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp
index 42b983ef45..07f2b8b406 100644
--- a/modules/gdscript/gdscript_tokenizer.cpp
+++ b/modules/gdscript/gdscript_tokenizer.cpp
@@ -857,10 +857,14 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() {
STRING_NODEPATH,
};
+ bool is_raw = false;
bool is_multiline = false;
StringType type = STRING_REGULAR;
- if (_peek(-1) == '&') {
+ if (_peek(-1) == 'r') {
+ is_raw = true;
+ _advance();
+ } else if (_peek(-1) == '&') {
type = STRING_NAME;
_advance();
} else if (_peek(-1) == '^') {
@@ -890,7 +894,12 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() {
char32_t ch = _peek();
if (ch == 0x200E || ch == 0x200F || (ch >= 0x202A && ch <= 0x202E) || (ch >= 0x2066 && ch <= 0x2069)) {
- Token error = make_error("Invisible text direction control character present in the string, escape it (\"\\u" + String::num_int64(ch, 16) + "\") to avoid confusion.");
+ Token error;
+ if (is_raw) {
+ error = make_error("Invisible text direction control character present in the string, use regular string literal instead of r-string.");
+ } else {
+ error = make_error("Invisible text direction control character present in the string, escape it (\"\\u" + String::num_int64(ch, 16) + "\") to avoid confusion.");
+ }
error.start_column = column;
error.leftmost_column = error.start_column;
error.end_column = column + 1;
@@ -905,144 +914,164 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() {
return make_error("Unterminated string.");
}
- // Grab escape character.
- char32_t code = _peek();
- _advance();
- if (_is_at_end()) {
- return make_error("Unterminated string.");
- }
+ if (is_raw) {
+ if (_peek() == quote_char) {
+ _advance();
+ if (_is_at_end()) {
+ return make_error("Unterminated string.");
+ }
+ result += '\\';
+ result += quote_char;
+ } else if (_peek() == '\\') { // For `\\\"`.
+ _advance();
+ if (_is_at_end()) {
+ return make_error("Unterminated string.");
+ }
+ result += '\\';
+ result += '\\';
+ } else {
+ result += '\\';
+ }
+ } else {
+ // Grab escape character.
+ char32_t code = _peek();
+ _advance();
+ if (_is_at_end()) {
+ return make_error("Unterminated string.");
+ }
- char32_t escaped = 0;
- bool valid_escape = true;
+ char32_t escaped = 0;
+ bool valid_escape = true;
- switch (code) {
- case 'a':
- escaped = '\a';
- break;
- case 'b':
- escaped = '\b';
- break;
- case 'f':
- escaped = '\f';
- break;
- case 'n':
- escaped = '\n';
- break;
- case 'r':
- escaped = '\r';
- break;
- case 't':
- escaped = '\t';
- break;
- case 'v':
- escaped = '\v';
- break;
- case '\'':
- escaped = '\'';
- break;
- case '\"':
- escaped = '\"';
- break;
- case '\\':
- escaped = '\\';
- break;
- case 'U':
- case 'u': {
- // Hexadecimal sequence.
- int hex_len = (code == 'U') ? 6 : 4;
- for (int j = 0; j < hex_len; j++) {
- if (_is_at_end()) {
- return make_error("Unterminated string.");
+ switch (code) {
+ case 'a':
+ escaped = '\a';
+ break;
+ case 'b':
+ escaped = '\b';
+ break;
+ case 'f':
+ escaped = '\f';
+ break;
+ case 'n':
+ escaped = '\n';
+ break;
+ case 'r':
+ escaped = '\r';
+ break;
+ case 't':
+ escaped = '\t';
+ break;
+ case 'v':
+ escaped = '\v';
+ break;
+ case '\'':
+ escaped = '\'';
+ break;
+ case '\"':
+ escaped = '\"';
+ break;
+ case '\\':
+ escaped = '\\';
+ break;
+ case 'U':
+ case 'u': {
+ // Hexadecimal sequence.
+ int hex_len = (code == 'U') ? 6 : 4;
+ for (int j = 0; j < hex_len; j++) {
+ if (_is_at_end()) {
+ return make_error("Unterminated string.");
+ }
+
+ char32_t digit = _peek();
+ char32_t value = 0;
+ if (is_digit(digit)) {
+ value = digit - '0';
+ } else if (digit >= 'a' && digit <= 'f') {
+ value = digit - 'a';
+ value += 10;
+ } else if (digit >= 'A' && digit <= 'F') {
+ value = digit - 'A';
+ value += 10;
+ } else {
+ // Make error, but keep parsing the string.
+ Token error = make_error("Invalid hexadecimal digit in unicode escape sequence.");
+ error.start_column = column;
+ error.leftmost_column = error.start_column;
+ error.end_column = column + 1;
+ error.rightmost_column = error.end_column;
+ push_error(error);
+ valid_escape = false;
+ break;
+ }
+
+ escaped <<= 4;
+ escaped |= value;
+
+ _advance();
}
-
- char32_t digit = _peek();
- char32_t value = 0;
- if (is_digit(digit)) {
- value = digit - '0';
- } else if (digit >= 'a' && digit <= 'f') {
- value = digit - 'a';
- value += 10;
- } else if (digit >= 'A' && digit <= 'F') {
- value = digit - 'A';
- value += 10;
- } else {
- // Make error, but keep parsing the string.
- Token error = make_error("Invalid hexadecimal digit in unicode escape sequence.");
- error.start_column = column;
- error.leftmost_column = error.start_column;
- error.end_column = column + 1;
- error.rightmost_column = error.end_column;
- push_error(error);
- valid_escape = false;
+ } break;
+ case '\r':
+ if (_peek() != '\n') {
+ // Carriage return without newline in string. (???)
+ // Just add it to the string and keep going.
+ result += ch;
+ _advance();
break;
}
-
- escaped <<= 4;
- escaped |= value;
-
- _advance();
- }
- } break;
- case '\r':
- if (_peek() != '\n') {
- // Carriage return without newline in string. (???)
- // Just add it to the string and keep going.
- result += ch;
- _advance();
+ [[fallthrough]];
+ case '\n':
+ // Escaping newline.
+ newline(false);
+ valid_escape = false; // Don't add to the string.
break;
- }
- [[fallthrough]];
- case '\n':
- // Escaping newline.
- newline(false);
- valid_escape = false; // Don't add to the string.
- break;
- default:
- Token error = make_error("Invalid escape in string.");
- error.start_column = column - 2;
- error.leftmost_column = error.start_column;
- push_error(error);
- valid_escape = false;
- break;
- }
- // Parse UTF-16 pair.
- if (valid_escape) {
- if ((escaped & 0xfffffc00) == 0xd800) {
- if (prev == 0) {
- prev = escaped;
- prev_pos = column - 2;
- continue;
- } else {
- Token error = make_error("Invalid UTF-16 sequence in string, unpaired lead surrogate");
+ default:
+ Token error = make_error("Invalid escape in string.");
error.start_column = column - 2;
error.leftmost_column = error.start_column;
push_error(error);
valid_escape = false;
- prev = 0;
+ break;
+ }
+ // Parse UTF-16 pair.
+ if (valid_escape) {
+ if ((escaped & 0xfffffc00) == 0xd800) {
+ if (prev == 0) {
+ prev = escaped;
+ prev_pos = column - 2;
+ continue;
+ } else {
+ Token error = make_error("Invalid UTF-16 sequence in string, unpaired lead surrogate.");
+ error.start_column = column - 2;
+ error.leftmost_column = error.start_column;
+ push_error(error);
+ valid_escape = false;
+ prev = 0;
+ }
+ } else if ((escaped & 0xfffffc00) == 0xdc00) {
+ if (prev == 0) {
+ Token error = make_error("Invalid UTF-16 sequence in string, unpaired trail surrogate.");
+ error.start_column = column - 2;
+ error.leftmost_column = error.start_column;
+ push_error(error);
+ valid_escape = false;
+ } else {
+ escaped = (prev << 10UL) + escaped - ((0xd800 << 10UL) + 0xdc00 - 0x10000);
+ prev = 0;
+ }
}
- } else if ((escaped & 0xfffffc00) == 0xdc00) {
- if (prev == 0) {
- Token error = make_error("Invalid UTF-16 sequence in string, unpaired trail surrogate");
- error.start_column = column - 2;
+ if (prev != 0) {
+ Token error = make_error("Invalid UTF-16 sequence in string, unpaired lead surrogate.");
+ error.start_column = prev_pos;
error.leftmost_column = error.start_column;
push_error(error);
- valid_escape = false;
- } else {
- escaped = (prev << 10UL) + escaped - ((0xd800 << 10UL) + 0xdc00 - 0x10000);
prev = 0;
}
}
- if (prev != 0) {
- Token error = make_error("Invalid UTF-16 sequence in string, unpaired lead surrogate");
- error.start_column = prev_pos;
- error.leftmost_column = error.start_column;
- push_error(error);
- prev = 0;
- }
- }
- if (valid_escape) {
- result += escaped;
+ if (valid_escape) {
+ result += escaped;
+ }
}
} else if (ch == quote_char) {
if (prev != 0) {
@@ -1216,7 +1245,7 @@ void GDScriptTokenizer::check_indent() {
if (line_continuation || multiline_mode) {
// We cleared up all the whitespace at the beginning of the line.
- // But if this is a continuation or multiline mode and we don't want any indentation change.
+ // If this is a line continuation or we're in multiline mode then we don't want any indentation changes.
return;
}
@@ -1416,6 +1445,9 @@ GDScriptTokenizer::Token GDScriptTokenizer::scan() {
if (is_digit(c)) {
return number();
+ } else if (c == 'r' && (_peek() == '"' || _peek() == '\'')) {
+ // Raw string literals.
+ return string();
} else if (is_unicode_identifier_start(c)) {
return potential_identifier();
}
diff --git a/modules/gdscript/gdscript_tokenizer.h b/modules/gdscript/gdscript_tokenizer.h
index 068393cee9..f916407b18 100644
--- a/modules/gdscript/gdscript_tokenizer.h
+++ b/modules/gdscript/gdscript_tokenizer.h
@@ -187,6 +187,8 @@ public:
#ifdef TOOLS_ENABLED
struct CommentData {
String comment;
+ // true: Comment starts at beginning of line or after indentation.
+ // false: Inline comment (starts after some code).
bool new_line = false;
CommentData() {}
CommentData(const String &p_comment, bool p_new_line) {
diff --git a/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.gd b/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.gd
new file mode 100644
index 0000000000..dafd2ec0c8
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.gd
@@ -0,0 +1,17 @@
+class_name TestExportEnumAsDictionary
+
+enum MyEnum {A, B, C}
+
+const Utils = preload("../../utils.notest.gd")
+
+@export var x1 = MyEnum
+@export var x2 = MyEnum.A
+@export var x3 := MyEnum
+@export var x4 := MyEnum.A
+@export var x5: MyEnum
+
+func test():
+ for property in get_property_list():
+ if property.usage & PROPERTY_USAGE_SCRIPT_VARIABLE:
+ print(Utils.get_property_signature(property))
+ print(" ", Utils.get_property_additional_info(property))
diff --git a/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.out b/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.out
new file mode 100644
index 0000000000..f1a13f1045
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.out
@@ -0,0 +1,11 @@
+GDTEST_OK
+@export var x1: Dictionary
+ hint=NONE hint_string="" usage=DEFAULT|SCRIPT_VARIABLE
+@export var x2: TestExportEnumAsDictionary.MyEnum
+ hint=ENUM hint_string="A:0,B:1,C:2" usage=DEFAULT|SCRIPT_VARIABLE|CLASS_IS_ENUM
+@export var x3: Dictionary
+ hint=NONE hint_string="" usage=DEFAULT|SCRIPT_VARIABLE
+@export var x4: TestExportEnumAsDictionary.MyEnum
+ hint=ENUM hint_string="A:0,B:1,C:2" usage=DEFAULT|SCRIPT_VARIABLE|CLASS_IS_ENUM
+@export var x5: TestExportEnumAsDictionary.MyEnum
+ hint=ENUM hint_string="A:0,B:1,C:2" usage=DEFAULT|SCRIPT_VARIABLE|CLASS_IS_ENUM
diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_array_dont_make_literal_typed_with_weak_type.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_array_dont_make_literal_typed_with_weak_type.gd
new file mode 100644
index 0000000000..e1a1f07e47
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/typed_array_dont_make_literal_typed_with_weak_type.gd
@@ -0,0 +1,22 @@
+var _typed_array: Array[int]
+
+func weak_param_func(weak_param = _typed_array):
+ weak_param = [11] # Don't treat the literal as typed!
+ return weak_param
+
+func hard_param_func(hard_param := _typed_array):
+ hard_param = [12]
+ return hard_param
+
+func test():
+ var weak_var = _typed_array
+ print(weak_var.is_typed())
+ weak_var = [21] # Don't treat the literal as typed!
+ print(weak_var.is_typed())
+ print(weak_param_func().is_typed())
+
+ var hard_var := _typed_array
+ print(hard_var.is_typed())
+ hard_var = [22]
+ print(hard_var.is_typed())
+ print(hard_param_func().is_typed())
diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_array_dont_make_literal_typed_with_weak_type.out b/modules/gdscript/tests/scripts/analyzer/features/typed_array_dont_make_literal_typed_with_weak_type.out
new file mode 100644
index 0000000000..34b18dbe7c
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/typed_array_dont_make_literal_typed_with_weak_type.out
@@ -0,0 +1,7 @@
+GDTEST_OK
+true
+false
+false
+true
+true
+true
diff --git a/modules/gdscript/tests/scripts/parser/errors/bad_r_string_1.gd b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_1.gd
new file mode 100644
index 0000000000..e5eecbb819
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_1.gd
@@ -0,0 +1,2 @@
+func test():
+ print(r"\")
diff --git a/modules/gdscript/tests/scripts/parser/errors/bad_r_string_1.out b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_1.out
new file mode 100644
index 0000000000..c8e843b0d7
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_1.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Unterminated string.
diff --git a/modules/gdscript/tests/scripts/parser/errors/bad_r_string_2.gd b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_2.gd
new file mode 100644
index 0000000000..9168b69f86
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_2.gd
@@ -0,0 +1,2 @@
+func test():
+ print(r"\\"")
diff --git a/modules/gdscript/tests/scripts/parser/errors/bad_r_string_2.out b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_2.out
new file mode 100644
index 0000000000..c8e843b0d7
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_2.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Unterminated string.
diff --git a/modules/gdscript/tests/scripts/parser/errors/bad_r_string_3.gd b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_3.gd
new file mode 100644
index 0000000000..37dc910e5f
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_3.gd
@@ -0,0 +1,3 @@
+func test():
+ # v
+ print(r"['"]*")
diff --git a/modules/gdscript/tests/scripts/parser/errors/bad_r_string_3.out b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_3.out
new file mode 100644
index 0000000000..dcb5c2f289
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_3.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Closing "]" doesn't have an opening counterpart.
diff --git a/modules/gdscript/tests/scripts/parser/features/r_strings.gd b/modules/gdscript/tests/scripts/parser/features/r_strings.gd
new file mode 100644
index 0000000000..6f546f28be
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/r_strings.gd
@@ -0,0 +1,22 @@
+func test():
+ print(r"test ' \' \" \\ \n \t \u2023 test")
+ print(r"\n\\[\t ]*(\w+)")
+ print(r"")
+ print(r"\"")
+ print(r"\\\"")
+ print(r"\\")
+ print(r"\" \\\" \\\\\"")
+ print(r"\ \\ \\\ \\\\ \\\\\ \\")
+ print(r'"')
+ print(r'"(?:\\.|[^"])*"')
+ print(r"""""")
+ print(r"""test \t "test"="" " \" \\\" \ \\ \\\ test""")
+ print(r'''r"""test \t "test"="" " \" \\\" \ \\ \\\ test"""''')
+ print(r"\t
+ \t")
+ print(r"\t \
+ \t")
+ print(r"""\t
+ \t""")
+ print(r"""\t \
+ \t""")
diff --git a/modules/gdscript/tests/scripts/parser/features/r_strings.out b/modules/gdscript/tests/scripts/parser/features/r_strings.out
new file mode 100644
index 0000000000..114ef0a6c3
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/r_strings.out
@@ -0,0 +1,22 @@
+GDTEST_OK
+test ' \' \" \\ \n \t \u2023 test
+\n\\[\t ]*(\w+)
+
+\"
+\\\"
+\\
+\" \\\" \\\\\"
+\ \\ \\\ \\\\ \\\\\ \\
+"
+"(?:\\.|[^"])*"
+
+test \t "test"="" " \" \\\" \ \\ \\\ test
+r"""test \t "test"="" " \" \\\" \ \\ \\\ test"""
+\t
+ \t
+\t \
+ \t
+\t
+ \t
+\t \
+ \t
diff --git a/modules/gdscript/tests/scripts/utils.notest.gd b/modules/gdscript/tests/scripts/utils.notest.gd
index 50444e62a1..fb20817117 100644
--- a/modules/gdscript/tests/scripts/utils.notest.gd
+++ b/modules/gdscript/tests/scripts/utils.notest.gd
@@ -19,6 +19,7 @@ static func get_type(property: Dictionary, is_return: bool = false) -> String:
return property.class_name
return variant_get_type_name(property.type)
+
static func get_property_signature(property: Dictionary, is_static: bool = false) -> String:
var result: String = ""
if not (property.usage & PROPERTY_USAGE_SCRIPT_VARIABLE):
@@ -30,6 +31,15 @@ static func get_property_signature(property: Dictionary, is_static: bool = false
result += "var " + property.name + ": " + get_type(property)
return result
+
+static func get_property_additional_info(property: Dictionary) -> String:
+ return 'hint=%s hint_string="%s" usage=%s' % [
+ get_property_hint_name(property.hint).trim_prefix("PROPERTY_HINT_"),
+ str(property.hint_string).c_escape(),
+ get_property_usage_string(property.usage).replace("PROPERTY_USAGE_", ""),
+ ]
+
+
static func get_method_signature(method: Dictionary, is_signal: bool = false) -> String:
var result: String = ""
if method.flags & METHOD_FLAG_STATIC:
@@ -55,6 +65,7 @@ static func get_method_signature(method: Dictionary, is_signal: bool = false) ->
result += " -> " + get_type(method.return, true)
return result
+
static func variant_get_type_name(type: Variant.Type) -> String:
match type:
TYPE_NIL:
@@ -135,3 +146,136 @@ static func variant_get_type_name(type: Variant.Type) -> String:
return "PackedColorArray"
push_error("Argument `type` is invalid. Use `TYPE_*` constants.")
return "<invalid type>"
+
+
+static func get_property_hint_name(hint: PropertyHint) -> String:
+ match hint:
+ PROPERTY_HINT_NONE:
+ return "PROPERTY_HINT_NONE"
+ PROPERTY_HINT_RANGE:
+ return "PROPERTY_HINT_RANGE"
+ PROPERTY_HINT_ENUM:
+ return "PROPERTY_HINT_ENUM"
+ PROPERTY_HINT_ENUM_SUGGESTION:
+ return "PROPERTY_HINT_ENUM_SUGGESTION"
+ PROPERTY_HINT_EXP_EASING:
+ return "PROPERTY_HINT_EXP_EASING"
+ PROPERTY_HINT_LINK:
+ return "PROPERTY_HINT_LINK"
+ PROPERTY_HINT_FLAGS:
+ return "PROPERTY_HINT_FLAGS"
+ PROPERTY_HINT_LAYERS_2D_RENDER:
+ return "PROPERTY_HINT_LAYERS_2D_RENDER"
+ PROPERTY_HINT_LAYERS_2D_PHYSICS:
+ return "PROPERTY_HINT_LAYERS_2D_PHYSICS"
+ PROPERTY_HINT_LAYERS_2D_NAVIGATION:
+ return "PROPERTY_HINT_LAYERS_2D_NAVIGATION"
+ PROPERTY_HINT_LAYERS_3D_RENDER:
+ return "PROPERTY_HINT_LAYERS_3D_RENDER"
+ PROPERTY_HINT_LAYERS_3D_PHYSICS:
+ return "PROPERTY_HINT_LAYERS_3D_PHYSICS"
+ PROPERTY_HINT_LAYERS_3D_NAVIGATION:
+ return "PROPERTY_HINT_LAYERS_3D_NAVIGATION"
+ PROPERTY_HINT_LAYERS_AVOIDANCE:
+ return "PROPERTY_HINT_LAYERS_AVOIDANCE"
+ PROPERTY_HINT_FILE:
+ return "PROPERTY_HINT_FILE"
+ PROPERTY_HINT_DIR:
+ return "PROPERTY_HINT_DIR"
+ PROPERTY_HINT_GLOBAL_FILE:
+ return "PROPERTY_HINT_GLOBAL_FILE"
+ PROPERTY_HINT_GLOBAL_DIR:
+ return "PROPERTY_HINT_GLOBAL_DIR"
+ PROPERTY_HINT_RESOURCE_TYPE:
+ return "PROPERTY_HINT_RESOURCE_TYPE"
+ PROPERTY_HINT_MULTILINE_TEXT:
+ return "PROPERTY_HINT_MULTILINE_TEXT"
+ PROPERTY_HINT_EXPRESSION:
+ return "PROPERTY_HINT_EXPRESSION"
+ PROPERTY_HINT_PLACEHOLDER_TEXT:
+ return "PROPERTY_HINT_PLACEHOLDER_TEXT"
+ PROPERTY_HINT_COLOR_NO_ALPHA:
+ return "PROPERTY_HINT_COLOR_NO_ALPHA"
+ PROPERTY_HINT_OBJECT_ID:
+ return "PROPERTY_HINT_OBJECT_ID"
+ PROPERTY_HINT_TYPE_STRING:
+ return "PROPERTY_HINT_TYPE_STRING"
+ PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE:
+ return "PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE"
+ PROPERTY_HINT_OBJECT_TOO_BIG:
+ return "PROPERTY_HINT_OBJECT_TOO_BIG"
+ PROPERTY_HINT_NODE_PATH_VALID_TYPES:
+ return "PROPERTY_HINT_NODE_PATH_VALID_TYPES"
+ PROPERTY_HINT_SAVE_FILE:
+ return "PROPERTY_HINT_SAVE_FILE"
+ PROPERTY_HINT_GLOBAL_SAVE_FILE:
+ return "PROPERTY_HINT_GLOBAL_SAVE_FILE"
+ PROPERTY_HINT_INT_IS_OBJECTID:
+ return "PROPERTY_HINT_INT_IS_OBJECTID"
+ PROPERTY_HINT_INT_IS_POINTER:
+ return "PROPERTY_HINT_INT_IS_POINTER"
+ PROPERTY_HINT_ARRAY_TYPE:
+ return "PROPERTY_HINT_ARRAY_TYPE"
+ PROPERTY_HINT_LOCALE_ID:
+ return "PROPERTY_HINT_LOCALE_ID"
+ PROPERTY_HINT_LOCALIZABLE_STRING:
+ return "PROPERTY_HINT_LOCALIZABLE_STRING"
+ PROPERTY_HINT_NODE_TYPE:
+ return "PROPERTY_HINT_NODE_TYPE"
+ PROPERTY_HINT_HIDE_QUATERNION_EDIT:
+ return "PROPERTY_HINT_HIDE_QUATERNION_EDIT"
+ PROPERTY_HINT_PASSWORD:
+ return "PROPERTY_HINT_PASSWORD"
+ push_error("Argument `hint` is invalid. Use `PROPERTY_HINT_*` constants.")
+ return "<invalid hint>"
+
+
+static func get_property_usage_string(usage: int) -> String:
+ if usage == PROPERTY_USAGE_NONE:
+ return "PROPERTY_USAGE_NONE"
+
+ const FLAGS: Array[Array] = [
+ [PROPERTY_USAGE_DEFAULT, "PROPERTY_USAGE_DEFAULT"],
+ [PROPERTY_USAGE_STORAGE, "PROPERTY_USAGE_STORAGE"],
+ [PROPERTY_USAGE_EDITOR, "PROPERTY_USAGE_EDITOR"],
+ [PROPERTY_USAGE_INTERNAL, "PROPERTY_USAGE_INTERNAL"],
+ [PROPERTY_USAGE_CHECKABLE, "PROPERTY_USAGE_CHECKABLE"],
+ [PROPERTY_USAGE_CHECKED, "PROPERTY_USAGE_CHECKED"],
+ [PROPERTY_USAGE_GROUP, "PROPERTY_USAGE_GROUP"],
+ [PROPERTY_USAGE_CATEGORY, "PROPERTY_USAGE_CATEGORY"],
+ [PROPERTY_USAGE_SUBGROUP, "PROPERTY_USAGE_SUBGROUP"],
+ [PROPERTY_USAGE_CLASS_IS_BITFIELD, "PROPERTY_USAGE_CLASS_IS_BITFIELD"],
+ [PROPERTY_USAGE_NO_INSTANCE_STATE, "PROPERTY_USAGE_NO_INSTANCE_STATE"],
+ [PROPERTY_USAGE_RESTART_IF_CHANGED, "PROPERTY_USAGE_RESTART_IF_CHANGED"],
+ [PROPERTY_USAGE_SCRIPT_VARIABLE, "PROPERTY_USAGE_SCRIPT_VARIABLE"],
+ [PROPERTY_USAGE_STORE_IF_NULL, "PROPERTY_USAGE_STORE_IF_NULL"],
+ [PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED, "PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED"],
+ [PROPERTY_USAGE_SCRIPT_DEFAULT_VALUE, "PROPERTY_USAGE_SCRIPT_DEFAULT_VALUE"],
+ [PROPERTY_USAGE_CLASS_IS_ENUM, "PROPERTY_USAGE_CLASS_IS_ENUM"],
+ [PROPERTY_USAGE_NIL_IS_VARIANT, "PROPERTY_USAGE_NIL_IS_VARIANT"],
+ [PROPERTY_USAGE_ARRAY, "PROPERTY_USAGE_ARRAY"],
+ [PROPERTY_USAGE_ALWAYS_DUPLICATE, "PROPERTY_USAGE_ALWAYS_DUPLICATE"],
+ [PROPERTY_USAGE_NEVER_DUPLICATE, "PROPERTY_USAGE_NEVER_DUPLICATE"],
+ [PROPERTY_USAGE_HIGH_END_GFX, "PROPERTY_USAGE_HIGH_END_GFX"],
+ [PROPERTY_USAGE_NODE_PATH_FROM_SCENE_ROOT, "PROPERTY_USAGE_NODE_PATH_FROM_SCENE_ROOT"],
+ [PROPERTY_USAGE_RESOURCE_NOT_PERSISTENT, "PROPERTY_USAGE_RESOURCE_NOT_PERSISTENT"],
+ [PROPERTY_USAGE_KEYING_INCREMENTS, "PROPERTY_USAGE_KEYING_INCREMENTS"],
+ [PROPERTY_USAGE_DEFERRED_SET_RESOURCE, "PROPERTY_USAGE_DEFERRED_SET_RESOURCE"],
+ [PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT, "PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT"],
+ [PROPERTY_USAGE_EDITOR_BASIC_SETTING, "PROPERTY_USAGE_EDITOR_BASIC_SETTING"],
+ [PROPERTY_USAGE_READ_ONLY, "PROPERTY_USAGE_READ_ONLY"],
+ [PROPERTY_USAGE_SECRET, "PROPERTY_USAGE_SECRET"],
+ ]
+
+ var result: String = ""
+
+ for flag in FLAGS:
+ if usage & flag[0]:
+ result += flag[1] + "|"
+ usage &= ~flag[0]
+
+ if usage != PROPERTY_USAGE_NONE:
+ push_error("Argument `usage` is invalid. Use `PROPERTY_USAGE_*` constants.")
+ return "<invalid usage flags>"
+
+ return result.left(-1)
diff --git a/modules/gridmap/grid_map.cpp b/modules/gridmap/grid_map.cpp
index 4ac143c7ff..6f493f48e3 100644
--- a/modules/gridmap/grid_map.cpp
+++ b/modules/gridmap/grid_map.cpp
@@ -267,6 +267,7 @@ void GridMap::set_mesh_library(const Ref<MeshLibrary> &p_mesh_library) {
}
_recreate_octant_data();
+ emit_signal(CoreStringNames::get_singleton()->changed);
}
Ref<MeshLibrary> GridMap::get_mesh_library() const {
diff --git a/modules/lightmapper_rd/lightmapper_rd.cpp b/modules/lightmapper_rd/lightmapper_rd.cpp
index 748e8ae50c..4ed730b3af 100644
--- a/modules/lightmapper_rd/lightmapper_rd.cpp
+++ b/modules/lightmapper_rd/lightmapper_rd.cpp
@@ -589,8 +589,12 @@ void LightmapperRD::_raster_geometry(RenderingDevice *rd, Size2i atlas_size, int
raster_push_constant.grid_size[0] = grid_size;
raster_push_constant.grid_size[1] = grid_size;
raster_push_constant.grid_size[2] = grid_size;
- raster_push_constant.uv_offset[0] = 0;
- raster_push_constant.uv_offset[1] = 0;
+
+ // Half pixel offset is required so the rasterizer doesn't output face edges directly aligned into pixels.
+ // This fixes artifacts where the pixel would be traced from the edge of a face, causing half the rays to
+ // be outside of the boundaries of the geometry. See <https://github.com/godotengine/godot/issues/69126>.
+ raster_push_constant.uv_offset[0] = -0.5f / float(atlas_size.x);
+ raster_push_constant.uv_offset[1] = -0.5f / float(atlas_size.y);
RD::DrawListID draw_list = rd->draw_list_begin(framebuffers[i], RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_READ, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, clear_colors);
//draw opaque
@@ -1579,8 +1583,8 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
{
seams_push_constant.base_index = seam_offset;
rd->draw_list_bind_render_pipeline(draw_list, blendseams_line_raster_pipeline);
- seams_push_constant.uv_offset[0] = uv_offsets[0].x / float(atlas_size.width);
- seams_push_constant.uv_offset[1] = uv_offsets[0].y / float(atlas_size.height);
+ seams_push_constant.uv_offset[0] = (uv_offsets[0].x - 0.5f) / float(atlas_size.width);
+ seams_push_constant.uv_offset[1] = (uv_offsets[0].y - 0.5f) / float(atlas_size.height);
seams_push_constant.blend = uv_offsets[0].z;
rd->draw_list_set_push_constant(draw_list, &seams_push_constant, sizeof(RasterSeamsPushConstant));
@@ -1603,8 +1607,8 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
for (int j = 1; j < uv_offset_count; j++) {
seams_push_constant.base_index = seam_offset;
- seams_push_constant.uv_offset[0] = uv_offsets[j].x / float(atlas_size.width);
- seams_push_constant.uv_offset[1] = uv_offsets[j].y / float(atlas_size.height);
+ seams_push_constant.uv_offset[0] = (uv_offsets[j].x - 0.5f) / float(atlas_size.width);
+ seams_push_constant.uv_offset[1] = (uv_offsets[j].y - 0.5f) / float(atlas_size.height);
seams_push_constant.blend = uv_offsets[0].z;
rd->draw_list_set_push_constant(draw_list, &seams_push_constant, sizeof(RasterSeamsPushConstant));
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp
index b41f2155f8..26c684f769 100644
--- a/modules/mono/csharp_script.cpp
+++ b/modules/mono/csharp_script.cpp
@@ -2353,6 +2353,8 @@ void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) {
mi.arguments.push_back(arg_info);
}
+ mi.flags = (uint32_t)method_info_dict["flags"];
+
p_script->methods.set(push_index++, CSharpMethodInfo{ name, mi });
}
@@ -2602,6 +2604,18 @@ MethodInfo CSharpScript::get_method_info(const StringName &p_method) const {
return MethodInfo();
}
+Variant CSharpScript::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
+ ERR_FAIL_COND_V(!valid, Variant());
+
+ Variant ret;
+ bool ok = GDMonoCache::managed_callbacks.ScriptManagerBridge_CallStatic(this, &p_method, p_args, p_argcount, &r_error, &ret);
+ if (ok) {
+ return ret;
+ }
+
+ return Script::callp(p_method, p_args, p_argcount, r_error);
+}
+
Error CSharpScript::reload(bool p_keep_state) {
if (!reload_invalidated) {
return OK;
diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h
index 33862016a4..fd9e281e63 100644
--- a/modules/mono/csharp_script.h
+++ b/modules/mono/csharp_script.h
@@ -199,6 +199,7 @@ public:
void get_script_method_list(List<MethodInfo> *p_list) const override;
bool has_method(const StringName &p_method) const override;
MethodInfo get_method_info(const StringName &p_method) const override;
+ Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override;
int get_member_line(const StringName &p_member) const override;
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs
index 5ea0ca53c3..7b643914bb 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs
@@ -125,7 +125,7 @@ namespace Godot.SourceGenerators
var members = symbol.GetMembers();
var methodSymbols = members
- .Where(s => !s.IsStatic && s.Kind == SymbolKind.Method && !s.IsImplicitlyDeclared)
+ .Where(s => s.Kind == SymbolKind.Method && !s.IsImplicitlyDeclared)
.Cast<IMethodSymbol>()
.Where(m => m.MethodKind == MethodKind.Ordinary);
@@ -221,6 +221,29 @@ namespace Godot.SourceGenerators
source.Append(" }\n");
}
+ // Generate InvokeGodotClassStaticMethod
+
+ var godotClassStaticMethods = godotClassMethods.Where(m => m.Method.IsStatic).ToArray();
+
+ if (godotClassStaticMethods.Length > 0)
+ {
+ source.Append("#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword\n");
+ source.Append(" [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]\n");
+ source.Append(" internal new static bool InvokeGodotClassStaticMethod(in godot_string_name method, ");
+ source.Append("NativeVariantPtrArgs args, out godot_variant ret)\n {\n");
+
+ foreach (var method in godotClassStaticMethods)
+ {
+ GenerateMethodInvoker(method, source);
+ }
+
+ source.Append(" ret = default;\n");
+ source.Append(" return false;\n");
+ source.Append(" }\n");
+
+ source.Append("#pragma warning restore CS0109\n");
+ }
+
// Generate HasGodotClassMethod
if (distinctMethodNames.Length > 0)
@@ -356,7 +379,14 @@ namespace Godot.SourceGenerators
arguments = null;
}
- return new MethodInfo(method.Method.Name, returnVal, MethodFlags.Default, arguments,
+ MethodFlags flags = MethodFlags.Default;
+
+ if (method.Method.IsStatic)
+ {
+ flags |= MethodFlags.Static;
+ }
+
+ return new MethodInfo(method.Method.Name, returnVal, flags, arguments,
defaultArguments: null);
}
diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
index 1c1185d91b..e186c0302b 100644
--- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
@@ -190,6 +190,9 @@ namespace GodotTools
case ExternalEditorId.CustomEditor:
{
string file = ProjectSettings.GlobalizePath(script.ResourcePath);
+ string project = ProjectSettings.GlobalizePath("res://");
+ // Since ProjectSettings.GlobalizePath replaces only "res:/", leaving a trailing slash, it is removed here.
+ project = project[..^1];
var execCommand = _editorSettings.GetSetting(Settings.CustomExecPath).As<string>();
var execArgs = _editorSettings.GetSetting(Settings.CustomExecPathArgs).As<string>();
var args = new List<string>();
@@ -226,6 +229,7 @@ namespace GodotTools
hasFileFlag = true;
}
+ arg = arg.ReplaceN("{project}", project);
arg = arg.ReplaceN("{file}", file);
args.Add(arg);
diff --git a/modules/mono/glue/GodotSharp/.editorconfig b/modules/mono/glue/GodotSharp/.editorconfig
index d0c660de4c..df4a6c2d0d 100644
--- a/modules/mono/glue/GodotSharp/.editorconfig
+++ b/modules/mono/glue/GodotSharp/.editorconfig
@@ -1,5 +1,5 @@
[**/Generated/**.cs]
-# Validate parameter is non-null before using it
+# CA1062: Validate parameter is non-null before using it
# Useful for generated code, as it disables nullable
dotnet_diagnostic.CA1062.severity = error
# CA1069: Enums should not have duplicate values
@@ -10,3 +10,8 @@ dotnet_diagnostic.CA1708.severity = none
dotnet_diagnostic.CS1591.severity = none
# CS1573: Parameter has no matching param tag in the XML comment
dotnet_diagnostic.CS1573.severity = none
+
+[GodotSharp/Core/**.cs]
+# CS1591: Missing XML comment for publicly visible type or member
+# TODO: Temporary change to not pollute the warnings, but we need to document public APIs
+dotnet_diagnostic.CS1591.severity = suggestion
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs
index 109643c2d4..5d6583b404 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs
@@ -29,6 +29,7 @@ namespace Godot.Bridge
public delegate* unmanaged<IntPtr, IntPtr*, godot_bool, godot_bool> ScriptManagerBridge_SwapGCHandleForType;
public delegate* unmanaged<IntPtr, delegate* unmanaged<IntPtr, godot_string*, void*, int, void>, void> ScriptManagerBridge_GetPropertyInfoList;
public delegate* unmanaged<IntPtr, delegate* unmanaged<IntPtr, void*, int, void>, void> ScriptManagerBridge_GetPropertyDefaultValues;
+ public delegate* unmanaged<IntPtr, godot_string_name*, godot_variant**, int, godot_variant_call_error*, godot_variant*, godot_bool> ScriptManagerBridge_CallStatic;
public delegate* unmanaged<IntPtr, godot_string_name*, godot_variant**, int, godot_variant_call_error*, godot_variant*, godot_bool> CSharpInstanceBridge_Call;
public delegate* unmanaged<IntPtr, godot_string_name*, godot_variant*, godot_bool> CSharpInstanceBridge_Set;
public delegate* unmanaged<IntPtr, godot_string_name*, godot_variant*, godot_bool> CSharpInstanceBridge_Get;
@@ -70,6 +71,7 @@ namespace Godot.Bridge
ScriptManagerBridge_SwapGCHandleForType = &ScriptManagerBridge.SwapGCHandleForType,
ScriptManagerBridge_GetPropertyInfoList = &ScriptManagerBridge.GetPropertyInfoList,
ScriptManagerBridge_GetPropertyDefaultValues = &ScriptManagerBridge.GetPropertyDefaultValues,
+ ScriptManagerBridge_CallStatic = &ScriptManagerBridge.CallStatic,
CSharpInstanceBridge_Call = &CSharpInstanceBridge.Call,
CSharpInstanceBridge_Set = &CSharpInstanceBridge.Set,
CSharpInstanceBridge_Get = &CSharpInstanceBridge.Get,
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
index 0fe4bcdfce..1a7d2d9075 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
@@ -90,7 +90,7 @@ namespace Godot.Bridge
internal static unsafe IntPtr CreateManagedForGodotObjectBinding(godot_string_name* nativeTypeName,
IntPtr godotObject)
{
- // TODO: Optimize with source generators and delegate pointers
+ // TODO: Optimize with source generators and delegate pointers.
try
{
@@ -124,7 +124,7 @@ namespace Godot.Bridge
IntPtr godotObject,
godot_variant** args, int argCount)
{
- // TODO: Optimize with source generators and delegate pointers
+ // TODO: Optimize with source generators and delegate pointers.
try
{
@@ -677,6 +677,8 @@ namespace Godot.Bridge
methodInfo.Add("params", methodParams);
+ methodInfo.Add("flags", (int)method.Flags);
+
methods.Add(methodInfo);
}
}
@@ -958,6 +960,54 @@ namespace Godot.Bridge
public godot_variant Value; // Not owned
}
+ private delegate bool InvokeGodotClassStaticMethodDelegate(in godot_string_name method, NativeVariantPtrArgs args, out godot_variant ret);
+
+ [UnmanagedCallersOnly]
+ internal static unsafe godot_bool CallStatic(IntPtr scriptPtr, godot_string_name* method,
+ godot_variant** args, int argCount, godot_variant_call_error* refCallError, godot_variant* ret)
+ {
+ // TODO: Optimize with source generators and delegate pointers.
+
+ try
+ {
+ Type scriptType = _scriptTypeBiMap.GetScriptType(scriptPtr);
+
+ Type? top = scriptType;
+ Type native = GodotObject.InternalGetClassNativeBase(top);
+
+ while (top != null && top != native)
+ {
+ var invokeGodotClassStaticMethod = top.GetMethod(
+ "InvokeGodotClassStaticMethod",
+ BindingFlags.DeclaredOnly | BindingFlags.Static |
+ BindingFlags.NonPublic | BindingFlags.Public);
+
+ if (invokeGodotClassStaticMethod != null)
+ {
+ var invoked = invokeGodotClassStaticMethod.CreateDelegate<InvokeGodotClassStaticMethodDelegate>()(
+ CustomUnsafe.AsRef(method), new NativeVariantPtrArgs(args, argCount), out godot_variant retValue);
+ if (invoked)
+ {
+ *ret = retValue;
+ return godot_bool.True;
+ }
+ }
+
+ top = top.BaseType;
+ }
+ }
+ catch (Exception e)
+ {
+ ExceptionUtils.LogException(e);
+ *ret = default;
+ return godot_bool.False;
+ }
+
+ *ret = default;
+ (*refCallError).Error = godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_ERROR_INVALID_METHOD;
+ return godot_bool.False;
+ }
+
[UnmanagedCallersOnly]
internal static unsafe void GetPropertyDefaultValues(IntPtr scriptPtr,
delegate* unmanaged<IntPtr, void*, int, void> addDefValFunc)
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
index 8a36b3e514..a55b8d693b 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
+++ b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
@@ -11,9 +11,6 @@
<LangVersion>10</LangVersion>
<AnalysisMode>Recommended</AnalysisMode>
-
- <!-- Disabled temporarily as it pollutes the warnings, but we need to document public APIs. -->
- <NoWarn>CS1591</NoWarn>
</PropertyGroup>
<PropertyGroup>
<Description>Godot C# Core API.</Description>
diff --git a/modules/mono/mono_gd/gd_mono_cache.cpp b/modules/mono/mono_gd/gd_mono_cache.cpp
index 8fdf163b26..5a1f90fa1d 100644
--- a/modules/mono/mono_gd/gd_mono_cache.cpp
+++ b/modules/mono/mono_gd/gd_mono_cache.cpp
@@ -70,6 +70,7 @@ void update_godot_api_cache(const ManagedCallbacks &p_managed_callbacks) {
CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, SwapGCHandleForType);
CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, GetPropertyInfoList);
CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, GetPropertyDefaultValues);
+ CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, CallStatic);
CHECK_CALLBACK_NOT_NULL(CSharpInstanceBridge, Call);
CHECK_CALLBACK_NOT_NULL(CSharpInstanceBridge, Set);
CHECK_CALLBACK_NOT_NULL(CSharpInstanceBridge, Get);
diff --git a/modules/mono/mono_gd/gd_mono_cache.h b/modules/mono/mono_gd/gd_mono_cache.h
index f604e4d681..6ddc688ea0 100644
--- a/modules/mono/mono_gd/gd_mono_cache.h
+++ b/modules/mono/mono_gd/gd_mono_cache.h
@@ -95,6 +95,7 @@ struct ManagedCallbacks {
using FuncScriptManagerBridge_SwapGCHandleForType = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, GCHandleIntPtr *, bool);
using FuncScriptManagerBridge_GetPropertyInfoList = void(GD_CLR_STDCALL *)(CSharpScript *, Callback_ScriptManagerBridge_GetPropertyInfoList_Add);
using FuncScriptManagerBridge_GetPropertyDefaultValues = void(GD_CLR_STDCALL *)(CSharpScript *, Callback_ScriptManagerBridge_GetPropertyDefaultValues_Add);
+ using FuncScriptManagerBridge_CallStatic = bool(GD_CLR_STDCALL *)(const CSharpScript *, const StringName *, const Variant **, int32_t, Callable::CallError *, Variant *);
using FuncCSharpInstanceBridge_Call = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, const StringName *, const Variant **, int32_t, Callable::CallError *, Variant *);
using FuncCSharpInstanceBridge_Set = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, const StringName *, const Variant *);
using FuncCSharpInstanceBridge_Get = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, const StringName *, Variant *);
@@ -130,6 +131,7 @@ struct ManagedCallbacks {
FuncScriptManagerBridge_SwapGCHandleForType ScriptManagerBridge_SwapGCHandleForType;
FuncScriptManagerBridge_GetPropertyInfoList ScriptManagerBridge_GetPropertyInfoList;
FuncScriptManagerBridge_GetPropertyDefaultValues ScriptManagerBridge_GetPropertyDefaultValues;
+ FuncScriptManagerBridge_CallStatic ScriptManagerBridge_CallStatic;
FuncCSharpInstanceBridge_Call CSharpInstanceBridge_Call;
FuncCSharpInstanceBridge_Set CSharpInstanceBridge_Set;
FuncCSharpInstanceBridge_Get CSharpInstanceBridge_Get;
diff --git a/modules/regex/doc_classes/RegEx.xml b/modules/regex/doc_classes/RegEx.xml
index 5770e7155e..ab74fce3a9 100644
--- a/modules/regex/doc_classes/RegEx.xml
+++ b/modules/regex/doc_classes/RegEx.xml
@@ -10,7 +10,7 @@
var regex = RegEx.new()
regex.compile("\\w-(\\d+)")
[/codeblock]
- The search pattern must be escaped first for GDScript before it is escaped for the expression. For example, [code]compile("\\d+")[/code] would be read by RegEx as [code]\d+[/code]. Similarly, [code]compile("\"(?:\\\\.|[^\"])*\"")[/code] would be read as [code]"(?:\\.|[^"])*"[/code].
+ The search pattern must be escaped first for GDScript before it is escaped for the expression. For example, [code]compile("\\d+")[/code] would be read by RegEx as [code]\d+[/code]. Similarly, [code]compile("\"(?:\\\\.|[^\"])*\"")[/code] would be read as [code]"(?:\\.|[^"])*"[/code]. In GDScript, you can also use raw string literals (r-strings). For example, [code]compile(r'"(?:\\.|[^"])*"')[/code] would be read the same.
Using [method search], you can find the pattern within the given text. If a pattern is found, [RegExMatch] is returned and you can retrieve details of the results using methods such as [method RegExMatch.get_string] and [method RegExMatch.get_start].
[codeblock]
var regex = RegEx.new()
diff --git a/scene/3d/soft_body_3d.cpp b/scene/3d/soft_body_3d.cpp
index d1753fff6c..d24dd755dc 100644
--- a/scene/3d/soft_body_3d.cpp
+++ b/scene/3d/soft_body_3d.cpp
@@ -78,14 +78,13 @@ void SoftBodyRenderingServerHandler::commit_changes() {
RS::get_singleton()->mesh_surface_update_vertex_region(mesh, surface, 0, buffer);
}
-void SoftBodyRenderingServerHandler::set_vertex(int p_vertex_id, const void *p_vector3) {
- memcpy(&write_buffer[p_vertex_id * stride + offset_vertices], p_vector3, sizeof(float) * 3);
+void SoftBodyRenderingServerHandler::set_vertex(int p_vertex_id, const Vector3 &p_vertex) {
+ memcpy(&write_buffer[p_vertex_id * stride + offset_vertices], &p_vertex, sizeof(Vector3));
}
-void SoftBodyRenderingServerHandler::set_normal(int p_vertex_id, const void *p_vector3) {
+void SoftBodyRenderingServerHandler::set_normal(int p_vertex_id, const Vector3 &p_normal) {
// Store normal vector in A2B10G10R10 format.
- Vector3 n;
- memcpy(&n, p_vector3, sizeof(Vector3));
+ Vector3 n = p_normal;
n *= Vector3(0.5, 0.5, 0.5);
n += Vector3(0.5, 0.5, 0.5);
Vector2 res = n.octahedron_encode();
diff --git a/scene/3d/soft_body_3d.h b/scene/3d/soft_body_3d.h
index 0b75ae2cda..6648f956dc 100644
--- a/scene/3d/soft_body_3d.h
+++ b/scene/3d/soft_body_3d.h
@@ -59,8 +59,8 @@ private:
void commit_changes();
public:
- void set_vertex(int p_vertex_id, const void *p_vector3) override;
- void set_normal(int p_vertex_id, const void *p_vector3) override;
+ void set_vertex(int p_vertex_id, const Vector3 &p_vertex) override;
+ void set_normal(int p_vertex_id, const Vector3 &p_normal) override;
void set_aabb(const AABB &p_aabb) override;
};
diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp
index 6ef8eacfd0..d35d35d36d 100644
--- a/scene/gui/code_edit.cpp
+++ b/scene/gui/code_edit.cpp
@@ -433,7 +433,7 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
// Allow unicode handling if:
// No modifiers are pressed (except Shift and CapsLock)
- bool allow_unicode_handling = !(k->is_command_or_control_pressed() || k->is_ctrl_pressed() || k->is_alt_pressed() || k->is_meta_pressed());
+ bool allow_unicode_handling = !(k->is_ctrl_pressed() || k->is_alt_pressed() || k->is_meta_pressed());
/* AUTO-COMPLETE */
if (code_completion_enabled && k->is_action("ui_text_completion_query", true)) {
diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp
index 6b5e3486ac..e37a3671f3 100644
--- a/scene/gui/graph_edit.cpp
+++ b/scene/gui/graph_edit.cpp
@@ -1155,7 +1155,7 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) {
// Snapping can be toggled temporarily by holding down Ctrl.
// This is done here as to not toggle the grid when holding down Ctrl.
- if (snapping_enabled ^ Input::get_singleton()->is_key_pressed(Key::CTRL)) {
+ if (snapping_enabled ^ Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) {
pos = pos.snapped(Vector2(snapping_distance, snapping_distance));
}
@@ -1214,7 +1214,7 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) {
}
if (mb->get_button_index() == MouseButton::LEFT && !mb->is_pressed() && dragging) {
- if (!just_selected && drag_accum == Vector2() && Input::get_singleton()->is_key_pressed(Key::CTRL)) {
+ if (!just_selected && drag_accum == Vector2() && Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) {
// Deselect current node.
for (int i = get_child_count() - 1; i >= 0; i--) {
GraphElement *graph_element = Object::cast_to<GraphElement>(get_child(i));
@@ -1281,7 +1281,7 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) {
dragging = true;
drag_accum = Vector2();
just_selected = !graph_element->is_selected();
- if (!graph_element->is_selected() && !Input::get_singleton()->is_key_pressed(Key::CTRL)) {
+ if (!graph_element->is_selected() && !Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) {
for (int i = 0; i < get_child_count(); i++) {
GraphElement *child_element = Object::cast_to<GraphElement>(get_child(i));
if (!child_element) {
@@ -1314,7 +1314,7 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) {
// Left-clicked on empty space, start box select.
box_selecting = true;
box_selecting_from = mb->get_position();
- if (mb->is_ctrl_pressed()) {
+ if (mb->is_command_or_control_pressed()) {
box_selection_mode_additive = true;
prev_selected.clear();
for (int i = get_child_count() - 1; i >= 0; i--) {
diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp
index 8746d4079a..100e0c4548 100644
--- a/scene/gui/line_edit.cpp
+++ b/scene/gui/line_edit.cpp
@@ -614,7 +614,7 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
// Allow unicode handling if:
// * No Modifiers are pressed (except shift)
- bool allow_unicode_handling = !(k->is_command_or_control_pressed() || k->is_ctrl_pressed() || k->is_alt_pressed() || k->is_meta_pressed());
+ bool allow_unicode_handling = !(k->is_ctrl_pressed() || k->is_alt_pressed() || k->is_meta_pressed());
if (allow_unicode_handling && editable && k->get_unicode() >= 32) {
// Handle Unicode if no modifiers are active.
@@ -679,13 +679,13 @@ void LineEdit::drop_data(const Point2 &p_point, const Variant &p_data) {
set_caret_at_pixel_pos(p_point.x);
int caret_column_tmp = caret_column;
bool is_inside_sel = selection.enabled && caret_column >= selection.begin && caret_column <= selection.end;
- if (Input::get_singleton()->is_key_pressed(Key::CTRL)) {
+ if (Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) {
is_inside_sel = selection.enabled && caret_column > selection.begin && caret_column < selection.end;
}
if (selection.drag_attempt) {
selection.drag_attempt = false;
if (!is_inside_sel) {
- if (!Input::get_singleton()->is_key_pressed(Key::CTRL)) {
+ if (!Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) {
if (caret_column_tmp > selection.end) {
caret_column_tmp = caret_column_tmp - (selection.end - selection.begin);
}
@@ -1139,7 +1139,7 @@ void LineEdit::_notification(int p_what) {
if (is_drag_successful()) {
if (selection.drag_attempt) {
selection.drag_attempt = false;
- if (is_editable() && !Input::get_singleton()->is_key_pressed(Key::CTRL)) {
+ if (is_editable() && !Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) {
selection_delete();
} else if (deselect_on_focus_loss_enabled) {
deselect();
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index 98bedd4493..9e159cd303 100644
--- a/scene/gui/text_edit.cpp
+++ b/scene/gui/text_edit.cpp
@@ -1598,7 +1598,7 @@ void TextEdit::_notification(int p_what) {
if (is_drag_successful()) {
if (selection_drag_attempt) {
selection_drag_attempt = false;
- if (is_editable() && !Input::get_singleton()->is_key_pressed(Key::CTRL)) {
+ if (is_editable() && !Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) {
delete_selection();
} else if (deselect_on_focus_loss_enabled) {
deselect();
@@ -2049,7 +2049,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
// Allow unicode handling if:
// * No modifiers are pressed (except Shift and CapsLock)
- bool allow_unicode_handling = !(k->is_command_or_control_pressed() || k->is_ctrl_pressed() || k->is_alt_pressed() || k->is_meta_pressed());
+ bool allow_unicode_handling = !(k->is_ctrl_pressed() || k->is_alt_pressed() || k->is_meta_pressed());
// Check and handle all built-in shortcuts.
@@ -3077,13 +3077,13 @@ void TextEdit::drop_data(const Point2 &p_point, const Variant &p_data) {
int caret_column_tmp = pos.x;
if (selection_drag_attempt) {
selection_drag_attempt = false;
- if (!is_mouse_over_selection(!Input::get_singleton()->is_key_pressed(Key::CTRL))) {
+ if (!is_mouse_over_selection(!Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL))) {
// Set caret back at selection for undo / redo.
set_caret_line(get_selection_to_line(), false, false);
set_caret_column(get_selection_to_column());
begin_complex_operation();
- if (!Input::get_singleton()->is_key_pressed(Key::CTRL)) {
+ if (!Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) {
if (caret_row_tmp > get_selection_to_line()) {
caret_row_tmp = caret_row_tmp - (get_selection_to_line() - get_selection_from_line());
} else if (caret_row_tmp == get_selection_to_line() && caret_column_tmp >= get_selection_to_column()) {
@@ -6018,6 +6018,10 @@ bool TextEdit::is_drawing_spaces() const {
return draw_spaces;
}
+Color TextEdit::get_font_color() const {
+ return theme_cache.font_color;
+}
+
void TextEdit::_bind_methods() {
/* Internal. */
diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h
index 7be58bd927..56d5b67e0b 100644
--- a/scene/gui/text_edit.h
+++ b/scene/gui/text_edit.h
@@ -41,8 +41,6 @@
class TextEdit : public Control {
GDCLASS(TextEdit, Control);
- friend class CodeHighlighter;
-
public:
/* Edit Actions. */
enum EditAction {
@@ -1029,6 +1027,8 @@ public:
void set_draw_spaces(bool p_enabled);
bool is_drawing_spaces() const;
+ Color get_font_color() const;
+
TextEdit(const String &p_placeholder = String());
};
diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp
index 4ed52bd465..e2b16cdd66 100644
--- a/scene/gui/tree.cpp
+++ b/scene/gui/tree.cpp
@@ -3815,7 +3815,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
}
if (mb->get_button_index() == MouseButton::LEFT) {
- if (get_item_at_position(mb->get_position()) == nullptr && !mb->is_shift_pressed() && !mb->is_ctrl_pressed() && !mb->is_command_or_control_pressed()) {
+ if (get_item_at_position(mb->get_position()) == nullptr && !mb->is_shift_pressed() && !mb->is_command_or_control_pressed()) {
emit_signal(SNAME("nothing_selected"));
}
}
diff --git a/scene/resources/syntax_highlighter.cpp b/scene/resources/syntax_highlighter.cpp
index aa311baa43..441c8859cf 100644
--- a/scene/resources/syntax_highlighter.cpp
+++ b/scene/resources/syntax_highlighter.cpp
@@ -419,7 +419,7 @@ void CodeHighlighter::_clear_highlighting_cache() {
}
void CodeHighlighter::_update_cache() {
- font_color = text_edit->theme_cache.font_color;
+ font_color = text_edit->get_font_color();
}
void CodeHighlighter::add_keyword_color(const String &p_keyword, const Color &p_color) {
diff --git a/servers/audio/effects/audio_stream_generator.cpp b/servers/audio/effects/audio_stream_generator.cpp
index 5cfe51465d..f4727e72ec 100644
--- a/servers/audio/effects/audio_stream_generator.cpp
+++ b/servers/audio/effects/audio_stream_generator.cpp
@@ -143,6 +143,10 @@ void AudioStreamGeneratorPlayback::clear_buffer() {
}
int AudioStreamGeneratorPlayback::_mix_internal(AudioFrame *p_buffer, int p_frames) {
+ if (!active) {
+ return 0;
+ }
+
int read_amount = buffer.data_left();
if (p_frames < read_amount) {
read_amount = p_frames;
@@ -151,16 +155,15 @@ int AudioStreamGeneratorPlayback::_mix_internal(AudioFrame *p_buffer, int p_fram
buffer.read(p_buffer, read_amount);
if (read_amount < p_frames) {
- //skipped, not ideal
+ // Fill with zeros as fallback in case of buffer underrun.
for (int i = read_amount; i < p_frames; i++) {
p_buffer[i] = AudioFrame(0, 0);
}
-
skips++;
}
mixed += p_frames / generator->get_mix_rate();
- return read_amount < p_frames ? read_amount : p_frames;
+ return p_frames;
}
float AudioStreamGeneratorPlayback::get_stream_sampling_rate() {
@@ -181,7 +184,7 @@ void AudioStreamGeneratorPlayback::stop() {
}
bool AudioStreamGeneratorPlayback::is_playing() const {
- return active; //always playing, can't be stopped
+ return active;
}
int AudioStreamGeneratorPlayback::get_loop_count() const {
diff --git a/servers/physics_3d/godot_soft_body_3d.cpp b/servers/physics_3d/godot_soft_body_3d.cpp
index 4b35dd1500..2081c132af 100644
--- a/servers/physics_3d/godot_soft_body_3d.cpp
+++ b/servers/physics_3d/godot_soft_body_3d.cpp
@@ -155,11 +155,9 @@ void GodotSoftBody3D::update_rendering_server(PhysicsServer3DRenderingServerHand
for (uint32_t i = 0; i < vertex_count; ++i) {
const uint32_t node_index = map_visual_to_physics[i];
const Node &node = nodes[node_index];
- const Vector3 &vertex_position = node.x;
- const Vector3 &vertex_normal = node.n;
- p_rendering_server_handler->set_vertex(i, &vertex_position);
- p_rendering_server_handler->set_normal(i, &vertex_normal);
+ p_rendering_server_handler->set_vertex(i, node.x);
+ p_rendering_server_handler->set_normal(i, node.n);
}
p_rendering_server_handler->set_aabb(bounds);
diff --git a/servers/physics_server_3d.cpp b/servers/physics_server_3d.cpp
index 8497bc78e2..0dfc87f199 100644
--- a/servers/physics_server_3d.cpp
+++ b/servers/physics_server_3d.cpp
@@ -34,20 +34,24 @@
#include "core/string/print_string.h"
#include "core/variant/typed_array.h"
-void PhysicsServer3DRenderingServerHandler::set_vertex(int p_vertex_id, const void *p_vector3) {
- GDVIRTUAL_REQUIRED_CALL(_set_vertex, p_vertex_id, p_vector3);
+void PhysicsServer3DRenderingServerHandler::set_vertex(int p_vertex_id, const Vector3 &p_vertex) {
+ GDVIRTUAL_REQUIRED_CALL(_set_vertex, p_vertex_id, p_vertex);
}
-void PhysicsServer3DRenderingServerHandler::set_normal(int p_vertex_id, const void *p_vector3) {
- GDVIRTUAL_REQUIRED_CALL(_set_normal, p_vertex_id, p_vector3);
+void PhysicsServer3DRenderingServerHandler::set_normal(int p_vertex_id, const Vector3 &p_normal) {
+ GDVIRTUAL_REQUIRED_CALL(_set_normal, p_vertex_id, p_normal);
}
void PhysicsServer3DRenderingServerHandler::set_aabb(const AABB &p_aabb) {
GDVIRTUAL_REQUIRED_CALL(_set_aabb, p_aabb);
}
void PhysicsServer3DRenderingServerHandler::_bind_methods() {
- GDVIRTUAL_BIND(_set_vertex, "vertex_id", "vertices");
- GDVIRTUAL_BIND(_set_normal, "vertex_id", "normals");
+ GDVIRTUAL_BIND(_set_vertex, "vertex_id", "vertex");
+ GDVIRTUAL_BIND(_set_normal, "vertex_id", "normal");
GDVIRTUAL_BIND(_set_aabb, "aabb");
+
+ ClassDB::bind_method(D_METHOD("set_vertex", "vertex_id", "vertex"), &PhysicsServer3DRenderingServerHandler::set_vertex);
+ ClassDB::bind_method(D_METHOD("set_normal", "vertex_id", "normal"), &PhysicsServer3DRenderingServerHandler::set_normal);
+ ClassDB::bind_method(D_METHOD("set_aabb", "aabb"), &PhysicsServer3DRenderingServerHandler::set_aabb);
}
PhysicsServer3D *PhysicsServer3D::singleton = nullptr;
diff --git a/servers/physics_server_3d.h b/servers/physics_server_3d.h
index 553d73b549..68dda8b84d 100644
--- a/servers/physics_server_3d.h
+++ b/servers/physics_server_3d.h
@@ -211,15 +211,15 @@ public:
class PhysicsServer3DRenderingServerHandler : public Object {
GDCLASS(PhysicsServer3DRenderingServerHandler, Object)
protected:
- GDVIRTUAL2(_set_vertex, int, GDExtensionConstPtr<void>)
- GDVIRTUAL2(_set_normal, int, GDExtensionConstPtr<void>)
+ GDVIRTUAL2(_set_vertex, int, const Vector3 &)
+ GDVIRTUAL2(_set_normal, int, const Vector3 &)
GDVIRTUAL1(_set_aabb, const AABB &)
static void _bind_methods();
public:
- virtual void set_vertex(int p_vertex_id, const void *p_vector3);
- virtual void set_normal(int p_vertex_id, const void *p_vector3);
+ virtual void set_vertex(int p_vertex_id, const Vector3 &p_vertex);
+ virtual void set_normal(int p_vertex_id, const Vector3 &p_normal);
virtual void set_aabb(const AABB &p_aabb);
virtual ~PhysicsServer3DRenderingServerHandler() {}
diff --git a/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl b/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl
index cfba408fe1..2b8b8fa9d2 100644
--- a/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl
+++ b/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl
@@ -1277,9 +1277,10 @@ void fragment_shader(in SceneData scene_data) {
} else if (bool(instances.data[instance_index].flags & INSTANCE_FLAGS_USE_LIGHTMAP)) { // has actual lightmap
bool uses_sh = bool(instances.data[instance_index].flags & INSTANCE_FLAGS_USE_SH_LIGHTMAP);
uint ofs = instances.data[instance_index].gi_offset & 0xFFFF;
+ uint slice = instances.data[instance_index].gi_offset >> 16;
vec3 uvw;
uvw.xy = uv2 * instances.data[instance_index].lightmap_uv_scale.zw + instances.data[instance_index].lightmap_uv_scale.xy;
- uvw.z = float((instances.data[instance_index].gi_offset >> 16) & 0xFFFF);
+ uvw.z = float(slice);
if (uses_sh) {
uvw.z *= 4.0; //SH textures use 4 times more data
@@ -1288,9 +1289,8 @@ void fragment_shader(in SceneData scene_data) {
vec3 lm_light_l1_0 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 2.0), 0.0).rgb;
vec3 lm_light_l1p1 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 3.0), 0.0).rgb;
- uint idx = instances.data[instance_index].gi_offset >> 20;
- vec3 n = normalize(lightmaps.data[idx].normal_xform * normal);
- float en = lightmaps.data[idx].exposure_normalization;
+ vec3 n = normalize(lightmaps.data[ofs].normal_xform * normal);
+ float en = lightmaps.data[ofs].exposure_normalization;
ambient_light += lm_light_l0 * 0.282095f * en;
ambient_light += lm_light_l1n1 * 0.32573 * n.y * en;
@@ -1304,8 +1304,7 @@ void fragment_shader(in SceneData scene_data) {
}
} else {
- uint idx = instances.data[instance_index].gi_offset >> 20;
- ambient_light += textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw, 0.0).rgb * lightmaps.data[idx].exposure_normalization;
+ ambient_light += textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw, 0.0).rgb * lightmaps.data[ofs].exposure_normalization;
}
}
#else
diff --git a/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl b/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl
index cdf81bb6ec..7f6a9a50e5 100644
--- a/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl
+++ b/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl
@@ -1121,11 +1121,10 @@ void main() {
} else if (bool(draw_call.flags & INSTANCE_FLAGS_USE_LIGHTMAP)) { // has actual lightmap
bool uses_sh = bool(draw_call.flags & INSTANCE_FLAGS_USE_SH_LIGHTMAP);
uint ofs = draw_call.gi_offset & 0xFFFF;
+ uint slice = draw_call.gi_offset >> 16;
vec3 uvw;
uvw.xy = uv2 * draw_call.lightmap_uv_scale.zw + draw_call.lightmap_uv_scale.xy;
- uvw.z = float((draw_call.gi_offset >> 16) & 0xFFFF);
-
- uint idx = draw_call.gi_offset >> 20;
+ uvw.z = float(slice);
if (uses_sh) {
uvw.z *= 4.0; //SH textures use 4 times more data
@@ -1134,8 +1133,8 @@ void main() {
vec3 lm_light_l1_0 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 2.0), 0.0).rgb;
vec3 lm_light_l1p1 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 3.0), 0.0).rgb;
- vec3 n = normalize(lightmaps.data[idx].normal_xform * normal);
- float exposure_normalization = lightmaps.data[idx].exposure_normalization;
+ vec3 n = normalize(lightmaps.data[ofs].normal_xform * normal);
+ float exposure_normalization = lightmaps.data[ofs].exposure_normalization;
ambient_light += lm_light_l0 * 0.282095f;
ambient_light += lm_light_l1n1 * 0.32573 * n.y * exposure_normalization;
@@ -1149,7 +1148,7 @@ void main() {
}
} else {
- ambient_light += textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw, 0.0).rgb * lightmaps.data[idx].exposure_normalization;
+ ambient_light += textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw, 0.0).rgb * lightmaps.data[ofs].exposure_normalization;
}
}