/*
 * Copyright (C) 1997-2001 Id Software, Inc.
 *
 * 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.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 59
 * Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 */
/* g_misc.c */

#include "g_local.h"


/*
 * QUAKED func_group (0 0 0) ? Used to group brushes together just for editor
 * convenience.
 */

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

void
Use_Areaportal(edict_t * ent, edict_t * other, edict_t * activator)
{
	ent->count ^= 1;	/* toggle state */
	/* gi.dprintf ("portalstate: %i = %i\n", ent->style, ent->count); */
	gi.SetAreaPortalState(ent->style, ent->count);
}

/*
 * QUAKED func_areaportal (0 0 0) ?
 *
 * This is a non-visible object that divides the world into areas that are
 * seperated when this portal is not activated. Usually enclosed in the
 * middle of a door.
 */
void
SP_func_areaportal(edict_t * ent)
{
	ent->use = Use_Areaportal;
	ent->count = 0;		/* always start closed; */
}

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


/*
 * ================= Misc functions =================
 */
void
VelocityForDamage(int damage, vec3_t v)
{
	v[0] = 100.0 * crandom();
	v[1] = 100.0 * crandom();
	v[2] = 200.0 + 100.0 * random();

	if (damage < 50)
		VectorScale(v, 0.7, v);
	else
		VectorScale(v, 1.2, v);
}

void
ClipGibVelocity(edict_t * ent)
{
	if (ent->velocity[0] < -300)
		ent->velocity[0] = -300;
	else if (ent->velocity[0] > 300)
		ent->velocity[0] = 300;
	if (ent->velocity[1] < -300)
		ent->velocity[1] = -300;
	else if (ent->velocity[1] > 300)
		ent->velocity[1] = 300;
	if (ent->velocity[2] < 200)
		ent->velocity[2] = 200;	/* always some upwards */
	else if (ent->velocity[2] > 500)
		ent->velocity[2] = 500;
}


/*
 * ================= gibs =================
 */
void
gib_think(edict_t * self)
{
	self->s.frame++;
	self->nextthink = level.time + FRAMETIME;

	if (self->s.frame == 10) {
		self->think = G_FreeEdict;
		self->nextthink = level.time + 8 + random() * 10;
	}
}

void
gib_touch(edict_t * self, edict_t * other, cplane_t * plane, csurface_t * surf)
{
	vec3_t		normal_angles, right;

	if (!self->groundentity)
		return;

	self->touch = NULL;

	if (plane) {
		gi.sound(self, CHAN_VOICE, gi.soundindex("misc/fhit3.wav"), 1, ATTN_NORM, 0);

		vectoangles(plane->normal, normal_angles);
		AngleVectors(normal_angles, NULL, right, NULL);
		vectoangles(right, self->s.angles);

		if (self->s.modelindex == sm_meat_index) {
			self->s.frame++;
			self->think = gib_think;
			self->nextthink = level.time + FRAMETIME;
		}
	}
}

void
gib_die(edict_t * self, edict_t * inflictor, edict_t * attacker, int damage, vec3_t point)
{
	G_FreeEdict(self);
}

void
ThrowGib(edict_t * self, char *gibname, int damage, int type)
{
	edict_t        *gib;
	vec3_t		vd;
	vec3_t		origin;
	vec3_t		size;
	float		vscale;

	gib = G_Spawn();

	VectorScale(self->size, 0.5, size);
	VectorAdd(self->absmin, size, origin);
	gib->s.origin[0] = origin[0] + crandom() * size[0];
	gib->s.origin[1] = origin[1] + crandom() * size[1];
	gib->s.origin[2] = origin[2] + crandom() * size[2];

	gi.setmodel(gib, gibname);
	gib->solid = SOLID_NOT;
	gib->s.effects |= EF_GIB;
	gib->flags |= FL_NO_KNOCKBACK;
	gib->takedamage = DAMAGE_YES;
	gib->die = gib_die;

	if (type == GIB_ORGANIC) {
		gib->movetype = MOVETYPE_TOSS;
		gib->touch = gib_touch;
		vscale = 0.5;
	} else {
		gib->movetype = MOVETYPE_BOUNCE;
		vscale = 1.0;
	}

	VelocityForDamage(damage, vd);
	VectorMA(self->velocity, vscale, vd, gib->velocity);
	ClipGibVelocity(gib);
	gib->avelocity[0] = random() * 600;
	gib->avelocity[1] = random() * 600;
	gib->avelocity[2] = random() * 600;

	gib->think = G_FreeEdict;
	gib->nextthink = level.time + 10 + random() * 10;

	gi.linkentity(gib);
}

void
ThrowHead(edict_t * self, char *gibname, int damage, int type)
{
	vec3_t		vd;
	float		vscale;

	self->s.skinnum = 0;
	self->s.frame = 0;
	VectorClear(self->mins);
	VectorClear(self->maxs);

	self->s.modelindex2 = 0;
	gi.setmodel(self, gibname);
	self->solid = SOLID_NOT;
	self->s.effects |= EF_GIB;
	self->s.effects &= ~EF_FLIES;
	self->s.sound = 0;
	self->flags |= FL_NO_KNOCKBACK;
	self->svflags &= ~SVF_MONSTER;
	self->takedamage = DAMAGE_YES;
	self->die = gib_die;

	if (type == GIB_ORGANIC) {
		self->movetype = MOVETYPE_TOSS;
		self->touch = gib_touch;
		vscale = 0.5;
	} else {
		self->movetype = MOVETYPE_BOUNCE;
		vscale = 1.0;
	}

	VelocityForDamage(damage, vd);
	VectorMA(self->velocity, vscale, vd, self->velocity);
	ClipGibVelocity(self);

	self->avelocity[YAW] = crandom() * 600;

	self->think = G_FreeEdict;
	self->nextthink = level.time + 10 + random() * 10;

	gi.linkentity(self);
}


void
ThrowClientHead(edict_t * self, int damage)
{
	vec3_t		vd;
	char           *gibname;

	if (rand() & 1) {
		gibname = "models/objects/gibs/head2/tris.md2";
		self->s.skinnum = 1;	/* second skin is player */
	} else {
		gibname = "models/objects/gibs/skull/tris.md2";
		self->s.skinnum = 0;
	}

	self->s.origin[2] += 32;
	self->s.frame = 0;
	gi.setmodel(self, gibname);
	VectorSet(self->mins, -16, -16, 0);
	VectorSet(self->maxs, 16, 16, 16);

	self->takedamage = DAMAGE_NO;
	self->solid = SOLID_NOT;
	self->s.effects = EF_GIB;
	self->s.sound = 0;
	self->flags |= FL_NO_KNOCKBACK;

	self->movetype = MOVETYPE_BOUNCE;
	VelocityForDamage(damage, vd);
	VectorAdd(self->velocity, vd, self->velocity);

	if (self->client) {	/* bodies in the queue don't have a client
				 * anymore */
		self->client->anim_priority = ANIM_DEATH;
		self->client->anim_end = self->s.frame;
	} else {
		self->think = NULL;
		self->nextthink = 0;
	}

#ifdef GAME_MOD
	Blinky_OnClientTerminate(self);
#endif
	gi.linkentity(self);
}


/*
 * ================= debris =================
 */
void
debris_die(edict_t * self, edict_t * inflictor, edict_t * attacker, int damage, vec3_t point)
{
	G_FreeEdict(self);
}

