mirror of
https://github.com/odamex/odamex
synced 2026-08-19 02:23:09 -04:00
474 lines
10 KiB
C++
474 lines
10 KiB
C++
// Emacs style mode select -*- C++ -*-
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
// $Id$
|
|
//
|
|
// Copyright (C) 1998-2006 by Randy Heit (ZDoom).
|
|
// Copyright (C) 2006-2026 by The Odamex Team.
|
|
//
|
|
// This program is free software; you can redistribute it and/or
|
|
// modify it under the terms of the GNU General Public License
|
|
// as published by the Free Software Foundation; either version 2
|
|
// 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 General Public License for more details.
|
|
//
|
|
// DESCRIPTION:
|
|
// COMMON Particle effect thinkers
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
#include "odamex.h"
|
|
|
|
#include "actor.h"
|
|
#include "c_effect.h"
|
|
#include "p_local.h"
|
|
#include "v_video.h"
|
|
#include "m_random.h"
|
|
#include "r_things.h"
|
|
#include "s_sound.h"
|
|
#include "w_wad.h"
|
|
|
|
// [RH] particle globals
|
|
int NumParticles;
|
|
int ActiveParticles;
|
|
int InactiveParticles;
|
|
particle_t *Particles;
|
|
|
|
#define FADEFROMTTL(a) (255/(a))
|
|
|
|
static int grey1, grey2, grey3, grey4, red, green, blue, yellow, black,
|
|
red1, green1, blue1, yellow1, purple, purple1, white,
|
|
rblue1, rblue2, rblue3, rblue4, orange, yorange, dred, grey5,
|
|
maroon1, maroon2, pink;
|
|
|
|
static const struct ColorList {
|
|
int *color, r, g, b;
|
|
} Colors[] = {
|
|
{&grey1, 85, 85, 85 },
|
|
{&grey2, 171, 171, 171},
|
|
{&grey3, 50, 50, 50 },
|
|
{&grey4, 210, 210, 210},
|
|
{&grey5, 128, 128, 128},
|
|
{&red, 255, 0, 0 },
|
|
{&green, 0, 200, 0 },
|
|
{&blue, 0, 0, 255},
|
|
{&yellow, 255, 255, 0 },
|
|
{&black, 0, 0, 0 },
|
|
{&red1, 255, 127, 127},
|
|
{&green1, 127, 255, 127},
|
|
{&blue1, 127, 127, 255},
|
|
{&yellow1, 255, 255, 180},
|
|
{&purple, 120, 0, 160},
|
|
{&purple1, 200, 30, 255},
|
|
{&white, 255, 255, 255},
|
|
{&rblue1, 81, 81, 255},
|
|
{&rblue2, 0, 0, 227},
|
|
{&rblue3, 0, 0, 130},
|
|
{&rblue4, 0, 0, 80 },
|
|
{&orange, 255, 120, 0 },
|
|
{&yorange, 255, 170, 0 },
|
|
{&dred, 80, 0, 0 },
|
|
{&maroon1, 154, 49, 49 },
|
|
{&maroon2, 125, 24, 24 },
|
|
{&pink, 255, 112, 185 },
|
|
{NULL, 0, 0, 0}
|
|
};
|
|
|
|
|
|
void P_InitEffects()
|
|
{
|
|
const struct ColorList* color = Colors;
|
|
const argb_t* palette_colors = V_GetDefaultPalette()->basecolors;
|
|
|
|
while (color->color)
|
|
{
|
|
*(color->color) = V_BestColor(palette_colors, color->r, color->g, color->b);
|
|
color++;
|
|
}
|
|
}
|
|
|
|
void P_DrawSplash (int count, fixed_t x, fixed_t y, fixed_t z, angle_t angle, int kind)
|
|
{
|
|
if (!clientside)
|
|
return;
|
|
|
|
int color1, color2;
|
|
|
|
switch (kind) {
|
|
case 1: // Spark
|
|
color1 = orange;
|
|
color2 = yorange;
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
for (; count; count--) {
|
|
particle_t *p = JitterParticle (10);
|
|
angle_t an;
|
|
|
|
if (!p)
|
|
break;
|
|
|
|
p->size = 2;
|
|
p->color = M_Random() & 0x80 ? color1 : color2;
|
|
p->velz -= M_Random () * 512;
|
|
p->accz -= FRACUNIT/8;
|
|
p->accx += (M_Random () - 128) * 8;
|
|
p->accy += (M_Random () - 128) * 8;
|
|
p->z = z - M_Random () * 1024;
|
|
an = (angle + (M_Random() << 21)) >> ANGLETOFINESHIFT;
|
|
p->x = x + (M_Random () & 15)*finecosine[an];
|
|
p->y = y + (M_Random () & 15)*finesine[an];
|
|
}
|
|
}
|
|
|
|
void P_DrawSplash2 (int count, fixed_t x, fixed_t y, fixed_t z, angle_t angle, int updown, int kind)
|
|
{
|
|
}
|
|
|
|
//
|
|
// [RH] Particle functions
|
|
//
|
|
|
|
static void MakeFountain (const AActor *actor, int color1, int color2)
|
|
{
|
|
if (!clientside)
|
|
return;
|
|
|
|
particle_t *particle;
|
|
|
|
if (!(level.time & 1))
|
|
return;
|
|
|
|
particle = JitterParticle (51);
|
|
|
|
if (particle) {
|
|
angle_t an = M_Random()<<(24-ANGLETOFINESHIFT);
|
|
fixed_t out = FixedMul (actor->radius, M_Random()<<8);
|
|
|
|
particle->x = actor->x + FixedMul (out, finecosine[an]);
|
|
particle->y = actor->y + FixedMul (out, finesine[an]);
|
|
particle->z = actor->z + actor->height + FRACUNIT;
|
|
if (out < actor->radius/8)
|
|
particle->velz += FRACUNIT*10/3;
|
|
else
|
|
particle->velz += FRACUNIT*3;
|
|
particle->accz -= FRACUNIT/11;
|
|
if (M_Random() < 30) {
|
|
particle->size = 4;
|
|
particle->color = color2;
|
|
} else {
|
|
particle->size = 6;
|
|
particle->color = color1;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void MakeHearts(const AActor* actor)
|
|
{
|
|
if (!clientside)
|
|
return;
|
|
|
|
particle_t* particle;
|
|
|
|
if (!(level.time % 5 == 0))
|
|
return;
|
|
|
|
particle = JitterParticle(51);
|
|
|
|
if (particle)
|
|
{
|
|
angle_t an = M_Random() << (24 - ANGLETOFINESHIFT);
|
|
fixed_t out = FixedMul(actor->radius, M_Random() << 8);
|
|
|
|
particle->x = actor->x + FixedMul(out, finecosine[an]);
|
|
particle->y = actor->y + FixedMul(out, finesine[an]);
|
|
particle->z = actor->z + actor->height + FRACUNIT;
|
|
if (out < actor->radius / 8)
|
|
particle->velz += FRACUNIT * 10 / 3;
|
|
else
|
|
particle->velz += FRACUNIT * 3;
|
|
particle->accz -= FRACUNIT / 15;
|
|
|
|
particle->sprite = W_GetNumForName("FRNDHRT");
|
|
}
|
|
}
|
|
|
|
//
|
|
// AddParticle
|
|
//
|
|
// Creates a particle with "jitter"
|
|
//
|
|
particle_t *JitterParticle (int ttl)
|
|
{
|
|
if (!clientside)
|
|
return NULL;
|
|
|
|
particle_t *particle = NewParticle ();
|
|
|
|
if (particle) {
|
|
fixed_t *val = &particle->velx;
|
|
int i;
|
|
|
|
// Set initial velocities
|
|
for (i = 3; i; i--, val++)
|
|
*val = (FRACUNIT/4096) * (M_Random () - 128);
|
|
// Set initial accelerations
|
|
for (i = 3; i; i--, val++)
|
|
*val = (FRACUNIT/16384) * (M_Random () - 128);
|
|
|
|
particle->trans = 255; // fully opaque
|
|
particle->ttl = ttl;
|
|
particle->fade = FADEFROMTTL(ttl);
|
|
particle->sprite = NO_PARTICLE;
|
|
}
|
|
return particle;
|
|
}
|
|
|
|
//
|
|
// P_RunEffects
|
|
//
|
|
// Run effects on all mobjs in world
|
|
//
|
|
void P_RunEffects (void)
|
|
{
|
|
if (!clientside)
|
|
return;
|
|
|
|
AActor *actor;
|
|
TThinkerIterator<AActor> iterator;
|
|
|
|
while ( (actor = iterator.Next ()) )
|
|
{
|
|
if (actor->effects)
|
|
P_RunEffect (actor, actor->effects);
|
|
}
|
|
}
|
|
|
|
void P_RunEffect (const AActor *actor, int effects)
|
|
{
|
|
if (!actor || !clientside)
|
|
return;
|
|
|
|
if (effects & FX_FOUNTAINMASK) {
|
|
// Particle fountain
|
|
|
|
static const int *fountainColors[16] =
|
|
{ &black, &black,
|
|
&red, &red1,
|
|
&green, &green1,
|
|
&blue, &blue1,
|
|
&yellow, &yellow1,
|
|
&purple, &purple1,
|
|
&black, &grey3,
|
|
&grey4, &white
|
|
};
|
|
int color = (effects & FX_FOUNTAINMASK) >> 15;
|
|
MakeFountain (actor, *fountainColors[color], *fountainColors[color+1]);
|
|
}
|
|
|
|
if (effects & FX_FRIENDHEARTS)
|
|
{
|
|
// Friend hearts
|
|
MakeHearts(actor);
|
|
}
|
|
}
|
|
|
|
void P_ThinkParticles (void)
|
|
{
|
|
if (!clientside)
|
|
return;
|
|
|
|
int i = ActiveParticles;
|
|
particle_t *particle = nullptr, *prev = nullptr;
|
|
while (i != NO_PARTICLE) {
|
|
byte oldtrans;
|
|
|
|
particle = Particles + i;
|
|
i = particle->next;
|
|
oldtrans = particle->trans;
|
|
particle->trans -= particle->fade;
|
|
if (oldtrans < particle->trans || --particle->ttl == 0) {
|
|
memset (particle, 0, sizeof(particle_t));
|
|
if (prev)
|
|
prev->next = i;
|
|
else
|
|
ActiveParticles = i;
|
|
particle->next = InactiveParticles;
|
|
InactiveParticles = particle - Particles;
|
|
continue;
|
|
}
|
|
particle->x += particle->velx;
|
|
particle->y += particle->vely;
|
|
particle->z += particle->velz;
|
|
|
|
if (particle->sprite)
|
|
{
|
|
int timefrac = ::level.time % 35;
|
|
if (timefrac > 16)
|
|
{
|
|
particle->accx += FixedMul(-timefrac, FRACUNIT * M_RandomInt(9));
|
|
particle->accy += FixedMul(-timefrac, FRACUNIT * M_RandomInt(9));
|
|
}
|
|
else
|
|
{
|
|
particle->accx -= FixedMul(timefrac, FRACUNIT * M_RandomInt(9));
|
|
particle->accy -= FixedMul(timefrac, FRACUNIT * M_RandomInt(9));
|
|
}
|
|
}
|
|
|
|
particle->velx += particle->accx;
|
|
particle->vely += particle->accy;
|
|
particle->velz += particle->accz;
|
|
prev = particle;
|
|
}
|
|
}
|
|
|
|
|
|
void P_DrawRailTrail(const v3double_t &start, const v3double_t &end)
|
|
{
|
|
if (!clientside)
|
|
return;
|
|
|
|
v3double_t step, dir, pos, extend, point;
|
|
|
|
M_SubVec3(&dir, &end, &start);
|
|
|
|
double length = M_LengthVec3(dir);
|
|
int steps = (int)(length*0.3333);
|
|
|
|
if (!length) // line is 0 length, so nothing to do
|
|
return;
|
|
|
|
// [jsd] fix null reference bug:
|
|
if (!clientside)
|
|
return;
|
|
|
|
// The railgun's sound is a special case. It gets played from the
|
|
// point on the slug's trail that is closest to the hearing player.
|
|
AActor *mo = consoleplayer().camera;
|
|
|
|
double ilength = 1.0 / length;
|
|
|
|
if (abs(mo->x - FLOAT2FIXED(start.x)) < 20 * FRACUNIT &&
|
|
abs(mo->y - FLOAT2FIXED(start.y)) < 20 * FRACUNIT)
|
|
{
|
|
// This player (probably) fired the railgun
|
|
S_Sound (mo, CHAN_WEAPON, "weapons/railgf", 1, ATTN_NORM);
|
|
}
|
|
else
|
|
{
|
|
// Only consider sound in 2D (for now, anyway)
|
|
double r = ((start.y - FIXED2FLOAT(mo->y)) * (-dir.y) -
|
|
(start.x - FIXED2FLOAT(mo->x)) * (dir.x)) * ilength * ilength;
|
|
|
|
M_ScaleVec3(&point, &dir, r);
|
|
M_AddVec3(&point, &start, &point);
|
|
point.z = start.z;
|
|
|
|
S_Sound (FLOAT2FIXED(point.x), FLOAT2FIXED(point.y),
|
|
CHAN_WEAPON, "weapons/railgf", 1, ATTN_NORM);
|
|
}
|
|
|
|
M_ScaleVec3(&dir, &dir, ilength);
|
|
M_PerpendicularVec3(&extend, &dir);
|
|
M_ScaleVec3(&extend, &extend, 3.0);
|
|
M_ScaleVec3(&step, &dir, 3.0);
|
|
|
|
pos = start;
|
|
|
|
float deg = 270.0f;
|
|
for (int i = steps; i; i--) {
|
|
particle_t *p = NewParticle ();
|
|
|
|
if (!p)
|
|
return;
|
|
|
|
p->sprite = NO_PARTICLE;
|
|
p->trans = 255;
|
|
p->ttl = 35;
|
|
p->fade = FADEFROMTTL(35);
|
|
p->size = 3;
|
|
|
|
v3double_t tempvec;
|
|
M_RotatePointAroundVector(&tempvec, &dir, &extend, deg);
|
|
|
|
p->velx = FLOAT2FIXED(tempvec.x)>>4;
|
|
p->vely = FLOAT2FIXED(tempvec.y)>>4;
|
|
p->velz = FLOAT2FIXED(tempvec.z)>>4;
|
|
M_AddVec3(&tempvec, &tempvec, &pos);
|
|
deg += 14;
|
|
if (deg >= 360)
|
|
deg -= 360;
|
|
p->x = FLOAT2FIXED(tempvec.x);
|
|
p->y = FLOAT2FIXED(tempvec.y);
|
|
p->z = FLOAT2FIXED(tempvec.z);
|
|
M_AddVec3(&pos, &pos, &step);
|
|
|
|
int rand = M_Random();
|
|
|
|
if (rand < 155)
|
|
p->color = rblue2;
|
|
else if (rand < 188)
|
|
p->color = rblue1;
|
|
else if (rand < 222)
|
|
p->color = rblue3;
|
|
else
|
|
p->color = rblue4;
|
|
}
|
|
|
|
pos = start;
|
|
|
|
for (int i = steps; i; i--) {
|
|
particle_t *p = JitterParticle (33);
|
|
|
|
if (!p)
|
|
return;
|
|
|
|
p->size = 2;
|
|
p->x = FLOAT2FIXED(pos.x);
|
|
p->y = FLOAT2FIXED(pos.y);
|
|
p->z = FLOAT2FIXED(pos.z);
|
|
p->accz -= FRACUNIT/4096;
|
|
M_AddVec3(&pos, &pos, &step);
|
|
|
|
int rand = M_Random();
|
|
|
|
if (rand < 85)
|
|
p->color = orange;
|
|
else if (rand < 170)
|
|
p->color = grey2;
|
|
else
|
|
p->color = yorange;
|
|
|
|
p->color = yellow;
|
|
}
|
|
}
|
|
|
|
void P_DisconnectEffect (const AActor *actor)
|
|
{
|
|
if (!actor || !clientside)
|
|
return;
|
|
|
|
for (int i = 64; i; i--) {
|
|
particle_t *p = JitterParticle (TICRATE*2);
|
|
|
|
if (!p)
|
|
break;
|
|
|
|
p->x = actor->x + ((M_Random()-128)<<9) * (actor->radius>>FRACBITS);
|
|
p->y = actor->y + ((M_Random()-128)<<9) * (actor->radius>>FRACBITS);
|
|
p->z = actor->z + (M_Random()<<8) * (actor->height>>FRACBITS);
|
|
p->accz -= FRACUNIT/4096;
|
|
p->color = M_Random() < 128 ? maroon1 : maroon2;
|
|
p->size = 4;
|
|
}
|
|
}
|
|
|
|
VERSION_CONTROL (p_effect_cpp, "$Id$")
|