945 lines
37 KiB
Plaintext
945 lines
37 KiB
Plaintext
/*======================================================================
|
|
Generate PARTICLES
|
|
|
|
PRIMARY SPRITE/MODE PARTICLE EMITTER FUNCTION
|
|
- Designed for Fitz type engines (retro pixel goodness)
|
|
|
|
DPP particle functions
|
|
- float(string effectname) particleeffectnum
|
|
- -void(entity ent, float effectnum, vector start, vector end) trailparticles
|
|
- -void(float effectnum, vector org, vector vel, float howmany) pointparticles
|
|
|
|
======================================================================*/
|
|
void() emit_particle;
|
|
float() emit_checksource =
|
|
{
|
|
// killtarget can easily destroy connections between entities
|
|
// Some particle emitter types rely on the source entity connection
|
|
// Quake maintains all entities in one list with a number as reference
|
|
// If an entity is deleted the entity number can be given to any new
|
|
// entity spawned. This is not a reference to the original entity but
|
|
// it is just a location in a list.
|
|
// To get around this problem any entity that spawns a particle
|
|
// emitter will generate an unique number which the emitter will
|
|
// keep track of and compare to make sure the connection is valid.
|
|
//
|
|
// If the connection is broken, destroy particle emitter
|
|
if (self.entno_unique != self.pemit_source.entno_unique)
|
|
self.part_style = PARTICLE_STYLE_EMPTY;
|
|
|
|
if (self.part_style == PARTICLE_STYLE_EMPTY) self.state = STATE_OFF;
|
|
// Exception - respawning particle are never blocked!
|
|
else if (self.part_style != PARTICLE_STYLE_RESPAWN) {
|
|
// Has the particle emitter SOURCE been switched off?
|
|
if (self.pemit_source.estate & ESTATE_BLOCK) self.state = STATE_OFF;
|
|
}
|
|
|
|
// Final check to see if emitter is active or not?
|
|
if (self.state == STATE_OFF) { self.estate = ESTATE_OFF; return TRUE; }
|
|
else return FALSE;
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
void() emitdpp_particle = {
|
|
|
|
// Check source entity and style type are valid
|
|
if (emit_checksource()) return;
|
|
|
|
// Double check DP particles active? (quickload issues)
|
|
// If not active, revert back to Fitz/particle system
|
|
if (!ext_dppart) {
|
|
self.think = emit_particle;
|
|
self.nextthink = time + random();
|
|
// Check if particle banks need extending
|
|
extend_particlechain();
|
|
return;
|
|
}
|
|
|
|
if (self.pemit_target) {
|
|
// Easier solution for finding particle direction (velocity)
|
|
// Subtract target from source and normalize (range 0-1)
|
|
// Previous version was using vectoangles (was broken)
|
|
self.dpp_vel = normalize(self.pemit_target.origin - self.pemit_source.origin);
|
|
}
|
|
|
|
// Check for volume particle spawning
|
|
if (!CheckZeroVector(self.part_vol)) {
|
|
|
|
// Check if owner is a Bmodel? (different origin location)
|
|
if (self.pemit_source.bsporigin)
|
|
self.pos1 = self.pemit_source.origin + bmodel_origin(self.pemit_source);
|
|
else self.pos1 = self.pemit_source.origin;
|
|
|
|
self.pos1_x = self.pos1_x + ( (random()*(self.part_vol_x*2)) - self.part_vol_x);
|
|
self.pos1_y = self.pos1_y + ( (random()*(self.part_vol_y*2)) - self.part_vol_y);
|
|
self.pos1_z = self.pos1_z + ( (random()*(self.part_vol_z*2)) - self.part_vol_z);
|
|
}
|
|
else self.pos1 = self.pemit_source.origin;
|
|
|
|
// Generate particles (DP Engine)
|
|
// DP particles are designed to go at origin, not origin+offset
|
|
pointparticles(particleeffectnum(self.dpp_name), self.pos1, self.dpp_vel, 1);
|
|
self.think = emitdpp_particle;
|
|
self.nextthink = time + self.dpp_wait + (random() * self.dpp_rnd);
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
// Custom setup for Floor Circle particle emitter
|
|
//----------------------------------------------------------------------
|
|
void() emitdpp_fcircle =
|
|
{
|
|
local float loop_cnt;
|
|
local vector circle_vec, circle_org;
|
|
|
|
// Check source entity and style type are valid
|
|
if (emit_checksource()) return;
|
|
|
|
// Double check DP particles active? (quickload issues)
|
|
// If not active, revert back to Fitz/particle system
|
|
if (!ext_dppart) {
|
|
self.think = emit_particle;
|
|
self.nextthink = time + random();
|
|
return;
|
|
}
|
|
|
|
loop_cnt = 4;
|
|
while(loop_cnt > 0) {
|
|
loop_cnt = loop_cnt - 1;
|
|
|
|
circle_vec = '0 0 0';
|
|
circle_vec_y = circle_vec_y + rint(random()*360);
|
|
makevectors(circle_vec);
|
|
circle_org = self.pemit_source.origin + v_forward * self.part_vol_x;
|
|
|
|
// Generate particles (DP Engine) around circumference
|
|
pointparticles(particleeffectnum(self.dpp_name), circle_org, self.dpp_vel, 1);
|
|
}
|
|
|
|
// Draw bright pulse ring texture over the floor pattern
|
|
// Only draw this if the circle is the original shape
|
|
if (self.part_vol_x == 56)
|
|
pointparticles(particleeffectnum(DPP_FCIRCLE_RING), self.pemit_source.origin, '0 0 0', 1);
|
|
|
|
self.think = emitdpp_fcircle;
|
|
self.nextthink = time + self.dpp_wait + (random() * self.dpp_rnd);
|
|
};
|
|
|
|
//======================================================================
|
|
// Particle weather system
|
|
//======================================================================
|
|
// splash animation (runs at 20fps)
|
|
void() pe_splash1 = [0, pe_splash2] {self.nextthink = time+0.05;};
|
|
void() pe_splash2 = [1, pe_splash3] {self.nextthink = time+0.05;};
|
|
void() pe_splash3 = [2, pe_splash4] {self.nextthink = time+0.05;};
|
|
void() pe_splash4 = [3, pe_splash5] {self.nextthink = time+0.05;};
|
|
void() pe_splash5 = [4, pe_splash6] {self.nextthink = time+0.05;};
|
|
void() pe_splash6 = [5, finish_particle] {self.nextthink = time+0.05;};
|
|
|
|
//----------------------------------------------------------------------
|
|
void() emitepp_weather =
|
|
{
|
|
// Check for variable wind direction
|
|
if (self.attack_speed < time && self.speed > 0) {
|
|
self.attack_speed = time + self.speed + (random()*self.speed);
|
|
self.pos3_x = crandom() * self.pos2_x;
|
|
self.pos3_y = crandom() * self.pos2_y;
|
|
}
|
|
|
|
// Work out random colour range
|
|
self.t_width = self.pos1_x + random()*self.pos1_z;
|
|
// Vary the count by +/- 25% of total
|
|
self.t_length = self.count * 0.75 + ((random() * 0.5) * self.count);
|
|
// Vary the wind speed by +/- 25% of total
|
|
self.pos3_z = self.pos2_z * 0.75 + ((random() * 0.5) * self.pos2_z);
|
|
|
|
// DP volume particle system parameters
|
|
// vector mins : minimum corner of the cube
|
|
// vector maxs : maximum corner of the cube
|
|
// vector velocity : velocity of particles
|
|
// short count : number of particles
|
|
// byte color : 8bit palette color
|
|
|
|
// Double check if engine weather systems are enabled
|
|
if (query_configflag(SVR_WEATHER)) {
|
|
// Wait for any change, slow timer
|
|
self.think = emitepp_weather;
|
|
self.nextthink = time + 2;
|
|
}
|
|
else {
|
|
if (self.spawnflags & PARTICLE_WEATHER_SNOW)
|
|
te_particlesnow(self.bbmins, self.bbmaxs, self.pos3, self.t_length, self.t_width);
|
|
else te_particlerain(self.bbmins, self.bbmaxs, self.pos3, self.t_length, self.t_width);
|
|
self.think = emitepp_weather;
|
|
self.nextthink = time + 0.1;
|
|
}
|
|
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
void() emit_weather_think =
|
|
{
|
|
if (self.origin_z < (self.pos2_z-4)) {
|
|
self.movetype = MOVETYPE_NONE;
|
|
setorigin(self, self.pos2 + '0 0 12');
|
|
setmodel(self, self.headmdl);
|
|
self.velocity = '0 0 0';
|
|
pe_splash1();
|
|
}
|
|
else {
|
|
self.think = emit_weather_think;
|
|
self.nextthink = time + 0.1;
|
|
}
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
void() emit_weather =
|
|
{
|
|
// Check source entity and style type are valid
|
|
if (emit_checksource()) return;
|
|
if (!ext_dppart) return;
|
|
|
|
// Setup next particle (loop)
|
|
self.think = emit_weather;
|
|
|
|
// make sure player distance check is available
|
|
if (!self.enemy) self.enemy = find(world, classname, "player");
|
|
if ( !(self.enemy.flags & FL_CLIENT) ) {
|
|
self.nextthink = time + 0.1;
|
|
return;
|
|
}
|
|
|
|
// Is the particle emitter close to the player?
|
|
if (range_distance(self.enemy, TRUE) < self.wakeup_dist) {
|
|
|
|
// Normal think timer, player within range
|
|
self.nextthink = time + self.spawn_base + (random()*self.spawn_rand);
|
|
|
|
// Is there anymore particles left to spawn?
|
|
if (self.pemit_tcount < self.part_limit) {
|
|
part = fetch_particle();
|
|
// Only preceed if a particle has been returned
|
|
if (part.classtype == CT_PARTICLE) {
|
|
part.owner = self;
|
|
self.pemit_tcount = self.pemit_tcount + 1;
|
|
part.movetype = self.part_movetype;
|
|
setmodel(part, self.spr_name1);
|
|
part.headmdl = self.spr_name2;
|
|
setsize(part, VEC_ORIGIN, VEC_ORIGIN);
|
|
part.velocity_x = self.part_velrand_x * random();
|
|
part.velocity_y = self.part_velrand_y * random();
|
|
part.velocity_z = self.part_velrand_z * random();
|
|
part.velocity = part.velocity + self.part_vel;
|
|
part.velocity_z = -part.velocity_z;
|
|
|
|
// Pick a random point across the top of volume
|
|
part.pos1_x = self.pos1_x * random();
|
|
part.pos1_y = self.pos1_y * random();
|
|
part.pos1_z = 0;
|
|
part.pos1 = part.pos1 + self.pos2;
|
|
setorigin(part, part.pos1);
|
|
|
|
// trace down to find travel distance
|
|
traceline (part.pos1, part.pos1 + '0 0 -4096', TRUE, self);
|
|
part.pos2 = trace_endpos;
|
|
|
|
// Find out if any liquid in the way
|
|
if (trace_inwater) self.height = 8;
|
|
else self.count = 0;
|
|
|
|
// Binary divide the distance to find water surface
|
|
while (self.height > 0) {
|
|
// Break out early from loop if <8 from water surface
|
|
if (fabs(part.pos2_z-part.pos1_z) < 8) self.height = 0;
|
|
// Calculate midway point between origin and endtrace
|
|
part.pos3 = part.pos1;
|
|
part.pos3_z = part.pos1_z + ((part.pos2_z - part.pos1_z)*0.5);
|
|
|
|
// Test which half has water and shift top/bottom positions
|
|
traceline (part.pos1, part.pos3, TRUE, self);
|
|
if (trace_inwater) part.pos2 = part.pos3;
|
|
else part.pos1 = part.pos3;
|
|
// Only loop a limited amount of times
|
|
self.height = self.height - 1;
|
|
}
|
|
|
|
// Check travel distance
|
|
part.think = emit_weather_think;
|
|
part.nextthink = time + 0.1;
|
|
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// If no player within range of the emitter, slower think timer
|
|
self.nextthink = time + self.wakeup_timer + (random()*self.spawn_rand);
|
|
}
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
void() emit_particle =
|
|
{
|
|
local float randno;
|
|
local vector angle_vec;
|
|
|
|
// Check source entity and style type are valid
|
|
if (emit_checksource()) return;
|
|
|
|
// Setup next particle (loop)
|
|
self.think = emit_particle;
|
|
|
|
// make sure player distance check is available
|
|
if (!self.enemy) self.enemy = find(world, classname, "player");
|
|
if ( !(self.enemy.flags & FL_CLIENT) ) {
|
|
self.nextthink = time + 0.1;
|
|
return;
|
|
}
|
|
|
|
// Is the particle emitter close to the player?
|
|
if (range_distance(self.enemy, TRUE) < self.wakeup_dist) {
|
|
|
|
// Normal think timer, player within range
|
|
self.nextthink = time + self.spawn_base + (random()*self.spawn_rand);
|
|
|
|
// Is there anymore particles left to spawn?
|
|
if (self.pemit_tcount < self.part_limit) {
|
|
part = fetch_particle();
|
|
// Only preceed if a particle has been returned
|
|
if (part.classtype == CT_PARTICLE) {
|
|
part.owner = self;
|
|
self.pemit_tcount = self.pemit_tcount + 1;
|
|
part.movetype = self.part_movetype; // 6=Gravity, 8=Noclip, 10=Bounce
|
|
|
|
// Select particle type
|
|
randno = random();
|
|
|
|
//----------------------------------------------------------------------
|
|
// Setup SPRITE particle (string and frame no)
|
|
//----------------------------------------------------------------------
|
|
if (randno < 0.3) setmodel(part, self.spr_name1);
|
|
else if (randno < 0.6) setmodel(part, self.spr_name2);
|
|
else setmodel(part, self.spr_name3);
|
|
|
|
//----------------------------------------------------------------------
|
|
// Sprite Frame style (1=LIGHT, 2=DARK, 3=ALL)
|
|
//----------------------------------------------------------------------
|
|
if (self.spr_frame == 1) part.frame = rint(random()*1.4);
|
|
else if (self.spr_frame == 2) part.frame = rint(random()*2);
|
|
else part.frame = rint(random()*3.4);
|
|
|
|
// Zero size and world interaction
|
|
setsize (part, VEC_ORIGIN, VEC_ORIGIN);
|
|
|
|
//----------------------------------------------------------------------
|
|
// STARTING POINT (Particle origin - uses MANGLE key)
|
|
//
|
|
// 0 = Randomly picked from a volume (X/Y/Z) - DEFAULT
|
|
// 1 = Circular motion (radius/segment controlled)
|
|
// 2 = Randomly pick a point on the circle circumference
|
|
// 3 = Spiral in/out from circle circumference
|
|
// 5 = Explosion from central point
|
|
//----------------------------------------------------------------------
|
|
// Circular motion around emitter (self)
|
|
if (self.part_veltype == PARTICLE_ORIGIN_CIRCLE) {
|
|
angle_vec = '0 0 0';
|
|
angle_vec_y = angle_vec_y + self.circular_angle;
|
|
makevectors(angle_vec);
|
|
part.origin = self.origin + v_forward * self.part_vol_x;
|
|
setorigin (part, part.origin + self.part_ofs);
|
|
self.circular_angle = anglemod(self.circular_angle + self.part_vol_y);
|
|
}
|
|
// Random circumference point around emitter (self)
|
|
else if (self.part_veltype == PARTICLE_ORIGIN_RANDCIRCLE) {
|
|
angle_vec = '0 0 0';
|
|
angle_vec_y = angle_vec_y + rint(random()*360);
|
|
makevectors(angle_vec);
|
|
part.origin = self.origin + v_forward * self.part_vol_x;
|
|
setorigin (part, part.origin + self.part_ofs);
|
|
}
|
|
// Spiral in/out around circumference point (self)
|
|
else if (self.part_veltype == PARTICLE_ORIGIN_SPIRAL) {
|
|
angle_vec = '0 0 0';
|
|
angle_vec_y = angle_vec_y + self.circular_angle;
|
|
makevectors(angle_vec);
|
|
if (self.lefty == 0) self.lefty = 1;
|
|
self.part_vol_z = self.part_vol_z + self.lefty;
|
|
if (self.part_vol_z == 0) self.lefty = 1;
|
|
if (self.part_vol_z == self.part_vol_x) self.lefty = -1;
|
|
part.origin = self.origin + v_forward * self.part_vol_z;
|
|
setorigin (part, part.origin + self.part_ofs);
|
|
self.circular_angle = anglemod(self.circular_angle + self.part_vol_y);
|
|
}
|
|
// Explosion from central point (self)
|
|
else if (self.part_veltype == PARTICLE_ORIGIN_CENTER) {
|
|
// Always calculate from the center of original object
|
|
part.origin = self.pemit_source.origin + self.part_ofs;
|
|
setorigin (part, part.origin);
|
|
}
|
|
// Random volume point around particle (part)
|
|
else {
|
|
// Check if owner is a Bmodel? (different origin location)
|
|
if (self.pemit_source.bsporigin) {
|
|
part.pos1 = self.pemit_source.origin + bmodel_origin(self.pemit_source);
|
|
part.origin = part.pos1;
|
|
// Double check the bmodel volume has been setup correctly (not zero)
|
|
// Particle emitters can be started before bmodel has initialized
|
|
if (CheckZeroVector(self.part_vol)) self.part_vol = self.pemit_source.size * 0.5;
|
|
}
|
|
else part.origin = self.pemit_source.origin + self.part_ofs;
|
|
|
|
part.origin_x = part.origin_x + ( (random()*(self.part_vol_x*2)) - self.part_vol_x);
|
|
part.origin_y = part.origin_y + ( (random()*(self.part_vol_y*2)) - self.part_vol_y);
|
|
part.origin_z = part.origin_z + ( (random()*(self.part_vol_z*2)) - self.part_vol_z);
|
|
setorigin (part, part.origin);
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
// VELOCITY (Particle speed/direction - uses ANGLES key)
|
|
//
|
|
// Initially setup linear velocity
|
|
// if PART_VELRAND defined create extra wobble/randomness
|
|
// if oldenemy (noise4) specified go towards specific target entity
|
|
//----------------------------------------------------------------------
|
|
if (self.pemit_target) {
|
|
// Check if finish target is a Bmodel? (different origin location)
|
|
if (self.pemit_target.bsporigin) {
|
|
part.pos2 = self.pemit_target.origin + bmodel_origin(self.pemit_target);
|
|
angle_vec = part.pos2 - part.origin;
|
|
}
|
|
else
|
|
angle_vec = self.pemit_target.origin - part.origin; // Target -> Self
|
|
|
|
angle_vec = normalize(angle_vec); // vector unit scale
|
|
part.velocity = angle_vec * ( (random()*self.part_vel_x) + self.part_vel_x);
|
|
|
|
// Add extra wobble if part_velrand defined
|
|
if (self.part_velrand_x > 0 || self.part_velrand_y > 0 || self.part_velrand_z > 0) {
|
|
part.velocity_x = part.velocity_x + (random()*(self.part_velrand_x*2)) - self.part_velrand_x;
|
|
part.velocity_y = part.velocity_y + (random()*(self.part_velrand_y*2)) - self.part_velrand_y;
|
|
part.velocity_z = part.velocity_z + (random()*(self.part_velrand_z*2)) - self.part_velrand_z;
|
|
}
|
|
}
|
|
// Explosion from central point (self)
|
|
else if (self.part_veltype == PARTICLE_ORIGIN_CENTER) {
|
|
// Setup BASE + CRANDOM (+/-) velocity
|
|
part.velocity = '0 0 0';
|
|
part.velocity_x = self.part_vel_x - (random()*(self.part_vel_x*2));
|
|
part.velocity_y = self.part_vel_y - (random()*(self.part_vel_y*2));
|
|
part.velocity_z = self.part_vel_z - (random()*(self.part_vel_z*2));
|
|
part.velocity = part.velocity + self.part_velbase;
|
|
}
|
|
else {
|
|
// Setup BASE + RANDOM (+) velocity
|
|
part.velocity = '0 0 0';
|
|
part.velocity_x = random(self.part_vel_x) + self.part_vel_x;
|
|
part.velocity_y = random(self.part_vel_y) + self.part_vel_y;
|
|
part.velocity_z = random(self.part_vel_z) + self.part_vel_z;
|
|
part.velocity = part.velocity + self.part_velbase;
|
|
|
|
// Add extra wobble if part_velrand defined
|
|
if (self.part_velrand_x > 0 || self.part_velrand_y > 0 || self.part_velrand_z > 0) {
|
|
part.velocity_x = part.velocity_x + (random()*(self.part_velrand_x*2)) - self.part_velrand_x;
|
|
part.velocity_y = part.velocity_y + (random()*(self.part_velrand_y*2)) - self.part_velrand_y;
|
|
part.velocity_z = part.velocity_z + (random()*(self.part_velrand_z*2)) - self.part_velrand_z;
|
|
}
|
|
}
|
|
//----------------------------------------------------------------------
|
|
// ROTATION (Y axis only - uses PART_VELROT key)
|
|
// Random Y axis rotation
|
|
//----------------------------------------------------------------------
|
|
if (self.part_velrot) {
|
|
part.angles = self.enemy.angles; // Rotate the same way first
|
|
part.avelocity = '0 0 0';
|
|
part.avelocity_y = self.part_velrot + (random()*self.part_velrot);
|
|
}
|
|
|
|
// Setup time of death!
|
|
part.nextthink = time + random() + self.part_life;
|
|
part.think = finish_particle;
|
|
}
|
|
|
|
}
|
|
}
|
|
else {
|
|
// If no player within range of the emitter, slower think timer
|
|
self.nextthink = time + self.wakeup_timer + (random()*self.spawn_rand);
|
|
}
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
// Pick a particle sprite based on type
|
|
//----------------------------------------------------------------------
|
|
void(entity part_ent, float part_style) particle_style =
|
|
{
|
|
local float ring_rand;
|
|
|
|
ring_rand = random();
|
|
if (ring_rand < 0.4) {
|
|
if (part_style & PARTICLE_BURST_YELLOW) setmodel (part_ent, PART_DOTSML_GOLD);
|
|
else if (part_style & PARTICLE_BURST_GREEN) setmodel (part_ent, PART_DOTSML_LGREEN);
|
|
else if (part_style & PARTICLE_BURST_RED) setmodel (part_ent, PART_DOTSML_GREY);
|
|
else if (part_style & PARTICLE_BURST_BLUE) setmodel (part_ent, PART_DOTSML_BLUE);
|
|
else if (part_style & PARTICLE_BURST_PURPLE) setmodel (part_ent, PART_DOTSML_PURP);
|
|
else if (part_style & PARTICLE_BURST_FIRE) setmodel (part_ent, PART_TORCH1);
|
|
else setmodel (part_ent, PART_DOTSML_WHITE);
|
|
}
|
|
else if (ring_rand < 0.8) {
|
|
//----------------------------------------------------------------------
|
|
if (part_style & PARTICLE_BURST_YELLOW) setmodel (part_ent, PART_DOTSML_YELLOW);
|
|
else if (part_style & PARTICLE_BURST_GREEN) setmodel (part_ent, PART_DOTSML_GREEN);
|
|
else if (part_style & PARTICLE_BURST_RED) setmodel (part_ent, PART_DOTSML_RED);
|
|
else if (part_style & PARTICLE_BURST_BLUE) setmodel (part_ent, PART_DOTSML_GREY);
|
|
else if (part_style & PARTICLE_BURST_PURPLE) setmodel (part_ent, PART_DOTSML_GREY);
|
|
else if (part_style & PARTICLE_BURST_FIRE) setmodel (part_ent, PART_DOTSML_GOLD);
|
|
else setmodel (part_ent, PART_DOTSML_GREY);
|
|
}
|
|
else {
|
|
//----------------------------------------------------------------------
|
|
if (part_style & PARTICLE_BURST_YELLOW) setmodel (part_ent, PART_DOTMED_YELLOW);
|
|
else if (part_style & PARTICLE_BURST_GREEN) setmodel (part_ent, PART_DOTMED_GREEN);
|
|
else if (part_style & PARTICLE_BURST_RED) setmodel (part_ent, PART_DOTMED_RED);
|
|
else if (part_style & PARTICLE_BURST_BLUE) setmodel (part_ent, PART_DOTMED_BLUE);
|
|
else if (part_style & PARTICLE_BURST_PURPLE) setmodel (part_ent, PART_DOTMED_PURP);
|
|
else if (part_style & PARTICLE_BURST_FIRE) setmodel (part_ent, PART_DOTMED_GREY);
|
|
else setmodel (part_ent, PART_DOTMED_GREY);
|
|
}
|
|
// Random frame (sprite)
|
|
part.frame = rint(random()*3);
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
// Particle ring explosion (used for respawn function)
|
|
// ring_org : center of particle ring
|
|
// ring_dir : direction for particles to move
|
|
// ring_rdir : random wobble for particle direction
|
|
// ring_rad : starting point offset (grow/shrink ring size)
|
|
// ring_qty : circle division (how many points around circumference)
|
|
// ring_time : lifespan of particle (time + random()*time)
|
|
// ring_style : particle ring colour (1=yellow, 2=green, 3=red, 4=white)
|
|
//----------------------------------------------------------------------
|
|
void(vector ring_org, vector ring_dir, vector ring_rdir, float ring_rad, float ring_qty, float ring_time, float ring_style) particle_ring =
|
|
{
|
|
local vector circle_vec, circle_org;
|
|
local float ring_loop, ring_angle;
|
|
local string dpp_string;
|
|
|
|
// Have particles been enabled via serverflags?
|
|
if (query_configflag(SVR_PARTICLES) != SVR_PARTICLES) return;
|
|
|
|
// Setup DP particle style based on supplied style
|
|
if (ext_dppart && query_configflag(SVR_SPRPARTON) == FALSE && world.sprite_particles == FALSE) {
|
|
if (ring_style & PARTICLE_BURST_YELLOW) dpp_string = DPP_RINGPARTY;
|
|
else if (ring_style & PARTICLE_BURST_GREEN) dpp_string = DPP_RINGPARTG;
|
|
else if (ring_style & PARTICLE_BURST_RED) dpp_string = DPP_RINGPARTR;
|
|
else if (ring_style & PARTICLE_BURST_BLUE) dpp_string = DPP_RINGPARTB;
|
|
else if (ring_style & PARTICLE_BURST_PURPLE) dpp_string = DPP_RINGPARTP;
|
|
else dpp_string = DPP_RINGPARTW;
|
|
}
|
|
else {
|
|
// Are there any free particles?
|
|
// Check for maximim amount of particles available
|
|
if ( (part_total+ring_qty) >= part_max - 1) return;
|
|
}
|
|
|
|
ring_loop = 0;
|
|
ring_angle = 360 / ring_qty;
|
|
while (ring_loop < ring_qty) {
|
|
// Is the DP particle system active?
|
|
if (ext_dppart && query_configflag(SVR_SPRPARTON) == FALSE && world.sprite_particles == FALSE) {
|
|
// Work out point on circle circumference using
|
|
// the v_forward vector with incremental angles
|
|
circle_vec = '0 0 0';
|
|
circle_vec_y = circle_vec_y + (ring_angle * ring_loop);
|
|
makevectors(circle_vec);
|
|
circle_org = ring_org + (v_forward * ring_rad);
|
|
|
|
// Generate particles (DP Engine)
|
|
pointparticles(particleeffectnum(dpp_string), circle_org, '0 0 0', 1);
|
|
}
|
|
else {
|
|
part = fetch_particle();
|
|
// Only preceed if a particle has been returned
|
|
if (part.classtype == CT_PARTICLE) {
|
|
part.owner = self;
|
|
part.movetype = MOVETYPE_NOCLIP;
|
|
|
|
// Setup correct particle type (sprite/model) based on ring colour
|
|
particle_style(part, ring_style);
|
|
|
|
// Work out point on circle circumference using
|
|
// the v_forward vector with incremental angles
|
|
circle_vec = '0 0 0';
|
|
circle_vec_y = circle_vec_y + (ring_angle * ring_loop);
|
|
makevectors(circle_vec);
|
|
part.origin = ring_org + (v_forward * ring_rad);
|
|
setorigin (part, part.origin);
|
|
// Zero size and world interaction
|
|
setsize (part, VEC_ORIGIN, VEC_ORIGIN);
|
|
|
|
// Setup particle velocity with random element
|
|
part.velocity_x = ring_dir_x + ( (random()*(ring_rdir_x*2)) - ring_rdir_x);
|
|
part.velocity_y = ring_dir_y + ( (random()*(ring_rdir_y*2)) - ring_rdir_y);
|
|
part.velocity_z = ring_dir_z + (random()*ring_rdir_z);
|
|
|
|
// Setup time of death!
|
|
part.nextthink = time + (random()*ring_time) + ring_time;
|
|
part.think = finish_particle;
|
|
}
|
|
}
|
|
// Keep on loopin!
|
|
ring_loop = ring_loop + 1;
|
|
}
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
void(entity part_ent, vector part_org, float expl_style) explosion_style =
|
|
{
|
|
local vector org_volume, expl_vel;
|
|
|
|
// Ogre hammer is a flat floor explosion with particles bouncing
|
|
if (expl_style & PARTICLE_BURST_SHOCKWAVE) {
|
|
org_volume = '0 0 0';
|
|
org_volume_x = crandom()*16;
|
|
org_volume_y = crandom()*16;
|
|
part_ent.origin = part_org + org_volume;
|
|
setorigin (part_ent, part_ent.origin);
|
|
|
|
// Random chance of dead particles that are
|
|
// stationary on the ground
|
|
if (random() < 0.3) {
|
|
part_ent.velocity = '0 0 0';
|
|
if (random() < 0.5) setmodel (part_ent, PART_DOTSML_GREY);
|
|
else setmodel (part_ent, PART_DOTMED_GREY);
|
|
part_ent.frame = rint(random()*3);
|
|
}
|
|
else {
|
|
// Particles that explode up and out
|
|
expl_vel_x = crandom() * 75;
|
|
expl_vel_y = crandom() * 75;
|
|
expl_vel_z = 150 + random() * 150;
|
|
part_ent.velocity = expl_vel;
|
|
part_ent.avelocity = vecrand(100,200,FALSE);
|
|
part_ent.movetype = MOVETYPE_TOSS;
|
|
part_ent.gravity = 0.75;
|
|
part_ent.flags = 0;
|
|
}
|
|
}
|
|
// Skull wizard teleporting = particle burst upwards
|
|
else if (expl_style & PARTICLE_BURST_SKULLUP) {
|
|
part_ent.origin_x = part_org_x + crandom()*8;
|
|
part_ent.origin_y = part_org_y + crandom()*8;
|
|
// If skull wizard dead, spawn particles from robes on floor
|
|
// Otherwise the particles are being used for a teleport
|
|
if (self.health < 0) part_ent.origin_z = part_org_z - MON_VIEWOFS;
|
|
else part_ent.origin_z = part_org_z - crandom()*MON_VIEWOFS;
|
|
setorigin (part_ent, part_ent.origin);
|
|
part_ent.velocity_x = crandom()*8;
|
|
part_ent.velocity_y = crandom()*8;
|
|
part_ent.velocity_z = random()*25;
|
|
part_ent.avelocity = vecrand(100,200,FALSE);
|
|
part_ent.movetype = MOVETYPE_FLY;
|
|
part_ent.flags = 0;
|
|
}
|
|
// Lost souls fire particles upwards
|
|
else if (expl_style & PARTICLE_BURST_LOSTUP) {
|
|
part_ent.origin_x = part_org_x + crandom()*4;
|
|
part_ent.origin_y = part_org_y + crandom()*4;
|
|
part_ent.origin_z = part_org_z;
|
|
setorigin (part_ent, part_ent.origin);
|
|
part_ent.velocity_x = crandom()*4;
|
|
part_ent.velocity_y = crandom()*4;
|
|
part_ent.velocity_z = random()*10;
|
|
part_ent.avelocity = vecrand(100,200,FALSE);
|
|
part_ent.movetype = MOVETYPE_FLY;
|
|
part_ent.flags = 0;
|
|
}
|
|
// Minotaur finished plasma attack
|
|
else if (expl_style & PARTICLE_BURST_MINOTAUR) {
|
|
part_ent.origin_x = part_org_x + crandom()*8;
|
|
part_ent.origin_y = part_org_y + crandom()*8;
|
|
part_ent.origin_z = part_org_z - crandom()*MON_VIEWOFS;
|
|
setorigin (part_ent, part_ent.origin);
|
|
part_ent.velocity_x = crandom()*8;
|
|
part_ent.velocity_y = crandom()*8;
|
|
part_ent.velocity_z = random()*35;
|
|
part_ent.avelocity = vecrand(100,200,FALSE);
|
|
part_ent.movetype = MOVETYPE_FLY;
|
|
part_ent.flags = 0;
|
|
}
|
|
// Generic particle burst drifting upward
|
|
else if (expl_style & PARTICLE_BURST_UPWARD) {
|
|
part_ent.origin_x = part_org_x + crandom()*24;
|
|
part_ent.origin_y = part_org_y + crandom()*24;
|
|
part_ent.origin_z = part_org_z + random()*64;
|
|
setorigin (part_ent, part_ent.origin);
|
|
part_ent.velocity_x = crandom()*16;
|
|
part_ent.velocity_y = crandom()*16;
|
|
part_ent.velocity_z = 8+random()*16;
|
|
part_ent.movetype = MOVETYPE_FLY;
|
|
part_ent.flags = 0;
|
|
}
|
|
else {
|
|
// default = explode outwards in all directions
|
|
part_ent.origin = part_org;
|
|
setorigin (part_ent, part_ent.origin);
|
|
part_ent.velocity = vecrand(0,16,TRUE);
|
|
part_ent.avelocity = vecrand(100,200,FALSE);
|
|
part_ent.movetype = MOVETYPE_FLY;
|
|
part_ent.flags = 0;
|
|
}
|
|
};
|
|
|
|
/*======================================================================
|
|
Particle Implode
|
|
|
|
e_org : center of particle implode
|
|
e_qty : quantity of particle burst
|
|
e_speed : Particle speed towards center
|
|
e_dist : circumference of implosion circle
|
|
p_style : sprite style (check function particle_style above)
|
|
======================================================================*/
|
|
void(vector e_org, float e_qty, float e_speed, float e_dist, float p_style) particle_implode =
|
|
{
|
|
local float e_loop, e_len, e_traveltime;
|
|
local vector e_dir, e_vdestdelta, e_finaldest;
|
|
local string dpp_string;
|
|
|
|
// Have particles been enabled via serverflags?
|
|
if (query_configflag(SVR_PARTICLES) != SVR_PARTICLES) return;
|
|
|
|
// Are there any free particles?
|
|
// Check for maximim amount of particles available
|
|
if ( (part_total+e_qty) >= part_max - 1) return;
|
|
|
|
if (ext_dppart && query_configflag(SVR_SPRPARTON) == FALSE && world.sprite_particles == FALSE) {
|
|
// Setup DP particle style based on supplied style
|
|
if (p_style & PARTICLE_BURST_YELLOW) dpp_string = DPP_BURSTPARTY;
|
|
else if (p_style & PARTICLE_BURST_GREEN) dpp_string = DPP_BURSTPARTG;
|
|
else if (p_style & PARTICLE_BURST_RED) dpp_string = DPP_BURSTPARTR;
|
|
else if (p_style & PARTICLE_BURST_BLUE) dpp_string = DPP_BURSTPARTB;
|
|
else if (p_style & PARTICLE_BURST_PURPLE) dpp_string = DPP_BURSTPARTP;
|
|
else dpp_string = DPP_BURSTPARTW;
|
|
}
|
|
// pointparticles(particleeffectnum(dpp_string), e_org, '0 0 0', e_qty);
|
|
|
|
e_loop = 0;
|
|
while (e_loop < e_qty) {
|
|
e_loop = e_loop + 1; // Keep on loopin!
|
|
part = fetch_particle();
|
|
// Only preceed if a particle has been returned
|
|
if (part.classtype == CT_PARTICLE) {
|
|
part.owner = self;
|
|
particle_style(part, p_style);
|
|
|
|
// Work out random direction from origin center
|
|
e_dir = normalize(vecrand(0,50,TRUE));
|
|
e_finaldest = e_org + e_dir * (e_dist + random()*50);
|
|
|
|
// Calculate distance and time travelled
|
|
e_vdestdelta = e_org - e_finaldest;
|
|
e_len = vlen(e_vdestdelta);
|
|
e_traveltime = e_len / (e_speed + random()*50);
|
|
|
|
// Move to outer edge of implosion circle
|
|
setorigin(part, e_finaldest);
|
|
setsize (part, VEC_ORIGIN, VEC_ORIGIN);
|
|
part.movetype = MOVETYPE_NOCLIP;
|
|
|
|
// Setup inward velocity and time to kill
|
|
part.velocity = e_vdestdelta * (1/e_traveltime);
|
|
part.nextthink = time + e_traveltime;
|
|
part.think = finish_particle;
|
|
}
|
|
}
|
|
};
|
|
|
|
/*======================================================================
|
|
Particle explosion
|
|
|
|
e_org : center of particle explosion
|
|
e_qty : quantity of particle burst
|
|
e_time : lifespan of particle (time + random()*time)
|
|
p_style : sprite style (check function particle_style above)
|
|
e_style : explosion style (pre-defined in part_manage.qc)
|
|
======================================================================*/
|
|
void(vector e_org, float e_qty, float e_time, float p_style, float e_style) particle_explode =
|
|
{
|
|
local float e_loop;
|
|
local vector dp_vec;
|
|
local string dpp_string;
|
|
|
|
// Have particles been enabled via serverflags?
|
|
if (query_configflag(SVR_PARTICLES) != SVR_PARTICLES) return;
|
|
|
|
// Can only have integer particle quantity
|
|
e_qty = rint(e_qty);
|
|
|
|
if (ext_dppart && query_configflag(SVR_SPRPARTON) == FALSE && world.sprite_particles == FALSE) {
|
|
// DP hammer impact, 1=regular 2=shockwave
|
|
if (e_style & PARTICLE_BURST_SHOCKWAVE) {
|
|
if (e_style & 1) pointparticles(particleeffectnum(DPP_BURSTSHOCKWAVE1), e_org, '0 0 0', 1);
|
|
else pointparticles(particleeffectnum(DPP_BURSTSHOCKWAVE1), e_org, '0 0 0', 1);
|
|
}
|
|
else {
|
|
// Setup DP particle style based on supplied style
|
|
if (p_style & PARTICLE_BURST_YELLOW) dpp_string = DPP_BURSTPARTY;
|
|
else if (p_style & PARTICLE_BURST_GREEN) dpp_string = DPP_BURSTPARTG;
|
|
else if (p_style & PARTICLE_BURST_RED) dpp_string = DPP_BURSTPARTR;
|
|
else if (p_style & PARTICLE_BURST_BLUE) dpp_string = DPP_BURSTPARTB;
|
|
else if (p_style & PARTICLE_BURST_PURPLE) dpp_string = DPP_BURSTPARTP;
|
|
else dpp_string = DPP_BURSTPARTW;
|
|
pointparticles(particleeffectnum(dpp_string), e_org, '0 0 0', e_qty);
|
|
}
|
|
}
|
|
else {
|
|
// Are there any free particles?
|
|
// Check for maximim amount of particles available
|
|
if ( (part_total+e_qty) >= part_max - 1) return;
|
|
e_loop = 0;
|
|
while (e_loop < e_qty) {
|
|
e_loop = e_loop + 1; // Keep on loopin!
|
|
part = fetch_particle();
|
|
// Only preceed if a particle has been returned
|
|
if (part.classtype == CT_PARTICLE) {
|
|
part.owner = self;
|
|
particle_style(part, p_style);
|
|
explosion_style(part, e_org, e_style);
|
|
// Zero size and world interaction
|
|
setsize (part, VEC_ORIGIN, VEC_ORIGIN);
|
|
part.nextthink = ((time + (random() * e_time)) + e_time);
|
|
part.think = finish_particle;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
// Slowly rising cloud of particle dots
|
|
//----------------------------------------------------------------------
|
|
void(vector debuff_center, float debuff_volsize, float debuff_qty, float debuff_style) particle_debuff =
|
|
{
|
|
local float debuff_loop, debuff_time;
|
|
local vector debuff_org, debuff_vol;
|
|
local string dpp_string;
|
|
|
|
// Have particles been enabled via serverflags?
|
|
if (query_configflag(SVR_PARTICLES) != SVR_PARTICLES) return;
|
|
|
|
// Setup DP particle style based on supplied style
|
|
if (ext_dppart && query_configflag(SVR_SPRPARTON) == FALSE && world.sprite_particles == FALSE) {
|
|
if (debuff_style & PARTICLE_BURST_YELLOW) dpp_string = DPP_RINGPARTY;
|
|
else if (debuff_style & PARTICLE_BURST_GREEN) dpp_string = DPP_RINGPARTG;
|
|
else if (debuff_style & PARTICLE_BURST_RED) dpp_string = DPP_RINGPARTR;
|
|
else if (debuff_style & PARTICLE_BURST_BLUE) dpp_string = DPP_RINGPARTB;
|
|
else if (debuff_style & PARTICLE_BURST_PURPLE) dpp_string = DPP_RINGPARTP;
|
|
else dpp_string = DPP_RINGPARTW;
|
|
}
|
|
else {
|
|
// Are there any free particles?
|
|
// Check for maximim amount of particles available
|
|
if ( (part_total+debuff_qty) >= part_max - 1) return;
|
|
}
|
|
|
|
debuff_loop = 0;
|
|
debuff_time = 2;
|
|
|
|
while (debuff_loop < debuff_qty) {
|
|
debuff_vol = vecrand(0,debuff_volsize,TRUE);
|
|
debuff_org = debuff_center + debuff_vol;
|
|
|
|
// Is the DP particle system active?
|
|
if (ext_dppart && query_configflag(SVR_SPRPARTON) == FALSE && world.sprite_particles == FALSE)
|
|
pointparticles(particleeffectnum(dpp_string), debuff_org, '0 0 0', 1);
|
|
else {
|
|
part = fetch_particle();
|
|
// Only preceed if a particle has been returned
|
|
if (part.classtype == CT_PARTICLE) {
|
|
part.owner = self;
|
|
part.movetype = MOVETYPE_NOCLIP;
|
|
|
|
// Setup correct particle type (sprite/model) based on ring colour
|
|
particle_style(part, debuff_style);
|
|
setorigin (part, debuff_org);
|
|
// Zero size and world interaction
|
|
setsize (part, VEC_ORIGIN, VEC_ORIGIN);
|
|
|
|
// Setup particle velocity with random element
|
|
part.velocity = vecrand(0,2,TRUE);
|
|
part.velocity_z = 8 + random()*24;
|
|
|
|
// Setup time of death!
|
|
part.nextthink = time + (random()*debuff_time) + debuff_time;
|
|
part.think = finish_particle;
|
|
}
|
|
}
|
|
// Keep on loopin!
|
|
debuff_loop = debuff_loop + 1;
|
|
}
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
// Falling particle dust/dots
|
|
//----------------------------------------------------------------------
|
|
void(vector dust_center, float dust_qty, float dust_style) particle_dust =
|
|
{
|
|
local float dust_loop, dust_volsize, dust_time;
|
|
local vector dust_org, dust_vol;
|
|
local string dpp_string;
|
|
|
|
// Have particles been enabled via serverflags?
|
|
if (query_configflag(SVR_PARTICLES) != SVR_PARTICLES) return;
|
|
|
|
// Setup DP particle style based on supplied style
|
|
if (ext_dppart && query_configflag(SVR_SPRPARTON) == FALSE && world.sprite_particles == FALSE) {
|
|
if (dust_style & PARTICLE_BURST_YELLOW) dpp_string = DPP_RINGPARTY;
|
|
else if (dust_style & PARTICLE_BURST_GREEN) dpp_string = DPP_RINGPARTG;
|
|
else if (dust_style & PARTICLE_BURST_RED) dpp_string = DPP_RINGPARTR;
|
|
else if (dust_style & PARTICLE_BURST_BLUE) dpp_string = DPP_RINGPARTB;
|
|
else if (dust_style & PARTICLE_BURST_PURPLE) dpp_string = DPP_RINGPARTP;
|
|
else dpp_string = DPP_RINGPARTW;
|
|
}
|
|
else {
|
|
// Are there any free particles?
|
|
// Check for maximim amount of particles available
|
|
if ( (part_total+dust_qty) >= part_max - 1) return;
|
|
}
|
|
|
|
dust_loop = 0;
|
|
dust_time = 1;
|
|
dust_volsize = 16;
|
|
|
|
while (dust_loop < dust_qty) {
|
|
dust_vol = vecrand(0,dust_volsize,TRUE);
|
|
dust_org = dust_center + dust_vol;
|
|
|
|
// Is the DP particle system active?
|
|
if (ext_dppart && query_configflag(SVR_SPRPARTON) == FALSE && world.sprite_particles == FALSE)
|
|
pointparticles(particleeffectnum(dpp_string), dust_org, '0 0 0', 1);
|
|
else {
|
|
part = fetch_particle();
|
|
// Only preceed if a particle has been returned
|
|
if (part.classtype == CT_PARTICLE) {
|
|
part.owner = self;
|
|
part.movetype = MOVETYPE_NOCLIP;
|
|
part.solid = SOLID_NOT;
|
|
|
|
// Setup correct particle type (sprite/model) based on ring colour
|
|
particle_style(part, dust_style);
|
|
setorigin (part, dust_org);
|
|
// Zero size and world interaction
|
|
setsize (part, VEC_ORIGIN, VEC_ORIGIN);
|
|
|
|
// Setup particle velocity with random element
|
|
part.velocity = vecrand(0,35,TRUE);
|
|
part.velocity_z = -100 - random()*100;
|
|
part.avelocity = '300 300 300';
|
|
|
|
// Setup time of death!
|
|
part.nextthink = time + (random()*dust_time) + dust_time;
|
|
part.think = finish_particle;
|
|
}
|
|
}
|
|
// Keep on loopin!
|
|
dust_loop = dust_loop + 1;
|
|
}
|
|
};
|