Animated particlespawners and more (#11545)
Co-authored-by: Lars Mueller <appgurulars@gmx.de> Co-authored-by: sfan5 <sfan5@live.de> Co-authored-by: Dmitry Kostenko <codeforsmile@gmail.com>
This commit is contained in:
		
							parent
							
								
									8724fe6e3f
								
							
						
					
					
						commit
						20bd6bdb68
					
				@ -21,6 +21,7 @@ core.features = {
 | 
			
		||||
	use_texture_alpha_string_modes = true,
 | 
			
		||||
	degrotate_240_steps = true,
 | 
			
		||||
	abm_min_max_y = true,
 | 
			
		||||
	particlespawner_tweenable = true,
 | 
			
		||||
	dynamic_add_media_table = true,
 | 
			
		||||
	get_sky_as_table = true,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										346
									
								
								doc/lua_api.txt
									
									
									
									
									
								
							
							
						
						
									
										346
									
								
								doc/lua_api.txt
									
									
									
									
									
								
							@ -4856,6 +4856,11 @@ Utilities
 | 
			
		||||
          abm_min_max_y = true,
 | 
			
		||||
          -- dynamic_add_media supports passing a table with options (5.5.0)
 | 
			
		||||
          dynamic_add_media_table = true,
 | 
			
		||||
          -- particlespawners support texpools and animation of properties,
 | 
			
		||||
          -- particle textures support smooth fade and scale animations, and
 | 
			
		||||
          -- sprite-sheet particle animations can by synced to the lifetime
 | 
			
		||||
          -- of individual particles (5.6.0)
 | 
			
		||||
          particlespawner_tweenable = true,
 | 
			
		||||
          -- allows get_sky to return a table instead of separate values (5.6.0)
 | 
			
		||||
          get_sky_as_table = true,
 | 
			
		||||
      }
 | 
			
		||||
@ -8984,6 +8989,8 @@ Used by `minetest.add_particle`.
 | 
			
		||||
 | 
			
		||||
        texture = "image.png",
 | 
			
		||||
        -- The texture of the particle
 | 
			
		||||
        -- v5.6.0 and later: also supports the table format described in the
 | 
			
		||||
        -- following section
 | 
			
		||||
 | 
			
		||||
        playername = "singleplayer",
 | 
			
		||||
        -- Optional, if specified spawns particle only on the player's client
 | 
			
		||||
@ -9005,6 +9012,12 @@ Used by `minetest.add_particle`.
 | 
			
		||||
        -- If set to a valid number 1-6, specifies the tile from which the
 | 
			
		||||
        -- particle texture is picked.
 | 
			
		||||
        -- Otherwise, the default behavior is used. (currently: any random tile)
 | 
			
		||||
 | 
			
		||||
        drag = {x=0, y=0, z=0},
 | 
			
		||||
        -- v5.6.0 and later: Optional drag value, consult the following section
 | 
			
		||||
 | 
			
		||||
        bounce = {min = ..., max = ..., bias = 0},
 | 
			
		||||
        -- v5.6.0 and later: Optional bounce range, consult the following section
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -9013,7 +9026,20 @@ Used by `minetest.add_particle`.
 | 
			
		||||
 | 
			
		||||
Used by `minetest.add_particlespawner`.
 | 
			
		||||
 | 
			
		||||
Before v5.6.0, particlespawners used a different syntax and had a more limited set
 | 
			
		||||
of features. Definition fields that are the same in both legacy and modern versions
 | 
			
		||||
are shown in the next listing, and the fields that are used by legacy versions are
 | 
			
		||||
shown separated by a comment; the modern fields are too complex to compactly
 | 
			
		||||
describe in this manner and are documented after the listing.
 | 
			
		||||
 | 
			
		||||
The older syntax can be used in combination with the newer syntax (e.g. having
 | 
			
		||||
`minpos`, `maxpos`, and `pos` all set) to support older servers. On newer servers,
 | 
			
		||||
the new syntax will override the older syntax; on older servers, the newer syntax
 | 
			
		||||
will be ignored.
 | 
			
		||||
 | 
			
		||||
    {
 | 
			
		||||
        -- Common fields (same name and meaning in both new and legacy syntax)
 | 
			
		||||
 | 
			
		||||
        amount = 1,
 | 
			
		||||
        -- Number of particles spawned over the time period `time`.
 | 
			
		||||
 | 
			
		||||
@ -9022,22 +9048,6 @@ Used by `minetest.add_particlespawner`.
 | 
			
		||||
        -- If time is 0 spawner has infinite lifespan and spawns the `amount` on
 | 
			
		||||
        -- a per-second basis.
 | 
			
		||||
 | 
			
		||||
        minpos = {x=0, y=0, z=0},
 | 
			
		||||
        maxpos = {x=0, y=0, z=0},
 | 
			
		||||
        minvel = {x=0, y=0, z=0},
 | 
			
		||||
        maxvel = {x=0, y=0, z=0},
 | 
			
		||||
        minacc = {x=0, y=0, z=0},
 | 
			
		||||
        maxacc = {x=0, y=0, z=0},
 | 
			
		||||
        minexptime = 1,
 | 
			
		||||
        maxexptime = 1,
 | 
			
		||||
        minsize = 1,
 | 
			
		||||
        maxsize = 1,
 | 
			
		||||
        -- The particles' properties are random values between the min and max
 | 
			
		||||
        -- values.
 | 
			
		||||
        -- applies to: pos, velocity, acceleration, expirationtime, size
 | 
			
		||||
        -- If `node` is set, min and maxsize can be set to 0 to spawn
 | 
			
		||||
        -- randomly-sized particles (just like actual node dig particles).
 | 
			
		||||
 | 
			
		||||
        collisiondetection = false,
 | 
			
		||||
        -- If true collide with `walkable` nodes and, depending on the
 | 
			
		||||
        -- `object_collision` field, objects too.
 | 
			
		||||
@ -9066,8 +9076,11 @@ Used by `minetest.add_particlespawner`.
 | 
			
		||||
 | 
			
		||||
        animation = {Tile Animation definition},
 | 
			
		||||
        -- Optional, specifies how to animate the particles' texture
 | 
			
		||||
        -- v5.6.0 and later: set length to -1 to sychronize the length
 | 
			
		||||
        -- of the animation with the expiration time of individual particles.
 | 
			
		||||
        -- (-2 causes the animation to be played twice, and so on)
 | 
			
		||||
 | 
			
		||||
        glow = 0
 | 
			
		||||
        glow = 0,
 | 
			
		||||
        -- Optional, specify particle self-luminescence in darkness.
 | 
			
		||||
        -- Values 0-14.
 | 
			
		||||
 | 
			
		||||
@ -9081,8 +9094,307 @@ Used by `minetest.add_particlespawner`.
 | 
			
		||||
        -- If set to a valid number 1-6, specifies the tile from which the
 | 
			
		||||
        -- particle texture is picked.
 | 
			
		||||
        -- Otherwise, the default behavior is used. (currently: any random tile)
 | 
			
		||||
 | 
			
		||||
        -- Legacy definition fields
 | 
			
		||||
 | 
			
		||||
        minpos = {x=0, y=0, z=0},
 | 
			
		||||
        maxpos = {x=0, y=0, z=0},
 | 
			
		||||
        minvel = {x=0, y=0, z=0},
 | 
			
		||||
        maxvel = {x=0, y=0, z=0},
 | 
			
		||||
        minacc = {x=0, y=0, z=0},
 | 
			
		||||
        maxacc = {x=0, y=0, z=0},
 | 
			
		||||
        minexptime = 1,
 | 
			
		||||
        maxexptime = 1,
 | 
			
		||||
        minsize = 1,
 | 
			
		||||
        maxsize = 1,
 | 
			
		||||
        -- The particles' properties are random values between the min and max
 | 
			
		||||
        -- values.
 | 
			
		||||
        -- applies to: pos, velocity, acceleration, expirationtime, size
 | 
			
		||||
        -- If `node` is set, min and maxsize can be set to 0 to spawn
 | 
			
		||||
        -- randomly-sized particles (just like actual node dig particles).
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
### Modern definition fields
 | 
			
		||||
 | 
			
		||||
After v5.6.0, spawner properties can be defined in several different ways depending
 | 
			
		||||
on the level of control you need. `pos` for instance can be set as a single vector,
 | 
			
		||||
in which case all particles will appear at that exact point throughout the lifetime
 | 
			
		||||
of the spawner. Alternately, it can be specified as a min-max pair, specifying a
 | 
			
		||||
cubic range the particles can appear randomly within. Finally, some properties can
 | 
			
		||||
be animated by suffixing their key with `_tween` (e.g. `pos_tween`) and supplying
 | 
			
		||||
a tween table.
 | 
			
		||||
 | 
			
		||||
The following definitions are all equivalent, listed in order of precedence from
 | 
			
		||||
lowest (the legacy syntax) to highest (tween tables). If multiple forms of a
 | 
			
		||||
property definition are present, the highest-precidence form will be selected
 | 
			
		||||
and all lower-precedence fields will be ignored, allowing for graceful
 | 
			
		||||
degradation in older clients).
 | 
			
		||||
 | 
			
		||||
    {
 | 
			
		||||
      -- old syntax
 | 
			
		||||
      maxpos = {x = 0, y = 0, z = 0},
 | 
			
		||||
      minpos = {x = 0, y = 0, z = 0},
 | 
			
		||||
 | 
			
		||||
      -- absolute value
 | 
			
		||||
      pos = 0,
 | 
			
		||||
      -- all components of every particle's position vector will be set to this
 | 
			
		||||
      -- value
 | 
			
		||||
 | 
			
		||||
      -- vec3
 | 
			
		||||
      pos = vector.new(0,0,0),
 | 
			
		||||
      -- all particles will appear at this exact position throughout the lifetime
 | 
			
		||||
      -- of the particlespawner
 | 
			
		||||
 | 
			
		||||
      -- vec3 range
 | 
			
		||||
      pos = {
 | 
			
		||||
            -- the particle will appear at a position that is picked at random from
 | 
			
		||||
            -- within a cubic range
 | 
			
		||||
 | 
			
		||||
            min = vector.new(0,0,0),
 | 
			
		||||
            -- `min` is the minimum value this property will be set to in particles
 | 
			
		||||
            -- spawned by the generator
 | 
			
		||||
 | 
			
		||||
            max = vector.new(0,0,0),
 | 
			
		||||
            -- `max` is the minimum value this property will be set to in particles
 | 
			
		||||
            -- spawned by the generator
 | 
			
		||||
 | 
			
		||||
            bias = 0,
 | 
			
		||||
            -- when `bias` is 0, all random values are exactly as likely as any
 | 
			
		||||
            -- other. when it is positive, the higher it is, the more likely values
 | 
			
		||||
            -- will appear towards the minimum end of the allowed spectrum. when
 | 
			
		||||
            -- it is negative, the lower it is, the more likely values will appear
 | 
			
		||||
            -- towards the maximum end of the allowed spectrum. the curve is
 | 
			
		||||
            -- exponential and there is no particular maximum or minimum value
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        -- tween table
 | 
			
		||||
        pos_tween = {...},
 | 
			
		||||
        -- a tween table should consist of a list of frames in the same form as the
 | 
			
		||||
        -- untweened pos property above, which the engine will interpolate between,
 | 
			
		||||
        -- and optionally a number of properties that control how the interpolation
 | 
			
		||||
        -- takes place. currently **only two frames**, the first and the last, are
 | 
			
		||||
        -- used, but extra frames are accepted for the sake of forward compatibility.
 | 
			
		||||
        -- any of the above definition styles can be used here as well in any combination
 | 
			
		||||
        -- supported by the property type
 | 
			
		||||
 | 
			
		||||
        pos_tween = {
 | 
			
		||||
            style = "fwd",
 | 
			
		||||
            -- linear animation from first to last frame (default)
 | 
			
		||||
            style = "rev",
 | 
			
		||||
            -- linear animation from last to first frame
 | 
			
		||||
            style = "pulse",
 | 
			
		||||
            -- linear animation from first to last then back to first again
 | 
			
		||||
            style = "flicker",
 | 
			
		||||
            -- like "pulse", but slightly randomized to add a bit of stutter
 | 
			
		||||
 | 
			
		||||
            reps = 1,
 | 
			
		||||
            -- number of times the animation is played over the particle's lifespan
 | 
			
		||||
 | 
			
		||||
            start = 0.0,
 | 
			
		||||
            -- point in the spawner's lifespan at which the animation begins. 0 is
 | 
			
		||||
            -- the very beginning, 1 is the very end
 | 
			
		||||
 | 
			
		||||
            -- frames can be defined in a number of different ways, depending on the
 | 
			
		||||
            -- underlying type of the property. for now, all but the first and last
 | 
			
		||||
            -- frame are ignored
 | 
			
		||||
 | 
			
		||||
            -- frames
 | 
			
		||||
 | 
			
		||||
                -- floats
 | 
			
		||||
                0, 0,
 | 
			
		||||
 | 
			
		||||
                -- vec3s
 | 
			
		||||
                vector.new(0,0,0),
 | 
			
		||||
                vector.new(0,0,0),
 | 
			
		||||
 | 
			
		||||
                -- vec3 ranges
 | 
			
		||||
                { min = vector.new(0,0,0), max = vector.new(0,0,0), bias = 0 },
 | 
			
		||||
                { min = vector.new(0,0,0), max = vector.new(0,0,0), bias = 0 },
 | 
			
		||||
 | 
			
		||||
                -- mixed
 | 
			
		||||
                0, { min = vector.new(0,0,0), max = vector.new(0,0,0), bias = 0 },
 | 
			
		||||
        },
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
All of the properties that can be defined in this way are listed in the next
 | 
			
		||||
section, along with the datatypes they accept.
 | 
			
		||||
 | 
			
		||||
#### List of particlespawner properties
 | 
			
		||||
All of the properties in this list can be animated with `*_tween` tables
 | 
			
		||||
unless otherwise specified. For example, `jitter` can be tweened by setting
 | 
			
		||||
a `jitter_tween` table instead of (or in addition to) a `jitter` table/value.
 | 
			
		||||
Types used are defined in the previous section.
 | 
			
		||||
 | 
			
		||||
* vec3 range `pos`: the position at which particles can appear
 | 
			
		||||
* vec3 range `vel`: the initial velocity of the particle
 | 
			
		||||
* vec3 range `acc`: the direction and speed with which the particle
 | 
			
		||||
  accelerates
 | 
			
		||||
* vec3 range `jitter`: offsets the velocity of each particle by a random
 | 
			
		||||
  amount within the specified range each frame. used to create Brownian motion.
 | 
			
		||||
* vec3 range `drag`: the amount by which absolute particle velocity along
 | 
			
		||||
  each axis is decreased per second.  a value of 1.0 means that the particle
 | 
			
		||||
  will be slowed to a stop over the space of a second; a value of -1.0 means
 | 
			
		||||
  that the particle speed will be doubled every second. to avoid interfering
 | 
			
		||||
  with gravity provided by `acc`, a drag vector like `vector.new(1,0,1)` can
 | 
			
		||||
  be used instead of a uniform value.
 | 
			
		||||
* float range `bounce`: how bouncy the particles are when `collisiondetection`
 | 
			
		||||
  is turned on. values less than or equal to `0` turn off particle bounce;
 | 
			
		||||
  `1` makes the particles bounce without losing any velocity, and `2` makes
 | 
			
		||||
  them double their velocity with every bounce.  `bounce` is not bounded but
 | 
			
		||||
  values much larger than `1.0` probably aren't very useful.
 | 
			
		||||
* float range `exptime`: the number of seconds after which the particle
 | 
			
		||||
  disappears.
 | 
			
		||||
* table `attract`: sets the birth orientation of particles relative to various
 | 
			
		||||
  shapes defined in world coordinate space. this is an alternative means of
 | 
			
		||||
  setting the velocity which allows particles to emerge from or enter into
 | 
			
		||||
  some entity or node on the map, rather than simply being assigned random
 | 
			
		||||
  velocity values within a range. the velocity calculated by this method will
 | 
			
		||||
  be **added** to that specified by `vel` if `vel` is also set, so in most
 | 
			
		||||
  cases **`vel` should be set to 0**. `attract` has the fields:
 | 
			
		||||
  * string `kind`: selects the kind of shape towards which the particles will
 | 
			
		||||
    be oriented. it must have one of the following values:
 | 
			
		||||
    * `"none"`: no attractor is set and the `attractor` table is ignored
 | 
			
		||||
    * `"point"`: the particles are attracted to a specific point in space.
 | 
			
		||||
      use this also if you want a sphere-like effect, in combination with
 | 
			
		||||
      the `radius` property.
 | 
			
		||||
    * `"line"`: the particles are attracted to an (infinite) line passing
 | 
			
		||||
      through the points `origin` and `angle`. use this for e.g. beacon
 | 
			
		||||
      effects, energy beam effects, etc.
 | 
			
		||||
    * `"plane"`: the particles are attracted to an (infinite) plane on whose
 | 
			
		||||
      surface `origin` designates a point in world coordinate space. use this
 | 
			
		||||
      for e.g. particles entering or emerging from a portal.
 | 
			
		||||
  * float range `strength`: the speed with which particles will move towards
 | 
			
		||||
    `attractor`. If negative, the particles will instead move away from that
 | 
			
		||||
    point.
 | 
			
		||||
  * vec3 `origin`: the origin point of the shape towards which particles will
 | 
			
		||||
    initially be oriented. functions as an offset if `origin_attached` is also
 | 
			
		||||
    set.
 | 
			
		||||
  * vec3 `direction`: sets the direction in which the attractor shape faces. for
 | 
			
		||||
    lines, this sets the angle of the line; e.g. a vector of (0,1,0) will
 | 
			
		||||
    create a vertical line that passes through `origin`. for planes, `direction`
 | 
			
		||||
    is the surface normal of an infinite plane on whose surface `origin` is
 | 
			
		||||
    a point. functions as an offset if `direction_attached` is also set.
 | 
			
		||||
  * entity `origin_attached`: allows the origin to be specified as an offset
 | 
			
		||||
    from the position of an entity rather than a coordinate in world space.
 | 
			
		||||
  * entity `direction_attached`: allows the direction to be specified as an offset
 | 
			
		||||
    from the position of an entity rather than a coordinate in world space.
 | 
			
		||||
  * bool `die_on_contact`: if true, the particles' lifetimes are adjusted so
 | 
			
		||||
    that they will die as they cross the attractor threshold. this behavior
 | 
			
		||||
    is the default but is undesirable for some kinds of animations; set it to
 | 
			
		||||
    false to allow particles to live out their natural lives.
 | 
			
		||||
* vec3 range `radius`: if set, particles will be arranged in a sphere around
 | 
			
		||||
  `pos`. A constant can be used to create a spherical shell of particles, a
 | 
			
		||||
  vector to create an ovoid shell, and a range to create a volume; e.g.
 | 
			
		||||
  `{min = 0.5, max = 1, bias = 1}` will allow particles to appear between 0.5
 | 
			
		||||
  and 1 nodes away from `pos` but will cluster them towards the center of the
 | 
			
		||||
  sphere. Usually if `radius` is used, `pos` should be a single point, but it
 | 
			
		||||
  can still be a range if you really know what you're doing (e.g. to create a
 | 
			
		||||
  "roundcube" emitter volume).
 | 
			
		||||
 | 
			
		||||
### Textures
 | 
			
		||||
 | 
			
		||||
In versions before v5.6.0, particlespawner textures could only be specified as a single
 | 
			
		||||
texture string. After v5.6.0, textures can now be specified as a table as well. This
 | 
			
		||||
table contains options that allow simple animations to be applied to the texture.
 | 
			
		||||
 | 
			
		||||
    texture = {
 | 
			
		||||
        name = "mymod_particle_texture.png",
 | 
			
		||||
        -- the texture specification string
 | 
			
		||||
 | 
			
		||||
        alpha = 1.0,
 | 
			
		||||
        -- controls how visible the particle is; at 1.0 the particle is fully
 | 
			
		||||
        -- visible, at 0, it is completely invisible.
 | 
			
		||||
 | 
			
		||||
        alpha_tween = {1, 0},
 | 
			
		||||
        -- can be used instead of `alpha` to animate the alpha value over the
 | 
			
		||||
        -- particle's lifetime. these tween tables work identically to the tween
 | 
			
		||||
        -- tables used in particlespawner properties, except that time references
 | 
			
		||||
        -- are understood with respect to the particle's lifetime, not the
 | 
			
		||||
        -- spawner's. {1,0} fades the particle out over its lifetime.
 | 
			
		||||
 | 
			
		||||
        scale = 1,
 | 
			
		||||
        scale = {x = 1, y = 1},
 | 
			
		||||
        -- scales the texture onscreen
 | 
			
		||||
 | 
			
		||||
        scale_tween = {
 | 
			
		||||
            {x = 1, y = 1},
 | 
			
		||||
            {x = 0, y = 1},
 | 
			
		||||
        },
 | 
			
		||||
        -- animates the scale over the particle's lifetime. works like the
 | 
			
		||||
        -- alpha_tween table, but can accept two-dimensional vectors as well as
 | 
			
		||||
        -- integer values. the example value would cause the particle to shrink
 | 
			
		||||
        -- in one dimension over the course of its life until it disappears
 | 
			
		||||
 | 
			
		||||
        blend = "alpha",
 | 
			
		||||
        -- (default) blends transparent pixels with those they are drawn atop
 | 
			
		||||
        -- according to the alpha channel of the source texture. useful for
 | 
			
		||||
        -- e.g. material objects like rocks, dirt, smoke, or node chunks
 | 
			
		||||
        blend = "add",
 | 
			
		||||
        -- adds the value of pixels to those underneath them, modulo the sources
 | 
			
		||||
        -- alpha channel. useful for e.g. bright light effects like sparks or fire
 | 
			
		||||
        blend = "screen",
 | 
			
		||||
        -- like "add" but less bright. useful for subtler light effecs. note that
 | 
			
		||||
        -- this is NOT formally equivalent to the "screen" effect used in image
 | 
			
		||||
        -- editors and compositors, as it does not respect the alpha channel of
 | 
			
		||||
        -- of the image being blended
 | 
			
		||||
        blend = "sub",
 | 
			
		||||
        -- the inverse of "add"; the value of the source pixel is subtracted from
 | 
			
		||||
        -- the pixel underneath it. a white pixel will turn whatever is underneath
 | 
			
		||||
        -- it black; a black pixel will be "transparent". useful for creating
 | 
			
		||||
        -- darkening effects
 | 
			
		||||
 | 
			
		||||
        animation = {Tile Animation definition},
 | 
			
		||||
        -- overrides the particlespawner's global animation property for a single
 | 
			
		||||
        -- specific texture
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
Instead of setting a single texture definition, it is also possible to set a
 | 
			
		||||
`texpool` property. A `texpool` consists of a list of possible particle textures.
 | 
			
		||||
Every time a particle is spawned, the engine will pick a texture at random from
 | 
			
		||||
the `texpool` and assign it as that particle's texture. You can also specify a
 | 
			
		||||
`texture` in addition to a `texpool`; the `texture` value will be ignored on newer
 | 
			
		||||
clients but will be sent to older (pre-v5.6.0) clients that do not implement
 | 
			
		||||
texpools.
 | 
			
		||||
 | 
			
		||||
    texpool = {
 | 
			
		||||
        "mymod_particle_texture.png";
 | 
			
		||||
        { name = "mymod_spark.png", fade = "out" },
 | 
			
		||||
        {
 | 
			
		||||
          name = "mymod_dust.png",
 | 
			
		||||
          alpha = 0.3,
 | 
			
		||||
          scale = 1.5,
 | 
			
		||||
          animation = {
 | 
			
		||||
                type = "vertical_frames",
 | 
			
		||||
                aspect_w = 16, aspect_h = 16,
 | 
			
		||||
 | 
			
		||||
                length = 3,
 | 
			
		||||
                -- the animation lasts for 3s and then repeats
 | 
			
		||||
                length = -3,
 | 
			
		||||
                -- repeat the animation three times over the particle's lifetime
 | 
			
		||||
                -- (post-v5.6.0 clients only)
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
#### List of animatable texture properties
 | 
			
		||||
 | 
			
		||||
While animated particlespawner values vary over the course of the particlespawner's
 | 
			
		||||
lifetime, animated texture properties vary over the lifespans of the individual
 | 
			
		||||
particles spawned with that texture. So a particle with the texture property
 | 
			
		||||
 | 
			
		||||
    alpha_tween = {
 | 
			
		||||
        0.0, 1.0,
 | 
			
		||||
        style = "pulse",
 | 
			
		||||
        reps = 4,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
would be invisible at its spawning, pulse visible four times throughout its
 | 
			
		||||
lifespan, and then vanish again before expiring.
 | 
			
		||||
 | 
			
		||||
* float `alpha` (0.0 - 1.0): controls the visibility of the texture
 | 
			
		||||
* vec2 `scale`: controls the size of the displayed billboard onscreen. Its units
 | 
			
		||||
  are multiples of the parent particle's assigned size (see the `size` property above)
 | 
			
		||||
 | 
			
		||||
`HTTPRequest` definition
 | 
			
		||||
------------------------
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -33,23 +33,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 | 
			
		||||
#include "client.h"
 | 
			
		||||
#include "settings.h"
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	Utility
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
static f32 random_f32(f32 min, f32 max)
 | 
			
		||||
{
 | 
			
		||||
	return rand() / (float)RAND_MAX * (max - min) + min;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static v3f random_v3f(v3f min, v3f max)
 | 
			
		||||
{
 | 
			
		||||
	return v3f(
 | 
			
		||||
		random_f32(min.X, max.X),
 | 
			
		||||
		random_f32(min.Y, max.Y),
 | 
			
		||||
		random_f32(min.Z, max.Z));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	Particle
 | 
			
		||||
*/
 | 
			
		||||
@ -59,25 +42,71 @@ Particle::Particle(
 | 
			
		||||
	LocalPlayer *player,
 | 
			
		||||
	ClientEnvironment *env,
 | 
			
		||||
	const ParticleParameters &p,
 | 
			
		||||
	video::ITexture *texture,
 | 
			
		||||
	const ClientTexRef& texture,
 | 
			
		||||
	v2f texpos,
 | 
			
		||||
	v2f texsize,
 | 
			
		||||
	video::SColor color
 | 
			
		||||
):
 | 
			
		||||
	scene::ISceneNode(((Client *)gamedef)->getSceneManager()->getRootSceneNode(),
 | 
			
		||||
		((Client *)gamedef)->getSceneManager())
 | 
			
		||||
		((Client *)gamedef)->getSceneManager()),
 | 
			
		||||
	m_texture(texture)
 | 
			
		||||
{
 | 
			
		||||
	// Misc
 | 
			
		||||
	m_gamedef = gamedef;
 | 
			
		||||
	m_env = env;
 | 
			
		||||
 | 
			
		||||
	// translate blend modes to GL blend functions
 | 
			
		||||
	video::E_BLEND_FACTOR bfsrc, bfdst;
 | 
			
		||||
	video::E_BLEND_OPERATION blendop;
 | 
			
		||||
	const auto blendmode = texture.tex != nullptr
 | 
			
		||||
			? texture.tex -> blendmode
 | 
			
		||||
			: ParticleParamTypes::BlendMode::alpha;
 | 
			
		||||
 | 
			
		||||
	switch (blendmode) {
 | 
			
		||||
		case ParticleParamTypes::BlendMode::alpha:
 | 
			
		||||
			bfsrc = video::EBF_SRC_ALPHA;
 | 
			
		||||
			bfdst = video::EBF_ONE_MINUS_SRC_ALPHA;
 | 
			
		||||
			blendop = video::EBO_ADD;
 | 
			
		||||
		break;
 | 
			
		||||
 | 
			
		||||
		case ParticleParamTypes::BlendMode::add:
 | 
			
		||||
			bfsrc = video::EBF_SRC_ALPHA;
 | 
			
		||||
			bfdst = video::EBF_DST_ALPHA;
 | 
			
		||||
			blendop = video::EBO_ADD;
 | 
			
		||||
		break;
 | 
			
		||||
 | 
			
		||||
		case ParticleParamTypes::BlendMode::sub:
 | 
			
		||||
			bfsrc = video::EBF_SRC_ALPHA;
 | 
			
		||||
			bfdst = video::EBF_DST_ALPHA;
 | 
			
		||||
			blendop = video::EBO_REVSUBTRACT;
 | 
			
		||||
		break;
 | 
			
		||||
 | 
			
		||||
		case ParticleParamTypes::BlendMode::screen:
 | 
			
		||||
			bfsrc = video::EBF_ONE;
 | 
			
		||||
			bfdst = video::EBF_ONE_MINUS_SRC_COLOR;
 | 
			
		||||
			blendop = video::EBO_ADD;
 | 
			
		||||
		break;
 | 
			
		||||
 | 
			
		||||
		default: assert(false);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Texture
 | 
			
		||||
	m_material.setFlag(video::EMF_LIGHTING, false);
 | 
			
		||||
	m_material.setFlag(video::EMF_BACK_FACE_CULLING, false);
 | 
			
		||||
	m_material.setFlag(video::EMF_BILINEAR_FILTER, false);
 | 
			
		||||
	m_material.setFlag(video::EMF_FOG_ENABLE, true);
 | 
			
		||||
	m_material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
 | 
			
		||||
	m_material.setTexture(0, texture);
 | 
			
		||||
 | 
			
		||||
	// correctly render layered transparent particles -- see #10398
 | 
			
		||||
	m_material.setFlag(video::EMF_ZWRITE_ENABLE, true);
 | 
			
		||||
 | 
			
		||||
	// enable alpha blending and set blend mode
 | 
			
		||||
	m_material.MaterialType = video::EMT_ONETEXTURE_BLEND;
 | 
			
		||||
	m_material.MaterialTypeParam = video::pack_textureBlendFunc(
 | 
			
		||||
			bfsrc, bfdst,
 | 
			
		||||
			video::EMFN_MODULATE_1X,
 | 
			
		||||
			video::EAS_TEXTURE | video::EAS_VERTEX_COLOR);
 | 
			
		||||
	m_material.BlendOperation = blendop;
 | 
			
		||||
	m_material.setTexture(0, m_texture.ref);
 | 
			
		||||
	m_texpos = texpos;
 | 
			
		||||
	m_texsize = texsize;
 | 
			
		||||
	m_animation = p.animation;
 | 
			
		||||
@ -90,6 +119,9 @@ Particle::Particle(
 | 
			
		||||
	m_pos = p.pos;
 | 
			
		||||
	m_velocity = p.vel;
 | 
			
		||||
	m_acceleration = p.acc;
 | 
			
		||||
	m_drag = p.drag;
 | 
			
		||||
	m_jitter = p.jitter;
 | 
			
		||||
	m_bounce = p.bounce;
 | 
			
		||||
	m_expiration = p.expirationtime;
 | 
			
		||||
	m_player = player;
 | 
			
		||||
	m_size = p.size;
 | 
			
		||||
@ -98,6 +130,8 @@ Particle::Particle(
 | 
			
		||||
	m_object_collision = p.object_collision;
 | 
			
		||||
	m_vertical = p.vertical;
 | 
			
		||||
	m_glow = p.glow;
 | 
			
		||||
	m_alpha = 0;
 | 
			
		||||
	m_parent = nullptr;
 | 
			
		||||
 | 
			
		||||
	// Irrlicht stuff
 | 
			
		||||
	const float c = p.size / 2;
 | 
			
		||||
@ -111,6 +145,14 @@ Particle::Particle(
 | 
			
		||||
	updateVertices();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Particle::~Particle()
 | 
			
		||||
{
 | 
			
		||||
	/* if our textures aren't owned by a particlespawner, we need to clean
 | 
			
		||||
	 * them up ourselves when the particle dies */
 | 
			
		||||
	if (m_parent == nullptr)
 | 
			
		||||
		delete m_texture.tex;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Particle::OnRegisterSceneNode()
 | 
			
		||||
{
 | 
			
		||||
	if (IsVisible)
 | 
			
		||||
@ -134,6 +176,12 @@ void Particle::render()
 | 
			
		||||
void Particle::step(float dtime)
 | 
			
		||||
{
 | 
			
		||||
	m_time += dtime;
 | 
			
		||||
 | 
			
		||||
	// apply drag (not handled by collisionMoveSimple) and brownian motion
 | 
			
		||||
	v3f av = vecAbsolute(m_velocity);
 | 
			
		||||
	av -= av * (m_drag * dtime);
 | 
			
		||||
	m_velocity = av*vecSign(m_velocity) + v3f(m_jitter.pickWithin())*dtime;
 | 
			
		||||
 | 
			
		||||
	if (m_collisiondetection) {
 | 
			
		||||
		aabb3f box = m_collisionbox;
 | 
			
		||||
		v3f p_pos = m_pos * BS;
 | 
			
		||||
@ -141,17 +189,41 @@ void Particle::step(float dtime)
 | 
			
		||||
		collisionMoveResult r = collisionMoveSimple(m_env, m_gamedef, BS * 0.5f,
 | 
			
		||||
			box, 0.0f, dtime, &p_pos, &p_velocity, m_acceleration * BS, nullptr,
 | 
			
		||||
			m_object_collision);
 | 
			
		||||
		if (m_collision_removal && r.collides) {
 | 
			
		||||
			// force expiration of the particle
 | 
			
		||||
			m_expiration = -1.0;
 | 
			
		||||
 | 
			
		||||
		f32 bounciness = m_bounce.pickWithin();
 | 
			
		||||
		if (r.collides && (m_collision_removal || bounciness > 0)) {
 | 
			
		||||
			if (m_collision_removal) {
 | 
			
		||||
				// force expiration of the particle
 | 
			
		||||
				m_expiration = -1.0f;
 | 
			
		||||
			} else if (bounciness > 0) {
 | 
			
		||||
				/* cheap way to get a decent bounce effect is to only invert the
 | 
			
		||||
				 * largest component of the velocity vector, so e.g. you don't
 | 
			
		||||
				 * have a rock immediately bounce back in your face when you try
 | 
			
		||||
				 * to skip it across the water (as would happen if we simply
 | 
			
		||||
				 * downscaled and negated the velocity vector). this means
 | 
			
		||||
				 * bounciness will work properly for cubic objects, but meshes
 | 
			
		||||
				 * with diagonal angles and entities will not yield the correct
 | 
			
		||||
				 * visual. this is probably unavoidable */
 | 
			
		||||
				if (av.Y > av.X && av.Y > av.Z) {
 | 
			
		||||
					m_velocity.Y = -(m_velocity.Y * bounciness);
 | 
			
		||||
				} else if (av.X > av.Y && av.X > av.Z) {
 | 
			
		||||
					m_velocity.X = -(m_velocity.X * bounciness);
 | 
			
		||||
				} else if (av.Z > av.Y && av.Z > av.X) {
 | 
			
		||||
					m_velocity.Z = -(m_velocity.Z * bounciness);
 | 
			
		||||
				} else { // well now we're in a bit of a pickle
 | 
			
		||||
					m_velocity = -(m_velocity * bounciness);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			m_pos = p_pos / BS;
 | 
			
		||||
			m_velocity = p_velocity / BS;
 | 
			
		||||
		}
 | 
			
		||||
		m_pos = p_pos / BS;
 | 
			
		||||
	} else {
 | 
			
		||||
		// apply acceleration
 | 
			
		||||
		m_velocity += m_acceleration * dtime;
 | 
			
		||||
		m_pos += m_velocity * dtime;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (m_animation.type != TAT_NONE) {
 | 
			
		||||
		m_animation_time += dtime;
 | 
			
		||||
		int frame_length_i, frame_count;
 | 
			
		||||
@ -165,11 +237,21 @@ void Particle::step(float dtime)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// animate particle alpha in accordance with settings
 | 
			
		||||
	if (m_texture.tex != nullptr)
 | 
			
		||||
		m_alpha = m_texture.tex -> alpha.blend(m_time / (m_expiration+0.1f));
 | 
			
		||||
	else
 | 
			
		||||
		m_alpha = 1.f;
 | 
			
		||||
 | 
			
		||||
	// Update lighting
 | 
			
		||||
	updateLight();
 | 
			
		||||
 | 
			
		||||
	// Update model
 | 
			
		||||
	updateVertices();
 | 
			
		||||
 | 
			
		||||
	// Update position -- see #10398
 | 
			
		||||
	v3s16 camera_offset = m_env->getCameraOffset();
 | 
			
		||||
	setPosition(m_pos*BS - intToFloat(camera_offset, BS));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Particle::updateLight()
 | 
			
		||||
@ -189,7 +271,7 @@ void Particle::updateLight()
 | 
			
		||||
		light = blend_light(m_env->getDayNightRatio(), LIGHT_SUN, 0);
 | 
			
		||||
 | 
			
		||||
	u8 m_light = decode_light(light + m_glow);
 | 
			
		||||
	m_color.set(255,
 | 
			
		||||
	m_color.set(m_alpha*255,
 | 
			
		||||
		m_light * m_base_color.getRed() / 255,
 | 
			
		||||
		m_light * m_base_color.getGreen() / 255,
 | 
			
		||||
		m_light * m_base_color.getBlue() / 255);
 | 
			
		||||
@ -198,6 +280,12 @@ void Particle::updateLight()
 | 
			
		||||
void Particle::updateVertices()
 | 
			
		||||
{
 | 
			
		||||
	f32 tx0, tx1, ty0, ty1;
 | 
			
		||||
	v2f scale;
 | 
			
		||||
 | 
			
		||||
	if (m_texture.tex != nullptr)
 | 
			
		||||
		scale = m_texture.tex -> scale.blend(m_time / (m_expiration+0.1));
 | 
			
		||||
	else
 | 
			
		||||
		scale = v2f(1.f, 1.f);
 | 
			
		||||
 | 
			
		||||
	if (m_animation.type != TAT_NONE) {
 | 
			
		||||
		const v2u32 texsize = m_material.getTexture(0)->getSize();
 | 
			
		||||
@ -218,16 +306,24 @@ void Particle::updateVertices()
 | 
			
		||||
		ty1 = m_texpos.Y + m_texsize.Y;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	m_vertices[0] = video::S3DVertex(-m_size / 2, -m_size / 2,
 | 
			
		||||
	auto half = m_size * .5f,
 | 
			
		||||
	     hx   = half * scale.X,
 | 
			
		||||
	     hy   = half * scale.Y;
 | 
			
		||||
	m_vertices[0] = video::S3DVertex(-hx, -hy,
 | 
			
		||||
		0, 0, 0, 0, m_color, tx0, ty1);
 | 
			
		||||
	m_vertices[1] = video::S3DVertex(m_size / 2, -m_size / 2,
 | 
			
		||||
	m_vertices[1] = video::S3DVertex(hx, -hy,
 | 
			
		||||
		0, 0, 0, 0, m_color, tx1, ty1);
 | 
			
		||||
	m_vertices[2] = video::S3DVertex(m_size / 2, m_size / 2,
 | 
			
		||||
	m_vertices[2] = video::S3DVertex(hx, hy,
 | 
			
		||||
		0, 0, 0, 0, m_color, tx1, ty0);
 | 
			
		||||
	m_vertices[3] = video::S3DVertex(-m_size / 2, m_size / 2,
 | 
			
		||||
	m_vertices[3] = video::S3DVertex(-hx, hy,
 | 
			
		||||
		0, 0, 0, 0, m_color, tx0, ty0);
 | 
			
		||||
 | 
			
		||||
	v3s16 camera_offset = m_env->getCameraOffset();
 | 
			
		||||
 | 
			
		||||
	// see #10398
 | 
			
		||||
	// v3s16 camera_offset = m_env->getCameraOffset();
 | 
			
		||||
	// particle position is now handled by step()
 | 
			
		||||
	m_box.reset(v3f());
 | 
			
		||||
 | 
			
		||||
	for (video::S3DVertex &vertex : m_vertices) {
 | 
			
		||||
		if (m_vertical) {
 | 
			
		||||
			v3f ppos = m_player->getPosition()/BS;
 | 
			
		||||
@ -238,7 +334,6 @@ void Particle::updateVertices()
 | 
			
		||||
			vertex.Pos.rotateXZBy(m_player->getYaw());
 | 
			
		||||
		}
 | 
			
		||||
		m_box.addInternalPoint(vertex.Pos);
 | 
			
		||||
		vertex.Pos += m_pos*BS - intToFloat(camera_offset, BS);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -251,7 +346,8 @@ ParticleSpawner::ParticleSpawner(
 | 
			
		||||
	LocalPlayer *player,
 | 
			
		||||
	const ParticleSpawnerParameters &p,
 | 
			
		||||
	u16 attached_id,
 | 
			
		||||
	video::ITexture *texture,
 | 
			
		||||
	std::unique_ptr<ClientTexture[]>& texpool,
 | 
			
		||||
	size_t texcount,
 | 
			
		||||
	ParticleManager *p_manager
 | 
			
		||||
):
 | 
			
		||||
	m_particlemanager(p_manager), p(p)
 | 
			
		||||
@ -259,21 +355,66 @@ ParticleSpawner::ParticleSpawner(
 | 
			
		||||
	m_gamedef = gamedef;
 | 
			
		||||
	m_player = player;
 | 
			
		||||
	m_attached_id = attached_id;
 | 
			
		||||
	m_texture = texture;
 | 
			
		||||
	m_texpool = std::move(texpool);
 | 
			
		||||
	m_texcount = texcount;
 | 
			
		||||
	m_time = 0;
 | 
			
		||||
	m_active = 0;
 | 
			
		||||
	m_dying = false;
 | 
			
		||||
 | 
			
		||||
	m_spawntimes.reserve(p.amount + 1);
 | 
			
		||||
	for (u16 i = 0; i <= p.amount; i++) {
 | 
			
		||||
		float spawntime = rand() / (float)RAND_MAX * p.time;
 | 
			
		||||
		float spawntime = myrand_float() * p.time;
 | 
			
		||||
		m_spawntimes.push_back(spawntime);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	size_t max_particles = 0; // maximum number of particles likely to be visible at any given time
 | 
			
		||||
	if (p.time != 0) {
 | 
			
		||||
		auto maxGenerations = p.time / std::min(p.exptime.start.min, p.exptime.end.min);
 | 
			
		||||
		max_particles = p.amount / maxGenerations;
 | 
			
		||||
	} else {
 | 
			
		||||
		auto longestLife = std::max(p.exptime.start.max, p.exptime.end.max);
 | 
			
		||||
		max_particles = p.amount * longestLife;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	p_manager->reserveParticleSpace(max_particles * 1.2);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
namespace {
 | 
			
		||||
	GenericCAO *findObjectByID(ClientEnvironment *env, u16 id) {
 | 
			
		||||
		if (id == 0)
 | 
			
		||||
			return nullptr;
 | 
			
		||||
		return env->getGenericCAO(id);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius,
 | 
			
		||||
	const core::matrix4 *attached_absolute_pos_rot_matrix)
 | 
			
		||||
{
 | 
			
		||||
	float fac = 0;
 | 
			
		||||
	if (p.time != 0) { // ensure safety from divide-by-zeroes
 | 
			
		||||
		fac = m_time / (p.time+0.1f);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	auto r_pos = p.pos.blend(fac);
 | 
			
		||||
	auto r_vel = p.vel.blend(fac);
 | 
			
		||||
	auto r_acc = p.acc.blend(fac);
 | 
			
		||||
	auto r_drag = p.drag.blend(fac);
 | 
			
		||||
	auto r_radius = p.radius.blend(fac);
 | 
			
		||||
	auto r_jitter = p.jitter.blend(fac);
 | 
			
		||||
	auto r_bounce = p.bounce.blend(fac);
 | 
			
		||||
	v3f  attractor_origin = p.attractor_origin.blend(fac);
 | 
			
		||||
	v3f  attractor_direction = p.attractor_direction.blend(fac);
 | 
			
		||||
	auto attractor_obj       = findObjectByID(env, p.attractor_attachment);
 | 
			
		||||
	auto attractor_direction_obj = findObjectByID(env, p.attractor_direction_attachment);
 | 
			
		||||
 | 
			
		||||
	auto r_exp = p.exptime.blend(fac);
 | 
			
		||||
	auto r_size = p.size.blend(fac);
 | 
			
		||||
	auto r_attract = p.attract.blend(fac);
 | 
			
		||||
	auto attract = r_attract.pickWithin();
 | 
			
		||||
 | 
			
		||||
	v3f ppos = m_player->getPosition() / BS;
 | 
			
		||||
	v3f pos = random_v3f(p.minpos, p.maxpos);
 | 
			
		||||
	v3f pos = r_pos.pickWithin();
 | 
			
		||||
	v3f sphere_radius = r_radius.pickWithin();
 | 
			
		||||
 | 
			
		||||
	// Need to apply this first or the following check
 | 
			
		||||
	// will be wrong for attached spawners
 | 
			
		||||
@ -287,15 +428,18 @@ void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius,
 | 
			
		||||
		pos.Z += camera_offset.Z;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (pos.getDistanceFrom(ppos) > radius)
 | 
			
		||||
	if (pos.getDistanceFromSQ(ppos) > radius*radius)
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	// Parameters for the single particle we're about to spawn
 | 
			
		||||
	ParticleParameters pp;
 | 
			
		||||
	pp.pos = pos;
 | 
			
		||||
 | 
			
		||||
	pp.vel = random_v3f(p.minvel, p.maxvel);
 | 
			
		||||
	pp.acc = random_v3f(p.minacc, p.maxacc);
 | 
			
		||||
	pp.vel = r_vel.pickWithin();
 | 
			
		||||
	pp.acc = r_acc.pickWithin();
 | 
			
		||||
	pp.drag = r_drag.pickWithin();
 | 
			
		||||
	pp.jitter = r_jitter;
 | 
			
		||||
	pp.bounce = r_bounce;
 | 
			
		||||
 | 
			
		||||
	if (attached_absolute_pos_rot_matrix) {
 | 
			
		||||
		// Apply attachment rotation
 | 
			
		||||
@ -303,30 +447,137 @@ void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius,
 | 
			
		||||
		attached_absolute_pos_rot_matrix->rotateVect(pp.acc);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pp.expirationtime = random_f32(p.minexptime, p.maxexptime);
 | 
			
		||||
	if (attractor_obj)
 | 
			
		||||
		attractor_origin += attractor_obj->getPosition() / BS;
 | 
			
		||||
	if (attractor_direction_obj) {
 | 
			
		||||
		auto *attractor_absolute_pos_rot_matrix = attractor_direction_obj->getAbsolutePosRotMatrix();
 | 
			
		||||
		if (attractor_absolute_pos_rot_matrix)
 | 
			
		||||
			attractor_absolute_pos_rot_matrix->rotateVect(attractor_direction);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pp.expirationtime = r_exp.pickWithin();
 | 
			
		||||
 | 
			
		||||
	if (sphere_radius != v3f()) {
 | 
			
		||||
		f32 l = sphere_radius.getLength();
 | 
			
		||||
		v3f mag = sphere_radius;
 | 
			
		||||
		mag.normalize();
 | 
			
		||||
 | 
			
		||||
		v3f ofs = v3f(l,0,0);
 | 
			
		||||
		ofs.rotateXZBy(myrand_range(0.f,360.f));
 | 
			
		||||
		ofs.rotateYZBy(myrand_range(0.f,360.f));
 | 
			
		||||
		ofs.rotateXYBy(myrand_range(0.f,360.f));
 | 
			
		||||
 | 
			
		||||
		pp.pos += ofs * mag;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (p.attractor_kind != ParticleParamTypes::AttractorKind::none && attract != 0) {
 | 
			
		||||
		v3f dir;
 | 
			
		||||
		f32 dist = 0; /* =0 necessary to silence warning */
 | 
			
		||||
		switch (p.attractor_kind) {
 | 
			
		||||
			case ParticleParamTypes::AttractorKind::none:
 | 
			
		||||
				break;
 | 
			
		||||
 | 
			
		||||
			case ParticleParamTypes::AttractorKind::point: {
 | 
			
		||||
				dist = pp.pos.getDistanceFrom(attractor_origin);
 | 
			
		||||
				dir = pp.pos - attractor_origin;
 | 
			
		||||
				dir.normalize();
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			case ParticleParamTypes::AttractorKind::line: {
 | 
			
		||||
				// https://github.com/minetest/minetest/issues/11505#issuecomment-915612700
 | 
			
		||||
				const auto& lorigin = attractor_origin;
 | 
			
		||||
				v3f ldir = attractor_direction;
 | 
			
		||||
				ldir.normalize();
 | 
			
		||||
				auto origin_to_point = pp.pos - lorigin;
 | 
			
		||||
				auto scalar_projection = origin_to_point.dotProduct(ldir);
 | 
			
		||||
				auto point_on_line = lorigin + (ldir * scalar_projection);
 | 
			
		||||
 | 
			
		||||
				dist = pp.pos.getDistanceFrom(point_on_line);
 | 
			
		||||
				dir = (point_on_line - pp.pos);
 | 
			
		||||
				dir.normalize();
 | 
			
		||||
				dir *= -1; // flip it around so strength=1 attracts, not repulses
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			case ParticleParamTypes::AttractorKind::plane: {
 | 
			
		||||
				// https://github.com/minetest/minetest/issues/11505#issuecomment-915612700
 | 
			
		||||
				const v3f& porigin = attractor_origin;
 | 
			
		||||
				v3f normal = attractor_direction;
 | 
			
		||||
				normal.normalize();
 | 
			
		||||
				v3f point_to_origin = porigin - pp.pos;
 | 
			
		||||
				f32 factor = normal.dotProduct(point_to_origin);
 | 
			
		||||
				if (numericAbsolute(factor) == 0.0f) {
 | 
			
		||||
					dir = normal;
 | 
			
		||||
				} else {
 | 
			
		||||
					factor = numericSign(factor);
 | 
			
		||||
					dir = normal * factor;
 | 
			
		||||
				}
 | 
			
		||||
				dist = numericAbsolute(normal.dotProduct(pp.pos - porigin));
 | 
			
		||||
				dir *= -1; // flip it around so strength=1 attracts, not repulses
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		f32 speedTowards = numericAbsolute(attract) * dist;
 | 
			
		||||
		v3f avel = dir * speedTowards;
 | 
			
		||||
		if (attract > 0 && speedTowards > 0) {
 | 
			
		||||
			avel *= -1;
 | 
			
		||||
			if (p.attractor_kill) {
 | 
			
		||||
				// make sure the particle dies after crossing the attractor threshold
 | 
			
		||||
				f32 timeToCenter = dist / speedTowards;
 | 
			
		||||
				if (timeToCenter < pp.expirationtime)
 | 
			
		||||
					pp.expirationtime = timeToCenter;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		pp.vel += avel;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	p.copyCommon(pp);
 | 
			
		||||
 | 
			
		||||
	video::ITexture *texture;
 | 
			
		||||
	ClientTexRef texture;
 | 
			
		||||
	v2f texpos, texsize;
 | 
			
		||||
	video::SColor color(0xFFFFFFFF);
 | 
			
		||||
 | 
			
		||||
	if (p.node.getContent() != CONTENT_IGNORE) {
 | 
			
		||||
		const ContentFeatures &f =
 | 
			
		||||
			m_particlemanager->m_env->getGameDef()->ndef()->get(p.node);
 | 
			
		||||
		if (!ParticleManager::getNodeParticleParams(p.node, f, pp, &texture,
 | 
			
		||||
		if (!ParticleManager::getNodeParticleParams(p.node, f, pp, &texture.ref,
 | 
			
		||||
				texpos, texsize, &color, p.node_tile))
 | 
			
		||||
			return;
 | 
			
		||||
	} else {
 | 
			
		||||
		texture = m_texture;
 | 
			
		||||
		if (m_texcount == 0)
 | 
			
		||||
			return;
 | 
			
		||||
		texture = decltype(texture)(m_texpool[m_texcount == 1 ? 0 : myrand_range(0,m_texcount-1)]);
 | 
			
		||||
		texpos = v2f(0.0f, 0.0f);
 | 
			
		||||
		texsize = v2f(1.0f, 1.0f);
 | 
			
		||||
		if (texture.tex->animated)
 | 
			
		||||
			pp.animation = texture.tex->animation;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// synchronize animation length with particle life if desired
 | 
			
		||||
	if (pp.animation.type != TAT_NONE) {
 | 
			
		||||
		if (pp.animation.type == TAT_VERTICAL_FRAMES &&
 | 
			
		||||
			pp.animation.vertical_frames.length < 0) {
 | 
			
		||||
			auto& a = pp.animation.vertical_frames;
 | 
			
		||||
			// we add a tiny extra value to prevent the first frame
 | 
			
		||||
			// from flickering back on just before the particle dies
 | 
			
		||||
			a.length = (pp.expirationtime / -a.length) + 0.1;
 | 
			
		||||
		} else if (pp.animation.type == TAT_SHEET_2D &&
 | 
			
		||||
				   pp.animation.sheet_2d.frame_length < 0) {
 | 
			
		||||
			auto& a = pp.animation.sheet_2d;
 | 
			
		||||
			auto frames = a.frames_w * a.frames_h;
 | 
			
		||||
			auto runtime = (pp.expirationtime / -a.frame_length) + 0.1;
 | 
			
		||||
			pp.animation.sheet_2d.frame_length = frames / runtime;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Allow keeping default random size
 | 
			
		||||
	if (p.maxsize > 0.0f)
 | 
			
		||||
		pp.size = random_f32(p.minsize, p.maxsize);
 | 
			
		||||
	if (p.size.start.max > 0.0f || p.size.end.max > 0.0f)
 | 
			
		||||
		pp.size = r_size.pickWithin();
 | 
			
		||||
 | 
			
		||||
	m_particlemanager->addParticle(new Particle(
 | 
			
		||||
	++m_active;
 | 
			
		||||
	auto pa = new Particle(
 | 
			
		||||
		m_gamedef,
 | 
			
		||||
		m_player,
 | 
			
		||||
		env,
 | 
			
		||||
@ -335,7 +586,9 @@ void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius,
 | 
			
		||||
		texpos,
 | 
			
		||||
		texsize,
 | 
			
		||||
		color
 | 
			
		||||
	));
 | 
			
		||||
	);
 | 
			
		||||
	pa->m_parent = this;
 | 
			
		||||
	m_particlemanager->addParticle(pa);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ParticleSpawner::step(float dtime, ClientEnvironment *env)
 | 
			
		||||
@ -348,7 +601,7 @@ void ParticleSpawner::step(float dtime, ClientEnvironment *env)
 | 
			
		||||
	bool unloaded = false;
 | 
			
		||||
	const core::matrix4 *attached_absolute_pos_rot_matrix = nullptr;
 | 
			
		||||
	if (m_attached_id) {
 | 
			
		||||
		if (GenericCAO *attached = dynamic_cast<GenericCAO *>(env->getActiveObject(m_attached_id))) {
 | 
			
		||||
		if (GenericCAO *attached = env->getGenericCAO(m_attached_id)) {
 | 
			
		||||
			attached_absolute_pos_rot_matrix = attached->getAbsolutePosRotMatrix();
 | 
			
		||||
		} else {
 | 
			
		||||
			unloaded = true;
 | 
			
		||||
@ -379,7 +632,7 @@ void ParticleSpawner::step(float dtime, ClientEnvironment *env)
 | 
			
		||||
			return;
 | 
			
		||||
 | 
			
		||||
		for (int i = 0; i <= p.amount; i++) {
 | 
			
		||||
			if (rand() / (float)RAND_MAX < dtime)
 | 
			
		||||
			if (myrand_float() < dtime)
 | 
			
		||||
				spawnParticle(env, radius, attached_absolute_pos_rot_matrix);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@ -408,9 +661,15 @@ void ParticleManager::stepSpawners(float dtime)
 | 
			
		||||
{
 | 
			
		||||
	MutexAutoLock lock(m_spawner_list_lock);
 | 
			
		||||
	for (auto i = m_particle_spawners.begin(); i != m_particle_spawners.end();) {
 | 
			
		||||
		if (i->second->get_expired()) {
 | 
			
		||||
			delete i->second;
 | 
			
		||||
			m_particle_spawners.erase(i++);
 | 
			
		||||
		if (i->second->getExpired()) {
 | 
			
		||||
			// the particlespawner owns the textures, so we need to make
 | 
			
		||||
			// sure there are no active particles before we free it
 | 
			
		||||
			if (i->second->m_active == 0) {
 | 
			
		||||
				delete i->second;
 | 
			
		||||
				m_particle_spawners.erase(i++);
 | 
			
		||||
			} else {
 | 
			
		||||
				++i;
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			i->second->step(dtime, m_env);
 | 
			
		||||
			++i;
 | 
			
		||||
@ -423,6 +682,10 @@ void ParticleManager::stepParticles(float dtime)
 | 
			
		||||
	MutexAutoLock lock(m_particle_list_lock);
 | 
			
		||||
	for (auto i = m_particles.begin(); i != m_particles.end();) {
 | 
			
		||||
		if ((*i)->get_expired()) {
 | 
			
		||||
			if ((*i)->m_parent) {
 | 
			
		||||
				assert((*i)->m_parent->m_active != 0);
 | 
			
		||||
				--(*i)->m_parent->m_active;
 | 
			
		||||
			}
 | 
			
		||||
			(*i)->remove();
 | 
			
		||||
			delete *i;
 | 
			
		||||
			i = m_particles.erase(i);
 | 
			
		||||
@ -464,13 +727,29 @@ void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
 | 
			
		||||
 | 
			
		||||
			const ParticleSpawnerParameters &p = *event->add_particlespawner.p;
 | 
			
		||||
 | 
			
		||||
			video::ITexture *texture =
 | 
			
		||||
				client->tsrc()->getTextureForMesh(p.texture);
 | 
			
		||||
			// texture pool
 | 
			
		||||
			std::unique_ptr<ClientTexture[]> texpool = nullptr;
 | 
			
		||||
			size_t txpsz = 0;
 | 
			
		||||
			if (!p.texpool.empty()) {
 | 
			
		||||
				txpsz = p.texpool.size();
 | 
			
		||||
				texpool = decltype(texpool)(new ClientTexture [txpsz]);
 | 
			
		||||
 | 
			
		||||
				for (size_t i = 0; i < txpsz; ++i) {
 | 
			
		||||
					texpool[i] = ClientTexture(p.texpool[i], client->tsrc());
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				// no texpool in use, use fallback texture
 | 
			
		||||
				txpsz = 1;
 | 
			
		||||
				texpool = decltype(texpool)(new ClientTexture[1] {
 | 
			
		||||
					ClientTexture(p.texture, client->tsrc())
 | 
			
		||||
				});
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			auto toadd = new ParticleSpawner(client, player,
 | 
			
		||||
					p,
 | 
			
		||||
					event->add_particlespawner.attached_id,
 | 
			
		||||
					texture,
 | 
			
		||||
					texpool,
 | 
			
		||||
					txpsz,
 | 
			
		||||
					this);
 | 
			
		||||
 | 
			
		||||
			addParticleSpawner(event->add_particlespawner.id, toadd);
 | 
			
		||||
@ -481,7 +760,7 @@ void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
 | 
			
		||||
		case CE_SPAWN_PARTICLE: {
 | 
			
		||||
			ParticleParameters &p = *event->spawn_particle;
 | 
			
		||||
 | 
			
		||||
			video::ITexture *texture;
 | 
			
		||||
			ClientTexRef texture;
 | 
			
		||||
			v2f texpos, texsize;
 | 
			
		||||
			video::SColor color(0xFFFFFFFF);
 | 
			
		||||
 | 
			
		||||
@ -489,11 +768,15 @@ void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
 | 
			
		||||
 | 
			
		||||
			if (p.node.getContent() != CONTENT_IGNORE) {
 | 
			
		||||
				const ContentFeatures &f = m_env->getGameDef()->ndef()->get(p.node);
 | 
			
		||||
				if (!getNodeParticleParams(p.node, f, p, &texture, texpos,
 | 
			
		||||
						texsize, &color, p.node_tile))
 | 
			
		||||
					texture = nullptr;
 | 
			
		||||
				getNodeParticleParams(p.node, f, p, &texture.ref, texpos,
 | 
			
		||||
						texsize, &color, p.node_tile);
 | 
			
		||||
			} else {
 | 
			
		||||
				texture = client->tsrc()->getTextureForMesh(p.texture);
 | 
			
		||||
				/* with no particlespawner to own the texture, we need
 | 
			
		||||
				 * to save it on the heap. it will be freed when the
 | 
			
		||||
				 * particle is destroyed */
 | 
			
		||||
				auto texstore = new ClientTexture(p.texture, client->tsrc());
 | 
			
		||||
 | 
			
		||||
				texture = ClientTexRef(*texstore);
 | 
			
		||||
				texpos = v2f(0.0f, 0.0f);
 | 
			
		||||
				texsize = v2f(1.0f, 1.0f);
 | 
			
		||||
			}
 | 
			
		||||
@ -502,7 +785,7 @@ void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
 | 
			
		||||
			if (oldsize > 0.0f)
 | 
			
		||||
				p.size = oldsize;
 | 
			
		||||
 | 
			
		||||
			if (texture) {
 | 
			
		||||
			if (texture.ref) {
 | 
			
		||||
				Particle *toadd = new Particle(client, player, m_env,
 | 
			
		||||
						p, texture, texpos, texsize, color);
 | 
			
		||||
 | 
			
		||||
@ -529,7 +812,7 @@ bool ParticleManager::getNodeParticleParams(const MapNode &n,
 | 
			
		||||
	if (tilenum > 0 && tilenum <= 6)
 | 
			
		||||
		texid = tilenum - 1;
 | 
			
		||||
	else
 | 
			
		||||
		texid = rand() % 6;
 | 
			
		||||
		texid = myrand_range(0,5);
 | 
			
		||||
	const TileLayer &tile = f.tiles[texid].layers[0];
 | 
			
		||||
	p.animation.type = TAT_NONE;
 | 
			
		||||
 | 
			
		||||
@ -539,13 +822,13 @@ bool ParticleManager::getNodeParticleParams(const MapNode &n,
 | 
			
		||||
	else
 | 
			
		||||
		*texture = tile.texture;
 | 
			
		||||
 | 
			
		||||
	float size = (rand() % 8) / 64.0f;
 | 
			
		||||
	float size = (myrand_range(0,8)) / 64.0f;
 | 
			
		||||
	p.size = BS * size;
 | 
			
		||||
	if (tile.scale)
 | 
			
		||||
		size /= tile.scale;
 | 
			
		||||
	texsize = v2f(size * 2.0f, size * 2.0f);
 | 
			
		||||
	texpos.X = (rand() % 64) / 64.0f - texsize.X;
 | 
			
		||||
	texpos.Y = (rand() % 64) / 64.0f - texsize.Y;
 | 
			
		||||
	texpos.X = (myrand_range(0,64)) / 64.0f - texsize.X;
 | 
			
		||||
	texpos.Y = (myrand_range(0,64)) / 64.0f - texsize.Y;
 | 
			
		||||
 | 
			
		||||
	if (tile.has_color)
 | 
			
		||||
		*color = tile.color;
 | 
			
		||||
@ -577,20 +860,20 @@ void ParticleManager::addNodeParticle(IGameDef *gamedef,
 | 
			
		||||
	LocalPlayer *player, v3s16 pos, const MapNode &n, const ContentFeatures &f)
 | 
			
		||||
{
 | 
			
		||||
	ParticleParameters p;
 | 
			
		||||
	video::ITexture *texture;
 | 
			
		||||
	video::ITexture *ref = nullptr;
 | 
			
		||||
	v2f texpos, texsize;
 | 
			
		||||
	video::SColor color;
 | 
			
		||||
 | 
			
		||||
	if (!getNodeParticleParams(n, f, p, &texture, texpos, texsize, &color))
 | 
			
		||||
	if (!getNodeParticleParams(n, f, p, &ref, texpos, texsize, &color))
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	p.expirationtime = (rand() % 100) / 100.0f;
 | 
			
		||||
	p.expirationtime = myrand_range(0, 100) / 100.0f;
 | 
			
		||||
 | 
			
		||||
	// Physics
 | 
			
		||||
	p.vel = v3f(
 | 
			
		||||
		(rand() % 150) / 50.0f - 1.5f,
 | 
			
		||||
		(rand() % 150) / 50.0f,
 | 
			
		||||
		(rand() % 150) / 50.0f - 1.5f
 | 
			
		||||
		myrand_range(-1.5f,1.5f),
 | 
			
		||||
		myrand_range(0.f,3.f),
 | 
			
		||||
		myrand_range(-1.5f,1.5f)
 | 
			
		||||
	);
 | 
			
		||||
	p.acc = v3f(
 | 
			
		||||
		0.0f,
 | 
			
		||||
@ -598,9 +881,9 @@ void ParticleManager::addNodeParticle(IGameDef *gamedef,
 | 
			
		||||
		0.0f
 | 
			
		||||
	);
 | 
			
		||||
	p.pos = v3f(
 | 
			
		||||
		(f32)pos.X + (rand() % 100) / 200.0f - 0.25f,
 | 
			
		||||
		(f32)pos.Y + (rand() % 100) / 200.0f - 0.25f,
 | 
			
		||||
		(f32)pos.Z + (rand() % 100) / 200.0f - 0.25f
 | 
			
		||||
		(f32)pos.X + myrand_range(0.f, .5f) - .25f,
 | 
			
		||||
		(f32)pos.Y + myrand_range(0.f, .5f) - .25f,
 | 
			
		||||
		(f32)pos.Z + myrand_range(0.f, .5f) - .25f
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
	Particle *toadd = new Particle(
 | 
			
		||||
@ -608,7 +891,7 @@ void ParticleManager::addNodeParticle(IGameDef *gamedef,
 | 
			
		||||
		player,
 | 
			
		||||
		m_env,
 | 
			
		||||
		p,
 | 
			
		||||
		texture,
 | 
			
		||||
		ClientTexRef(ref),
 | 
			
		||||
		texpos,
 | 
			
		||||
		texsize,
 | 
			
		||||
		color);
 | 
			
		||||
@ -616,6 +899,12 @@ void ParticleManager::addNodeParticle(IGameDef *gamedef,
 | 
			
		||||
	addParticle(toadd);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ParticleManager::reserveParticleSpace(size_t max_estimate)
 | 
			
		||||
{
 | 
			
		||||
	MutexAutoLock lock(m_particle_list_lock);
 | 
			
		||||
	m_particles.reserve(m_particles.size() + max_estimate);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ParticleManager::addParticle(Particle *toadd)
 | 
			
		||||
{
 | 
			
		||||
	MutexAutoLock lock(m_particle_list_lock);
 | 
			
		||||
@ -634,7 +923,6 @@ void ParticleManager::deleteParticleSpawner(u64 id)
 | 
			
		||||
	MutexAutoLock lock(m_spawner_list_lock);
 | 
			
		||||
	auto it = m_particle_spawners.find(id);
 | 
			
		||||
	if (it != m_particle_spawners.end()) {
 | 
			
		||||
		delete it->second;
 | 
			
		||||
		m_particle_spawners.erase(it);
 | 
			
		||||
		it->second->setDying();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -31,20 +31,53 @@ class ClientEnvironment;
 | 
			
		||||
struct MapNode;
 | 
			
		||||
struct ContentFeatures;
 | 
			
		||||
 | 
			
		||||
struct ClientTexture
 | 
			
		||||
{
 | 
			
		||||
	/* per-spawner structure used to store the ParticleTexture structs
 | 
			
		||||
	 * that spawned particles will refer to through ClientTexRef */
 | 
			
		||||
	ParticleTexture tex;
 | 
			
		||||
	video::ITexture *ref = nullptr;
 | 
			
		||||
 | 
			
		||||
	ClientTexture() = default;
 | 
			
		||||
	ClientTexture(const ClientTexture&) = default;
 | 
			
		||||
	ClientTexture(const ServerParticleTexture& p, ITextureSource *t):
 | 
			
		||||
			tex(p),
 | 
			
		||||
			ref(t->getTextureForMesh(p.string)) {};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct ClientTexRef
 | 
			
		||||
{
 | 
			
		||||
	/* per-particle structure used to avoid massively duplicating the
 | 
			
		||||
	 * fairly large ParticleTexture struct */
 | 
			
		||||
	ParticleTexture* tex = nullptr;
 | 
			
		||||
	video::ITexture* ref = nullptr;
 | 
			
		||||
	ClientTexRef() = default;
 | 
			
		||||
	ClientTexRef(const ClientTexRef&) = default;
 | 
			
		||||
 | 
			
		||||
	/* constructor used by particles spawned from a spawner */
 | 
			
		||||
	ClientTexRef(ClientTexture& t):
 | 
			
		||||
			tex(&t.tex), ref(t.ref) {};
 | 
			
		||||
 | 
			
		||||
	/* constructor used for node particles */
 | 
			
		||||
	ClientTexRef(decltype(ref) tp): ref(tp) {};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class ParticleSpawner;
 | 
			
		||||
 | 
			
		||||
class Particle : public scene::ISceneNode
 | 
			
		||||
{
 | 
			
		||||
	public:
 | 
			
		||||
public:
 | 
			
		||||
	Particle(
 | 
			
		||||
		IGameDef* gamedef,
 | 
			
		||||
		IGameDef *gamedef,
 | 
			
		||||
		LocalPlayer *player,
 | 
			
		||||
		ClientEnvironment *env,
 | 
			
		||||
		const ParticleParameters &p,
 | 
			
		||||
		video::ITexture *texture,
 | 
			
		||||
		const ClientTexRef &texture,
 | 
			
		||||
		v2f texpos,
 | 
			
		||||
		v2f texsize,
 | 
			
		||||
		video::SColor color
 | 
			
		||||
	);
 | 
			
		||||
	~Particle() = default;
 | 
			
		||||
	~Particle();
 | 
			
		||||
 | 
			
		||||
	virtual const aabb3f &getBoundingBox() const
 | 
			
		||||
	{
 | 
			
		||||
@ -69,9 +102,12 @@ class Particle : public scene::ISceneNode
 | 
			
		||||
	bool get_expired ()
 | 
			
		||||
	{ return m_expiration < m_time; }
 | 
			
		||||
 | 
			
		||||
	ParticleSpawner *m_parent;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	void updateLight();
 | 
			
		||||
	void updateVertices();
 | 
			
		||||
	void setVertexAlpha(float a);
 | 
			
		||||
 | 
			
		||||
	video::S3DVertex m_vertices[4];
 | 
			
		||||
	float m_time = 0.0f;
 | 
			
		||||
@ -81,14 +117,19 @@ private:
 | 
			
		||||
	IGameDef *m_gamedef;
 | 
			
		||||
	aabb3f m_box;
 | 
			
		||||
	aabb3f m_collisionbox;
 | 
			
		||||
	ClientTexRef m_texture;
 | 
			
		||||
	video::SMaterial m_material;
 | 
			
		||||
	v2f m_texpos;
 | 
			
		||||
	v2f m_texsize;
 | 
			
		||||
	v3f m_pos;
 | 
			
		||||
	v3f m_velocity;
 | 
			
		||||
	v3f m_acceleration;
 | 
			
		||||
	v3f m_drag;
 | 
			
		||||
	ParticleParamTypes::v3fRange m_jitter;
 | 
			
		||||
	ParticleParamTypes::f32Range m_bounce;
 | 
			
		||||
	LocalPlayer *m_player;
 | 
			
		||||
	float m_size;
 | 
			
		||||
 | 
			
		||||
	//! Color without lighting
 | 
			
		||||
	video::SColor m_base_color;
 | 
			
		||||
	//! Final rendered color
 | 
			
		||||
@ -102,24 +143,27 @@ private:
 | 
			
		||||
	float m_animation_time = 0.0f;
 | 
			
		||||
	int m_animation_frame = 0;
 | 
			
		||||
	u8 m_glow;
 | 
			
		||||
	float m_alpha = 0.0f;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class ParticleSpawner
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
	ParticleSpawner(IGameDef* gamedef,
 | 
			
		||||
	ParticleSpawner(IGameDef *gamedef,
 | 
			
		||||
		LocalPlayer *player,
 | 
			
		||||
		const ParticleSpawnerParameters &p,
 | 
			
		||||
		u16 attached_id,
 | 
			
		||||
		video::ITexture *texture,
 | 
			
		||||
		std::unique_ptr<ClientTexture[]> &texpool,
 | 
			
		||||
		size_t texcount,
 | 
			
		||||
		ParticleManager* p_manager);
 | 
			
		||||
 | 
			
		||||
	~ParticleSpawner() = default;
 | 
			
		||||
 | 
			
		||||
	void step(float dtime, ClientEnvironment *env);
 | 
			
		||||
 | 
			
		||||
	bool get_expired ()
 | 
			
		||||
	{ return p.amount <= 0 && p.time != 0; }
 | 
			
		||||
	size_t m_active;
 | 
			
		||||
 | 
			
		||||
	bool getExpired() const
 | 
			
		||||
	{ return m_dying || (p.amount <= 0 && p.time != 0); }
 | 
			
		||||
	void setDying() { m_dying = true; }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	void spawnParticle(ClientEnvironment *env, float radius,
 | 
			
		||||
@ -127,10 +171,12 @@ private:
 | 
			
		||||
 | 
			
		||||
	ParticleManager *m_particlemanager;
 | 
			
		||||
	float m_time;
 | 
			
		||||
	bool m_dying;
 | 
			
		||||
	IGameDef *m_gamedef;
 | 
			
		||||
	LocalPlayer *m_player;
 | 
			
		||||
	ParticleSpawnerParameters p;
 | 
			
		||||
	video::ITexture *m_texture;
 | 
			
		||||
	std::unique_ptr<ClientTexture[]> m_texpool;
 | 
			
		||||
	size_t m_texcount;
 | 
			
		||||
	std::vector<float> m_spawntimes;
 | 
			
		||||
	u16 m_attached_id;
 | 
			
		||||
};
 | 
			
		||||
@ -156,6 +202,8 @@ public:
 | 
			
		||||
	void addNodeParticle(IGameDef *gamedef, LocalPlayer *player, v3s16 pos,
 | 
			
		||||
		const MapNode &n, const ContentFeatures &f);
 | 
			
		||||
 | 
			
		||||
	void reserveParticleSpace(size_t max_estimate);
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * This function is only used by client particle spawners
 | 
			
		||||
	 *
 | 
			
		||||
 | 
			
		||||
@ -994,18 +994,18 @@ void Client::handleCommand_AddParticleSpawner(NetworkPacket* pkt)
 | 
			
		||||
 | 
			
		||||
	p.amount             = readU16(is);
 | 
			
		||||
	p.time               = readF32(is);
 | 
			
		||||
	p.minpos             = readV3F32(is);
 | 
			
		||||
	p.maxpos             = readV3F32(is);
 | 
			
		||||
	p.minvel             = readV3F32(is);
 | 
			
		||||
	p.maxvel             = readV3F32(is);
 | 
			
		||||
	p.minacc             = readV3F32(is);
 | 
			
		||||
	p.maxacc             = readV3F32(is);
 | 
			
		||||
	p.minexptime         = readF32(is);
 | 
			
		||||
	p.maxexptime         = readF32(is);
 | 
			
		||||
	p.minsize            = readF32(is);
 | 
			
		||||
	p.maxsize            = readF32(is);
 | 
			
		||||
 | 
			
		||||
	// older protocols do not support tweening, and send only
 | 
			
		||||
	// static ranges, so we can't just use the normal serialization
 | 
			
		||||
	// functions for the older values.
 | 
			
		||||
	p.pos.start.legacyDeSerialize(is);
 | 
			
		||||
	p.vel.start.legacyDeSerialize(is);
 | 
			
		||||
	p.acc.start.legacyDeSerialize(is);
 | 
			
		||||
	p.exptime.start.legacyDeSerialize(is);
 | 
			
		||||
	p.size.start.legacyDeSerialize(is);
 | 
			
		||||
 | 
			
		||||
	p.collisiondetection = readU8(is);
 | 
			
		||||
	p.texture            = deSerializeString32(is);
 | 
			
		||||
	p.texture.string     = deSerializeString32(is);
 | 
			
		||||
 | 
			
		||||
	server_id = readU32(is);
 | 
			
		||||
 | 
			
		||||
@ -1018,6 +1018,8 @@ void Client::handleCommand_AddParticleSpawner(NetworkPacket* pkt)
 | 
			
		||||
	p.glow = readU8(is);
 | 
			
		||||
	p.object_collision = readU8(is);
 | 
			
		||||
 | 
			
		||||
	bool legacy_format = true;
 | 
			
		||||
 | 
			
		||||
	// This is kinda awful
 | 
			
		||||
	do {
 | 
			
		||||
		u16 tmp_param0 = readU16(is);
 | 
			
		||||
@ -1026,7 +1028,70 @@ void Client::handleCommand_AddParticleSpawner(NetworkPacket* pkt)
 | 
			
		||||
		p.node.param0 = tmp_param0;
 | 
			
		||||
		p.node.param2 = readU8(is);
 | 
			
		||||
		p.node_tile   = readU8(is);
 | 
			
		||||
	} while (0);
 | 
			
		||||
 | 
			
		||||
		// v >= 5.6.0
 | 
			
		||||
		f32 tmp_sbias = readF32(is);
 | 
			
		||||
		if (is.eof())
 | 
			
		||||
			break;
 | 
			
		||||
 | 
			
		||||
		// initial bias must be stored separately in the stream to preserve
 | 
			
		||||
		// backwards compatibility with older clients, which do not support
 | 
			
		||||
		// a bias field in their range "format"
 | 
			
		||||
		p.pos.start.bias = tmp_sbias;
 | 
			
		||||
		p.vel.start.bias = readF32(is);
 | 
			
		||||
		p.acc.start.bias = readF32(is);
 | 
			
		||||
		p.exptime.start.bias = readF32(is);
 | 
			
		||||
		p.size.start.bias = readF32(is);
 | 
			
		||||
 | 
			
		||||
		p.pos.end.deSerialize(is);
 | 
			
		||||
		p.vel.end.deSerialize(is);
 | 
			
		||||
		p.acc.end.deSerialize(is);
 | 
			
		||||
		p.exptime.end.deSerialize(is);
 | 
			
		||||
		p.size.end.deSerialize(is);
 | 
			
		||||
 | 
			
		||||
		// properties for legacy texture field
 | 
			
		||||
		p.texture.deSerialize(is, m_proto_ver, true);
 | 
			
		||||
 | 
			
		||||
		p.drag.deSerialize(is);
 | 
			
		||||
		p.jitter.deSerialize(is);
 | 
			
		||||
		p.bounce.deSerialize(is);
 | 
			
		||||
		ParticleParamTypes::deSerializeParameterValue(is, p.attractor_kind);
 | 
			
		||||
		using ParticleParamTypes::AttractorKind;
 | 
			
		||||
		if (p.attractor_kind != AttractorKind::none) {
 | 
			
		||||
			p.attract.deSerialize(is);
 | 
			
		||||
			p.attractor_origin.deSerialize(is);
 | 
			
		||||
			p.attractor_attachment = readU16(is);
 | 
			
		||||
			/* we only check the first bit, in order to allow this value
 | 
			
		||||
			 * to be turned into a bit flag field later if needed */
 | 
			
		||||
			p.attractor_kill = !!(readU8(is) & 1);
 | 
			
		||||
			if (p.attractor_kind != AttractorKind::point) {
 | 
			
		||||
				p.attractor_direction.deSerialize(is);
 | 
			
		||||
				p.attractor_direction_attachment = readU16(is);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		p.radius.deSerialize(is);
 | 
			
		||||
 | 
			
		||||
		u16 texpoolsz = readU16(is);
 | 
			
		||||
		p.texpool.reserve(texpoolsz);
 | 
			
		||||
		for (u16 i = 0; i < texpoolsz; ++i) {
 | 
			
		||||
			ServerParticleTexture newtex;
 | 
			
		||||
			newtex.deSerialize(is, m_proto_ver);
 | 
			
		||||
			p.texpool.push_back(newtex);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		legacy_format = false;
 | 
			
		||||
	} while(0);
 | 
			
		||||
 | 
			
		||||
	if (legacy_format) {
 | 
			
		||||
		// there's no tweening data to be had, so we need to set the
 | 
			
		||||
		// legacy params to constant values, otherwise everything old
 | 
			
		||||
		// will tween to zero
 | 
			
		||||
		p.pos.end = p.pos.start;
 | 
			
		||||
		p.vel.end = p.vel.start;
 | 
			
		||||
		p.acc.end = p.acc.start;
 | 
			
		||||
		p.exptime.end = p.exptime.start;
 | 
			
		||||
		p.size.end = p.size.start;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	auto event = new ClientEvent();
 | 
			
		||||
	event->type                            = CE_ADD_PARTICLESPAWNER;
 | 
			
		||||
 | 
			
		||||
@ -207,6 +207,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 | 
			
		||||
		Minimap modes
 | 
			
		||||
	PROTOCOL VERSION 40:
 | 
			
		||||
		TOCLIENT_MEDIA_PUSH changed, TOSERVER_HAVE_MEDIA added
 | 
			
		||||
		Added new particlespawner parameters (5.6.0)
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
#define LATEST_PROTOCOL_VERSION 40
 | 
			
		||||
@ -511,11 +512,12 @@ enum ToClientCommand
 | 
			
		||||
 | 
			
		||||
	TOCLIENT_SPAWN_PARTICLE = 0x46,
 | 
			
		||||
	/*
 | 
			
		||||
		v3f1000 pos
 | 
			
		||||
		v3f1000 velocity
 | 
			
		||||
		v3f1000 acceleration
 | 
			
		||||
		f1000 expirationtime
 | 
			
		||||
		f1000 size
 | 
			
		||||
		-- struct range<T> { T min, T max, f32 bias };
 | 
			
		||||
		v3f pos
 | 
			
		||||
		v3f velocity
 | 
			
		||||
		v3f acceleration
 | 
			
		||||
		f32 expirationtime
 | 
			
		||||
		f32 size
 | 
			
		||||
		u8 bool collisiondetection
 | 
			
		||||
		u32 len
 | 
			
		||||
		u8[len] texture
 | 
			
		||||
@ -524,22 +526,26 @@ enum ToClientCommand
 | 
			
		||||
		TileAnimation animation
 | 
			
		||||
		u8 glow
 | 
			
		||||
		u8 object_collision
 | 
			
		||||
		v3f drag
 | 
			
		||||
		range<v3f> bounce
 | 
			
		||||
	*/
 | 
			
		||||
 | 
			
		||||
	TOCLIENT_ADD_PARTICLESPAWNER = 0x47,
 | 
			
		||||
	/*
 | 
			
		||||
		-- struct range<T> { T min, T max, f32 bias };
 | 
			
		||||
		-- struct tween<T> { T start, T end };
 | 
			
		||||
		u16 amount
 | 
			
		||||
		f1000 spawntime
 | 
			
		||||
		v3f1000 minpos
 | 
			
		||||
		v3f1000 maxpos
 | 
			
		||||
		v3f1000 minvel
 | 
			
		||||
		v3f1000 maxvel
 | 
			
		||||
		v3f1000 minacc
 | 
			
		||||
		v3f1000 maxacc
 | 
			
		||||
		f1000 minexptime
 | 
			
		||||
		f1000 maxexptime
 | 
			
		||||
		f1000 minsize
 | 
			
		||||
		f1000 maxsize
 | 
			
		||||
		f32 spawntime
 | 
			
		||||
		v3f minpos
 | 
			
		||||
		v3f maxpos
 | 
			
		||||
		v3f minvel
 | 
			
		||||
		v3f maxvel
 | 
			
		||||
		v3f minacc
 | 
			
		||||
		v3f maxacc
 | 
			
		||||
		f32 minexptime
 | 
			
		||||
		f32 maxexptime
 | 
			
		||||
		f32 minsize
 | 
			
		||||
		f32 maxsize
 | 
			
		||||
		u8 bool collisiondetection
 | 
			
		||||
		u32 len
 | 
			
		||||
		u8[len] texture
 | 
			
		||||
@ -549,6 +555,63 @@ enum ToClientCommand
 | 
			
		||||
		TileAnimation animation
 | 
			
		||||
		u8 glow
 | 
			
		||||
		u8 object_collision
 | 
			
		||||
 | 
			
		||||
		f32 pos_start_bias
 | 
			
		||||
		f32 vel_start_bias
 | 
			
		||||
		f32 acc_start_bias
 | 
			
		||||
		f32 exptime_start_bias
 | 
			
		||||
		f32 size_start_bias
 | 
			
		||||
 | 
			
		||||
		range<v3f> pos_end
 | 
			
		||||
		-- i.e v3f pos_end_min
 | 
			
		||||
		--     v3f pos_end_max
 | 
			
		||||
		--     f32 pos_end_bias
 | 
			
		||||
		range<v3f> vel_end
 | 
			
		||||
		range<v3f> acc_end
 | 
			
		||||
 | 
			
		||||
		tween<range<v3f>> drag
 | 
			
		||||
		-- i.e. v3f drag_start_min
 | 
			
		||||
		--      v3f drag_start_max
 | 
			
		||||
		--      f32 drag_start_bias
 | 
			
		||||
		--      v3f drag_end_min
 | 
			
		||||
		--      v3f drag_end_max
 | 
			
		||||
		--      f32 drag_end_bias
 | 
			
		||||
		tween<range<v3f>> jitter
 | 
			
		||||
		tween<range<f32>> bounce
 | 
			
		||||
 | 
			
		||||
		u8 attraction_kind
 | 
			
		||||
			none  = 0
 | 
			
		||||
			point = 1
 | 
			
		||||
			line  = 2
 | 
			
		||||
			plane = 3
 | 
			
		||||
 | 
			
		||||
		if attraction_kind > none {
 | 
			
		||||
			tween<range<f32>> attract_strength
 | 
			
		||||
			tween<v3f>        attractor_origin
 | 
			
		||||
			u16               attractor_origin_attachment_object_id
 | 
			
		||||
			u8                spawner_flags
 | 
			
		||||
			    bit 1: attractor_kill (particles dies on contact)
 | 
			
		||||
			if attraction_mode > point {
 | 
			
		||||
				tween<v3f> attractor_angle
 | 
			
		||||
				u16        attractor_origin_attachment_object_id
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		tween<range<v3f>> radius
 | 
			
		||||
		tween<range<v3f>> drag
 | 
			
		||||
 | 
			
		||||
		u16 texpool_sz
 | 
			
		||||
		texpool_sz.times {
 | 
			
		||||
			u8 flags
 | 
			
		||||
			-- bit 0: animated
 | 
			
		||||
			-- other bits free & ignored as of proto v40
 | 
			
		||||
			tween<f32> alpha
 | 
			
		||||
			tween<v2f> scale
 | 
			
		||||
			if flags.animated {
 | 
			
		||||
				TileAnimation animation
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	*/
 | 
			
		||||
 | 
			
		||||
	TOCLIENT_DELETE_PARTICLESPAWNER_LEGACY = 0x48, // Obsolete
 | 
			
		||||
 | 
			
		||||
@ -18,7 +18,103 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
#include "particles.h"
 | 
			
		||||
#include "util/serialize.h"
 | 
			
		||||
#include <type_traits>
 | 
			
		||||
using namespace ParticleParamTypes;
 | 
			
		||||
 | 
			
		||||
#define PARAM_PVFN(n) ParticleParamTypes::n##ParameterValue
 | 
			
		||||
v2f PARAM_PVFN(pick) (float* f, const v2f a, const v2f b) {
 | 
			
		||||
	return v2f(
 | 
			
		||||
		numericalBlend(f[0], a.X, b.X),
 | 
			
		||||
		numericalBlend(f[1], a.Y, b.Y)
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
v3f PARAM_PVFN(pick) (float* f, const v3f a, const v3f b) {
 | 
			
		||||
	return v3f(
 | 
			
		||||
		numericalBlend(f[0], a.X, b.X),
 | 
			
		||||
		numericalBlend(f[1], a.Y, b.Y),
 | 
			
		||||
		numericalBlend(f[2], a.Z, b.Z)
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
v2f PARAM_PVFN(interpolate) (float fac, const v2f a, const v2f b)
 | 
			
		||||
	{ return b.getInterpolated(a, fac); }
 | 
			
		||||
v3f PARAM_PVFN(interpolate) (float fac, const v3f a, const v3f b)
 | 
			
		||||
	{ return b.getInterpolated(a, fac); }
 | 
			
		||||
 | 
			
		||||
#define PARAM_DEF_SRZR(T, wr, rd) \
 | 
			
		||||
	void PARAM_PVFN(serialize)  (std::ostream& os, T  v) {wr(os,v);  } \
 | 
			
		||||
	void PARAM_PVFN(deSerialize)(std::istream& is, T& v) {v = rd(is);}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#define PARAM_DEF_NUM(T, wr, rd) PARAM_DEF_SRZR(T, wr, rd) \
 | 
			
		||||
	T PARAM_PVFN(interpolate)(float fac, const T a, const T b) \
 | 
			
		||||
			{ return numericalBlend<T>(fac,a,b); } \
 | 
			
		||||
	T PARAM_PVFN(pick)       (float* f, const T a, const T b) \
 | 
			
		||||
			{ return numericalBlend<T>(f[0],a,b); }
 | 
			
		||||
 | 
			
		||||
PARAM_DEF_NUM(u8,  writeU8,    readU8);  PARAM_DEF_NUM(s8,  writeS8,    readS8);
 | 
			
		||||
PARAM_DEF_NUM(u16, writeU16,   readU16); PARAM_DEF_NUM(s16, writeS16,   readS16);
 | 
			
		||||
PARAM_DEF_NUM(u32, writeU32,   readU32); PARAM_DEF_NUM(s32, writeS32,   readS32);
 | 
			
		||||
PARAM_DEF_NUM(f32, writeF32,   readF32);
 | 
			
		||||
PARAM_DEF_SRZR(v2f, writeV2F32, readV2F32);
 | 
			
		||||
PARAM_DEF_SRZR(v3f, writeV3F32, readV3F32);
 | 
			
		||||
 | 
			
		||||
enum class ParticleTextureFlags : u8 {
 | 
			
		||||
	/* each value specifies a bit in a bitmask; if the maximum value
 | 
			
		||||
	 * goes above 1<<7 the type of the flags field must be changed
 | 
			
		||||
	 * from u8, which will necessitate a protocol change! */
 | 
			
		||||
 | 
			
		||||
	// the first bit indicates whether the texture is animated
 | 
			
		||||
	animated = 1,
 | 
			
		||||
 | 
			
		||||
	/* the next three bits indicate the blending mode of the texture
 | 
			
		||||
	 * blendmode is encoded by (flags |= (u8)blend << 1); retrieve with
 | 
			
		||||
	 * (flags & ParticleTextureFlags::blend) >> 1. note that the third
 | 
			
		||||
	 * bit is currently reserved for adding more blend modes in the future */
 | 
			
		||||
	blend = 0x7 << 1,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/* define some shorthand so we don't have to repeat ourselves or use
 | 
			
		||||
 * decltype everywhere */
 | 
			
		||||
using FlagT = std::underlying_type_t<ParticleTextureFlags>;
 | 
			
		||||
 | 
			
		||||
void ServerParticleTexture::serialize(std::ostream &os, u16 protocol_ver, bool newPropertiesOnly) const
 | 
			
		||||
{
 | 
			
		||||
	/* newPropertiesOnly is used to de/serialize parameters of the legacy texture
 | 
			
		||||
	 * field, which are encoded separately from the texspec string */
 | 
			
		||||
	FlagT flags = 0;
 | 
			
		||||
	if (animated)
 | 
			
		||||
		flags |= FlagT(ParticleTextureFlags::animated);
 | 
			
		||||
	if (blendmode != BlendMode::alpha)
 | 
			
		||||
		flags |= FlagT(blendmode) << 1;
 | 
			
		||||
	serializeParameterValue(os, flags);
 | 
			
		||||
 | 
			
		||||
	alpha.serialize(os);
 | 
			
		||||
	scale.serialize(os);
 | 
			
		||||
	if (!newPropertiesOnly)
 | 
			
		||||
		os << serializeString32(string);
 | 
			
		||||
 | 
			
		||||
	if (animated)
 | 
			
		||||
		animation.serialize(os, protocol_ver);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ServerParticleTexture::deSerialize(std::istream &is, u16 protocol_ver, bool newPropertiesOnly)
 | 
			
		||||
{
 | 
			
		||||
	FlagT flags = 0;
 | 
			
		||||
	deSerializeParameterValue(is, flags);
 | 
			
		||||
 | 
			
		||||
	animated = !!(flags & FlagT(ParticleTextureFlags::animated));
 | 
			
		||||
	blendmode = BlendMode((flags & FlagT(ParticleTextureFlags::blend)) >> 1);
 | 
			
		||||
 | 
			
		||||
	alpha.deSerialize(is);
 | 
			
		||||
	scale.deSerialize(is);
 | 
			
		||||
	if (!newPropertiesOnly)
 | 
			
		||||
		string = deSerializeString32(is);
 | 
			
		||||
 | 
			
		||||
	if (animated)
 | 
			
		||||
		animation.deSerialize(is, protocol_ver);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ParticleParameters::serialize(std::ostream &os, u16 protocol_ver) const
 | 
			
		||||
{
 | 
			
		||||
@ -28,7 +124,7 @@ void ParticleParameters::serialize(std::ostream &os, u16 protocol_ver) const
 | 
			
		||||
	writeF32(os, expirationtime);
 | 
			
		||||
	writeF32(os, size);
 | 
			
		||||
	writeU8(os, collisiondetection);
 | 
			
		||||
	os << serializeString32(texture);
 | 
			
		||||
	os << serializeString32(texture.string);
 | 
			
		||||
	writeU8(os, vertical);
 | 
			
		||||
	writeU8(os, collision_removal);
 | 
			
		||||
	animation.serialize(os, 6); /* NOT the protocol ver */
 | 
			
		||||
@ -37,6 +133,20 @@ void ParticleParameters::serialize(std::ostream &os, u16 protocol_ver) const
 | 
			
		||||
	writeU16(os, node.param0);
 | 
			
		||||
	writeU8(os, node.param2);
 | 
			
		||||
	writeU8(os, node_tile);
 | 
			
		||||
	writeV3F32(os, drag);
 | 
			
		||||
	jitter.serialize(os);
 | 
			
		||||
	bounce.serialize(os);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename T, T (reader)(std::istream& is)>
 | 
			
		||||
inline bool streamEndsBeforeParam(T& val, std::istream& is)
 | 
			
		||||
{
 | 
			
		||||
	// This is kinda awful
 | 
			
		||||
	T tmp = reader(is);
 | 
			
		||||
	if (is.eof())
 | 
			
		||||
		return true;
 | 
			
		||||
	val = tmp;
 | 
			
		||||
	return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ParticleParameters::deSerialize(std::istream &is, u16 protocol_ver)
 | 
			
		||||
@ -47,17 +157,20 @@ void ParticleParameters::deSerialize(std::istream &is, u16 protocol_ver)
 | 
			
		||||
	expirationtime     = readF32(is);
 | 
			
		||||
	size               = readF32(is);
 | 
			
		||||
	collisiondetection = readU8(is);
 | 
			
		||||
	texture            = deSerializeString32(is);
 | 
			
		||||
	texture.string     = deSerializeString32(is);
 | 
			
		||||
	vertical           = readU8(is);
 | 
			
		||||
	collision_removal  = readU8(is);
 | 
			
		||||
	animation.deSerialize(is, 6); /* NOT the protocol ver */
 | 
			
		||||
	glow               = readU8(is);
 | 
			
		||||
	object_collision   = readU8(is);
 | 
			
		||||
	// This is kinda awful
 | 
			
		||||
	u16 tmp_param0 = readU16(is);
 | 
			
		||||
	if (is.eof())
 | 
			
		||||
 | 
			
		||||
	if (streamEndsBeforeParam<u16, readU16>(node.param0, is))
 | 
			
		||||
		return;
 | 
			
		||||
	node.param0 = tmp_param0;
 | 
			
		||||
	node.param2 = readU8(is);
 | 
			
		||||
	node_tile   = readU8(is);
 | 
			
		||||
 | 
			
		||||
	if (streamEndsBeforeParam<v3f, readV3F32>(drag, is))
 | 
			
		||||
		return;
 | 
			
		||||
	jitter.deSerialize(is);
 | 
			
		||||
	bounce.deSerialize(is);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										375
									
								
								src/particles.h
									
									
									
									
									
								
							
							
						
						
									
										375
									
								
								src/particles.h
									
									
									
									
									
								
							@ -20,19 +20,352 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <sstream>
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include <ctgmath>
 | 
			
		||||
#include <type_traits>
 | 
			
		||||
#include "irrlichttypes_bloated.h"
 | 
			
		||||
#include "tileanimation.h"
 | 
			
		||||
#include "mapnode.h"
 | 
			
		||||
#include "util/serialize.h"
 | 
			
		||||
#include "util/numeric.h"
 | 
			
		||||
 | 
			
		||||
// This file defines the particle-related structures that both the server and
 | 
			
		||||
// client need. The ParticleManager and rendering is in client/particles.h
 | 
			
		||||
 | 
			
		||||
struct CommonParticleParams {
 | 
			
		||||
namespace ParticleParamTypes
 | 
			
		||||
{
 | 
			
		||||
	template <bool cond, typename T>
 | 
			
		||||
	using enableIf = typename std::enable_if<cond, T>::type;
 | 
			
		||||
	// std::enable_if_t does not appear to be present in GCC????
 | 
			
		||||
	// std::is_enum_v also missing. wtf. these are supposed to be
 | 
			
		||||
	// present as of c++14
 | 
			
		||||
 | 
			
		||||
	template<typename T> using BlendFunction = T(float,T,T);
 | 
			
		||||
	#define DECL_PARAM_SRZRS(type) \
 | 
			
		||||
		void serializeParameterValue  (std::ostream& os, type   v); \
 | 
			
		||||
		void deSerializeParameterValue(std::istream& is, type&  r);
 | 
			
		||||
	#define DECL_PARAM_OVERLOADS(type) DECL_PARAM_SRZRS(type) \
 | 
			
		||||
		type interpolateParameterValue(float  fac,  const type a, const type b); \
 | 
			
		||||
		type pickParameterValue       (float* facs, const type a, const type b);
 | 
			
		||||
 | 
			
		||||
	DECL_PARAM_OVERLOADS(u8);  DECL_PARAM_OVERLOADS(s8);
 | 
			
		||||
	DECL_PARAM_OVERLOADS(u16); DECL_PARAM_OVERLOADS(s16);
 | 
			
		||||
	DECL_PARAM_OVERLOADS(u32); DECL_PARAM_OVERLOADS(s32);
 | 
			
		||||
	DECL_PARAM_OVERLOADS(f32);
 | 
			
		||||
	DECL_PARAM_OVERLOADS(v2f);
 | 
			
		||||
	DECL_PARAM_OVERLOADS(v3f);
 | 
			
		||||
 | 
			
		||||
	/* C++ is a strongly typed language. this means that enums cannot be implicitly
 | 
			
		||||
	 * cast to integers, as they can be in C. while this may sound good in principle,
 | 
			
		||||
	 * it means that our normal serialization functions cannot be called on
 | 
			
		||||
	 * enumerations unless they are explicitly cast to a particular type first. this
 | 
			
		||||
	 * is problematic, because in C++ enums can have any integral type as an underlying
 | 
			
		||||
	 * type, and that type would need to be named everywhere an enumeration is
 | 
			
		||||
	 * de/serialized.
 | 
			
		||||
	 *
 | 
			
		||||
	 * this is obviously not cool, both in terms of writing legible, succinct code,
 | 
			
		||||
	 * and in terms of robustness: the underlying type might be changed at some point,
 | 
			
		||||
	 * e.g. if a bitmask gets too big for its britches. we could use an equivalent of
 | 
			
		||||
	 * `std::to_underlying(value)` everywhere we need to deal with enumerations, but
 | 
			
		||||
	 * that's hideous and unintuitive. instead, we supply the following functions to
 | 
			
		||||
	 * transparently map enumeration types to their underlying values. */
 | 
			
		||||
 | 
			
		||||
	template <typename E, enableIf<std::is_enum<E>::value, bool> = true>
 | 
			
		||||
	void serializeParameterValue(std::ostream& os, E k) {
 | 
			
		||||
		serializeParameterValue(os, (std::underlying_type_t<E>)k);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	template <typename E, enableIf<std::is_enum<E>::value, bool> = true>
 | 
			
		||||
	void deSerializeParameterValue(std::istream& is, E& k) {
 | 
			
		||||
		std::underlying_type_t<E> v;
 | 
			
		||||
		deSerializeParameterValue(is, v);
 | 
			
		||||
		k = (E)v;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* this is your brain on C++. */
 | 
			
		||||
 | 
			
		||||
	template <typename T, size_t PN>
 | 
			
		||||
	struct Parameter
 | 
			
		||||
	{
 | 
			
		||||
		using ValType = T;
 | 
			
		||||
		using pickFactors = float[PN];
 | 
			
		||||
 | 
			
		||||
		T val;
 | 
			
		||||
		using This = Parameter<T, PN>;
 | 
			
		||||
 | 
			
		||||
		Parameter() = default;
 | 
			
		||||
		Parameter(const This& a) = default;
 | 
			
		||||
		template <typename... Args>
 | 
			
		||||
		Parameter(Args... args) : val(args...) {}
 | 
			
		||||
 | 
			
		||||
		virtual void serialize(std::ostream &os) const
 | 
			
		||||
			{ serializeParameterValue  (os, this->val); }
 | 
			
		||||
		virtual void deSerialize(std::istream &is)
 | 
			
		||||
			{ deSerializeParameterValue(is, this->val); }
 | 
			
		||||
 | 
			
		||||
		virtual T interpolate(float fac, const This& against) const
 | 
			
		||||
		{
 | 
			
		||||
			return interpolateParameterValue(fac, this->val, against.val);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		static T pick(float* f, const This& a, const This& b)
 | 
			
		||||
		{
 | 
			
		||||
			return pickParameterValue(f, a.val, b.val);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		operator T() const { return val; }
 | 
			
		||||
		T operator=(T b) { return val = b; }
 | 
			
		||||
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	template <typename T> T numericalBlend(float fac, T min, T max)
 | 
			
		||||
		{ return min + ((max - min) * fac); }
 | 
			
		||||
 | 
			
		||||
	template <typename T, size_t N>
 | 
			
		||||
	struct VectorParameter : public Parameter<T,N> {
 | 
			
		||||
		using This = VectorParameter<T,N>;
 | 
			
		||||
		template <typename... Args>
 | 
			
		||||
		VectorParameter(Args... args) : Parameter<T,N>(args...) {}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	template <typename T, size_t PN>
 | 
			
		||||
	inline std::string dump(const Parameter<T,PN>& p)
 | 
			
		||||
	{
 | 
			
		||||
		return std::to_string(p.val);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	template <typename T, size_t N>
 | 
			
		||||
	inline std::string dump(const VectorParameter<T,N>& v)
 | 
			
		||||
	{
 | 
			
		||||
		std::ostringstream oss;
 | 
			
		||||
		if (N == 3)
 | 
			
		||||
			oss << PP(v.val);
 | 
			
		||||
		else
 | 
			
		||||
			oss << PP2(v.val);
 | 
			
		||||
		return oss.str();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	using u8Parameter  = Parameter<u8,  1>; using s8Parameter  = Parameter<s8,  1>;
 | 
			
		||||
	using u16Parameter = Parameter<u16, 1>; using s16Parameter = Parameter<s16, 1>;
 | 
			
		||||
	using u32Parameter = Parameter<u32, 1>; using s32Parameter = Parameter<s32, 1>;
 | 
			
		||||
 | 
			
		||||
	using f32Parameter = Parameter<f32, 1>;
 | 
			
		||||
 | 
			
		||||
	using v2fParameter = VectorParameter<v2f, 2>;
 | 
			
		||||
	using v3fParameter = VectorParameter<v3f, 3>;
 | 
			
		||||
 | 
			
		||||
	template <typename T>
 | 
			
		||||
	struct RangedParameter
 | 
			
		||||
	{
 | 
			
		||||
		using ValType = T;
 | 
			
		||||
		using This = RangedParameter<T>;
 | 
			
		||||
 | 
			
		||||
		T min, max;
 | 
			
		||||
		f32 bias = 0;
 | 
			
		||||
 | 
			
		||||
		RangedParameter() = default;
 | 
			
		||||
		RangedParameter(const This& a) = default;
 | 
			
		||||
		RangedParameter(T _min, T _max)            : min(_min),  max(_max)  {}
 | 
			
		||||
		template <typename M> RangedParameter(M b) : min(b),     max(b)     {}
 | 
			
		||||
 | 
			
		||||
		// these functions handle the old range serialization "format"; bias must
 | 
			
		||||
		// be manually encoded in a separate part of the stream. NEVER ADD FIELDS
 | 
			
		||||
		// TO THESE FUNCTIONS
 | 
			
		||||
		void legacySerialize(std::ostream& os) const
 | 
			
		||||
		{
 | 
			
		||||
			min.serialize(os);
 | 
			
		||||
			max.serialize(os);
 | 
			
		||||
		}
 | 
			
		||||
		void legacyDeSerialize(std::istream& is)
 | 
			
		||||
		{
 | 
			
		||||
			min.deSerialize(is);
 | 
			
		||||
			max.deSerialize(is);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// these functions handle the format used by new fields. new fields go here
 | 
			
		||||
		void serialize(std::ostream &os) const
 | 
			
		||||
		{
 | 
			
		||||
			legacySerialize(os);
 | 
			
		||||
			writeF32(os, bias);
 | 
			
		||||
		}
 | 
			
		||||
		void deSerialize(std::istream &is)
 | 
			
		||||
		{
 | 
			
		||||
			legacyDeSerialize(is);
 | 
			
		||||
			bias = readF32(is);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		This interpolate(float fac, const This against) const
 | 
			
		||||
		{
 | 
			
		||||
			This r;
 | 
			
		||||
			r.min = min.interpolate(fac, against.min);
 | 
			
		||||
			r.max = max.interpolate(fac, against.max);
 | 
			
		||||
			r.bias = bias;
 | 
			
		||||
			return r;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		T pickWithin() const
 | 
			
		||||
		{
 | 
			
		||||
			typename T::pickFactors values;
 | 
			
		||||
			auto p = numericAbsolute(bias) + 1;
 | 
			
		||||
			for (size_t i = 0; i < sizeof(values) / sizeof(values[0]); ++i) {
 | 
			
		||||
				if (bias < 0)
 | 
			
		||||
					values[i] = 1.0f - pow(myrand_float(), p);
 | 
			
		||||
				else
 | 
			
		||||
					values[i] = pow(myrand_float(), p);
 | 
			
		||||
			}
 | 
			
		||||
			return T::pick(values, min, max);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	template <typename T>
 | 
			
		||||
	inline std::string dump(const RangedParameter<T>& r)
 | 
			
		||||
	{
 | 
			
		||||
		std::ostringstream s;
 | 
			
		||||
		s << "range<" << dump(r.min) << " ~ " << dump(r.max);
 | 
			
		||||
		if (r.bias != 0)
 | 
			
		||||
			s << " :: " << r.bias;
 | 
			
		||||
		s << ">";
 | 
			
		||||
		return s.str();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	enum class TweenStyle : u8 { fwd, rev, pulse, flicker };
 | 
			
		||||
 | 
			
		||||
	template <typename T>
 | 
			
		||||
	struct TweenedParameter
 | 
			
		||||
	{
 | 
			
		||||
		using ValType = T;
 | 
			
		||||
		using This = TweenedParameter<T>;
 | 
			
		||||
 | 
			
		||||
		TweenStyle style = TweenStyle::fwd;
 | 
			
		||||
		u16 reps = 1;
 | 
			
		||||
		f32 beginning = 0.0f;
 | 
			
		||||
 | 
			
		||||
		T start, end;
 | 
			
		||||
 | 
			
		||||
		TweenedParameter() = default;
 | 
			
		||||
		TweenedParameter(const This& a) = default;
 | 
			
		||||
		TweenedParameter(T _start, T _end)          : start(_start),  end(_end) {}
 | 
			
		||||
		template <typename M> TweenedParameter(M b) : start(b),       end(b) {}
 | 
			
		||||
 | 
			
		||||
		T blend(float fac) const
 | 
			
		||||
		{
 | 
			
		||||
			// warp time coordinates in accordance w/ settings
 | 
			
		||||
			if (fac > beginning) {
 | 
			
		||||
				// remap for beginning offset
 | 
			
		||||
				auto len = 1 - beginning;
 | 
			
		||||
				fac -= beginning;
 | 
			
		||||
				fac /= len;
 | 
			
		||||
 | 
			
		||||
				// remap for repetitions
 | 
			
		||||
				fac *= reps;
 | 
			
		||||
				if (fac > 1) // poor man's modulo
 | 
			
		||||
					fac -= (decltype(reps))fac;
 | 
			
		||||
 | 
			
		||||
				// remap for style
 | 
			
		||||
				switch (style) {
 | 
			
		||||
					case TweenStyle::fwd: /* do nothing */  break;
 | 
			
		||||
					case TweenStyle::rev: fac = 1.0f - fac; break;
 | 
			
		||||
					case TweenStyle::pulse:
 | 
			
		||||
					case TweenStyle::flicker: {
 | 
			
		||||
						if (fac > 0.5f) {
 | 
			
		||||
							fac = 1.f - (fac*2.f - 1.f);
 | 
			
		||||
						} else {
 | 
			
		||||
							fac = fac * 2;
 | 
			
		||||
						}
 | 
			
		||||
						if (style == TweenStyle::flicker) {
 | 
			
		||||
							fac *= myrand_range(0.7f, 1.0f);
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				if (fac>1.f)
 | 
			
		||||
					fac = 1.f;
 | 
			
		||||
				else if (fac<0.f)
 | 
			
		||||
					fac = 0.f;
 | 
			
		||||
			} else {
 | 
			
		||||
				fac = (style == TweenStyle::rev) ? 1.f : 0.f;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return start.interpolate(fac, end);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		void serialize(std::ostream &os) const
 | 
			
		||||
		{
 | 
			
		||||
			writeU8(os, static_cast<u8>(style));
 | 
			
		||||
			writeU16(os, reps);
 | 
			
		||||
			writeF32(os, beginning);
 | 
			
		||||
			start.serialize(os);
 | 
			
		||||
			end.serialize(os);
 | 
			
		||||
		}
 | 
			
		||||
		void deSerialize(std::istream &is)
 | 
			
		||||
		{
 | 
			
		||||
			style = static_cast<TweenStyle>(readU8(is));
 | 
			
		||||
			reps = readU16(is);
 | 
			
		||||
			beginning = readF32(is);
 | 
			
		||||
			start.deSerialize(is);
 | 
			
		||||
			end.deSerialize(is);
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	template <typename T>
 | 
			
		||||
	inline std::string dump(const TweenedParameter<T>& t)
 | 
			
		||||
	{
 | 
			
		||||
		std::ostringstream s;
 | 
			
		||||
		const char* icon;
 | 
			
		||||
		switch (t.style) {
 | 
			
		||||
			case TweenStyle::fwd: icon = "→"; break;
 | 
			
		||||
			case TweenStyle::rev: icon = "←"; break;
 | 
			
		||||
			case TweenStyle::pulse: icon = "↔"; break;
 | 
			
		||||
			case TweenStyle::flicker: icon = "↯"; break;
 | 
			
		||||
		}
 | 
			
		||||
		s << "tween<";
 | 
			
		||||
		if (t.reps != 1)
 | 
			
		||||
			s << t.reps << "x ";
 | 
			
		||||
		s << dump(t.start) << " "<<icon<<" " << dump(t.end) << ">";
 | 
			
		||||
		return s.str();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	enum class AttractorKind : u8 { none, point, line, plane };
 | 
			
		||||
	enum class BlendMode     : u8 { alpha, add, sub, screen  };
 | 
			
		||||
 | 
			
		||||
	// these are consistently-named convenience aliases to make code more readable without `using ParticleParamTypes` declarations
 | 
			
		||||
	using v3fRange = RangedParameter<v3fParameter>;
 | 
			
		||||
	using f32Range = RangedParameter<f32Parameter>;
 | 
			
		||||
 | 
			
		||||
	using v2fTween      = TweenedParameter<v2fParameter>;
 | 
			
		||||
	using v3fTween      = TweenedParameter<v3fParameter>;
 | 
			
		||||
	using f32Tween      = TweenedParameter<f32Parameter>;
 | 
			
		||||
	using v3fRangeTween = TweenedParameter<v3fRange>;
 | 
			
		||||
	using f32RangeTween = TweenedParameter<f32Range>;
 | 
			
		||||
 | 
			
		||||
	#undef DECL_PARAM_SRZRS
 | 
			
		||||
	#undef DECL_PARAM_OVERLOADS
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct ParticleTexture
 | 
			
		||||
{
 | 
			
		||||
	bool animated = false;
 | 
			
		||||
	ParticleParamTypes::BlendMode blendmode = ParticleParamTypes::BlendMode::alpha;
 | 
			
		||||
	TileAnimationParams animation;
 | 
			
		||||
	ParticleParamTypes::f32Tween alpha{1.0f};
 | 
			
		||||
	ParticleParamTypes::v2fTween scale{v2f(1.0f)};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct ServerParticleTexture : public ParticleTexture
 | 
			
		||||
{
 | 
			
		||||
	std::string string;
 | 
			
		||||
	void serialize(std::ostream &os, u16 protocol_ver, bool newPropertiesOnly = false) const;
 | 
			
		||||
	void deSerialize(std::istream &is, u16 protocol_ver, bool newPropertiesOnly = false);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct CommonParticleParams
 | 
			
		||||
{
 | 
			
		||||
	bool collisiondetection = false;
 | 
			
		||||
	bool collision_removal = false;
 | 
			
		||||
	bool object_collision = false;
 | 
			
		||||
	bool vertical = false;
 | 
			
		||||
	std::string texture;
 | 
			
		||||
	ServerParticleTexture texture;
 | 
			
		||||
	struct TileAnimationParams animation;
 | 
			
		||||
	u8 glow = 0;
 | 
			
		||||
	MapNode node;
 | 
			
		||||
@ -58,22 +391,42 @@ struct CommonParticleParams {
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct ParticleParameters : CommonParticleParams {
 | 
			
		||||
	v3f pos;
 | 
			
		||||
	v3f vel;
 | 
			
		||||
	v3f acc;
 | 
			
		||||
	f32 expirationtime = 1;
 | 
			
		||||
	f32 size = 1;
 | 
			
		||||
struct ParticleParameters : CommonParticleParams
 | 
			
		||||
{
 | 
			
		||||
	v3f pos, vel, acc, drag;
 | 
			
		||||
	f32 size = 1, expirationtime = 1;
 | 
			
		||||
	ParticleParamTypes::f32Range bounce;
 | 
			
		||||
	ParticleParamTypes::v3fRange jitter;
 | 
			
		||||
 | 
			
		||||
	void serialize(std::ostream &os, u16 protocol_ver) const;
 | 
			
		||||
	void deSerialize(std::istream &is, u16 protocol_ver);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct ParticleSpawnerParameters : CommonParticleParams {
 | 
			
		||||
struct ParticleSpawnerParameters : CommonParticleParams
 | 
			
		||||
{
 | 
			
		||||
	u16 amount = 1;
 | 
			
		||||
	v3f minpos, maxpos, minvel, maxvel, minacc, maxacc;
 | 
			
		||||
	f32 time = 1;
 | 
			
		||||
	f32 minexptime = 1, maxexptime = 1, minsize = 1, maxsize = 1;
 | 
			
		||||
 | 
			
		||||
	std::vector<ServerParticleTexture> texpool;
 | 
			
		||||
 | 
			
		||||
	ParticleParamTypes::v3fRangeTween
 | 
			
		||||
		pos, vel, acc, drag, radius, jitter;
 | 
			
		||||
 | 
			
		||||
	ParticleParamTypes::AttractorKind
 | 
			
		||||
		attractor_kind;
 | 
			
		||||
	ParticleParamTypes::v3fTween
 | 
			
		||||
		attractor_origin, attractor_direction;
 | 
			
		||||
	// object IDs
 | 
			
		||||
	u16 attractor_attachment = 0,
 | 
			
		||||
	    attractor_direction_attachment = 0;
 | 
			
		||||
	// do particles disappear when they cross the attractor threshold?
 | 
			
		||||
	bool attractor_kill = true;
 | 
			
		||||
 | 
			
		||||
	ParticleParamTypes::f32RangeTween
 | 
			
		||||
		exptime{1.0f},
 | 
			
		||||
		size   {1.0f},
 | 
			
		||||
		attract{0.0f},
 | 
			
		||||
		bounce {0.0f};
 | 
			
		||||
 | 
			
		||||
	// For historical reasons no (de-)serialization methods here
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -42,7 +42,7 @@ struct EnumString es_TileAnimationType[] =
 | 
			
		||||
	{TAT_NONE, "none"},
 | 
			
		||||
	{TAT_VERTICAL_FRAMES, "vertical_frames"},
 | 
			
		||||
	{TAT_SHEET_2D, "sheet_2d"},
 | 
			
		||||
	{0, NULL},
 | 
			
		||||
	{0, nullptr},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/******************************************************************************/
 | 
			
		||||
 | 
			
		||||
@ -100,6 +100,7 @@ void               setboolfield(lua_State *L, int table,
 | 
			
		||||
                             const char *fieldname, bool value);
 | 
			
		||||
 | 
			
		||||
v3f                 checkFloatPos       (lua_State *L, int index);
 | 
			
		||||
v2f                 check_v2f           (lua_State *L, int index);
 | 
			
		||||
v3f                 check_v3f           (lua_State *L, int index);
 | 
			
		||||
v3s16               check_v3s16         (lua_State *L, int index);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										279
									
								
								src/script/lua_api/l_particleparams.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										279
									
								
								src/script/lua_api/l_particleparams.h
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,279 @@
 | 
			
		||||
/*
 | 
			
		||||
Minetest
 | 
			
		||||
Copyright (C) 2021 velartrill, Lexi Hale <lexi@hale.su>
 | 
			
		||||
 | 
			
		||||
This program is free software; you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
the Free Software Foundation; either version 2.1 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU Lesser General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU Lesser General Public License along
 | 
			
		||||
with this program; if not, write to the Free Software Foundation, Inc.,
 | 
			
		||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
#include "lua_api/l_particles.h"
 | 
			
		||||
#include "lua_api/l_object.h"
 | 
			
		||||
#include "lua_api/l_internal.h"
 | 
			
		||||
#include "common/c_converter.h"
 | 
			
		||||
#include "common/c_content.h"
 | 
			
		||||
#include "server.h"
 | 
			
		||||
#include "particles.h"
 | 
			
		||||
 | 
			
		||||
namespace LuaParticleParams
 | 
			
		||||
{
 | 
			
		||||
	using namespace ParticleParamTypes;
 | 
			
		||||
 | 
			
		||||
	template<typename T>
 | 
			
		||||
	inline void readNumericLuaValue(lua_State* L, T& ret)
 | 
			
		||||
	{
 | 
			
		||||
		if (lua_isnil(L,-1))
 | 
			
		||||
			return;
 | 
			
		||||
 | 
			
		||||
		if (std::is_integral<T>())
 | 
			
		||||
			ret = lua_tointeger(L, -1);
 | 
			
		||||
		else
 | 
			
		||||
			ret = lua_tonumber(L, -1);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	template <typename T, size_t N>
 | 
			
		||||
	inline void readNumericLuaValue(lua_State* L, Parameter<T,N>& ret)
 | 
			
		||||
	{
 | 
			
		||||
		readNumericLuaValue<T>(L, ret.val);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// these are unfortunately necessary as C++ intentionally disallows function template
 | 
			
		||||
	// specialization and there's no way to make template overloads reliably resolve correctly
 | 
			
		||||
	inline void readLuaValue(lua_State* L, f32Parameter& ret) { readNumericLuaValue(L, ret); }
 | 
			
		||||
	inline void readLuaValue(lua_State* L, f32& ret)          { readNumericLuaValue(L, ret); }
 | 
			
		||||
	inline void readLuaValue(lua_State* L, u16& ret)          { readNumericLuaValue(L, ret); }
 | 
			
		||||
	inline void readLuaValue(lua_State* L, u8& ret)           { readNumericLuaValue(L, ret); }
 | 
			
		||||
 | 
			
		||||
	inline void readLuaValue(lua_State* L, v3fParameter& ret)
 | 
			
		||||
	{
 | 
			
		||||
		if (lua_isnil(L, -1))
 | 
			
		||||
			return;
 | 
			
		||||
 | 
			
		||||
		if (lua_isnumber(L, -1)) { // shortcut for uniform vectors
 | 
			
		||||
			auto n = lua_tonumber(L, -1);
 | 
			
		||||
			ret = v3fParameter(n,n,n);
 | 
			
		||||
		} else {
 | 
			
		||||
			ret = (v3fParameter)check_v3f(L, -1);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	inline void readLuaValue(lua_State* L, v2fParameter& ret)
 | 
			
		||||
	{
 | 
			
		||||
		if (lua_isnil(L, -1))
 | 
			
		||||
			return;
 | 
			
		||||
 | 
			
		||||
		if (lua_isnumber(L, -1)) { // shortcut for uniform vectors
 | 
			
		||||
			auto n = lua_tonumber(L, -1);
 | 
			
		||||
			ret = v2fParameter(n,n);
 | 
			
		||||
		} else {
 | 
			
		||||
			ret = (v2fParameter)check_v2f(L, -1);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	inline void readLuaValue(lua_State* L, TweenStyle& ret)
 | 
			
		||||
	{
 | 
			
		||||
		if (lua_isnil(L, -1))
 | 
			
		||||
			return;
 | 
			
		||||
 | 
			
		||||
		static const EnumString opts[] = {
 | 
			
		||||
			{(int)TweenStyle::fwd,     "fwd"},
 | 
			
		||||
			{(int)TweenStyle::rev,     "rev"},
 | 
			
		||||
			{(int)TweenStyle::pulse,   "pulse"},
 | 
			
		||||
			{(int)TweenStyle::flicker, "flicker"},
 | 
			
		||||
			{0, nullptr},
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		luaL_checktype(L, -1, LUA_TSTRING);
 | 
			
		||||
		int v = (int)TweenStyle::fwd;
 | 
			
		||||
		if (!string_to_enum(opts, v, lua_tostring(L, -1))) {
 | 
			
		||||
			throw LuaError("tween style must be one of ('fwd', 'rev', 'pulse', 'flicker')");
 | 
			
		||||
		}
 | 
			
		||||
		ret = (TweenStyle)v;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	inline void readLuaValue(lua_State* L, AttractorKind& ret)
 | 
			
		||||
	{
 | 
			
		||||
		if (lua_isnil(L, -1))
 | 
			
		||||
			return;
 | 
			
		||||
 | 
			
		||||
		static const EnumString opts[] = {
 | 
			
		||||
			{(int)AttractorKind::none,  "none"},
 | 
			
		||||
			{(int)AttractorKind::point, "point"},
 | 
			
		||||
			{(int)AttractorKind::line,  "line"},
 | 
			
		||||
			{(int)AttractorKind::plane, "plane"},
 | 
			
		||||
			{0, nullptr},
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		luaL_checktype(L, -1, LUA_TSTRING);
 | 
			
		||||
		int v = (int)AttractorKind::none;
 | 
			
		||||
		if (!string_to_enum(opts, v, lua_tostring(L, -1))) {
 | 
			
		||||
			throw LuaError("attractor kind must be one of ('none', 'point', 'line', 'plane')");
 | 
			
		||||
		}
 | 
			
		||||
		ret = (AttractorKind)v;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	inline void readLuaValue(lua_State* L, BlendMode& ret)
 | 
			
		||||
	{
 | 
			
		||||
		if (lua_isnil(L, -1))
 | 
			
		||||
			return;
 | 
			
		||||
 | 
			
		||||
		static const EnumString opts[] = {
 | 
			
		||||
			{(int)BlendMode::alpha,  "alpha"},
 | 
			
		||||
			{(int)BlendMode::add,    "add"},
 | 
			
		||||
			{(int)BlendMode::sub,    "sub"},
 | 
			
		||||
			{(int)BlendMode::screen, "screen"},
 | 
			
		||||
			{0, nullptr},
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		luaL_checktype(L, -1, LUA_TSTRING);
 | 
			
		||||
		int v = (int)BlendMode::alpha;
 | 
			
		||||
		if (!string_to_enum(opts, v, lua_tostring(L, -1))) {
 | 
			
		||||
			throw LuaError("blend mode must be one of ('alpha', 'add', 'sub', 'screen')");
 | 
			
		||||
		}
 | 
			
		||||
		ret = (BlendMode)v;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	template <typename T> void
 | 
			
		||||
	readLuaValue(lua_State* L, RangedParameter<T>& field)
 | 
			
		||||
	{
 | 
			
		||||
		if (lua_isnil(L,-1))
 | 
			
		||||
			return;
 | 
			
		||||
		if (!lua_istable(L,-1)) // is this is just a literal value?
 | 
			
		||||
			goto set_uniform;
 | 
			
		||||
 | 
			
		||||
		lua_getfield(L, -1, "min");
 | 
			
		||||
		// handle convenience syntax for non-range values
 | 
			
		||||
		if (lua_isnil(L,-1)) {
 | 
			
		||||
			lua_pop(L, 1);
 | 
			
		||||
			goto set_uniform;
 | 
			
		||||
		}
 | 
			
		||||
		readLuaValue(L,field.min);
 | 
			
		||||
		lua_pop(L, 1);
 | 
			
		||||
 | 
			
		||||
		lua_getfield(L, -1, "max");
 | 
			
		||||
		readLuaValue(L,field.max);
 | 
			
		||||
		lua_pop(L, 1);
 | 
			
		||||
 | 
			
		||||
		lua_getfield(L, -1, "bias");
 | 
			
		||||
		if (!lua_isnil(L,-1))
 | 
			
		||||
			readLuaValue(L,field.bias);
 | 
			
		||||
		lua_pop(L, 1);
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
		set_uniform:
 | 
			
		||||
		readLuaValue(L, field.min);
 | 
			
		||||
		readLuaValue(L, field.max);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	template <typename T> void
 | 
			
		||||
	readLegacyValue(lua_State* L, const char* name, T& field) {}
 | 
			
		||||
 | 
			
		||||
	template <typename T> void
 | 
			
		||||
	readLegacyValue(lua_State* L, const char* name, RangedParameter<T>& field)
 | 
			
		||||
	{
 | 
			
		||||
		int tbl = lua_gettop(L);
 | 
			
		||||
		lua_pushliteral(L, "min");
 | 
			
		||||
		lua_pushstring(L, name);
 | 
			
		||||
		lua_concat(L, 2);
 | 
			
		||||
		lua_gettable(L, tbl);
 | 
			
		||||
		if (!lua_isnil(L, -1)) {
 | 
			
		||||
			readLuaValue(L, field.min);
 | 
			
		||||
		}
 | 
			
		||||
		lua_settop(L, tbl);
 | 
			
		||||
 | 
			
		||||
		lua_pushliteral(L, "max");
 | 
			
		||||
		lua_pushstring(L, name);
 | 
			
		||||
		lua_concat(L, 2);
 | 
			
		||||
		lua_gettable(L, tbl);
 | 
			
		||||
		if (!lua_isnil(L, -1)) {
 | 
			
		||||
			readLuaValue(L, field.max);
 | 
			
		||||
		}
 | 
			
		||||
		lua_settop(L, tbl);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	template <typename T> void
 | 
			
		||||
	readTweenTable(lua_State* L, const char* name, TweenedParameter<T>& field)
 | 
			
		||||
	{
 | 
			
		||||
		int tbl = lua_gettop(L);
 | 
			
		||||
 | 
			
		||||
		lua_pushstring(L, name);
 | 
			
		||||
		lua_pushliteral(L, "_tween");
 | 
			
		||||
		lua_concat(L, 2);
 | 
			
		||||
		lua_gettable(L, tbl);
 | 
			
		||||
		if(lua_istable(L, -1)) {
 | 
			
		||||
			int tween = lua_gettop(L);
 | 
			
		||||
			// get the starting value
 | 
			
		||||
			lua_pushinteger(L, 1), lua_gettable(L, tween);
 | 
			
		||||
			readLuaValue(L, field.start);
 | 
			
		||||
			lua_pop(L, 1);
 | 
			
		||||
 | 
			
		||||
			// get the final value -- use len instead of 2 so that this
 | 
			
		||||
			// gracefully degrades if keyframe support is later added
 | 
			
		||||
			lua_pushinteger(L, (lua_Integer)lua_objlen(L, -1)), lua_gettable(L, tween);
 | 
			
		||||
			readLuaValue(L, field.end);
 | 
			
		||||
			lua_pop(L, 1);
 | 
			
		||||
 | 
			
		||||
			// get the effect settings
 | 
			
		||||
			lua_getfield(L, -1, "style");
 | 
			
		||||
			lua_isnil(L,-1) || (readLuaValue(L, field.style), true);
 | 
			
		||||
			lua_pop(L, 1);
 | 
			
		||||
 | 
			
		||||
			lua_getfield(L, -1, "reps");
 | 
			
		||||
			lua_isnil(L,-1) || (readLuaValue(L, field.reps), true);
 | 
			
		||||
			lua_pop(L, 1);
 | 
			
		||||
 | 
			
		||||
			lua_getfield(L, -1, "start");
 | 
			
		||||
			lua_isnil(L,-1) || (readLuaValue(L, field.beginning), true);
 | 
			
		||||
			lua_pop(L, 1);
 | 
			
		||||
 | 
			
		||||
			goto done;
 | 
			
		||||
		} else {
 | 
			
		||||
			lua_pop(L,1);
 | 
			
		||||
		}
 | 
			
		||||
		// the table is not present; check for nonanimated values
 | 
			
		||||
 | 
			
		||||
		lua_getfield(L, tbl, name);
 | 
			
		||||
		if(!lua_isnil(L, -1)) {
 | 
			
		||||
			readLuaValue(L, field.start);
 | 
			
		||||
			lua_settop(L, tbl);
 | 
			
		||||
			goto set_uniform;
 | 
			
		||||
		} else {
 | 
			
		||||
			lua_pop(L,1);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// the goto did not trigger, so this table is not present either
 | 
			
		||||
		// check for pre-5.6.0 legacy values
 | 
			
		||||
		readLegacyValue(L, name, field.start);
 | 
			
		||||
 | 
			
		||||
		set_uniform:
 | 
			
		||||
		field.end = field.start;
 | 
			
		||||
		done:
 | 
			
		||||
		lua_settop(L, tbl); // clean up after ourselves
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	inline u16 readAttachmentID(lua_State* L, const char* name)
 | 
			
		||||
	{
 | 
			
		||||
		u16 id = 0;
 | 
			
		||||
		lua_getfield(L, -1, name);
 | 
			
		||||
		if (!lua_isnil(L, -1)) {
 | 
			
		||||
			ObjectRef *ref = ObjectRef::checkobject(L, -1);
 | 
			
		||||
			if (auto obj = ObjectRef::getobject(ref))
 | 
			
		||||
				id = obj->getId();
 | 
			
		||||
		}
 | 
			
		||||
		lua_pop(L, 1);
 | 
			
		||||
		return id;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void readTexValue(lua_State* L, ServerParticleTexture& tex);
 | 
			
		||||
}
 | 
			
		||||
@ -20,30 +20,50 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 | 
			
		||||
#include "lua_api/l_particles.h"
 | 
			
		||||
#include "lua_api/l_object.h"
 | 
			
		||||
#include "lua_api/l_internal.h"
 | 
			
		||||
#include "lua_api/l_particleparams.h"
 | 
			
		||||
#include "common/c_converter.h"
 | 
			
		||||
#include "common/c_content.h"
 | 
			
		||||
#include "server.h"
 | 
			
		||||
#include "particles.h"
 | 
			
		||||
 | 
			
		||||
// add_particle({pos=, velocity=, acceleration=, expirationtime=,
 | 
			
		||||
//     size=, collisiondetection=, collision_removal=, object_collision=,
 | 
			
		||||
//     vertical=, texture=, player=})
 | 
			
		||||
// pos/velocity/acceleration = {x=num, y=num, z=num}
 | 
			
		||||
// expirationtime = num (seconds)
 | 
			
		||||
// size = num
 | 
			
		||||
// collisiondetection = bool
 | 
			
		||||
// collision_removal = bool
 | 
			
		||||
// object_collision = bool
 | 
			
		||||
// vertical = bool
 | 
			
		||||
// texture = e.g."default_wood.png"
 | 
			
		||||
// animation = TileAnimation definition
 | 
			
		||||
// glow = num
 | 
			
		||||
void LuaParticleParams::readTexValue(lua_State* L, ServerParticleTexture& tex)
 | 
			
		||||
{
 | 
			
		||||
	StackUnroller unroll(L);
 | 
			
		||||
 | 
			
		||||
	tex.animated = false;
 | 
			
		||||
	if (lua_isstring(L, -1)) {
 | 
			
		||||
		tex.string = lua_tostring(L, -1);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	luaL_checktype(L, -1, LUA_TTABLE);
 | 
			
		||||
	lua_getfield(L, -1, "name");
 | 
			
		||||
	tex.string = luaL_checkstring(L, -1);
 | 
			
		||||
	lua_pop(L, 1);
 | 
			
		||||
 | 
			
		||||
	lua_getfield(L, -1, "animation");
 | 
			
		||||
	if (! lua_isnil(L, -1)) {
 | 
			
		||||
		tex.animated = true;
 | 
			
		||||
		tex.animation = read_animation_definition(L, -1);
 | 
			
		||||
	}
 | 
			
		||||
	lua_pop(L, 1);
 | 
			
		||||
 | 
			
		||||
	lua_getfield(L, -1, "blend");
 | 
			
		||||
	LuaParticleParams::readLuaValue(L, tex.blendmode);
 | 
			
		||||
	lua_pop(L, 1);
 | 
			
		||||
 | 
			
		||||
	LuaParticleParams::readTweenTable(L, "alpha", tex.alpha);
 | 
			
		||||
	LuaParticleParams::readTweenTable(L, "scale", tex.scale);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// add_particle({...})
 | 
			
		||||
int ModApiParticles::l_add_particle(lua_State *L)
 | 
			
		||||
{
 | 
			
		||||
	NO_MAP_LOCK_REQUIRED;
 | 
			
		||||
 | 
			
		||||
	// Get parameters
 | 
			
		||||
	struct ParticleParameters p;
 | 
			
		||||
	ParticleParameters p;
 | 
			
		||||
	std::string playername;
 | 
			
		||||
 | 
			
		||||
	if (lua_gettop(L) > 1) // deprecated
 | 
			
		||||
@ -56,7 +76,7 @@ int ModApiParticles::l_add_particle(lua_State *L)
 | 
			
		||||
		p.expirationtime = luaL_checknumber(L, 4);
 | 
			
		||||
		p.size = luaL_checknumber(L, 5);
 | 
			
		||||
		p.collisiondetection = readParam<bool>(L, 6);
 | 
			
		||||
		p.texture = luaL_checkstring(L, 7);
 | 
			
		||||
		p.texture.string = luaL_checkstring(L, 7);
 | 
			
		||||
		if (lua_gettop(L) == 8) // only spawn for a single player
 | 
			
		||||
			playername = luaL_checkstring(L, 8);
 | 
			
		||||
	}
 | 
			
		||||
@ -108,7 +128,12 @@ int ModApiParticles::l_add_particle(lua_State *L)
 | 
			
		||||
		p.animation = read_animation_definition(L, -1);
 | 
			
		||||
		lua_pop(L, 1);
 | 
			
		||||
 | 
			
		||||
		p.texture = getstringfield_default(L, 1, "texture", p.texture);
 | 
			
		||||
		lua_getfield(L, 1, "texture");
 | 
			
		||||
		if (!lua_isnil(L, -1)) {
 | 
			
		||||
			LuaParticleParams::readTexValue(L, p.texture);
 | 
			
		||||
		}
 | 
			
		||||
		lua_pop(L, 1);
 | 
			
		||||
 | 
			
		||||
		p.glow = getintfield_default(L, 1, "glow", p.glow);
 | 
			
		||||
 | 
			
		||||
		lua_getfield(L, 1, "node");
 | 
			
		||||
@ -119,34 +144,26 @@ int ModApiParticles::l_add_particle(lua_State *L)
 | 
			
		||||
		p.node_tile = getintfield_default(L, 1, "node_tile", p.node_tile);
 | 
			
		||||
 | 
			
		||||
		playername = getstringfield_default(L, 1, "playername", "");
 | 
			
		||||
 | 
			
		||||
		lua_getfield(L, 1, "drag");
 | 
			
		||||
		if (lua_istable(L, -1))
 | 
			
		||||
			p.drag = check_v3f(L, -1);
 | 
			
		||||
		lua_pop(L, 1);
 | 
			
		||||
 | 
			
		||||
		lua_getfield(L, 1, "jitter");
 | 
			
		||||
		LuaParticleParams::readLuaValue(L, p.jitter);
 | 
			
		||||
		lua_pop(L, 1);
 | 
			
		||||
 | 
			
		||||
		lua_getfield(L, 1, "bounce");
 | 
			
		||||
		LuaParticleParams::readLuaValue(L, p.bounce);
 | 
			
		||||
		lua_pop(L, 1);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	getServer(L)->spawnParticle(playername, p);
 | 
			
		||||
	return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// add_particlespawner({amount=, time=,
 | 
			
		||||
//				minpos=, maxpos=,
 | 
			
		||||
//				minvel=, maxvel=,
 | 
			
		||||
//				minacc=, maxacc=,
 | 
			
		||||
//				minexptime=, maxexptime=,
 | 
			
		||||
//				minsize=, maxsize=,
 | 
			
		||||
//				collisiondetection=,
 | 
			
		||||
//				collision_removal=,
 | 
			
		||||
//				object_collision=,
 | 
			
		||||
//				vertical=,
 | 
			
		||||
//				texture=,
 | 
			
		||||
//				player=})
 | 
			
		||||
// minpos/maxpos/minvel/maxvel/minacc/maxacc = {x=num, y=num, z=num}
 | 
			
		||||
// minexptime/maxexptime = num (seconds)
 | 
			
		||||
// minsize/maxsize = num
 | 
			
		||||
// collisiondetection = bool
 | 
			
		||||
// collision_removal = bool
 | 
			
		||||
// object_collision = bool
 | 
			
		||||
// vertical = bool
 | 
			
		||||
// texture = e.g."default_wood.png"
 | 
			
		||||
// animation = TileAnimation definition
 | 
			
		||||
// glow = num
 | 
			
		||||
// add_particlespawner({...})
 | 
			
		||||
int ModApiParticles::l_add_particlespawner(lua_State *L)
 | 
			
		||||
{
 | 
			
		||||
	NO_MAP_LOCK_REQUIRED;
 | 
			
		||||
@ -156,24 +173,31 @@ int ModApiParticles::l_add_particlespawner(lua_State *L)
 | 
			
		||||
	ServerActiveObject *attached = NULL;
 | 
			
		||||
	std::string playername;
 | 
			
		||||
 | 
			
		||||
	using namespace ParticleParamTypes;
 | 
			
		||||
	if (lua_gettop(L) > 1) //deprecated
 | 
			
		||||
	{
 | 
			
		||||
		log_deprecated(L, "Deprecated add_particlespawner call with "
 | 
			
		||||
			"individual parameters instead of definition");
 | 
			
		||||
		p.amount = luaL_checknumber(L, 1);
 | 
			
		||||
		p.time = luaL_checknumber(L, 2);
 | 
			
		||||
		p.minpos = check_v3f(L, 3);
 | 
			
		||||
		p.maxpos = check_v3f(L, 4);
 | 
			
		||||
		p.minvel = check_v3f(L, 5);
 | 
			
		||||
		p.maxvel = check_v3f(L, 6);
 | 
			
		||||
		p.minacc = check_v3f(L, 7);
 | 
			
		||||
		p.maxacc = check_v3f(L, 8);
 | 
			
		||||
		p.minexptime = luaL_checknumber(L, 9);
 | 
			
		||||
		p.maxexptime = luaL_checknumber(L, 10);
 | 
			
		||||
		p.minsize = luaL_checknumber(L, 11);
 | 
			
		||||
		p.maxsize = luaL_checknumber(L, 12);
 | 
			
		||||
		auto minpos = check_v3f(L, 3);
 | 
			
		||||
		auto maxpos = check_v3f(L, 4);
 | 
			
		||||
		auto minvel = check_v3f(L, 5);
 | 
			
		||||
		auto maxvel = check_v3f(L, 6);
 | 
			
		||||
		auto minacc = check_v3f(L, 7);
 | 
			
		||||
		auto maxacc = check_v3f(L, 8);
 | 
			
		||||
		auto minexptime = luaL_checknumber(L, 9);
 | 
			
		||||
		auto maxexptime = luaL_checknumber(L, 10);
 | 
			
		||||
		auto minsize = luaL_checknumber(L, 11);
 | 
			
		||||
		auto maxsize = luaL_checknumber(L, 12);
 | 
			
		||||
		p.pos = v3fRange(minpos, maxpos);
 | 
			
		||||
		p.vel = v3fRange(minvel, maxvel);
 | 
			
		||||
		p.acc = v3fRange(minacc, maxacc);
 | 
			
		||||
		p.exptime = f32Range(minexptime, maxexptime);
 | 
			
		||||
		p.size = f32Range(minsize, maxsize);
 | 
			
		||||
 | 
			
		||||
		p.collisiondetection = readParam<bool>(L, 13);
 | 
			
		||||
		p.texture = luaL_checkstring(L, 14);
 | 
			
		||||
		p.texture.string = luaL_checkstring(L, 14);
 | 
			
		||||
		if (lua_gettop(L) == 15) // only spawn for a single player
 | 
			
		||||
			playername = luaL_checkstring(L, 15);
 | 
			
		||||
	}
 | 
			
		||||
@ -182,40 +206,46 @@ int ModApiParticles::l_add_particlespawner(lua_State *L)
 | 
			
		||||
		p.amount = getintfield_default(L, 1, "amount", p.amount);
 | 
			
		||||
		p.time = getfloatfield_default(L, 1, "time", p.time);
 | 
			
		||||
 | 
			
		||||
		lua_getfield(L, 1, "minpos");
 | 
			
		||||
		if (lua_istable(L, -1))
 | 
			
		||||
			p.minpos = check_v3f(L, -1);
 | 
			
		||||
		lua_pop(L, 1);
 | 
			
		||||
		// set default values
 | 
			
		||||
		p.exptime = 1;
 | 
			
		||||
		p.size = 1;
 | 
			
		||||
 | 
			
		||||
		lua_getfield(L, 1, "maxpos");
 | 
			
		||||
		if (lua_istable(L, -1))
 | 
			
		||||
			p.maxpos = check_v3f(L, -1);
 | 
			
		||||
		lua_pop(L, 1);
 | 
			
		||||
		// read spawner parameters from the table
 | 
			
		||||
		LuaParticleParams::readTweenTable(L, "pos", p.pos);
 | 
			
		||||
		LuaParticleParams::readTweenTable(L, "vel", p.vel);
 | 
			
		||||
		LuaParticleParams::readTweenTable(L, "acc", p.acc);
 | 
			
		||||
		LuaParticleParams::readTweenTable(L, "size", p.size);
 | 
			
		||||
		LuaParticleParams::readTweenTable(L, "exptime", p.exptime);
 | 
			
		||||
		LuaParticleParams::readTweenTable(L, "drag", p.drag);
 | 
			
		||||
		LuaParticleParams::readTweenTable(L, "jitter", p.jitter);
 | 
			
		||||
		LuaParticleParams::readTweenTable(L, "bounce", p.bounce);
 | 
			
		||||
		lua_getfield(L, 1, "attract");
 | 
			
		||||
		if (!lua_isnil(L, -1)) {
 | 
			
		||||
			luaL_checktype(L, -1, LUA_TTABLE);
 | 
			
		||||
			lua_getfield(L, -1, "kind");
 | 
			
		||||
			LuaParticleParams::readLuaValue(L, p.attractor_kind);
 | 
			
		||||
			lua_pop(L,1);
 | 
			
		||||
 | 
			
		||||
		lua_getfield(L, 1, "minvel");
 | 
			
		||||
		if (lua_istable(L, -1))
 | 
			
		||||
			p.minvel = check_v3f(L, -1);
 | 
			
		||||
		lua_pop(L, 1);
 | 
			
		||||
			lua_getfield(L, -1, "die_on_contact");
 | 
			
		||||
			if (!lua_isnil(L, -1))
 | 
			
		||||
				p.attractor_kill = readParam<bool>(L, -1);
 | 
			
		||||
			lua_pop(L,1);
 | 
			
		||||
 | 
			
		||||
		lua_getfield(L, 1, "maxvel");
 | 
			
		||||
		if (lua_istable(L, -1))
 | 
			
		||||
			p.maxvel = check_v3f(L, -1);
 | 
			
		||||
		lua_pop(L, 1);
 | 
			
		||||
			if (p.attractor_kind != AttractorKind::none) {
 | 
			
		||||
				LuaParticleParams::readTweenTable(L, "strength", p.attract);
 | 
			
		||||
				LuaParticleParams::readTweenTable(L, "origin", p.attractor_origin);
 | 
			
		||||
				p.attractor_attachment = LuaParticleParams::readAttachmentID(L, "origin_attached");
 | 
			
		||||
				if (p.attractor_kind != AttractorKind::point) {
 | 
			
		||||
					LuaParticleParams::readTweenTable(L, "direction", p.attractor_direction);
 | 
			
		||||
					p.attractor_direction_attachment = LuaParticleParams::readAttachmentID(L, "direction_attached");
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			p.attractor_kind = AttractorKind::none;
 | 
			
		||||
		}
 | 
			
		||||
		lua_pop(L,1);
 | 
			
		||||
		LuaParticleParams::readTweenTable(L, "radius", p.radius);
 | 
			
		||||
 | 
			
		||||
		lua_getfield(L, 1, "minacc");
 | 
			
		||||
		if (lua_istable(L, -1))
 | 
			
		||||
			p.minacc = check_v3f(L, -1);
 | 
			
		||||
		lua_pop(L, 1);
 | 
			
		||||
 | 
			
		||||
		lua_getfield(L, 1, "maxacc");
 | 
			
		||||
		if (lua_istable(L, -1))
 | 
			
		||||
			p.maxacc = check_v3f(L, -1);
 | 
			
		||||
		lua_pop(L, 1);
 | 
			
		||||
 | 
			
		||||
		p.minexptime = getfloatfield_default(L, 1, "minexptime", p.minexptime);
 | 
			
		||||
		p.maxexptime = getfloatfield_default(L, 1, "maxexptime", p.maxexptime);
 | 
			
		||||
		p.minsize = getfloatfield_default(L, 1, "minsize", p.minsize);
 | 
			
		||||
		p.maxsize = getfloatfield_default(L, 1, "maxsize", p.maxsize);
 | 
			
		||||
		p.collisiondetection = getboolfield_default(L, 1,
 | 
			
		||||
			"collisiondetection", p.collisiondetection);
 | 
			
		||||
		p.collision_removal = getboolfield_default(L, 1,
 | 
			
		||||
@ -234,11 +264,29 @@ int ModApiParticles::l_add_particlespawner(lua_State *L)
 | 
			
		||||
			attached = ObjectRef::getobject(ref);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		lua_getfield(L, 1, "texture");
 | 
			
		||||
		if (!lua_isnil(L, -1)) {
 | 
			
		||||
			LuaParticleParams::readTexValue(L, p.texture);
 | 
			
		||||
		}
 | 
			
		||||
		lua_pop(L, 1);
 | 
			
		||||
 | 
			
		||||
		p.vertical = getboolfield_default(L, 1, "vertical", p.vertical);
 | 
			
		||||
		p.texture = getstringfield_default(L, 1, "texture", p.texture);
 | 
			
		||||
		playername = getstringfield_default(L, 1, "playername", "");
 | 
			
		||||
		p.glow = getintfield_default(L, 1, "glow", p.glow);
 | 
			
		||||
 | 
			
		||||
		lua_getfield(L, 1, "texpool");
 | 
			
		||||
		if (lua_istable(L, -1)) {
 | 
			
		||||
			size_t tl = lua_objlen(L, -1);
 | 
			
		||||
			p.texpool.reserve(tl);
 | 
			
		||||
			for (size_t i = 0; i < tl; ++i) {
 | 
			
		||||
				lua_pushinteger(L, i+1), lua_gettable(L, -2);
 | 
			
		||||
				p.texpool.emplace_back();
 | 
			
		||||
				LuaParticleParams::readTexValue(L, p.texpool.back());
 | 
			
		||||
				lua_pop(L,1);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		lua_pop(L, 1);
 | 
			
		||||
 | 
			
		||||
		lua_getfield(L, 1, "node");
 | 
			
		||||
		if (lua_istable(L, -1))
 | 
			
		||||
			p.node = readnode(L, -1, getGameDef(L)->ndef());
 | 
			
		||||
 | 
			
		||||
@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 | 
			
		||||
#include "common/c_converter.h"
 | 
			
		||||
#include "lua_api/l_internal.h"
 | 
			
		||||
#include "lua_api/l_object.h"
 | 
			
		||||
#include "lua_api/l_particleparams.h"
 | 
			
		||||
#include "client/particles.h"
 | 
			
		||||
#include "client/client.h"
 | 
			
		||||
#include "client/clientevent.h"
 | 
			
		||||
@ -49,6 +50,19 @@ int ModApiParticlesLocal::l_add_particle(lua_State *L)
 | 
			
		||||
		p.acc = check_v3f(L, -1);
 | 
			
		||||
	lua_pop(L, 1);
 | 
			
		||||
 | 
			
		||||
	lua_getfield(L, 1, "drag");
 | 
			
		||||
	if (lua_istable(L, -1))
 | 
			
		||||
		p.drag = check_v3f(L, -1);
 | 
			
		||||
	lua_pop(L, 1);
 | 
			
		||||
 | 
			
		||||
	lua_getfield(L, 1, "jitter");
 | 
			
		||||
	LuaParticleParams::readLuaValue(L, p.jitter);
 | 
			
		||||
	lua_pop(L, 1);
 | 
			
		||||
 | 
			
		||||
	lua_getfield(L, 1, "bounce");
 | 
			
		||||
	LuaParticleParams::readLuaValue(L, p.bounce);
 | 
			
		||||
	lua_pop(L, 1);
 | 
			
		||||
 | 
			
		||||
	p.expirationtime = getfloatfield_default(L, 1, "expirationtime",
 | 
			
		||||
		p.expirationtime);
 | 
			
		||||
	p.size = getfloatfield_default(L, 1, "size", p.size);
 | 
			
		||||
@ -64,7 +78,11 @@ int ModApiParticlesLocal::l_add_particle(lua_State *L)
 | 
			
		||||
	p.animation = read_animation_definition(L, -1);
 | 
			
		||||
	lua_pop(L, 1);
 | 
			
		||||
 | 
			
		||||
	p.texture = getstringfield_default(L, 1, "texture", p.texture);
 | 
			
		||||
	lua_getfield(L, 1, "texture");
 | 
			
		||||
	if (!lua_isnil(L, -1)) {
 | 
			
		||||
		LuaParticleParams::readTexValue(L,p.texture);
 | 
			
		||||
	}
 | 
			
		||||
	lua_pop(L, 1);
 | 
			
		||||
	p.glow = getintfield_default(L, 1, "glow", p.glow);
 | 
			
		||||
 | 
			
		||||
	lua_getfield(L, 1, "node");
 | 
			
		||||
@ -88,44 +106,50 @@ int ModApiParticlesLocal::l_add_particlespawner(lua_State *L)
 | 
			
		||||
 | 
			
		||||
	// Get parameters
 | 
			
		||||
	ParticleSpawnerParameters p;
 | 
			
		||||
 | 
			
		||||
	p.amount = getintfield_default(L, 1, "amount", p.amount);
 | 
			
		||||
	p.time = getfloatfield_default(L, 1, "time", p.time);
 | 
			
		||||
 | 
			
		||||
	lua_getfield(L, 1, "minpos");
 | 
			
		||||
	if (lua_istable(L, -1))
 | 
			
		||||
		p.minpos = check_v3f(L, -1);
 | 
			
		||||
	lua_pop(L, 1);
 | 
			
		||||
	// set default values
 | 
			
		||||
	p.exptime = 1;
 | 
			
		||||
	p.size = 1;
 | 
			
		||||
 | 
			
		||||
	lua_getfield(L, 1, "maxpos");
 | 
			
		||||
	if (lua_istable(L, -1))
 | 
			
		||||
		p.maxpos = check_v3f(L, -1);
 | 
			
		||||
	lua_pop(L, 1);
 | 
			
		||||
	// read spawner parameters from the table
 | 
			
		||||
	using namespace ParticleParamTypes;
 | 
			
		||||
	LuaParticleParams::readTweenTable(L, "pos", p.pos);
 | 
			
		||||
	LuaParticleParams::readTweenTable(L, "vel", p.vel);
 | 
			
		||||
	LuaParticleParams::readTweenTable(L, "acc", p.acc);
 | 
			
		||||
	LuaParticleParams::readTweenTable(L, "size", p.size);
 | 
			
		||||
	LuaParticleParams::readTweenTable(L, "exptime", p.exptime);
 | 
			
		||||
	LuaParticleParams::readTweenTable(L, "drag", p.drag);
 | 
			
		||||
	LuaParticleParams::readTweenTable(L, "jitter", p.jitter);
 | 
			
		||||
	LuaParticleParams::readTweenTable(L, "bounce", p.bounce);
 | 
			
		||||
	lua_getfield(L, 1, "attract");
 | 
			
		||||
	if (!lua_isnil(L, -1)) {
 | 
			
		||||
		luaL_checktype(L, -1, LUA_TTABLE);
 | 
			
		||||
		lua_getfield(L, -1, "kind");
 | 
			
		||||
		LuaParticleParams::readLuaValue(L, p.attractor_kind);
 | 
			
		||||
		lua_pop(L,1);
 | 
			
		||||
 | 
			
		||||
	lua_getfield(L, 1, "minvel");
 | 
			
		||||
	if (lua_istable(L, -1))
 | 
			
		||||
		p.minvel = check_v3f(L, -1);
 | 
			
		||||
	lua_pop(L, 1);
 | 
			
		||||
		lua_getfield(L, -1, "die_on_contact");
 | 
			
		||||
		if (!lua_isnil(L, -1))
 | 
			
		||||
			p.attractor_kill = readParam<bool>(L, -1);
 | 
			
		||||
		lua_pop(L,1);
 | 
			
		||||
 | 
			
		||||
	lua_getfield(L, 1, "maxvel");
 | 
			
		||||
	if (lua_istable(L, -1))
 | 
			
		||||
		p.maxvel = check_v3f(L, -1);
 | 
			
		||||
	lua_pop(L, 1);
 | 
			
		||||
		if (p.attractor_kind != AttractorKind::none) {
 | 
			
		||||
			LuaParticleParams::readTweenTable(L, "strength", p.attract);
 | 
			
		||||
			LuaParticleParams::readTweenTable(L, "origin", p.attractor_origin);
 | 
			
		||||
			p.attractor_attachment = LuaParticleParams::readAttachmentID(L, "origin_attached");
 | 
			
		||||
			if (p.attractor_kind != AttractorKind::point) {
 | 
			
		||||
				LuaParticleParams::readTweenTable(L, "direction", p.attractor_direction);
 | 
			
		||||
				p.attractor_direction_attachment = LuaParticleParams::readAttachmentID(L, "direction_attached");
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		p.attractor_kind = AttractorKind::none;
 | 
			
		||||
	}
 | 
			
		||||
	lua_pop(L,1);
 | 
			
		||||
	LuaParticleParams::readTweenTable(L, "radius", p.radius);
 | 
			
		||||
 | 
			
		||||
	lua_getfield(L, 1, "minacc");
 | 
			
		||||
	if (lua_istable(L, -1))
 | 
			
		||||
		p.minacc = check_v3f(L, -1);
 | 
			
		||||
	lua_pop(L, 1);
 | 
			
		||||
 | 
			
		||||
	lua_getfield(L, 1, "maxacc");
 | 
			
		||||
	if (lua_istable(L, -1))
 | 
			
		||||
		p.maxacc = check_v3f(L, -1);
 | 
			
		||||
	lua_pop(L, 1);
 | 
			
		||||
 | 
			
		||||
	p.minexptime = getfloatfield_default(L, 1, "minexptime", p.minexptime);
 | 
			
		||||
	p.maxexptime = getfloatfield_default(L, 1, "maxexptime", p.maxexptime);
 | 
			
		||||
	p.minsize = getfloatfield_default(L, 1, "minsize", p.minsize);
 | 
			
		||||
	p.maxsize = getfloatfield_default(L, 1, "maxsize", p.maxsize);
 | 
			
		||||
	p.collisiondetection = getboolfield_default(L, 1,
 | 
			
		||||
		"collisiondetection", p.collisiondetection);
 | 
			
		||||
	p.collision_removal = getboolfield_default(L, 1,
 | 
			
		||||
@ -137,10 +161,28 @@ int ModApiParticlesLocal::l_add_particlespawner(lua_State *L)
 | 
			
		||||
	p.animation = read_animation_definition(L, -1);
 | 
			
		||||
	lua_pop(L, 1);
 | 
			
		||||
 | 
			
		||||
	lua_getfield(L, 1, "texture");
 | 
			
		||||
	if (!lua_isnil(L, -1)) {
 | 
			
		||||
		LuaParticleParams::readTexValue(L, p.texture);
 | 
			
		||||
	}
 | 
			
		||||
	lua_pop(L, 1);
 | 
			
		||||
 | 
			
		||||
	p.vertical = getboolfield_default(L, 1, "vertical", p.vertical);
 | 
			
		||||
	p.texture = getstringfield_default(L, 1, "texture", p.texture);
 | 
			
		||||
	p.glow = getintfield_default(L, 1, "glow", p.glow);
 | 
			
		||||
 | 
			
		||||
	lua_getfield(L, 1, "texpool");
 | 
			
		||||
	if (lua_istable(L, -1)) {
 | 
			
		||||
		size_t tl = lua_objlen(L, -1);
 | 
			
		||||
		p.texpool.reserve(tl);
 | 
			
		||||
		for (size_t i = 0; i < tl; ++i) {
 | 
			
		||||
			lua_pushinteger(L, i+1), lua_gettable(L, -2);
 | 
			
		||||
			p.texpool.emplace_back();
 | 
			
		||||
			LuaParticleParams::readTexValue(L, p.texpool.back());
 | 
			
		||||
			lua_pop(L,1);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	lua_pop(L, 1);
 | 
			
		||||
 | 
			
		||||
	lua_getfield(L, 1, "node");
 | 
			
		||||
	if (lua_istable(L, -1))
 | 
			
		||||
		p.node = readnode(L, -1, getGameDef(L)->ndef());
 | 
			
		||||
 | 
			
		||||
@ -160,6 +160,7 @@ v3f ServerPlayingSound::getPos(ServerEnvironment *env, bool *pos_exists) const
 | 
			
		||||
			return sao->getBasePosition();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return v3f(0,0,0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1599,7 +1600,12 @@ void Server::SendAddParticleSpawner(session_t peer_id, u16 protocol_version,
 | 
			
		||||
 | 
			
		||||
	if (peer_id == PEER_ID_INEXISTENT) {
 | 
			
		||||
		std::vector<session_t> clients = m_clients.getClientIDs();
 | 
			
		||||
		const v3f pos = (p.minpos + p.maxpos) / 2.0f * BS;
 | 
			
		||||
		const v3f pos = (
 | 
			
		||||
			p.pos.start.min.val +
 | 
			
		||||
			p.pos.start.max.val +
 | 
			
		||||
			p.pos.end.min.val +
 | 
			
		||||
			p.pos.end.max.val
 | 
			
		||||
		) / 4.0f * BS;
 | 
			
		||||
		const float radius_sq = radius * radius;
 | 
			
		||||
		/* Don't send short-lived spawners to distant players.
 | 
			
		||||
		 * This could be replaced with proper tracking at some point. */
 | 
			
		||||
@ -1627,11 +1633,19 @@ void Server::SendAddParticleSpawner(session_t peer_id, u16 protocol_version,
 | 
			
		||||
 | 
			
		||||
	NetworkPacket pkt(TOCLIENT_ADD_PARTICLESPAWNER, 100, peer_id);
 | 
			
		||||
 | 
			
		||||
	pkt << p.amount << p.time << p.minpos << p.maxpos << p.minvel
 | 
			
		||||
		<< p.maxvel << p.minacc << p.maxacc << p.minexptime << p.maxexptime
 | 
			
		||||
		<< p.minsize << p.maxsize << p.collisiondetection;
 | 
			
		||||
	pkt << p.amount << p.time;
 | 
			
		||||
	{ // serialize legacy fields
 | 
			
		||||
		std::ostringstream os(std::ios_base::binary);
 | 
			
		||||
		p.pos.start.legacySerialize(os);
 | 
			
		||||
		p.vel.start.legacySerialize(os);
 | 
			
		||||
		p.acc.start.legacySerialize(os);
 | 
			
		||||
		p.exptime.start.legacySerialize(os);
 | 
			
		||||
		p.size.start.legacySerialize(os);
 | 
			
		||||
		pkt.putRawString(os.str());
 | 
			
		||||
	}
 | 
			
		||||
	pkt << p.collisiondetection;
 | 
			
		||||
 | 
			
		||||
	pkt.putLongString(p.texture);
 | 
			
		||||
	pkt.putLongString(p.texture.string);
 | 
			
		||||
 | 
			
		||||
	pkt << id << p.vertical << p.collision_removal << attached_id;
 | 
			
		||||
	{
 | 
			
		||||
@ -1642,6 +1656,51 @@ void Server::SendAddParticleSpawner(session_t peer_id, u16 protocol_version,
 | 
			
		||||
	pkt << p.glow << p.object_collision;
 | 
			
		||||
	pkt << p.node.param0 << p.node.param2 << p.node_tile;
 | 
			
		||||
 | 
			
		||||
	{ // serialize new fields
 | 
			
		||||
		// initial bias for older properties
 | 
			
		||||
		pkt << p.pos.start.bias
 | 
			
		||||
			<< p.vel.start.bias
 | 
			
		||||
			<< p.acc.start.bias
 | 
			
		||||
			<< p.exptime.start.bias
 | 
			
		||||
			<< p.size.start.bias;
 | 
			
		||||
 | 
			
		||||
		std::ostringstream os(std::ios_base::binary);
 | 
			
		||||
 | 
			
		||||
		// final tween frames of older properties
 | 
			
		||||
		p.pos.end.serialize(os);
 | 
			
		||||
		p.vel.end.serialize(os);
 | 
			
		||||
		p.acc.end.serialize(os);
 | 
			
		||||
		p.exptime.end.serialize(os);
 | 
			
		||||
		p.size.end.serialize(os);
 | 
			
		||||
 | 
			
		||||
		// properties for legacy texture field
 | 
			
		||||
		p.texture.serialize(os, protocol_version, true);
 | 
			
		||||
 | 
			
		||||
		// new properties
 | 
			
		||||
		p.drag.serialize(os);
 | 
			
		||||
		p.jitter.serialize(os);
 | 
			
		||||
		p.bounce.serialize(os);
 | 
			
		||||
		ParticleParamTypes::serializeParameterValue(os, p.attractor_kind);
 | 
			
		||||
		if (p.attractor_kind != ParticleParamTypes::AttractorKind::none) {
 | 
			
		||||
			p.attract.serialize(os);
 | 
			
		||||
			p.attractor_origin.serialize(os);
 | 
			
		||||
			writeU16(os, p.attractor_attachment); /* object ID */
 | 
			
		||||
			writeU8(os, p.attractor_kill);
 | 
			
		||||
			if (p.attractor_kind != ParticleParamTypes::AttractorKind::point) {
 | 
			
		||||
				p.attractor_direction.serialize(os);
 | 
			
		||||
				writeU16(os, p.attractor_direction_attachment);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		p.radius.serialize(os);
 | 
			
		||||
 | 
			
		||||
		ParticleParamTypes::serializeParameterValue(os, (u16)p.texpool.size());
 | 
			
		||||
		for (const auto& tex : p.texpool) {
 | 
			
		||||
			tex.serialize(os, protocol_version);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		pkt.putRawString(os.str());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Send(&pkt);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -3267,7 +3326,7 @@ bool Server::hudSetFlags(RemotePlayer *player, u32 flags, u32 mask)
 | 
			
		||||
	u32 new_hud_flags = (player->hud_flags & ~mask) | flags;
 | 
			
		||||
	if (new_hud_flags == player->hud_flags) // no change
 | 
			
		||||
		return true;
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	SendHUDSetFlags(player->getPeerId(), flags, mask);
 | 
			
		||||
	player->hud_flags = new_hud_flags;
 | 
			
		||||
 | 
			
		||||
@ -3692,8 +3751,8 @@ v3f Server::findSpawnPos()
 | 
			
		||||
		s32 range = MYMIN(1 + i, range_max);
 | 
			
		||||
		// We're going to try to throw the player to this position
 | 
			
		||||
		v2s16 nodepos2d = v2s16(
 | 
			
		||||
			-range + (myrand() % (range * 2)),
 | 
			
		||||
			-range + (myrand() % (range * 2)));
 | 
			
		||||
			-range + myrand_range(0, range*2),
 | 
			
		||||
			-range + myrand_range(0, range*2));
 | 
			
		||||
		// Get spawn level at point
 | 
			
		||||
		s16 spawn_level = m_emerge->getSpawnLevelAtPoint(nodepos2d);
 | 
			
		||||
		// Continue if MAX_MAP_GENERATION_LIMIT was returned by the mapgen to
 | 
			
		||||
 | 
			
		||||
@ -46,11 +46,22 @@ void myrand_bytes(void *out, size_t len)
 | 
			
		||||
	g_pcgrand.bytes(out, len);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float myrand_float()
 | 
			
		||||
{
 | 
			
		||||
	u32 uv = g_pcgrand.next();
 | 
			
		||||
	return (float)uv / (float)U32_MAX;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int myrand_range(int min, int max)
 | 
			
		||||
{
 | 
			
		||||
	return g_pcgrand.range(min, max);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float myrand_range(float min, float max)
 | 
			
		||||
{
 | 
			
		||||
	return (max-min) * myrand_float() + min;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	64-bit unaligned version of MurmurHash
 | 
			
		||||
 | 
			
		||||
@ -223,6 +223,8 @@ u32 myrand();
 | 
			
		||||
void mysrand(unsigned int seed);
 | 
			
		||||
void myrand_bytes(void *out, size_t len);
 | 
			
		||||
int myrand_range(int min, int max);
 | 
			
		||||
float myrand_range(float min, float max);
 | 
			
		||||
float myrand_float();
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	Miscellaneous functions
 | 
			
		||||
@ -446,3 +448,24 @@ inline irr::video::SColor multiplyColorValue(const irr::video::SColor &color, fl
 | 
			
		||||
			core::clamp<u32>(color.getGreen() * mod, 0, 255),
 | 
			
		||||
			core::clamp<u32>(color.getBlue() * mod, 0, 255));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename T> inline T numericAbsolute(T v) { return v < 0 ? T(-v) : v;                }
 | 
			
		||||
template <typename T> inline T numericSign(T v)     { return T(v < 0 ? -1 : (v == 0 ? 0 : 1)); }
 | 
			
		||||
 | 
			
		||||
inline v3f vecAbsolute(v3f v)
 | 
			
		||||
{
 | 
			
		||||
	return v3f(
 | 
			
		||||
		numericAbsolute(v.X),
 | 
			
		||||
		numericAbsolute(v.Y),
 | 
			
		||||
		numericAbsolute(v.Z)
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
inline v3f vecSign(v3f v)
 | 
			
		||||
{
 | 
			
		||||
	return v3f(
 | 
			
		||||
		numericSign(v.X),
 | 
			
		||||
		numericSign(v.Y),
 | 
			
		||||
		numericSign(v.Z)
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -45,7 +45,7 @@ public:
 | 
			
		||||
	Buffer()
 | 
			
		||||
	{
 | 
			
		||||
		m_size = 0;
 | 
			
		||||
		data = NULL;
 | 
			
		||||
		data = nullptr;
 | 
			
		||||
	}
 | 
			
		||||
	Buffer(unsigned int size)
 | 
			
		||||
	{
 | 
			
		||||
@ -53,7 +53,7 @@ public:
 | 
			
		||||
		if(size != 0)
 | 
			
		||||
			data = new T[size];
 | 
			
		||||
		else
 | 
			
		||||
			data = NULL;
 | 
			
		||||
			data = nullptr;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Disable class copy
 | 
			
		||||
@ -82,7 +82,7 @@ public:
 | 
			
		||||
			memcpy(data, t, size);
 | 
			
		||||
		}
 | 
			
		||||
		else
 | 
			
		||||
			data = NULL;
 | 
			
		||||
			data = nullptr;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	~Buffer()
 | 
			
		||||
@ -166,7 +166,7 @@ public:
 | 
			
		||||
		if(m_size != 0)
 | 
			
		||||
			data = new T[m_size];
 | 
			
		||||
		else
 | 
			
		||||
			data = NULL;
 | 
			
		||||
			data = nullptr;
 | 
			
		||||
		refcount = new unsigned int;
 | 
			
		||||
		memset(data,0,sizeof(T)*m_size);
 | 
			
		||||
		(*refcount) = 1;
 | 
			
		||||
@ -201,7 +201,7 @@ public:
 | 
			
		||||
			memcpy(data, t, m_size);
 | 
			
		||||
		}
 | 
			
		||||
		else
 | 
			
		||||
			data = NULL;
 | 
			
		||||
			data = nullptr;
 | 
			
		||||
		refcount = new unsigned int;
 | 
			
		||||
		(*refcount) = 1;
 | 
			
		||||
	}
 | 
			
		||||
@ -216,7 +216,7 @@ public:
 | 
			
		||||
				memcpy(data, *buffer, buffer.getSize());
 | 
			
		||||
		}
 | 
			
		||||
		else
 | 
			
		||||
			data = NULL;
 | 
			
		||||
			data = nullptr;
 | 
			
		||||
		refcount = new unsigned int;
 | 
			
		||||
		(*refcount) = 1;
 | 
			
		||||
	}
 | 
			
		||||
@ -256,3 +256,4 @@ private:
 | 
			
		||||
	unsigned int m_size;
 | 
			
		||||
	unsigned int *refcount;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user