/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
Copyright (C) 2006 Pat Raynor <raynorpat@sbcglobal.net>

This file is part of the Fortress Evolution source code.

Fortress Evolution 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.

The Fortress Evolution source code 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.

You should have received a copy of the GNU General Public License
along with Fortress Evolution; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/

// g_misc.c
#include "g_local.h"


/*QUAKED func_group (0 0 0) ?
Used to group brushes together just for editor convenience.  They are turned into normal brushes by the utilities.
*/


/*QUAKED info_camp (0 0.5 0) (-4 -4 -4) (4 4 4)
Used as a positional target for calculations in the utilities (spotlights, etc), but removed during gameplay.
*/
void SP_info_camp(gentity_t * self)
{
	G_SetOrigin(self, self->s.origin);
}


/*QUAKED info_null (0 0.5 0) (-4 -4 -4) (4 4 4)
Used as a positional target for calculations in the utilities (spotlights, etc), but removed during gameplay.
*/
void SP_info_null(gentity_t * self)
{
	G_FreeEntity(self);
}


/*QUAKED info_notnull (0 0.5 0) (-4 -4 -4) (4 4 4)
Used as a positional target for in-game calculation, like jumppad targets.
target_position does the same thing
*/
void SP_info_notnull(gentity_t * self)
{
	G_SetOrigin(self, self->s.origin);
}


/*QUAKED light (0 1 0) (-8 -8 -8) (8 8 8) linear
Non-displayed light.
"light" overrides the default 300 intensity.
Linear checbox gives linear falloff instead of inverse square
Lights pointed at a target will be spotlights.
"radius" overrides the default 64 unit radius of a spotlight at the target point.
*/
void SP_light(gentity_t * self)
{
	G_FreeEntity(self);
}



/*
=================================================================================

TELEPORTERS

=================================================================================
*/

void TeleportPlayer(gentity_t * player, vec3_t origin, vec3_t angles)
{
	gentity_t      *tent;

	// use temp events at source and destination to prevent the effect
	// from getting dropped by a second player event
	if(player->client->sess.sessionTeam != TEAM_SPECTATOR)
	{
		tent = G_TempEntity(player->client->ps.origin, EV_PLAYER_TELEPORT_OUT);
		tent->s.clientNum = player->s.clientNum;

		tent = G_TempEntity(origin, EV_PLAYER_TELEPORT_IN);
		tent->s.clientNum = player->s.clientNum;
	}

	// unlink to make sure it can't possibly interfere with G_KillBox
	trap_UnlinkEntity(player);

	VectorCopy(origin, player->client->ps.origin);
	player->client->ps.origin[2] += 1;

	// spit the player out
	AngleVectors(angles, player->client->ps.velocity, NULL, NULL);
	VectorScale(player->client->ps.velocity, 400, player->client->ps.velocity);
	player->client->ps.pm_time = 160;	// hold time
	player->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;

	// toggle the teleport bit so the client knows to not lerp
	player->client->ps.eFlags ^= EF_TELEPORT_BIT;

	// set angles
	SetClientViewAngle(player, angles);

	// kill anything at the destination
	if(player->client->sess.sessionTeam != TEAM_SPECTATOR)
	{
		G_KillBox(player);
	}

	// save results of pmove
	BG_PlayerStateToEntityState(&player->client->ps, &player->s, qtrue);

	// use the precise origin for linking
	VectorCopy(player->client->ps.origin, player->r.currentOrigin);

	if(player->client->sess.sessionTeam != TEAM_SPECTATOR)
	{
		trap_LinkEntity(player);
	}
}


/*QUAKED misc_teleporter_dest (1 0 0) (-32 -32 -24) (32 32 -16)
Point teleporters at these.
Now that we don't have teleport destination pads, this is just
an info_notnull
*/
void SP_misc_teleporter_dest(gentity_t * ent)
{
}


//===========================================================

/*QUAKED misc_model (1 0 0) (-16 -16 -16) (16 16 16)
"model"		arbitrary .md3 file to display
*/
void SP_misc_model(gentity_t * ent)
{

#if 0
	ent->s.modelindex = G_ModelIndex(ent->model);
	VectorSet(ent->mins, -16, -16, -16);
	VectorSet(ent->maxs, 16, 16, 16);
	trap_LinkEntity(ent);

	G_SetOrigin(ent, ent->s.origin);
	VectorCopy(ent->s.angles, ent->s.apos.trBase);
#else
	G_FreeEntity(ent);
#endif
}

//===========================================================