void
ThrowDebris(edict_t * self, char *modelname, float speed, vec3_t origin)
{
	edict_t        *chunk;
	vec3_t		v;

	chunk = G_Spawn();
	VectorCopy(origin, chunk->s.origin);
	gi.setmodel(chunk, modelname);
	v[0] = 100 * crandom();
	v[1] = 100 * crandom();
	v[2] = 100 + 100 * crandom();
	VectorMA(self->velocity, speed, v, chunk->velocity);
	chunk->movetype = MOVETYPE_BOUNCE;
	chunk->solid = SOLID_NOT;
	chunk->avelocity[0] = random() * 600;
	chunk->avelocity[1] = random() * 600;
	chunk->avelocity[2] = random() * 600;
	chunk->think = G_FreeEdict;
	chunk->nextthink = level.time + 5 + random() * 5;
	chunk->s.frame = 0;
	chunk->flags = 0;
	chunk->classname = "debris";
	chunk->takedamage = DAMAGE_YES;
	chunk->die = debris_die;
	gi.linkentity(chunk);
}


void
BecomeExplosion1(edict_t * self)
{
	gi.WriteByte(svc_temp_entity);
	gi.WriteByte(TE_EXPLOSION1);
	gi.WritePosition(self->s.origin);
	gi.multicast(self->s.origin, MULTICAST_PVS);

	G_FreeEdict(self);
}


void
BecomeExplosion2(edict_t * self)
{
	gi.WriteByte(svc_temp_entity);
	gi.WriteByte(TE_EXPLOSION2);
	gi.WritePosition(self->s.origin);
	gi.multicast(self->s.origin, MULTICAST_PVS);

	G_FreeEdict(self);
}


/*
 * QUAKED path_corner (.5 .3 0) (-8 -8 -8) (8 8 8) TELEPORT Target: next path
 * corner Pathtarget: gets used when an entity that has this path_corner
 * targeted touches it
 */

void
path_corner_touch(edict_t * self, edict_t * other, cplane_t * plane, csurface_t * surf)
{
	vec3_t		v;
	edict_t        *next;

	if (other->movetarget != self)
		return;

	if (other->enemy)
		return;

	if (self->pathtarget) {
		char           *savetarget;

		savetarget = self->target;
		self->target = self->pathtarget;
		G_UseTargets(self, other);
		self->target = savetarget;
	}
	if (self->target)
		next = G_PickTarget(self->target);
	else
		next = NULL;

	if ((next) && (next->spawnflags & 1)) {
		VectorCopy(next->s.origin, v);
		v[2] += next->mins[2];
		v[2] -= other->mins[2];
		VectorCopy(v, other->s.origin);
		next = G_PickTarget(next->target);
		other->s.event = EV_OTHER_TELEPORT;
	}
	other->goalentity = other->movetarget = next;

	if (self->wait) {
		other->monsterinfo.pausetime = level.time + self->wait;
		other->monsterinfo.stand(other);
		return;
	}
	if (!other->movetarget) {
		other->monsterinfo.pausetime = level.time + 100000000;
		other->monsterinfo.stand(other);
	} else {
		VectorSubtract(other->goalentity->s.origin, other->s.origin, v);
		other->ideal_yaw = vectoyaw(v);
	}
}

void
SP_path_corner(edict_t * self)
{
	if (!self->targetname) {
		gi.dprintf("path_corner with no targetname at %s\n", vtos(self->s.origin));
		G_FreeEdict(self);
		return;
	}
	self->solid = SOLID_TRIGGER;
	self->touch = path_corner_touch;
	VectorSet(self->mins, -8, -8, -8);
	VectorSet(self->maxs, 8, 8, 8);
	self->svflags |= SVF_NOCLIENT;
	gi.linkentity(self);
}


/*
 * QUAKED point_combat (0.5 0.3 0) (-8 -8 -8) (8 8 8) Hold Makes this the
 * target of a monster and it will head here when first activated before
 * going after the activator.  If hold is selected, it will stay here.
 */
void
point_combat_touch(edict_t * self, edict_t * other, cplane_t * plane, csurface_t * surf)
{
	edict_t        *activator;

	if (other->movetarget != self)
		return;

	if (self->target) {
		other->target = self->target;
		other->goalentity = other->movetarget = G_PickTarget(other->target);
		if (!other->goalentity) {
			gi.dprintf("%s at %s target %s does not exist\n", self->classname, vtos(self->s.origin), self->target);
			other->movetarget = self;
		}
		self->target = NULL;
	} else if ((self->spawnflags & 1) && !(other->flags & (FL_SWIM | FL_FLY))) {
		other->monsterinfo.pausetime = level.time + 100000000;
		other->monsterinfo.aiflags |= AI_STAND_GROUND;
		other->monsterinfo.stand(other);
	}
	if (other->movetarget == self) {
		other->target = NULL;
		other->movetarget = NULL;
		other->goalentity = other->enemy;
		other->monsterinfo.aiflags &= ~AI_COMBAT_POINT;
	}
	if (self->pathtarget) {
		char           *savetarget;

		savetarget = self->target;
		self->target = self->pathtarget;
		if (other->enemy && other->enemy->client)
			activator = other->enemy;
		else if (other->oldenemy && other->oldenemy->client)
			activator = other->oldenemy;
		else if (other->activator && other->activator->client)
			activator = other->activator;
		else
			activator = other;
		G_UseTargets(self, activator);
		self->target = savetarget;
	}
}

void
SP_point_combat(edict_t * self)
{
	if (deathmatch->value) {
		G_FreeEdict(self);
		return;
	}
	self->solid = SOLID_TRIGGER;
	self->touch = point_combat_touch;
	VectorSet(self->mins, -8, -8, -16);
	VectorSet(self->maxs, 8, 8, 16);
	self->svflags = SVF_NOCLIENT;
	gi.linkentity(self);
}


/*
 * QUAKED viewthing (0 .5 .8) (-8 -8 -8) (8 8 8) Just for the debugging
 * level.  Don't use
 */
void
TH_viewthing(edict_t * ent)
{
	ent->s.frame = (ent->s.frame + 1) % 7;
	ent->nextthink = level.time + FRAMETIME;
}

void
SP_viewthing(edict_t * ent)
{
	gi.dprintf("viewthing spawned\n");

	ent->movetype = MOVETYPE_NONE;
	ent->solid = SOLID_BBOX;
	ent->s.renderfx = RF_FRAMELERP;
	VectorSet(ent->mins, -16, -16, -24);
	VectorSet(ent->maxs, 16, 16, 32);
	ent->s.modelindex = gi.modelindex("models/objects/banner/tris.md2");
	gi.linkentity(ent);
	ent->nextthink = level.time + 0.5;
	ent->think = TH_viewthing;
	return;
}


/*
 * QUAKED info_null (0 0.5 0) (-4 -4 -4) (4 4 4) Used as a positional target
 * for spotlights, etc.
 */
void
SP_info_null(edict_t * self)
{
	G_FreeEdict(self);
}


/*
 * QUAKED info_notnull (0 0.5 0) (-4 -4 -4) (4 4 4) Used as a positional
 * target for lightning.
 */
void
SP_info_notnull(edict_t * self)
{
	VectorCopy(self->s.origin, self->absmin);
	VectorCopy(self->s.origin, self->absmax);
}


