#include "impulse.qh"
// targeted (directional) mode
void trigger_impulse_touch_directional(entity this, entity toucher)
{
	entity targ;
	float pushdeltatime;
	float str;

	if (this.active != ACTIVE_ACTIVE)
		return;

	if (!isPushable(toucher))
		return;

	EXACTTRIGGER_TOUCH(this, toucher);

	targ = find(NULL, targetname, this.target);
	if(!targ)
	{
		objerror(this, "trigger_force without a (valid) .target!\n");
		delete(this);
		return;
	}

	// falloff is not supported because radius is always 0 in directional mode
	str = this.strength;

	pushdeltatime = time - toucher.lastpushtime;
	if (pushdeltatime > IMPULSE_MAX_PUSHDELTATIME)
	{
		pushdeltatime = 0;
	}
	toucher.lastpushtime = time;
	if(!pushdeltatime)
	{
		return;
	}

	if(this.spawnflags & IMPULSE_DIRECTIONAL_SPEEDTARGET)
	{
		float addspeed = str - toucher.velocity * normalize(targ.origin - this.origin);
		if (addspeed > 0)
		{
			float accelspeed = min(IMPULSE_DIRECTIONAL_MAX_ACCEL_FACTOR * pushdeltatime * str, addspeed);
			toucher.velocity += accelspeed * normalize(targ.origin - this.origin);
		}
	}
	else
		toucher.velocity += normalize(targ.origin - this.origin) * str * pushdeltatime;

	UNSET_ONGROUND(toucher);

#ifdef SVQC
	UpdateCSQCProjectile(toucher);
#endif
}

// Directionless (accelerator/decelerator) mode
void trigger_impulse_touch_accel(entity this, entity toucher)
{
	float pushdeltatime;

	if (this.active != ACTIVE_ACTIVE)
		return;

	if (!isPushable(toucher))
		return;

	EXACTTRIGGER_TOUCH(this, toucher);

	pushdeltatime = time - toucher.lastpushtime;
	if (pushdeltatime > IMPULSE_MAX_PUSHDELTATIME)
	{
		pushdeltatime = 0;
	}
	toucher.lastpushtime = time;
	if(!pushdeltatime)
	{
		return;
	}

	// div0: ticrate independent, 1 = identity (not 20)
	toucher.velocity *= this.strength ** pushdeltatime;

#ifdef SVQC
	UpdateCSQCProjectile(toucher);
#endif
}

// Spherical (gravity/repulsor) mode
void trigger_impulse_touch_radial(entity this, entity toucher)
{
	float pushdeltatime;
	float str;

	if (this.active != ACTIVE_ACTIVE)
		return;

	if (!isPushable(toucher))
		return;

	EXACTTRIGGER_TOUCH(this, toucher);

	pushdeltatime = time - toucher.lastpushtime;
	if (pushdeltatime > IMPULSE_MAX_PUSHDELTATIME)
	{
		pushdeltatime = 0;
	}
	toucher.lastpushtime = time;
	if(!pushdeltatime)
	{
		return;
	}

	setsize(this, '-1 -1 -1' * this.radius,'1 1 1' * this.radius);

	str = min(this.radius, vlen(this.origin - toucher.origin));

	if(this.falloff == FALLOFF_LINEAR)
		str = (1 - str / this.radius) * this.strength; // 1 in the inside
	else if(this.falloff == FALLOFF_LINEAR_INV)
		str = (str / this.radius) * this.strength; // 0 in the inside
	else
		str = this.strength;

	toucher.velocity += normalize(toucher.origin - this.origin) * str * pushdeltatime;

#ifdef SVQC
	UpdateCSQCProjectile(toucher);
#endif
}

REGISTER_NET_LINKED(ENT_CLIENT_TRIGGER_IMPULSE)

/*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
Force field
-------- KEYS --------
target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
		 If not, this trigger acts like a damper/accelerator field.

strength : This is how much force to add in the direction of .target each second
		   when .target is set. If not, this is how much to slow down/accelerate
		   something cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)

radius   : If set, act as a spherical device rather then a linear one.

falloff : 0 = none, 1 = liniar, 2 = inverted liniar

-------- NOTES --------
Use a brush textured with common/origin in the trigger entity to determine the origin of the force
in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
*/
#ifdef SVQC
bool trigger_impulse_send(entity this, entity to, int sf)
{
	WriteHeader(MSG_ENTITY, ENT_CLIENT_TRIGGER_IMPULSE);

	WriteByte(MSG_ENTITY, this.spawnflags);
	WriteCoord(MSG_ENTITY, this.radius);
	WriteCoord(MSG_ENTITY, this.strength);
	WriteByte(MSG_ENTITY, this.falloff);
	WriteByte(MSG_ENTITY, this.active);

	trigger_common_write(this, true);

	return true;
}

void trigger_impulse_link(entity this)
{
	trigger_link(this, trigger_impulse_send);
}

spawnfunc(trigger_impulse)
{
	this.active = ACTIVE_ACTIVE;
	WarpZoneLib_ExactTrigger_Init(this, false);
	BITSET_ASSIGN(this.effects, EF_NODEPTHTEST);

	if(this.radius)
	{
		if(!this.strength)
		{
			this.strength = IMPULSE_DEFAULT_RADIAL_STRENGTH * autocvar_g_triggerimpulse_radial_multiplier;
		}
		setorigin(this, this.origin);
		setsize(this, '-1 -1 -1' * this.radius,'1 1 1' * this.radius);
		settouch(this, trigger_impulse_touch_radial);
	}
	else
	{
		if(this.target)
		{
			if(!this.strength)
			{
				this.strength = IMPULSE_DEFAULT_DIRECTIONAL_STRENGTH * autocvar_g_triggerimpulse_directional_multiplier;
			}
			settouch(this, trigger_impulse_touch_directional);
		}
		else
		{
			if(!this.strength)
			{
				this.strength = IMPULSE_DEFAULT_ACCEL_STRENGTH;
			}
			this.strength = (this.strength ** autocvar_g_triggerimpulse_accel_power) * autocvar_g_triggerimpulse_accel_multiplier;
			settouch(this, trigger_impulse_touch_accel);
		}
	}

	trigger_impulse_link(this);
}
#elif defined(CSQC)
NET_HANDLE(ENT_CLIENT_TRIGGER_IMPULSE, bool isnew)
{
	this.spawnflags = ReadByte();
	this.radius = ReadCoord();
	this.strength = ReadCoord();
	this.falloff = ReadByte();
	this.active = ReadByte();

	trigger_common_read(this, true);
	return = true;

	this.solid = SOLID_TRIGGER;
	this.entremove = trigger_remove_generic;
	this.move_time = time;

	if (this.radius)
	{
		settouch(this, trigger_impulse_touch_radial);
	}
	else if (this.target)
	{
		settouch(this, trigger_impulse_touch_directional);
	}
	else
	{
		settouch(this, trigger_impulse_touch_accel);
	}
}
#endif