void locateCamera(gentity_t * ent)
{
	vec3_t          dir;
	gentity_t      *target;
	gentity_t      *owner;

	owner = G_PickTarget(ent->target);
	if(!owner)
	{
		G_Printf("Couldn't find target for misc_partal_surface\n");
		G_FreeEntity(ent);
		return;
	}
	ent->r.ownerNum = owner->s.number;

	// frame holds the rotate speed
	if(owner->spawnflags & 1)
	{
		ent->s.frame = 25;
	}
	else if(owner->spawnflags & 2)
	{
		ent->s.frame = 75;
	}

	// swing camera ?
	if(owner->spawnflags & 4)
	{
		// set to 0 for no rotation at all
		ent->s.powerups = 0;
	}
	else
	{
		ent->s.powerups = 1;
	}

	// clientNum holds the rotate offset
	ent->s.clientNum = owner->s.clientNum;

	VectorCopy(owner->s.origin, ent->s.origin2);

	// see if the portal_camera has a target
	target = G_PickTarget(owner->target);
	if(target)
	{
		VectorSubtract(target->s.origin, owner->s.origin, dir);
		VectorNormalize(dir);
	}
	else
	{
		G_SetMovedir(owner->s.angles, dir);
	}

	ent->s.eventParm = DirToByte(dir);
}

/*QUAKED misc_portal_surface (0 0 1) (-8 -8 -8) (8 8 8)
The portal surface nearest this entity will show a view from the targeted misc_portal_camera, or a mirror view if untargeted.
This must be within 64 world units of the surface!
*/
void SP_misc_portal_surface(gentity_t * ent)
{
	VectorClear(ent->r.mins);
	VectorClear(ent->r.maxs);
	trap_LinkEntity(ent);

	ent->r.svFlags = SVF_PORTAL;
	ent->s.eType = ET_PORTAL;

	if(!ent->target)
	{
		VectorCopy(ent->s.origin, ent->s.origin2);
	}
	else
	{
		ent->think = locateCamera;
		ent->nextthink = level.time + 100;
	}
}

/*QUAKED misc_portal_camera (0 0 1) (-8 -8 -8) (8 8 8) slowrotate fastrotate noswing
The target for a misc_portal_director.  You can set either angles or target another entity to determine the direction of view.
"roll" an angle modifier to orient the camera around the target vector;
*/
void SP_misc_portal_camera(gentity_t * ent)
{
	float           roll;

	VectorClear(ent->r.mins);
	VectorClear(ent->r.maxs);
	trap_LinkEntity(ent);

	G_SpawnFloat("roll", "0", &roll);

	ent->s.clientNum = roll / 360.0 * 256;
}

/*
======================================================================

  SHOOTERS

======================================================================
*/

void Use_Shooter(gentity_t * ent, gentity_t * other, gentity_t * activator)
{
	vec3_t          dir;
	float           deg;
	vec3_t          up, right;

	// see if we have a target
	if(ent->enemy)
	{
		VectorSubtract(ent->enemy->r.currentOrigin, ent->s.origin, dir);
		VectorNormalize(dir);
	}
	else
	{
		VectorCopy(ent->movedir, dir);
	}

	// randomize a bit
	PerpendicularVector(up, dir);
	CrossProduct(up, dir, right);

	deg = crandom() * ent->random;
	VectorMA(dir, deg, up, dir);

	deg = crandom() * ent->random;
	VectorMA(dir, deg, right, dir);

	VectorNormalize(dir);

	switch (ent->s.weapon)
	{
		case WP_GRENADE_LAUNCHER:
			fire_grenade(ent, ent->s.origin, dir);
			break;
		case WP_ROCKET_LAUNCHER:
			fire_rocket(ent, ent->s.origin, dir);
			break;
		case WP_PLASMAGUN:
			fire_plasma(ent, ent->s.origin, dir);
			break;
	}

	G_AddEvent(ent, EV_FIRE_WEAPON, 0);
}


static void InitShooter_Finish(gentity_t * ent)
{
	ent->enemy = G_PickTarget(ent->target);
	ent->think = 0;
	ent->nextthink = 0;
}

void InitShooter(gentity_t * ent, int weapon)
{
	ent->use = Use_Shooter;
	ent->s.weapon = weapon;

	RegisterItem(BG_FindItemForWeapon(weapon));

	G_SetMovedir(ent->s.angles, ent->movedir);

	if(!ent->random)
	{
		ent->random = 1.0;
	}
	ent->random = sin(M_PI * ent->random / 180);
	// target might be a moving object, so we can't set movedir for it
	if(ent->target)
	{
		ent->think = InitShooter_Finish;
		ent->nextthink = level.time + 500;
	}
	trap_LinkEntity(ent);
}

/*QUAKED shooter_rocket (1 0 0) (-16 -16 -16) (16 16 16)
Fires at either the target or the current direction.
"random" the number of degrees of deviance from the taget. (1.0 default)
*/
void SP_shooter_rocket(gentity_t * ent)
{
	InitShooter(ent, WP_ROCKET_LAUNCHER);
}

/*QUAKED shooter_plasma (1 0 0) (-16 -16 -16) (16 16 16)
Fires at either the target or the current direction.
"random" is the number of degrees of deviance from the taget. (1.0 default)
*/
void SP_shooter_plasma(gentity_t * ent)
{
	InitShooter(ent, WP_PLASMAGUN);
}

/*QUAKED shooter_grenade (1 0 0) (-16 -16 -16) (16 16 16)
Fires at either the target or the current direction.
"random" is the number of degrees of deviance from the taget. (1.0 default)
*/
void SP_shooter_grenade(gentity_t * ent)
{
	InitShooter(ent, WP_GRENADE_LAUNCHER);
}


