// Emacs style mode select   -*- C++ -*-
//-----------------------------------------------------------------------------
//
// $Id: 02c9b9263cbce6ba7a2f6bc70b29284ccb812ed7 $
//
// 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: 02c9b9263cbce6ba7a2f6bc70b29284ccb812ed7 $")
