mirror of
https://github.com/minetest/minetest.git
synced 2024-08-18 20:44:16 +02:00
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