#ifdef MISSIONPACK
static void PortalDie(gentity_t * self, gentity_t * inflictor, gentity_t * attacker, int damage, int mod)
{
	G_FreeEntity(self);
	//FIXME do something more interesting
}


void DropPortalDestination(gentity_t * player)
{
	gentity_t      *ent;
	vec3_t          snapped;

	// create the portal destination
	ent = G_Spawn();
	ent->s.modelindex = G_ModelIndex("models/powerups/teleporter/tele_exit.md3");

	VectorCopy(player->s.pos.trBase, snapped);
	SnapVector(snapped);
	G_SetOrigin(ent, snapped);
	VectorCopy(player->r.mins, ent->r.mins);
	VectorCopy(player->r.maxs, ent->r.maxs);

	ent->classname = "hi_portal destination";
	ent->s.pos.trType = TR_STATIONARY;

	ent->r.contents = CONTENTS_CORPSE;
	ent->takedamage = qtrue;
	ent->health = 200;
	ent->die = PortalDie;

	VectorCopy(player->s.apos.trBase, ent->s.angles);

	ent->think = G_FreeEntity;
	ent->nextthink = level.time + 2 * 60 * 1000;

	trap_LinkEntity(ent);

	player->client->portalID = ++level.portalSequence;
	ent->count = player->client->portalID;

	// give the item back so they can drop the source now
	player->client->ps.stats[STAT_HOLDABLE_ITEM] = BG_FindItem("Portal") - bg_itemlist;
}


static void PortalTouch(gentity_t * self, gentity_t * other, trace_t * trace)
{
	gentity_t      *destination;

	// see if we will even let other try to use it
	if(other->health <= 0)
	{
		return;
	}
	if(!other->client)
	{
		return;
	}
//  if( other->client->ps.persistant[PERS_TEAM] != self->spawnflags ) {
//      return;
//  }

	if(other->client->ps.powerups[PW_NEUTRALFLAG])
	{							// only happens in One Flag CTF
		Drop_Item(other, BG_FindItemForPowerup(PW_NEUTRALFLAG), 0);
		other->client->ps.powerups[PW_NEUTRALFLAG] = 0;
	}
	else if(other->client->ps.powerups[PW_REDFLAG])
	{							// only happens in standard CTF
		Drop_Item(other, BG_FindItemForPowerup(PW_REDFLAG), 0);
		other->client->ps.powerups[PW_REDFLAG] = 0;
	}
	else if(other->client->ps.powerups[PW_BLUEFLAG])
	{							// only happens in standard CTF
		Drop_Item(other, BG_FindItemForPowerup(PW_BLUEFLAG), 0);
		other->client->ps.powerups[PW_BLUEFLAG] = 0;
	}

	// find the destination
	destination = NULL;
	while((destination = G_Find(destination, FOFS(classname), "hi_portal destination")) != NULL)
	{
		if(destination->count == self->count)
		{
			break;
		}
	}

	// if there is not one, die!
	if(!destination)
	{
		if(self->pos1[0] || self->pos1[1] || self->pos1[2])
		{
			TeleportPlayer(other, self->pos1, self->s.angles);
		}
		G_Damage(other, other, other, NULL, NULL, 100000, DAMAGE_NO_PROTECTION, MOD_TELEFRAG);
		return;
	}

	TeleportPlayer(other, destination->s.pos.trBase, destination->s.angles);
}


static void PortalEnable(gentity_t * self)
{
	self->touch = PortalTouch;
	self->think = G_FreeEntity;
	self->nextthink = level.time + 2 * 60 * 1000;
}


void DropPortalSource(gentity_t * player)
{
	gentity_t      *ent;
	gentity_t      *destination;
	vec3_t          snapped;

	// create the portal source
	ent = G_Spawn();
	ent->s.modelindex = G_ModelIndex("models/powerups/teleporter/tele_enter.md3");

	VectorCopy(player->s.pos.trBase, snapped);
	SnapVector(snapped);
	G_SetOrigin(ent, snapped);
	VectorCopy(player->r.mins, ent->r.mins);
	VectorCopy(player->r.maxs, ent->r.maxs);

	ent->classname = "hi_portal source";
	ent->s.pos.trType = TR_STATIONARY;

	ent->r.contents = CONTENTS_CORPSE | CONTENTS_TRIGGER;
	ent->takedamage = qtrue;
	ent->health = 200;
	ent->die = PortalDie;

	trap_LinkEntity(ent);

	ent->count = player->client->portalID;
	player->client->portalID = 0;

//  ent->spawnflags = player->client->ps.persistant[PERS_TEAM];

	ent->nextthink = level.time + 1000;
	ent->think = PortalEnable;

	// find the destination
	destination = NULL;
	while((destination = G_Find(destination, FOFS(classname), "hi_portal destination")) != NULL)
	{
		if(destination->count == ent->count)
		{
			VectorCopy(destination->s.pos.trBase, ent->pos1);
			break;
		}
	}

}
#endif