/*
 * QUAKED light (0 1 0) (-8 -8 -8) (8 8 8) START_OFF Non-displayed light.
 * Default light value is 300. Default style is 0. If targeted, will toggle
 * between on and off. Default _cone value is 10 (used to set size of light
 * for spotlights)
 */

#define START_OFF	1

static void
light_use(edict_t * self, edict_t * other, edict_t * activator)
{
	if (self->spawnflags & START_OFF) {
		gi.configstring(CS_LIGHTS + self->style, "m");
		self->spawnflags &= ~START_OFF;
	} else {
		gi.configstring(CS_LIGHTS + self->style, "a");
		self->spawnflags |= START_OFF;
	}
}

void
SP_light(edict_t * self)
{

	/*
	 * no targeted lights in deathmatch, because they cause global
	 * messages
	 */
	if (!self->targetname || deathmatch->value) {
		G_FreeEdict(self);
		return;
	}
	if (self->style >= 32) {
		self->use = light_use;
		if (self->spawnflags & START_OFF)
			gi.configstring(CS_LIGHTS + self->style, "a");
		else
			gi.configstring(CS_LIGHTS + self->style, "m");
	}
}

/*
 * QUAKED func_wall (0 .5 .8) ? TRIGGER_SPAWN TOGGLE START_ON ANIMATED
 * ANIMATED_FAST This is just a solid wall if not inhibited
 *
 * TRIGGER_SPAWN	the wall will not be present until triggered it will then
 * blink in to existance; it will kill anything that was in it's way
 *
 * TOGGLE			only valid for TRIGGER_SPAWN walls this allows the
 * wall to be turned on and off
 *
 * START_ON		only valid for TRIGGER_SPAWN walls the wall will
 * initially be present
 */

void
func_wall_use(edict_t * self, edict_t * other, edict_t * activator)
{
	if (self->solid == SOLID_NOT) {
		self->solid = SOLID_BSP;
		self->svflags &= ~SVF_NOCLIENT;
		KillBox(self);
	} else {
		self->solid = SOLID_NOT;
		self->svflags |= SVF_NOCLIENT;
	}
	gi.linkentity(self);

	if (!(self->spawnflags & 2))
		self->use = NULL;
}

void
SP_func_wall(edict_t * self)
{
	self->movetype = MOVETYPE_PUSH;
	gi.setmodel(self, self->model);

	if (self->spawnflags & 8)
		self->s.effects |= EF_ANIM_ALL;
	if (self->spawnflags & 16)
		self->s.effects |= EF_ANIM_ALLFAST;

	/* just a wall */
	if ((self->spawnflags & 7) == 0) {
		self->solid = SOLID_BSP;
		gi.linkentity(self);
		return;
	}
	/* it must be TRIGGER_SPAWN */
	if (!(self->spawnflags & 1)) {
		/* gi.dprintf("func_wall missing TRIGGER_SPAWN\n"); */
		self->spawnflags |= 1;
	}
	/* yell if the spawnflags are odd */
	if (self->spawnflags & 4) {
		if (!(self->spawnflags & 2)) {
			gi.dprintf("func_wall START_ON without TOGGLE\n");
			self->spawnflags |= 2;
		}
	}
	self->use = func_wall_use;
	if (self->spawnflags & 4) {
		self->solid = SOLID_BSP;
	} else {
		self->solid = SOLID_NOT;
		self->svflags |= SVF_NOCLIENT;
	}
	gi.linkentity(self);
}


/*
 * QUAKED func_object (0 .5 .8) ? TRIGGER_SPAWN ANIMATED ANIMATED_FAST This
 * is solid bmodel that will fall if it's support it removed.
 */

