Browse Source

Merge pull request #80773 from akien-mga/3.5-cherrypicks

Cherry-picks for the 3.5 branch (future 3.5.3) - 1st batch
Rémi Verschelde 2 years ago
parent
commit
ffd1181cef
67 changed files with 1062 additions and 589 deletions
  1. 1 1
      .github/workflows/javascript_builds.yml
  2. 26 25
      core/math/basis.cpp
  3. 2 2
      core/script_debugger_local.cpp
  4. 1 0
      doc/classes/Area.xml
  5. 5 1
      doc/classes/CPUParticles.xml
  6. 5 5
      doc/classes/CanvasItem.xml
  7. 6 0
      doc/classes/CanvasLayer.xml
  8. 1 0
      doc/classes/CollisionObject.xml
  9. 1 0
      doc/classes/CollisionPolygon.xml
  10. 1 0
      doc/classes/CollisionShape.xml
  11. 1 0
      doc/classes/KinematicBody.xml
  12. 2 1
      doc/classes/NodePath.xml
  13. 1 0
      doc/classes/OptionButton.xml
  14. 5 1
      doc/classes/ParticlesMaterial.xml
  15. 1 0
      doc/classes/PhysicalBone.xml
  16. 1 0
      doc/classes/PhysicsBody.xml
  17. 1 0
      doc/classes/PopupMenu.xml
  18. 2 1
      doc/classes/ProjectSettings.xml
  19. 1 0
      doc/classes/RigidBody.xml
  20. 2 2
      doc/classes/Spatial.xml
  21. 6 2
      doc/classes/StaticBody.xml
  22. 1 1
      doc/classes/String.xml
  23. 1 0
      doc/classes/TreeItem.xml
  24. 1 0
      doc/classes/VehicleBody.xml
  25. 1 1
      drivers/gles2/rasterizer_scene_gles2.cpp
  26. 1 1
      drivers/gles3/rasterizer_scene_gles3.cpp
  27. 18 0
      drivers/unix/net_socket_posix.cpp
  28. 1 0
      drivers/unix/net_socket_posix.h
  29. 1 1
      editor/editor_atlas_packer.cpp
  30. 4 0
      editor/editor_node.cpp
  31. 6 5
      editor/import/resource_importer_texture_atlas.cpp
  32. 1 1
      editor/import/resource_importer_wav.cpp
  33. 3 2
      editor/plugins/spatial_editor_plugin.cpp
  34. 7 0
      editor/plugins/tile_set_editor_plugin.cpp
  35. 64 0
      main/tests/test_basis.cpp
  36. 1 1
      modules/gdscript/gdscript_editor.cpp
  37. 2 2
      modules/gdscript/gdscript_parser.cpp
  38. 4 1
      modules/gridmap/grid_map.cpp
  39. 3 1
      modules/regex/regex.cpp
  40. 1 1
      modules/webm/doc_classes/VideoStreamWebm.xml
  41. 5 1
      platform/android/export/export_plugin.cpp
  42. 3 3
      platform/android/java/app/config.gradle
  43. 1 1
      platform/android/java/gradle/wrapper/gradle-wrapper.properties
  44. 10 0
      platform/android/java/lib/AndroidManifest.xml
  45. 11 0
      platform/android/java/lib/res/xml/godot_provider_paths.xml
  46. 27 11
      platform/android/java/lib/src/org/godotengine/godot/GodotIO.java
  47. 6 5
      platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java
  48. 12 0
      platform/android/java/lib/src/org/godotengine/godot/io/StorageScope.kt
  49. 490 320
      platform/javascript/package-lock.json
  50. 5 5
      platform/javascript/package.json
  51. 34 4
      platform/x11/joypad_linux.cpp
  52. 1 1
      platform/x11/os_x11.cpp
  53. 1 0
      scene/2d/animated_sprite.cpp
  54. 4 4
      scene/2d/canvas_item.cpp
  55. 3 2
      scene/gui/grid_container.cpp
  56. 1 1
      scene/gui/text_edit.cpp
  57. 13 0
      scene/main/canvas_layer.cpp
  58. 1 0
      scene/main/canvas_layer.h
  59. 1 1
      scene/main/viewport.cpp
  60. 1 1
      scene/resources/bit_map.cpp
  61. 2 2
      scene/resources/default_theme/default_theme.cpp
  62. 1 1
      thirdparty/README.md
  63. 10 6
      thirdparty/enet/enet/enet.h
  64. 14 2
      thirdparty/enet/host.c
  65. 35 42
      thirdparty/enet/packet.c
  66. 54 30
      thirdparty/enet/peer.c
  67. 123 87
      thirdparty/enet/protocol.c

+ 1 - 1
.github/workflows/javascript_builds.yml

@@ -22,7 +22,7 @@ jobs:
       - uses: actions/checkout@v3
       - uses: actions/checkout@v3
 
 
       - name: Set up Emscripten latest
       - name: Set up Emscripten latest
-        uses: mymindstorm/setup-emsdk@v11
+        uses: mymindstorm/setup-emsdk@v12
         with:
         with:
           version: ${{env.EM_VERSION}}
           version: ${{env.EM_VERSION}}
           actions-cache-folder: ${{env.EM_CACHE_FOLDER}}
           actions-cache-folder: ${{env.EM_CACHE_FOLDER}}

+ 26 - 25
core/math/basis.cpp

