diff options
37 files changed, 1463 insertions, 241 deletions
diff --git a/core/engine.cpp b/core/engine.cpp index 9607dedb3c..50822244cf 100644 --- a/core/engine.cpp +++ b/core/engine.cpp @@ -38,6 +38,7 @@  void Engine::set_iterations_per_second(int p_ips) { +	ERR_FAIL_COND(p_ips <= 0);  	ips = p_ips;  }  int Engine::get_iterations_per_second() const { diff --git a/core/math/a_star.cpp b/core/math/a_star.cpp index 3d71e66f80..0b6e9ae929 100644 --- a/core/math/a_star.cpp +++ b/core/math/a_star.cpp @@ -99,14 +99,22 @@ void AStar::remove_point(int p_id) {  	Point *p = points[p_id]; -	Map<int, Point *>::Element *PE = points.front(); -	while (PE) { -		for (Set<Point *>::Element *E = PE->get()->neighbours.front(); E; E = E->next()) { -			Segment s(p_id, E->get()->id); -			segments.erase(s); -			E->get()->neighbours.erase(p); -		} -		PE = PE->next(); +	for (Set<Point *>::Element *E = p->neighbours.front(); E; E = E->next()) { + +		Segment s(p_id, E->get()->id); +		segments.erase(s); + +		E->get()->neighbours.erase(p); +		E->get()->unlinked_neighbours.erase(p); +	} + +	for (Set<Point *>::Element *E = p->unlinked_neighbours.front(); E; E = E->next()) { + +		Segment s(p_id, E->get()->id); +		segments.erase(s); + +		E->get()->neighbours.erase(p); +		E->get()->unlinked_neighbours.erase(p);  	}  	memdelete(p); @@ -125,6 +133,8 @@ void AStar::connect_points(int p_id, int p_with_id, bool bidirectional) {  	if (bidirectional)  		b->neighbours.insert(a); +	else +		b->unlinked_neighbours.insert(a);  	Segment s(p_id, p_with_id);  	if (s.from == p_id) { @@ -147,7 +157,9 @@ void AStar::disconnect_points(int p_id, int p_with_id) {  	Point *a = points[p_id];  	Point *b = points[p_with_id];  	a->neighbours.erase(b); +	a->unlinked_neighbours.erase(b);  	b->neighbours.erase(a); +	b->unlinked_neighbours.erase(a);  }  bool AStar::has_point(int p_id) const { diff --git a/core/math/a_star.h b/core/math/a_star.h index fac8a9d312..ba35d929b3 100644 --- a/core/math/a_star.h +++ b/core/math/a_star.h @@ -54,6 +54,7 @@ class AStar : public Reference {  		bool enabled;  		Set<Point *> neighbours; +		Set<Point *> unlinked_neighbours;  		// Used for pathfinding  		Point *prev_point; diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml index eb612191e7..ba31a774fc 100644 --- a/doc/classes/@GlobalScope.xml +++ b/doc/classes/@GlobalScope.xml @@ -1013,7 +1013,7 @@  			Trigger on a VR controller  		</constant>  		<constant name="JOY_OCULUS_AX" value="7" enum="JoystickList"> -			A button on the right Oculus Touch controller, X button on the left controller (also when used in OpenVR)	 +			A button on the right Oculus Touch controller, X button on the left controller (also when used in OpenVR)  		</constant>  		<constant name="JOY_OCULUS_BY" value="1" enum="JoystickList">  			B button on the right Oculus Touch controller, Y button on the left controller (also when used in OpenVR) diff --git a/doc/classes/Bone2D.xml b/doc/classes/Bone2D.xml index 59f7bec889..757c6c6a34 100644 --- a/doc/classes/Bone2D.xml +++ b/doc/classes/Bone2D.xml @@ -1,8 +1,13 @@  <?xml version="1.0" encoding="UTF-8" ?>  <class name="Bone2D" inherits="Node2D" category="Core" version="3.2">  	<brief_description> +		Joint used with [Skeleton2D] to control and animate other nodes.  	</brief_description>  	<description> +		Use a hierarchy of [code]Bone2D[/code] bound to a [Skeleton2D] to control, and animate other [Node2D] nodes. +		You can use [code]Bone2D[/code] and [code]Skeleton2D[/code] nodes to animate 2D meshes created with the Polygon 2D UV editor. +		Each bone has a [member rest] transform that you can reset to with [method apply_rest]. These rest poses are relative to the bone's parent. +		If in the editor, you can set the rest pose of an entire skeleton using a menu option, from the code, you need to iterate over the bones to set their individual rest poses.  	</description>  	<tutorials>  	</tutorials> @@ -11,25 +16,30 @@  			<return type="void">  			</return>  			<description> +				Stores the node's current transforms in [member rest].  			</description>  		</method>  		<method name="get_index_in_skeleton" qualifiers="const">  			<return type="int">  			</return>  			<description> +				Returns the node's index as part of the entire skeleton. See [Skeleton2D].  			</description>  		</method>  		<method name="get_skeleton_rest" qualifiers="const">  			<return type="Transform2D">  			</return>  			<description> +				Returns the node's [member rest] [code]Transform2D[/code] if it doesn't have a parent, or its rest pose relative to its parent.  			</description>  		</method>  	</methods>  	<members>  		<member name="default_length" type="float" setter="set_default_length" getter="get_default_length"> +			Length of the bone's representation drawn in the editor's viewport in pixels.  		</member>  		<member name="rest" type="Transform2D" setter="set_rest" getter="get_rest"> +			Rest transform of the bone. You can reset the node's transforms to this value using [method apply_rest].  		</member>  	</members>  	<constants> diff --git a/doc/classes/CPUParticles.xml b/doc/classes/CPUParticles.xml index 5458a87a9e..c9c92102f3 100644 --- a/doc/classes/CPUParticles.xml +++ b/doc/classes/CPUParticles.xml @@ -1,8 +1,11 @@  <?xml version="1.0" encoding="UTF-8" ?>  <class name="CPUParticles" inherits="GeometryInstance" category="Core" version="3.2">  	<brief_description> +		CPU-based 3D particle emitter.  	</brief_description>  	<description> +		CPU-based 3D particle node used to create a variety of particle systems and effects. +		See also [Particles], which provides the same functionality with hardware acceleration, but may not run on older devices.  	</description>  	<tutorials>  	</tutorials> @@ -13,56 +16,77 @@  			<argument index="0" name="particles" type="Node">  			</argument>  			<description> +				Sets this node's properties to match a given [Particles] node with an assigned [ParticlesMaterial].  			</description>  		</method>  		<method name="restart">  			<return type="void">  			</return>  			<description> +				Restarts the particle emitter.  			</description>  		</method>  	</methods>  	<members>  		<member name="amount" type="int" setter="set_amount" getter="get_amount"> +			Number of particles emitted in one emission cycle.  		</member>  		<member name="angle" type="float" setter="set_param" getter="get_param"> +			Initial rotation applied to each particle, in degrees.  		</member>  		<member name="angle_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> +			Each particle's rotation will be animated along this [Curve].  		</member>  		<member name="angle_random" type="float" setter="set_param_randomness" getter="get_param_randomness"> +			Rotation randomness ratio. Default value: [code]0[/code].  		</member>  		<member name="angular_velocity" type="float" setter="set_param" getter="get_param"> +			Initial angular velocity applied to each particle. Sets the speed of rotation of the particle.  		</member>  		<member name="angular_velocity_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> +			Each particle's angular velocity will vary along this [Curve].  		</member>  		<member name="angular_velocity_random" type="float" setter="set_param_randomness" getter="get_param_randomness"> +			Angular velocity randomness ratio. Default value: [code]0[/code].  		</member>  		<member name="anim_offset" type="float" setter="set_param" getter="get_param"> +			Particle animation offset.  		</member>  		<member name="anim_offset_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> +			Each particle's animation offset will vary along this [Curve].  		</member>  		<member name="anim_offset_random" type="float" setter="set_param_randomness" getter="get_param_randomness"> +			Animation offset randomness ratio. Default value: [code]0[/code].  		</member>  		<member name="anim_speed" type="float" setter="set_param" getter="get_param"> +			Particle animation speed.  		</member>  		<member name="anim_speed_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> +			Each particle's animation speed will vary along this [Curve].  		</member>  		<member name="anim_speed_random" type="float" setter="set_param_randomness" getter="get_param_randomness"> +			Animation speed randomness ratio. Default value: [code]0[/code].  		</member>  		<member name="color" type="Color" setter="set_color" getter="get_color"> +			Unused for 3D particles.  		</member>  		<member name="color_ramp" type="Gradient" setter="set_color_ramp" getter="get_color_ramp"> -			Each particle's vertex color will vary along this [GradientTexture]. +			Unused for 3D particles.  		</member>  		<member name="damping" type="float" setter="set_param" getter="get_param"> +			The rate at which particles lose velocity.  		</member>  		<member name="damping_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> +			Damping will vary along this [Curve].  		</member>  		<member name="damping_random" type="float" setter="set_param_randomness" getter="get_param_randomness"> +			Damping randomness ratio. Default value: [code]0[/code].  		</member>  		<member name="draw_order" type="int" setter="set_draw_order" getter="get_draw_order" enum="CPUParticles.DrawOrder"> +			Particle draw order. Uses [enum DrawOrder] values. Default value: [constant DRAW_ORDER_INDEX].  		</member>  		<member name="emission_box_extents" type="Vector3" setter="set_emission_box_extents" getter="get_emission_box_extents"> +			The rectangle's extents if [member emission_shape] is set to [constant EMISSION_SHAPE_BOX].  		</member>  		<member name="emission_colors" type="PoolColorArray" setter="set_emission_colors" getter="get_emission_colors">  		</member> @@ -71,134 +95,199 @@  		<member name="emission_points" type="PoolVector3Array" setter="set_emission_points" getter="get_emission_points">  		</member>  		<member name="emission_shape" type="int" setter="set_emission_shape" getter="get_emission_shape" enum="CPUParticles.EmissionShape"> +			Particles will be emitted inside this region. Use [enum EmissionShape] for values. Default value: [constant EMISSION_SHAPE_POINT].  		</member>  		<member name="emission_sphere_radius" type="float" setter="set_emission_sphere_radius" getter="get_emission_sphere_radius"> +			The sphere's radius if [enum EmissionShape] is set to [constant EMISSION_SHAPE_SPHERE].  		</member>  		<member name="emitting" type="bool" setter="set_emitting" getter="is_emitting"> +			If [code]true[/code], particles are being emitted. Default value: [code]true[/code].  		</member>  		<member name="explosiveness" type="float" setter="set_explosiveness_ratio" getter="get_explosiveness_ratio"> +			How rapidly particles in an emission cycle are emitted. If greater than [code]0[/code], there will be a gap in emissions before the next cycle begins. Default value: [code]0[/code].  		</member>  		<member name="fixed_fps" type="int" setter="set_fixed_fps" getter="get_fixed_fps"> +			The particle system's frame rate is fixed to a value. For instance, changing the value to 2 will make the particles render at 2 frames per second. Note this does not slow down the particle system itself.  		</member>  		<member name="flag_align_y" type="bool" setter="set_particle_flag" getter="get_particle_flag"> +			Align y-axis of particle with the direction of its velocity.  		</member>  		<member name="flag_disable_z" type="bool" setter="set_particle_flag" getter="get_particle_flag"> +			If [code]true[/code], particles will not move on the z axis. Default value: [code]false[/code].  		</member>  		<member name="flag_rotate_y" type="bool" setter="set_particle_flag" getter="get_particle_flag"> +			If [code]true[/code], particles rotate around y-axis by [member angle].  		</member>  		<member name="flatness" type="float" setter="set_flatness" getter="get_flatness"> +			Amount of [member spread] in Y/Z plane. A value of [code]1[/code] restricts particles to X/Z plane. Default [code]0[/code].  		</member>  		<member name="fract_delta" type="bool" setter="set_fractional_delta" getter="get_fractional_delta"> +			If [code]true[/code], results in fractional delta calculation which has a smoother particles display effect. Default value: [code]true[/code]  		</member>  		<member name="gravity" type="Vector3" setter="set_gravity" getter="get_gravity"> +			Gravity applied to every particle. Default value: [code](0, -9.8, 0)[/code].  		</member>  		<member name="hue_variation" type="float" setter="set_param" getter="get_param"> +			Initial hue variation applied to each particle.  		</member>  		<member name="hue_variation_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> +			Each particle's hue will vary along this [Curve].  		</member>  		<member name="hue_variation_random" type="float" setter="set_param_randomness" getter="get_param_randomness"> +			Hue variation randomness ratio. Default value: [code]0[/code].  		</member>  		<member name="initial_velocity" type="float" setter="set_param" getter="get_param"> +			Initial velocity magnitude for each particle. Direction comes from [member spread] and the node's orientation.  		</member>  		<member name="initial_velocity_random" type="float" setter="set_param_randomness" getter="get_param_randomness"> +			Initial velocity randomness ratio. Default value: [code]0[/code].  		</member>  		<member name="lifetime" type="float" setter="set_lifetime" getter="get_lifetime"> +			Amount of time each particle will exist. Default value: [code]1[/code].  		</member>  		<member name="linear_accel" type="float" setter="set_param" getter="get_param"> +			Linear acceleration applied to each particle in the direction of motion.  		</member>  		<member name="linear_accel_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> +			Each particle's linear acceleration will vary along this [Curve].  		</member>  		<member name="linear_accel_random" type="float" setter="set_param_randomness" getter="get_param_randomness"> +			Linear acceleration randomness ratio. Default value: [code]0[/code].  		</member>  		<member name="local_coords" type="bool" setter="set_use_local_coordinates" getter="get_use_local_coordinates"> +			If [code]true[/code], particles use the parent node's coordinate space. If [code]false[/code], they use global coordinates. Default value: [code]true[/code].  		</member>  		<member name="mesh" type="Mesh" setter="set_mesh" getter="get_mesh"> +			The [Mesh] used for each particle. If [code]null[/code], particles will be spheres.  		</member>  		<member name="one_shot" type="bool" setter="set_one_shot" getter="get_one_shot"> +			If [code]true[/code], only one emission cycle occurs. If set [code]true[/code] during a cycle, emission will stop at the cycle's end. Default value: [code]false[/code].  		</member>  		<member name="orbit_velocity" type="float" setter="set_param" getter="get_param"> +			Orbital velocity applied to each particle. Makes the particles circle around origin in the local XY plane. Specified in number of full rotations around origin per second. +			This property is only available when [member flag_disable_z] is [code]true[/code].  		</member>  		<member name="orbit_velocity_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> +			Each particle's orbital velocity will vary along this [Curve].  		</member>  		<member name="orbit_velocity_random" type="float" setter="set_param_randomness" getter="get_param_randomness"> +			Orbital velocity randomness ratio. Default value: [code]0[/code].  		</member>  		<member name="preprocess" type="float" setter="set_pre_process_time" getter="get_pre_process_time"> +			Particle system starts as if it had already run for this many seconds.  		</member>  		<member name="radial_accel" type="float" setter="set_param" getter="get_param"> +			Radial acceleration applied to each particle. Makes particle accelerate away from origin.  		</member>  		<member name="radial_accel_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> +			Each particle's radial acceleration will vary along this [Curve].  		</member>  		<member name="radial_accel_random" type="float" setter="set_param_randomness" getter="get_param_randomness"> +			Radial acceleration randomness ratio. Default value: [code]0[/code].  		</member>  		<member name="randomness" type="float" setter="set_randomness_ratio" getter="get_randomness_ratio"> +			Emission lifetime randomness ratio. Default value: [code]0[/code].  		</member>  		<member name="scale_amount" type="float" setter="set_param" getter="get_param"> +			Initial scale applied to each particle.  		</member>  		<member name="scale_amount_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> +			Each particle's scale will vary along this [Curve].  		</member>  		<member name="scale_amount_random" type="float" setter="set_param_randomness" getter="get_param_randomness"> +			Scale randomness ratio. Default value: [code]0[/code].  		</member>  		<member name="speed_scale" type="float" setter="set_speed_scale" getter="get_speed_scale"> +			Particle system's running speed scaling ratio. Default value: [code]1[/code]. A value of [code]0[/code] can be used to pause the particles.  		</member>  		<member name="spread" type="float" setter="set_spread" getter="get_spread"> +			Each particle's initial direction range from [code]+spread[/code] to [code]-spread[/code] degrees. Default value: [code]45[/code].  		</member>  		<member name="tangential_accel" type="float" setter="set_param" getter="get_param"> +			Tangential acceleration applied to each particle. Tangential acceleration is perpendicular to the particle's velocity giving the particles a swirling motion.  		</member>  		<member name="tangential_accel_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> +			Each particle's tangential acceleration will vary along this [Curve].  		</member>  		<member name="tangential_accel_random" type="float" setter="set_param_randomness" getter="get_param_randomness"> +			Tangential acceleration randomness ratio. Default value: [code]0[/code].  		</member>  	</members>  	<constants>  		<constant name="DRAW_ORDER_INDEX" value="0" enum="DrawOrder"> +			Particles are drawn in the order emitted.  		</constant>  		<constant name="DRAW_ORDER_LIFETIME" value="1" enum="DrawOrder"> +			Particles are drawn in order of remaining lifetime.  		</constant>  		<constant name="DRAW_ORDER_VIEW_DEPTH" value="2" enum="DrawOrder"> +			Particles are drawn in order of depth.  		</constant>  		<constant name="PARAM_INITIAL_LINEAR_VELOCITY" value="0" enum="Parameter"> +			Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set initial velocity properties.  		</constant>  		<constant name="PARAM_ANGULAR_VELOCITY" value="1" enum="Parameter"> +			Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set angular velocity properties.  		</constant>  		<constant name="PARAM_ORBIT_VELOCITY" value="2" enum="Parameter"> +			Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set orbital velocity properties.  		</constant>  		<constant name="PARAM_LINEAR_ACCEL" value="3" enum="Parameter"> +			Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set linear acceleration properties.  		</constant>  		<constant name="PARAM_RADIAL_ACCEL" value="4" enum="Parameter"> +			Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set radial acceleration properties.  		</constant>  		<constant name="PARAM_TANGENTIAL_ACCEL" value="5" enum="Parameter"> +			Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set tangential acceleration properties.  		</constant>  		<constant name="PARAM_DAMPING" value="6" enum="Parameter"> +			Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set damping properties.  		</constant>  		<constant name="PARAM_ANGLE" value="7" enum="Parameter"> +			Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set angle properties.  		</constant>  		<constant name="PARAM_SCALE" value="8" enum="Parameter"> +			Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set scale properties.  		</constant>  		<constant name="PARAM_HUE_VARIATION" value="9" enum="Parameter"> +			Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set hue variation properties.  		</constant>  		<constant name="PARAM_ANIM_SPEED" value="10" enum="Parameter"> +			Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set animation speed properties.  		</constant>  		<constant name="PARAM_ANIM_OFFSET" value="11" enum="Parameter"> +			Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set animation offset properties.  		</constant>  		<constant name="PARAM_MAX" value="12" enum="Parameter"> +			Represents the size of the [enum Parameter] enum.  		</constant>  		<constant name="FLAG_ALIGN_Y_TO_VELOCITY" value="0" enum="Flags"> +			Use with [method set_flag] to set [member flag_align_y].  		</constant>  		<constant name="FLAG_ROTATE_Y" value="1" enum="Flags"> +			Use with [method set_flag] to set [member flag_rotate_y].  		</constant>  		<constant name="FLAG_DISABLE_Z" value="2" enum="Flags"> +			Use with [method set_flag] to set [member flag_disable_z].  		</constant>  		<constant name="FLAG_MAX" value="3" enum="Flags"> +			Represents the size of the [enum Flags] enum.  		</constant>  		<constant name="EMISSION_SHAPE_POINT" value="0" enum="EmissionShape"> +			All particles will be emitted from a single point.  		</constant>  		<constant name="EMISSION_SHAPE_SPHERE" value="1" enum="EmissionShape"> +			Particles will be emitted in the volume of a sphere.  		</constant>  		<constant name="EMISSION_SHAPE_BOX" value="2" enum="EmissionShape"> +			Particles will be emitted in the volume of a box.  		</constant>  		<constant name="EMISSION_SHAPE_POINTS" value="3" enum="EmissionShape"> +			Particles will be emitted at a position chosen randomly among [member emission_points]. Particle color will be modulated by [member emission_colors].  		</constant>  		<constant name="EMISSION_SHAPE_DIRECTED_POINTS" value="4" enum="EmissionShape"> +			Particles will be emitted at a position chosen randomly among [member emission_points]. Particle velocity and rotation will be set based on [member emission_normals]. Particle color will be modulated by [member emission_colors].  		</constant>  	</constants>  </class> diff --git a/doc/classes/CPUParticles2D.xml b/doc/classes/CPUParticles2D.xml index 17c68ccb87..4351a0b4d4 100644 --- a/doc/classes/CPUParticles2D.xml +++ b/doc/classes/CPUParticles2D.xml @@ -1,10 +1,14 @@  <?xml version="1.0" encoding="UTF-8" ?>  <class name="CPUParticles2D" inherits="Node2D" category="Core" version="3.2">  	<brief_description> +		CPU-based 2D particle emitter.  	</brief_description>  	<description> +		CPU-based 2D particle node used to create a variety of particle systems and effects. +		See also [Particles2D], which provides the same functionality with hardware acceleration, but may not run on older devices.  	</description>  	<tutorials> +		<link>https://docs.godotengine.org/en/latest/tutorials/2d/particle_systems_2d.html</link>  	</tutorials>  	<methods>  		<method name="convert_from_particles"> @@ -13,53 +17,74 @@  			<argument index="0" name="particles" type="Node">  			</argument>  			<description> +				Sets this node's properties to match a given [Particles2D] node with an assigned [ParticlesMaterial].  			</description>  		</method>  		<method name="restart">  			<return type="void">  			</return>  			<description> +				Restarts the particle emitter.  			</description>  		</method>  	</methods>  	<members>  		<member name="amount" type="int" setter="set_amount" getter="get_amount"> +			Number of particles emitted in one emission cycle.  		</member>  		<member name="angle" type="float" setter="set_param" getter="get_param"> +			Initial rotation applied to each particle, in degrees.  		</member>  		<member name="angle_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> +			Each particle's rotation will be animated along this [Curve].  		</member>  		<member name="angle_random" type="float" setter="set_param_randomness" getter="get_param_randomness"> +			Rotation randomness ratio. Default value: [code]0[/code].  		</member>  		<member name="angular_velocity" type="float" setter="set_param" getter="get_param"> +			Initial angular velocity applied to each particle. Sets the speed of rotation of the particle.  		</member>  		<member name="angular_velocity_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> +			Each particle's angular velocity will vary along this [Curve].  		</member>  		<member name="angular_velocity_random" type="float" setter="set_param_randomness" getter="get_param_randomness"> +			Angular velocity randomness ratio. Default value: [code]0[/code].  		</member>  		<member name="anim_offset" type="float" setter="set_param" getter="get_param"> +			Particle animation offset.  		</member>  		<member name="anim_offset_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> +			Each particle's animation offset will vary along this [Curve].  		</member>  		<member name="anim_offset_random" type="float" setter="set_param_randomness" getter="get_param_randomness"> +			Animation offset randomness ratio. Default value: [code]0[/code].  		</member>  		<member name="anim_speed" type="float" setter="set_param" getter="get_param"> +			Particle animation speed.  		</member>  		<member name="anim_speed_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> +			Each particle's animation speed will vary along this [Curve].  		</member>  		<member name="anim_speed_random" type="float" setter="set_param_randomness" getter="get_param_randomness"> +			Animation speed randomness ratio. Default value: [code]0[/code].  		</member>  		<member name="color" type="Color" setter="set_color" getter="get_color"> +			Each particle's initial color. If [member texture] is defined, it will be multiplied by this color.  		</member>  		<member name="color_ramp" type="Gradient" setter="set_color_ramp" getter="get_color_ramp"> +			Each particle's color will vary along this [Gradient].  		</member>  		<member name="damping" type="float" setter="set_param" getter="get_param"> +			The rate at which particles lose velocity.  		</member>  		<member name="damping_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> +			Damping will vary along this [Curve].  		</member>  		<member name="damping_random" type="float" setter="set_param_randomness" getter="get_param_randomness"> +			Damping randomness ratio. Default value: [code]0[/code].  		</member>  		<member name="draw_order" type="int" setter="set_draw_order" getter="get_draw_order" enum="CPUParticles2D.DrawOrder"> +			Particle draw order. Uses [enum DrawOrder] values. Default value: [constant DRAW_ORDER_INDEX].  		</member>  		<member name="emission_colors" type="PoolColorArray" setter="set_emission_colors" getter="get_emission_colors">  		</member> @@ -68,132 +93,194 @@  		<member name="emission_points" type="PoolVector2Array" setter="set_emission_points" getter="get_emission_points">  		</member>  		<member name="emission_rect_extents" type="Vector2" setter="set_emission_rect_extents" getter="get_emission_rect_extents"> +			The rectangle's extents if [member emission_shape] is set to [constant EMISSION_SHAPE_RECTANGLE].  		</member>  		<member name="emission_shape" type="int" setter="set_emission_shape" getter="get_emission_shape" enum="CPUParticles2D.EmissionShape"> +			Particles will be emitted inside this region. Use [enum EmissionShape] for values. Default value: [constant EMISSION_SHAPE_POINT].  		</member>  		<member name="emission_sphere_radius" type="float" setter="set_emission_sphere_radius" getter="get_emission_sphere_radius"> +			The circle's radius if [member emission_shape] is set to [constant EMISSION_SHAPE_CIRCLE].  		</member>  		<member name="emitting" type="bool" setter="set_emitting" getter="is_emitting"> +			If [code]true[/code], particles are being emitted. Default value: [code]true[/code].  		</member>  		<member name="explosiveness" type="float" setter="set_explosiveness_ratio" getter="get_explosiveness_ratio"> +			How rapidly particles in an emission cycle are emitted. If greater than [code]0[/code], there will be a gap in emissions before the next cycle begins. Default value: [code]0[/code].  		</member>  		<member name="fixed_fps" type="int" setter="set_fixed_fps" getter="get_fixed_fps"> +			The particle system's frame rate is fixed to a value. For instance, changing the value to 2 will make the particles render at 2 frames per second. Note this does not slow down the simulation of the particle system itself.  		</member>  		<member name="flag_align_y" type="bool" setter="set_particle_flag" getter="get_particle_flag"> +			Align y-axis of particle with the direction of its velocity.  		</member>  		<member name="flatness" type="float" setter="set_flatness" getter="get_flatness">  		</member>  		<member name="fract_delta" type="bool" setter="set_fractional_delta" getter="get_fractional_delta"> +			If [code]true[/code], results in fractional delta calculation which has a smoother particles display effect. Default value: [code]true[/code]  		</member>  		<member name="gravity" type="Vector2" setter="set_gravity" getter="get_gravity"> +			Gravity applied to every particle. Default value: [code](0, 98)[/code].  		</member>  		<member name="hue_variation" type="float" setter="set_param" getter="get_param"> +			Initial hue variation applied to each particle.  		</member>  		<member name="hue_variation_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> +			Each particle's hue will vary along this [Curve].  		</member>  		<member name="hue_variation_random" type="float" setter="set_param_randomness" getter="get_param_randomness"> +			Hue variation randomness ratio. Default value: [code]0[/code].  		</member>  		<member name="initial_velocity" type="float" setter="set_param" getter="get_param"> +			Initial velocity magnitude for each particle. Direction comes from [member spread] and the node's orientation.  		</member>  		<member name="initial_velocity_random" type="float" setter="set_param_randomness" getter="get_param_randomness"> +			Initial velocity randomness ratio. Default value: [code]0[/code].  		</member>  		<member name="lifetime" type="float" setter="set_lifetime" getter="get_lifetime"> +			Amount of time each particle will exist. Default value: [code]1[/code].  		</member>  		<member name="linear_accel" type="float" setter="set_param" getter="get_param"> +			Linear acceleration applied to each particle in the direction of motion.  		</member>  		<member name="linear_accel_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> +			Each particle's linear acceleration will vary along this [Curve].  		</member>  		<member name="linear_accel_random" type="float" setter="set_param_randomness" getter="get_param_randomness"> +			Linear acceleration randomness ratio. Default value: [code]0[/code].  		</member>  		<member name="local_coords" type="bool" setter="set_use_local_coordinates" getter="get_use_local_coordinates"> +			If [code]true[/code], particles use the parent node's coordinate space. If [code]false[/code], they use global coordinates. Default value: [code]true[/code].  		</member>  		<member name="normalmap" type="Texture" setter="set_normalmap" getter="get_normalmap"> +			Normal map to be used for the [member texture] property.  		</member>  		<member name="one_shot" type="bool" setter="set_one_shot" getter="get_one_shot"> +			If [code]true[/code], only one emission cycle occurs. If set [code]true[/code] during a cycle, emission will stop at the cycle's end. Default value: [code]false[/code].  		</member>  		<member name="orbit_velocity" type="float" setter="set_param" getter="get_param"> +			Orbital velocity applied to each particle. Makes the particles circle around origin. Specified in number of full rotations around origin per second.  		</member>  		<member name="orbit_velocity_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> +			Each particle's orbital velocity will vary along this [Curve].  		</member>  		<member name="orbit_velocity_random" type="float" setter="set_param_randomness" getter="get_param_randomness"> +			Orbital velocity randomness ratio. Default value: [code]0[/code].  		</member>  		<member name="preprocess" type="float" setter="set_pre_process_time" getter="get_pre_process_time"> +			Particle system starts as if it had already run for this many seconds.  		</member>  		<member name="radial_accel" type="float" setter="set_param" getter="get_param"> +			Radial acceleration applied to each particle. Makes particle accelerate away from origin.  		</member>  		<member name="radial_accel_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> +			Each particle's radial acceleration will vary along this [Curve].  		</member>  		<member name="radial_accel_random" type="float" setter="set_param_randomness" getter="get_param_randomness"> +			Radial acceleration randomness ratio. Default value: [code]0[/code].  		</member>  		<member name="randomness" type="float" setter="set_randomness_ratio" getter="get_randomness_ratio"> +			Emission lifetime randomness ratio. Default value: [code]0[/code].  		</member>  		<member name="scale_amount" type="float" setter="set_param" getter="get_param"> +			Initial scale applied to each particle.  		</member>  		<member name="scale_amount_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> +			Each particle's scale will vary along this [Curve].  		</member>  		<member name="scale_amount_random" type="float" setter="set_param_randomness" getter="get_param_randomness"> +			Scale randomness ratio. Default value: [code]0[/code].  		</member>  		<member name="speed_scale" type="float" setter="set_speed_scale" getter="get_speed_scale"> +			Particle system's running speed scaling ratio. Default value: [code]1[/code]. A value of [code]0[/code] can be used to pause the particles.  		</member>  		<member name="spread" type="float" setter="set_spread" getter="get_spread"> +			Each particle's initial direction range from [code]+spread[/code] to [code]-spread[/code] degrees. Default value: [code]45[/code].  		</member>  		<member name="tangential_accel" type="float" setter="set_param" getter="get_param"> +			Tangential acceleration applied to each particle. Tangential acceleration is perpendicular to the particle's velocity giving the particles a swirling motion.  		</member>  		<member name="tangential_accel_curve" type="Curve" setter="set_param_curve" getter="get_param_curve"> +			Each particle's tangential acceleration will vary along this [Curve].  		</member>  		<member name="tangential_accel_random" type="float" setter="set_param_randomness" getter="get_param_randomness"> +			Tangential acceleration randomness ratio. Default value: [code]0[/code].  		</member>  		<member name="texture" type="Texture" setter="set_texture" getter="get_texture"> +			Particle texture. If [code]null[/code] particles will be squares.  		</member>  	</members>  	<constants>  		<constant name="DRAW_ORDER_INDEX" value="0" enum="DrawOrder"> +			Particles are drawn in the order emitted.  		</constant>  		<constant name="DRAW_ORDER_LIFETIME" value="1" enum="DrawOrder"> +			Particles are drawn in order of remaining lifetime.  		</constant>  		<constant name="PARAM_INITIAL_LINEAR_VELOCITY" value="0" enum="Parameter"> +			Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set initial velocity properties.  		</constant>  		<constant name="PARAM_ANGULAR_VELOCITY" value="1" enum="Parameter"> +			Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set angular velocity properties.  		</constant>  		<constant name="PARAM_ORBIT_VELOCITY" value="2" enum="Parameter"> +			Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set orbital velocity properties.  		</constant>  		<constant name="PARAM_LINEAR_ACCEL" value="3" enum="Parameter"> +			Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set linear acceleration properties.  		</constant>  		<constant name="PARAM_RADIAL_ACCEL" value="4" enum="Parameter"> +			Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set radial acceleration properties.  		</constant>  		<constant name="PARAM_TANGENTIAL_ACCEL" value="5" enum="Parameter"> +			Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set tangential acceleration properties.  		</constant>  		<constant name="PARAM_DAMPING" value="6" enum="Parameter"> +			Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set damping properties.  		</constant>  		<constant name="PARAM_ANGLE" value="7" enum="Parameter"> +			Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set angle properties.  		</constant>  		<constant name="PARAM_SCALE" value="8" enum="Parameter"> +			Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set scale properties.  		</constant>  		<constant name="PARAM_HUE_VARIATION" value="9" enum="Parameter"> +			Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set hue variation properties.  		</constant>  		<constant name="PARAM_ANIM_SPEED" value="10" enum="Parameter"> +			Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set animation speed properties.  		</constant>  		<constant name="PARAM_ANIM_OFFSET" value="11" enum="Parameter"> +			Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set animation offset properties.  		</constant>  		<constant name="PARAM_MAX" value="12" enum="Parameter"> +			Represents the size of the [enum Parameter] enum.  		</constant>  		<constant name="FLAG_ALIGN_Y_TO_VELOCITY" value="0" enum="Flags"> +			Use with [method set_flag] to set [member flag_align_y].  		</constant>  		<constant name="FLAG_ROTATE_Y" value="1" enum="Flags"> +			Present for consistency with 3D particle nodes, not used in 2D.  		</constant>  		<constant name="FLAG_DISABLE_Z" value="2" enum="Flags"> +			Present for consistency with 3D particle nodes, not used in 2D.  		</constant>  		<constant name="FLAG_MAX" value="3" enum="Flags"> +			Represents the size of the [enum Flags] enum.  		</constant>  		<constant name="EMISSION_SHAPE_POINT" value="0" enum="EmissionShape"> +			All particles will be emitted from a single point.  		</constant>  		<constant name="EMISSION_SHAPE_CIRCLE" value="1" enum="EmissionShape"> +			Particles will be emitted on the perimeter of a circle.  		</constant>  		<constant name="EMISSION_SHAPE_RECTANGLE" value="2" enum="EmissionShape"> +			Particles will be emitted in the area of a rectangle.  		</constant>  		<constant name="EMISSION_SHAPE_POINTS" value="3" enum="EmissionShape"> +			Particles will be emitted at a position chosen randomly among [member emission_points]. Particle color will be modulated by [member emission_colors].  		</constant>  		<constant name="EMISSION_SHAPE_DIRECTED_POINTS" value="4" enum="EmissionShape"> +			Particles will be emitted at a position chosen randomly among [member emission_points]. Particle velocity and rotation will be set based on [member emission_normals]. Particle color will be modulated by [member emission_colors].  		</constant>  	</constants>  </class> diff --git a/doc/classes/ClippedCamera.xml b/doc/classes/ClippedCamera.xml index b7f158dd65..c6dcd6cd96 100644 --- a/doc/classes/ClippedCamera.xml +++ b/doc/classes/ClippedCamera.xml @@ -29,6 +29,12 @@  			<description>  			</description>  		</method> +		<method name="get_clip_offset" qualifiers="const"> +			<return type="float"> +			</return> +			<description> +			</description> +		</method>  		<method name="get_collision_mask_bit" qualifiers="const">  			<return type="bool">  			</return> diff --git a/doc/classes/FileDialog.xml b/doc/classes/FileDialog.xml index 953f364bdb..908c017ac9 100644 --- a/doc/classes/FileDialog.xml +++ b/doc/classes/FileDialog.xml @@ -138,5 +138,7 @@  		</theme_item>  		<theme_item name="reload" type="Texture">  		</theme_item> +		<theme_item name="toggle_hidden" type="Texture"> +		</theme_item>  	</theme_items>  </class> diff --git a/doc/classes/MeshInstance2D.xml b/doc/classes/MeshInstance2D.xml index 39a733fdb3..4b38b9aa96 100644 --- a/doc/classes/MeshInstance2D.xml +++ b/doc/classes/MeshInstance2D.xml @@ -22,6 +22,12 @@  			The [Texture] that will be used if using the default [CanvasItemMaterial]. Can be accessed as [code]TEXTURE[/code] in CanvasItem shader.  		</member>  	</members> +	<signals> +		<signal name="texture_changed"> +			<description> +			</description> +		</signal> +	</signals>  	<constants>  	</constants>  </class> diff --git a/doc/classes/MultiMeshInstance2D.xml b/doc/classes/MultiMeshInstance2D.xml index 0bf243ee24..8509986c3c 100644 --- a/doc/classes/MultiMeshInstance2D.xml +++ b/doc/classes/MultiMeshInstance2D.xml @@ -22,6 +22,12 @@  			The [Texture] that will be used if using the default [CanvasItemMaterial]. Can be accessed as [code]TEXTURE[/code] in CanvasItem shader.  		</member>  	</members> +	<signals> +		<signal name="texture_changed"> +			<description> +			</description> +		</signal> +	</signals>  	<constants>  	</constants>  </class> diff --git a/doc/classes/OS.xml b/doc/classes/OS.xml index 2592bc6775..e994c24582 100644 --- a/doc/classes/OS.xml +++ b/doc/classes/OS.xml @@ -508,7 +508,8 @@  			<argument index="0" name="tag_name" type="String">  			</argument>  			<description> -				Returns [code]true[/code] if the feature for the given feature tag is supported in the currently running instance, depending on platform, build etc. Can be used to check whether you're currently running a debug build, on a certain platform or arch, etc. See feature tags documentation. +				Returns [code]true[/code] if the feature for the given feature tag is supported in the currently running instance, depending on platform, build etc. Can be used to check whether you're currently running a debug build, on a certain platform or arch, etc. Refer to the [url=https://docs.godotengine.org/en/latest/getting_started/workflow/export/feature_tags.html]Feature Tags[/url] documentation for more details. +				Note that tag names are case-sensitive.  			</description>  		</method>  		<method name="has_touchscreen_ui_hint" qualifiers="const"> diff --git a/doc/classes/Particles.xml b/doc/classes/Particles.xml index 7820c63ad7..a29b621406 100644 --- a/doc/classes/Particles.xml +++ b/doc/classes/Particles.xml @@ -55,8 +55,10 @@  			Time ratio between each emission. If [code]0[/code] particles are emitted continuously. If [code]1[/code] all particles are emitted simultaneously. Default value: [code]0[/code].  		</member>  		<member name="fixed_fps" type="int" setter="set_fixed_fps" getter="get_fixed_fps"> +			The particle system's frame rate is fixed to a value. For instance, changing the value to 2 will make the particles render at 2 frames per second. Note this does not slow down the simulation of the particle system itself.  		</member>  		<member name="fract_delta" type="bool" setter="set_fractional_delta" getter="get_fractional_delta"> +			If [code]true[/code], results in fractional delta calculation which has a smoother particles display effect. Default value: [code]true[/code].  		</member>  		<member name="lifetime" type="float" setter="set_lifetime" getter="get_lifetime">  			Amount of time each particle will exist. Default value: [code]1[/code]. diff --git a/doc/classes/Particles2D.xml b/doc/classes/Particles2D.xml index de4877b639..78114e985d 100644 --- a/doc/classes/Particles2D.xml +++ b/doc/classes/Particles2D.xml @@ -40,7 +40,7 @@  			How rapidly particles in an emission cycle are emitted. If greater than [code]0[/code], there will be a gap in emissions before the next cycle begins. Default value: [code]0[/code].  		</member>  		<member name="fixed_fps" type="int" setter="set_fixed_fps" getter="get_fixed_fps"> -			The particle system's frame rate is fixed to a value. For instance, changing the value to 2 will make the particles render at 2 frames per second. Note this does not slow down the particle system itself. +			The particle system's frame rate is fixed to a value. For instance, changing the value to 2 will make the particles render at 2 frames per second. Note this does not slow down the simulation of the particle system itself.  		</member>  		<member name="fract_delta" type="bool" setter="set_fractional_delta" getter="get_fractional_delta">  			If [code]true[/code], results in fractional delta calculation which has a smoother particles display effect. Default value: [code]true[/code] @@ -52,7 +52,7 @@  			If [code]true[/code], particles use the parent node's coordinate space. If [code]false[/code], they use global coordinates. Default value: [code]true[/code].  		</member>  		<member name="normal_map" type="Texture" setter="set_normal_map" getter="get_normal_map"> -			Normal map to be used for the [code]texture[/code] property. +			Normal map to be used for the [member texture] property.  		</member>  		<member name="one_shot" type="bool" setter="set_one_shot" getter="get_one_shot">  			If [code]true[/code], only one emission cycle occurs. If set [code]true[/code] during a cycle, emission will stop at the cycle's end. Default value: [code]false[/code]. diff --git a/doc/classes/ParticlesMaterial.xml b/doc/classes/ParticlesMaterial.xml index dd7a7cd151..cb06593bc2 100644 --- a/doc/classes/ParticlesMaterial.xml +++ b/doc/classes/ParticlesMaterial.xml @@ -100,7 +100,7 @@  			Amount of [member spread] in Y/Z plane. A value of [code]1[/code] restricts particles to X/Z plane. Default [code]0[/code].  		</member>  		<member name="gravity" type="Vector3" setter="set_gravity" getter="get_gravity"> -			Gravity applied to every particle. Default value: [code](0, 98, 0)[/code]. +			Gravity applied to every particle. Default value: [code](0, -9.8, 0)[/code].  		</member>  		<member name="hue_variation" type="float" setter="set_param" getter="get_param">  			Initial hue variation applied to each particle. @@ -112,13 +112,13 @@  			Hue variation randomness ratio. Default value: [code]0[/code].  		</member>  		<member name="initial_velocity" type="float" setter="set_param" getter="get_param"> -			Initial velocity magnitude for each particle. Direction comes from [member spread]. +			Initial velocity magnitude for each particle. Direction comes from [member spread] and the node's orientation.  		</member>  		<member name="initial_velocity_random" type="float" setter="set_param_randomness" getter="get_param_randomness">  			Initial velocity randomness ratio. Default value: [code]0[/code].  		</member>  		<member name="linear_accel" type="float" setter="set_param" getter="get_param"> -			Linear acceleration applied to each particle. Acceleration increases velocity magnitude each frame without affecting direction. +			Linear acceleration applied to each particle in the direction of motion.  		</member>  		<member name="linear_accel_curve" type="Texture" setter="set_param_texture" getter="get_param_texture">  			Each particle's linear acceleration will vary along this [CurveTexture]. @@ -184,7 +184,7 @@  			Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set angular velocity properties.  		</constant>  		<constant name="PARAM_ORBIT_VELOCITY" value="2" enum="Parameter"> -			Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set orbital_velocity properties. +			Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set orbital velocity properties.  		</constant>  		<constant name="PARAM_LINEAR_ACCEL" value="3" enum="Parameter">  			Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set linear acceleration properties. @@ -205,7 +205,7 @@  			Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set scale properties.  		</constant>  		<constant name="PARAM_HUE_VARIATION" value="9" enum="Parameter"> -			Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set hue_variation properties. +			Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set hue variation properties.  		</constant>  		<constant name="PARAM_ANIM_SPEED" value="10" enum="Parameter">  			Use with [method set_param], [method set_param_randomness], and [method set_param_texture] to set animation speed properties. diff --git a/editor/connections_dialog.cpp b/editor/connections_dialog.cpp index 569ad0297e..6d3603f31b 100644 --- a/editor/connections_dialog.cpp +++ b/editor/connections_dialog.cpp @@ -363,7 +363,7 @@ ConnectDialog::ConnectDialog() {  	tree->connect("node_selected", this, "_tree_node_selected");  	tree->set_connect_to_script_mode(true); -	Node *mc = vbc_left->add_margin_child(TTR("Connect To Script:"), tree, true); +	Node *mc = vbc_left->add_margin_child(TTR("Connect to Script:"), tree, true);  	connect_to_label = Object::cast_to<Label>(vbc_left->get_child(mc->get_index() - 1));  	error_label = memnew(Label); @@ -413,7 +413,7 @@ ConnectDialog::ConnectDialog() {  	vbc_right->add_margin_child(TTR("Extra Call Arguments:"), bind_editor, true);  	HBoxContainer *dstm_hb = memnew(HBoxContainer); -	vbc_left->add_margin_child("Method to Create:", dstm_hb); +	vbc_left->add_margin_child("Receiver Method:", dstm_hb);  	dst_method = memnew(LineEdit);  	dst_method->set_h_size_flags(SIZE_EXPAND_FILL); @@ -489,8 +489,26 @@ void ConnectionsDock::_make_or_edit_connection() {  	bool oshot = connect_dialog->get_oneshot();  	cToMake.flags = CONNECT_PERSIST | (defer ? CONNECT_DEFERRED : 0) | (oshot ? CONNECT_ONESHOT : 0); -	// Conditions to add function, must have a script and must have a method. -	bool add_script_function = !target->get_script().is_null() && !ClassDB::has_method(target->get_class(), cToMake.method); +	// Conditions to add function: must have a script and must not have the method already +	// (in the class, the script itself, or inherited). +	bool add_script_function = false; +	Ref<Script> script = target->get_script(); +	if (!target->get_script().is_null() && !ClassDB::has_method(target->get_class(), cToMake.method)) { +		// There is a chance that the method is inherited from another script. +		bool found_inherited_function = false; +		Ref<Script> inherited_script = script->get_base_script(); +		while (!inherited_script.is_null()) { +			int line = inherited_script->get_language()->find_function(cToMake.method, inherited_script->get_source_code()); +			if (line != -1) { +				found_inherited_function = true; +				break; +			} + +			inherited_script = inherited_script->get_base_script(); +		} + +		add_script_function = !found_inherited_function; +	}  	PoolStringArray script_function_args;  	if (add_script_function) {  		// Pick up args here before "it" is deleted by update_tree. @@ -507,8 +525,7 @@ void ConnectionsDock::_make_or_edit_connection() {  		_connect(cToMake);  	} -	// IMPORTANT NOTE: _disconnect and _connect cause an update_tree, -	// which will delete the object "it" is pointing to. +	// IMPORTANT NOTE: _disconnect and _connect cause an update_tree, which will delete the object "it" is pointing to.  	it = NULL;  	if (add_script_function) { @@ -677,7 +694,7 @@ void ConnectionsDock::_open_connection_dialog(Connection cToEdit) {  	if (src && dst) {  		connect_dialog->set_title(TTR("Edit Connection:") + cToEdit.signal); -		connect_dialog->popup_centered_ratio(); +		connect_dialog->popup_centered();  		connect_dialog->init(cToEdit, true);  	}  } diff --git a/editor/editor_fonts.cpp b/editor/editor_fonts.cpp index cddabbc4e4..cf14cf3e00 100644 --- a/editor/editor_fonts.cpp +++ b/editor/editor_fonts.cpp @@ -206,7 +206,7 @@ void editor_register_fonts(Ref<Theme> p_theme) {  	dfmono->set_font_ptr(_font_Hack_Regular, _font_Hack_Regular_size);  	//dfd->set_force_autohinter(true); //just looks better..i think? -	int default_font_size = int(EditorSettings::get_singleton()->get("interface/editor/main_font_size")) * EDSCALE; +	int default_font_size = int(EDITOR_GET("interface/editor/main_font_size")) * EDSCALE;  	// Default font  	MAKE_DEFAULT_FONT(df, default_font_size); @@ -221,9 +221,9 @@ void editor_register_fonts(Ref<Theme> p_theme) {  	p_theme->set_font("title", "EditorFonts", df_title);  	// Doc font -	MAKE_BOLD_FONT(df_doc_title, int(EDITOR_DEF("text_editor/help/help_title_font_size", 23)) * EDSCALE); +	MAKE_BOLD_FONT(df_doc_title, int(EDITOR_GET("text_editor/help/help_title_font_size")) * EDSCALE); -	MAKE_DEFAULT_FONT(df_doc, int(EDITOR_DEF("text_editor/help/help_font_size", 15)) * EDSCALE); +	MAKE_DEFAULT_FONT(df_doc, int(EDITOR_GET("text_editor/help/help_font_size")) * EDSCALE);  	p_theme->set_font("doc", "EditorFonts", df_doc);  	p_theme->set_font("doc_title", "EditorFonts", df_doc_title); @@ -236,13 +236,13 @@ void editor_register_fonts(Ref<Theme> p_theme) {  	p_theme->set_font("rulers", "EditorFonts", df_rulers);  	// Code font -	MAKE_SOURCE_FONT(df_code, int(EditorSettings::get_singleton()->get("interface/editor/code_font_size")) * EDSCALE); +	MAKE_SOURCE_FONT(df_code, int(EDITOR_GET("interface/editor/code_font_size")) * EDSCALE);  	p_theme->set_font("source", "EditorFonts", df_code); -	MAKE_SOURCE_FONT(df_expression, (int(EditorSettings::get_singleton()->get("interface/editor/code_font_size")) - 1) * EDSCALE); +	MAKE_SOURCE_FONT(df_expression, (int(EDITOR_GET("interface/editor/code_font_size")) - 1) * EDSCALE);  	p_theme->set_font("expression", "EditorFonts", df_expression); -	MAKE_SOURCE_FONT(df_output_code, int(EDITOR_DEF("run/output/font_size", 13)) * EDSCALE); +	MAKE_SOURCE_FONT(df_output_code, int(EDITOR_GET("run/output/font_size")) * EDSCALE);  	p_theme->set_font("output_source", "EditorFonts", df_output_code);  	MAKE_SOURCE_FONT(df_text_editor_status_code, default_font_size); diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index a6746c6d25..58e3cc6fc1 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -320,19 +320,19 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {  	_initial_set("interface/editor/custom_display_scale", 1.0f);  	hints["interface/editor/custom_display_scale"] = PropertyInfo(Variant::REAL, "interface/editor/custom_display_scale", PROPERTY_HINT_RANGE, "0.5,3,0.01", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED);  	_initial_set("interface/editor/main_font_size", 14); -	hints["interface/editor/main_font_size"] = PropertyInfo(Variant::INT, "interface/editor/main_font_size", PROPERTY_HINT_RANGE, "10,40,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED); -	_initial_set("interface/editor/code_font_size", 14); -	hints["interface/editor/code_font_size"] = PropertyInfo(Variant::INT, "interface/editor/code_font_size", PROPERTY_HINT_RANGE, "8,96,1", PROPERTY_USAGE_DEFAULT); +	hints["interface/editor/main_font_size"] = PropertyInfo(Variant::INT, "interface/editor/main_font_size", PROPERTY_HINT_RANGE, "8,48,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED);  	_initial_set("interface/editor/main_font_antialiased", true); -	_initial_set("interface/editor/code_font_antialiased", true);  	_initial_set("interface/editor/main_font_hinting", 2);  	hints["interface/editor/main_font_hinting"] = PropertyInfo(Variant::INT, "interface/editor/main_font_hinting", PROPERTY_HINT_ENUM, "None,Light,Normal", PROPERTY_USAGE_DEFAULT); -	_initial_set("interface/editor/code_font_hinting", 2); -	hints["interface/editor/code_font_hinting"] = PropertyInfo(Variant::INT, "interface/editor/code_font_hinting", PROPERTY_HINT_ENUM, "None,Light,Normal", PROPERTY_USAGE_DEFAULT);  	_initial_set("interface/editor/main_font", "");  	hints["interface/editor/main_font"] = PropertyInfo(Variant::STRING, "interface/editor/main_font", PROPERTY_HINT_GLOBAL_FILE, "*.ttf,*.otf", PROPERTY_USAGE_DEFAULT);  	_initial_set("interface/editor/main_font_bold", "");  	hints["interface/editor/main_font_bold"] = PropertyInfo(Variant::STRING, "interface/editor/main_font_bold", PROPERTY_HINT_GLOBAL_FILE, "*.ttf,*.otf", PROPERTY_USAGE_DEFAULT); +	_initial_set("interface/editor/code_font_size", 14); +	hints["interface/editor/code_font_size"] = PropertyInfo(Variant::INT, "interface/editor/code_font_size", PROPERTY_HINT_RANGE, "8,48,1", PROPERTY_USAGE_DEFAULT); +	_initial_set("interface/editor/code_font_antialiased", true); +	_initial_set("interface/editor/code_font_hinting", 2); +	hints["interface/editor/code_font_hinting"] = PropertyInfo(Variant::INT, "interface/editor/code_font_hinting", PROPERTY_HINT_ENUM, "None,Light,Normal", PROPERTY_USAGE_DEFAULT);  	_initial_set("interface/editor/code_font", "");  	hints["interface/editor/code_font"] = PropertyInfo(Variant::STRING, "interface/editor/code_font", PROPERTY_HINT_GLOBAL_FILE, "*.ttf,*.otf", PROPERTY_USAGE_DEFAULT);  	_initial_set("interface/editor/dim_editor_on_dialog_popup", true); @@ -491,8 +491,12 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {  	// Help  	_initial_set("text_editor/help/show_help_index", true); -	_initial_set("text_editor/help_source_font_size", 14); -	hints["text_editor/help/help_source_font_size"] = PropertyInfo(Variant::REAL, "text_editor/help/help_source_font_size", PROPERTY_HINT_RANGE, "10, 50, 1"); +	_initial_set("text_editor/help/help_font_size", 15); +	hints["text_editor/help/help_font_size"] = PropertyInfo(Variant::INT, "text_editor/help/help_font_size", PROPERTY_HINT_RANGE, "8,48,1"); +	_initial_set("text_editor/help/help_source_font_size", 14); +	hints["text_editor/help/help_source_font_size"] = PropertyInfo(Variant::INT, "text_editor/help/help_source_font_size", PROPERTY_HINT_RANGE, "8,48,1"); +	_initial_set("text_editor/help/help_title_font_size", 23); +	hints["text_editor/help/help_title_font_size"] = PropertyInfo(Variant::INT, "text_editor/help/help_title_font_size", PROPERTY_HINT_RANGE, "8,48,1");  	/* Editors */ @@ -600,7 +604,8 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {  	_initial_set("run/auto_save/save_before_running", true);  	// Output -	hints["run/output/font_size"] = PropertyInfo(Variant::INT, "run/output/font_size", PROPERTY_HINT_RANGE, "8,96,1", PROPERTY_USAGE_DEFAULT); +	_initial_set("run/output/font_size", 13); +	hints["run/output/font_size"] = PropertyInfo(Variant::INT, "run/output/font_size", PROPERTY_HINT_RANGE, "8,48,1");  	_initial_set("run/output/always_clear_output_on_play", true);  	_initial_set("run/output/always_open_output_on_play", true);  	_initial_set("run/output/always_close_output_on_stop", false); diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index cea65bb9b4..c7a438948d 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -882,26 +882,29 @@ void ScriptTextEditor::_update_connected_methods() {  				continue;  			} -			int line = script->get_language()->find_function(connection.method, text_edit->get_text()); -			if (line < 0) { -				// There is a chance that the method is inherited from another script. -				bool found_inherited_function = false; -				Ref<Script> inherited_script = script->get_base_script(); -				while (!inherited_script.is_null()) { -					line = inherited_script->get_language()->find_function(connection.method, inherited_script->get_source_code()); -					if (line != -1) { -						found_inherited_function = true; -						break; +			if (!ClassDB::has_method(script->get_instance_base_type(), connection.method)) { + +				int line = script->get_language()->find_function(connection.method, text_edit->get_text()); +				if (line < 0) { +					// There is a chance that the method is inherited from another script. +					bool found_inherited_function = false; +					Ref<Script> inherited_script = script->get_base_script(); +					while (!inherited_script.is_null()) { +						line = inherited_script->get_language()->find_function(connection.method, inherited_script->get_source_code()); +						if (line != -1) { +							found_inherited_function = true; +							break; +						} + +						inherited_script = inherited_script->get_base_script();  					} -					inherited_script = inherited_script->get_base_script(); -				} - -				if (!found_inherited_function) { -					missing_connections.push_back(connection); +					if (!found_inherited_function) { +						missing_connections.push_back(connection); +					} +				} else { +					text_edit->set_line_info_icon(line - 1, get_parent_control()->get_icon("Slot", "EditorIcons"), connection.method);  				} -			} else { -				text_edit->set_line_info_icon(line - 1, get_parent_control()->get_icon("Slot", "EditorIcons"), connection.method);  			}  		}  	} diff --git a/main/main.cpp b/main/main.cpp index 3f01a17ea7..7e4fdeeaa7 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -1020,6 +1020,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph  	}  	Engine::get_singleton()->set_iterations_per_second(GLOBAL_DEF("physics/common/physics_fps", 60)); +	ProjectSettings::get_singleton()->set_custom_property_info("physics/common/physics_fps", PropertyInfo(Variant::INT, "physics/common/physics_fps", PROPERTY_HINT_RANGE, "1,120,1,or_greater"));  	Engine::get_singleton()->set_physics_jitter_fix(GLOBAL_DEF("physics/common/physics_jitter_fix", 0.5));  	Engine::get_singleton()->set_target_fps(GLOBAL_DEF("debug/settings/fps/force_fps", 0));  	ProjectSettings::get_singleton()->set_custom_property_info("debug/settings/fps/force_fps", PropertyInfo(Variant::INT, "debug/settings/fps/force_fps", PROPERTY_HINT_RANGE, "0,120,1,or_greater")); diff --git a/modules/webrtc/config.py b/modules/webrtc/config.py index 2e3a18ad0e..48b4c33c5d 100644 --- a/modules/webrtc/config.py +++ b/modules/webrtc/config.py @@ -7,7 +7,8 @@ def configure(env):  def get_doc_classes():      return [          "WebRTCPeerConnection", -        "WebRTCDataChannel" +        "WebRTCDataChannel", +        "WebRTCMultiplayer"      ]  def get_doc_path(): diff --git a/modules/webrtc/doc_classes/WebRTCDataChannel.xml b/modules/webrtc/doc_classes/WebRTCDataChannel.xml index dcc14d4ddb..7cce97244d 100644 --- a/modules/webrtc/doc_classes/WebRTCDataChannel.xml +++ b/modules/webrtc/doc_classes/WebRTCDataChannel.xml @@ -11,85 +11,106 @@  			<return type="void">  			</return>  			<description> +				Closes this data channel, notifying the other peer.  			</description>  		</method>  		<method name="get_id" qualifiers="const">  			<return type="int">  			</return>  			<description> +				Returns the id assigned to this channel during creation (or auto-assigned during negotiation). +				If the channel is not negotiated out-of-band the id will only be available after the connection is established (will return [code]65535[/code] until then).  			</description>  		</method>  		<method name="get_label" qualifiers="const">  			<return type="String">  			</return>  			<description> +				Returns the label assigned to this channel during creation.  			</description>  		</method>  		<method name="get_max_packet_life_time" qualifiers="const">  			<return type="int">  			</return>  			<description> +				Returns the [code]maxPacketLifeTime[/code] value assigned to this channel during creation. +				Will be [code]65535[/code] if not specified.  			</description>  		</method>  		<method name="get_max_retransmits" qualifiers="const">  			<return type="int">  			</return>  			<description> +				Returns the [code]maxRetransmits[/code] value assigned to this channel during creation. +				Will be [code]65535[/code] if not specified.  			</description>  		</method>  		<method name="get_protocol" qualifiers="const">  			<return type="String">  			</return>  			<description> +				Returns the sub-protocol assigned to this channel during creation. An empty string if not specified.  			</description>  		</method>  		<method name="get_ready_state" qualifiers="const">  			<return type="int" enum="WebRTCDataChannel.ChannelState">  			</return>  			<description> +				Returns the current state of this channel, see [enum WebRTCDataChannel.ChannelState].  			</description>  		</method>  		<method name="is_negotiated" qualifiers="const">  			<return type="bool">  			</return>  			<description> +				Returns [code]true[/code] if this channel was created with out-of-band configuration.  			</description>  		</method>  		<method name="is_ordered" qualifiers="const">  			<return type="bool">  			</return>  			<description> +				Returns [code]true[/code] if this channel was created with ordering enabled (default).  			</description>  		</method>  		<method name="poll">  			<return type="int" enum="Error">  			</return>  			<description> +				Reserved, but not used for now.  			</description>  		</method>  		<method name="was_string_packet" qualifiers="const">  			<return type="bool">  			</return>  			<description> +				Returns [code]true[/code] if the last received packet was transferred as text. See [member write_mode].  			</description>  		</method>  	</methods>  	<members>  		<member name="write_mode" type="int" setter="set_write_mode" getter="get_write_mode" enum="WebRTCDataChannel.WriteMode"> +			The transfer mode to use when sending outgoing packet. Either text or binary.  		</member>  	</members>  	<constants>  		<constant name="WRITE_MODE_TEXT" value="0" enum="WriteMode"> +			Tells the channel to send data over this channel as text. An external peer (non-Godot) would receive this as a string.  		</constant>  		<constant name="WRITE_MODE_BINARY" value="1" enum="WriteMode"> +			Tells the channel to send data over this channel as binary. An external peer (non-Godot) would receive this as array buffer or blob.  		</constant>  		<constant name="STATE_CONNECTING" value="0" enum="ChannelState"> +			The channel was created, but it's still trying to connect.  		</constant>  		<constant name="STATE_OPEN" value="1" enum="ChannelState"> +			The channel is currently open, and data can flow over it.  		</constant>  		<constant name="STATE_CLOSING" value="2" enum="ChannelState"> +			The channel is being closed, no new messages will be accepted, but those already in queue will be flushed.  		</constant>  		<constant name="STATE_CLOSED" value="3" enum="ChannelState"> +			The channel was closed, or connection failed.  		</constant>  	</constants>  </class> diff --git a/modules/webrtc/doc_classes/WebRTCMultiplayer.xml b/modules/webrtc/doc_classes/WebRTCMultiplayer.xml new file mode 100644 index 0000000000..2b0622fffa --- /dev/null +++ b/modules/webrtc/doc_classes/WebRTCMultiplayer.xml @@ -0,0 +1,85 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="WebRTCMultiplayer" inherits="NetworkedMultiplayerPeer" category="Core" version="3.2"> +	<brief_description> +		A simple interface to create a peer-to-peer mesh network composed of [WebRTCPeerConnection] that is compatible with the [MultiplayerAPI]. +	</brief_description> +	<description> +		This class constructs a full mesh of [WebRTCPeerConnection] (one connection for each peer) that can be used as a [member MultiplayerAPI.network_peer]. +		You can add each [WebRTCPeerConnection] via [method add_peer] or remove them via [method remove_peer]. Peers must be added in [constant WebRTCPeerConnection.STATE_NEW] state to allow it to create the appropriate channels. This class will not create offers nor set descriptions, it will only poll them, and notify connections and disconnections. +		[signal NetworkedMultiplayerPeer.connection_succeeded] and [signal NetworkedMultiplayerPeer.server_disconnected] will not be emitted unless [code]server_compatibility[/code] is [code]true[/code] in [method initialize]. Beside that data transfer works like in a [NetworkedMultiplayerPeer]. +	</description> +	<tutorials> +	</tutorials> +	<methods> +		<method name="add_peer"> +			<return type="int" enum="Error"> +			</return> +			<argument index="0" name="peer" type="WebRTCPeerConnection"> +			</argument> +			<argument index="1" name="peer_id" type="int"> +			</argument> +			<argument index="2" name="unreliable_lifetime" type="int" default="1"> +			</argument> +			<description> +				Add a new peer to the mesh with the given [code]peer_id[/code]. The [WebRTCPeerConnection] must be in state [constant WebRTCPeerConnection.STATE_NEW]. +				Three channels will be created for reliable, unreliable, and ordered transport. The value of [code]unreliable_lifetime[/code] will be passed to the [code]maxPacketLifetime[/code] option when creating unreliable and ordered channels (see [method WebRTCPeerConnection.create_data_channel]). +			</description> +		</method> +		<method name="close"> +			<return type="void"> +			</return> +			<description> +				Close all the add peer connections and channels, freeing all resources. +			</description> +		</method> +		<method name="get_peer"> +			<return type="Dictionary"> +			</return> +			<argument index="0" name="peer_id" type="int"> +			</argument> +			<description> +				Return a dictionary representation of the peer with given [code]peer_id[/code] with three keys. [code]connection[/code] containing the [WebRTCPeerConnection] to this peer, [code]channels[/code] an array of three [WebRTCDataChannel], and [code]connected[/code] a boolean representing if the peer connection is currently connected (all three channels are open). +			</description> +		</method> +		<method name="get_peers"> +			<return type="Dictionary"> +			</return> +			<description> +				Returns a dictionary which keys are the peer ids and values the peer representation as in [method get_peer] +			</description> +		</method> +		<method name="has_peer"> +			<return type="bool"> +			</return> +			<argument index="0" name="peer_id" type="int"> +			</argument> +			<description> +				Returns [code]true[/code] if the given [code]peer_id[/code] is in the peers map (it might not be connected though). +			</description> +		</method> +		<method name="initialize"> +			<return type="int" enum="Error"> +			</return> +			<argument index="0" name="peer_id" type="int"> +			</argument> +			<argument index="1" name="server_compatibility" type="bool" default="false"> +			</argument> +			<description> +				Initialize the multiplayer peer with the given [code]peer_id[/code] (must be between 1 and 2147483647). +				If [code]server_compatibilty[/code] is [code]false[/code] (default), the multiplayer peer will be immediately in state [constant NetworkedMultiplayerPeer.CONNECTION_CONNECTED] and [signal NetworkedMultiplayerPeer.connection_succeeded] will not be emitted. +				If [code]server_compatibilty[/code] is [code]true[/code] the peer will suppress all [signal NetworkedMultiplayerPeer.peer_connected] signals until a peer with id [constant NetworkedMultiplayerPeer.TARGET_PEER_SERVER] connects and then emit [signal NetworkedMultiplayerPeer.connection_succeeded]. After that the signal [signal NetworkedMultiplayerPeer.peer_connected] will be emitted for every already connected peer, and any new peer that might connect. If the server peer disconnects after that, signal [signal NetworkedMultiplayerPeer.server_disconnected] will be emitted and state will become [constant NetworkedMultiplayerPeer.CONNECTION_CONNECTED]. +			</description> +		</method> +		<method name="remove_peer"> +			<return type="void"> +			</return> +			<argument index="0" name="peer_id" type="int"> +			</argument> +			<description> +				Remove the peer with given [code]peer_id[/code] from the mesh. If the peer was connected, and [signal NetworkedMultiplayerPeer.peer_connected] was emitted for it, then [signal NetworkedMultiplayerPeer.peer_disconnected] will be emitted. +			</description> +		</method> +	</methods> +	<constants> +	</constants> +</class> diff --git a/modules/webrtc/doc_classes/WebRTCPeerConnection.xml b/modules/webrtc/doc_classes/WebRTCPeerConnection.xml index 8b14c60deb..ae709877f4 100644 --- a/modules/webrtc/doc_classes/WebRTCPeerConnection.xml +++ b/modules/webrtc/doc_classes/WebRTCPeerConnection.xml @@ -1,8 +1,15 @@  <?xml version="1.0" encoding="UTF-8" ?>  <class name="WebRTCPeerConnection" inherits="Reference" category="Core" version="3.2">  	<brief_description> +		Interface to a WebRTC peer connection.  	</brief_description>  	<description> +		A WebRTC connection between the local computer and a remote peer. Provides an interface to connect, maintain and monitor the connection. +		Setting up a WebRTC connection between two peers from now on) may not seem a trivial task, but it can be broken down into 3 main steps: +		- The peer that wants to initiate the connection ([code]A[/code] from now on) creates an offer and send it to the other peer ([code]B[/code] from now on). +		- [code]B[/code] receives the offer, generate and answer, and sends it to [code]B[/code]). +		- [code]A[/code] and [code]B[/code] then generates and exchange ICE candidates with each other. +		After these steps, the connection should become connected. Keep on reading or look into the tutorial for more information.  	</description>  	<tutorials>  	</tutorials> @@ -17,12 +24,14 @@  			<argument index="2" name="name" type="String">  			</argument>  			<description> +				Add an ice candidate generated by a remote peer (and received over the signaling server). See [signal ice_candidate_created].  			</description>  		</method>  		<method name="close">  			<return type="void">  			</return>  			<description> +				Close the peer connection and all data channels associated with it. Note, you cannot reuse this object for a new connection unless you call [method initialize].  			</description>  		</method>  		<method name="create_data_channel"> @@ -35,18 +44,38 @@  }">  			</argument>  			<description> +				Returns a new [WebRTCDataChannel] (or [code]null[/code] on failure) with given [code]label[/code] and optionally configured via the [code]options[/code] dictionary. This method can only be called when the connection is in state [constant STATE_NEW]. +				There are two ways to create a working data channel: either call [method create_data_channel] on only one of the peer and listen to [signal data_channel_received] on the other, or call [method create_data_channel] on both peers, with the same values, and the [code]negotiated[/code] option set to [code]true[/code]. +				Valid [code]options[/code] are: +				[codeblock] +				{ +				    "negotiated": true, # When set to true (default off), means the channel is negotiated out of band. "id" must be set too. data_channel_received will not be called. +				    "id": 1, # When "negotiated" is true this value must also be set to the same value on both peer. + +				    # Only one of maxRetransmits and maxPacketLifeTime can be specified, not both. They make the channel unreliable (but also better at real time). +				    "maxRetransmits": 1, # Specify the maximum number of attempt the peer will make to retransmits packets if they are not acknowledged. +				    "maxPacketLifeTime": 100, # Specify the maximum amount of time before giving up retransmitions of unacknowledged packets (in milliseconds). +				    "ordered": true, # When in unreliable mode (i.e. either "maxRetransmits" or "maxPacketLifetime" is set), "ordered" (true by default) specify if packet ordering is to be enforced. + +				    "protocol": "my-custom-protocol", # A custom sub-protocol string for this channel. +				} +				[/codeblock] +				NOTE: You must keep a reference to channels created this way, or it will be closed.  			</description>  		</method>  		<method name="create_offer">  			<return type="int" enum="Error">  			</return>  			<description> +				Creates a new SDP offer to start a WebRTC connection with a remote peer. At least one [WebRTCDataChannel] must have been created before calling this method. +				If this functions returns [code]OK[/code], [signal session_description_created] will be called when the session is ready to be sent.  			</description>  		</method>  		<method name="get_connection_state" qualifiers="const">  			<return type="int" enum="WebRTCPeerConnection.ConnectionState">  			</return>  			<description> +				Returns the connection state. See [enum ConnectionState].  			</description>  		</method>  		<method name="initialize"> @@ -57,12 +86,29 @@  }">  			</argument>  			<description> +				Re-initialize this peer connection, closing any previously active connection, and going back to state [constant STATE_NEW]. A dictionary of [code]options[/code] can be passed to configure the peer connection. +				Valid [code]options[/code] are: +				[codeblock] +				{ +				    "iceServers": [ +				        { +				            "urls": [ "stun:stun.example.com:3478" ], # One or more STUN servers. +				        }, +				        { +				            "urls": [ "turn:turn.example.com:3478" ], # One or more TURN servers. +				            "username": "a_username", # Optional username for the TURN server. +				            "credentials": "a_password", # Optional password for the TURN server. +				        } +				    ] +				} +				[/codeblock]  			</description>  		</method>  		<method name="poll">  			<return type="int" enum="Error">  			</return>  			<description> +				Call this method frequently (e.g. in [method Node._process] or [method Node._physics_process]) to properly receive signals.  			</description>  		</method>  		<method name="set_local_description"> @@ -73,6 +119,8 @@  			<argument index="1" name="sdp" type="String">  			</argument>  			<description> +				Sets the SDP description of the local peer. This should be called in response to [signal session_description_created]. +				If [code]type[/code] is [code]answer[/code] the peer will start emitting [signal ice_candidate_created].  			</description>  		</method>  		<method name="set_remote_description"> @@ -83,6 +131,9 @@  			<argument index="1" name="sdp" type="String">  			</argument>  			<description> +				Sets the SDP description of the remote peer. This should be called with the values generated by a remote peer and received over the signaling server. +				If [code]type[/code] is [code]offer[/code] the peer will emit [signal session_description_created] with the appropriate answer. +				If [code]type[/code] is [code]answer[/code] the peer will start emitting [signal ice_candidate_created].  			</description>  		</method>  	</methods> @@ -91,6 +142,8 @@  			<argument index="0" name="channel" type="Object">  			</argument>  			<description> +				Emitted when a new in-band channel is received, i.e. when the channel was created with [code]negotiated: false[/code] (default). +				The object will be an instance of [WebRTCDataChannel]. You must keep a reference of it or it will be closed automatically. See [method create_data_channel]  			</description>  		</signal>  		<signal name="ice_candidate_created"> @@ -101,6 +154,7 @@  			<argument index="2" name="name" type="String">  			</argument>  			<description> +				Emitted when a new ICE candidate has been created. The three parameters are meant to be passed to the remote peer over the signaling server.  			</description>  		</signal>  		<signal name="session_description_created"> @@ -109,21 +163,28 @@  			<argument index="1" name="sdp" type="String">  			</argument>  			<description> +				Emitted after a successful call to [method create_offer] or [method set_remote_description] (when it generates an answer). The parameters are meant to be passed to [method set_local_description] on this object, and sent to the remote peer over the signaling server.  			</description>  		</signal>  	</signals>  	<constants>  		<constant name="STATE_NEW" value="0" enum="ConnectionState"> +			The connection is new, data channels and an offer can be created in this state.  		</constant>  		<constant name="STATE_CONNECTING" value="1" enum="ConnectionState"> +			The peer is connecting, ICE is in progress, non of the transports has failed.  		</constant>  		<constant name="STATE_CONNECTED" value="2" enum="ConnectionState"> +			The peer is connected, all ICE transports are connected.  		</constant>  		<constant name="STATE_DISCONNECTED" value="3" enum="ConnectionState"> +			At least one ICE transport is disconnected.  		</constant>  		<constant name="STATE_FAILED" value="4" enum="ConnectionState"> +			One or more of the ICE transports failed.  		</constant>  		<constant name="STATE_CLOSED" value="5" enum="ConnectionState"> +			The peer connection is closed (after calling [method close] for example).  		</constant>  	</constants>  </class> diff --git a/modules/webrtc/register_types.cpp b/modules/webrtc/register_types.cpp index 44e072cc89..58b68d926b 100644 --- a/modules/webrtc/register_types.cpp +++ b/modules/webrtc/register_types.cpp @@ -40,6 +40,7 @@  #include "webrtc_data_channel_gdnative.h"  #include "webrtc_peer_connection_gdnative.h"  #endif +#include "webrtc_multiplayer.h"  void register_webrtc_types() {  #ifdef JAVASCRIPT_ENABLED @@ -54,6 +55,7 @@ void register_webrtc_types() {  	ClassDB::register_class<WebRTCDataChannelGDNative>();  #endif  	ClassDB::register_virtual_class<WebRTCDataChannel>(); +	ClassDB::register_class<WebRTCMultiplayer>();  }  void unregister_webrtc_types() {} diff --git a/modules/webrtc/webrtc_data_channel_js.cpp b/modules/webrtc/webrtc_data_channel_js.cpp index 2e7c64aa72..ce2fada634 100644 --- a/modules/webrtc/webrtc_data_channel_js.cpp +++ b/modules/webrtc/webrtc_data_channel_js.cpp @@ -205,30 +205,45 @@ String WebRTCDataChannelJS::get_label() const {  }  /* clang-format off */ -#define _JS_GET(PROP)				\ +#define _JS_GET(PROP, DEF)			\  EM_ASM_INT({					\  	var dict = Module.IDHandler.get($0);	\  	if (!dict || !dict["channel"]) {	\ -		return 0;			\ -	};					\ -	return dict["channel"].PROP;		\ +		return DEF;			\ +	}					\ +	var out = dict["channel"].PROP;		\ +	return out === null ? DEF : out;	\  }, _js_id)  /* clang-format on */  bool WebRTCDataChannelJS::is_ordered() const { -	return _JS_GET(ordered); +	return _JS_GET(ordered, true);  }  int WebRTCDataChannelJS::get_id() const { -	return _JS_GET(id); +	return _JS_GET(id, 65535);  }  int WebRTCDataChannelJS::get_max_packet_life_time() const { -	return _JS_GET(maxPacketLifeTime); +	// Can't use macro, webkit workaround. +	/* clang-format off */ +	return EM_ASM_INT({ +		var dict = Module.IDHandler.get($0); +		if (!dict || !dict["channel"]) { +			return 65535; +		} +		if (dict["channel"].maxRetransmitTime !== undefined) { +			// Guess someone didn't appreciate the standardization process. +			return dict["channel"].maxRetransmitTime; +		} +		var out = dict["channel"].maxPacketLifeTime; +		return out === null ? 65535 : out; +	}, _js_id); +	/* clang-format on */  }  int WebRTCDataChannelJS::get_max_retransmits() const { -	return _JS_GET(maxRetransmits); +	return _JS_GET(maxRetransmits, 65535);  }  String WebRTCDataChannelJS::get_protocol() const { @@ -236,7 +251,7 @@ String WebRTCDataChannelJS::get_protocol() const {  }  bool WebRTCDataChannelJS::is_negotiated() const { -	return _JS_GET(negotiated); +	return _JS_GET(negotiated, false);  }  WebRTCDataChannelJS::WebRTCDataChannelJS() { diff --git a/modules/webrtc/webrtc_multiplayer.cpp b/modules/webrtc/webrtc_multiplayer.cpp new file mode 100644 index 0000000000..17dafff93a --- /dev/null +++ b/modules/webrtc/webrtc_multiplayer.cpp @@ -0,0 +1,384 @@ +/*************************************************************************/ +/*  webrtc_multiplayer.cpp                                               */ +/*************************************************************************/ +/*                       This file is part of:                           */ +/*                           GODOT ENGINE                                */ +/*                      https://godotengine.org                          */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur.                 */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md)    */ +/*                                                                       */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the       */ +/* "Software"), to deal in the Software without restriction, including   */ +/* without limitation the rights to use, copy, modify, merge, publish,   */ +/* distribute, sublicense, and/or sell copies of the Software, and to    */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions:                                             */ +/*                                                                       */ +/* The above copyright notice and this permission notice shall be        */ +/* included in all copies or substantial portions of the Software.       */ +/*                                                                       */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */ +/*************************************************************************/ + +#include "webrtc_multiplayer.h" + +#include "core/io/marshalls.h" +#include "core/os/os.h" + +void WebRTCMultiplayer::_bind_methods() { +	ClassDB::bind_method(D_METHOD("initialize", "peer_id", "server_compatibility"), &WebRTCMultiplayer::initialize, DEFVAL(false)); +	ClassDB::bind_method(D_METHOD("add_peer", "peer", "peer_id", "unreliable_lifetime"), &WebRTCMultiplayer::add_peer, DEFVAL(1)); +	ClassDB::bind_method(D_METHOD("remove_peer", "peer_id"), &WebRTCMultiplayer::remove_peer); +	ClassDB::bind_method(D_METHOD("has_peer", "peer_id"), &WebRTCMultiplayer::has_peer); +	ClassDB::bind_method(D_METHOD("get_peer", "peer_id"), &WebRTCMultiplayer::get_peer); +	ClassDB::bind_method(D_METHOD("get_peers"), &WebRTCMultiplayer::get_peers); +	ClassDB::bind_method(D_METHOD("close"), &WebRTCMultiplayer::close); +} + +void WebRTCMultiplayer::set_transfer_mode(TransferMode p_mode) { +	transfer_mode = p_mode; +} + +NetworkedMultiplayerPeer::TransferMode WebRTCMultiplayer::get_transfer_mode() const { +	return transfer_mode; +} + +void WebRTCMultiplayer::set_target_peer(int p_peer_id) { +	target_peer = p_peer_id; +} + +/* Returns the ID of the NetworkedMultiplayerPeer who sent the most recent packet: */ +int WebRTCMultiplayer::get_packet_peer() const { +	return next_packet_peer; +} + +bool WebRTCMultiplayer::is_server() const { +	return unique_id == TARGET_PEER_SERVER; +} + +void WebRTCMultiplayer::poll() { +	if (peer_map.size() == 0) +		return; + +	List<int> remove; +	List<int> add; +	for (Map<int, Ref<ConnectedPeer> >::Element *E = peer_map.front(); E; E = E->next()) { +		Ref<ConnectedPeer> peer = E->get(); +		peer->connection->poll(); +		// Check peer state +		switch (peer->connection->get_connection_state()) { +			case WebRTCPeerConnection::STATE_NEW: +			case WebRTCPeerConnection::STATE_CONNECTING: +				// Go to next peer, not ready yet. +				continue; +			case WebRTCPeerConnection::STATE_CONNECTED: +				// Good to go, go ahead and check channel state. +				break; +			default: +				// Peer is closed or in error state. Got to next peer. +				remove.push_back(E->key()); +				continue; +		} +		// Check channels state +		int ready = 0; +		for (List<Ref<WebRTCDataChannel> >::Element *C = peer->channels.front(); C && C->get().is_valid(); C = C->next()) { +			Ref<WebRTCDataChannel> ch = C->get(); +			switch (ch->get_ready_state()) { +				case WebRTCDataChannel::STATE_CONNECTING: +					continue; +				case WebRTCDataChannel::STATE_OPEN: +					ready++; +					continue; +				default: +					// Channel was closed or in error state, remove peer id. +					remove.push_back(E->key()); +			} +			// We got a closed channel break out, the peer will be removed. +			break; +		} +		// This peer has newly connected, and all channels are now open. +		if (ready == peer->channels.size() && !peer->connected) { +			peer->connected = true; +			add.push_back(E->key()); +		} +	} +	// Remove disconnected peers +	for (List<int>::Element *E = remove.front(); E; E = E->next()) { +		remove_peer(E->get()); +		if (next_packet_peer == E->get()) +			next_packet_peer = 0; +	} +	// Signal newly connected peers +	for (List<int>::Element *E = add.front(); E; E = E->next()) { +		// Already connected to server: simply notify new peer. +		// NOTE: Mesh is always connected. +		if (connection_status == CONNECTION_CONNECTED) +			emit_signal("peer_connected", E->get()); + +		// Server emulation mode suppresses peer_conencted until server connects. +		if (server_compat && E->get() == TARGET_PEER_SERVER) { +			// Server connected. +			connection_status = CONNECTION_CONNECTED; +			emit_signal("peer_connected", TARGET_PEER_SERVER); +			emit_signal("connection_succeeded"); +			// Notify of all previously connected peers +			for (Map<int, Ref<ConnectedPeer> >::Element *F = peer_map.front(); F; F = F->next()) { +				if (F->key() != 1 && F->get()->connected) +					emit_signal("peer_connected", F->key()); +			} +			break; // Because we already notified of all newly added peers. +		} +	} +	// Fetch next packet +	if (next_packet_peer == 0) +		_find_next_peer(); +} + +void WebRTCMultiplayer::_find_next_peer() { +	Map<int, Ref<ConnectedPeer> >::Element *E = peer_map.find(next_packet_peer); +	if (E) E = E->next(); +	// After last. +	while (E) { +		for (List<Ref<WebRTCDataChannel> >::Element *F = E->get()->channels.front(); F; F = F->next()) { +			if (F->get()->get_available_packet_count()) { +				next_packet_peer = E->key(); +				return; +			} +		} +		E = E->next(); +	} +	E = peer_map.front(); +	// Before last +	while (E) { +		for (List<Ref<WebRTCDataChannel> >::Element *F = E->get()->channels.front(); F; F = F->next()) { +			if (F->get()->get_available_packet_count()) { +				next_packet_peer = E->key(); +				return; +			} +		} +		if (E->key() == (int)next_packet_peer) +			break; +		E = E->next(); +	} +	// No packet found +	next_packet_peer = 0; +} + +void WebRTCMultiplayer::set_refuse_new_connections(bool p_enable) { +	refuse_connections = p_enable; +} + +bool WebRTCMultiplayer::is_refusing_new_connections() const { +	return refuse_connections; +} + +NetworkedMultiplayerPeer::ConnectionStatus WebRTCMultiplayer::get_connection_status() const { +	return connection_status; +} + +Error WebRTCMultiplayer::initialize(int p_self_id, bool p_server_compat) { +	ERR_FAIL_COND_V(p_self_id < 0 || p_self_id > ~(1 << 31), ERR_INVALID_PARAMETER); +	unique_id = p_self_id; +	server_compat = p_server_compat; + +	// Mesh and server are always connected +	if (!server_compat || p_self_id == 1) +		connection_status = CONNECTION_CONNECTED; +	else +		connection_status = CONNECTION_CONNECTING; +	return OK; +} + +int WebRTCMultiplayer::get_unique_id() const { +	ERR_FAIL_COND_V(connection_status == CONNECTION_DISCONNECTED, 1); +	return unique_id; +} + +void WebRTCMultiplayer::_peer_to_dict(Ref<ConnectedPeer> p_connected_peer, Dictionary &r_dict) { +	Array channels; +	for (List<Ref<WebRTCDataChannel> >::Element *F = p_connected_peer->channels.front(); F; F = F->next()) { +		channels.push_back(F->get()); +	} +	r_dict["connection"] = p_connected_peer->connection; +	r_dict["connected"] = p_connected_peer->connected; +	r_dict["channels"] = channels; +} + +bool WebRTCMultiplayer::has_peer(int p_peer_id) { +	return peer_map.has(p_peer_id); +} + +Dictionary WebRTCMultiplayer::get_peer(int p_peer_id) { +	ERR_FAIL_COND_V(!peer_map.has(p_peer_id), Dictionary()); +	Dictionary out; +	_peer_to_dict(peer_map[p_peer_id], out); +	return out; +} + +Dictionary WebRTCMultiplayer::get_peers() { +	Dictionary out; +	for (Map<int, Ref<ConnectedPeer> >::Element *E = peer_map.front(); E; E = E->next()) { +		Dictionary d; +		_peer_to_dict(E->get(), d); +		out[E->key()] = d; +	} +	return out; +} + +Error WebRTCMultiplayer::add_peer(Ref<WebRTCPeerConnection> p_peer, int p_peer_id, int p_unreliable_lifetime) { +	ERR_FAIL_COND_V(p_peer_id < 0 || p_peer_id > ~(1 << 31), ERR_INVALID_PARAMETER); +	ERR_FAIL_COND_V(p_unreliable_lifetime < 0, ERR_INVALID_PARAMETER); +	ERR_FAIL_COND_V(refuse_connections, ERR_UNAUTHORIZED); +	// Peer must be valid, and in new state (to create data channels) +	ERR_FAIL_COND_V(!p_peer.is_valid(), ERR_INVALID_PARAMETER); +	ERR_FAIL_COND_V(p_peer->get_connection_state() != WebRTCPeerConnection::STATE_NEW, ERR_INVALID_PARAMETER); + +	Ref<ConnectedPeer> peer = memnew(ConnectedPeer); +	peer->connection = p_peer; + +	// Initialize data channels +	Dictionary cfg; +	cfg["negotiated"] = true; +	cfg["ordered"] = true; + +	cfg["id"] = 1; +	peer->channels[CH_RELIABLE] = p_peer->create_data_channel("reliable", cfg); +	ERR_FAIL_COND_V(!peer->channels[CH_RELIABLE].is_valid(), FAILED); + +	cfg["id"] = 2; +	cfg["maxPacketLifetime"] = p_unreliable_lifetime; +	peer->channels[CH_ORDERED] = p_peer->create_data_channel("ordered", cfg); +	ERR_FAIL_COND_V(!peer->channels[CH_ORDERED].is_valid(), FAILED); + +	cfg["id"] = 3; +	cfg["ordered"] = false; +	peer->channels[CH_UNRELIABLE] = p_peer->create_data_channel("unreliable", cfg); +	ERR_FAIL_COND_V(!peer->channels[CH_UNRELIABLE].is_valid(), FAILED); + +	peer_map[p_peer_id] = peer; // add the new peer connection to the peer_map + +	return OK; +} + +void WebRTCMultiplayer::remove_peer(int p_peer_id) { +	ERR_FAIL_COND(!peer_map.has(p_peer_id)); +	Ref<ConnectedPeer> peer = peer_map[p_peer_id]; +	peer_map.erase(p_peer_id); +	if (peer->connected) { +		peer->connected = false; +		emit_signal("peer_disconnected", p_peer_id); +		if (server_compat && p_peer_id == TARGET_PEER_SERVER) { +			emit_signal("server_disconnected"); +			connection_status = CONNECTION_DISCONNECTED; +		} +	} +} + +Error WebRTCMultiplayer::get_packet(const uint8_t **r_buffer, int &r_buffer_size) { +	// Peer not available +	if (next_packet_peer == 0 || !peer_map.has(next_packet_peer)) { +		_find_next_peer(); +		ERR_FAIL_V(ERR_UNAVAILABLE); +	} +	for (List<Ref<WebRTCDataChannel> >::Element *E = peer_map[next_packet_peer]->channels.front(); E; E = E->next()) { +		if (E->get()->get_available_packet_count()) { +			Error err = E->get()->get_packet(r_buffer, r_buffer_size); +			_find_next_peer(); +			return err; +		} +	} +	// Channels for that peer were empty. Bug? +	_find_next_peer(); +	ERR_FAIL_V(ERR_BUG); +} + +Error WebRTCMultiplayer::put_packet(const uint8_t *p_buffer, int p_buffer_size) { +	ERR_FAIL_COND_V(connection_status == CONNECTION_DISCONNECTED, ERR_UNCONFIGURED); + +	int ch = CH_RELIABLE; +	switch (transfer_mode) { +		case TRANSFER_MODE_RELIABLE: +			ch = CH_RELIABLE; +			break; +		case TRANSFER_MODE_UNRELIABLE_ORDERED: +			ch = CH_ORDERED; +			break; +		case TRANSFER_MODE_UNRELIABLE: +			ch = CH_UNRELIABLE; +			break; +	} + +	Map<int, Ref<ConnectedPeer> >::Element *E = NULL; + +	if (target_peer > 0) { + +		E = peer_map.find(target_peer); +		if (!E) { +			ERR_EXPLAIN("Invalid Target Peer: " + itos(target_peer)); +			ERR_FAIL_V(ERR_INVALID_PARAMETER); +		} +		ERR_FAIL_COND_V(E->value()->channels.size() <= ch, ERR_BUG); +		ERR_FAIL_COND_V(!E->value()->channels[ch].is_valid(), ERR_BUG); +		return E->value()->channels[ch]->put_packet(p_buffer, p_buffer_size); + +	} else { +		int exclude = -target_peer; + +		for (Map<int, Ref<ConnectedPeer> >::Element *F = peer_map.front(); F; F = F->next()) { + +			// Exclude packet. If target_peer == 0 then don't exclude any packets +			if (target_peer != 0 && F->key() == exclude) +				continue; + +			ERR_CONTINUE(F->value()->channels.size() <= ch || !F->value()->channels[ch].is_valid()); +			F->value()->channels[ch]->put_packet(p_buffer, p_buffer_size); +		} +	} +	return OK; +} + +int WebRTCMultiplayer::get_available_packet_count() const { +	if (next_packet_peer == 0) +		return 0; // To be sure next call to get_packet works if size > 0 . +	int size = 0; +	for (Map<int, Ref<ConnectedPeer> >::Element *E = peer_map.front(); E; E = E->next()) { +		for (List<Ref<WebRTCDataChannel> >::Element *F = E->get()->channels.front(); F; F = F->next()) { +			size += F->get()->get_available_packet_count(); +		} +	} +	return size; +} + +int WebRTCMultiplayer::get_max_packet_size() const { +	return 1200; +} + +void WebRTCMultiplayer::close() { +	peer_map.clear(); +	unique_id = 0; +	next_packet_peer = 0; +	target_peer = 0; +	connection_status = CONNECTION_DISCONNECTED; +} + +WebRTCMultiplayer::WebRTCMultiplayer() { +	unique_id = 0; +	next_packet_peer = 0; +	target_peer = 0; +	transfer_mode = TRANSFER_MODE_RELIABLE; +	refuse_connections = false; +	connection_status = CONNECTION_DISCONNECTED; +	server_compat = false; +} + +WebRTCMultiplayer::~WebRTCMultiplayer() { +	close(); +} diff --git a/modules/webrtc/webrtc_multiplayer.h b/modules/webrtc/webrtc_multiplayer.h new file mode 100644 index 0000000000..82bbfd4f68 --- /dev/null +++ b/modules/webrtc/webrtc_multiplayer.h @@ -0,0 +1,116 @@ +/*************************************************************************/ +/*  webrtc_multiplayer.h                                                 */ +/*************************************************************************/ +/*                       This file is part of:                           */ +/*                           GODOT ENGINE                                */ +/*                      https://godotengine.org                          */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur.                 */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md)    */ +/*                                                                       */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the       */ +/* "Software"), to deal in the Software without restriction, including   */ +/* without limitation the rights to use, copy, modify, merge, publish,   */ +/* distribute, sublicense, and/or sell copies of the Software, and to    */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions:                                             */ +/*                                                                       */ +/* The above copyright notice and this permission notice shall be        */ +/* included in all copies or substantial portions of the Software.       */ +/*                                                                       */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */ +/*************************************************************************/ + +#ifndef WEBRTC_MULTIPLAYER_H +#define WEBRTC_MULTIPLAYER_H + +#include "core/io/networked_multiplayer_peer.h" +#include "webrtc_peer_connection.h" + +class WebRTCMultiplayer : public NetworkedMultiplayerPeer { + +	GDCLASS(WebRTCMultiplayer, NetworkedMultiplayerPeer); + +protected: +	static void _bind_methods(); + +private: +	enum { +		CH_RELIABLE = 0, +		CH_ORDERED = 1, +		CH_UNRELIABLE = 2, +		CH_RESERVED_MAX = 3 +	}; + +	class ConnectedPeer : public Reference { + +	public: +		Ref<WebRTCPeerConnection> connection; +		List<Ref<WebRTCDataChannel> > channels; +		bool connected; + +		ConnectedPeer() { +			connected = false; +			for (int i = 0; i < CH_RESERVED_MAX; i++) +				channels.push_front(Ref<WebRTCDataChannel>()); +		} +	}; + +	uint32_t unique_id; +	int target_peer; +	int client_count; +	bool refuse_connections; +	ConnectionStatus connection_status; +	TransferMode transfer_mode; +	int next_packet_peer; +	bool server_compat; + +	Map<int, Ref<ConnectedPeer> > peer_map; + +	void _peer_to_dict(Ref<ConnectedPeer> p_connected_peer, Dictionary &r_dict); +	void _find_next_peer(); + +public: +	WebRTCMultiplayer(); +	~WebRTCMultiplayer(); + +	Error initialize(int p_self_id, bool p_server_compat = false); +	Error add_peer(Ref<WebRTCPeerConnection> p_peer, int p_peer_id, int p_unreliable_lifetime = 1); +	void remove_peer(int p_peer_id); +	bool has_peer(int p_peer_id); +	Dictionary get_peer(int p_peer_id); +	Dictionary get_peers(); +	void close(); + +	// PacketPeer +	Error get_packet(const uint8_t **r_buffer, int &r_buffer_size); ///< buffer is GONE after next get_packet +	Error put_packet(const uint8_t *p_buffer, int p_buffer_size); +	int get_available_packet_count() const; +	int get_max_packet_size() const; + +	// NetworkedMultiplayerPeer +	void set_transfer_mode(TransferMode p_mode); +	TransferMode get_transfer_mode() const; +	void set_target_peer(int p_peer_id); + +	int get_unique_id() const; +	int get_packet_peer() const; + +	bool is_server() const; + +	void poll(); + +	void set_refuse_new_connections(bool p_enable); +	bool is_refusing_new_connections() const; + +	ConnectionStatus get_connection_status() const; +}; + +#endif diff --git a/scene/3d/camera.cpp b/scene/3d/camera.cpp index a00f2173c0..c7d6919a2b 100644 --- a/scene/3d/camera.cpp +++ b/scene/3d/camera.cpp @@ -869,6 +869,11 @@ void ClippedCamera::clear_exceptions() {  	exclude.clear();  } +float ClippedCamera::get_clip_offset() const { + +	return clip_offset; +} +  void ClippedCamera::set_clip_to_areas(bool p_clip) {  	clip_to_areas = p_clip; @@ -912,6 +917,8 @@ void ClippedCamera::_bind_methods() {  	ClassDB::bind_method(D_METHOD("set_clip_to_areas", "enable"), &ClippedCamera::set_clip_to_areas);  	ClassDB::bind_method(D_METHOD("is_clip_to_areas_enabled"), &ClippedCamera::is_clip_to_areas_enabled); +	ClassDB::bind_method(D_METHOD("get_clip_offset"), &ClippedCamera::get_clip_offset); +  	ClassDB::bind_method(D_METHOD("set_clip_to_bodies", "enable"), &ClippedCamera::set_clip_to_bodies);  	ClassDB::bind_method(D_METHOD("is_clip_to_bodies_enabled"), &ClippedCamera::is_clip_to_bodies_enabled); diff --git a/scene/3d/camera.h b/scene/3d/camera.h index 1cd729199d..c0a4f77435 100644 --- a/scene/3d/camera.h +++ b/scene/3d/camera.h @@ -234,6 +234,8 @@ public:  	void remove_exception(const Object *p_object);  	void clear_exceptions(); +	float get_clip_offset() const; +  	ClippedCamera();  	~ClippedCamera();  }; diff --git a/scene/animation/tween.cpp b/scene/animation/tween.cpp index 23998183b8..c70e58564f 100644 --- a/scene/animation/tween.cpp +++ b/scene/animation/tween.cpp @@ -34,10 +34,14 @@  void Tween::_add_pending_command(StringName p_key, const Variant &p_arg1, const Variant &p_arg2, const Variant &p_arg3, const Variant &p_arg4, const Variant &p_arg5, const Variant &p_arg6, const Variant &p_arg7, const Variant &p_arg8, const Variant &p_arg9, const Variant &p_arg10) { +	// Add a new pending command and reference it  	pending_commands.push_back(PendingCommand());  	PendingCommand &cmd = pending_commands.back()->get(); +	// Update the command with the target key  	cmd.key = p_key; + +	// Determine command argument count  	int &count = cmd.args;  	if (p_arg10.get_type() != Variant::NIL)  		count = 10; @@ -59,6 +63,9 @@ void Tween::_add_pending_command(StringName p_key, const Variant &p_arg1, const  		count = 2;  	else if (p_arg1.get_type() != Variant::NIL)  		count = 1; + +	// Add the specified arguments to the command +	// TODO: Make this a switch statement?  	if (count > 0)  		cmd.arg[0] = p_arg1;  	if (count > 1) @@ -83,10 +90,14 @@ void Tween::_add_pending_command(StringName p_key, const Variant &p_arg1, const  void Tween::_process_pending_commands() { +	// For each pending command...  	for (List<PendingCommand>::Element *E = pending_commands.front(); E; E = E->next()) { +		// Get the command  		PendingCommand &cmd = E->get();  		Variant::CallError err; + +		// Grab all of the arguments for the command  		Variant *arg[10] = {  			&cmd.arg[0],  			&cmd.arg[1], @@ -99,16 +110,20 @@ void Tween::_process_pending_commands() {  			&cmd.arg[8],  			&cmd.arg[9],  		}; + +		// Execute the command (and retrieve any errors)  		this->call(cmd.key, (const Variant **)arg, cmd.args, err);  	} + +	// Clear the pending commands  	pending_commands.clear();  }  bool Tween::_set(const StringName &p_name, const Variant &p_value) { +	// Set the correct attribute based on the given name  	String name = p_name; - -	if (name == "playback/speed" || name == "speed") { //bw compatibility +	if (name == "playback/speed" || name == "speed") { // Backwards compatibility  		set_speed_scale(p_value);  	} else if (name == "playback/active") { @@ -122,69 +137,78 @@ bool Tween::_set(const StringName &p_name, const Variant &p_value) {  bool Tween::_get(const StringName &p_name, Variant &r_ret) const { +	// Get the correct attribute based on the given name  	String name = p_name; - -	if (name == "playback/speed") { //bw compatibility - +	if (name == "playback/speed") { // Backwards compatibility  		r_ret = speed_scale; -	} else if (name == "playback/active") { +	} else if (name == "playback/active") {  		r_ret = is_active(); -	} else if (name == "playback/repeat") { +	} else if (name == "playback/repeat") {  		r_ret = is_repeat();  	} -  	return true;  }  void Tween::_get_property_list(List<PropertyInfo> *p_list) const { - +	// Add the property info for the Tween object  	p_list->push_back(PropertyInfo(Variant::BOOL, "playback/active", PROPERTY_HINT_NONE, ""));  	p_list->push_back(PropertyInfo(Variant::BOOL, "playback/repeat", PROPERTY_HINT_NONE, ""));  	p_list->push_back(PropertyInfo(Variant::REAL, "playback/speed", PROPERTY_HINT_RANGE, "-64,64,0.01"));  }  void Tween::_notification(int p_what) { - +	// What notification did we receive?  	switch (p_what) {  		case NOTIFICATION_ENTER_TREE: { - +			// Are we not already active?  			if (!is_active()) { -				//make sure that a previous process state was not saved -				//only process if "processing" is set +				// Make sure that a previous process state was not saved +				// Only process if "processing" is set  				set_physics_process_internal(false);  				set_process_internal(false);  			}  		} break; -		case NOTIFICATION_READY: { +		case NOTIFICATION_READY: { +			// Do nothing  		} break; +  		case NOTIFICATION_INTERNAL_PROCESS: { +			// Are we processing during physics time?  			if (tween_process_mode == TWEEN_PROCESS_PHYSICS) +				// Do nothing since we aren't aligned with physics when we should be  				break; +			// Should we update?  			if (is_active()) +				// Update the tweens  				_tween_process(get_process_delta_time());  		} break; -		case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { +		case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { +			// Are we processing during 'regular' time?  			if (tween_process_mode == TWEEN_PROCESS_IDLE) +				// Do nothing since we whould only process during idle time  				break; +			// Should we update?  			if (is_active()) +				// Update the tweens  				_tween_process(get_physics_process_delta_time());  		} break; -		case NOTIFICATION_EXIT_TREE: { +		case NOTIFICATION_EXIT_TREE: { +			// We've left the tree. Stop all tweens  			stop_all();  		} break;  	}  }  void Tween::_bind_methods() { - +	// Bind getters and setters  	ClassDB::bind_method(D_METHOD("is_active"), &Tween::is_active);  	ClassDB::bind_method(D_METHOD("set_active", "active"), &Tween::set_active); @@ -197,6 +221,7 @@ void Tween::_bind_methods() {  	ClassDB::bind_method(D_METHOD("set_tween_process_mode", "mode"), &Tween::set_tween_process_mode);  	ClassDB::bind_method(D_METHOD("get_tween_process_mode"), &Tween::get_tween_process_mode); +	// Bind the various Tween control methods  	ClassDB::bind_method(D_METHOD("start"), &Tween::start);  	ClassDB::bind_method(D_METHOD("reset", "object", "key"), &Tween::reset, DEFVAL(""));  	ClassDB::bind_method(D_METHOD("reset_all"), &Tween::reset_all); @@ -211,6 +236,7 @@ void Tween::_bind_methods() {  	ClassDB::bind_method(D_METHOD("tell"), &Tween::tell);  	ClassDB::bind_method(D_METHOD("get_runtime"), &Tween::get_runtime); +	// Bind interpolation and follow methods  	ClassDB::bind_method(D_METHOD("interpolate_property", "object", "property", "initial_val", "final_val", "duration", "trans_type", "ease_type", "delay"), &Tween::interpolate_property, DEFVAL(0));  	ClassDB::bind_method(D_METHOD("interpolate_method", "object", "method", "initial_val", "final_val", "duration", "trans_type", "ease_type", "delay"), &Tween::interpolate_method, DEFVAL(0));  	ClassDB::bind_method(D_METHOD("interpolate_callback", "object", "duration", "callback", "arg1", "arg2", "arg3", "arg4", "arg5"), &Tween::interpolate_callback, DEFVAL(Variant()), DEFVAL(Variant()), DEFVAL(Variant()), DEFVAL(Variant()), DEFVAL(Variant())); @@ -220,18 +246,22 @@ void Tween::_bind_methods() {  	ClassDB::bind_method(D_METHOD("targeting_property", "object", "property", "initial", "initial_val", "final_val", "duration", "trans_type", "ease_type", "delay"), &Tween::targeting_property, DEFVAL(0));  	ClassDB::bind_method(D_METHOD("targeting_method", "object", "method", "initial", "initial_method", "final_val", "duration", "trans_type", "ease_type", "delay"), &Tween::targeting_method, DEFVAL(0)); +	// Add the Tween signals  	ADD_SIGNAL(MethodInfo("tween_started", PropertyInfo(Variant::OBJECT, "object"), PropertyInfo(Variant::NODE_PATH, "key")));  	ADD_SIGNAL(MethodInfo("tween_step", PropertyInfo(Variant::OBJECT, "object"), PropertyInfo(Variant::NODE_PATH, "key"), PropertyInfo(Variant::REAL, "elapsed"), PropertyInfo(Variant::OBJECT, "value")));  	ADD_SIGNAL(MethodInfo("tween_completed", PropertyInfo(Variant::OBJECT, "object"), PropertyInfo(Variant::NODE_PATH, "key")));  	ADD_SIGNAL(MethodInfo("tween_all_completed")); +	// Add the properties and tie them to the getters and setters  	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "repeat"), "set_repeat", "is_repeat");  	ADD_PROPERTY(PropertyInfo(Variant::INT, "playback_process_mode", PROPERTY_HINT_ENUM, "Physics,Idle"), "set_tween_process_mode", "get_tween_process_mode");  	ADD_PROPERTY(PropertyInfo(Variant::REAL, "playback_speed", PROPERTY_HINT_RANGE, "-64,64,0.01"), "set_speed_scale", "get_speed_scale"); +	// Bind Idle vs Physics process  	BIND_ENUM_CONSTANT(TWEEN_PROCESS_PHYSICS);  	BIND_ENUM_CONSTANT(TWEEN_PROCESS_IDLE); +	// Bind the Transition type constants  	BIND_ENUM_CONSTANT(TRANS_LINEAR);  	BIND_ENUM_CONSTANT(TRANS_SINE);  	BIND_ENUM_CONSTANT(TRANS_QUINT); @@ -244,6 +274,7 @@ void Tween::_bind_methods() {  	BIND_ENUM_CONSTANT(TRANS_BOUNCE);  	BIND_ENUM_CONSTANT(TRANS_BACK); +	// Bind the easing constants  	BIND_ENUM_CONSTANT(EASE_IN);  	BIND_ENUM_CONSTANT(EASE_OUT);  	BIND_ENUM_CONSTANT(EASE_IN_OUT); @@ -252,27 +283,30 @@ void Tween::_bind_methods() {  Variant &Tween::_get_initial_val(InterpolateData &p_data) { +	// What type of data are we interpolating?  	switch (p_data.type) {  		case INTER_PROPERTY:  		case INTER_METHOD:  		case FOLLOW_PROPERTY:  		case FOLLOW_METHOD: +			// Simply use the given initial value  			return p_data.initial_val;  		case TARGETING_PROPERTY:  		case TARGETING_METHOD: { - +			// Get the object that is being targeted  			Object *object = ObjectDB::get_instance(p_data.target_id);  			ERR_FAIL_COND_V(object == NULL, p_data.initial_val); +			// Are we targeting a property or a method?  			static Variant initial_val;  			if (p_data.type == TARGETING_PROPERTY) { - +				// Get the property from the target object  				bool valid = false;  				initial_val = object->get_indexed(p_data.target_key, &valid);  				ERR_FAIL_COND_V(!valid, p_data.initial_val);  			} else { - +				// Call the method and get the initial value from it  				Variant::CallError error;  				initial_val = object->call(p_data.target_key[0], NULL, 0, error);  				ERR_FAIL_COND_V(error.error != Variant::CallError::CALL_OK, p_data.initial_val); @@ -281,64 +315,75 @@ Variant &Tween::_get_initial_val(InterpolateData &p_data) {  		}  		case INTER_CALLBACK: +			// Callback does not have a special initial value  			break;  	} +	// If we've made it here, just return the delta value as the initial value  	return p_data.delta_val;  }  Variant &Tween::_get_delta_val(InterpolateData &p_data) { +	// What kind of data are we interpolating?  	switch (p_data.type) {  		case INTER_PROPERTY:  		case INTER_METHOD: +			// Simply return the given delta value  			return p_data.delta_val;  		case FOLLOW_PROPERTY:  		case FOLLOW_METHOD: { - +			// We're following an object, so grab that instance  			Object *target = ObjectDB::get_instance(p_data.target_id);  			ERR_FAIL_COND_V(target == NULL, p_data.initial_val); +			// We want to figure out the final value  			Variant final_val; -  			if (p_data.type == FOLLOW_PROPERTY) { - +				// Read the property as-is  				bool valid = false;  				final_val = target->get_indexed(p_data.target_key, &valid);  				ERR_FAIL_COND_V(!valid, p_data.initial_val);  			} else { - +				// We're looking at a method. Call the method on the target object  				Variant::CallError error;  				final_val = target->call(p_data.target_key[0], NULL, 0, error);  				ERR_FAIL_COND_V(error.error != Variant::CallError::CALL_OK, p_data.initial_val);  			} -			// convert INT to REAL is better for interpolaters +			// If we're looking at an INT value, instead convert it to a REAL +			// This is better for interpolation  			if (final_val.get_type() == Variant::INT) final_val = final_val.operator real_t(); + +			// Calculate the delta based on the initial value and the final value  			_calc_delta_val(p_data.initial_val, final_val, p_data.delta_val);  			return p_data.delta_val;  		}  		case TARGETING_PROPERTY:  		case TARGETING_METHOD: { - +			// Grab the initial value from the data to calculate delta  			Variant initial_val = _get_initial_val(p_data); -			// convert INT to REAL is better for interpolaters + +			// If we're looking at an INT value, instead convert it to a REAL +			// This is better for interpolation  			if (initial_val.get_type() == Variant::INT) initial_val = initial_val.operator real_t(); -			//_calc_delta_val(p_data.initial_val, p_data.final_val, p_data.delta_val); +			// Calculate the delta based on the initial value and the final value  			_calc_delta_val(initial_val, p_data.final_val, p_data.delta_val);  			return p_data.delta_val;  		}  		case INTER_CALLBACK: +			// Callbacks have no special delta  			break;  	} +	// If we've made it here, use the initial value as the delta  	return p_data.initial_val;  }  Variant Tween::_run_equation(InterpolateData &p_data) { - +	// Get the initial and delta values from the data  	Variant &initial_val = _get_initial_val(p_data);  	Variant &delta_val = _get_delta_val(p_data);  	Variant result; @@ -346,48 +391,59 @@ Variant Tween::_run_equation(InterpolateData &p_data) {  #define APPLY_EQUATION(element) \  	r.element = _run_equation(p_data.trans_type, p_data.ease_type, p_data.elapsed - p_data.delay, i.element, d.element, p_data.duration); +	// What type of data are we interpolating?  	switch (initial_val.get_type()) {  		case Variant::BOOL: +			// Run the boolean specific equation (checking if it is at least 0.5)  			result = (_run_equation(p_data.trans_type, p_data.ease_type, p_data.elapsed - p_data.delay, initial_val, delta_val, p_data.duration)) >= 0.5;  			break;  		case Variant::INT: +			// Run the integer specific equation  			result = (int)_run_equation(p_data.trans_type, p_data.ease_type, p_data.elapsed - p_data.delay, (int)initial_val, (int)delta_val, p_data.duration);  			break;  		case Variant::REAL: +			// Run the REAL specific equation  			result = _run_equation(p_data.trans_type, p_data.ease_type, p_data.elapsed - p_data.delay, (real_t)initial_val, (real_t)delta_val, p_data.duration);  			break;  		case Variant::VECTOR2: { +			// Get vectors for initial and delta values  			Vector2 i = initial_val;  			Vector2 d = delta_val;  			Vector2 r; +			// Execute the equation and mutate the r vector +			// This uses the custom APPLY_EQUATION macro defined above  			APPLY_EQUATION(x);  			APPLY_EQUATION(y); -  			result = r;  		} break;  		case Variant::VECTOR3: { +			// Get vectors for initial and delta values  			Vector3 i = initial_val;  			Vector3 d = delta_val;  			Vector3 r; +			// Execute the equation and mutate the r vector +			// This uses the custom APPLY_EQUATION macro defined above  			APPLY_EQUATION(x);  			APPLY_EQUATION(y);  			APPLY_EQUATION(z); -  			result = r;  		} break;  		case Variant::BASIS: { +			// Get the basis for initial and delta values  			Basis i = initial_val;  			Basis d = delta_val;  			Basis r; +			// Execute the equation on all the basis and mutate the r basis +			// This uses the custom APPLY_EQUATION macro defined above  			APPLY_EQUATION(elements[0][0]);  			APPLY_EQUATION(elements[0][1]);  			APPLY_EQUATION(elements[0][2]); @@ -397,55 +453,63 @@ Variant Tween::_run_equation(InterpolateData &p_data) {  			APPLY_EQUATION(elements[2][0]);  			APPLY_EQUATION(elements[2][1]);  			APPLY_EQUATION(elements[2][2]); -  			result = r;  		} break;  		case Variant::TRANSFORM2D: { +			// Get the transforms for initial and delta values  			Transform2D i = initial_val;  			Transform2D d = delta_val;  			Transform2D r; +			// Execute the equation on the transforms and mutate the r transform +			// This uses the custom APPLY_EQUATION macro defined above  			APPLY_EQUATION(elements[0][0]);  			APPLY_EQUATION(elements[0][1]);  			APPLY_EQUATION(elements[1][0]);  			APPLY_EQUATION(elements[1][1]);  			APPLY_EQUATION(elements[2][0]);  			APPLY_EQUATION(elements[2][1]); -  			result = r;  		} break;  		case Variant::QUAT: { +			// Get the quaternian for the initial and delta values  			Quat i = initial_val;  			Quat d = delta_val;  			Quat r; +			// Execute the equation on the quaternian values and mutate the r quaternian +			// This uses the custom APPLY_EQUATION macro defined above  			APPLY_EQUATION(x);  			APPLY_EQUATION(y);  			APPLY_EQUATION(z);  			APPLY_EQUATION(w); -  			result = r;  		} break;  		case Variant::AABB: { +			// Get the AABB's for the initial and delta values  			AABB i = initial_val;  			AABB d = delta_val;  			AABB r; +			// Execute the equation for the position and size of the AABB's and mutate the r AABB +			// This uses the custom APPLY_EQUATION macro defined above  			APPLY_EQUATION(position.x);  			APPLY_EQUATION(position.y);  			APPLY_EQUATION(position.z);  			APPLY_EQUATION(size.x);  			APPLY_EQUATION(size.y);  			APPLY_EQUATION(size.z); -  			result = r;  		} break;  		case Variant::TRANSFORM: { +			// Get the transforms for the initial and delta values  			Transform i = initial_val;  			Transform d = delta_val;  			Transform r; +			// Execute the equation for each of the transforms and their origin and mutate the r transform +			// This uses the custom APPLY_EQUATION macro defined above  			APPLY_EQUATION(basis.elements[0][0]);  			APPLY_EQUATION(basis.elements[0][1]);  			APPLY_EQUATION(basis.elements[0][2]); @@ -458,40 +522,45 @@ Variant Tween::_run_equation(InterpolateData &p_data) {  			APPLY_EQUATION(origin.x);  			APPLY_EQUATION(origin.y);  			APPLY_EQUATION(origin.z); -  			result = r;  		} break;  		case Variant::COLOR: { +			// Get the Color for initial and delta value  			Color i = initial_val;  			Color d = delta_val;  			Color r; +			// Apply the equation on the Color RGBA, and mutate the r color +			// This uses the custom APPLY_EQUATION macro defined above  			APPLY_EQUATION(r);  			APPLY_EQUATION(g);  			APPLY_EQUATION(b);  			APPLY_EQUATION(a); -  			result = r;  		} break;  		default: { +			// If unknown, just return the initial value  			result = initial_val;  		} break;  	};  #undef APPLY_EQUATION - +	// Return the result that was computed  	return result;  }  bool Tween::_apply_tween_value(InterpolateData &p_data, Variant &value) { +	// Get the object we want to apply the new value to  	Object *object = ObjectDB::get_instance(p_data.id);  	ERR_FAIL_COND_V(object == NULL, false); +	// What kind of data are we mutating?  	switch (p_data.type) {  		case INTER_PROPERTY:  		case FOLLOW_PROPERTY:  		case TARGETING_PROPERTY: { +			// Simply set the property on the object  			bool valid = false;  			object->set_indexed(p_data.key, value, &valid);  			return valid; @@ -500,85 +569,112 @@ bool Tween::_apply_tween_value(InterpolateData &p_data, Variant &value) {  		case INTER_METHOD:  		case FOLLOW_METHOD:  		case TARGETING_METHOD: { +			// We want to call the method on the target object  			Variant::CallError error; + +			// Do we have a non-nil value passed in?  			if (value.get_type() != Variant::NIL) { +				// Pass it as an argument to the function call  				Variant *arg[1] = { &value };  				object->call(p_data.key[0], (const Variant **)arg, 1, error);  			} else { +				// Don't pass any argument  				object->call(p_data.key[0], NULL, 0, error);  			} +			// Did we get an error from the function call?  			if (error.error == Variant::CallError::CALL_OK)  				return true;  			return false;  		}  		case INTER_CALLBACK: +			// Nothing to apply for a callback  			break;  	}; +	// No issues found!  	return true;  }  void Tween::_tween_process(float p_delta) { - +	// Process all of the pending commands  	_process_pending_commands(); +	// If the scale is 0, make no progress on the tweens  	if (speed_scale == 0)  		return; -	p_delta *= speed_scale; +	// Update the delta and whether we are pending an update +	p_delta *= speed_scale;  	pending_update++; -	// if repeat and all interpolates was finished then reset all interpolates -	bool all_finished = true; -	if (repeat) { +	// Are we repeating the interpolations? +	if (repeat) { +		// For each interpolation... +		bool repeats_finished = true;  		for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) { - +			// Get the data from it  			InterpolateData &data = E->get(); +			// Is not finished?  			if (!data.finish) { -				all_finished = false; +				// We aren't finished yet, no need to check the rest +				repeats_finished = false;  				break;  			}  		} -		if (all_finished) +		// If we are all finished, we can reset all of the tweens +		if (repeats_finished)  			reset_all();  	} -	all_finished = true; +	// Are all of the tweens complete? +	bool all_finished = true; + +	// For each tween we wish to interpolate...  	for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) { +		// Get the data from it  		InterpolateData &data = E->get(); + +		// Track if we hit one that isn't finished yet  		all_finished = all_finished && data.finish; +		// Is the data not active or already finished? No need to go any further  		if (!data.active || data.finish)  			continue; +		// Get the target object for this interpolation  		Object *object = ObjectDB::get_instance(data.id);  		if (object == NULL)  			continue; +		// Are we still delaying this tween?  		bool prev_delaying = data.elapsed <= data.delay;  		data.elapsed += p_delta;  		if (data.elapsed < data.delay)  			continue;  		else if (prev_delaying) { - +			// We can apply the tween's value to the data and emit that the tween has started  			_apply_tween_value(data, data.initial_val);  			emit_signal("tween_started", object, NodePath(Vector<StringName>(), data.key, false));  		} +		// Are we at the end of the tween?  		if (data.elapsed > (data.delay + data.duration)) { - +			// Set the elapsed time to the end and mark this one as finished  			data.elapsed = data.delay + data.duration;  			data.finish = true;  		} +		// Are we interpolating a callback?  		if (data.type == INTER_CALLBACK) { +			// Is the tween completed?  			if (data.finish) { +				// Are we calling this callback deferred or immediately?  				if (data.call_deferred) { - +					// Run the deferred function callback, applying the correct number of arguments  					switch (data.args) {  						case 0:  							object->call_deferred(data.key[0]); @@ -600,6 +696,7 @@ void Tween::_tween_process(float p_delta) {  							break;  					}  				} else { +					// Call the function directly with the arguments  					Variant::CallError error;  					Variant *arg[5] = {  						&data.arg[0], @@ -612,23 +709,35 @@ void Tween::_tween_process(float p_delta) {  				}  			}  		} else { +			// We can apply the value directly  			Variant result = _run_equation(data);  			_apply_tween_value(data, result); + +			// Emit that the tween has taken a step  			emit_signal("tween_step", object, NodePath(Vector<StringName>(), data.key, false), data.elapsed, result);  		} +		// Is the tween now finished?  		if (data.finish) { +			// Set it to the final value directly  			_apply_tween_value(data, data.final_val); + +			// Mark the tween as completed and emit the signal  			data.elapsed = 0;  			emit_signal("tween_completed", object, NodePath(Vector<StringName>(), data.key, false)); -			// not repeat mode, remove completed action + +			// If we are not repeating the tween, remove it  			if (!repeat)  				call_deferred("_remove_by_uid", data.uid); -		} else if (!repeat) +		} else if (!repeat) { +			// Check whether all tweens are finished  			all_finished = all_finished && data.finish; +		}  	} +	// One less update left to go  	pending_update--; +	// If all tweens are completed, we no longer need to be active  	if (all_finished) {  		set_active(false);  		emit_signal("tween_all_completed"); @@ -636,76 +745,75 @@ void Tween::_tween_process(float p_delta) {  }  void Tween::set_tween_process_mode(TweenProcessMode p_mode) { -  	tween_process_mode = p_mode;  }  Tween::TweenProcessMode Tween::get_tween_process_mode() const { -  	return tween_process_mode;  }  bool Tween::is_active() const { -  	return is_processing_internal() || is_physics_processing_internal();  }  void Tween::set_active(bool p_active) { - +	// Do nothing if it's the same active mode that we currently are  	if (is_active() == p_active)  		return; +	// Depending on physics or idle, set processing  	switch (tween_process_mode) { -  		case TWEEN_PROCESS_IDLE: set_process_internal(p_active); break;  		case TWEEN_PROCESS_PHYSICS: set_physics_process_internal(p_active); break;  	}  }  bool Tween::is_repeat() const { -  	return repeat;  }  void Tween::set_repeat(bool p_repeat) { -  	repeat = p_repeat;  }  void Tween::set_speed_scale(float p_speed) { -  	speed_scale = p_speed;  }  float Tween::get_speed_scale() const { -  	return speed_scale;  }  bool Tween::start() { +	// Are there any pending updates?  	if (pending_update != 0) { +		// Start the tweens after deferring  		call_deferred("start");  		return true;  	} +	// We want to be activated  	set_active(true);  	return true;  }  bool Tween::reset(Object *p_object, StringName p_key) { - +	// Find all interpolations that use the same object and target string  	pending_update++;  	for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) { - +		// Get the target object  		InterpolateData &data = E->get();  		Object *object = ObjectDB::get_instance(data.id);  		if (object == NULL)  			continue; +		// Do we have the correct object and key?  		if (object == p_object && (data.concatenated_key == p_key || p_key == "")) { - +			// Reset the tween to the initial state  			data.elapsed = 0;  			data.finish = false; + +			// Also apply the initial state if there isn't a delay  			if (data.delay == 0)  				_apply_tween_value(data, data.initial_val);  		} @@ -715,13 +823,15 @@ bool Tween::reset(Object *p_object, StringName p_key) {  }  bool Tween::reset_all() { - +	// Go through all interpolations  	pending_update++;  	for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) { - +		// Get the target data and set it back to the initial state  		InterpolateData &data = E->get();  		data.elapsed = 0;  		data.finish = false; + +		// If there isn't a delay, apply the value to the object  		if (data.delay == 0)  			_apply_tween_value(data, data.initial_val);  	} @@ -730,15 +840,19 @@ bool Tween::reset_all() {  }  bool Tween::stop(Object *p_object, StringName p_key) { - +	// Find the tween that has the given target object and string key  	pending_update++;  	for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) { +		// Get the object the tween is targeting  		InterpolateData &data = E->get();  		Object *object = ObjectDB::get_instance(data.id);  		if (object == NULL)  			continue; + +		// Is this the correct object and does it have the given key?  		if (object == p_object && (data.concatenated_key == p_key || p_key == "")) +			// Disable the tween  			data.active = false;  	}  	pending_update--; @@ -746,12 +860,13 @@ bool Tween::stop(Object *p_object, StringName p_key) {  }  bool Tween::stop_all() { - +	// We no longer need to be active since all tweens have been stopped  	set_active(false); +	// For each interpolation...  	pending_update++;  	for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) { - +		// Simply set it inactive  		InterpolateData &data = E->get();  		data.active = false;  	} @@ -760,16 +875,20 @@ bool Tween::stop_all() {  }  bool Tween::resume(Object *p_object, StringName p_key) { - +	// We need to be activated +	// TODO: What if no tween is found??  	set_active(true); +	// Find the tween that uses the given target object and string key  	pending_update++;  	for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) { - +		// Grab the object  		InterpolateData &data = E->get();  		Object *object = ObjectDB::get_instance(data.id);  		if (object == NULL)  			continue; + +		// If the object and string key match, activate it  		if (object == p_object && (data.concatenated_key == p_key || p_key == ""))  			data.active = true;  	} @@ -778,12 +897,14 @@ bool Tween::resume(Object *p_object, StringName p_key) {  }  bool Tween::resume_all() { - +	// Set ourselves active so we can process tweens +	// TODO: What if there are no tweens? We get set to active for no reason!  	set_active(true); +	// For each interpolation...  	pending_update++;  	for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) { - +		// Simply grab it and set it to active  		InterpolateData &data = E->get();  		data.active = true;  	} @@ -792,35 +913,46 @@ bool Tween::resume_all() {  }  bool Tween::remove(Object *p_object, StringName p_key) { +	// If we are still updating, call this function again later  	if (pending_update != 0) {  		call_deferred("remove", p_object, p_key);  		return true;  	} + +	// For each interpolation...  	List<List<InterpolateData>::Element *> for_removal;  	for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) { - +		// Get the target object  		InterpolateData &data = E->get();  		Object *object = ObjectDB::get_instance(data.id);  		if (object == NULL)  			continue; + +		// If the target object and string key match, queue it for removal  		if (object == p_object && (data.concatenated_key == p_key || p_key == "")) {  			for_removal.push_back(E);  		}  	} + +	// For each interpolation we wish to remove...  	for (List<List<InterpolateData>::Element *>::Element *E = for_removal.front(); E; E = E->next()) { +		// Erase it  		interpolates.erase(E->get());  	}  	return true;  }  void Tween::_remove_by_uid(int uid) { +	// If we are still updating, call this function again later  	if (pending_update != 0) {  		call_deferred("_remove_by_uid", uid);  		return;  	} +	// Find the interpolation that matches the given UID  	for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) {  		if (uid == E->get().uid) { +			// It matches, erase it and stop looking  			E->erase();  			break;  		} @@ -829,49 +961,61 @@ void Tween::_remove_by_uid(int uid) {  void Tween::_push_interpolate_data(InterpolateData &p_data) {  	pending_update++; + +	// Add the new interpolation  	p_data.uid = ++uid;  	interpolates.push_back(p_data); +  	pending_update--;  }  bool Tween::remove_all() { - +	// If we are still updating, call this function again later  	if (pending_update != 0) {  		call_deferred("remove_all");  		return true;  	} +	// We no longer need to be active  	set_active(false); + +	// Clear out all interpolations and reset the uid  	interpolates.clear();  	uid = 0; +  	return true;  }  bool Tween::seek(real_t p_time) { - +	// Go through each interpolation...  	pending_update++;  	for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) { - +		// Get the target data  		InterpolateData &data = E->get(); +		// Update the elapsed data to be set to the target time  		data.elapsed = p_time; -		if (data.elapsed < data.delay) { +		// Are we at the end? +		if (data.elapsed < data.delay) { +			// There is still time left to go  			data.finish = false;  			continue;  		} else if (data.elapsed >= (data.delay + data.duration)) { - -			data.finish = true; +			// We are past the end of it, set the elapsed time to the end and mark as finished  			data.elapsed = (data.delay + data.duration); +			data.finish = true;  		} else { +			// We are not finished with this interpolation yet  			data.finish = false;  		} +		// If we are a callback, do nothing special  		if (data.type == INTER_CALLBACK) {  			continue;  		} +		// Run the equation on the data and apply the value  		Variant result = _run_equation(data); -  		_apply_tween_value(data, result);  	}  	pending_update--; @@ -879,13 +1023,16 @@ bool Tween::seek(real_t p_time) {  }  real_t Tween::tell() const { - +	// We want to grab the position of the furthest along tween  	pending_update++;  	real_t pos = 0; -	for (const List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) { +	// For each interpolation... +	for (const List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) { +		// Get the data and figure out if it's position is further along than the previous ones  		const InterpolateData &data = E->get();  		if (data.elapsed > pos) +			// Save it if so  			pos = data.elapsed;  	}  	pending_update--; @@ -893,55 +1040,63 @@ real_t Tween::tell() const {  }  real_t Tween::get_runtime() const { - +	// If the tween isn't moving, it'll last forever  	if (speed_scale == 0) {  		return INFINITY;  	}  	pending_update++; + +	// For each interpolation...  	real_t runtime = 0;  	for (const List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) { - +		// Get the tween data and see if it's runtime is greater than the previous tweens  		const InterpolateData &data = E->get();  		real_t t = data.delay + data.duration;  		if (t > runtime) +			// This is the longest running tween  			runtime = t;  	}  	pending_update--; +	// Adjust the runtime for the current speed scale  	return runtime / speed_scale;  }  bool Tween::_calc_delta_val(const Variant &p_initial_val, const Variant &p_final_val, Variant &p_delta_val) { +	// Get the initial, final, and delta values  	const Variant &initial_val = p_initial_val;  	const Variant &final_val = p_final_val;  	Variant &delta_val = p_delta_val; +	// What kind of data are we interpolating?  	switch (initial_val.get_type()) {  		case Variant::BOOL: -			//delta_val = p_final_val; -			delta_val = (int)p_final_val - (int)p_initial_val; -			break; - +			// We'll treat booleans just like integers  		case Variant::INT: +			// Compute the integer delta  			delta_val = (int)final_val - (int)initial_val;  			break;  		case Variant::REAL: +			// Convert to REAL and find the delta  			delta_val = (real_t)final_val - (real_t)initial_val;  			break;  		case Variant::VECTOR2: +			// Convert to Vectors and find the delta  			delta_val = final_val.operator Vector2() - initial_val.operator Vector2();  			break;  		case Variant::VECTOR3: +			// Convert to Vectors and find the delta  			delta_val = final_val.operator Vector3() - initial_val.operator Vector3();  			break;  		case Variant::BASIS: { +			// Build a new basis which is the delta between the initial and final values  			Basis i = initial_val;  			Basis f = final_val;  			delta_val = Basis(f.elements[0][0] - i.elements[0][0], @@ -956,6 +1111,7 @@ bool Tween::_calc_delta_val(const Variant &p_initial_val, const Variant &p_final  		} break;  		case Variant::TRANSFORM2D: { +			// Build a new transform which is the difference between the initial and final values  			Transform2D i = initial_val;  			Transform2D f = final_val;  			Transform2D d = Transform2D(); @@ -967,15 +1123,21 @@ bool Tween::_calc_delta_val(const Variant &p_initial_val, const Variant &p_final  			d[2][1] = f.elements[2][1] - i.elements[2][1];  			delta_val = d;  		} break; +  		case Variant::QUAT: +			// Convert to quaternianls and find the delta  			delta_val = final_val.operator Quat() - initial_val.operator Quat();  			break; +  		case Variant::AABB: { +			// Build a new AABB and use the new position and sizes to make a delta  			AABB i = initial_val;  			AABB f = final_val;  			delta_val = AABB(f.position - i.position, f.size - i.size);  		} break; +  		case Variant::TRANSFORM: { +			// Build a new transform which is the difference between the initial and final values  			Transform i = initial_val;  			Transform f = final_val;  			Transform d; @@ -994,124 +1156,157 @@ bool Tween::_calc_delta_val(const Variant &p_initial_val, const Variant &p_final  			delta_val = d;  		} break; +  		case Variant::COLOR: { +			// Make a new color which is the difference between each the color's RGBA attributes  			Color i = initial_val;  			Color f = final_val;  			delta_val = Color(f.r - i.r, f.g - i.g, f.b - i.b, f.a - i.a);  		} break;  		default: +			// TODO: Should move away from a 'magic string'?  			ERR_PRINT("Invalid param type, except(int/real/vector2/vector/matrix/matrix32/quat/aabb/transform/color)");  			return false;  	};  	return true;  } -bool Tween::interpolate_property(Object *p_object, NodePath p_property, Variant p_initial_val, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) { -	if (pending_update != 0) { -		_add_pending_command("interpolate_property", p_object, p_property, p_initial_val, p_final_val, p_duration, p_trans_type, p_ease_type, p_delay); -		return true; -	} -	p_property = p_property.get_as_property_path(); - -	if (p_initial_val.get_type() == Variant::NIL) p_initial_val = p_object->get_indexed(p_property.get_subnames()); - -	// convert INT to REAL is better for interpolaters -	if (p_initial_val.get_type() == Variant::INT) p_initial_val = p_initial_val.operator real_t(); -	if (p_final_val.get_type() == Variant::INT) p_final_val = p_final_val.operator real_t(); - -	ERR_FAIL_COND_V(p_object == NULL, false); -	ERR_FAIL_COND_V(!ObjectDB::instance_validate(p_object), false); -	ERR_FAIL_COND_V(p_initial_val.get_type() != p_final_val.get_type(), false); -	ERR_FAIL_COND_V(p_duration <= 0, false); -	ERR_FAIL_COND_V(p_trans_type < 0 || p_trans_type >= TRANS_COUNT, false); -	ERR_FAIL_COND_V(p_ease_type < 0 || p_ease_type >= EASE_COUNT, false); -	ERR_FAIL_COND_V(p_delay < 0, false); +bool Tween::_build_interpolation(InterpolateType p_interpolation_type, Object *p_object, NodePath *p_property, StringName *p_method, Variant p_initial_val, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) { -	bool prop_valid = false; -	p_object->get_indexed(p_property.get_subnames(), &prop_valid); -	ERR_FAIL_COND_V(!prop_valid, false); +	// TODO: Add initialization+implementation for remaining interpolation types +	// TODO: Fix this method's organization to take advantage of the type +	// Make a new interpolation data  	InterpolateData data;  	data.active = true; -	data.type = INTER_PROPERTY; +	data.type = p_interpolation_type;  	data.finish = false;  	data.elapsed = 0; +	// Validate and apply interpolation data + +	// Give it the object +	ERR_EXPLAIN("Invalid object provided to Tween!"); +	ERR_FAIL_COND_V(p_object == NULL, false); // Is the object real +	ERR_FAIL_COND_V(!ObjectDB::instance_validate(p_object), false); // Is the object a valid instance?  	data.id = p_object->get_instance_id(); -	data.key = p_property.get_subnames(); -	data.concatenated_key = p_property.get_concatenated_subnames(); + +	// Validate the initial and final values +	ERR_EXPLAIN("Initial value type does not match final value type!"); // TODO: Print both types to make debugging easier +	ERR_FAIL_COND_V(p_initial_val.get_type() != p_final_val.get_type(), false); // Do the initial and final value types match?  	data.initial_val = p_initial_val;  	data.final_val = p_final_val; + +	// Check the Duration +	ERR_EXPLAIN("Only non-negative duration values allowed in Tweens!"); +	ERR_FAIL_COND_V(p_duration < 0, false); // Is the tween duration non-negative  	data.duration = p_duration; + +	// Tween Delay +	ERR_EXPLAIN("Only non-negative delay values allowed in Tweens!"); +	ERR_FAIL_COND_V(p_delay < 0, false); // Is the delay non-negative? +	data.delay = p_delay; + +	// Transition type +	ERR_EXPLAIN("Invalid transition type provided to Tween"); +	ERR_FAIL_COND_V(p_trans_type < 0 || p_trans_type >= TRANS_COUNT, false); // Is the transition type valid  	data.trans_type = p_trans_type; + +	// Easing type +	ERR_EXPLAIN("Invalid easing type provided to Tween"); +	ERR_FAIL_COND_V(p_ease_type < 0 || p_ease_type >= EASE_COUNT, false); // Is the easing type valid  	data.ease_type = p_ease_type; -	data.delay = p_delay; +	// Is the property defined? +	if (p_property) { +		// Check that the object actually contains the given property +		bool prop_valid = false; +		p_object->get_indexed(p_property->get_subnames(), &prop_valid); +		ERR_EXPLAIN("Tween target object has no property named: " + p_property->get_concatenated_subnames()); +		ERR_FAIL_COND_V(!prop_valid, false); + +		data.key = p_property->get_subnames(); +		data.concatenated_key = p_property->get_concatenated_subnames(); +	} + +	// Is the method defined? +	if (p_method) { +		// Does the object even have the requested method? +		ERR_EXPLAIN("Tween target object has no method named: " + *p_method); // TODO: Fix this error message +		ERR_FAIL_COND_V(!p_object->has_method(*p_method), false); + +		data.key.push_back(*p_method); +		data.concatenated_key = *p_method; +	} + +	// Is there not a valid delta?  	if (!_calc_delta_val(data.initial_val, data.final_val, data.delta_val))  		return false; +	// Add this interpolation to the total  	_push_interpolate_data(data);  	return true;  } -bool Tween::interpolate_method(Object *p_object, StringName p_method, Variant p_initial_val, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) { +bool Tween::interpolate_property(Object *p_object, NodePath p_property, Variant p_initial_val, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) { +	// If we are busy updating, call this function again later  	if (pending_update != 0) { -		_add_pending_command("interpolate_method", p_object, p_method, p_initial_val, p_final_val, p_duration, p_trans_type, p_ease_type, p_delay); +		_add_pending_command("interpolate_property", p_object, p_property, p_initial_val, p_final_val, p_duration, p_trans_type, p_ease_type, p_delay);  		return true;  	} -	// convert INT to REAL is better for interpolaters -	if (p_initial_val.get_type() == Variant::INT) p_initial_val = p_initial_val.operator real_t(); -	if (p_final_val.get_type() == Variant::INT) p_final_val = p_final_val.operator real_t(); -	ERR_FAIL_COND_V(p_object == NULL, false); -	ERR_FAIL_COND_V(!ObjectDB::instance_validate(p_object), false); -	ERR_FAIL_COND_V(p_initial_val.get_type() != p_final_val.get_type(), false); -	ERR_FAIL_COND_V(p_duration <= 0, false); -	ERR_FAIL_COND_V(p_trans_type < 0 || p_trans_type >= TRANS_COUNT, false); -	ERR_FAIL_COND_V(p_ease_type < 0 || p_ease_type >= EASE_COUNT, false); -	ERR_FAIL_COND_V(p_delay < 0, false); +	// Get the property from the node path +	p_property = p_property.get_as_property_path(); -	ERR_EXPLAIN("Object has no method named: %s" + p_method); -	ERR_FAIL_COND_V(!p_object->has_method(p_method), false); +	// If no initial value given, grab the initial value from the object +	// TODO: Is this documented? This is very useful and removes a lot of clutter from tweens! +	if (p_initial_val.get_type() == Variant::NIL) p_initial_val = p_object->get_indexed(p_property.get_subnames()); -	InterpolateData data; -	data.active = true; -	data.type = INTER_METHOD; -	data.finish = false; -	data.elapsed = 0; +	// Convert any integers into REALs as they are better for interpolation +	if (p_initial_val.get_type() == Variant::INT) p_initial_val = p_initial_val.operator real_t(); +	if (p_final_val.get_type() == Variant::INT) p_final_val = p_final_val.operator real_t(); -	data.id = p_object->get_instance_id(); -	data.key.push_back(p_method); -	data.concatenated_key = p_method; -	data.initial_val = p_initial_val; -	data.final_val = p_final_val; -	data.duration = p_duration; -	data.trans_type = p_trans_type; -	data.ease_type = p_ease_type; -	data.delay = p_delay; +	// Build the interpolation data +	bool result = _build_interpolation(INTER_PROPERTY, p_object, &p_property, NULL, p_initial_val, p_final_val, p_duration, p_trans_type, p_ease_type, p_delay); +	return result; +} -	if (!_calc_delta_val(data.initial_val, data.final_val, data.delta_val)) -		return false; +bool Tween::interpolate_method(Object *p_object, StringName p_method, Variant p_initial_val, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) { +	// If we are busy updating, call this function again later +	if (pending_update != 0) { +		_add_pending_command("interpolate_method", p_object, p_method, p_initial_val, p_final_val, p_duration, p_trans_type, p_ease_type, p_delay); +		return true; +	} -	_push_interpolate_data(data); -	return true; +	// Convert any integers into REALs as they are better for interpolation +	if (p_initial_val.get_type() == Variant::INT) p_initial_val = p_initial_val.operator real_t(); +	if (p_final_val.get_type() == Variant::INT) p_final_val = p_final_val.operator real_t(); + +	// Build the interpolation data +	bool result = _build_interpolation(INTER_METHOD, p_object, NULL, &p_method, p_initial_val, p_final_val, p_duration, p_trans_type, p_ease_type, p_delay); +	return result;  }  bool Tween::interpolate_callback(Object *p_object, real_t p_duration, String p_callback, VARIANT_ARG_DECLARE) { - +	// If we are already updating, call this function again later  	if (pending_update != 0) {  		_add_pending_command("interpolate_callback", p_object, p_duration, p_callback, p_arg1, p_arg2, p_arg3, p_arg4, p_arg5);  		return true;  	} +	// Check that the target object is valid  	ERR_FAIL_COND_V(p_object == NULL, false);  	ERR_FAIL_COND_V(!ObjectDB::instance_validate(p_object), false); + +	// Duration cannot be negative  	ERR_FAIL_COND_V(p_duration < 0, false); +	// Check whether the object even has the callback  	ERR_EXPLAIN("Object has no callback named: %s" + p_callback);  	ERR_FAIL_COND_V(!p_object->has_method(p_callback), false); +	// Build a new InterpolationData  	InterpolateData data;  	data.active = true;  	data.type = INTER_CALLBACK; @@ -1119,12 +1314,14 @@ bool Tween::interpolate_callback(Object *p_object, real_t p_duration, String p_c  	data.call_deferred = false;  	data.elapsed = 0; +	// Give the data it's configuration  	data.id = p_object->get_instance_id();  	data.key.push_back(p_callback);  	data.concatenated_key = p_callback;  	data.duration = p_duration;  	data.delay = 0; +	// Add arguments to the interpolation  	int args = 0;  	if (p_arg5.get_type() != Variant::NIL)  		args = 5; @@ -1146,23 +1343,30 @@ bool Tween::interpolate_callback(Object *p_object, real_t p_duration, String p_c  	data.arg[3] = p_arg4;  	data.arg[4] = p_arg5; +	// Add the new interpolation  	_push_interpolate_data(data);  	return true;  }  bool Tween::interpolate_deferred_callback(Object *p_object, real_t p_duration, String p_callback, VARIANT_ARG_DECLARE) { - +	// If we are already updating, call this function again later  	if (pending_update != 0) {  		_add_pending_command("interpolate_deferred_callback", p_object, p_duration, p_callback, p_arg1, p_arg2, p_arg3, p_arg4, p_arg5);  		return true;  	} + +	// Check that the target object is valid  	ERR_FAIL_COND_V(p_object == NULL, false);  	ERR_FAIL_COND_V(!ObjectDB::instance_validate(p_object), false); + +	// No negative durations allowed  	ERR_FAIL_COND_V(p_duration < 0, false); +	// Confirm the callback exists on the object  	ERR_EXPLAIN("Object has no callback named: %s" + p_callback);  	ERR_FAIL_COND_V(!p_object->has_method(p_callback), false); +	// Create a new InterpolateData for the callback  	InterpolateData data;  	data.active = true;  	data.type = INTER_CALLBACK; @@ -1170,12 +1374,14 @@ bool Tween::interpolate_deferred_callback(Object *p_object, real_t p_duration, S  	data.call_deferred = true;  	data.elapsed = 0; +	// Give the data it's configuration  	data.id = p_object->get_instance_id();  	data.key.push_back(p_callback);  	data.concatenated_key = p_callback;  	data.duration = p_duration;  	data.delay = 0; +	// Collect arguments for the callback  	int args = 0;  	if (p_arg5.get_type() != Variant::NIL)  		args = 5; @@ -1197,32 +1403,46 @@ bool Tween::interpolate_deferred_callback(Object *p_object, real_t p_duration, S  	data.arg[3] = p_arg4;  	data.arg[4] = p_arg5; +	// Add the new interpolation  	_push_interpolate_data(data);  	return true;  }  bool Tween::follow_property(Object *p_object, NodePath p_property, Variant p_initial_val, Object *p_target, NodePath p_target_property, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) { +	// If we are already updating, call this function again later  	if (pending_update != 0) {  		_add_pending_command("follow_property", p_object, p_property, p_initial_val, p_target, p_target_property, p_duration, p_trans_type, p_ease_type, p_delay);  		return true;  	} + +	// Get the two properties from their paths  	p_property = p_property.get_as_property_path();  	p_target_property = p_target_property.get_as_property_path(); +	// If no initial value is given, grab it from the source object +	// TODO: Is this documented? It's really helpful for decluttering tweens  	if (p_initial_val.get_type() == Variant::NIL) p_initial_val = p_object->get_indexed(p_property.get_subnames()); -	// convert INT to REAL is better for interpolaters +	// Convert initial INT values to REAL as they are better for interpolation  	if (p_initial_val.get_type() == Variant::INT) p_initial_val = p_initial_val.operator real_t(); +	// Confirm the source and target objects are valid  	ERR_FAIL_COND_V(p_object == NULL, false);  	ERR_FAIL_COND_V(!ObjectDB::instance_validate(p_object), false);  	ERR_FAIL_COND_V(p_target == NULL, false);  	ERR_FAIL_COND_V(!ObjectDB::instance_validate(p_target), false); -	ERR_FAIL_COND_V(p_duration <= 0, false); + +	// No negative durations +	ERR_FAIL_COND_V(p_duration < 0, false); + +	// Ensure transition and easing types are valid  	ERR_FAIL_COND_V(p_trans_type < 0 || p_trans_type >= TRANS_COUNT, false);  	ERR_FAIL_COND_V(p_ease_type < 0 || p_ease_type >= EASE_COUNT, false); + +	// No negative delays  	ERR_FAIL_COND_V(p_delay < 0, false); +	// Confirm the source and target objects have the desired properties  	bool prop_valid = false;  	p_object->get_indexed(p_property.get_subnames(), &prop_valid);  	ERR_FAIL_COND_V(!prop_valid, false); @@ -1231,16 +1451,20 @@ bool Tween::follow_property(Object *p_object, NodePath p_property, Variant p_ini  	Variant target_val = p_target->get_indexed(p_target_property.get_subnames(), &target_prop_valid);  	ERR_FAIL_COND_V(!target_prop_valid, false); -	// convert INT to REAL is better for interpolaters +	// Convert target INT to REAL since it is better for interpolation  	if (target_val.get_type() == Variant::INT) target_val = target_val.operator real_t(); + +	// Verify that the target value and initial value are the same type  	ERR_FAIL_COND_V(target_val.get_type() != p_initial_val.get_type(), false); +	// Create a new InterpolateData  	InterpolateData data;  	data.active = true;  	data.type = FOLLOW_PROPERTY;  	data.finish = false;  	data.elapsed = 0; +	// Give the InterpolateData it's configuration  	data.id = p_object->get_instance_id();  	data.key = p_property.get_subnames();  	data.concatenated_key = p_property.get_concatenated_subnames(); @@ -1252,46 +1476,59 @@ bool Tween::follow_property(Object *p_object, NodePath p_property, Variant p_ini  	data.ease_type = p_ease_type;  	data.delay = p_delay; +	// Add the interpolation  	_push_interpolate_data(data);  	return true;  }  bool Tween::follow_method(Object *p_object, StringName p_method, Variant p_initial_val, Object *p_target, StringName p_target_method, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) { +	// If we are currently updating, call this function again later  	if (pending_update != 0) {  		_add_pending_command("follow_method", p_object, p_method, p_initial_val, p_target, p_target_method, p_duration, p_trans_type, p_ease_type, p_delay);  		return true;  	} -	// convert INT to REAL is better for interpolaters +	// Convert initial INT values to REAL as they are better for interpolation  	if (p_initial_val.get_type() == Variant::INT) p_initial_val = p_initial_val.operator real_t(); +	// Verify the source and target objects are valid  	ERR_FAIL_COND_V(p_object == NULL, false);  	ERR_FAIL_COND_V(!ObjectDB::instance_validate(p_object), false);  	ERR_FAIL_COND_V(p_target == NULL, false);  	ERR_FAIL_COND_V(!ObjectDB::instance_validate(p_target), false); -	ERR_FAIL_COND_V(p_duration <= 0, false); + +	// No negative durations +	ERR_FAIL_COND_V(p_duration < 0, false); + +	// Ensure that the transition and ease types are valid  	ERR_FAIL_COND_V(p_trans_type < 0 || p_trans_type >= TRANS_COUNT, false);  	ERR_FAIL_COND_V(p_ease_type < 0 || p_ease_type >= EASE_COUNT, false); + +	// No negative delays  	ERR_FAIL_COND_V(p_delay < 0, false); +	// Confirm both objects have the target methods  	ERR_EXPLAIN("Object has no method named: %s" + p_method);  	ERR_FAIL_COND_V(!p_object->has_method(p_method), false);  	ERR_EXPLAIN("Target has no method named: %s" + p_target_method);  	ERR_FAIL_COND_V(!p_target->has_method(p_target_method), false); +	// Call the method to get the target value  	Variant::CallError error;  	Variant target_val = p_target->call(p_target_method, NULL, 0, error);  	ERR_FAIL_COND_V(error.error != Variant::CallError::CALL_OK, false); -	// convert INT to REAL is better for interpolaters +	// Convert target INT values to REAL as they are better for interpolation  	if (target_val.get_type() == Variant::INT) target_val = target_val.operator real_t();  	ERR_FAIL_COND_V(target_val.get_type() != p_initial_val.get_type(), false); +	// Make the new InterpolateData for the method follow  	InterpolateData data;  	data.active = true;  	data.type = FOLLOW_METHOD;  	data.finish = false;  	data.elapsed = 0; +	// Give the data it's configuration  	data.id = p_object->get_instance_id();  	data.key.push_back(p_method);  	data.concatenated_key = p_method; @@ -1303,31 +1540,41 @@ bool Tween::follow_method(Object *p_object, StringName p_method, Variant p_initi  	data.ease_type = p_ease_type;  	data.delay = p_delay; +	// Add the new interpolation  	_push_interpolate_data(data);  	return true;  }  bool Tween::targeting_property(Object *p_object, NodePath p_property, Object *p_initial, NodePath p_initial_property, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) { - +	// If we are currently updating, call this function again later  	if (pending_update != 0) {  		_add_pending_command("targeting_property", p_object, p_property, p_initial, p_initial_property, p_final_val, p_duration, p_trans_type, p_ease_type, p_delay);  		return true;  	} +	// Grab the target property and the target property  	p_property = p_property.get_as_property_path();  	p_initial_property = p_initial_property.get_as_property_path(); -	// convert INT to REAL is better for interpolaters +	// Convert the initial INT values to REAL as they are better for Interpolation  	if (p_final_val.get_type() == Variant::INT) p_final_val = p_final_val.operator real_t(); +	// Verify both objects are valid  	ERR_FAIL_COND_V(p_object == NULL, false);  	ERR_FAIL_COND_V(!ObjectDB::instance_validate(p_object), false);  	ERR_FAIL_COND_V(p_initial == NULL, false);  	ERR_FAIL_COND_V(!ObjectDB::instance_validate(p_initial), false); -	ERR_FAIL_COND_V(p_duration <= 0, false); + +	// No negative durations +	ERR_FAIL_COND_V(p_duration < 0, false); + +	// Ensure transition and easing types are valid  	ERR_FAIL_COND_V(p_trans_type < 0 || p_trans_type >= TRANS_COUNT, false);  	ERR_FAIL_COND_V(p_ease_type < 0 || p_ease_type >= EASE_COUNT, false); + +	// No negative delays  	ERR_FAIL_COND_V(p_delay < 0, false); +	// Ensure the initial and target properties exist on their objects  	bool prop_valid = false;  	p_object->get_indexed(p_property.get_subnames(), &prop_valid);  	ERR_FAIL_COND_V(!prop_valid, false); @@ -1336,16 +1583,18 @@ bool Tween::targeting_property(Object *p_object, NodePath p_property, Object *p_  	Variant initial_val = p_initial->get_indexed(p_initial_property.get_subnames(), &initial_prop_valid);  	ERR_FAIL_COND_V(!initial_prop_valid, false); -	// convert INT to REAL is better for interpolaters +	// Convert the initial INT value to REAL as it is better for interpolation  	if (initial_val.get_type() == Variant::INT) initial_val = initial_val.operator real_t();  	ERR_FAIL_COND_V(initial_val.get_type() != p_final_val.get_type(), false); +	// Build the InterpolateData object  	InterpolateData data;  	data.active = true;  	data.type = TARGETING_PROPERTY;  	data.finish = false;  	data.elapsed = 0; +	// Give the data it's configuration  	data.id = p_object->get_instance_id();  	data.key = p_property.get_subnames();  	data.concatenated_key = p_property.get_concatenated_subnames(); @@ -1358,49 +1607,64 @@ bool Tween::targeting_property(Object *p_object, NodePath p_property, Object *p_  	data.ease_type = p_ease_type;  	data.delay = p_delay; +	// Ensure there is a valid delta  	if (!_calc_delta_val(data.initial_val, data.final_val, data.delta_val))  		return false; +	// Add the interpolation  	_push_interpolate_data(data);  	return true;  }  bool Tween::targeting_method(Object *p_object, StringName p_method, Object *p_initial, StringName p_initial_method, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) { +	// If we are currently updating, call this function again later  	if (pending_update != 0) {  		_add_pending_command("targeting_method", p_object, p_method, p_initial, p_initial_method, p_final_val, p_duration, p_trans_type, p_ease_type, p_delay);  		return true;  	} -	// convert INT to REAL is better for interpolaters + +	// Convert final INT values to REAL as they are better for interpolation  	if (p_final_val.get_type() == Variant::INT) p_final_val = p_final_val.operator real_t(); +	// Make sure the given objects are valid  	ERR_FAIL_COND_V(p_object == NULL, false);  	ERR_FAIL_COND_V(!ObjectDB::instance_validate(p_object), false);  	ERR_FAIL_COND_V(p_initial == NULL, false);  	ERR_FAIL_COND_V(!ObjectDB::instance_validate(p_initial), false); -	ERR_FAIL_COND_V(p_duration <= 0, false); + +	// No negative durations +	ERR_FAIL_COND_V(p_duration < 0, false); + +	// Ensure transition and easing types are valid  	ERR_FAIL_COND_V(p_trans_type < 0 || p_trans_type >= TRANS_COUNT, false);  	ERR_FAIL_COND_V(p_ease_type < 0 || p_ease_type >= EASE_COUNT, false); + +	// No negative delays  	ERR_FAIL_COND_V(p_delay < 0, false); +	// Make sure both objects have the given method  	ERR_EXPLAIN("Object has no method named: %s" + p_method);  	ERR_FAIL_COND_V(!p_object->has_method(p_method), false);  	ERR_EXPLAIN("Initial Object has no method named: %s" + p_initial_method);  	ERR_FAIL_COND_V(!p_initial->has_method(p_initial_method), false); +	// Call the method to get the initial value  	Variant::CallError error;  	Variant initial_val = p_initial->call(p_initial_method, NULL, 0, error);  	ERR_FAIL_COND_V(error.error != Variant::CallError::CALL_OK, false); -	// convert INT to REAL is better for interpolaters +	// Convert initial INT values to REAL as they aer better for interpolation  	if (initial_val.get_type() == Variant::INT) initial_val = initial_val.operator real_t();  	ERR_FAIL_COND_V(initial_val.get_type() != p_final_val.get_type(), false); +	// Build the new InterpolateData object  	InterpolateData data;  	data.active = true;  	data.type = TARGETING_METHOD;  	data.finish = false;  	data.elapsed = 0; +	// Configure the data  	data.id = p_object->get_instance_id();  	data.key.push_back(p_method);  	data.concatenated_key = p_method; @@ -1413,16 +1677,17 @@ bool Tween::targeting_method(Object *p_object, StringName p_method, Object *p_in  	data.ease_type = p_ease_type;  	data.delay = p_delay; +	// Ensure there is a valid delta  	if (!_calc_delta_val(data.initial_val, data.final_val, data.delta_val))  		return false; +	// Add the interpolation  	_push_interpolate_data(data);  	return true;  }  Tween::Tween() { - -	//String autoplay; +	// Initialize tween attributes  	tween_process_mode = TWEEN_PROCESS_IDLE;  	repeat = false;  	speed_scale = 1; diff --git a/scene/animation/tween.h b/scene/animation/tween.h index 6fe3bffdbe..64ce099ecd 100644 --- a/scene/animation/tween.h +++ b/scene/animation/tween.h @@ -135,6 +135,7 @@ private:  	void _tween_process(float p_delta);  	void _remove_by_uid(int uid);  	void _push_interpolate_data(InterpolateData &p_data); +	bool _build_interpolation(InterpolateType p_interpolation_type, Object *p_object, NodePath *p_property, StringName *p_method, Variant p_initial_val, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay);  protected:  	bool _set(const StringName &p_name, const Variant &p_value); diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp index bdb1342019..bc8dcf0e82 100644 --- a/scene/gui/file_dialog.cpp +++ b/scene/gui/file_dialog.cpp @@ -48,8 +48,9 @@ void FileDialog::_notification(int p_what) {  	if (p_what == NOTIFICATION_ENTER_TREE) { -		refresh->set_icon(get_icon("reload"));  		dir_up->set_icon(get_icon("parent_folder")); +		refresh->set_icon(get_icon("reload")); +		show_hidden->set_icon(get_icon("toggle_hidden"));  	}  	if (p_what == NOTIFICATION_POPUP_HIDE) { @@ -393,20 +394,19 @@ void FileDialog::update_file_list() {  	List<String> files;  	List<String> dirs; -	bool isdir; -	bool ishidden; -	bool show_hidden = show_hidden_files; +	bool is_dir; +	bool is_hidden;  	String item; -	while ((item = dir_access->get_next(&isdir)) != "") { +	while ((item = dir_access->get_next(&is_dir)) != "") {  		if (item == "." || item == "..")  			continue; -		ishidden = dir_access->current_is_hidden(); +		is_hidden = dir_access->current_is_hidden(); -		if (show_hidden || !ishidden) { -			if (!isdir) +		if (show_hidden_files || !is_hidden) { +			if (!is_dir)  				files.push_back(item);  			else  				dirs.push_back(item); @@ -873,6 +873,13 @@ FileDialog::FileDialog() {  	refresh->connect("pressed", this, "_update_file_list");  	hbc->add_child(refresh); +	show_hidden = memnew(ToolButton); +	show_hidden->set_toggle_mode(true); +	show_hidden->set_pressed(is_showing_hidden_files()); +	show_hidden->set_tooltip(RTR("Toggle Hidden Files")); +	show_hidden->connect("toggled", this, "set_show_hidden_files"); +	hbc->add_child(show_hidden); +  	drives = memnew(OptionButton);  	hbc->add_child(drives);  	drives->connect("item_selected", this, "_select_drive"); diff --git a/scene/gui/file_dialog.h b/scene/gui/file_dialog.h index 85edac0b12..9f7ea1a2f2 100644 --- a/scene/gui/file_dialog.h +++ b/scene/gui/file_dialog.h @@ -90,6 +90,7 @@ private:  	ToolButton *dir_up;  	ToolButton *refresh; +	ToolButton *show_hidden;  	Vector<String> filters; diff --git a/scene/resources/default_theme/default_theme.cpp b/scene/resources/default_theme/default_theme.cpp index e8359e3dfe..2c5dfc375c 100644 --- a/scene/resources/default_theme/default_theme.cpp +++ b/scene/resources/default_theme/default_theme.cpp @@ -541,8 +541,9 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const  	// File Dialog -	theme->set_icon("reload", "FileDialog", make_icon(icon_reload_png));  	theme->set_icon("parent_folder", "FileDialog", make_icon(icon_parent_folder_png)); +	theme->set_icon("reload", "FileDialog", make_icon(icon_reload_png)); +	theme->set_icon("toggle_hidden", "FileDialog", make_icon(icon_visibility_png));  	// Popup diff --git a/scene/resources/default_theme/icon_visibility.png b/scene/resources/default_theme/icon_visibility.png Binary files differnew file mode 100644 index 0000000000..6402571f3e --- /dev/null +++ b/scene/resources/default_theme/icon_visibility.png diff --git a/scene/resources/default_theme/theme_data.h b/scene/resources/default_theme/theme_data.h index 87b8afd6a3..5e13a6625a 100644 --- a/scene/resources/default_theme/theme_data.h +++ b/scene/resources/default_theme/theme_data.h @@ -194,6 +194,10 @@ static const unsigned char icon_stop_png[] = {  	0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x4, 0x0, 0x0, 0x0, 0xb5, 0xfa, 0x37, 0xea, 0x0, 0x0, 0x0, 0x1e, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x20, 0x2, 0x7c, 0x60, 0x26, 0x28, 0xf3, 0xf0, 0x3f, 0x76, 0x8, 0x94, 0xa2, 0x97, 0x82, 0x51, 0x5, 0x84, 0x23, 0x8b, 0x30, 0x0, 0x0, 0x66, 0x60, 0x11, 0xdc, 0x92, 0xb3, 0xb7, 0xe7, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82  }; +static const unsigned char icon_visibility_png[] = { +	0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x3, 0x0, 0x0, 0x0, 0x28, 0x2d, 0xf, 0x53, 0x0, 0x0, 0x0, 0x3, 0x73, 0x42, 0x49, 0x54, 0x8, 0x8, 0x8, 0xdb, 0xe1, 0x4f, 0xe0, 0x0, 0x0, 0x0, 0x9, 0x70, 0x48, 0x59, 0x73, 0x0, 0x0, 0xe, 0xc4, 0x0, 0x0, 0xe, 0xc4, 0x1, 0x95, 0x2b, 0xe, 0x1b, 0x0, 0x0, 0x0, 0x19, 0x74, 0x45, 0x58, 0x74, 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x0, 0x77, 0x77, 0x77, 0x2e, 0x69, 0x6e, 0x6b, 0x73, 0x63, 0x61, 0x70, 0x65, 0x2e, 0x6f, 0x72, 0x67, 0x9b, 0xee, 0x3c, 0x1a, 0x0, 0x0, 0x0, 0x96, 0x50, 0x4c, 0x54, 0x45, 0x0, 0x0, 0x0, 0xdf, 0xdf, 0xdf, 0xe3, 0xe3, 0xe3, 0xe6, 0xe6, 0xe6, 0xd5, 0xd5, 0xd5, 0xd8, 0xd8, 0xd8, 0xdb, 0xdb, 0xdb, 0xdd, 0xdd, 0xdd, 0xe1, 0xe1, 0xe1, 0xe3, 0xe3, 0xe3, 0xe4, 0xe4, 0xe4, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xe0, 0xe0, 0xe0, 0xe1, 0xe1, 0xe1, 0xde, 0xde, 0xde, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xdf, 0xdf, 0xdf, 0xe0, 0xe0, 0xe0, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xe0, 0xe0, 0xe0, 0xde, 0xde, 0xde, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe1, 0xe1, 0xe1, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe1, 0xe1, 0xe1, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xb7, 0x7e, 0xd, 0xb6, 0x0, 0x0, 0x0, 0x32, 0x74, 0x52, 0x4e, 0x53, 0x0, 0x8, 0x9, 0xa, 0xc, 0xd, 0xe, 0xf, 0x11, 0x12, 0x13, 0x2e, 0x2f, 0x32, 0x33, 0x36, 0x37, 0x38, 0x48, 0x49, 0x4b, 0x50, 0x53, 0x55, 0x56, 0x6c, 0x6d, 0x6e, 0x70, 0x77, 0x79, 0x7b, 0x7c, 0xc5, 0xd7, 0xd8, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe1, 0xe2, 0xe3, 0xf0, 0xf2, 0xf3, 0xf4, 0xfe, 0x5e, 0x62, 0x1a, 0x26, 0x0, 0x0, 0x0, 0x86, 0x49, 0x44, 0x41, 0x54, 0x18, 0x19, 0x8d, 0xc1, 0xb, 0x16, 0x42, 0x40, 0x0, 0x86, 0xd1, 0x2f, 0xa2, 0x77, 0x2a, 0x85, 0xde, 0x91, 0x5e, 0x33, 0x8a, 0x7f, 0xff, 0x9b, 0xcb, 0x99, 0x63, 0x1, 0xee, 0xa5, 0x9f, 0xc1, 0x3e, 0x6f, 0xea, 0xfc, 0xe0, 0xd1, 0x99, 0xdd, 0xe5, 0x94, 0xb, 0x9c, 0xf9, 0x57, 0x26, 0x9, 0x82, 0x5d, 0xa1, 0xdf, 0x92, 0x96, 0xf7, 0x94, 0x99, 0xd0, 0x1a, 0x1b, 0x7d, 0x7c, 0xe0, 0x2c, 0x25, 0x6c, 0x2b, 0x1b, 0x93, 0x4a, 0x17, 0xa0, 0x94, 0x2, 0xac, 0x64, 0x8, 0xa5, 0x12, 0x78, 0x48, 0x1, 0x56, 0x32, 0x8c, 0xa4, 0x7, 0x70, 0x93, 0x76, 0xc4, 0xd6, 0x6c, 0xc8, 0xa4, 0x2b, 0x30, 0xac, 0x54, 0x8c, 0x69, 0x4d, 0xad, 0xcc, 0x90, 0xd6, 0xba, 0x91, 0x49, 0xc3, 0x30, 0xb3, 0x6a, 0x22, 0x9c, 0xd5, 0x5b, 0xce, 0x2b, 0xa2, 0xe3, 0x9f, 0xf2, 0xba, 0xce, 0x8f, 0x3e, 0xbd, 0xfc, 0x1, 0xdb, 0xf3, 0x10, 0xc5, 0x78, 0x85, 0x14, 0x89, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 +}; +  static const unsigned char icon_zoom_less_png[] = {  	0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x4, 0x0, 0x0, 0x0, 0xb5, 0xfa, 0x37, 0xea, 0x0, 0x0, 0x0, 0x13, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x18, 0x31, 0xe0, 0xc1, 0x7f, 0x3c, 0x90, 0xb0, 0x82, 0x11, 0x2, 0x0, 0xbf, 0x57, 0x36, 0x25, 0x52, 0x24, 0x7b, 0x26, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82  };  |