void
func_object_touch(edict_t * self, edict_t * other, cplane_t * plane, csurface_t * surf)
{
	/* only squash thing we fall on top of */
	if (!plane)
		return;
	if (plane->normal[2] < 1.0)
		return;
	if (other->takedamage == DAMAGE_NO)
		return;
	T_Damage(other, self, self, vec3_origin, self->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
}

void
func_object_release(edict_t * self)
{
	self->movetype = MOVETYPE_TOSS;
	self->touch = func_object_touch;
}

void
func_object_use(edict_t * self, edict_t * other, edict_t * activator)
{
	self->solid = SOLID_BSP;
	self->svflags &= ~SVF_NOCLIENT;
	self->use = NULL;
	KillBox(self);
	func_object_release(self);
}

void
SP_func_object(edict_t * self)
{
	gi.setmodel(self, self->model);

	self->mins[0] += 1;
	self->mins[1] += 1;
	self->mins[2] += 1;
	self->maxs[0] -= 1;
	self->maxs[1] -= 1;
	self->maxs[2] -= 1;

	if (!self->dmg)
		self->dmg = 100;

	if (self->spawnflags == 0) {
		self->solid = SOLID_BSP;
		self->movetype = MOVETYPE_PUSH;
		self->think = func_object_release;
		self->nextthink = level.time + 2 * FRAMETIME;
	} else {
		self->solid = SOLID_NOT;
		self->movetype = MOVETYPE_PUSH;
		self->use = func_object_use;
		self->svflags |= SVF_NOCLIENT;
	}

	if (self->spawnflags & 2)
		self->s.effects |= EF_ANIM_ALL;
	if (self->spawnflags & 4)
		self->s.effects |= EF_ANIM_ALLFAST;

	self->clipmask = MASK_MONSTERSOLID;

	gi.linkentity(self);
}


/*
 * QUAKED func_explosive (0 .5 .8) ? Trigger_Spawn ANIMATED ANIMATED_FAST Any
 * brush that you want to explode or break apart.  If you want an ex0plosion,
 * set dmg and it will do a radius explosion of that amount at the center of
 * the bursh.
 *
 * If targeted it will not be shootable.
 *
 * health defaults to 100.
 *
 * mass defaults to 75.  This determines how much debris is emitted when it
 * explodes.  You get one large chunk per 100 of mass (up to 8) and one small
 * chunk per 25 of mass (up to 16).  So 800 gives the most.
 */
void
func_explosive_explode(edict_t * self, edict_t * inflictor, edict_t * attacker, int damage, vec3_t point)
{
	vec3_t		origin;
	vec3_t		chunkorigin;
	vec3_t		size;
	int		count;
	int		mass;

	/* bmodel origins are (0 0 0), we need to adjust that here */
	VectorScale(self->size, 0.5, size);
	VectorAdd(self->absmin, size, origin);
	VectorCopy(origin, self->s.origin);

	self->takedamage = DAMAGE_NO;

	if (self->dmg)
		T_RadiusDamage(self, attacker, self->dmg, NULL, self->dmg + 40, MOD_EXPLOSIVE);

	VectorSubtract(self->s.origin, inflictor->s.origin, self->velocity);
	VectorNormalize(self->velocity);
	VectorScale(self->velocity, 150, self->velocity);

	/* start chunks towards the center */
	VectorScale(size, 0.5, size);

	mass = self->mass;
	if (!mass)
		mass = 75;

	/* big chunks */
	if (mass >= 100) {
		count = mass / 100;
		if (count > 8)
			count = 8;
		while (count--) {
			chunkorigin[0] = origin[0] + crandom() * size[0];
			chunkorigin[1] = origin[1] + crandom() * size[1];
			chunkorigin[2] = origin[2] + crandom() * size[2];
			ThrowDebris(self, "models/objects/debris1/tris.md2", 1, chunkorigin);
		}
	}
	/* small chunks */
	count = mass / 25;
	if (count > 16)
		count = 16;
	while (count--) {
		chunkorigin[0] = origin[0] + crandom() * size[0];
		chunkorigin[1] = origin[1] + crandom() * size[1];
		chunkorigin[2] = origin[2] + crandom() * size[2];
		ThrowDebris(self, "models/objects/debris2/tris.md2", 2, chunkorigin);
	}

	G_UseTargets(self, attacker);

	if (self->dmg)
		BecomeExplosion1(self);
	else
		G_FreeEdict(self);
}

void
func_explosive_use(edict_t * self, edict_t * other, edict_t * activator)
{
	func_explosive_explode(self, self, other, self->health, vec3_origin);
}

void
func_explosive_spawn(edict_t * self, edict_t * other, edict_t * activator)
{
	self->solid = SOLID_BSP;
	self->svflags &= ~SVF_NOCLIENT;
	self->use = NULL;
	KillBox(self);
	gi.linkentity(self);
}

void
SP_func_explosive(edict_t * self)
{
	if (deathmatch->value) {/* auto-remove for deathmatch */
		G_FreeEdict(self);
		return;
	}
	self->movetype = MOVETYPE_PUSH;

	gi.modelindex("models/objects/debris1/tris.md2");
	gi.modelindex("models/objects/debris2/tris.md2");

	gi.setmodel(self, self->model);

	if (self->spawnflags & 1) {
		self->svflags |= SVF_NOCLIENT;
		self->solid = SOLID_NOT;
		self->use = func_explosive_spawn;
	} else {
		self->solid = SOLID_BSP;
		if (self->targetname)
			self->use = func_explosive_use;
	}

	if (self->spawnflags & 2)
		self->s.effects |= EF_ANIM_ALL;
	if (self->spawnflags & 4)
		self->s.effects |= EF_ANIM_ALLFAST;

	if (self->use != func_explosive_use) {
		if (!self->health)
			self->health = 100;
		self->die = func_explosive_explode;
		self->takedamage = DAMAGE_YES;
	}
	gi.linkentity(self);
}


/*
 * QUAKED misc_explobox (0 .5 .8) (-16 -16 0) (16 16 40) Large exploding box.
 * You can override its mass (100), health (80), and dmg (150).
 */

void
barrel_touch(edict_t * self, edict_t * other, cplane_t * plane, csurface_t * surf)
{
	float		ratio;
	vec3_t		v;

	if ((!other->groundentity) || (other->groundentity == self))
		return;

	ratio = (float)other->mass / (float)self->mass;
	VectorSubtract(self->s.origin, other->s.origin, v);
	M_walkmove(self, vectoyaw(v), 20 * ratio * FRAMETIME);
}

void
barrel_explode(edict_t * self)
{
	vec3_t		org;
	float		spd;
	vec3_t		save;

	T_RadiusDamage(self, self->activator, self->dmg, NULL, self->dmg + 40, MOD_BARREL);

	VectorCopy(self->s.origin, save);
	VectorMA(self->absmin, 0.5, self->size, self->s.origin);

	/* a few big chunks */
	spd = 1.5 * (float)self->dmg / 200.0;
	org[0] = self->s.origin[0] + crandom() * self->size[0];
	org[1] = self->s.origin[1] + crandom() * self->size[1];
	org[2] = self->s.origin[2] + crandom() * self->size[2];
	ThrowDebris(self, "models/objects/debris1/tris.md2", spd, org);
	org[0] = self->s.origin[0] + crandom() * self->size[0];
	org[1] = self->s.origin[1] + crandom() * self->size[1];
	org[2] = self->s.origin[2] + crandom() * self->size[2];
	ThrowDebris(self, "models/objects/debris1/tris.md2", spd, org);

	/* bottom corners */
	spd = 1.75 * (float)self->dmg / 200.0;
	VectorCopy(self->absmin, org);
	ThrowDebris(self, "models/objects/debris3/tris.md2", spd, org);
	VectorCopy(self->absmin, org);
	org[0] += self->size[0];
	ThrowDebris(self, "models/objects/debris3/tris.md2", spd, org);
	VectorCopy(self->absmin, org);
	org[1] += self->size[1];
	ThrowDebris(self, "models/objects/debris3/tris.md2", spd, org);
	VectorCopy(self->absmin, org);
	org[0] += self->size[0];
	org[1] += self->size[1];
	ThrowDebris(self, "models/objects/debris3/tris.md2", spd, org);

	/* a bunch of little chunks */
	spd = 2 * self->dmg / 200;
	org[0] = self->s.origin[0] + crandom() * self->size[0];
	org[1] = self->s.origin[1] + crandom() * self->size[1];
	org[2] = self->s.origin[2] + crandom() * self->size[2];
	ThrowDebris(self, "models/objects/debris2/tris.md2", spd, org);
	org[0] = self->s.origin[0] + crandom() * self->size[0];
	org[1] = self->s.origin[1] + crandom() * self->size[1];
	org[2] = self->s.origin[2] + crandom() * self->size[2];
	ThrowDebris(self, "models/objects/debris2/tris.md2", spd, org);
	org[0] = self->s.origin[0] + crandom() * self->size[0];
	org[1] = self->s.origin[1] + crandom() * self->size[1];
	org[2] = self->s.origin[2] + crandom() * self->size[2];
	ThrowDebris(self, "models/objects/debris2/tris.md2", spd, org);
	org[0] = self->s.origin[0] + crandom() * self->size[0];
	org[1] = self->s.origin[1] + crandom() * self->size[1];
	org[2] = self->s.origin[2] + crandom() * self->size[2];
	ThrowDebris(self, "models/objects/debris2/tris.md2", spd, org);
	org[0] = self->s.origin[0] + crandom() * self->size[0];
	org[1] = self->s.origin[1] + crandom() * self->size[1];
	org[2] = self->s.origin[2] + crandom() * self->size[2];
	ThrowDebris(self, "models/objects/debris2/tris.md2", spd, org);
	org[0] = self->s.origin[0] + crandom() * self->size[0];
	org[1] = self->s.origin[1] + crandom() * self->size[1];
	org[2] = self->s.origin[2] + crandom() * self->size[2];
	ThrowDebris(self, "models/objects/debris2/tris.md2", spd, org);
	org[0] = self->s.origin[0] + crandom() * self->size[0];
	org[1] = self->s.origin[1] + crandom() * self->size[1];
	org[2] = self->s.origin[2] + crandom() * self->size[2];
	ThrowDebris(self, "models/objects/debris2/tris.md2", spd, org);
	org[0] = self->s.origin[0] + crandom() * self->size[0];
	org[1] = self->s.origin[1] + crandom() * self->size[1];
	org[2] = self->s.origin[2] + crandom() * self->size[2];
	ThrowDebris(self, "models/objects/debris2/tris.md2", spd, org);

	VectorCopy(save, self->s.origin);
	if (self->groundentity)
		BecomeExplosion2(self);
	else
		BecomeExplosion1(self);
}

void
barrel_delay(edict_t * self, edict_t * inflictor, edict_t * attacker, int damage, vec3_t point)
{
	self->takedamage = DAMAGE_NO;
	self->nextthink = level.time + 2 * FRAMETIME;
	self->think = barrel_explode;
	self->activator = attacker;
}

void
SP_misc_explobox(edict_t * self)
{
	if (deathmatch->value) {/* auto-remove for deathmatch */
		G_FreeEdict(self);
		return;
	}
	gi.modelindex("models/objects/debris1/tris.md2");
	gi.modelindex("models/objects/debris2/tris.md2");
	gi.modelindex("models/objects/debris3/tris.md2");

	self->solid = SOLID_BBOX;
	self->movetype = MOVETYPE_STEP;

	self->model = "models/objects/barrels/tris.md2";
	self->s.modelindex = gi.modelindex(self->model);
	VectorSet(self->mins, -16, -16, 0);
	VectorSet(self->maxs, 16, 16, 40);

	if (!self->mass)
		self->mass = 400;
	if (!self->health)
		self->health = 10;
	if (!self->dmg)
		self->dmg = 150;

	self->die = barrel_delay;
	self->takedamage = DAMAGE_YES;
	self->monsterinfo.aiflags = AI_NOSTEP;

	self->touch = barrel_touch;

	self->think = M_droptofloor;
	self->nextthink = level.time + 2 * FRAMETIME;

	gi.linkentity(self);
}


//
/* miscellaneous specialty items */
//

/*
 * QUAKED misc_blackhole (1 .5 0) (-8 -8 -8) (8 8 8)
 */

void
misc_blackhole_use(edict_t * ent, edict_t * other, edict_t * activator)
{

	/*
	 * gi.WriteByte (svc_temp_entity); gi.WriteByte (TE_BOSSTPORT);
	 * gi.WritePosition (ent->s.origin); gi.multicast (ent->s.origin,
	 * MULTICAST_PVS);
	 */
	G_FreeEdict(ent);
}

void
misc_blackhole_think(edict_t * self)
{
	if (++self->s.frame < 19)
		self->nextthink = level.time + FRAMETIME;
	else {
		self->s.frame = 0;
		self->nextthink = level.time + FRAMETIME;
	}
}

void
SP_misc_blackhole(edict_t * ent)
{
	ent->movetype = MOVETYPE_NONE;
	ent->solid = SOLID_NOT;
	VectorSet(ent->mins, -64, -64, 0);
	VectorSet(ent->maxs, 64, 64, 8);
	ent->s.modelindex = gi.modelindex("models/objects/black/tris.md2");
	ent->s.renderfx = RF_TRANSLUCENT;
	ent->use = misc_blackhole_use;
	ent->think = misc_blackhole_think;
	ent->nextthink = level.time + 2 * FRAMETIME;
	gi.linkentity(ent);
}

/*
 * QUAKED misc_eastertank (1 .5 0) (-32 -32 -16) (32 32 32)
 */

void
misc_eastertank_think(edict_t * self)
{
	if (++self->s.frame < 293)
		self->nextthink = level.time + FRAMETIME;
	else {
		self->s.frame = 254;
		self->nextthink = level.time + FRAMETIME;
	}
}

void
SP_misc_eastertank(edict_t * ent)
{
	ent->movetype = MOVETYPE_NONE;
	ent->solid = SOLID_BBOX;
	VectorSet(ent->mins, -32, -32, -16);
	VectorSet(ent->maxs, 32, 32, 32);
	ent->s.modelindex = gi.modelindex("models/monsters/tank/tris.md2");
	ent->s.frame = 254;
	ent->think = misc_eastertank_think;
	ent->nextthink = level.time + 2 * FRAMETIME;
	gi.linkentity(ent);
}

/*
 * QUAKED misc_easterchick (1 .5 0) (-32 -32 0) (32 32 32)
 */


void
misc_easterchick_think(edict_t * self)
{
	if (++self->s.frame < 247)
		self->nextthink = level.time + FRAMETIME;
	else {
		self->s.frame = 208;
		self->nextthink = level.time + FRAMETIME;
	}
}

void
SP_misc_easterchick(edict_t * ent)
{
	ent->movetype = MOVETYPE_NONE;
	ent->solid = SOLID_BBOX;
	VectorSet(ent->mins, -32, -32, 0);
	VectorSet(ent->maxs, 32, 32, 32);
	ent->s.modelindex = gi.modelindex("models/monsters/bitch/tris.md2");
	ent->s.frame = 208;
	ent->think = misc_easterchick_think;
	ent->nextthink = level.time + 2 * FRAMETIME;
	gi.linkentity(ent);
}

/*
 * QUAKED misc_easterchick2 (1 .5 0) (-32 -32 0) (32 32 32)
 */


void
misc_easterchick2_think(edict_t * self)
{
	if (++self->s.frame < 287)
		self->nextthink = level.time + FRAMETIME;
	else {
		self->s.frame = 248;
		self->nextthink = level.time + FRAMETIME;
	}
}

void
SP_misc_easterchick2(edict_t * ent)
{
	ent->movetype = MOVETYPE_NONE;
	ent->solid = SOLID_BBOX;
	VectorSet(ent->mins, -32, -32, 0);
	VectorSet(ent->maxs, 32, 32, 32);
	ent->s.modelindex = gi.modelindex("models/monsters/bitch/tris.md2");
	ent->s.frame = 248;
	ent->think = misc_easterchick2_think;
	ent->nextthink = level.time + 2 * FRAMETIME;
	gi.linkentity(ent);
}


/*
 * QUAKED monster_commander_body (1 .5 0) (-32 -32 0) (32 32 48) Not really a
 * monster, this is the Tank Commander's decapitated body. There should be a
 * item_commander_head that has this as it's target.
 */

void
commander_body_think(edict_t * self)
{
	if (++self->s.frame < 24)
		self->nextthink = level.time + FRAMETIME;
	else
		self->nextthink = 0;

	if (self->s.frame == 22)
		gi.sound(self, CHAN_BODY, gi.soundindex("tank/thud.wav"), 1, ATTN_NORM, 0);
}

void
commander_body_use(edict_t * self, edict_t * other, edict_t * activator)
{
	self->think = commander_body_think;
	self->nextthink = level.time + FRAMETIME;
	gi.sound(self, CHAN_BODY, gi.soundindex("tank/pain.wav"), 1, ATTN_NORM, 0);
}

void
commander_body_drop(edict_t * self)
{
	self->movetype = MOVETYPE_TOSS;
	self->s.origin[2] += 2;
}

void
SP_monster_commander_body(edict_t * self)
{
	self->movetype = MOVETYPE_NONE;
	self->solid = SOLID_BBOX;
	self->model = "models/monsters/commandr/tris.md2";
	self->s.modelindex = gi.modelindex(self->model);
	VectorSet(self->mins, -32, -32, 0);
	VectorSet(self->maxs, 32, 32, 48);
	self->use = commander_body_use;
	self->takedamage = DAMAGE_YES;
	self->flags = FL_GODMODE;
	self->s.renderfx |= RF_FRAMELERP;
	gi.linkentity(self);

	gi.soundindex("tank/thud.wav");
	gi.soundindex("tank/pain.wav");

	self->think = commander_body_drop;
	self->nextthink = level.time + 5 * FRAMETIME;
}


/*
 * QUAKED misc_banner (1 .5 0) (-4 -4 -4) (4 4 4) The origin is the bottom of
 * the banner. The banner is 128 tall.
 */
void
misc_banner_think(edict_t * ent)
{
	ent->s.frame = (ent->s.frame + 1) % 16;
	ent->nextthink = level.time + FRAMETIME;
}

void
SP_misc_banner(edict_t * ent)
{
	ent->movetype = MOVETYPE_NONE;
	ent->solid = SOLID_NOT;
	ent->s.modelindex = gi.modelindex("models/objects/banner/tris.md2");
	ent->s.renderfx |= RF_NOSHADOW;
	ent->s.frame = rand() % 16;
	gi.linkentity(ent);

	ent->think = misc_banner_think;
	ent->nextthink = level.time + FRAMETIME;
}

/*
 * QUAKED misc_deadsoldier (1 .5 0) (-16 -16 0) (16 16 16) ON_BACK ON_STOMACH
 * BACK_DECAP FETAL_POS SIT_DECAP IMPALED This is the dead player model.
 * Comes in 6 exciting different poses!
 */
void
misc_deadsoldier_die(edict_t * self, edict_t * inflictor, edict_t * attacker, int damage, vec3_t point)
{
	int		n;

	if (self->health > -80)
		return;

	gi.sound(self, CHAN_BODY, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
	for (n = 0; n < 4; n++)
		ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
	ThrowHead(self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
}

void
SP_misc_deadsoldier(edict_t * ent)
{
	if (deathmatch->value) {/* auto-remove for deathmatch */
		G_FreeEdict(ent);
		return;
	}
	ent->movetype = MOVETYPE_NONE;
	ent->solid = SOLID_BBOX;
	ent->s.modelindex = gi.modelindex("models/deadbods/dude/tris.md2");

	/* Defaults to frame 0 */
	if (ent->spawnflags & 2)
		ent->s.frame = 1;
	else if (ent->spawnflags & 4)
		ent->s.frame = 2;
	else if (ent->spawnflags & 8)
		ent->s.frame = 3;
	else if (ent->spawnflags & 16)
		ent->s.frame = 4;
	else if (ent->spawnflags & 32)
		ent->s.frame = 5;
	else
		ent->s.frame = 0;

	VectorSet(ent->mins, -16, -16, 0);
	VectorSet(ent->maxs, 16, 16, 16);
	ent->deadflag = DEAD_DEAD;
	ent->takedamage = DAMAGE_YES;
	ent->svflags |= SVF_MONSTER | SVF_DEADMONSTER;
	ent->die = misc_deadsoldier_die;
	ent->monsterinfo.aiflags |= AI_GOOD_GUY;

	gi.linkentity(ent);
}

/*
 * QUAKED misc_viper (1 .5 0) (-16 -16 0) (16 16 32) This is the Viper for
 * the flyby bombing. It is trigger_spawned, so you must have something use
 * it for it to show up. There must be a path for it to follow once it is
 * activated.
 *
 * "speed"		How fast the Viper should fly
 */

extern void	train_use(edict_t * self, edict_t * other, edict_t * activator);
extern void	func_train_find(edict_t * self);

void
misc_viper_use(edict_t * self, edict_t * other, edict_t * activator)
{
	self->svflags &= ~SVF_NOCLIENT;
	self->use = train_use;
	train_use(self, other, activator);
}

void
SP_misc_viper(edict_t * ent)
{
	if (!ent->target) {
		gi.dprintf("misc_viper without a target at %s\n", vtos(ent->absmin));
		G_FreeEdict(ent);
		return;
	}
	if (!ent->speed)
		ent->speed = 300;

	ent->movetype = MOVETYPE_PUSH;
	ent->solid = SOLID_NOT;
	ent->s.modelindex = gi.modelindex("models/ships/viper/tris.md2");
	VectorSet(ent->mins, -16, -16, 0);
	VectorSet(ent->maxs, 16, 16, 32);

	ent->think = func_train_find;
	ent->nextthink = level.time + FRAMETIME;
	ent->use = misc_viper_use;
	ent->svflags |= SVF_NOCLIENT;
	ent->moveinfo.accel = ent->moveinfo.decel = ent->moveinfo.speed = ent->speed;

	gi.linkentity(ent);
}


/*
 * QUAKED misc_bigviper (1 .5 0) (-176 -120 -24) (176 120 72) This is a large
 * stationary viper as seen in Paul's intro
 */
void
SP_misc_bigviper(edict_t * ent)
{
	ent->movetype = MOVETYPE_NONE;
	ent->solid = SOLID_BBOX;
	VectorSet(ent->mins, -176, -120, -24);
	VectorSet(ent->maxs, 176, 120, 72);
	ent->s.modelindex = gi.modelindex("models/ships/bigviper/tris.md2");
	gi.linkentity(ent);
}


/*
 * QUAKED misc_viper_bomb (1 0 0) (-8 -8 -8) (8 8 8) "dmg"	how much boom
 * should the bomb make?
 */
void
misc_viper_bomb_touch(edict_t * self, edict_t * other, cplane_t * plane, csurface_t * surf)
{
	G_UseTargets(self, self->activator);

	self->s.origin[2] = self->absmin[2] + 1;
	T_RadiusDamage(self, self, self->dmg, NULL, self->dmg + 40, MOD_BOMB);
	BecomeExplosion2(self);
}

void
misc_viper_bomb_prethink(edict_t * self)
{
	vec3_t		v;
	float		diff;

	self->groundentity = NULL;

	diff = self->timestamp - level.time;
	if (diff < -1.0)
		diff = -1.0;

	VectorScale(self->moveinfo.dir, 1.0 + diff, v);
	v[2] = diff;

	diff = self->s.angles[2];
	vectoangles(v, self->s.angles);
	self->s.angles[2] = diff + 10;
}

void
misc_viper_bomb_use(edict_t * self, edict_t * other, edict_t * activator)
{
	edict_t        *viper;

	self->solid = SOLID_BBOX;
	self->svflags &= ~SVF_NOCLIENT;
	self->s.effects |= EF_ROCKET;
	self->use = NULL;
	self->movetype = MOVETYPE_TOSS;
	self->prethink = misc_viper_bomb_prethink;
	self->touch = misc_viper_bomb_touch;
	self->activator = activator;

	viper = G_Find(NULL, FOFS(classname), "misc_viper");
	VectorScale(viper->moveinfo.dir, viper->moveinfo.speed, self->velocity);

	self->timestamp = level.time;
	VectorCopy(viper->moveinfo.dir, self->moveinfo.dir);
}

void
SP_misc_viper_bomb(edict_t * self)
{
	self->movetype = MOVETYPE_NONE;
	self->solid = SOLID_NOT;
	VectorSet(self->mins, -8, -8, -8);
	VectorSet(self->maxs, 8, 8, 8);

	self->s.modelindex = gi.modelindex("models/objects/bomb/tris.md2");

	if (!self->dmg)
		self->dmg = 1000;

	self->use = misc_viper_bomb_use;
	self->svflags |= SVF_NOCLIENT;

	gi.linkentity(self);
}


/*
 * QUAKED misc_strogg_ship (1 .5 0) (-16 -16 0) (16 16 32) This is a Storgg
 * ship for the flybys. It is trigger_spawned, so you must have something use
 * it for it to show up. There must be a path for it to follow once it is
 * activated.
 *
 * "speed"		How fast it should fly
 */

extern void	train_use(edict_t * self, edict_t * other, edict_t * activator);
extern void	func_train_find(edict_t * self);

void
misc_strogg_ship_use(edict_t * self, edict_t * other, edict_t * activator)
{
	self->svflags &= ~SVF_NOCLIENT;
	self->use = train_use;
	train_use(self, other, activator);
}

void
SP_misc_strogg_ship(edict_t * ent)
{
	if (!ent->target) {
		gi.dprintf("%s without a target at %s\n", ent->classname, vtos(ent->absmin));
		G_FreeEdict(ent);
		return;
	}
	if (!ent->speed)
		ent->speed = 300;

	ent->movetype = MOVETYPE_PUSH;
	ent->solid = SOLID_NOT;
	ent->s.modelindex = gi.modelindex("models/ships/strogg1/tris.md2");
	VectorSet(ent->mins, -16, -16, 0);
	VectorSet(ent->maxs, 16, 16, 32);

	ent->think = func_train_find;
	ent->nextthink = level.time + FRAMETIME;
	ent->use = misc_strogg_ship_use;
	ent->svflags |= SVF_NOCLIENT;
	ent->moveinfo.accel = ent->moveinfo.decel = ent->moveinfo.speed = ent->speed;

	gi.linkentity(ent);
}


/*
 * QUAKED misc_satellite_dish (1 .5 0) (-64 -64 0) (64 64 128)
 */
void
misc_satellite_dish_think(edict_t * self)
{
	self->s.frame++;
	if (self->s.frame < 38)
		self->nextthink = level.time + FRAMETIME;
}

void
misc_satellite_dish_use(edict_t * self, edict_t * other, edict_t * activator)
{
	self->s.frame = 0;
	self->think = misc_satellite_dish_think;
	self->nextthink = level.time + FRAMETIME;
}

void
SP_misc_satellite_dish(edict_t * ent)
{
	ent->movetype = MOVETYPE_NONE;
	ent->solid = SOLID_BBOX;
	VectorSet(ent->mins, -64, -64, 0);
	VectorSet(ent->maxs, 64, 64, 128);
	ent->s.modelindex = gi.modelindex("models/objects/satellite/tris.md2");
	ent->use = misc_satellite_dish_use;
	gi.linkentity(ent);
}


/*
 * QUAKED light_mine1 (0 1 0) (-2 -2 -12) (2 2 12)
 */
void
SP_light_mine1(edict_t * ent)
{
	ent->movetype = MOVETYPE_NONE;
	ent->solid = SOLID_BBOX;
	ent->s.modelindex = gi.modelindex("models/objects/minelite/light1/tris.md2");
	ent->s.renderfx |= RF_NOSHADOW;
	gi.linkentity(ent);
}


/*
 * QUAKED light_mine2 (0 1 0) (-2 -2 -12) (2 2 12)
 */
void
SP_light_mine2(edict_t * ent)
{
	ent->movetype = MOVETYPE_NONE;
	ent->solid = SOLID_BBOX;
	ent->s.modelindex = gi.modelindex("models/objects/minelite/light2/tris.md2");
	ent->s.renderfx |= RF_NOSHADOW;
	gi.linkentity(ent);
}


/*
 * QUAKED misc_gib_arm (1 0 0) (-8 -8 -8) (8 8 8) Intended for use with the
 * target_spawner
 */
void
SP_misc_gib_arm(edict_t * ent)
{
	gi.setmodel(ent, "models/objects/gibs/arm/tris.md2");
	ent->solid = SOLID_NOT;
	ent->s.effects |= EF_GIB;
	ent->takedamage = DAMAGE_YES;
	ent->die = gib_die;
	ent->movetype = MOVETYPE_TOSS;
	ent->svflags |= SVF_MONSTER;
	ent->deadflag = DEAD_DEAD;
	ent->avelocity[0] = random() * 200;
	ent->avelocity[1] = random() * 200;
	ent->avelocity[2] = random() * 200;
	ent->think = G_FreeEdict;
	ent->nextthink = level.time + 30;
	gi.linkentity(ent);
}

/*
 * QUAKED misc_gib_leg (1 0 0) (-8 -8 -8) (8 8 8) Intended for use with the
 * target_spawner
 */
void
SP_misc_gib_leg(edict_t * ent)
{
	gi.setmodel(ent, "models/objects/gibs/leg/tris.md2");
	ent->solid = SOLID_NOT;
	ent->s.effects |= EF_GIB;
	ent->takedamage = DAMAGE_YES;
	ent->die = gib_die;
	ent->movetype = MOVETYPE_TOSS;
	ent->svflags |= SVF_MONSTER;
	ent->deadflag = DEAD_DEAD;
	ent->avelocity[0] = random() * 200;
	ent->avelocity[1] = random() * 200;
	ent->avelocity[2] = random() * 200;
	ent->think = G_FreeEdict;
	ent->nextthink = level.time + 30;
	gi.linkentity(ent);
}

/*
 * QUAKED misc_gib_head (1 0 0) (-8 -8 -8) (8 8 8) Intended for use with the
 * target_spawner
 */
void
SP_misc_gib_head(edict_t * ent)
{
	gi.setmodel(ent, "models/objects/gibs/head/tris.md2");
	ent->solid = SOLID_NOT;
	ent->s.effects |= EF_GIB;
	ent->takedamage = DAMAGE_YES;
	ent->die = gib_die;
	ent->movetype = MOVETYPE_TOSS;
	ent->svflags |= SVF_MONSTER;
	ent->deadflag = DEAD_DEAD;
	ent->avelocity[0] = random() * 200;
	ent->avelocity[1] = random() * 200;
	ent->avelocity[2] = random() * 200;
	ent->think = G_FreeEdict;
	ent->nextthink = level.time + 30;
	gi.linkentity(ent);
}

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

/*
 * QUAKED target_character (0 0 1) ? used with target_string (must be on same
 * "team") "count" is position in the string (starts at 1)
 */

void
SP_target_character(edict_t * self)
{
	self->movetype = MOVETYPE_PUSH;
	gi.setmodel(self, self->model);
	self->solid = SOLID_BSP;
	self->s.frame = 12;
	gi.linkentity(self);
	return;
}


/*
 * QUAKED target_string (0 0 1) (-8 -8 -8) (8 8 8)
 */

void
target_string_use(edict_t * self, edict_t * other, edict_t * activator)
{
	edict_t        *e;
	int		n         , l;
	char		c;

	l = strlen(self->message);
	for (e = self->teammaster; e; e = e->teamchain) {
		if (!e->count)
			continue;
		n = e->count - 1;
		if (n > l) {
			e->s.frame = 12;
			continue;
		}
		c = self->message[n];
		if (c >= '0' && c <= '9')
			e->s.frame = c - '0';
		else if (c == '-')
			e->s.frame = 10;
		else if (c == ':')
			e->s.frame = 11;
		else
			e->s.frame = 12;
	}
}

void
SP_target_string(edict_t * self)
{
	if (!self->message)
		self->message = "";
	self->use = target_string_use;
}


/*
 * QUAKED func_clock (0 0 1) (-8 -8 -8) (8 8 8) TIMER_UP TIMER_DOWN START_OFF
 * MULTI_USE target a target_string with this
 *
 * The default is to be a time of day clock
 *
 * TIMER_UP and TIMER_DOWN run for "count" seconds and the fire "pathtarget" If
 * START_OFF, this entity must be used before it starts
 *
 * "style"		0 "xx" 1 "xx:xx" 2 "xx:xx:xx"
 */

#define	CLOCK_MESSAGE_SIZE	16

/* don't let field width of any clock messages change, or it */
/* could cause an overwrite after a game load */

static void
func_clock_reset(edict_t * self)
{
	self->activator = NULL;
	if (self->spawnflags & 1) {
		self->health = 0;
		self->wait = self->count;
	} else if (self->spawnflags & 2) {
		self->health = self->count;
		self->wait = 0;
	}
}

static void
func_clock_format_countdown(edict_t * self)
{
	if (self->style == 0) {
		Com_sprintf(self->message, CLOCK_MESSAGE_SIZE, "%2i", self->health);
		return;
	}
	if (self->style == 1) {
		Com_sprintf(self->message, CLOCK_MESSAGE_SIZE, "%2i:%2i", self->health / 60, self->health % 60);
		if (self->message[3] == ' ')
			self->message[3] = '0';
		return;
	}
	if (self->style == 2) {
		Com_sprintf(self->message, CLOCK_MESSAGE_SIZE, "%2i:%2i:%2i", self->health / 3600, (self->health - (self->health / 3600) * 3600) / 60, self->health % 60);
		if (self->message[3] == ' ')
			self->message[3] = '0';
		if (self->message[6] == ' ')
			self->message[6] = '0';
		return;
	}
}

void
func_clock_think(edict_t * self)
{
	if (!self->enemy) {
		self->enemy = G_Find(NULL, FOFS(targetname), self->target);
		if (!self->enemy)
			return;
	}
	if (self->spawnflags & 1) {
		func_clock_format_countdown(self);
		self->health++;
	} else if (self->spawnflags & 2) {
		func_clock_format_countdown(self);
		self->health--;
	} else {
		struct tm      *ltime;
		time_t		gmtime;

		time(&gmtime);
		ltime = localtime(&gmtime);
		Com_sprintf(self->message, CLOCK_MESSAGE_SIZE, "%2i:%2i:%2i", ltime->tm_hour, ltime->tm_min, ltime->tm_sec);
		if (self->message[3] == ' ')
			self->message[3] = '0';
		if (self->message[6] == ' ')
			self->message[6] = '0';
	}

	self->enemy->message = self->message;
	self->enemy->use(self->enemy, self, self);

	if (((self->spawnflags & 1) && (self->health > self->wait)) ||
	    ((self->spawnflags & 2) && (self->health < self->wait))) {
		if (self->pathtarget) {
			char           *savetarget;
			char           *savemessage;

			savetarget = self->target;
			savemessage = self->message;
			self->target = self->pathtarget;
			self->message = NULL;
			G_UseTargets(self, self->activator);
			self->target = savetarget;
			self->message = savemessage;
		}
		if (!(self->spawnflags & 8))
			return;

		func_clock_reset(self);

		if (self->spawnflags & 4)
			return;
	}
	self->nextthink = level.time + 1;
}

void
func_clock_use(edict_t * self, edict_t * other, edict_t * activator)
{
	if (!(self->spawnflags & 8))
		self->use = NULL;
	if (self->activator)
		return;
	self->activator = activator;
	self->think(self);
}

void
SP_func_clock(edict_t * self)
{
	if (!self->target) {
		gi.dprintf("%s with no target at %s\n", self->classname, vtos(self->s.origin));
		G_FreeEdict(self);
		return;
	}
	if ((self->spawnflags & 2) && (!self->count)) {
		gi.dprintf("%s with no count at %s\n", self->classname, vtos(self->s.origin));
		G_FreeEdict(self);
		return;
	}
	if ((self->spawnflags & 1) && (!self->count))
		self->count = 60 * 60;;

	func_clock_reset(self);

	self->message = gi.TagMalloc(CLOCK_MESSAGE_SIZE, TAG_LEVEL);

	self->think = func_clock_think;

	if (self->spawnflags & 4)
		self->use = func_clock_use;
	else
		self->nextthink = level.time + 1;
}

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

void
teleporter_touch(edict_t * self, edict_t * other, cplane_t * plane, csurface_t * surf)
{
	edict_t        *dest;
	int		i;

	if (!other->client)
		return;
	dest = G_Find(NULL, FOFS(targetname), self->target);
	if (!dest) {
		gi.dprintf("Couldn't find destination\n");
		return;
	}
	/* unlink to make sure it can't possibly interfere with KillBox */
	gi.unlinkentity(other);

	VectorCopy(dest->s.origin, other->s.origin);
	VectorCopy(dest->s.origin, other->s.old_origin);
	other->s.origin[2] += 10;

	/* clear the velocity and hold them in place briefly */
	VectorClear(other->velocity);
	other->client->ps.pmove.pm_time = 160 >> 3;	/* hold time */
	other->client->ps.pmove.pm_flags |= PMF_TIME_TELEPORT;

	/* draw the teleport splash at source and on the player */
	self->owner->s.event = EV_PLAYER_TELEPORT;
	other->s.event = EV_PLAYER_TELEPORT;

	/* set angles */
	for (i = 0; i < 3; i++) {
		other->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(dest->s.angles[i] - other->client->resp.cmd_angles[i]);
	}

	VectorClear(other->s.angles);
	VectorClear(other->client->ps.viewangles);
	VectorClear(other->client->v_angle);

	/* kill anything at the destination */
	KillBox(other);

	gi.linkentity(other);
}

/*
 * QUAKED misc_teleporter (1 0 0) (-32 -32 -24) (32 32 -16) Stepping onto
 * this disc will teleport players to the targeted misc_teleporter_dest
 * object.
 */
void
SP_misc_teleporter(edict_t * ent)
{
	edict_t        *trig;

	if (!ent->target) {
		gi.dprintf("teleporter without a target.\n");
		G_FreeEdict(ent);
		return;
	}
	gi.setmodel(ent, "models/objects/dmspot/tris.md2");
	ent->s.skinnum = 1;
	ent->s.effects = EF_TELEPORTER;
	ent->s.sound = gi.soundindex("world/amb10.wav");
	ent->solid = SOLID_BBOX;

	VectorSet(ent->mins, -32, -32, -24);
	VectorSet(ent->maxs, 32, 32, -16);
	gi.linkentity(ent);

	trig = G_Spawn();
	trig->touch = teleporter_touch;
	trig->solid = SOLID_TRIGGER;
	trig->target = ent->target;
	trig->owner = ent;
	VectorCopy(ent->s.origin, trig->s.origin);
	VectorSet(trig->mins, -8, -8, 8);
	VectorSet(trig->maxs, 8, 8, 24);
	gi.linkentity(trig);

}

/*
 * QUAKED misc_teleporter_dest (1 0 0) (-32 -32 -24) (32 32 -16) Point
 * teleporters at these.
 */
void
SP_misc_teleporter_dest(edict_t * ent)
{
	gi.setmodel(ent, "models/objects/dmspot/tris.md2");
	ent->s.skinnum = 0;
	ent->solid = SOLID_BBOX;
	/* ent->s.effects |= EF_FLIES; */
	VectorSet(ent->mins, -32, -32, -24);
	VectorSet(ent->maxs, 32, 32, -16);
	gi.linkentity(ent);
}