@@ -865,29 +865,28 @@ void Basis::get_axis_angle(Vector3 &r_axis, real_t &r_angle) const {
 #ifdef MATH_CHECKS
 #ifdef MATH_CHECKS
 	ERR_FAIL_COND(!is_rotation());
 	ERR_FAIL_COND(!is_rotation());
 #endif
 #endif
-*/
-	real_t angle, x, y, z; // variables for result
-	real_t angle_epsilon = 0.1; // margin to distinguish between 0 and 180 degrees
-
-	if ((Math::abs(elements[1][0] - elements[0][1]) < CMP_EPSILON) && (Math::abs(elements[2][0] - elements[0][2]) < CMP_EPSILON) && (Math::abs(elements[2][1] - elements[1][2]) < CMP_EPSILON)) {
-		// singularity found
-		// first check for identity matrix which must have +1 for all terms
-		//  in leading diagonaland zero in other terms
-		if ((Math::abs(elements[1][0] + elements[0][1]) < angle_epsilon) && (Math::abs(elements[2][0] + elements[0][2]) < angle_epsilon) && (Math::abs(elements[2][1] + elements[1][2]) < angle_epsilon) && (Math::abs(elements[0][0] + elements[1][1] + elements[2][2] - 3) < angle_epsilon)) {
-			// this singularity is identity matrix so angle = 0
+	*/
+
+	// https://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToAngle/index.htm
+	real_t x, y, z; // Variables for result.
+	if (Math::is_zero_approx(elements[0][1] - elements[1][0]) && Math::is_zero_approx(elements[0][2] - elements[2][0]) && Math::is_zero_approx(elements[1][2] - elements[2][1])) {
+		// Singularity found.
+		// First check for identity matrix which must have +1 for all terms in leading diagonal and zero in other terms.
+		if (is_diagonal() && (Math::abs(elements[0][0] + elements[1][1] + elements[2][2] - 3) < 3 * CMP_EPSILON)) {
+			// This singularity is identity matrix so angle = 0.
 			r_axis = Vector3(0, 1, 0);
 			r_axis = Vector3(0, 1, 0);
 			r_angle = 0;
 			r_angle = 0;
 			return;
 			return;
 		}
 		}
-		// otherwise this singularity is angle = 180
-		angle = Math_PI;
+		// Otherwise this singularity is angle = 180.
 		real_t xx = (elements[0][0] + 1) / 2;
 		real_t xx = (elements[0][0] + 1) / 2;
 		real_t yy = (elements[1][1] + 1) / 2;
 		real_t yy = (elements[1][1] + 1) / 2;
 		real_t zz = (elements[2][2] + 1) / 2;
 		real_t zz = (elements[2][2] + 1) / 2;
-		real_t xy = (elements[1][0] + elements[0][1]) / 4;
-		real_t xz = (elements[2][0] + elements[0][2]) / 4;
-		real_t yz = (elements[2][1] + elements[1][2]) / 4;
-		if ((xx > yy) && (xx > zz)) { // elements[0][0] is the largest diagonal term
+		real_t xy = (elements[0][1] + elements[1][0]) / 4;
+		real_t xz = (elements[0][2] + elements[2][0]) / 4;
+		real_t yz = (elements[1][2] + elements[2][1]) / 4;
+
+		if ((xx > yy) && (xx > zz)) { // elements[0][0] is the largest diagonal term.
 			if (xx < CMP_EPSILON) {
 			if (xx < CMP_EPSILON) {
 				x = 0;
 				x = 0;
 				y = Math_SQRT12;
 				y = Math_SQRT12;
@@ -897,7 +896,7 @@ void Basis::get_axis_angle(Vector3 &r_axis, real_t &r_angle) const {
 				y = xy / x;
 				y = xy / x;
 				z = xz / x;
 				z = xz / x;
 			}
 			}
-		} else if (yy > zz) { // elements[1][1] is the largest diagonal term
+		} else if (yy > zz) { // elements[1][1] is the largest diagonal term.
 			if (yy < CMP_EPSILON) {
 			if (yy < CMP_EPSILON) {
 				x = Math_SQRT12;
 				x = Math_SQRT12;
 				y = 0;
 				y = 0;
@@ -907,7 +906,7 @@ void Basis::get_axis_angle(Vector3 &r_axis, real_t &r_angle) const {
 				x = xy / y;
 				x = xy / y;
 				z = yz / y;
 				z = yz / y;
 			}
 			}
-		} else { // elements[2][2] is the largest diagonal term so base result on this
+		} else { // elements[2][2] is the largest diagonal term so base result on this.
 			if (zz < CMP_EPSILON) {
 			if (zz < CMP_EPSILON) {
 				x = Math_SQRT12;
 				x = Math_SQRT12;
 				y = Math_SQRT12;
 				y = Math_SQRT12;
@@ -919,22 +918,24 @@ void Basis::get_axis_angle(Vector3 &r_axis, real_t &r_angle) const {
 			}
 			}
 		}
 		}
 		r_axis = Vector3(x, y, z);
 		r_axis = Vector3(x, y, z);
-		r_angle = angle;
+		r_angle = Math_PI;
 		return;
 		return;
 	}
 	}
-	// as we have reached here there are no singularities so we can handle normally
-	real_t s = Math::sqrt((elements[1][2] - elements[2][1]) * (elements[1][2] - elements[2][1]) + (elements[2][0] - elements[0][2]) * (elements[2][0] - elements[0][2]) + (elements[0][1] - elements[1][0]) * (elements[0][1] - elements[1][0])); // s=|axis||sin(angle)|, used to normalise
+	// As we have reached here there are no singularities so we can handle normally.
+	double s = Math::sqrt((elements[2][1] - elements[1][2]) * (elements[2][1] - elements[1][2]) + (elements[0][2] - elements[2][0]) * (elements[0][2] - elements[2][0]) + (elements[1][0] - elements[0][1]) * (elements[1][0] - elements[0][1])); // Used to normalise.
 
 
-	angle = Math::acos((elements[0][0] + elements[1][1] + elements[2][2] - 1) / 2);
-	if (angle < 0) {
-		s = -s;
+	if (Math::abs(s) < CMP_EPSILON) {
+		// Prevent divide by zero, should not happen if matrix is orthogonal and should be caught by singularity test above.
+		s = 1;
 	}
 	}
+
 	x = (elements[2][1] - elements[1][2]) / s;
 	x = (elements[2][1] - elements[1][2]) / s;
 	y = (elements[0][2] - elements[2][0]) / s;
 	y = (elements[0][2] - elements[2][0]) / s;
 	z = (elements[1][0] - elements[0][1]) / s;
 	z = (elements[1][0] - elements[0][1]) / s;
 
 
 	r_axis = Vector3(x, y, z);
 	r_axis = Vector3(x, y, z);
-	r_angle = angle;
+	// acos does clamping.
+	r_angle = Math::acos((elements[0][0] + elements[1][1] + elements[2][2] - 1) / 2);
 }
 }
 
 
 void Basis::set_quat(const Quat &p_quat) {
 void Basis::set_quat(const Quat &p_quat) {

+ 2 - 2
core/script_debugger_local.cpp

@@ -56,7 +56,7 @@ void ScriptDebuggerLocal::debug(ScriptLanguage *p_script, bool p_can_continue, b
 		// Cache options
 		// Cache options
 		String variable_prefix = options["variable_prefix"];
 		String variable_prefix = options["variable_prefix"];
 
 
-		if (line == "") {
+		if (line.empty() && !feof(stdin)) {
 			print_line("\nDebugger Break, Reason: '" + p_script->debug_get_error() + "'");
 			print_line("\nDebugger Break, Reason: '" + p_script->debug_get_error() + "'");
 			print_line("*Frame " + itos(current_frame) + " - " + p_script->debug_get_stack_level_source(current_frame) + ":" + itos(p_script->debug_get_stack_level_line(current_frame)) + " in function '" + p_script->debug_get_stack_level_function(current_frame) + "'");
 			print_line("*Frame " + itos(current_frame) + " - " + p_script->debug_get_stack_level_source(current_frame) + ":" + itos(p_script->debug_get_stack_level_line(current_frame)) + " in function '" + p_script->debug_get_stack_level_function(current_frame) + "'");
 			print_line("Enter \"help\" for assistance.");
 			print_line("Enter \"help\" for assistance.");
@@ -185,7 +185,7 @@ void ScriptDebuggerLocal::debug(ScriptLanguage *p_script, bool p_can_continue, b
 				print_line("Added breakpoint at " + source + ":" + itos(linenr));
 				print_line("Added breakpoint at " + source + ":" + itos(linenr));
 			}
 			}
 
 
-		} else if (line == "q" || line == "quit") {
+		} else if (line == "q" || line == "quit" || (line.empty() && feof(stdin))) {
 			// Do not stop again on quit
 			// Do not stop again on quit
 			clear_breakpoints();
 			clear_breakpoints();
 			ScriptDebugger::get_singleton()->set_depth(-1);
 			ScriptDebugger::get_singleton()->set_depth(-1);

+ 1 - 0
doc/classes/Area.xml

@@ -7,6 +7,7 @@
 		3D area that detects [CollisionObject] nodes overlapping, entering, or exiting. Can also alter or override local physics parameters (gravity, damping) and route audio to a custom audio bus.
 		3D area that detects [CollisionObject] nodes overlapping, entering, or exiting. Can also alter or override local physics parameters (gravity, damping) and route audio to a custom audio bus.
 		To give the area its shape, add a [CollisionShape] or a [CollisionPolygon] node as a [i]direct[/i] child (or add multiple such nodes as direct children) of the area.
 		To give the area its shape, add a [CollisionShape] or a [CollisionPolygon] node as a [i]direct[/i] child (or add multiple such nodes as direct children) of the area.
 		[b]Warning:[/b] See [ConcavePolygonShape] (also called "trimesh") for a warning about possibly unexpected behavior when using that shape for an area.
 		[b]Warning:[/b] See [ConcavePolygonShape] (also called "trimesh") for a warning about possibly unexpected behavior when using that shape for an area.
+		[b]Warning:[/b] With a non-uniform scale this node will probably not function as expected. Please make sure to keep its scale uniform (i.e. the same on all axes), and change the size(s) of its collision shape(s) instead.
 	</description>
 	</description>
 	<tutorials>
 	<tutorials>
 		<link title="3D Platformer Demo">https://godotengine.org/asset-library/asset/125</link>
 		<link title="3D Platformer Demo">https://godotengine.org/asset-library/asset/125</link>

+ 5 - 1
doc/classes/CPUParticles.xml

@@ -127,13 +127,16 @@
 			Animation speed randomness ratio.
 			Animation speed randomness ratio.
 		</member>
 		</member>
 		<member name="color" type="Color" setter="set_color" getter="get_color" default="Color( 1, 1, 1, 1 )">
 		<member name="color" type="Color" setter="set_color" getter="get_color" default="Color( 1, 1, 1, 1 )">
-			Each particle's initial color. To have particle display color in a [SpatialMaterial] make sure to set [member SpatialMaterial.vertex_color_use_as_albedo] to [code]true[/code].
+			Each particle's initial color.
+			[b]Note:[/b] [member color] multiplies the particle mesh's vertex colors. To have a visible effect on a [SpatialMaterial], [member SpatialMaterial.vertex_color_use_as_albedo] [i]must[/i] be [code]true[/code]. For a [ShaderMaterial], [code]ALBEDO *= COLOR.rgb;[/code] must be inserted in the shader's [code]fragment()[/code] function. Otherwise, [member color] will have no visible effect.
 		</member>
 		</member>
 		<member name="color_initial_ramp" type="Gradient" setter="set_color_initial_ramp" getter="get_color_initial_ramp">
 		<member name="color_initial_ramp" type="Gradient" setter="set_color_initial_ramp" getter="get_color_initial_ramp">
 			Each particle's initial color will vary along this [GradientTexture] (multiplied with [member color]).
 			Each particle's initial color will vary along this [GradientTexture] (multiplied with [member color]).
+			[b]Note:[/b] [member color_initial_ramp] multiplies the particle mesh's vertex colors. To have a visible effect on a [SpatialMaterial], [member SpatialMaterial.vertex_color_use_as_albedo] [i]must[/i] be [code]true[/code]. For a [ShaderMaterial], [code]ALBEDO *= COLOR.rgb;[/code] must be inserted in the shader's [code]fragment()[/code] function. Otherwise, [member color_initial_ramp] will have no visible effect.
 		</member>
 		</member>
 		<member name="color_ramp" type="Gradient" setter="set_color_ramp" getter="get_color_ramp">
 		<member name="color_ramp" type="Gradient" setter="set_color_ramp" getter="get_color_ramp">
 			Each particle's color will vary along this [GradientTexture] over its lifetime (multiplied with [member color]).
 			Each particle's color will vary along this [GradientTexture] over its lifetime (multiplied with [member color]).
+			[b]Note:[/b] [member color_ramp] multiplies the particle mesh's vertex colors. To have a visible effect on a [SpatialMaterial], [member SpatialMaterial.vertex_color_use_as_albedo] [i]must[/i] be [code]true[/code]. For a [ShaderMaterial], [code]ALBEDO *= COLOR.rgb;[/code] must be inserted in the shader's [code]fragment()[/code] function. Otherwise, [member color_ramp] will have no visible effect.
 		</member>
 		</member>
 		<member name="damping" type="float" setter="set_param" getter="get_param" default="0.0">
 		<member name="damping" type="float" setter="set_param" getter="get_param" default="0.0">
 			The rate at which particles lose velocity.
 			The rate at which particles lose velocity.
@@ -155,6 +158,7 @@
 		</member>
 		</member>
 		<member name="emission_colors" type="PoolColorArray" setter="set_emission_colors" getter="get_emission_colors">
 		<member name="emission_colors" type="PoolColorArray" setter="set_emission_colors" getter="get_emission_colors">
 			Sets the [Color]s to modulate particles by when using [constant EMISSION_SHAPE_POINTS] or [constant EMISSION_SHAPE_DIRECTED_POINTS].
 			Sets the [Color]s to modulate particles by when using [constant EMISSION_SHAPE_POINTS] or [constant EMISSION_SHAPE_DIRECTED_POINTS].
+			[b]Note:[/b] [member emission_colors] multiplies the particle mesh's vertex colors. To have a visible effect on a [SpatialMaterial], [member SpatialMaterial.vertex_color_use_as_albedo] [i]must[/i] be [code]true[/code]. For a [ShaderMaterial], [code]ALBEDO *= COLOR.rgb;[/code] must be inserted in the shader's [code]fragment()[/code] function. Otherwise, [member emission_colors] will have no visible effect.
 		</member>
 		</member>
 		<member name="emission_normals" type="PoolVector3Array" setter="set_emission_normals" getter="get_emission_normals">
 		<member name="emission_normals" type="PoolVector3Array" setter="set_emission_normals" getter="get_emission_normals">
 			Sets the direction the particles will be emitted in when using [constant EMISSION_SHAPE_DIRECTED_POINTS].
 			Sets the direction the particles will be emitted in when using [constant EMISSION_SHAPE_DIRECTED_POINTS].

+ 5 - 5
doc/classes/CanvasItem.xml

@@ -289,7 +289,7 @@
 		<method name="get_canvas_transform" qualifiers="const">
 		<method name="get_canvas_transform" qualifiers="const">
 			<return type="Transform2D" />
 			<return type="Transform2D" />
 			<description>
 			<description>
-				Returns the transform matrix of this item's canvas.
+				Returns the transform from the coordinate system of the canvas, this item is in, to the [Viewport]s coordinate system.
 			</description>
 			</description>
 		</method>
 		</method>
 		<method name="get_global_mouse_position" qualifiers="const">
 		<method name="get_global_mouse_position" qualifiers="const">
@@ -307,7 +307,7 @@
 		<method name="get_global_transform_with_canvas" qualifiers="const">
 		<method name="get_global_transform_with_canvas" qualifiers="const">
 			<return type="Transform2D" />
 			<return type="Transform2D" />
 			<description>
 			<description>
-				Returns the global transform matrix of this item in relation to the canvas.
+				Returns the transform from the local coordinate system of this [CanvasItem] to the [Viewport]s coordinate system.
 			</description>
 			</description>
 		</method>
 		</method>
 		<method name="get_local_mouse_position" qualifiers="const">
 		<method name="get_local_mouse_position" qualifiers="const">
@@ -331,7 +331,7 @@
 		<method name="get_viewport_transform" qualifiers="const">
 		<method name="get_viewport_transform" qualifiers="const">
 			<return type="Transform2D" />
 			<return type="Transform2D" />
 			<description>
 			<description>
-				Returns this item's transform in relation to the viewport.
+				Returns the transform from the coordinate system of the canvas, this item is in, to the [Viewport]s embedders coordinate system.
 			</description>
 			</description>
 		</method>
 		</method>
 		<method name="get_world_2d" qualifiers="const">
 		<method name="get_world_2d" qualifiers="const">
@@ -367,7 +367,7 @@
 		<method name="is_visible_in_tree" qualifiers="const">
 		<method name="is_visible_in_tree" qualifiers="const">
 			<return type="bool" />
 			<return type="bool" />
 			<description>
 			<description>
-				Returns [code]true[/code] if the node is present in the [SceneTree], its [member visible] property is [code]true[/code] and all its antecedents are also visible. If any antecedent is hidden, this node will not be visible in the scene tree, and is consequently not drawn (see [method _draw]).
+				Returns [code]true[/code] if the node is present in the [SceneTree], its [member visible] property is [code]true[/code] and all its ancestors are also visible. If any ancestor is hidden, this node will not be visible in the scene tree, and is consequently not drawn (see [method _draw]).
 			</description>
 			</description>
 		</method>
 		</method>
 		<method name="make_canvas_position_local" qualifiers="const">
 		<method name="make_canvas_position_local" qualifiers="const">
@@ -441,7 +441,7 @@
 			If [code]true[/code], the parent [CanvasItem]'s [member material] property is used as this one's material.
 			If [code]true[/code], the parent [CanvasItem]'s [member material] property is used as this one's material.
 		</member>
 		</member>
 		<member name="visible" type="bool" setter="set_visible" getter="is_visible" default="true">
 		<member name="visible" type="bool" setter="set_visible" getter="is_visible" default="true">
-			If [code]true[/code], this [CanvasItem] is drawn. The node is only visible if all of its antecedents are visible as well (in other words, [method is_visible_in_tree] must return [code]true[/code]).
+			If [code]true[/code], this [CanvasItem] is drawn. The node is only visible if all of its ancestors are visible as well (in other words, [method is_visible_in_tree] must return [code]true[/code]).
 			[b]Note:[/b] For controls that inherit [Popup], the correct way to make them visible is to call one of the multiple [code]popup*()[/code] functions instead.
 			[b]Note:[/b] For controls that inherit [Popup], the correct way to make them visible is to call one of the multiple [code]popup*()[/code] functions instead.
 		</member>
 		</member>
 	</members>
 	</members>

+ 6 - 0
doc/classes/CanvasLayer.xml

@@ -18,6 +18,12 @@
 				Returns the RID of the canvas used by this layer.
 				Returns the RID of the canvas used by this layer.
 			</description>
 			</description>
 		</method>
 		</method>
+		<method name="get_final_transform" qualifiers="const">
+			<return type="Transform2D" />
+			<description>
+				Returns the transform from the [CanvasLayer]s coordinate system to the [Viewport]s coordinate system.
+			</description>
+		</method>
 		<method name="hide">
 		<method name="hide">
 			<return type="void" />
 			<return type="void" />
 			<description>
 			<description>

+ 1 - 0
doc/classes/CollisionObject.xml

@@ -5,6 +5,7 @@
 	</brief_description>
 	</brief_description>
 	<description>
 	<description>
 		CollisionObject is the base class for physics objects. It can hold any number of collision [Shape]s. Each shape must be assigned to a [i]shape owner[/i]. The CollisionObject can have any number of shape owners. Shape owners are not nodes and do not appear in the editor, but are accessible through code using the [code]shape_owner_*[/code] methods.
 		CollisionObject is the base class for physics objects. It can hold any number of collision [Shape]s. Each shape must be assigned to a [i]shape owner[/i]. The CollisionObject can have any number of shape owners. Shape owners are not nodes and do not appear in the editor, but are accessible through code using the [code]shape_owner_*[/code] methods.
+		[b]Warning:[/b] With a non-uniform scale this node will probably not function as expected. Please make sure to keep its scale uniform (i.e. the same on all axes), and change the size(s) of its collision shape(s) instead.
 	</description>
 	</description>
 	<tutorials>
 	<tutorials>
 	</tutorials>
 	</tutorials>

+ 1 - 0
doc/classes/CollisionPolygon.xml

@@ -5,6 +5,7 @@
 	</brief_description>
 	</brief_description>
 	<description>
 	<description>
 		Allows editing a collision polygon's vertices on a selected plane. Can also set a depth perpendicular to that plane. This class is only available in the editor. It will not appear in the scene tree at run-time. Creates a [Shape] for gameplay. Properties modified during gameplay will have no effect.
 		Allows editing a collision polygon's vertices on a selected plane. Can also set a depth perpendicular to that plane. This class is only available in the editor. It will not appear in the scene tree at run-time. Creates a [Shape] for gameplay. Properties modified during gameplay will have no effect.
+		[b]Warning:[/b] A non-uniformly scaled CollisionPolygon3D node will probably not function as expected. Please make sure to keep its scale uniform (i.e. the same on all axes), and change its [member polygon]'s vertices instead.
 	</description>
 	</description>
 	<tutorials>
 	<tutorials>
 	</tutorials>
 	</tutorials>

+ 1 - 0
doc/classes/CollisionShape.xml

@@ -6,6 +6,7 @@
 	<description>
 	<description>
 		Editor facility for creating and editing collision shapes in 3D space. Set the [member shape] property to configure the shape. [b]IMPORTANT[/b]: this is an Editor-only helper to create shapes, use [method CollisionObject.shape_owner_get_shape] to get the actual shape.
 		Editor facility for creating and editing collision shapes in 3D space. Set the [member shape] property to configure the shape. [b]IMPORTANT[/b]: this is an Editor-only helper to create shapes, use [method CollisionObject.shape_owner_get_shape] to get the actual shape.
 		You can use this node to represent all sorts of collision shapes, for example, add this to an [Area] to give it a detection shape, or add it to a [PhysicsBody] to create a solid object.
 		You can use this node to represent all sorts of collision shapes, for example, add this to an [Area] to give it a detection shape, or add it to a [PhysicsBody] to create a solid object.
+		[b]Warning:[/b] A non-uniformly scaled CollisionShape3D node will probably not function as expected. Please make sure to keep its scale uniform (i.e. the same on all axes), and change the size of its [member shape] resource instead.
 	</description>
 	</description>
 	<tutorials>
 	<tutorials>
 		<link title="Physics introduction">$DOCS_URL/tutorials/physics/physics_introduction.html</link>
 		<link title="Physics introduction">$DOCS_URL/tutorials/physics/physics_introduction.html</link>

+ 1 - 0
doc/classes/KinematicBody.xml

@@ -7,6 +7,7 @@
 		Kinematic bodies are special types of bodies that are meant to be user-controlled. They are not affected by physics at all; to other types of bodies, such as a character or a rigid body, these are the same as a static body. However, they have two main uses:
 		Kinematic bodies are special types of bodies that are meant to be user-controlled. They are not affected by physics at all; to other types of bodies, such as a character or a rigid body, these are the same as a static body. However, they have two main uses:
 		[b]Simulated motion:[/b] When these bodies are moved manually, either from code or from an [AnimationPlayer] (with [member AnimationPlayer.playback_process_mode] set to "physics"), the physics will automatically compute an estimate of their linear and angular velocity. This makes them very useful for moving platforms or other AnimationPlayer-controlled objects (like a door, a bridge that opens, etc).
 		[b]Simulated motion:[/b] When these bodies are moved manually, either from code or from an [AnimationPlayer] (with [member AnimationPlayer.playback_process_mode] set to "physics"), the physics will automatically compute an estimate of their linear and angular velocity. This makes them very useful for moving platforms or other AnimationPlayer-controlled objects (like a door, a bridge that opens, etc).
 		[b]Kinematic characters:[/b] KinematicBody also has an API for moving objects (the [method move_and_collide] and [method move_and_slide] methods) while performing collision tests. This makes them really useful to implement characters that collide against a world, but don't require advanced physics.
 		[b]Kinematic characters:[/b] KinematicBody also has an API for moving objects (the [method move_and_collide] and [method move_and_slide] methods) while performing collision tests. This makes them really useful to implement characters that collide against a world, but don't require advanced physics.
+		[b]Warning:[/b] With a non-uniform scale this node will probably not function as expected. Please make sure to keep its scale uniform (i.e. the same on all axes), and change the size(s) of its collision shape(s) instead.
 	</description>
 	</description>
 	<tutorials>
 	<tutorials>
 		<link title="Kinematic character (2D)">$DOCS_URL/tutorials/physics/kinematic_character_2d.html</link>
 		<link title="Kinematic character (2D)">$DOCS_URL/tutorials/physics/kinematic_character_2d.html</link>

+ 2 - 1
doc/classes/NodePath.xml

@@ -15,6 +15,7 @@
 		@"." # The current node.
 		@"." # The current node.
 		@".." # The parent node.
 		@".." # The parent node.
 		@"../C" # A sibling node C.
 		@"../C" # A sibling node C.
+		@"../.." # The grandparent node.
 		# A leading slash means it is absolute from the SceneTree.
 		# A leading slash means it is absolute from the SceneTree.
 		@"/root" # Equivalent to get_tree().get_root().
 		@"/root" # Equivalent to get_tree().get_root().
 		@"/root/Main" # If your main scene's root node were named "Main".
 		@"/root/Main" # If your main scene's root node were named "Main".
@@ -96,7 +97,7 @@
 			<return type="String" />
 			<return type="String" />
 			<argument index="0" name="idx" type="int" />
 			<argument index="0" name="idx" type="int" />
 			<description>
 			<description>
-				Gets the resource or property name indicated by [code]idx[/code] (0 to [method get_subname_count]).
+				Gets the resource or property name indicated by [code]idx[/code] (0 to [method get_subname_count] - 1).
 				[codeblock]
 				[codeblock]
 				var node_path = NodePath("Path2D/PathFollow2D/Sprite:texture:load_path")
 				var node_path = NodePath("Path2D/PathFollow2D/Sprite:texture:load_path")
 				print(node_path.get_subname(0)) # texture
 				print(node_path.get_subname(0)) # texture

+ 1 - 0
doc/classes/OptionButton.xml

@@ -6,6 +6,7 @@
 	<description>
 	<description>
 		OptionButton is a type button that provides a selectable list of items when pressed. The item selected becomes the "current" item and is displayed as the button text.
 		OptionButton is a type button that provides a selectable list of items when pressed. The item selected becomes the "current" item and is displayed as the button text.
 		See also [BaseButton] which contains common properties and methods associated with this node.
 		See also [BaseButton] which contains common properties and methods associated with this node.
+		[b]Note:[/b] The ID values used for items are limited to 32 bits, not full 64 bits of [int]. This has a range of [code]-2^32[/code] to [code]2^32 - 1[/code], i.e. [code]-2147483648[/code] to [code]2147483647[/code].
 	</description>
 	</description>
 	<tutorials>
 	<tutorials>
 	</tutorials>
 	</tutorials>

+ 5 - 1
doc/classes/ParticlesMaterial.xml

@@ -112,13 +112,16 @@
 			Animation speed randomness ratio.
 			Animation speed randomness ratio.
 		</member>
 		</member>
 		<member name="color" type="Color" setter="set_color" getter="get_color" default="Color( 1, 1, 1, 1 )">
 		<member name="color" type="Color" setter="set_color" getter="get_color" default="Color( 1, 1, 1, 1 )">
-			Each particle's initial color. If the [Particles2D]'s [code]texture[/code] is defined, it will be multiplied by this color. To have particle display color in a [SpatialMaterial] make sure to set [member SpatialMaterial.vertex_color_use_as_albedo] to [code]true[/code].
+			Each particle's initial color. If the [Particles2D]'s or [Particles]'s [code]texture[/code] is defined, it will be multiplied by this color.
+			[b]Note:[/b] [member color] multiplies the particle mesh's vertex colors. To have a visible effect on a [SpatialMaterial], [member SpatialMaterial.vertex_color_use_as_albedo] [i]must[/i] be [code]true[/code]. For a [ShaderMaterial], [code]ALBEDO *= COLOR.rgb;[/code] must be inserted in the shader's [code]fragment()[/code] function. Otherwise, [member color] will have no visible effect.
 		</member>
 		</member>
 		<member name="color_initial_ramp" type="Texture" setter="set_color_initial_ramp" getter="get_color_initial_ramp">
 		<member name="color_initial_ramp" type="Texture" setter="set_color_initial_ramp" getter="get_color_initial_ramp">
 			Each particle's initial color will vary along this [GradientTexture] (multiplied with [member color]).
 			Each particle's initial color will vary along this [GradientTexture] (multiplied with [member color]).
+			[b]Note:[/b] [member color_initial_ramp] multiplies the particle mesh's vertex colors. To have a visible effect on a [SpatialMaterial], [member SpatialMaterial.vertex_color_use_as_albedo] [i]must[/i] be [code]true[/code]. For a [ShaderMaterial], [code]ALBEDO *= COLOR.rgb;[/code] must be inserted in the shader's [code]fragment()[/code] function. Otherwise, [member color_initial_ramp] will have no visible effect.
 		</member>
 		</member>
 		<member name="color_ramp" type="Texture" setter="set_color_ramp" getter="get_color_ramp">
 		<member name="color_ramp" type="Texture" setter="set_color_ramp" getter="get_color_ramp">
 			Each particle's color will vary along this [GradientTexture] over its lifetime (multiplied with [member color]).
 			Each particle's color will vary along this [GradientTexture] over its lifetime (multiplied with [member color]).
+			[b]Note:[/b] [member color_ramp] multiplies the particle mesh's vertex colors. To have a visible effect on a [SpatialMaterial], [member SpatialMaterial.vertex_color_use_as_albedo] [i]must[/i] be [code]true[/code]. For a [ShaderMaterial], [code]ALBEDO *= COLOR.rgb;[/code] must be inserted in the shader's [code]fragment()[/code] function. Otherwise, [member color_ramp] will have no visible effect.
 		</member>
 		</member>
 		<member name="damping" type="float" setter="set_param" getter="get_param" default="0.0">
 		<member name="damping" type="float" setter="set_param" getter="get_param" default="0.0">
 			The rate at which particles lose velocity.
 			The rate at which particles lose velocity.
@@ -137,6 +140,7 @@
 		</member>
 		</member>
 		<member name="emission_color_texture" type="Texture" setter="set_emission_color_texture" getter="get_emission_color_texture">
 		<member name="emission_color_texture" type="Texture" setter="set_emission_color_texture" getter="get_emission_color_texture">
 			Particle color will be modulated by color determined by sampling this texture at the same point as the [member emission_point_texture].
 			Particle color will be modulated by color determined by sampling this texture at the same point as the [member emission_point_texture].
+			[b]Note:[/b] [member emission_color_texture] multiplies the particle mesh's vertex colors. To have a visible effect on a [SpatialMaterial], [member SpatialMaterial.vertex_color_use_as_albedo] [i]must[/i] be [code]true[/code]. For a [ShaderMaterial], [code]ALBEDO *= COLOR.rgb;[/code] must be inserted in the shader's [code]fragment()[/code] function. Otherwise, [member emission_color_texture] will have no visible effect.
 		</member>
 		</member>
 		<member name="emission_normal_texture" type="Texture" setter="set_emission_normal_texture" getter="get_emission_normal_texture">
 		<member name="emission_normal_texture" type="Texture" setter="set_emission_normal_texture" getter="get_emission_normal_texture">
 			Particle velocity and rotation will be set by sampling this texture at the same point as the [member emission_point_texture]. Used only in [constant EMISSION_SHAPE_DIRECTED_POINTS]. Can be created automatically from mesh or node by selecting "Create Emission Points from Mesh/Node" under the "Particles" tool in the toolbar.
 			Particle velocity and rotation will be set by sampling this texture at the same point as the [member emission_point_texture]. Used only in [constant EMISSION_SHAPE_DIRECTED_POINTS]. Can be created automatically from mesh or node by selecting "Create Emission Points from Mesh/Node" under the "Particles" tool in the toolbar.

+ 1 - 0
doc/classes/PhysicalBone.xml

@@ -3,6 +3,7 @@
 	<brief_description>
 	<brief_description>
 	</brief_description>
 	</brief_description>
 	<description>
 	<description>
+		[b]Warning:[/b] With a non-uniform scale this node will probably not function as expected. Please make sure to keep its scale uniform (i.e. the same on all axes), and change the size(s) of its collision shape(s) instead.
 	</description>
 	</description>
 	<tutorials>
 	<tutorials>
 	</tutorials>
 	</tutorials>

+ 1 - 0
doc/classes/PhysicsBody.xml

@@ -5,6 +5,7 @@
 	</brief_description>
 	</brief_description>
 	<description>
 	<description>
 		PhysicsBody is an abstract base class for implementing a physics body. All *Body types inherit from it.
 		PhysicsBody is an abstract base class for implementing a physics body. All *Body types inherit from it.
+		[b]Warning:[/b] With a non-uniform scale this node will probably not function as expected. Please make sure to keep its scale uniform (i.e. the same on all axes), and change the size(s) of its collision shape(s) instead.
 	</description>
 	</description>
 	<tutorials>
 	<tutorials>
 		<link>$DOCS_URL/tutorials/physics/physics_introduction.html</link>
 		<link>$DOCS_URL/tutorials/physics/physics_introduction.html</link>

+ 1 - 0
doc/classes/PopupMenu.xml

@@ -6,6 +6,7 @@
 	<description>
 	<description>
 		[PopupMenu] is a [Control] that displays a list of options. They are popular in toolbars or context menus.
 		[PopupMenu] is a [Control] that displays a list of options. They are popular in toolbars or context menus.
 		[b]Incremental search:[/b] Like [ItemList] and [Tree], [PopupMenu] supports searching within the list while the control is focused. Press a key that matches the first letter of an item's name to select the first item starting with the given letter. After that point, there are two ways to perform incremental search: 1) Press the same key again before the timeout duration to select the next item starting with the same letter. 2) Press letter keys that match the rest of the word before the timeout duration to match to select the item in question directly. Both of these actions will be reset to the beginning of the list if the timeout duration has passed since the last keystroke was registered. You can adjust the timeout duration by changing [member ProjectSettings.gui/timers/incremental_search_max_interval_msec].
 		[b]Incremental search:[/b] Like [ItemList] and [Tree], [PopupMenu] supports searching within the list while the control is focused. Press a key that matches the first letter of an item's name to select the first item starting with the given letter. After that point, there are two ways to perform incremental search: 1) Press the same key again before the timeout duration to select the next item starting with the same letter. 2) Press letter keys that match the rest of the word before the timeout duration to match to select the item in question directly. Both of these actions will be reset to the beginning of the list if the timeout duration has passed since the last keystroke was registered. You can adjust the timeout duration by changing [member ProjectSettings.gui/timers/incremental_search_max_interval_msec].
+		[b]Note:[/b] The ID values used for items are limited to 32 bits, not full 64 bits of [int]. This has a range of [code]-2^32[/code] to [code]2^32 - 1[/code], i.e. [code]-2147483648[/code] to [code]2147483647[/code].
 	</description>
 	</description>
 	<tutorials>
 	<tutorials>
 	</tutorials>
 	</tutorials>

+ 2 - 1
doc/classes/ProjectSettings.xml

@@ -1545,9 +1545,10 @@
 		<member name="rendering/gles3/shaders/shader_compilation_mode" type="int" setter="" getter="" default="0">
 		<member name="rendering/gles3/shaders/shader_compilation_mode" type="int" setter="" getter="" default="0">
 			If set to [code]Asynchronous[/code] and available on the target device, asynchronous compilation of shaders is enabled (in contrast to [code]Asynchronous[/code]).
 			If set to [code]Asynchronous[/code] and available on the target device, asynchronous compilation of shaders is enabled (in contrast to [code]Asynchronous[/code]).
 			That means that when a shader is first used under some new rendering situation, the game won't stall while such shader is being compiled. Instead, a fallback will be used and the real shader will be compiled in the background. Once the actual shader is compiled, it will be used the next times it's used to draw a frame.
 			That means that when a shader is first used under some new rendering situation, the game won't stall while such shader is being compiled. Instead, a fallback will be used and the real shader will be compiled in the background. Once the actual shader is compiled, it will be used the next times it's used to draw a frame.
-			Depending on the async mode configured for a given material/shader, the fallback will be an "ubershader" (the default) or just skip rendering any item it is applied to.
+			Depending on the [member SpatialMaterial.async_mode] mode configured for a given material, the fallback will be an "ubershader" (the default) or just skip rendering any item it is applied to. In custom [ShaderMaterial]s, the async mode is set using [code]render_mode async_visible;[/code] (default) or [code]render_mode async_hidden;[/code] at the top of the shader.
 			An ubershader is a very complex shader, slow but suited to any rendering situation, that the engine generates internally so it can be used from the beginning while the traditional conditioned, optimized version of it is being compiled.
 			An ubershader is a very complex shader, slow but suited to any rendering situation, that the engine generates internally so it can be used from the beginning while the traditional conditioned, optimized version of it is being compiled.
 			To reduce loading times after the project has been launched at least once, you can use [code]Asynchronous + Cache[/code]. This also causes the ubershaders to be cached into storage so they can be ready faster next time they are used (provided the platform provides support for it).
 			To reduce loading times after the project has been launched at least once, you can use [code]Asynchronous + Cache[/code]. This also causes the ubershaders to be cached into storage so they can be ready faster next time they are used (provided the platform provides support for it).
+			[b]Note:[/b] Asynchronous compilation requires driver support for the [code]GL_ARB_get_program_binary[/code] OpenGL extension. This extension is supported by all hardware that supports OpenGL 4.1 or higher as well as most hardware that supports OpenGL 3.3 or higher.
 			[b]Note:[/b] Asynchronous compilation is currently only supported for spatial (3D) and particle materials/shaders. CanvasItem (2D) shaders will not use asynchronous compilation even if this setting is set to [code]Asynchronous[/code] or [code]Asynchronous + Cache[/code].
 			[b]Note:[/b] Asynchronous compilation is currently only supported for spatial (3D) and particle materials/shaders. CanvasItem (2D) shaders will not use asynchronous compilation even if this setting is set to [code]Asynchronous[/code] or [code]Asynchronous + Cache[/code].
 		</member>
 		</member>
 		<member name="rendering/gles3/shaders/shader_compilation_mode.mobile" type="int" setter="" getter="" default="0">
 		<member name="rendering/gles3/shaders/shader_compilation_mode.mobile" type="int" setter="" getter="" default="0">

+ 1 - 0
doc/classes/RigidBody.xml

@@ -9,6 +9,7 @@
 		[b]Note:[/b] Don't change a RigidBody's position every frame or very often. Sporadic changes work fine, but physics runs at a different granularity (fixed Hz) than usual rendering (process callback) and maybe even in a separate thread, so changing this from a process loop may result in strange behavior. If you need to directly affect the body's state, use [method _integrate_forces], which allows you to directly access the physics state.
 		[b]Note:[/b] Don't change a RigidBody's position every frame or very often. Sporadic changes work fine, but physics runs at a different granularity (fixed Hz) than usual rendering (process callback) and maybe even in a separate thread, so changing this from a process loop may result in strange behavior. If you need to directly affect the body's state, use [method _integrate_forces], which allows you to directly access the physics state.
 		If you need to override the default physics behavior, you can write a custom force integration function. See [member custom_integrator].
 		If you need to override the default physics behavior, you can write a custom force integration function. See [member custom_integrator].
 		With Bullet physics (the default), the center of mass is the RigidBody3D center. With GodotPhysics, the center of mass is the average of the [CollisionShape] centers.
 		With Bullet physics (the default), the center of mass is the RigidBody3D center. With GodotPhysics, the center of mass is the average of the [CollisionShape] centers.
+		[b]Warning:[/b] With a non-uniform scale this node will probably not function as expected. Please make sure to keep its scale uniform (i.e. the same on all axes), and change the size(s) of its collision shape(s) instead.
 	</description>
 	</description>
 	<tutorials>
 	<tutorials>
 		<link title="Physics introduction">$DOCS_URL/tutorials/physics/physics_introduction.html</link>
 		<link title="Physics introduction">$DOCS_URL/tutorials/physics/physics_introduction.html</link>

+ 2 - 2
doc/classes/Spatial.xml

@@ -93,7 +93,7 @@
 		<method name="is_visible_in_tree" qualifiers="const">
 		<method name="is_visible_in_tree" qualifiers="const">
 			<return type="bool" />
 			<return type="bool" />
 			<description>
 			<description>
-				Returns [code]true[/code] if the node is present in the [SceneTree], its [member visible] property is [code]true[/code] and all its antecedents are also visible. If any antecedent is hidden, this node will not be visible in the scene tree.
+				Returns [code]true[/code] if the node is present in the [SceneTree], its [member visible] property is [code]true[/code] and all its ancestors are also visible. If any ancestor is hidden, this node will not be visible in the scene tree.
 			</description>
 			</description>
 		</method>
 		</method>
 		<method name="look_at">
 		<method name="look_at">
@@ -282,7 +282,7 @@
 			Local translation of this node.
 			Local translation of this node.
 		</member>
 		</member>
 		<member name="visible" type="bool" setter="set_visible" getter="is_visible" default="true">
 		<member name="visible" type="bool" setter="set_visible" getter="is_visible" default="true">
-			If [code]true[/code], this node is drawn. The node is only visible if all of its antecedents are visible as well (in other words, [method is_visible_in_tree] must return [code]true[/code]).
+			If [code]true[/code], this node is drawn. The node is only visible if all of its ancestors are visible as well (in other words, [method is_visible_in_tree] must return [code]true[/code]).
 		</member>
 		</member>
 	</members>
 	</members>
 	<signals>
 	<signals>

+ 6 - 2
doc/classes/StaticBody.xml

@@ -4,8 +4,12 @@
 		Static body for 3D physics.
 		Static body for 3D physics.
 	</brief_description>
 	</brief_description>
 	<description>
 	<description>
-		Static body for 3D physics. A static body is a simple body that is not intended to move. In contrast to [RigidBody], they don't consume any CPU resources as long as they don't move.
-		Additionally, a constant linear or angular velocity can be set for the static body, so even if it doesn't move, it affects other bodies as if it was moving (this is useful for simulating conveyor belts or conveyor wheels).
+		Static body for 3D physics.
+		A static body is a simple body that doesn't move under physics simulation, i.e. it can't be moved by external forces or contacts but its transformation can still be updated manually by the user. It is ideal for implementing objects in the environment, such as walls or platforms. In contrast to [RigidBody], it doesn't consume any CPU resources as long as they don't move.
+		They have extra functionalities to move and affect other bodies:
+		[i]Static transform change:[/i] Static bodies can be moved by animation or script. In this case, they are just teleported and don't affect other bodies on their path.
+		[i]Constant velocity:[/i] When [member constant_linear_velocity] or [member constant_angular_velocity] is set, static bodies don't move themselves but affect touching bodies as if they were moving. This is useful for simulating conveyor belts or conveyor wheels.
+		[b]Warning:[/b] With a non-uniform scale this node will probably not function as expected. Please make sure to keep its scale uniform (i.e. the same on all axes), and change the size(s) of its collision shape(s) instead.
 	</description>
 	</description>
 	<tutorials>
 	<tutorials>
 		<link title="3D Physics Tests Demo">https://godotengine.org/asset-library/asset/675</link>
 		<link title="3D Physics Tests Demo">https://godotengine.org/asset-library/asset/675</link>

+ 1 - 1
doc/classes/String.xml

@@ -358,7 +358,7 @@
 			<argument index="0" name="delimiter" type="String" />
 			<argument index="0" name="delimiter" type="String" />
 			<argument index="1" name="slice" type="int" />
 			<argument index="1" name="slice" type="int" />
 			<description>
 			<description>
-				Splits a string using a [code]delimiter[/code] and returns a substring at index [code]slice[/code]. Returns an empty string if the index doesn't exist.
+				Splits a string using a [code]delimiter[/code] and returns a substring at index [code]slice[/code]. Returns the original string if [code]delimiter[/code] does not occur in the string. Returns an empty string if the index doesn't exist.
 				This is a more performant alternative to [method split] for cases when you need only one element from the array at a fixed index.
 				This is a more performant alternative to [method split] for cases when you need only one element from the array at a fixed index.
 				Example:
 				Example:
 				[codeblock]
 				[codeblock]

+ 1 - 0
doc/classes/TreeItem.xml

@@ -6,6 +6,7 @@
 	<description>
 	<description>
 		Control for a single item inside a [Tree]. May have child [TreeItem]s and be styled as well as contain buttons.
 		Control for a single item inside a [Tree]. May have child [TreeItem]s and be styled as well as contain buttons.
 		You can remove a [TreeItem] by using [method Object.free].
 		You can remove a [TreeItem] by using [method Object.free].
+		[b]Note:[/b] The ID values used for buttons are limited to 32 bits, not full 64 bits of [int]. This has a range of [code]-2^32[/code] to [code]2^32 - 1[/code], i.e. [code]-2147483648[/code] to [code]2147483647[/code].
 	</description>
 	</description>
 	<tutorials>
 	<tutorials>
 	</tutorials>
 	</tutorials>

+ 1 - 0
doc/classes/VehicleBody.xml

@@ -7,6 +7,7 @@
 		This node implements all the physics logic needed to simulate a car. It is based on the raycast vehicle system commonly found in physics engines. You will need to add a [CollisionShape] for the main body of your vehicle and add [VehicleWheel] nodes for the wheels. You should also add a [MeshInstance] to this node for the 3D model of your car but this model should not include meshes for the wheels. You should control the vehicle by using the [member brake], [member engine_force], and [member steering] properties and not change the position or orientation of this node directly.
 		This node implements all the physics logic needed to simulate a car. It is based on the raycast vehicle system commonly found in physics engines. You will need to add a [CollisionShape] for the main body of your vehicle and add [VehicleWheel] nodes for the wheels. You should also add a [MeshInstance] to this node for the 3D model of your car but this model should not include meshes for the wheels. You should control the vehicle by using the [member brake], [member engine_force], and [member steering] properties and not change the position or orientation of this node directly.
 		[b]Note:[/b] The origin point of your VehicleBody will determine the center of gravity of your vehicle so it is better to keep this low and move the [CollisionShape] and [MeshInstance] upwards.
 		[b]Note:[/b] The origin point of your VehicleBody will determine the center of gravity of your vehicle so it is better to keep this low and move the [CollisionShape] and [MeshInstance] upwards.
 		[b]Note:[/b] This class has known issues and isn't designed to provide realistic 3D vehicle physics. If you want advanced vehicle physics, you will probably have to write your own physics integration using another [PhysicsBody] class.
 		[b]Note:[/b] This class has known issues and isn't designed to provide realistic 3D vehicle physics. If you want advanced vehicle physics, you will probably have to write your own physics integration using another [PhysicsBody] class.
+		[b]Warning:[/b] With a non-uniform scale this node will probably not function as expected. Please make sure to keep its scale uniform (i.e. the same on all axes), and change the size(s) of its collision shape(s) instead.
 	</description>
 	</description>
 	<tutorials>
 	<tutorials>
 		<link title="3D Truck Town Demo">https://godotengine.org/asset-library/asset/524</link>
 		<link title="3D Truck Town Demo">https://godotengine.org/asset-library/asset/524</link>

+ 1 - 1
drivers/gles2/rasterizer_scene_gles2.cpp

@@ -3694,7 +3694,7 @@ void RasterizerSceneGLES2::render_shadow(RID p_light, RID p_shadow_atlas, int p_
 		} else if (directional_shadow.light_count == 2) {
 		} else if (directional_shadow.light_count == 2) {
 			light_instance->directional_rect = Rect2(0, 0, directional_shadow.size, directional_shadow.size / 2);
 			light_instance->directional_rect = Rect2(0, 0, directional_shadow.size, directional_shadow.size / 2);
 			if (light_instance->light_directional_index == 1) {
 			if (light_instance->light_directional_index == 1) {
-				light_instance->directional_rect.position.x += light_instance->directional_rect.size.x;
+				light_instance->directional_rect.position.y += light_instance->directional_rect.size.y;
 			}
 			}
 		} else { //3 and 4
 		} else { //3 and 4
 			light_instance->directional_rect = Rect2(0, 0, directional_shadow.size / 2, directional_shadow.size / 2);
 			light_instance->directional_rect = Rect2(0, 0, directional_shadow.size / 2, directional_shadow.size / 2);

+ 1 - 1
drivers/gles3/rasterizer_scene_gles3.cpp

@@ -4718,7 +4718,7 @@ void RasterizerSceneGLES3::render_shadow(RID p_light, RID p_shadow_atlas, int p_
 			} else if (directional_shadow.light_count == 2) {
 			} else if (directional_shadow.light_count == 2) {
 				light_instance->directional_rect = Rect2(0, 0, directional_shadow.size, directional_shadow.size / 2);
 				light_instance->directional_rect = Rect2(0, 0, directional_shadow.size, directional_shadow.size / 2);
 				if (light_instance->light_directional_index == 1) {
 				if (light_instance->light_directional_index == 1) {
-					light_instance->directional_rect.position.x += light_instance->directional_rect.size.x;
+					light_instance->directional_rect.position.y += light_instance->directional_rect.size.y;
 				}
 				}
 			} else { //3 and 4
 			} else { //3 and 4
 				light_instance->directional_rect = Rect2(0, 0, directional_shadow.size / 2, directional_shadow.size / 2);
 				light_instance->directional_rect = Rect2(0, 0, directional_shadow.size / 2, directional_shadow.size / 2);

+ 18 - 0
drivers/unix/net_socket_posix.cpp

@@ -195,6 +195,9 @@ NetSocketPosix::NetError NetSocketPosix::_get_socket_error() const {
 		return ERR_NET_IN_PROGRESS;
 		return ERR_NET_IN_PROGRESS;
 	if (err == WSAEWOULDBLOCK)
 	if (err == WSAEWOULDBLOCK)
 		return ERR_NET_WOULD_BLOCK;
 		return ERR_NET_WOULD_BLOCK;
+	if (err == WSAEMSGSIZE || err == WSAENOBUFS) {
+		return ERR_NET_BUFFER_TOO_SMALL;
+	}
 	print_verbose("Socket error: " + itos(err));
 	print_verbose("Socket error: " + itos(err));
 	return ERR_NET_OTHER;
 	return ERR_NET_OTHER;
 #else
 #else
@@ -207,6 +210,9 @@ NetSocketPosix::NetError NetSocketPosix::_get_socket_error() const {
 	if (errno == EAGAIN || errno == EWOULDBLOCK) {
 	if (errno == EAGAIN || errno == EWOULDBLOCK) {
 		return ERR_NET_WOULD_BLOCK;
 		return ERR_NET_WOULD_BLOCK;
 	}
 	}
+	if (errno == ENOBUFS) {
+		return ERR_NET_BUFFER_TOO_SMALL;
+	}
 	print_verbose("Socket error: " + itos(errno));
 	print_verbose("Socket error: " + itos(errno));
 	return ERR_NET_OTHER;
 	return ERR_NET_OTHER;
 #endif
 #endif
@@ -535,6 +541,9 @@ Error NetSocketPosix::recv(uint8_t *p_buffer, int p_len, int &r_read) {
 		if (err == ERR_NET_WOULD_BLOCK) {
 		if (err == ERR_NET_WOULD_BLOCK) {
 			return ERR_BUSY;
 			return ERR_BUSY;
 		}
 		}
+		if (err == ERR_NET_BUFFER_TOO_SMALL) {
+			return ERR_OUT_OF_MEMORY;
+		}
 
 
 		return FAILED;
 		return FAILED;
 	}
 	}
@@ -556,6 +565,9 @@ Error NetSocketPosix::recvfrom(uint8_t *p_buffer, int p_len, int &r_read, IP_Add
 		if (err == ERR_NET_WOULD_BLOCK) {
 		if (err == ERR_NET_WOULD_BLOCK) {
 			return ERR_BUSY;
 			return ERR_BUSY;
 		}
 		}
+		if (err == ERR_NET_BUFFER_TOO_SMALL) {
+			return ERR_OUT_OF_MEMORY;
+		}
 
 
 		return FAILED;
 		return FAILED;
 	}
 	}
@@ -592,6 +604,9 @@ Error NetSocketPosix::send(const uint8_t *p_buffer, int p_len, int &r_sent) {
 		if (err == ERR_NET_WOULD_BLOCK) {
 		if (err == ERR_NET_WOULD_BLOCK) {
 			return ERR_BUSY;
 			return ERR_BUSY;
 		}
 		}
+		if (err == ERR_NET_BUFFER_TOO_SMALL) {
+			return ERR_OUT_OF_MEMORY;
+		}
 
 
 		return FAILED;
 		return FAILED;
 	}
 	}
@@ -611,6 +626,9 @@ Error NetSocketPosix::sendto(const uint8_t *p_buffer, int p_len, int &r_sent, IP
 		if (err == ERR_NET_WOULD_BLOCK) {
 		if (err == ERR_NET_WOULD_BLOCK) {
 			return ERR_BUSY;
 			return ERR_BUSY;
 		}
 		}
+		if (err == ERR_NET_BUFFER_TOO_SMALL) {
+			return ERR_OUT_OF_MEMORY;
+		}
 
 
 		return FAILED;
 		return FAILED;
 	}
 	}

+ 1 - 0
drivers/unix/net_socket_posix.h

@@ -54,6 +54,7 @@ private:
 		ERR_NET_WOULD_BLOCK,
 		ERR_NET_WOULD_BLOCK,
 		ERR_NET_IS_CONNECTED,
 		ERR_NET_IS_CONNECTED,
 		ERR_NET_IN_PROGRESS,
 		ERR_NET_IN_PROGRESS,
+		ERR_NET_BUFFER_TOO_SMALL,
 		ERR_NET_OTHER
 		ERR_NET_OTHER
 	};
 	};
 
 

+ 1 - 1
editor/editor_atlas_packer.cpp

@@ -105,7 +105,7 @@ void EditorAtlasPacker::chart_pack(Vector<Chart> &charts, int &r_width, int &r_h
 
 
 		Ref<BitMap> src_bitmap;
 		Ref<BitMap> src_bitmap;
 		src_bitmap.instance();
 		src_bitmap.instance();
-		src_bitmap->create(aabb.size / divide_by);
+		src_bitmap->create(Size2(Math::ceil(aabb.size.x / (real_t)divide_by), Math::ceil(aabb.size.y / (real_t)divide_by)));
 
 
 		int w = src_bitmap->get_size().width;
 		int w = src_bitmap->get_size().width;
 		int h = src_bitmap->get_size().height;
 		int h = src_bitmap->get_size().height;

+ 4 - 0
editor/editor_node.cpp

@@ -556,6 +556,10 @@ void EditorNode::_notification(int p_what) {
 
 
 		case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
 		case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
 			scene_tabs->set_tab_close_display_policy((bool(EDITOR_GET("interface/scene_tabs/always_show_close_button")) ? Tabs::CLOSE_BUTTON_SHOW_ALWAYS : Tabs::CLOSE_BUTTON_SHOW_ACTIVE_ONLY));
 			scene_tabs->set_tab_close_display_policy((bool(EDITOR_GET("interface/scene_tabs/always_show_close_button")) ? Tabs::CLOSE_BUTTON_SHOW_ALWAYS : Tabs::CLOSE_BUTTON_SHOW_ACTIVE_ONLY));
+			FileDialog::set_default_show_hidden_files(EditorSettings::get_singleton()->get("filesystem/file_dialog/show_hidden_files"));
+			EditorFileDialog::set_default_show_hidden_files(EditorSettings::get_singleton()->get("filesystem/file_dialog/show_hidden_files"));
+			EditorFileDialog::set_default_display_mode((EditorFileDialog::DisplayMode)EditorSettings::get_singleton()->get("filesystem/file_dialog/display_mode").operator int());
+
 			theme = create_custom_theme(theme_base->get_theme());
 			theme = create_custom_theme(theme_base->get_theme());
 
 
 			theme_base->set_theme(theme);
 			theme_base->set_theme(theme);

+ 6 - 5
editor/import/resource_importer_texture_atlas.cpp

@@ -96,7 +96,8 @@ Error ResourceImporterTextureAtlas::import(const String &p_source_file, const St
 	return OK;
 	return OK;
 }
 }
 
 
-static void _plot_triangle(Vector2 *vertices, const Vector2 &p_offset, bool p_transposed, Ref<Image> p_image, const Ref<Image> &p_src_image) {
+// FIXME: Rasterization has issues, see https://github.com/godotengine/godot/issues/68350#issuecomment-1305610290
+static void _plot_triangle(Vector2 *p_vertices, const Vector2 &p_offset, bool p_transposed, Ref<Image> p_image, const Ref<Image> &p_src_image) {
 	int width = p_image->get_width();
 	int width = p_image->get_width();
 	int height = p_image->get_height();
 	int height = p_image->get_height();
 	int src_width = p_src_image->get_width();
 	int src_width = p_src_image->get_width();
@@ -106,8 +107,8 @@ static void _plot_triangle(Vector2 *vertices, const Vector2 &p_offset, bool p_tr
 	int y[3];
 	int y[3];
 
 
 	for (int j = 0; j < 3; j++) {
 	for (int j = 0; j < 3; j++) {
-		x[j] = vertices[j].x;
-		y[j] = vertices[j].y;
+		x[j] = p_vertices[j].x;
+		y[j] = p_vertices[j].y;
 	}
 	}
 
 
 	// sort the points vertically
 	// sort the points vertically
@@ -129,7 +130,7 @@ static void _plot_triangle(Vector2 *vertices, const Vector2 &p_offset, bool p_tr
 	double dx_low = double(x[2] - x[1]) / (y[2] - y[1] + 1);
 	double dx_low = double(x[2] - x[1]) / (y[2] - y[1] + 1);
 	double xf = x[0];
 	double xf = x[0];
 	double xt = x[0] + dx_upper; // if y[0] == y[1], special case
 	double xt = x[0] + dx_upper; // if y[0] == y[1], special case
-	int max_y = MIN(y[2], height - p_offset.y - 1);
+	int max_y = MIN(y[2], p_transposed ? (width - p_offset.x - 1) : (height - p_offset.y - 1));
 	for (int yi = y[0]; yi < max_y; yi++) {
 	for (int yi = y[0]; yi < max_y; yi++) {
 		if (yi >= 0) {
 		if (yi >= 0) {
 			for (int xi = (xf > 0 ? int(xf) : 0); xi < (xt <= src_width ? xt : src_width); xi++) {
 			for (int xi = (xf > 0 ? int(xf) : 0); xi < (xt <= src_width ? xt : src_width); xi++) {
@@ -246,7 +247,7 @@ Error ResourceImporterTextureAtlas::import_group_file(const String &p_group_file
 			Ref<BitMap> bit_map;
 			Ref<BitMap> bit_map;
 			bit_map.instance();
 			bit_map.instance();
 			bit_map->create_from_image_alpha(image);
 			bit_map->create_from_image_alpha(image);
-			Vector<Vector<Vector2>> polygons = bit_map->clip_opaque_to_polygons(Rect2(0, 0, image->get_width(), image->get_height()));
+			Vector<Vector<Vector2>> polygons = bit_map->clip_opaque_to_polygons(Rect2(Vector2(), image->get_size()));
 
 
 			for (int j = 0; j < polygons.size(); j++) {
 			for (int j = 0; j < polygons.size(); j++) {
 				EditorAtlasPacker::Chart chart;
 				EditorAtlasPacker::Chart chart;

+ 1 - 1
editor/import/resource_importer_wav.cpp

@@ -404,7 +404,7 @@ Error ResourceImporterWAV::import(const String &p_source_file, const String &p_s
 
 
 	bool trim = p_options["edit/trim"];
 	bool trim = p_options["edit/trim"];
 
 
-	if (trim && (loop_mode != AudioStreamSample::LOOP_DISABLED) && format_channels > 0) {
+	if (trim && (loop_mode == AudioStreamSample::LOOP_DISABLED) && format_channels > 0) {
 		int first = 0;
 		int first = 0;
 		int last = (frames / format_channels) - 1;
 		int last = (frames / format_channels) - 1;
 		bool found = false;
 		bool found = false;

+ 3 - 2
editor/plugins/spatial_editor_plugin.cpp

@@ -7013,11 +7013,12 @@ void SpatialEditorPlugin::edit(Object *p_object) {
 bool SpatialEditorPlugin::handles(Object *p_object) const {
 bool SpatialEditorPlugin::handles(Object *p_object) const {
 	if (p_object->is_class("Spatial")) {
 	if (p_object->is_class("Spatial")) {
 		return true;
 		return true;
-	} else {
+	}
+	if (!p_object->is_class("Resource")) {
 		// This ensures that gizmos are cleared when selecting a non-Spatial node.
 		// This ensures that gizmos are cleared when selecting a non-Spatial node.
 		const_cast<SpatialEditorPlugin *>(this)->edit((Object *)nullptr);
 		const_cast<SpatialEditorPlugin *>(this)->edit((Object *)nullptr);
-		return false;
 	}
 	}
+	return false;
 }
 }
 
 
 Dictionary SpatialEditorPlugin::get_state() const {
 Dictionary SpatialEditorPlugin::get_state() const {

+ 7 - 0
editor/plugins/tile_set_editor_plugin.cpp

@@ -1588,6 +1588,13 @@ void TileSetEditor::_on_workspace_input(const Ref<InputEvent> &p_ie) {
 					const real_t grab_threshold = EDITOR_GET("editors/poly_editor/point_grab_radius");
 					const real_t grab_threshold = EDITOR_GET("editors/poly_editor/point_grab_radius");
 					shape_anchor += current_tile_region.position;
 					shape_anchor += current_tile_region.position;
 					if (tools[TOOL_SELECT]->is_pressed()) {
 					if (tools[TOOL_SELECT]->is_pressed()) {
+						if (current_shape.size() > 0) {
+							for (int i = 0; i < current_shape.size(); i++) {
+								current_shape.set(i, snap_point(current_shape[i]));
+							}
+							workspace->update();
+						}
+
 						if (mb.is_valid()) {
 						if (mb.is_valid()) {
 							if (mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT) {
 							if (mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT) {
 								if (edit_mode != EDITMODE_PRIORITY && current_shape.size() > 0) {
 								if (edit_mode != EDITMODE_PRIORITY && current_shape.size() > 0) {

+ 64 - 0
main/tests/test_basis.cpp

@@ -315,9 +315,73 @@ void test_euler_conversion() {
 	}
 	}
 }
 }
 
 
+void check_test(const char *test_case_name, bool condition) {
+	if (!condition) {
+		OS::get_singleton()->print("FAILED - %s\n", test_case_name);
+	} else {
+		OS::get_singleton()->print("PASSED - %s\n", test_case_name);
+	}
+}
+
+void test_set_axis_angle() {
+	Vector3 axis;
+	real_t angle;
+	real_t pi = (real_t)Math_PI;
+
+	// Testing the singularity when the angle is 0°.
+	Basis identity(1, 0, 0, 0, 1, 0, 0, 0, 1);
+	identity.get_axis_angle(axis, angle);
+	check_test("Testing the singularity when the angle is 0.", angle == 0);
+
+	// Testing the singularity when the angle is 180°.
+	Basis singularityPi(-1, 0, 0, 0, 1, 0, 0, 0, -1);
+	singularityPi.get_axis_angle(axis, angle);
+	check_test("Testing the singularity when the angle is 180.", Math::is_equal_approx(angle, pi));
+
+	// Testing reversing the an axis (of an 30° angle).
+	float cos30deg = Math::cos(Math::deg2rad((real_t)30.0));
+	Basis z_positive(cos30deg, -0.5, 0, 0.5, cos30deg, 0, 0, 0, 1);
+	Basis z_negative(cos30deg, 0.5, 0, -0.5, cos30deg, 0, 0, 0, 1);
+
+	z_positive.get_axis_angle(axis, angle);
+	check_test("Testing reversing the an axis (of an 30 angle).", Math::is_equal_approx(angle, Math::deg2rad((real_t)30.0)));
+	check_test("Testing reversing the an axis (of an 30 angle).", axis == Vector3(0, 0, 1));
+
+	z_negative.get_axis_angle(axis, angle);
+	check_test("Testing reversing the an axis (of an 30 angle).", Math::is_equal_approx(angle, Math::deg2rad((real_t)30.0)));
+	check_test("Testing reversing the an axis (of an 30 angle).", axis == Vector3(0, 0, -1));
+
+	// Testing a rotation of 90° on x-y-z.
+	Basis x90deg(1, 0, 0, 0, 0, -1, 0, 1, 0);
+	x90deg.get_axis_angle(axis, angle);
+	check_test("Testing a rotation of 90 on x-y-z.", Math::is_equal_approx(angle, pi / (real_t)2));
+	check_test("Testing a rotation of 90 on x-y-z.", axis == Vector3(1, 0, 0));
+
+	Basis y90deg(0, 0, 1, 0, 1, 0, -1, 0, 0);
+	y90deg.get_axis_angle(axis, angle);
+	check_test("Testing a rotation of 90 on x-y-z.", axis == Vector3(0, 1, 0));
+
+	Basis z90deg(0, -1, 0, 1, 0, 0, 0, 0, 1);
+	z90deg.get_axis_angle(axis, angle);
+	check_test("Testing a rotation of 90 on x-y-z.", axis == Vector3(0, 0, 1));
+
+	// Regression test: checks that the method returns a small angle (not 0).
+	Basis tiny(1, 0, 0, 0, 0.9999995, -0.001, 0, 001, 0.9999995); // The min angle possible with float is 0.001rad.
+	tiny.get_axis_angle(axis, angle);
+	check_test("Regression test: checks that the method returns a small angle (not 0).", Math::is_equal_approx(angle, (real_t)0.001, (real_t)0.0001));
+
+	// Regression test: checks that the method returns an angle which is a number (not NaN)
+	Basis bugNan(1.00000024, 0, 0.000100001693, 0, 1, 0, -0.000100009143, 0, 1.00000024);
+	bugNan.get_axis_angle(axis, angle);
+	check_test("Regression test: checks that the method returns an angle which is a number (not NaN)", !Math::is_nan(angle));
+}
+
 MainLoop *test() {
 MainLoop *test() {
 	OS::get_singleton()->print("Start euler conversion checks.\n");
 	OS::get_singleton()->print("Start euler conversion checks.\n");
 	test_euler_conversion();
 	test_euler_conversion();
+	OS::get_singleton()->print("\n---------------\n");
+	OS::get_singleton()->print("Start set axis angle checks.\n");
+	test_set_axis_angle();
 
 
 	return nullptr;
 	return nullptr;
 }
 }

+ 1 - 1
modules/gdscript/gdscript_editor.cpp

@@ -2194,7 +2194,7 @@ static void _find_identifiers(const GDScriptCompletionContext &p_context, bool p
 
 
 	static const char *_keywords[] = {
 	static const char *_keywords[] = {
 		"and", "in", "not", "or", "false", "PI", "TAU", "INF", "NAN", "self", "true", "as", "assert",
 		"and", "in", "not", "or", "false", "PI", "TAU", "INF", "NAN", "self", "true", "as", "assert",
-		"breakpoint", "class", "extends", "is", "func", "preload", "setget", "signal", "tool", "yield",
+		"breakpoint", "class", "class_name", "extends", "is", "func", "preload", "setget", "signal", "tool", "yield",
 		"const", "enum", "export", "onready", "static", "var", "break", "continue", "if", "elif",
 		"const", "enum", "export", "onready", "static", "var", "break", "continue", "if", "elif",
 		"else", "for", "pass", "return", "match", "while", "remote", "sync", "master", "puppet", "slave",
 		"else", "for", "pass", "return", "match", "while", "remote", "sync", "master", "puppet", "slave",
 		"remotesync", "mastersync", "puppetsync",
 		"remotesync", "mastersync", "puppetsync",

+ 2 - 2
modules/gdscript/gdscript_parser.cpp

@@ -8592,8 +8592,8 @@ void GDScriptParser::_check_block_types(BlockNode *p_block) {
 	}
 	}
 
 
 	// Parse sub blocks
 	// Parse sub blocks
-	for (int i = 0; i < p_block->sub_blocks.size(); i++) {
-		current_block = p_block->sub_blocks[i];
+	for (const List<BlockNode *>::Element *E = p_block->sub_blocks.front(); E; E = E->next()) {
+		current_block = E->get();
 		_check_block_types(current_block);
 		_check_block_types(current_block);
 		current_block = p_block;
 		current_block = p_block;
 		if (error_set) {
 		if (error_set) {

+ 4 - 1
modules/gridmap/grid_map.cpp

@@ -473,7 +473,10 @@ bool GridMap::_octant_update(const OctantKey &p_key) {
 
 
 	//erase navigation
 	//erase navigation
 	for (Map<IndexKey, Octant::NavMesh>::Element *E = g.navmesh_ids.front(); E; E = E->next()) {
 	for (Map<IndexKey, Octant::NavMesh>::Element *E = g.navmesh_ids.front(); E; E = E->next()) {
-		NavigationServer::get_singleton()->free(E->get().region);
+		if (E->get().region.is_valid()) {
+			NavigationServer::get_singleton()->free(E->get().region);
+			E->get().region = RID();
+		}
 		if (E->get().navmesh_debug_instance.is_valid()) {
 		if (E->get().navmesh_debug_instance.is_valid()) {
 			VS::get_singleton()->free(E->get().navmesh_debug_instance);
 			VS::get_singleton()->free(E->get().navmesh_debug_instance);
 		}
 		}

+ 3 - 1
modules/regex/regex.cpp

@@ -40,7 +40,9 @@ static void *_regex_malloc(PCRE2_SIZE size, void *user) {
 }
 }
 
 
 static void _regex_free(void *ptr, void *user) {
 static void _regex_free(void *ptr, void *user) {
-	memfree(ptr);
+	if (ptr) {
+		memfree(ptr);
+	}
 }
 }
 
 
 int RegExMatch::_find(const Variant &p_name) const {
 int RegExMatch::_find(const Variant &p_name) const {

+ 1 - 1
modules/webm/doc_classes/VideoStreamWebm.xml

@@ -6,7 +6,7 @@
 	<description>
 	<description>
 		[VideoStream] resource handling the [url=https://www.webmproject.org/]WebM[/url] video format with [code].webm[/code] extension. Both the VP8 and VP9 codecs are supported. The VP8 and VP9 codecs are more efficient than [VideoStreamTheora], but they require more CPU resources to decode (especially VP9). Both the VP8 and VP9 codecs are decoded on the CPU.
 		[VideoStream] resource handling the [url=https://www.webmproject.org/]WebM[/url] video format with [code].webm[/code] extension. Both the VP8 and VP9 codecs are supported. The VP8 and VP9 codecs are more efficient than [VideoStreamTheora], but they require more CPU resources to decode (especially VP9). Both the VP8 and VP9 codecs are decoded on the CPU.
 		[b]Note:[/b] Alpha channel (also known as transparency) is not supported. The video will always appear to have a black background, even if it originally contains an alpha channel.
 		[b]Note:[/b] Alpha channel (also known as transparency) is not supported. The video will always appear to have a black background, even if it originally contains an alpha channel.
-		[b]Note:[/b] There are known bugs and performance issues with WebM video playback in Godot. If you run into problems, try using the Ogg Theora format instead: [VideoStreamTheora]
+		[b]Note:[/b] Not supported on iOS, or when compiled for RISC-V, and there are known bugs and performance issues with WebM video playback in Godot. If you run into problems, try using the Ogg Theora format instead: [VideoStreamTheora]
 	</description>
 	</description>
 	<tutorials>
 	<tutorials>
 	</tutorials>
 	</tutorials>

+ 5 - 1
platform/android/export/export_plugin.cpp

@@ -226,7 +226,7 @@ static const char *APK_ASSETS_DIRECTORY = "res://android/build/assets";
 static const char *AAB_ASSETS_DIRECTORY = "res://android/build/assetPacks/installTime/src/main/assets";
 static const char *AAB_ASSETS_DIRECTORY = "res://android/build/assetPacks/installTime/src/main/assets";
 
 
 static const int DEFAULT_MIN_SDK_VERSION = 19; // Should match the value in 'platform/android/java/app/config.gradle#minSdk'
 static const int DEFAULT_MIN_SDK_VERSION = 19; // Should match the value in 'platform/android/java/app/config.gradle#minSdk'
-static const int DEFAULT_TARGET_SDK_VERSION = 32; // Should match the value in 'platform/android/java/app/config.gradle#targetSdk'
+static const int DEFAULT_TARGET_SDK_VERSION = 33; // Should match the value in 'platform/android/java/app/config.gradle#targetSdk'
 
 
 #ifndef ANDROID_ENABLED
 #ifndef ANDROID_ENABLED
 void EditorExportPlatformAndroid::_check_for_changes_poll_thread(void *ud) {
 void EditorExportPlatformAndroid::_check_for_changes_poll_thread(void *ud) {
@@ -968,6 +968,10 @@ void EditorExportPlatformAndroid::_fix_manifest(const Ref<EditorExportPreset> &p
 						encode_uint32(is_resizeable, &p_manifest.write[iofs + 16]);
 						encode_uint32(is_resizeable, &p_manifest.write[iofs + 16]);
 					}
 					}
 
 
+					if (tname == "provider" && attrname == "authorities") {
+						string_table.write[attr_value] = get_package_name(package_name) + String(".fileprovider");
+					}
+
 					if (tname == "supports-screens") {
 					if (tname == "supports-screens") {
 						if (attrname == "smallScreens") {
 						if (attrname == "smallScreens") {
 							encode_uint32(screen_support_small ? 0xFFFFFFFF : 0, &p_manifest.write[iofs + 16]);
 							encode_uint32(screen_support_small ? 0xFFFFFFFF : 0, &p_manifest.write[iofs + 16]);

+ 3 - 3
platform/android/java/app/config.gradle

@@ -1,9 +1,9 @@
 ext.versions = [
 ext.versions = [
     androidGradlePlugin: '7.2.1',
     androidGradlePlugin: '7.2.1',
-    compileSdk         : 32,
+    compileSdk         : 33,
     minSdk             : 19, // Also update 'platform/android/export/export_plugin.cpp#DEFAULT_MIN_SDK_VERSION'
     minSdk             : 19, // Also update 'platform/android/export/export_plugin.cpp#DEFAULT_MIN_SDK_VERSION'
-    targetSdk          : 32, // Also update 'platform/android/export/export_plugin.cpp#DEFAULT_TARGET_SDK_VERSION'
-    buildTools         : '32.0.0',
+    targetSdk          : 33, // Also update 'platform/android/export/export_plugin.cpp#DEFAULT_TARGET_SDK_VERSION'
+    buildTools         : '33.0.2',
     kotlinVersion      : '1.7.0',
     kotlinVersion      : '1.7.0',
     fragmentVersion    : '1.3.6',
     fragmentVersion    : '1.3.6',
     nexusPublishVersion: '1.1.0',
     nexusPublishVersion: '1.1.0',

+ 1 - 1
platform/android/java/gradle/wrapper/gradle-wrapper.properties

@@ -1,5 +1,5 @@
 distributionBase=GRADLE_USER_HOME
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
 zipStoreBase=GRADLE_USER_HOME
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
 zipStorePath=wrapper/dists

+ 10 - 0
platform/android/java/lib/AndroidManifest.xml

@@ -20,6 +20,16 @@
             android:exported="false"
             android:exported="false"
             />
             />
 
 
+        <provider
+            android:name="androidx.core.content.FileProvider"
+            android:authorities="${applicationId}.fileprovider"
+            android:exported="false"
+            android:grantUriPermissions="true">
+            <meta-data
+                android:name="android.support.FILE_PROVIDER_PATHS"
+                android:resource="@xml/godot_provider_paths" />
+        </provider>
+
     </application>
     </application>
 
 
 </manifest>
 </manifest>

+ 11 - 0
platform/android/java/lib/res/xml/godot_provider_paths.xml

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<paths xmlns:android="http://schemas.android.com/apk/res/android">
+
+	<external-path
+		name="public"
+		path="." />
+
+	<external-files-path
+		name="app"
+		path="." />
+</paths>

+ 27 - 11
platform/android/java/lib/src/org/godotengine/godot/GodotIO.java

@@ -49,6 +49,9 @@ import android.view.Display;
 import android.view.DisplayCutout;
 import android.view.DisplayCutout;
 import android.view.WindowInsets;
 import android.view.WindowInsets;
 
 
+import androidx.core.content.FileProvider;
+
+import java.io.File;
 import java.util.List;
 import java.util.List;
 import java.util.Locale;
 import java.util.Locale;
 
 
@@ -84,29 +87,42 @@ public class GodotIO {
 	// MISCELLANEOUS OS IO
 	// MISCELLANEOUS OS IO
 	/////////////////////////
 	/////////////////////////
 
 
-	public int openURI(String p_uri) {
+	public int openURI(String uriString) {
 		try {
 		try {
-			String path = p_uri;
-			String type = "";
-			if (path.startsWith("/")) {
-				//absolute path to filesystem, prepend file://
-				path = "file://" + path;
-				if (p_uri.endsWith(".png") || p_uri.endsWith(".jpg") || p_uri.endsWith(".gif") || p_uri.endsWith(".webp")) {
-					type = "image/*";
+			Uri dataUri;
+			String dataType = "";
+			boolean grantReadUriPermission = false;
+
+			if (uriString.startsWith("/") || uriString.startsWith("file://")) {
+				String filePath = uriString;
+				// File uris needs to be provided via the FileProvider
+				grantReadUriPermission = true;
+				if (filePath.startsWith("file://")) {
+					filePath = filePath.replace("file://", "");
 				}
 				}
+
+				File targetFile = new File(filePath);
+				dataUri = FileProvider.getUriForFile(activity, activity.getPackageName() + ".fileprovider", targetFile);
+				dataType = activity.getContentResolver().getType(dataUri);
+			} else {
+				dataUri = Uri.parse(uriString);
 			}
 			}
 
 
 			Intent intent = new Intent();
 			Intent intent = new Intent();
 			intent.setAction(Intent.ACTION_VIEW);
 			intent.setAction(Intent.ACTION_VIEW);
-			if (!type.equals("")) {
-				intent.setDataAndType(Uri.parse(path), type);
+			if (TextUtils.isEmpty(dataType)) {
+				intent.setData(dataUri);
 			} else {
 			} else {
-				intent.setData(Uri.parse(path));
+				intent.setDataAndType(dataUri, dataType);
+			}
+			if (grantReadUriPermission) {
+				intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
 			}
 			}
 
 
 			activity.startActivity(intent);
 			activity.startActivity(intent);
 			return 0;
 			return 0;
 		} catch (ActivityNotFoundException e) {
 		} catch (ActivityNotFoundException e) {
+			Log.e(TAG, "Unable to open uri " + uriString, e);
 			return 1;
 			return 1;
 		}
 		}
 	}
 	}

+ 6 - 5
platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java

@@ -128,11 +128,12 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene
 	public boolean onEditorAction(final TextView pTextView, final int pActionID, final KeyEvent pKeyEvent) {
 	public boolean onEditorAction(final TextView pTextView, final int pActionID, final KeyEvent pKeyEvent) {
 		if (this.mEdit == pTextView && this.isFullScreenEdit() && pKeyEvent != null) {
 		if (this.mEdit == pTextView && this.isFullScreenEdit() && pKeyEvent != null) {
 			final String characters = pKeyEvent.getCharacters();
 			final String characters = pKeyEvent.getCharacters();
-
-			for (int i = 0; i < characters.length(); i++) {
-				final int ch = characters.codePointAt(i);
-				GodotLib.key(0, 0, ch, true);
-				GodotLib.key(0, 0, ch, false);
+			if (characters != null) {
+				for (int i = 0; i < characters.length(); i++) {
+					final int ch = characters.codePointAt(i);
+					GodotLib.key(0, 0, ch, true);
+					GodotLib.key(0, 0, ch, false);
+				}
 			}
 			}
 		}
 		}
 
 

+ 12 - 0
platform/android/java/lib/src/org/godotengine/godot/io/StorageScope.kt

@@ -76,6 +76,13 @@ internal enum class StorageScope {
 				return UNKNOWN
 				return UNKNOWN
 			}
 			}
 
 
+			// If we have 'All Files Access' permission, we can access all directories without
+			// restriction.
+			if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
+				&& Environment.isExternalStorageManager()) {
+				return APP
+			}
+
 			val canonicalPathFile = pathFile.canonicalPath
 			val canonicalPathFile = pathFile.canonicalPath
 
 
 			if (internalAppDir != null && canonicalPathFile.startsWith(internalAppDir)) {
 			if (internalAppDir != null && canonicalPathFile.startsWith(internalAppDir)) {
@@ -90,6 +97,11 @@ internal enum class StorageScope {
 				return APP
 				return APP
 			}
 			}
 
 
+			val rootDir: String? = System.getenv("ANDROID_ROOT")
+			if (rootDir != null && canonicalPathFile.startsWith(rootDir)) {
+				return APP
+			}
+
 			if (sharedDir != null && canonicalPathFile.startsWith(sharedDir)) {
 			if (sharedDir != null && canonicalPathFile.startsWith(sharedDir)) {
 				if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
 				if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
 					// Before R, apps had access to shared storage so long as they have the right
 					// Before R, apps had access to shared storage so long as they have the right

File diff suppressed because it is too large
+ 490 - 320
platform/javascript/package-lock.json


+ 5 - 5
platform/javascript/package.json

@@ -20,10 +20,10 @@
   "author": "Godot Engine contributors",
   "author": "Godot Engine contributors",
   "license": "MIT",
   "license": "MIT",
   "devDependencies": {
   "devDependencies": {
-    "eslint": "^7.28.0",
-    "eslint-config-airbnb-base": "^14.2.1",
-    "eslint-plugin-import": "^2.23.4",
-    "jsdoc": "^3.6.7",
-    "serve": "^13.0.2"
+    "eslint": "^8.47.0",
+    "eslint-config-airbnb-base": "^15.0.0",
+    "eslint-plugin-import": "^2.28.1",
+    "jsdoc": "^4.0.2",
+    "serve": "^14.2.0"
   }
   }
 }
 }

+ 34 - 4
platform/x11/joypad_linux.cpp

@@ -32,6 +32,8 @@
 
 
 #include "joypad_linux.h"
 #include "joypad_linux.h"
 
 
+#include "core/os/os.h"
+
 #include <dirent.h>
 #include <dirent.h>
 #include <errno.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <fcntl.h>
@@ -77,13 +79,41 @@ void JoypadLinux::Joypad::reset() {
 	events.clear();
 	events.clear();
 }
 }
 
 
+// This function is derived from SDL:
+// https://github.com/libsdl-org/SDL/blob/main/src/core/linux/SDL_sandbox.c#L28-L45
+static bool detect_sandbox() {
+	if (access("/.flatpak-info", F_OK) == 0) {
+		return true;
+	}
+
+	// For Snap, we check multiple variables because they might be set for
+	// unrelated reasons. This is the same thing WebKitGTK does.
+	if (OS::get_singleton()->has_environment("SNAP") && OS::get_singleton()->has_environment("SNAP_NAME") && OS::get_singleton()->has_environment("SNAP_REVISION")) {
+		return true;
+	}
+
+	if (access("/run/host/container-manager", F_OK) == 0) {
+		return true;
+	}
+
+	return false;
+}
+
 JoypadLinux::JoypadLinux(InputDefault *in) {
 JoypadLinux::JoypadLinux(InputDefault *in) {
 #ifdef UDEV_ENABLED
 #ifdef UDEV_ENABLED
-	use_udev = initialize_libudev() == 0;
-	if (use_udev) {
-		print_verbose("JoypadLinux: udev enabled and loaded successfully.");
+	if (detect_sandbox()) {
+		// Linux binaries in sandboxes / containers need special handling because
+		// libudev doesn't work there. So we need to fallback to manual parsing
+		// of /dev/input in such case.
+		use_udev = false;
+		print_verbose("JoypadLinux: udev enabled, but detected incompatible sandboxed mode. Falling back to /dev/input to detect joypads.");
 	} else {
 	} else {
-		print_verbose("JoypadLinux: udev enabled, but couldn't be loaded. Falling back to /dev/input to detect joypads.");
+		use_udev = initialize_libudev() == 0;
+		if (use_udev) {
+			print_verbose("JoypadLinux: udev enabled and loaded successfully.");
+		} else {
+			print_verbose("JoypadLinux: udev enabled, but couldn't be loaded. Falling back to /dev/input to detect joypads.");
+		}
 	}
 	}
 #else
 #else
 	print_verbose("JoypadLinux: udev disabled, parsing /dev/input to detect joypads.");
 	print_verbose("JoypadLinux: udev disabled, parsing /dev/input to detect joypads.");

+ 1 - 1
platform/x11/os_x11.cpp

@@ -4381,7 +4381,7 @@ uint32_t OS_X11::keyboard_get_scancode_from_physical(uint32_t p_scancode) const
 	unsigned int modifiers = p_scancode & KEY_MODIFIER_MASK;
 	unsigned int modifiers = p_scancode & KEY_MODIFIER_MASK;
 	unsigned int scancode_no_mod = p_scancode & KEY_CODE_MASK;
 	unsigned int scancode_no_mod = p_scancode & KEY_CODE_MASK;
 	unsigned int xkeycode = KeyMappingX11::get_xlibcode((uint32_t)scancode_no_mod);
 	unsigned int xkeycode = KeyMappingX11::get_xlibcode((uint32_t)scancode_no_mod);
-	KeySym xkeysym = XkbKeycodeToKeysym(x11_display, xkeycode, 0, 0);
+	KeySym xkeysym = XkbKeycodeToKeysym(x11_display, xkeycode, keyboard_get_current_layout(), 0);
 	if (xkeysym >= 'a' && xkeysym <= 'z') {
 	if (xkeysym >= 'a' && xkeysym <= 'z') {
 		xkeysym -= ('a' - 'A');
 		xkeysym -= ('a' - 'A');
 	}
 	}

+ 1 - 0
scene/2d/animated_sprite.cpp

@@ -274,6 +274,7 @@ void SpriteFrames::_set_animations(const Array &p_animations) {
 		}
 		}
 
 
 		animations[d["name"]] = anim;
 		animations[d["name"]] = anim;
+		animations[d["name"]].normal_name = String(d["name"]) + NORMAL_SUFFIX;
 	}
 	}
 }
 }
 
 

+ 4 - 4
scene/2d/canvas_item.cpp

@@ -466,7 +466,7 @@ void CanvasItem::_update_callback() {
 
 
 Transform2D CanvasItem::get_global_transform_with_canvas() const {
 Transform2D CanvasItem::get_global_transform_with_canvas() const {
 	if (canvas_layer) {
 	if (canvas_layer) {
-		return canvas_layer->get_transform() * get_global_transform();
+		return canvas_layer->get_final_transform() * get_global_transform();
 	} else if (is_inside_tree()) {
 	} else if (is_inside_tree()) {
 		return get_viewport()->get_canvas_transform() * get_global_transform();
 		return get_viewport()->get_canvas_transform() * get_global_transform();
 	} else {
 	} else {
@@ -1213,7 +1213,7 @@ Transform2D CanvasItem::get_canvas_transform() const {
 	ERR_FAIL_COND_V(!is_inside_tree(), Transform2D());
 	ERR_FAIL_COND_V(!is_inside_tree(), Transform2D());
 
 
 	if (canvas_layer) {
 	if (canvas_layer) {
-		return canvas_layer->get_transform();
+		return canvas_layer->get_final_transform();
 	} else if (Object::cast_to<CanvasItem>(get_parent())) {
 	} else if (Object::cast_to<CanvasItem>(get_parent())) {
 		return Object::cast_to<CanvasItem>(get_parent())->get_canvas_transform();
 		return Object::cast_to<CanvasItem>(get_parent())->get_canvas_transform();
 	} else {
 	} else {
@@ -1226,9 +1226,9 @@ Transform2D CanvasItem::get_viewport_transform() const {
 
 
 	if (canvas_layer) {
 	if (canvas_layer) {
 		if (get_viewport()) {
 		if (get_viewport()) {
-			return get_viewport()->get_final_transform() * canvas_layer->get_transform();
+			return get_viewport()->get_final_transform() * canvas_layer->get_final_transform();
 		} else {
 		} else {
-			return canvas_layer->get_transform();
+			return canvas_layer->get_final_transform();
 		}
 		}
 
 
 	} else {
 	} else {

+ 3 - 2
scene/gui/grid_container.cpp

@@ -40,8 +40,6 @@ void GridContainer::_notification(int p_what) {
 
 
 			int hsep = get_constant("hseparation");
 			int hsep = get_constant("hseparation");
 			int vsep = get_constant("vseparation");
 			int vsep = get_constant("vseparation");
-			int max_col = MIN(get_child_count(), columns);
-			int max_row = ceil((float)get_child_count() / (float)columns);
 
 
 			// Compute the per-column/per-row data.
 			// Compute the per-column/per-row data.
 			int valid_controls_index = 0;
 			int valid_controls_index = 0;
@@ -78,6 +76,9 @@ void GridContainer::_notification(int p_what) {
 				}
 				}
 			}
 			}
 
 
+			int max_col = MIN(valid_controls_index, columns);
+			int max_row = ceil((float)valid_controls_index / (float)columns);
+
 			// Consider all empty columns expanded.
 			// Consider all empty columns expanded.
 			for (int i = valid_controls_index; i < columns; i++) {
 			for (int i = valid_controls_index; i < columns; i++) {
 				col_expanded.insert(i);
 				col_expanded.insert(i);

+ 1 - 1
scene/gui/text_edit.cpp

@@ -5475,7 +5475,7 @@ int TextEdit::_is_line_in_region(int p_line) {
 	// If not find the closest line we have.
 	// If not find the closest line we have.
 	int previous_line = p_line - 1;
 	int previous_line = p_line - 1;
 	for (; previous_line > -1; previous_line--) {
 	for (; previous_line > -1; previous_line--) {
-		if (color_region_cache.has(p_line)) {
+		if (color_region_cache.has(previous_line)) {
 			break;
 			break;
 		}
 		}
 	}
 	}

+ 13 - 0
scene/main/canvas_layer.cpp

@@ -82,6 +82,18 @@ Transform2D CanvasLayer::get_transform() const {
 	return transform;
 	return transform;
 }
 }
 
 
+Transform2D CanvasLayer::get_final_transform() const {
+	if (is_following_viewport()) {
+		Transform2D follow;
+		follow.scale(Vector2(get_follow_viewport_scale(), get_follow_viewport_scale()));
+		if (vp) {
+			follow = vp->get_canvas_transform() * follow;
+		}
+		return follow * transform;
+	}
+	return transform;
+}
+
 void CanvasLayer::_update_xform() {
 void CanvasLayer::_update_xform() {
 	transform.set_rotation_and_scale(rot, scale);
 	transform.set_rotation_and_scale(rot, scale);
 	transform.set_origin(ofs);
 	transform.set_origin(ofs);
@@ -300,6 +312,7 @@ void CanvasLayer::_bind_methods() {
 
 
 	ClassDB::bind_method(D_METHOD("set_transform", "transform"), &CanvasLayer::set_transform);
 	ClassDB::bind_method(D_METHOD("set_transform", "transform"), &CanvasLayer::set_transform);
 	ClassDB::bind_method(D_METHOD("get_transform"), &CanvasLayer::get_transform);
 	ClassDB::bind_method(D_METHOD("get_transform"), &CanvasLayer::get_transform);
+	ClassDB::bind_method(D_METHOD("get_final_transform"), &CanvasLayer::get_final_transform);
 
 
 	ClassDB::bind_method(D_METHOD("set_offset", "offset"), &CanvasLayer::set_offset);
 	ClassDB::bind_method(D_METHOD("set_offset", "offset"), &CanvasLayer::set_offset);
 	ClassDB::bind_method(D_METHOD("get_offset"), &CanvasLayer::get_offset);
 	ClassDB::bind_method(D_METHOD("get_offset"), &CanvasLayer::get_offset);

+ 1 - 0
scene/main/canvas_layer.h

@@ -77,6 +77,7 @@ public:
 
 
 	void set_transform(const Transform2D &p_xform);
 	void set_transform(const Transform2D &p_xform);
 	Transform2D get_transform() const;
 	Transform2D get_transform() const;
+	Transform2D get_final_transform() const;
 
 
 	void set_offset(const Vector2 &p_offset);
 	void set_offset(const Vector2 &p_offset);
 	Vector2 get_offset() const;
 	Vector2 get_offset() const;

+ 1 - 1
scene/main/viewport.cpp

@@ -547,7 +547,7 @@ void Viewport::_process_picking(bool p_ignore_paused) {
 				ObjectID canvas_layer_id;
 				ObjectID canvas_layer_id;
 				if (E->get()) {
 				if (E->get()) {
 					// A descendant CanvasLayer
 					// A descendant CanvasLayer
-					canvas_transform = E->get()->get_transform();
+					canvas_transform = E->get()->get_final_transform();
 					canvas_layer_id = E->get()->get_instance_id();
 					canvas_layer_id = E->get()->get_instance_id();
 				} else {
 				} else {
 					// This Viewport's builtin canvas
 					// This Viewport's builtin canvas

+ 1 - 1
scene/resources/bit_map.cpp

@@ -318,7 +318,7 @@ Vector<Vector2> BitMap::_march_square(const Rect2i &rect, const Point2i &start)
 		prevx = stepx;
 		prevx = stepx;
 		prevy = stepy;
 		prevy = stepy;
 
 
-		ERR_FAIL_COND_V((int)count > width * height, _points);
+		ERR_FAIL_COND_V((int)count > 2 * (width * height + 1), _points);
 	} while (curx != startx || cury != starty);
 	} while (curx != startx || cury != starty);
 	return _points;
 	return _points;
 }
 }

+ 2 - 2
scene/resources/default_theme/default_theme.cpp

@@ -890,8 +890,8 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
 	theme->set_constant("vseparation", "GridContainer", 4 * scale);
 	theme->set_constant("vseparation", "GridContainer", 4 * scale);
 	theme->set_constant("separation", "HSplitContainer", 12 * scale);
 	theme->set_constant("separation", "HSplitContainer", 12 * scale);
 	theme->set_constant("separation", "VSplitContainer", 12 * scale);
 	theme->set_constant("separation", "VSplitContainer", 12 * scale);
-	theme->set_constant("autohide", "HSplitContainer", 1 * scale);
-	theme->set_constant("autohide", "VSplitContainer", 1 * scale);
+	theme->set_constant("autohide", "HSplitContainer", 1);
+	theme->set_constant("autohide", "VSplitContainer", 1);
 	theme->set_constant("hseparation", "HFlowContainer", 4 * scale);
 	theme->set_constant("hseparation", "HFlowContainer", 4 * scale);
 	theme->set_constant("vseparation", "HFlowContainer", 4 * scale);
 	theme->set_constant("vseparation", "HFlowContainer", 4 * scale);
 	theme->set_constant("hseparation", "VFlowContainer", 4 * scale);
 	theme->set_constant("hseparation", "VFlowContainer", 4 * scale);

+ 1 - 1
thirdparty/README.md

@@ -73,7 +73,7 @@ commits.
 ## enet
 ## enet
 
 
 - Upstream: http://enet.bespin.org
 - Upstream: http://enet.bespin.org
-- Version: 1.3.17 (e0e7045b7e056b454b5093cb34df49dc4cee0bee, 2020)
+- Version: git (ea4607a90dbfbcf4da2669ea998585253d8e70b1, 2023)
 - License: MIT
 - License: MIT
 
 
 Files extracted from upstream source:
 Files extracted from upstream source:

+ 10 - 6
thirdparty/enet/enet/enet.h

@@ -68,7 +68,8 @@ typedef enum _ENetSocketOption
    ENET_SOCKOPT_RCVTIMEO  = 6,
    ENET_SOCKOPT_RCVTIMEO  = 6,
    ENET_SOCKOPT_SNDTIMEO  = 7,
    ENET_SOCKOPT_SNDTIMEO  = 7,
    ENET_SOCKOPT_ERROR     = 8,
    ENET_SOCKOPT_ERROR     = 8,
-   ENET_SOCKOPT_NODELAY   = 9
+   ENET_SOCKOPT_NODELAY   = 9,
+   ENET_SOCKOPT_TTL       = 10
 } ENetSocketOption;
 } ENetSocketOption;
 
 
 typedef enum _ENetSocketShutdown
 typedef enum _ENetSocketShutdown
@@ -179,7 +180,7 @@ typedef struct _ENetOutgoingCommand
    enet_uint16  unreliableSequenceNumber;
    enet_uint16  unreliableSequenceNumber;
    enet_uint32  sentTime;
    enet_uint32  sentTime;
    enet_uint32  roundTripTimeout;
    enet_uint32  roundTripTimeout;
-   enet_uint32  roundTripTimeoutLimit;
+   enet_uint32  queueTime;
    enet_uint32  fragmentOffset;
    enet_uint32  fragmentOffset;
    enet_uint16  fragmentLength;
    enet_uint16  fragmentLength;
    enet_uint16  sendAttempts;
    enet_uint16  sendAttempts;
@@ -222,7 +223,7 @@ enum
    ENET_HOST_RECEIVE_BUFFER_SIZE          = 256 * 1024,
    ENET_HOST_RECEIVE_BUFFER_SIZE          = 256 * 1024,
    ENET_HOST_SEND_BUFFER_SIZE             = 256 * 1024,
    ENET_HOST_SEND_BUFFER_SIZE             = 256 * 1024,
    ENET_HOST_BANDWIDTH_THROTTLE_INTERVAL  = 1000,
    ENET_HOST_BANDWIDTH_THROTTLE_INTERVAL  = 1000,
-   ENET_HOST_DEFAULT_MTU                  = 1400,
+   ENET_HOST_DEFAULT_MTU                  = 1392,
    ENET_HOST_DEFAULT_MAXIMUM_PACKET_SIZE  = 32 * 1024 * 1024,
    ENET_HOST_DEFAULT_MAXIMUM_PACKET_SIZE  = 32 * 1024 * 1024,
    ENET_HOST_DEFAULT_MAXIMUM_WAITING_DATA = 32 * 1024 * 1024,
    ENET_HOST_DEFAULT_MAXIMUM_WAITING_DATA = 32 * 1024 * 1024,
 
 
@@ -262,7 +263,8 @@ typedef struct _ENetChannel
 
 
 typedef enum _ENetPeerFlag
 typedef enum _ENetPeerFlag
 {
 {
-   ENET_PEER_FLAG_NEEDS_DISPATCH = (1 << 0)
+   ENET_PEER_FLAG_NEEDS_DISPATCH   = (1 << 0),
+   ENET_PEER_FLAG_CONTINUE_SENDING = (1 << 1)
 } ENetPeerFlag;
 } ENetPeerFlag;
 
 
 /**
 /**
@@ -322,7 +324,7 @@ typedef struct _ENetPeer
    enet_uint16   outgoingReliableSequenceNumber;
    enet_uint16   outgoingReliableSequenceNumber;
    ENetList      acknowledgements;
    ENetList      acknowledgements;
    ENetList      sentReliableCommands;
    ENetList      sentReliableCommands;
-   ENetList      sentUnreliableCommands;
+   ENetList      outgoingSendReliableCommands;
    ENetList      outgoingCommands;
    ENetList      outgoingCommands;
    ENetList      dispatchedCommands;
    ENetList      dispatchedCommands;
    enet_uint16   flags;
    enet_uint16   flags;
@@ -385,7 +387,7 @@ typedef struct _ENetHost
    size_t               channelLimit;                /**< maximum number of channels allowed for connected peers */
    size_t               channelLimit;                /**< maximum number of channels allowed for connected peers */
    enet_uint32          serviceTime;
    enet_uint32          serviceTime;
    ENetList             dispatchQueue;
    ENetList             dispatchQueue;
-   int                  continueSending;
+   enet_uint32          totalQueued;
    size_t               packetSize;
    size_t               packetSize;
    enet_uint16          headerFlags;
    enet_uint16          headerFlags;
    ENetProtocol         commands [ENET_PROTOCOL_MAXIMUM_PACKET_COMMANDS];
    ENetProtocol         commands [ENET_PROTOCOL_MAXIMUM_PACKET_COMMANDS];
@@ -585,6 +587,7 @@ ENET_API void       enet_host_channel_limit (ENetHost *, size_t);
 ENET_API void       enet_host_bandwidth_limit (ENetHost *, enet_uint32, enet_uint32);
 ENET_API void       enet_host_bandwidth_limit (ENetHost *, enet_uint32, enet_uint32);
 extern   void       enet_host_bandwidth_throttle (ENetHost *);
 extern   void       enet_host_bandwidth_throttle (ENetHost *);
 extern  enet_uint32 enet_host_random_seed (void);
 extern  enet_uint32 enet_host_random_seed (void);
+extern  enet_uint32 enet_host_random (ENetHost *);
 
 
 ENET_API int                 enet_peer_send (ENetPeer *, enet_uint8, ENetPacket *);
 ENET_API int                 enet_peer_send (ENetPeer *, enet_uint8, ENetPacket *);
 ENET_API ENetPacket *        enet_peer_receive (ENetPeer *, enet_uint8 * channelID);
 ENET_API ENetPacket *        enet_peer_receive (ENetPeer *, enet_uint8 * channelID);
@@ -598,6 +601,7 @@ ENET_API void                enet_peer_disconnect_later (ENetPeer *, enet_uint32
 ENET_API void                enet_peer_throttle_configure (ENetPeer *, enet_uint32, enet_uint32, enet_uint32);
 ENET_API void                enet_peer_throttle_configure (ENetPeer *, enet_uint32, enet_uint32, enet_uint32);
 extern int                   enet_peer_throttle (ENetPeer *, enet_uint32);
 extern int                   enet_peer_throttle (ENetPeer *, enet_uint32);
 extern void                  enet_peer_reset_queues (ENetPeer *);
 extern void                  enet_peer_reset_queues (ENetPeer *);
+extern int                   enet_peer_has_outgoing_commands (ENetPeer *);
 extern void                  enet_peer_setup_outgoing_command (ENetPeer *, ENetOutgoingCommand *);
 extern void                  enet_peer_setup_outgoing_command (ENetPeer *, ENetOutgoingCommand *);
 extern ENetOutgoingCommand * enet_peer_queue_outgoing_command (ENetPeer *, const ENetProtocol *, ENetPacket *, enet_uint32, enet_uint16);
 extern ENetOutgoingCommand * enet_peer_queue_outgoing_command (ENetPeer *, const ENetProtocol *, ENetPacket *, enet_uint32, enet_uint16);
 extern ENetIncomingCommand * enet_peer_queue_incoming_command (ENetPeer *, const ENetProtocol *, const void *, size_t, enet_uint32, enet_uint32);
 extern ENetIncomingCommand * enet_peer_queue_incoming_command (ENetPeer *, const ENetProtocol *, const void *, size_t, enet_uint32, enet_uint32);

+ 14 - 2
thirdparty/enet/host.c

@@ -96,6 +96,7 @@ enet_host_create (const ENetAddress * address, size_t peerCount, size_t channelL
     host -> totalSentPackets = 0;
     host -> totalSentPackets = 0;
     host -> totalReceivedData = 0;
     host -> totalReceivedData = 0;
     host -> totalReceivedPackets = 0;
     host -> totalReceivedPackets = 0;
+    host -> totalQueued = 0;
 
 
     host -> connectedPeers = 0;
     host -> connectedPeers = 0;
     host -> bandwidthLimitedPeers = 0;
     host -> bandwidthLimitedPeers = 0;
@@ -123,8 +124,8 @@ enet_host_create (const ENetAddress * address, size_t peerCount, size_t channelL
 
 
        enet_list_clear (& currentPeer -> acknowledgements);
        enet_list_clear (& currentPeer -> acknowledgements);
        enet_list_clear (& currentPeer -> sentReliableCommands);
        enet_list_clear (& currentPeer -> sentReliableCommands);
-       enet_list_clear (& currentPeer -> sentUnreliableCommands);
        enet_list_clear (& currentPeer -> outgoingCommands);
        enet_list_clear (& currentPeer -> outgoingCommands);
+       enet_list_clear (& currentPeer -> outgoingSendReliableCommands);
        enet_list_clear (& currentPeer -> dispatchedCommands);
        enet_list_clear (& currentPeer -> dispatchedCommands);
 
 
        enet_peer_reset (currentPeer);
        enet_peer_reset (currentPeer);
@@ -160,6 +161,16 @@ enet_host_destroy (ENetHost * host)
     enet_free (host);
     enet_free (host);
 }
 }
 
 
+enet_uint32
+enet_host_random (ENetHost * host)
+{
+    /* Mulberry32 by Tommy Ettinger */
+    enet_uint32 n = (host -> randomSeed += 0x6D2B79F5U);
+    n = (n ^ (n >> 15)) * (n | 1U);
+    n ^= n + (n ^ (n >> 7)) * (n | 61U);
+    return n ^ (n >> 14);
+}
+
 /** Initiates a connection to a foreign host.
 /** Initiates a connection to a foreign host.
     @param host host seeking the connection
     @param host host seeking the connection
     @param address destination for the connection
     @param address destination for the connection
@@ -199,7 +210,8 @@ enet_host_connect (ENetHost * host, const ENetAddress * address, size_t channelC
     currentPeer -> channelCount = channelCount;
     currentPeer -> channelCount = channelCount;
     currentPeer -> state = ENET_PEER_STATE_CONNECTING;
     currentPeer -> state = ENET_PEER_STATE_CONNECTING;
     currentPeer -> address = * address;
     currentPeer -> address = * address;
-    currentPeer -> connectID = ++ host -> randomSeed;
+    currentPeer -> connectID = enet_host_random (host);
+    currentPeer -> mtu = host -> mtu;
 
 
     if (host -> outgoingBandwidth == 0)
     if (host -> outgoingBandwidth == 0)
       currentPeer -> windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE;
       currentPeer -> windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE;

+ 35 - 42
thirdparty/enet/packet.c

@@ -98,53 +98,46 @@ enet_packet_resize (ENetPacket * packet, size_t dataLength)
     return 0;
     return 0;
 }
 }
 
 
-static int initializedCRC32 = 0;
-static enet_uint32 crcTable [256];
-
-static enet_uint32 
-reflect_crc (int val, int bits)
+static const enet_uint32 crcTable [256] =
 {
 {
-    int result = 0, bit;
-
-    for (bit = 0; bit < bits; bit ++)
-    {
-        if(val & 1) result |= 1 << (bits - 1 - bit); 
-        val >>= 1;
-    }
-
-    return result;
-}
-
-static void 
-initialize_crc32 (void)
-{
-    int byte;
-
-    for (byte = 0; byte < 256; ++ byte)
-    {
-        enet_uint32 crc = reflect_crc (byte, 8) << 24;
-        int offset;
+    0,          0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
+    0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91,
+    0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
+    0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5,
+    0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
+    0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
+    0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
+    0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D,
+    0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
+    0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
+    0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457,
+    0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
+    0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB,
+    0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,
+    0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
+    0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD,
+    0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683,
+    0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
+    0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7,
+    0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
+    0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
+    0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79,
+    0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F,
+    0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
+    0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x5005713,
+    0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0xBDBDF21,
+    0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
+    0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,
+    0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB,
+    0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
+    0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF,
+    0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D
+};
 
 
-        for(offset = 0; offset < 8; ++ offset)
-        {
-            if (crc & 0x80000000)
-                crc = (crc << 1) ^ 0x04c11db7;
-            else
-                crc <<= 1;
-        }
-
-        crcTable [byte] = reflect_crc (crc, 32);
-    }
-
-    initializedCRC32 = 1;
-}
-    
 enet_uint32
 enet_uint32
 enet_crc32 (const ENetBuffer * buffers, size_t bufferCount)
 enet_crc32 (const ENetBuffer * buffers, size_t bufferCount)
 {
 {
     enet_uint32 crc = 0xFFFFFFFF;
     enet_uint32 crc = 0xFFFFFFFF;
-    
-    if (! initializedCRC32) initialize_crc32 ();
 
 
     while (bufferCount -- > 0)
     while (bufferCount -- > 0)
     {
     {
@@ -153,7 +146,7 @@ enet_crc32 (const ENetBuffer * buffers, size_t bufferCount)
 
 
         while (data < dataEnd)
         while (data < dataEnd)
         {
         {
-            crc = (crc >> 8) ^ crcTable [(crc & 0xFF) ^ *data++];        
+            crc = (crc >> 8) ^ crcTable [(crc & 0xFF) ^ *data++];
         }
         }
 
 
         ++ buffers;
         ++ buffers;

+ 54 - 30
thirdparty/enet/peer.c

@@ -90,6 +90,13 @@ enet_peer_throttle (ENetPeer * peer, enet_uint32 rtt)
 }
 }
 
 
 /** Queues a packet to be sent.
 /** Queues a packet to be sent.
+
+    On success, ENet will assume ownership of the packet, and so enet_packet_destroy
+    should not be called on it thereafter. On failure, the caller still must destroy
+    the packet on its own as ENet has not queued the packet. The caller can also
+    check the packet's referenceCount field after sending to check if ENet queued
+    the packet and thus incremented the referenceCount.
+
     @param peer destination for the packet
     @param peer destination for the packet
     @param channelID channel on which to send
     @param channelID channel on which to send
     @param packet packet to send
     @param packet packet to send
@@ -99,7 +106,7 @@ enet_peer_throttle (ENetPeer * peer, enet_uint32 rtt)
 int
 int
 enet_peer_send (ENetPeer * peer, enet_uint8 channelID, ENetPacket * packet)
 enet_peer_send (ENetPeer * peer, enet_uint8 channelID, ENetPacket * packet)
 {
 {
-   ENetChannel * channel = & peer -> channels [channelID];
+   ENetChannel * channel;
    ENetProtocol command;
    ENetProtocol command;
    size_t fragmentLength;
    size_t fragmentLength;
 
 
@@ -108,6 +115,7 @@ enet_peer_send (ENetPeer * peer, enet_uint8 channelID, ENetPacket * packet)
        packet -> dataLength > peer -> host -> maximumPacketSize)
        packet -> dataLength > peer -> host -> maximumPacketSize)
      return -1;
      return -1;
 
 
+   channel = & peer -> channels [channelID];
    fragmentLength = peer -> mtu - sizeof (ENetProtocolHeader) - sizeof (ENetProtocolSendFragment);
    fragmentLength = peer -> mtu - sizeof (ENetProtocolHeader) - sizeof (ENetProtocolSendFragment);
    if (peer -> host -> checksum != NULL)
    if (peer -> host -> checksum != NULL)
      fragmentLength -= sizeof(enet_uint32);
      fragmentLength -= sizeof(enet_uint32);
@@ -320,8 +328,8 @@ enet_peer_reset_queues (ENetPeer * peer)
       enet_free (enet_list_remove (enet_list_begin (& peer -> acknowledgements)));
       enet_free (enet_list_remove (enet_list_begin (& peer -> acknowledgements)));
 
 
     enet_peer_reset_outgoing_commands (& peer -> sentReliableCommands);
     enet_peer_reset_outgoing_commands (& peer -> sentReliableCommands);
-    enet_peer_reset_outgoing_commands (& peer -> sentUnreliableCommands);
     enet_peer_reset_outgoing_commands (& peer -> outgoingCommands);
     enet_peer_reset_outgoing_commands (& peer -> outgoingCommands);
+    enet_peer_reset_outgoing_commands (& peer -> outgoingSendReliableCommands);
     enet_peer_reset_incoming_commands (& peer -> dispatchedCommands);
     enet_peer_reset_incoming_commands (& peer -> dispatchedCommands);
 
 
     if (peer -> channels != NULL && peer -> channelCount > 0)
     if (peer -> channels != NULL && peer -> channelCount > 0)
@@ -563,6 +571,17 @@ enet_peer_disconnect (ENetPeer * peer, enet_uint32 data)
     }
     }
 }
 }
 
 
+int
+enet_peer_has_outgoing_commands (ENetPeer * peer)
+{
+  if (enet_list_empty (& peer -> outgoingCommands) &&
+      enet_list_empty (& peer -> outgoingSendReliableCommands) &&
+      enet_list_empty (& peer -> sentReliableCommands))
+    return 0;
+
+  return 1;
+}
+
 /** Request a disconnection from a peer, but only after all queued outgoing packets are sent.
 /** Request a disconnection from a peer, but only after all queued outgoing packets are sent.
     @param peer peer to request a disconnection
     @param peer peer to request a disconnection
     @param data data describing the disconnection
     @param data data describing the disconnection
@@ -573,8 +592,7 @@ void
 enet_peer_disconnect_later (ENetPeer * peer, enet_uint32 data)
 enet_peer_disconnect_later (ENetPeer * peer, enet_uint32 data)
 {   
 {   
     if ((peer -> state == ENET_PEER_STATE_CONNECTED || peer -> state == ENET_PEER_STATE_DISCONNECT_LATER) && 
     if ((peer -> state == ENET_PEER_STATE_CONNECTED || peer -> state == ENET_PEER_STATE_DISCONNECT_LATER) && 
-        ! (enet_list_empty (& peer -> outgoingCommands) &&
-           enet_list_empty (& peer -> sentReliableCommands)))
+        enet_peer_has_outgoing_commands (peer))
     {
     {
         peer -> state = ENET_PEER_STATE_DISCONNECT_LATER;
         peer -> state = ENET_PEER_STATE_DISCONNECT_LATER;
         peer -> eventData = data;
         peer -> eventData = data;
@@ -618,8 +636,6 @@ enet_peer_queue_acknowledgement (ENetPeer * peer, const ENetProtocol * command,
 void
 void
 enet_peer_setup_outgoing_command (ENetPeer * peer, ENetOutgoingCommand * outgoingCommand)
 enet_peer_setup_outgoing_command (ENetPeer * peer, ENetOutgoingCommand * outgoingCommand)
 {
 {
-    ENetChannel * channel = & peer -> channels [outgoingCommand -> command.header.channelID];
-    
     peer -> outgoingDataTotal += enet_protocol_command_size (outgoingCommand -> command.header.command) + outgoingCommand -> fragmentLength;
     peer -> outgoingDataTotal += enet_protocol_command_size (outgoingCommand -> command.header.command) + outgoingCommand -> fragmentLength;
 
 
     if (outgoingCommand -> command.header.channelID == 0xFF)
     if (outgoingCommand -> command.header.channelID == 0xFF)
@@ -630,36 +646,40 @@ enet_peer_setup_outgoing_command (ENetPeer * peer, ENetOutgoingCommand * outgoin
        outgoingCommand -> unreliableSequenceNumber = 0;
        outgoingCommand -> unreliableSequenceNumber = 0;
     }
     }
     else
     else
-    if (outgoingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE)
     {
     {
-       ++ channel -> outgoingReliableSequenceNumber;
-       channel -> outgoingUnreliableSequenceNumber = 0;
+        ENetChannel * channel = & peer -> channels [outgoingCommand -> command.header.channelID];
 
 
-       outgoingCommand -> reliableSequenceNumber = channel -> outgoingReliableSequenceNumber;
-       outgoingCommand -> unreliableSequenceNumber = 0;
-    }
-    else
-    if (outgoingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_FLAG_UNSEQUENCED)
-    {
-       ++ peer -> outgoingUnsequencedGroup;
+        if (outgoingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE)
+        {
+           ++ channel -> outgoingReliableSequenceNumber;
+           channel -> outgoingUnreliableSequenceNumber = 0;
 
 
-       outgoingCommand -> reliableSequenceNumber = 0;
-       outgoingCommand -> unreliableSequenceNumber = 0;
-    }
-    else
-    {
-       if (outgoingCommand -> fragmentOffset == 0)
-         ++ channel -> outgoingUnreliableSequenceNumber;
-        
-       outgoingCommand -> reliableSequenceNumber = channel -> outgoingReliableSequenceNumber;
-       outgoingCommand -> unreliableSequenceNumber = channel -> outgoingUnreliableSequenceNumber;
+           outgoingCommand -> reliableSequenceNumber = channel -> outgoingReliableSequenceNumber;
+           outgoingCommand -> unreliableSequenceNumber = 0;
+        }
+        else
+        if (outgoingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_FLAG_UNSEQUENCED)
+        {
+           ++ peer -> outgoingUnsequencedGroup;
+
+           outgoingCommand -> reliableSequenceNumber = 0;
+           outgoingCommand -> unreliableSequenceNumber = 0;
+        }
+        else
+        {
+           if (outgoingCommand -> fragmentOffset == 0)
+             ++ channel -> outgoingUnreliableSequenceNumber;
+
+           outgoingCommand -> reliableSequenceNumber = channel -> outgoingReliableSequenceNumber;
+           outgoingCommand -> unreliableSequenceNumber = channel -> outgoingUnreliableSequenceNumber;
+        }
     }
     }
-   
+
     outgoingCommand -> sendAttempts = 0;
     outgoingCommand -> sendAttempts = 0;
     outgoingCommand -> sentTime = 0;
     outgoingCommand -> sentTime = 0;
     outgoingCommand -> roundTripTimeout = 0;
     outgoingCommand -> roundTripTimeout = 0;
-    outgoingCommand -> roundTripTimeoutLimit = 0;
     outgoingCommand -> command.header.reliableSequenceNumber = ENET_HOST_TO_NET_16 (outgoingCommand -> reliableSequenceNumber);
     outgoingCommand -> command.header.reliableSequenceNumber = ENET_HOST_TO_NET_16 (outgoingCommand -> reliableSequenceNumber);
+    outgoingCommand -> queueTime = ++ peer -> host -> totalQueued;
 
 
     switch (outgoingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_MASK)
     switch (outgoingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_MASK)
     {
     {
@@ -670,12 +690,16 @@ enet_peer_setup_outgoing_command (ENetPeer * peer, ENetOutgoingCommand * outgoin
     case ENET_PROTOCOL_COMMAND_SEND_UNSEQUENCED:
     case ENET_PROTOCOL_COMMAND_SEND_UNSEQUENCED:
         outgoingCommand -> command.sendUnsequenced.unsequencedGroup = ENET_HOST_TO_NET_16 (peer -> outgoingUnsequencedGroup);
         outgoingCommand -> command.sendUnsequenced.unsequencedGroup = ENET_HOST_TO_NET_16 (peer -> outgoingUnsequencedGroup);
         break;
         break;
-    
+
     default:
     default:
         break;
         break;
     }
     }
 
 
-    enet_list_insert (enet_list_end (& peer -> outgoingCommands), outgoingCommand);
+    if ((outgoingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE) != 0 &&
+        outgoingCommand -> packet != NULL)
+      enet_list_insert (enet_list_end (& peer -> outgoingSendReliableCommands), outgoingCommand);
+    else
+      enet_list_insert (enet_list_end (& peer -> outgoingCommands), outgoingCommand);
 }
 }
 
 
 ENetOutgoingCommand *
 ENetOutgoingCommand *

+ 123 - 87
thirdparty/enet/protocol.c

@@ -9,7 +9,7 @@
 #include "enet/time.h"
 #include "enet/time.h"
 #include "enet/enet.h"
 #include "enet/enet.h"
 
 
-static size_t commandSizes [ENET_PROTOCOL_COMMAND_COUNT] =
+static const size_t commandSizes [ENET_PROTOCOL_COMMAND_COUNT] =
 {
 {
     0,
     0,
     sizeof (ENetProtocolAcknowledge),
     sizeof (ENetProtocolAcknowledge),
@@ -159,16 +159,16 @@ enet_protocol_notify_disconnect (ENetHost * host, ENetPeer * peer, ENetEvent * e
 }
 }
 
 
 static void
 static void
-enet_protocol_remove_sent_unreliable_commands (ENetPeer * peer)
+enet_protocol_remove_sent_unreliable_commands (ENetPeer * peer, ENetList * sentUnreliableCommands)
 {
 {
     ENetOutgoingCommand * outgoingCommand;
     ENetOutgoingCommand * outgoingCommand;
 
 
-    if (enet_list_empty (& peer -> sentUnreliableCommands))
+    if (enet_list_empty (sentUnreliableCommands))
       return;
       return;
 
 
     do
     do
     {
     {
-        outgoingCommand = (ENetOutgoingCommand *) enet_list_front (& peer -> sentUnreliableCommands);
+        outgoingCommand = (ENetOutgoingCommand *) enet_list_front (sentUnreliableCommands);
         
         
         enet_list_remove (& outgoingCommand -> outgoingCommandList);
         enet_list_remove (& outgoingCommand -> outgoingCommandList);
 
 
@@ -185,14 +185,38 @@ enet_protocol_remove_sent_unreliable_commands (ENetPeer * peer)
         }
         }
 
 
         enet_free (outgoingCommand);
         enet_free (outgoingCommand);
-    } while (! enet_list_empty (& peer -> sentUnreliableCommands));
+    } while (! enet_list_empty (sentUnreliableCommands));
 
 
     if (peer -> state == ENET_PEER_STATE_DISCONNECT_LATER &&
     if (peer -> state == ENET_PEER_STATE_DISCONNECT_LATER &&
-        enet_list_empty (& peer -> outgoingCommands) &&
-        enet_list_empty (& peer -> sentReliableCommands))
+        ! enet_peer_has_outgoing_commands (peer))
       enet_peer_disconnect (peer, peer -> eventData);
       enet_peer_disconnect (peer, peer -> eventData);
 }
 }
 
 
+static ENetOutgoingCommand *
+enet_protocol_find_sent_reliable_command (ENetList * list, enet_uint16 reliableSequenceNumber, enet_uint8 channelID)
+{
+    ENetListIterator currentCommand;
+
+    for (currentCommand = enet_list_begin (list);
+         currentCommand != enet_list_end (list);
+         currentCommand = enet_list_next (currentCommand))
+    {
+       ENetOutgoingCommand * outgoingCommand = (ENetOutgoingCommand *) currentCommand;
+
+       if (! (outgoingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE))
+         continue;
+
+       if (outgoingCommand -> sendAttempts < 1)
+         break;
+
+       if (outgoingCommand -> reliableSequenceNumber == reliableSequenceNumber &&
+           outgoingCommand -> command.header.channelID == channelID)
+         return outgoingCommand;
+    }
+
+    return NULL;
+}
+
 static ENetProtocolCommand
 static ENetProtocolCommand
 enet_protocol_remove_sent_reliable_command (ENetPeer * peer, enet_uint16 reliableSequenceNumber, enet_uint8 channelID)
 enet_protocol_remove_sent_reliable_command (ENetPeer * peer, enet_uint16 reliableSequenceNumber, enet_uint8 channelID)
 {
 {
@@ -214,24 +238,9 @@ enet_protocol_remove_sent_reliable_command (ENetPeer * peer, enet_uint16 reliabl
 
 
     if (currentCommand == enet_list_end (& peer -> sentReliableCommands))
     if (currentCommand == enet_list_end (& peer -> sentReliableCommands))
     {
     {
-       for (currentCommand = enet_list_begin (& peer -> outgoingCommands);
-            currentCommand != enet_list_end (& peer -> outgoingCommands);
-            currentCommand = enet_list_next (currentCommand))
-       {
-          outgoingCommand = (ENetOutgoingCommand *) currentCommand;
-
-          if (! (outgoingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE))
-            continue;
-
-          if (outgoingCommand -> sendAttempts < 1) return ENET_PROTOCOL_COMMAND_NONE;
-
-          if (outgoingCommand -> reliableSequenceNumber == reliableSequenceNumber &&
-              outgoingCommand -> command.header.channelID == channelID)
-            break;
-       }
-
-       if (currentCommand == enet_list_end (& peer -> outgoingCommands))
-         return ENET_PROTOCOL_COMMAND_NONE;
+       outgoingCommand = enet_protocol_find_sent_reliable_command (& peer -> outgoingCommands, reliableSequenceNumber, channelID);
+       if (outgoingCommand == NULL)
+         outgoingCommand = enet_protocol_find_sent_reliable_command (& peer -> outgoingSendReliableCommands, reliableSequenceNumber, channelID);
 
 
        wasSent = 0;
        wasSent = 0;
     }
     }
@@ -331,6 +340,7 @@ enet_protocol_handle_connect (ENetHost * host, ENetProtocolHeader * header, ENet
     peer -> state = ENET_PEER_STATE_ACKNOWLEDGING_CONNECT;
     peer -> state = ENET_PEER_STATE_ACKNOWLEDGING_CONNECT;
     peer -> connectID = command -> connect.connectID;
     peer -> connectID = command -> connect.connectID;
     peer -> address = host -> receivedAddress;
     peer -> address = host -> receivedAddress;
+    peer -> mtu = host -> mtu;
     peer -> outgoingPeerID = ENET_NET_TO_HOST_16 (command -> connect.outgoingPeerID);
     peer -> outgoingPeerID = ENET_NET_TO_HOST_16 (command -> connect.outgoingPeerID);
     peer -> incomingBandwidth = ENET_NET_TO_HOST_32 (command -> connect.incomingBandwidth);
     peer -> incomingBandwidth = ENET_NET_TO_HOST_32 (command -> connect.incomingBandwidth);
     peer -> outgoingBandwidth = ENET_NET_TO_HOST_32 (command -> connect.outgoingBandwidth);
     peer -> outgoingBandwidth = ENET_NET_TO_HOST_32 (command -> connect.outgoingBandwidth);
@@ -375,7 +385,8 @@ enet_protocol_handle_connect (ENetHost * host, ENetProtocolHeader * header, ENet
     if (mtu > ENET_PROTOCOL_MAXIMUM_MTU)
     if (mtu > ENET_PROTOCOL_MAXIMUM_MTU)
       mtu = ENET_PROTOCOL_MAXIMUM_MTU;
       mtu = ENET_PROTOCOL_MAXIMUM_MTU;
 
 
-    peer -> mtu = mtu;
+    if (mtu < peer -> mtu)
+      peer -> mtu = mtu;
 
 
     if (host -> outgoingBandwidth == 0 &&
     if (host -> outgoingBandwidth == 0 &&
         peer -> incomingBandwidth == 0)
         peer -> incomingBandwidth == 0)
@@ -542,7 +553,8 @@ enet_protocol_handle_send_fragment (ENetHost * host, ENetPeer * peer, const ENet
 
 
     fragmentLength = ENET_NET_TO_HOST_16 (command -> sendFragment.dataLength);
     fragmentLength = ENET_NET_TO_HOST_16 (command -> sendFragment.dataLength);
     * currentData += fragmentLength;
     * currentData += fragmentLength;
-    if (fragmentLength > host -> maximumPacketSize ||
+    if (fragmentLength <= 0 ||
+        fragmentLength > host -> maximumPacketSize ||
         * currentData < host -> receivedData ||
         * currentData < host -> receivedData ||
         * currentData > & host -> receivedData [host -> receivedDataLength])
         * currentData > & host -> receivedData [host -> receivedDataLength])
       return -1;
       return -1;
@@ -566,6 +578,7 @@ enet_protocol_handle_send_fragment (ENetHost * host, ENetPeer * peer, const ENet
     if (fragmentCount > ENET_PROTOCOL_MAXIMUM_FRAGMENT_COUNT ||
     if (fragmentCount > ENET_PROTOCOL_MAXIMUM_FRAGMENT_COUNT ||
         fragmentNumber >= fragmentCount ||
         fragmentNumber >= fragmentCount ||
         totalLength > host -> maximumPacketSize ||
         totalLength > host -> maximumPacketSize ||
+        totalLength < fragmentCount ||
         fragmentOffset >= totalLength ||
         fragmentOffset >= totalLength ||
         fragmentLength > totalLength - fragmentOffset)
         fragmentLength > totalLength - fragmentOffset)
       return -1;
       return -1;
@@ -921,8 +934,7 @@ enet_protocol_handle_acknowledge (ENetHost * host, ENetEvent * event, ENetPeer *
        break;
        break;
 
 
     case ENET_PEER_STATE_DISCONNECT_LATER:
     case ENET_PEER_STATE_DISCONNECT_LATER:
-       if (enet_list_empty (& peer -> outgoingCommands) &&
-           enet_list_empty (& peer -> sentReliableCommands))
+       if (! enet_peer_has_outgoing_commands (peer))
          enet_peer_disconnect (peer, peer -> eventData);
          enet_peer_disconnect (peer, peer -> eventData);
        break;
        break;
 
 
@@ -1230,6 +1242,9 @@ enet_protocol_receive_incoming_commands (ENetHost * host, ENetEvent * event)
                                              & buffer,
                                              & buffer,
                                              1);
                                              1);
 
 
+       if (receivedLength == -2)
+         continue;
+
        if (receivedLength < 0)
        if (receivedLength < 0)
          return -1;
          return -1;
 
 
@@ -1293,7 +1308,7 @@ enet_protocol_send_acknowledgements (ENetHost * host, ENetPeer * peer)
            buffer >= & host -> buffers [sizeof (host -> buffers) / sizeof (ENetBuffer)] ||
            buffer >= & host -> buffers [sizeof (host -> buffers) / sizeof (ENetBuffer)] ||
            peer -> mtu - host -> packetSize < sizeof (ENetProtocolAcknowledge))
            peer -> mtu - host -> packetSize < sizeof (ENetProtocolAcknowledge))
        {
        {
-          host -> continueSending = 1;
+          peer -> flags |= ENET_PEER_FLAG_CONTINUE_SENDING;
 
 
           break;
           break;
        }
        }
@@ -1333,10 +1348,11 @@ static int
 enet_protocol_check_timeouts (ENetHost * host, ENetPeer * peer, ENetEvent * event)
 enet_protocol_check_timeouts (ENetHost * host, ENetPeer * peer, ENetEvent * event)
 {
 {
     ENetOutgoingCommand * outgoingCommand;
     ENetOutgoingCommand * outgoingCommand;
-    ENetListIterator currentCommand, insertPosition;
+    ENetListIterator currentCommand, insertPosition, insertSendReliablePosition;
 
 
     currentCommand = enet_list_begin (& peer -> sentReliableCommands);
     currentCommand = enet_list_begin (& peer -> sentReliableCommands);
     insertPosition = enet_list_begin (& peer -> outgoingCommands);
     insertPosition = enet_list_begin (& peer -> outgoingCommands);
+    insertSendReliablePosition = enet_list_begin (& peer -> outgoingSendReliableCommands);
 
 
     while (currentCommand != enet_list_end (& peer -> sentReliableCommands))
     while (currentCommand != enet_list_end (& peer -> sentReliableCommands))
     {
     {
@@ -1353,7 +1369,7 @@ enet_protocol_check_timeouts (ENetHost * host, ENetPeer * peer, ENetEvent * even
 
 
        if (peer -> earliestTimeout != 0 &&
        if (peer -> earliestTimeout != 0 &&
              (ENET_TIME_DIFFERENCE (host -> serviceTime, peer -> earliestTimeout) >= peer -> timeoutMaximum ||
              (ENET_TIME_DIFFERENCE (host -> serviceTime, peer -> earliestTimeout) >= peer -> timeoutMaximum ||
-               (outgoingCommand -> roundTripTimeout >= outgoingCommand -> roundTripTimeoutLimit &&
+               ((1 << (outgoingCommand -> sendAttempts - 1)) >= peer -> timeoutLimit &&
                  ENET_TIME_DIFFERENCE (host -> serviceTime, peer -> earliestTimeout) >= peer -> timeoutMinimum)))
                  ENET_TIME_DIFFERENCE (host -> serviceTime, peer -> earliestTimeout) >= peer -> timeoutMinimum)))
        {
        {
           enet_protocol_notify_disconnect (host, peer, event);
           enet_protocol_notify_disconnect (host, peer, event);
@@ -1361,14 +1377,18 @@ enet_protocol_check_timeouts (ENetHost * host, ENetPeer * peer, ENetEvent * even
           return 1;
           return 1;
        }
        }
 
 
-       if (outgoingCommand -> packet != NULL)
-         peer -> reliableDataInTransit -= outgoingCommand -> fragmentLength;
-          
        ++ peer -> packetsLost;
        ++ peer -> packetsLost;
 
 
        outgoingCommand -> roundTripTimeout *= 2;
        outgoingCommand -> roundTripTimeout *= 2;
 
 
-       enet_list_insert (insertPosition, enet_list_remove (& outgoingCommand -> outgoingCommandList));
+       if (outgoingCommand -> packet != NULL)
+       {
+         peer -> reliableDataInTransit -= outgoingCommand -> fragmentLength;
+
+         enet_list_insert (insertSendReliablePosition, enet_list_remove (& outgoingCommand -> outgoingCommandList));
+       }
+       else
+         enet_list_insert (insertPosition, enet_list_remove (& outgoingCommand -> outgoingCommandList));
 
 
        if (currentCommand == enet_list_begin (& peer -> sentReliableCommands) &&
        if (currentCommand == enet_list_begin (& peer -> sentReliableCommands) &&
            ! enet_list_empty (& peer -> sentReliableCommands))
            ! enet_list_empty (& peer -> sentReliableCommands))
@@ -1383,22 +1403,41 @@ enet_protocol_check_timeouts (ENetHost * host, ENetPeer * peer, ENetEvent * even
 }
 }
 
 
 static int
 static int
-enet_protocol_check_outgoing_commands (ENetHost * host, ENetPeer * peer)
+enet_protocol_check_outgoing_commands (ENetHost * host, ENetPeer * peer, ENetList * sentUnreliableCommands)
 {
 {
     ENetProtocol * command = & host -> commands [host -> commandCount];
     ENetProtocol * command = & host -> commands [host -> commandCount];
     ENetBuffer * buffer = & host -> buffers [host -> bufferCount];
     ENetBuffer * buffer = & host -> buffers [host -> bufferCount];
     ENetOutgoingCommand * outgoingCommand;
     ENetOutgoingCommand * outgoingCommand;
-    ENetListIterator currentCommand;
-    ENetChannel *channel;
-    enet_uint16 reliableWindow;
+    ENetListIterator currentCommand, currentSendReliableCommand;
+    ENetChannel *channel = NULL;
+    enet_uint16 reliableWindow = 0;
     size_t commandSize;
     size_t commandSize;
-    int windowExceeded = 0, windowWrap = 0, canPing = 1;
+    int windowWrap = 0, canPing = 1;
 
 
     currentCommand = enet_list_begin (& peer -> outgoingCommands);
     currentCommand = enet_list_begin (& peer -> outgoingCommands);
-    
-    while (currentCommand != enet_list_end (& peer -> outgoingCommands))
+    currentSendReliableCommand = enet_list_begin (& peer -> outgoingSendReliableCommands);
+
+    for (;;)
     {
     {
-       outgoingCommand = (ENetOutgoingCommand *) currentCommand;
+       if (currentCommand != enet_list_end (& peer -> outgoingCommands))
+       {
+          outgoingCommand = (ENetOutgoingCommand *) currentCommand;
+
+          if (currentSendReliableCommand != enet_list_end (& peer -> outgoingSendReliableCommands) &&
+              ENET_TIME_LESS (((ENetOutgoingCommand *) currentSendReliableCommand) -> queueTime, outgoingCommand -> queueTime))
+            goto useSendReliableCommand;
+
+          currentCommand = enet_list_next (currentCommand);
+       }
+       else
+       if (currentSendReliableCommand != enet_list_end (& peer -> outgoingSendReliableCommands))
+       {
+       useSendReliableCommand:
+          outgoingCommand = (ENetOutgoingCommand *) currentSendReliableCommand;
+          currentSendReliableCommand = enet_list_next (currentSendReliableCommand);
+       }
+       else
+         break;
 
 
        if (outgoingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE)
        if (outgoingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE)
        {
        {
@@ -1406,33 +1445,29 @@ enet_protocol_check_outgoing_commands (ENetHost * host, ENetPeer * peer)
           reliableWindow = outgoingCommand -> reliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE;
           reliableWindow = outgoingCommand -> reliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE;
           if (channel != NULL)
           if (channel != NULL)
           {
           {
-             if (! windowWrap &&      
-                  outgoingCommand -> sendAttempts < 1 && 
+             if (windowWrap)
+               continue;
+             else
+             if (outgoingCommand -> sendAttempts < 1 &&
                   ! (outgoingCommand -> reliableSequenceNumber % ENET_PEER_RELIABLE_WINDOW_SIZE) &&
                   ! (outgoingCommand -> reliableSequenceNumber % ENET_PEER_RELIABLE_WINDOW_SIZE) &&
                   (channel -> reliableWindows [(reliableWindow + ENET_PEER_RELIABLE_WINDOWS - 1) % ENET_PEER_RELIABLE_WINDOWS] >= ENET_PEER_RELIABLE_WINDOW_SIZE ||
                   (channel -> reliableWindows [(reliableWindow + ENET_PEER_RELIABLE_WINDOWS - 1) % ENET_PEER_RELIABLE_WINDOWS] >= ENET_PEER_RELIABLE_WINDOW_SIZE ||
                     channel -> usedReliableWindows & ((((1 << (ENET_PEER_FREE_RELIABLE_WINDOWS + 2)) - 1) << reliableWindow) |
                     channel -> usedReliableWindows & ((((1 << (ENET_PEER_FREE_RELIABLE_WINDOWS + 2)) - 1) << reliableWindow) |
                       (((1 << (ENET_PEER_FREE_RELIABLE_WINDOWS + 2)) - 1) >> (ENET_PEER_RELIABLE_WINDOWS - reliableWindow)))))
                       (((1 << (ENET_PEER_FREE_RELIABLE_WINDOWS + 2)) - 1) >> (ENET_PEER_RELIABLE_WINDOWS - reliableWindow)))))
-                windowWrap = 1;
-             if (windowWrap)
              {
              {
-                currentCommand = enet_list_next (currentCommand);
- 
+                windowWrap = 1;
+                currentSendReliableCommand = enet_list_end (& peer -> outgoingSendReliableCommands);
+
                 continue;
                 continue;
              }
              }
           }
           }
- 
+
           if (outgoingCommand -> packet != NULL)
           if (outgoingCommand -> packet != NULL)
           {
           {
-             if (! windowExceeded)
-             {
-                enet_uint32 windowSize = (peer -> packetThrottle * peer -> windowSize) / ENET_PEER_PACKET_THROTTLE_SCALE;
-             
-                if (peer -> reliableDataInTransit + outgoingCommand -> fragmentLength > ENET_MAX (windowSize, peer -> mtu))
-                  windowExceeded = 1;
-             }
-             if (windowExceeded)
+             enet_uint32 windowSize = (peer -> packetThrottle * peer -> windowSize) / ENET_PEER_PACKET_THROTTLE_SCALE;
+
+             if (peer -> reliableDataInTransit + outgoingCommand -> fragmentLength > ENET_MAX (windowSize, peer -> mtu))
              {
              {
-                currentCommand = enet_list_next (currentCommand);
+                currentSendReliableCommand = enet_list_end (& peer -> outgoingSendReliableCommands);
 
 
                 continue;
                 continue;
              }
              }
@@ -1448,13 +1483,11 @@ enet_protocol_check_outgoing_commands (ENetHost * host, ENetPeer * peer)
            (outgoingCommand -> packet != NULL && 
            (outgoingCommand -> packet != NULL && 
              (enet_uint16) (peer -> mtu - host -> packetSize) < (enet_uint16) (commandSize + outgoingCommand -> fragmentLength)))
              (enet_uint16) (peer -> mtu - host -> packetSize) < (enet_uint16) (commandSize + outgoingCommand -> fragmentLength)))
        {
        {
-          host -> continueSending = 1;
-          
+          peer -> flags |= ENET_PEER_FLAG_CONTINUE_SENDING;
+
           break;
           break;
        }
        }
 
 
-       currentCommand = enet_list_next (currentCommand);
-
        if (outgoingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE)
        if (outgoingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE)
        {
        {
           if (channel != NULL && outgoingCommand -> sendAttempts < 1)
           if (channel != NULL && outgoingCommand -> sendAttempts < 1)
@@ -1466,10 +1499,7 @@ enet_protocol_check_outgoing_commands (ENetHost * host, ENetPeer * peer)
           ++ outgoingCommand -> sendAttempts;
           ++ outgoingCommand -> sendAttempts;
  
  
           if (outgoingCommand -> roundTripTimeout == 0)
           if (outgoingCommand -> roundTripTimeout == 0)
-          {
-             outgoingCommand -> roundTripTimeout = peer -> roundTripTime + 4 * peer -> roundTripTimeVariance;
-             outgoingCommand -> roundTripTimeoutLimit = peer -> timeoutLimit * outgoingCommand -> roundTripTimeout;
-          }
+            outgoingCommand -> roundTripTimeout = peer -> roundTripTime + 4 * peer -> roundTripTimeVariance;
 
 
           if (enet_list_empty (& peer -> sentReliableCommands))
           if (enet_list_empty (& peer -> sentReliableCommands))
             peer -> nextTimeout = host -> serviceTime + outgoingCommand -> roundTripTimeout;
             peer -> nextTimeout = host -> serviceTime + outgoingCommand -> roundTripTimeout;
@@ -1522,7 +1552,7 @@ enet_protocol_check_outgoing_commands (ENetHost * host, ENetPeer * peer)
           enet_list_remove (& outgoingCommand -> outgoingCommandList);
           enet_list_remove (& outgoingCommand -> outgoingCommandList);
 
 
           if (outgoingCommand -> packet != NULL)
           if (outgoingCommand -> packet != NULL)
-            enet_list_insert (enet_list_end (& peer -> sentUnreliableCommands), outgoingCommand);
+            enet_list_insert (enet_list_end (sentUnreliableCommands), outgoingCommand);
        }
        }
 
 
        buffer -> data = command;
        buffer -> data = command;
@@ -1555,9 +1585,8 @@ enet_protocol_check_outgoing_commands (ENetHost * host, ENetPeer * peer)
     host -> bufferCount = buffer - host -> buffers;
     host -> bufferCount = buffer - host -> buffers;
 
 
     if (peer -> state == ENET_PEER_STATE_DISCONNECT_LATER &&
     if (peer -> state == ENET_PEER_STATE_DISCONNECT_LATER &&
-        enet_list_empty (& peer -> outgoingCommands) &&
-        enet_list_empty (& peer -> sentReliableCommands) &&
-        enet_list_empty (& peer -> sentUnreliableCommands))
+        ! enet_peer_has_outgoing_commands (peer) &&
+        enet_list_empty (sentUnreliableCommands))
       enet_peer_disconnect (peer, peer -> eventData);
       enet_peer_disconnect (peer, peer -> eventData);
 
 
     return canPing;
     return canPing;
@@ -1568,22 +1597,24 @@ enet_protocol_send_outgoing_commands (ENetHost * host, ENetEvent * event, int ch
 {
 {
     enet_uint8 headerData [sizeof (ENetProtocolHeader) + sizeof (enet_uint32)];
     enet_uint8 headerData [sizeof (ENetProtocolHeader) + sizeof (enet_uint32)];
     ENetProtocolHeader * header = (ENetProtocolHeader *) headerData;
     ENetProtocolHeader * header = (ENetProtocolHeader *) headerData;
-    ENetPeer * currentPeer;
-    int sentLength;
+    int sentLength = 0;
     size_t shouldCompress = 0;
     size_t shouldCompress = 0;
- 
-    host -> continueSending = 1;
+    ENetList sentUnreliableCommands;
 
 
-    while (host -> continueSending)
-    for (host -> continueSending = 0,
-           currentPeer = host -> peers;
+    enet_list_clear (& sentUnreliableCommands);
+
+    for (int sendPass = 0, continueSending = 0; sendPass <= continueSending; ++ sendPass)
+    for (ENetPeer * currentPeer = host -> peers;
          currentPeer < & host -> peers [host -> peerCount];
          currentPeer < & host -> peers [host -> peerCount];
          ++ currentPeer)
          ++ currentPeer)
     {
     {
         if (currentPeer -> state == ENET_PEER_STATE_DISCONNECTED ||
         if (currentPeer -> state == ENET_PEER_STATE_DISCONNECTED ||
-            currentPeer -> state == ENET_PEER_STATE_ZOMBIE)
+            currentPeer -> state == ENET_PEER_STATE_ZOMBIE ||
+            (sendPass > 0 && ! (currentPeer -> flags & ENET_PEER_FLAG_CONTINUE_SENDING)))
           continue;
           continue;
 
 
+        currentPeer -> flags &= ~ ENET_PEER_FLAG_CONTINUE_SENDING;
+
         host -> headerFlags = 0;
         host -> headerFlags = 0;
         host -> commandCount = 0;
         host -> commandCount = 0;
         host -> bufferCount = 1;
         host -> bufferCount = 1;
@@ -1600,21 +1631,22 @@ enet_protocol_send_outgoing_commands (ENetHost * host, ENetEvent * event, int ch
             if (event != NULL && event -> type != ENET_EVENT_TYPE_NONE)
             if (event != NULL && event -> type != ENET_EVENT_TYPE_NONE)
               return 1;
               return 1;
             else
             else
-              continue;
+              goto nextPeer;
         }
         }
 
 
-        if ((enet_list_empty (& currentPeer -> outgoingCommands) ||
-              enet_protocol_check_outgoing_commands (host, currentPeer)) &&
+        if (((enet_list_empty (& currentPeer -> outgoingCommands) &&
+              enet_list_empty (& currentPeer -> outgoingSendReliableCommands)) ||
+             enet_protocol_check_outgoing_commands (host, currentPeer, & sentUnreliableCommands)) &&
             enet_list_empty (& currentPeer -> sentReliableCommands) &&
             enet_list_empty (& currentPeer -> sentReliableCommands) &&
             ENET_TIME_DIFFERENCE (host -> serviceTime, currentPeer -> lastReceiveTime) >= currentPeer -> pingInterval &&
             ENET_TIME_DIFFERENCE (host -> serviceTime, currentPeer -> lastReceiveTime) >= currentPeer -> pingInterval &&
             currentPeer -> mtu - host -> packetSize >= sizeof (ENetProtocolPing))
             currentPeer -> mtu - host -> packetSize >= sizeof (ENetProtocolPing))
         { 
         { 
             enet_peer_ping (currentPeer);
             enet_peer_ping (currentPeer);
-            enet_protocol_check_outgoing_commands (host, currentPeer);
+            enet_protocol_check_outgoing_commands (host, currentPeer, & sentUnreliableCommands);
         }
         }
 
 
         if (host -> commandCount == 0)
         if (host -> commandCount == 0)
-          continue;
+          goto nextPeer;
 
 
         if (currentPeer -> packetLossEpoch == 0)
         if (currentPeer -> packetLossEpoch == 0)
           currentPeer -> packetLossEpoch = host -> serviceTime;
           currentPeer -> packetLossEpoch = host -> serviceTime;
@@ -1625,7 +1657,7 @@ enet_protocol_send_outgoing_commands (ENetHost * host, ENetEvent * event, int ch
            enet_uint32 packetLoss = currentPeer -> packetsLost * ENET_PEER_PACKET_LOSS_SCALE / currentPeer -> packetsSent;
            enet_uint32 packetLoss = currentPeer -> packetsLost * ENET_PEER_PACKET_LOSS_SCALE / currentPeer -> packetsSent;
 
 
 #ifdef ENET_DEBUG
 #ifdef ENET_DEBUG
-           printf ("peer %u: %f%%+-%f%% packet loss, %u+-%u ms round trip time, %f%% throttle, %u outgoing, %u/%u incoming\n", currentPeer -> incomingPeerID, currentPeer -> packetLoss / (float) ENET_PEER_PACKET_LOSS_SCALE, currentPeer -> packetLossVariance / (float) ENET_PEER_PACKET_LOSS_SCALE, currentPeer -> roundTripTime, currentPeer -> roundTripTimeVariance, currentPeer -> packetThrottle / (float) ENET_PEER_PACKET_THROTTLE_SCALE, enet_list_size (& currentPeer -> outgoingCommands), currentPeer -> channels != NULL ? enet_list_size (& currentPeer -> channels -> incomingReliableCommands) : 0, currentPeer -> channels != NULL ? enet_list_size (& currentPeer -> channels -> incomingUnreliableCommands) : 0);
+           printf ("peer %u: %f%%+-%f%% packet loss, %u+-%u ms round trip time, %f%% throttle, %u outgoing, %u/%u incoming\n", currentPeer -> incomingPeerID, currentPeer -> packetLoss / (float) ENET_PEER_PACKET_LOSS_SCALE, currentPeer -> packetLossVariance / (float) ENET_PEER_PACKET_LOSS_SCALE, currentPeer -> roundTripTime, currentPeer -> roundTripTimeVariance, currentPeer -> packetThrottle / (float) ENET_PEER_PACKET_THROTTLE_SCALE, enet_list_size (& currentPeer -> outgoingCommands) + enet_list_size (& currentPeer -> outgoingSendReliableCommands), currentPeer -> channels != NULL ? enet_list_size (& currentPeer -> channels -> incomingReliableCommands) : 0, currentPeer -> channels != NULL ? enet_list_size (& currentPeer -> channels -> incomingUnreliableCommands) : 0);
 #endif
 #endif
 
 
            currentPeer -> packetLossVariance = (currentPeer -> packetLossVariance * 3 + ENET_DIFFERENCE (packetLoss, currentPeer -> packetLoss)) / 4;
            currentPeer -> packetLossVariance = (currentPeer -> packetLossVariance * 3 + ENET_DIFFERENCE (packetLoss, currentPeer -> packetLoss)) / 4;
@@ -1687,13 +1719,17 @@ enet_protocol_send_outgoing_commands (ENetHost * host, ENetEvent * event, int ch
 
 
         sentLength = enet_socket_send (host -> socket, & currentPeer -> address, host -> buffers, host -> bufferCount);
         sentLength = enet_socket_send (host -> socket, & currentPeer -> address, host -> buffers, host -> bufferCount);
 
 
-        enet_protocol_remove_sent_unreliable_commands (currentPeer);
+        enet_protocol_remove_sent_unreliable_commands (currentPeer, & sentUnreliableCommands);
 
 
         if (sentLength < 0)
         if (sentLength < 0)
           return -1;
           return -1;
 
 
         host -> totalSentData += sentLength;
         host -> totalSentData += sentLength;
         host -> totalSentPackets ++;
         host -> totalSentPackets ++;
+
+    nextPeer:
+        if (currentPeer -> flags & ENET_PEER_FLAG_CONTINUE_SENDING)
+          continueSending = sendPass + 1;
     }
     }
    
    
     return 0;
     return 0;

Some files were not shown because too many files changed in this diff