#include "world.h"

// exports
DllExport	void	func_door(userEntity_t *self);
DllExport	void	func_door_rotate (userEntity_t *self);
DllExport	void	func_plat (userEntity_t *self);
DllExport	void	func_rotate(userEntity_t *self);
DllExport	void	func_door_rotating (userEntity_t *self);
DllExport	void	func_train (userEntity_t *self);
DllExport	void	path_corner_train (userEntity_t *self);
DllExport	void	func_button (userEntity_t *self);
DllExport	void	func_door_secret (userEntity_t *self);
DllExport	void	func_wall (userEntity_t *self);
DllExport	void	func_multi_button (userEntity_t *self);
DllExport	void	func_areaportal (userEntity_t *self);

/////////////////////////////////////////////////////////////////////
// defines
/////////////////////////////////////////////////////////////////////

#define	AREAPORTAL_CLOSED	FALSE
#define	AREAPORTAL_OPEN		TRUE

#define DOOR_START_OPEN		1
#define	DOOR_REVERSE		2
#define DOOR_DONT_LINK		4
#define DOOR_TOGGLE			8 
#define	DOOR_AUTO_OPEN		16
#define	DOOR_USE_TO_CLOSE	32
#define	DOOR_CONTINUOUS		64

#define ROTATE_START_OPEN	1
#define	ROTATE_REVERSE		2
#define ROTATE_DONT_LINK	4
#define ROTATE_TOGGLE		8 
#define ROTATE_X_AXIS		16
#define	ROTATE_Y_AXIS		32
#define	ROTATE_AUTO_OPEN	64
#define	ROTATE_USE_TO_CLOSE	128

#define	PLAT_START_UP		1
#define	PLAT_TOGGLE			8

//buttons
#define	PUSH_TOUCH			1

//func_trains
#define	X_AXIS				1
#define	Y_AXIS				2
#define	Z_AXIS				4
#define	WAITFORTRIGGER		8

#define	STATE_DELAYED		255

#define	CONTINUOUS_OFF			0
#define	CONTINUOUS_ON			1
#define	CONTINUOUS_TRIGGERED	2
#define	CONTINUOUS_STOP_NEXT	3

///////////////////////////////////////////////////////////////////////////////
// prototypes
///////////////////////////////////////////////////////////////////////////////

void	door_fire (userEntity_t *self, userEntity_t *other, userEntity_t *activator);
void	door_use (userEntity_t *self, userEntity_t *other, userEntity_t *activator);

///////////////////////////////////////////////////////////////////////////////
//	com_CalcMoveFinished
//
//	makes sure a door and all its linked doors have completely closed before 
//	toggling an associated func_areaportal
///////////////////////////////////////////////////////////////////////////////

void	com_CalcMoveFinished (userEntity_t *self)
{
	doorHook_t		*hook = (doorHook_t *) self->userHook;
	doorHook_t		*lhook;
	int				all_closed = TRUE;
	userEntity_t	*linked_door;

	self->nextthink = -1.0;

	if (hook->done_think) hook->done_think (self);

	//	if this door is linked to a portal, and it is about to close the portal
	//	make sure that ALL linked doors are closed before this door
	//	can be reactivated, and before it can toggle an areaportal

	if (hook->portal && hook->portal->light_level == AREAPORTAL_OPEN && hook->state == STATE_BOTTOM)
	{
		if (self->owner)
		{
			for (linked_door = self->owner; linked_door; linked_door = lhook->linked_door)
			{
				lhook = (doorHook_t *) linked_door->userHook;
		
				if (lhook->state != hook->state)
				{
					//	not all linked doors have closed, so come back here next frame and try again
					self->think = com_CalcMoveFinished;
					self->nextthink = gstate->time + 0.1;

//					gstate->Con_Printf ("Door could not close portal\n");
					return;
				}
			}
		}

//		gstate->Con_Printf ("Door closed portal\n");
		hook->portal->use (hook->portal, self, self);
	}
}

///////////////////////////////////////////////////////////////////////////////
//	com_CalcMoveDone
//
//	After moving, set origin to exact final destination
//	Set velocity to 0
///////////////////////////////////////////////////////////////////////////////

void	com_CalcMoveDone (userEntity_t *self)
{
	doorHook_t	*hook = (doorHook_t *) self->userHook;
	Vector		move;

	//	FIXME:	do a pushmove rather than an origin set!!
	//	move to final destination
//	gstate->SetOrigin (self, hook->final_dest.x, hook->final_dest.y, hook->final_dest.z);
	move = hook->final_dest - Vector (self->s.origin);	
	gstate->PushMove (self, move.vec (), zero_vector);

	//	zero velocity
	com->ZeroVector (self->velocity);
	
	self->think = com_CalcMoveFinished;
	self->nextthink = gstate->time + 0.1;
}

///////////////////////////////////////////////////////////////////////////////
//	com_CalcMove
//
//	calculate self.velocity and self.nextthink to reach dest from
//	self.origin traveling at speed
///////////////////////////////////////////////////////////////////////////////

void	com_CalcMove (userEntity_t *self, Vector &tdest, float tspeed, think_t func)
{
	Vector		vdestdelta;
	float		len, traveltime;
	doorHook_t	*hook = (doorHook_t *) self->userHook;

	if (!tspeed)
		return;

	hook->done_think = func;
	hook->final_dest = tdest;
	self->think = com_CalcMoveDone;

	if (tdest == Vector (self->s.origin))
	{
		
		com->ZeroVector (self->velocity);
		//self->nextthink = self->ltime + 0.1;
		self->nextthink = gstate->time + 0.1;

		return;
	}

	// set destdleta to the vector needed to move
	vdestdelta = tdest - Vector (self->s.origin);

	// calculate length of vector
	len = vdestdelta.vlen ();

	// divide by speed to get time to read dest
	traveltime = len / tspeed;
	
	if (traveltime < 0.1)
	{
		com->ZeroVector (self->velocity);
		//self->nextthink = self->ltime + 0.1;
		self->nextthink = gstate->time + 0.1;
		return;
	}

	// set netthink to trigger a thnk when dest is reached
	//self->nextthink = self->ltime + traveltime;
	self->nextthink = gstate->time + traveltime;
	hook->end_move_time = self->nextthink;
	
	// scale the destdelta vector by the tme spent traveling to get velocity
	// self.velocity = vdestdelta * (1/traveltime)
	vdestdelta = vdestdelta * (1 / traveltime);
	vdestdelta.set (self->velocity);
}

///////////////////////////////////////////////////////////////////////////////
//	com_CalcAngleMoveDone
//
//	After moving, set origin to exact final destination
//	Set velocity to 0
///////////////////////////////////////////////////////////////////////////////

void	com_CalcAngleMoveDone (userEntity_t *self)
{
	doorHook_t	*hook = (doorHook_t *) self->userHook;
	Vector		amove;

	//hook->final_dest.set (self->s.angles);
	amove = hook->final_dest - Vector (self->s.angles);
	gstate->PushMove (self, zero_vector, amove.vec ());
	com->ZeroVector (self->avelocity);

	self->think = com_CalcMoveFinished;
	self->nextthink = gstate->time + 0.1;	

	//	self->nextthink = -1;	
	//	if (hook->done_think) hook->done_think (self);
}

///////////////////////////////////////////////////////////////////////////////
//	com_CalcAngleMove
//
//	calculate self.velocity and self.nextthink to reach dest from
//	self.origin traveling at speed
///////////////////////////////////////////////////////////////////////////////

void	com_CalcAngleMove (userEntity_t *self, Vector &destangle, float tspeed, think_t func)
{
	Vector		destdelta;
	float		len, traveltime;
	doorHook_t	*hook = (doorHook_t *) self->userHook;

	if (!tspeed)
		return;

	// set destdelta to the vector needed to move
	destdelta = destangle - Vector (self->s.angles);

	// calculate length of vector
	len = destdelta.vlen ();

	// divide by speed to get time to reach dest
	traveltime = len / tspeed;

	hook->done_think = func;
	hook->final_dest = destangle;

	if (traveltime)
	{
		// set nextthink to trigger a think when dest is reached
		//self->nextthink = self->ltime + traveltime;
		self->nextthink = gstate->time + traveltime;
		self->think = com_CalcAngleMoveDone;

		// scale the destdelta vector by the time spent traveling to get velocity
		destdelta = destdelta * (1 / traveltime);
		destdelta.set (self->avelocity);
	}
	else
		com_CalcAngleMoveDone (self);
}

///////////////////////////////////////////////////////////////////////////////
//	areaportal_toggle
//
//	toggles a func_areaportal on and off
//	
//	self->frags = portal number
//	self->light_level = state, TRUE = open, FALSE = closed
///////////////////////////////////////////////////////////////////////////////

void	areaportal_toggle (userEntity_t *self, userEntity_t *other, userEntity_t *activator)
{
	self->light_level ^= 1;		// toggle state

	gstate->SetAreaPortalState (self->frags, self->light_level);
/*
	if (self->light_level == AREAPORTAL_OPEN)
		gstate->Con_Printf ("area portal #%i opened.\n", self->frags);
	else
		gstate->Con_Printf ("area portal #%i closed.\n", self->frags);
*/
}

///////////////////////////////////////////////////////////////////////////////
//	areaportal_set
//
//	sets a func_areaportal to the specified state
//	
//	self->frags = portal number
//	self->light_level = state, TRUE = open, FALSE = closed
///////////////////////////////////////////////////////////////////////////////

void	areaportal_set (userEntity_t *self, int state)
{
	self->light_level = state;

	gstate->SetAreaPortalState (self->frags, state);
}

///////////////////////////////////////////////////////////////////////////////
//	func_areaportal
//
//	self->frags = portal number
//	self->light_level = state, TRUE = close, FALSE = open
///////////////////////////////////////////////////////////////////////////////

void	func_areaportal (userEntity_t *self)
{
	int	i;

	for (i = 0; self->epair [i].key != NULL; i++)
	{
		if (!stricmp (self->epair [i].key, "targetname"))
			self->targetname = self->epair [i].value;
		else if (!stricmp (self->epair [i].key, "target"))
			self->target = self->epair [i].value;
		else if (!stricmp (self->epair [i].key, "style"))
			self->frags = atoi (self->epair [i].value);

		//	get mins/maxs for epairs for areaportals
		else if (!stricmp (self->epair [i].key, "minsx"))
			self->absmin [0] = atof (self->epair [i].value);
		else if (!stricmp (self->epair [i].key, "minsy"))
			self->absmin [1] = atof (self->epair [i].value);
		else if (!stricmp (self->epair [i].key, "minsz"))
			self->absmin [2] = atof (self->epair [i].value);

		else if (!stricmp (self->epair [i].key, "maxsx"))
			self->absmax [0] = atof (self->epair [i].value);
		else if (!stricmp (self->epair [i].key, "maxsy"))
			self->absmax [1] = atof (self->epair [i].value);
		else if (!stricmp (self->epair [i].key, "maxsz"))
			self->absmax [2] = atof (self->epair [i].value);

	}

	self->use = areaportal_toggle;
	self->light_level = AREAPORTAL_CLOSED;
}

void	door_go_down (userEntity_t *self);
void	door_go_up (userEntity_t *self);
void	door_touch (userEntity_t *self, userEntity_t *other, cplane_t *plane, csurface_t *surf);
void	plat_touch (userEntity_t *self, userEntity_t *other, cplane_t *plane, csurface_t *surf);

///////////////////////////////////////////////////////////////////////////////
//	door_find_areaportal
//
//	looks for an area portal inside of a door and links them if found
///////////////////////////////////////////////////////////////////////////////

void	door_find_areaportal (userEntity_t *self)
{
	doorHook_t		*hook = (doorHook_t *) self->userHook;
	userEntity_t	*head;

	for (head = gstate->FirstEntity (); head; head = gstate->NextEntity (head))
	{
		if (head->className && !stricmp (head->className, "func_areaportal"))
		{
			//	check if this areaportal is already linked
			if (head->owner != NULL)
				continue;
			else if (head->absmin [0] > self->absmax [0] || 
					head->absmin [1] > self->absmax [1] || 
					head->absmin [2] > self->absmax [2] || 
					head->absmax [0] < self->absmin [0] || 
					head->absmax [1] < self->absmin [1] || 
					head->absmax [2] < self->absmin [2])
				continue;
			
			hook->portal = head;
			head->owner = self;

			//	toggle portal off if door starts open
			if (self->spawnflags & DOOR_START_OPEN)
				areaportal_set (head, AREAPORTAL_OPEN);

			return;
		}
	}
}

//////////////////////////////
//	door_blocked
//
//////////////////////////////

void	door_blocked (userEntity_t *self, userEntity_t *other)
{
	doorHook_t	*hook = (doorHook_t *) self->userHook;

	// FIXME: Call damage routine to squish

	com->Damage (other, self, self, zero_vector, zero_vector, hook->damage, DAMAGE_SQUISH | DAMAGE_NO_BLOOD);

	if (hook->wait >= 0)
	{
		if (hook->state == STATE_DOWN)
			door_go_up (self);
		else
			door_go_down (self);
	}
}

//////////////////////////////
//	plat_restore_touch
//
//////////////////////////////

void	plat_restore_touch (userEntity_t *self)
{
	self->touch = plat_touch;
	
	self->think = NULL;
	self->nextthink = -1;
}

///////////////////////////////////////////////////////////////////////////////
//	door_continuous_refire
//
///////////////////////////////////////////////////////////////////////////////

void	door_continuous_refire (userEntity_t *self)
{
	doorHook_t	*hook = (doorHook_t *) self->userHook;

	door_fire (self, self, hook->activator);
}

//////////////////////////////
//	door_hit_top
//
//////////////////////////////

void	door_hit_top (userEntity_t *self)
{
	doorHook_t	*hook = (doorHook_t *) self->userHook;

	gstate->PositionedSound (NULL, self, CHAN_OVERRIDE, hook->sound_top, 1.0, ATTN_NORM, 0.0);

	hook->state = STATE_TOP;

	if (!stricmp (self->className, "func_door"))
	{
		if (self->spawnflags & DOOR_USE_TO_CLOSE)
		{
			self->use = door_use;
			return;
		}
	}
	else if (!stricmp (self->className, "func_door_rotate"))
	{
		if (self->spawnflags & ROTATE_USE_TO_CLOSE)
		{
			self->use = door_use;
			return;
		}
//		else if (hook->wait == -1)
//			hook->no_more_move_damnit = TRUE;
	}
	else if (!stricmp (self->className, "func_plat"))
	{
		if (self->spawnflags & PLAT_START_UP)
		{
			self->think = plat_restore_touch;
			self->nextthink = gstate->time + 1.0;
		}
		else
			self->touch = plat_touch;
	}

	if (hook->continuous_mode == CONTINUOUS_TRIGGERED || hook->continuous_mode == CONTINUOUS_ON)
	{
		self->think = door_continuous_refire;
		if (hook->wait > 0)
			self->nextthink = gstate->time + hook->wait;
		else
			self->nextthink = gstate->time + 0.1;
		return;
	}	
	else if (self->spawnflags & DOOR_TOGGLE)
	{
		self->nextthink = -1;
		return;
	}
	else if (!(self->spawnflags & DOOR_START_OPEN) && hook->wait >= 0)
	{
		self->think = door_go_down;
		//	self->nextthink = self->ltime + hook->wait;
		self->nextthink = gstate->time + hook->wait;

	}
	else
		self->nextthink = -1;
}

//////////////////////////////
//	door_hit_bottom
//
//////////////////////////////

void	door_hit_bottom (userEntity_t *self)
{
	doorHook_t	*hook = (doorHook_t *) self->userHook;

	hook->state = STATE_BOTTOM;

	gstate->PositionedSound (NULL, self, CHAN_OVERRIDE, hook->sound_bottom, 1.0, ATTN_NORM, 0);

	// removed touch function
	if (!stricmp (self->className, "func_door"))
	{
		if (self->spawnflags & DOOR_AUTO_OPEN)
			self->touch = door_touch;
		if (self->spawnflags & DOOR_USE_TO_CLOSE)
			self->use = door_use;
	}
	else if (!stricmp (self->className, "func_door_rotate"))
	{
		if (self->spawnflags & ROTATE_AUTO_OPEN)
			self->touch = door_touch;
		if (self->spawnflags & ROTATE_USE_TO_CLOSE)
			self->use = door_use;
	}
	else if (stricmp (self->className, "func_button") == 0)
	{
		if (self->spawnflags & PUSH_TOUCH)
			self->touch = door_touch;
	}
	else if (!stricmp (self->className, "func_plat"))
	{
		self->touch = plat_touch;
	}
	else
		self->touch = door_touch;

	if (hook->continuous_mode == CONTINUOUS_TRIGGERED || hook->continuous_mode == CONTINUOUS_ON)
	{
		//	in continuous mode, fire again
	
		self->think = door_continuous_refire;
		if (hook->wait > 0)
			self->nextthink = gstate->time + hook->wait;
		else
			self->nextthink = gstate->time + 0.1;
		return;
	}
	else if (self->spawnflags & DOOR_TOGGLE)
	{
		self->nextthink = -1;
		return;
	}
	else if (!(self->spawnflags & DOOR_START_OPEN) || hook->wait < 0)
	{
		self->think = NULL;
		self->nextthink = -1;
	}
	else
	{
		self->think = door_go_up;
		//	self->nextthink = self->ltime + hook->wait;
		self->nextthink = gstate->time + hook->wait;
	}

	//	toggle area portal if one is associated with this door
//	if (hook->portal)
//		hook->portal->use (hook->portal, self, self);
}

//////////////////////////////
//	door_go_down
//
//////////////////////////////

void	door_go_down (userEntity_t *self)
{
	doorHook_t	*hook = (doorHook_t *) self->userHook;

	gstate->PositionedSound (NULL, self, CHAN_OVERRIDE, hook->sound_down, 1.0, ATTN_NORM, 0);

	if (hook->max_health)
	{
		self->takedamage = DAMAGE_YES;
		self->health = hook->max_health;
	}

	hook->state = STATE_DOWN;

	if (stricmp (self->className, "func_door_rotate") == 0)
		com_CalcAngleMove (self, hook->pos1, hook->speed, door_hit_bottom);
	else
		com_CalcMove (self, hook->pos1, hook->speed, door_hit_bottom);
}

//////////////////////////////
//	door_go_up
//
//////////////////////////////

void	door_go_up (userEntity_t *self)
{
	doorHook_t	*hook = (doorHook_t *) self->userHook;

	// is door already going up?
	if (hook->state == STATE_UP)
		return;

	if (hook->state == STATE_TOP)
	{
		//	self->nextthink = self->ltime + hook->wait;
		self->nextthink = gstate->time + hook->wait;
		return;
	}

	//	toggle area portal if one is associated with this door and door is closed
	if (hook->portal && hook->state == STATE_BOTTOM)
		hook->portal->use (hook->portal, self, self);

	gstate->PositionedSound (NULL, self, CHAN_OVERRIDE, hook->sound_up, 1.0, ATTN_NORM, 0.0);

	hook->state = STATE_UP;

	if (!stricmp (self->className, "func_door_rotate"))
		com_CalcAngleMove (self, hook->pos2, hook->speed, door_hit_top);
	else
		com_CalcMove (self, hook->pos2, hook->speed, door_hit_top);
}

/////////////////////////////
//	door_fire
//
/////////////////////////////

void	door_fire (userEntity_t *self, userEntity_t *other, userEntity_t *activator)
{
	userEntity_t	*head;
	doorHook_t		*hook = (doorHook_t *) self->userHook;
	int				i;

	if (self->owner != self)
	{
		// make sure we're pointing to a master door
		//	so that we can fire all linked doors
		self = self->owner;
		hook = (doorHook_t *) self->userHook;

//		com->Error ("door_fire: self->owner != self!\n");
//		return;
	}
	
	// FIXME: play use key sound

	self->message = "";

	// trigger all paired doors
	// basically traverse the linked_door field until it comes
	// up NULL
	head = self->owner;

	do
	{
		hook = (doorHook_t *) head->userHook;	

		if (hook->state == STATE_TOP)
		{
			//	com->UseTargets will handle delays for using targets
			//	Q2FIXME:	activator...
			com->UseTargets (head, other, activator);

			if (head->delay != 0.0)
			{
				// still need to delay using head if it has a delay
				head->think = door_go_down;
				//	head->nextthink = head->ltime + head->delay;
				head->nextthink = gstate->time + head->delay;
			}
			else
				door_go_down (head);
		}
		else
		if (hook->state == STATE_BOTTOM)
		{
			//	com->UseTargets will handle delays for using targets
			com->UseTargets (head, other, activator);

			if (head->delay != 0.0)
			{
				// still need to delay using head if it has a delay
				head->think = door_go_up;
				//head->nextthink = head->ltime + head->delay;
				head->nextthink = gstate->time + head->delay;
			}
			else
				door_go_up (head);
		}

		head = hook->linked_door;
	} while (head != NULL);
}

/////////////////////////////
//	door_use
//
/////////////////////////////

void	door_use (userEntity_t *self, userEntity_t *other, userEntity_t *activator)
{
	doorHook_t		*hook = (doorHook_t *) self->userHook;

	hook->activator = gstate->activator;

	if (self->spawnflags & DOOR_CONTINUOUS)
	{
		if (hook->continuous_mode != CONTINUOUS_OFF)
		{
			if (hook->continuous_mode == CONTINUOUS_TRIGGERED)
				//	only stop in starting position
				hook->continuous_mode = CONTINUOUS_STOP_NEXT;
			else
				hook->continuous_mode = CONTINUOUS_OFF;
		}
		else
			hook->continuous_mode = CONTINUOUS_ON;
	}

	if (self->message && activator->flags & FL_CLIENT)
		gstate->centerprintf (activator, self->message);

	//	fire master door
	door_fire (self->owner, other, activator);
}

///////////////////////////////////////////////////////////////////////////////
//	door_trigger_touch
//	  
///////////////////////////////////////////////////////////////////////////////

void	door_trigger_touch (userEntity_t *self, userEntity_t *other, cplane_t *plane, csurface_t *surf)
{
	doorHook_t		*hook = (doorHook_t *) self->userHook;

	//	if (!com->ValidTouch (self, other) || self->ltime < hook->move_finished)
	if (!com->ValidTouch (self, other) || gstate->time < hook->move_finished)
		return;

	gstate->activator = other;

	//	Q2FIXME: why are we passing other and activator?!?!?
	door_use (self, other, other);
}

///////////////////////////////////////////////////////////////////////////////
//	door_killed
//
///////////////////////////////////////////////////////////////////////////////

void	door_killed (userEntity_t *self, userEntity_t *inflictor, userEntity_t *attacker, int damage, vec3_t point)
{
	doorHook_t		*hook = (doorHook_t *) self->owner->userHook;

	self = self->owner;
	self->health = hook->max_health;
	self->takedamage = DAMAGE_NO;
	
	//	Q2FIXME: why are we passing other and activator?!?!?
	door_use (self, attacker, attacker);
}

///////////////////////////////////////////////////////////////////////////////
//	door_touch
//	  
//	Prints messages and opens key doors
//	
//	Used in Quake only for key doors, apparently
///////////////////////////////////////////////////////////////////////////////

void	door_touch (userEntity_t *self, userEntity_t *other, cplane_t *plane, csurface_t *surf)
{
	doorHook_t		*hook = (doorHook_t *) self->userHook;
	doorHook_t		*owner_hook = (doorHook_t *) self->owner->userHook;
	userEntity_t	*owner = self->owner;

	if (!com->ValidTouch (self, other))
		return;

	if (stricmp (self->className, "func_plat") == 0)
	{
		if (hook->ignore_time > gstate->time)
			return;
	}

	if (owner_hook->move_finished > gstate->time)
		return;

	owner_hook->move_finished = gstate->time + 2;

	if (owner->message && other->flags & FL_CLIENT)
		gstate->centerprintf (other, owner->message);

	self->touch = NULL;

	if (hook->linked_door)
		hook->linked_door->touch = NULL;

	door_use (self, other, other);
}

////////////////////////////////
//	plat_wait_move
//
////////////////////////////////

void	plat_wait_move (userEntity_t *self)
{	
	doorHook_t		*hook = (doorHook_t *) self->userHook;
	doorHook_t		*owner_hook = (doorHook_t *) self->owner->userHook;
	userEntity_t	*owner = self->owner;

	owner_hook->move_finished = gstate->time + 2;

	self->touch = NULL;

	if (hook->linked_door)
		hook->linked_door->touch = NULL;

	door_use (self, self, self);
}

///////////////////////////////////////////////////////////////////////////////
//	plat_touch
//  
///////////////////////////////////////////////////////////////////////////////

void	plat_touch (userEntity_t *self, userEntity_t *other, cplane_t *plane, csurface_t *surf)
{
	doorHook_t		*hook = (doorHook_t *) self->userHook;
	doorHook_t		*owner_hook = (doorHook_t *) self->owner->userHook;
	userEntity_t	*owner = self->owner;

//	gstate->Con_Printf ("plat_touch\n");

	//	don't start based on touch if we're at the top and initial position is down, or
	//	if we're at the bottom and initial position is up
	if (hook->state == STATE_TOP && !(self->spawnflags & PLAT_START_UP))
		return;
	if (hook->state == STATE_BOTTOM && self->spawnflags & PLAT_START_UP)
		return;

	//	don't start if toucher is not moving since this probably means they just rode
	//	the plat down and are simply standing on it
	if (other->velocity [0] == 0 && other->velocity [1] == 0)
		return;

	if (!com->ValidTouch (self, other))
		return;

	if (hook->ignore_time > gstate->time)
		return;

	if (owner_hook->move_finished > gstate->time)
		return;

	owner_hook->move_finished = gstate->time + 2;

	if (owner->message && other->flags & FL_CLIENT)
		gstate->centerprintf (other, owner->message);
	
	self->touch = NULL;

	if (hook->linked_door)
		hook->linked_door->touch = NULL;

	//	Q2FIXME: why are we passing other and activator?!?!?
	door_use (self, other, other);
}

/////////////////////////////
//	plat_trigger_touch
//
//	trigger spawns over the plat's down position
//	for plats that start down
/////////////////////////////  

void	plat_trigger_touch (userEntity_t *self, userEntity_t *other, cplane_t *plane, csurface_t *surf)
{
	doorHook_t		*owner_hook = (doorHook_t *) self->owner->userHook;
	userEntity_t	*old_self;

	if (!com->ValidTouch (self, other))
		return;

	if (owner_hook->state == STATE_UP)
		return;

	if (owner_hook->ignore_time >= gstate->time && other->velocity [2] == 0.0)
		return;

//	gstate->Con_Printf ("plat_trigger_touch\n");

	owner_hook->ignore_time = gstate->time + 0.5;
}

/*
===========================================================
Spawning functions
===========================================================
*/

///////////////////////////////////////////////////////////////////////////////
//	SpawnPlatTrigger
//
//	spawns a trigger on top of a platform's down position, when this trigger
//	is hit, the associated platform will be be stopped from triggering for
//	an amount of time after that
//
//	stops plats from going up immediately when someone falls down on them
///////////////////////////////////////////////////////////////////////////////

userEntity_t *SpawnPlatTrigger (userEntity_t *self)
{
	userEntity_t	*trigger;
	Vector			min, max, temp, org, t_org;

	if (self->spawnflags & PLAT_START_UP)
		return	NULL;

	trigger = gstate->SpawnEntity ();
	trigger->movetype = MOVETYPE_NONE;
	trigger->solid = SOLID_TRIGGER;
	trigger->owner = self;
	trigger->touch = plat_trigger_touch;
	trigger->className = "plat_trigger";

	trigger->userHook = com->malloc_hook (sizeof (doorHook_t), MEM_HUNK);

	com->GetMinsMaxs (self, org, min, max);

	//	spawn trigger above plat
	t_org= org + Vector (0, 0, max.z + 72.0);

	gstate->SetOrigin (trigger, t_org.x, t_org.y, t_org.z);

	// make 8 smaller than plat on each edge
	temp = Vector (8.0, 8.0, 0.0);
	min = min + temp;
	max = max - temp;
	max.z = 8.0;
	min.z = -8.0;

//	gstate->SetSize (trigger, min.x, min.y, min.z, max.x, max.y, max.z);
//	com->SpawnMarker (self, t_org, "models/debug/marker.mdl", 1000);

	return trigger;
}

///////////////////////////////////////////////////////////////////////////////
//	SpawnField
//
//	spawns a trigger field around a door so that it can be opened before the
//	door is actually touched
///////////////////////////////////////////////////////////////////////////////

userEntity_t *SpawnField (userEntity_t *self, Vector &fmins, Vector &fmaxs)
{
	userEntity_t	*trigger;
	Vector			t1, t2, temp;

	trigger = gstate->SpawnEntity ();
	trigger->movetype = MOVETYPE_NONE;
	trigger->solid = SOLID_TRIGGER;
	trigger->owner = self;
	trigger->touch = door_trigger_touch;
	trigger->className = "door_trigger";

	trigger->userHook = com->malloc_hook (sizeof (doorHook_t), MEM_HUNK);

	t1 = fmins;
	t2 = fmins;

	if (stricmp (self->className, "func_door_rotate") == 0)
		temp = Vector (96.0, 96.0, 8.0);
	else
		temp = Vector (60.0, 60.0, 8.0);

	t1 = t1 - temp;
	t2 = t2 + temp;
	
	gstate->SetSize (trigger, t1.x, t1.y, t1.z, t2.x, t2.y, t2.z);

	return trigger;
}

///////////////////////////////////////////////////////////////////////////////
//	EntitiesTouching
//
/////////////////////////////////////////////////////////////////////////////// 

int	EntitiesTouching (userEntity_t *e1, userEntity_t *e2)
{
	Vector	mins1, maxs1, mins2, maxs2;

	mins1 = Vector (e1->absmin);
	maxs1 = Vector (e1->absmax);

	mins2 = Vector (e2->absmin);
	maxs2 = Vector (e2->absmax);

	if (mins1.x > maxs2.x)
		return FALSE;
	if (mins1.y > maxs2.y)
		return FALSE;
	if (mins1.z > maxs2.z)
		return FALSE;
	if (maxs1.x < mins2.x)
		return FALSE;
	if (maxs1.y < mins2.y)
		return FALSE;
	if (maxs1.z < mins2.z)
		return FALSE;

	return TRUE;
}

///////////////////////////
//	TouchesDoorInList
//
///////////////////////////

int	TouchesDoorInList (userEntity_t *start_door, userEntity_t *door)
{
	doorHook_t		*cur_hook;
	userEntity_t	*cur_door;

	cur_door = start_door;

	while (cur_door)
	{
		if (EntitiesTouching (cur_door, door))
			return	TRUE;

		cur_hook = (doorHook_t *) cur_door->userHook;
		cur_door = cur_hook->linked_door;
	}

	return	FALSE;
}

///////////////////////////
//	TouchingDoor
//
//	searches through the entire entity list
//	and returns the first unlinked door that
//	touches self
/////////////////////////// 

userEntity_t	*TouchingDoor (userEntity_t *self)
{
	userEntity_t	*door;
	doorHook_t		*dhook;

	for (door = gstate->FirstEntity (); door; door = gstate->NextEntity (door))
	{
		if (!door->className || door == self)
			continue;
		if (stricmp (door->className, "func_door") && stricmp (door->className, "func_door_rotate"))
			continue;
	
		dhook = (doorHook_t *) door->userHook;
		if (dhook->already_linked)
			continue;

		if (EntitiesTouching (self, door))
			return	door;
	}

	return	NULL;
}

///////////////////////////
//	AddDoorToList
//
//	adds a door to the end of a master
//	door's linked_door list
//
//	master doors have self->owner == self
//	
//	this function will accept a non-master
//	door being passed as master, as long
//	as master->owner is the real master door
/////////////////////////// 

void	AddDoorToList (userEntity_t *master, userEntity_t *new_door)
{
	userEntity_t	*door;
	doorHook_t		*hook;

	//	just in case a non-master door is passed
	master = master->owner;
	hook = (doorHook_t *) master->userHook;

	//	go to last door in list
	while (hook->linked_door)
		hook = (doorHook_t *) hook->linked_door->userHook;

	hook->linked_door = new_door;
	
	hook = (doorHook_t *) new_door->userHook;
	hook->linked_door = NULL;
	hook->already_linked = TRUE;
}

///////////////////////////
//	DoorFindTouching
//
//	recursively finds all doors touching
//	self, or doors touching those doors...
/////////////////////////// 

void	DoorFindTouching (userEntity_t *door, Vector &trig_min, Vector &trig_max)
{
	userEntity_t	*new_door;

	for (new_door = TouchingDoor (door); new_door; new_door = TouchingDoor (door))
	{
		if (new_door->absmin [0] < trig_min.x)
			trig_min.z = new_door->absmin [0];
		if (new_door->absmin [1] < trig_min.y)
			trig_min.y = new_door->absmin [1];
		if (new_door->absmin [2] < trig_min.z)
			trig_min.z = new_door->absmin [2];
		if (new_door->absmax [0] < trig_max.x)
			trig_max.x = new_door->absmax [0];
		if (new_door->absmax [1] < trig_max.y)
			trig_max.y = new_door->absmax [1];
		if (new_door->absmax [2] < trig_max.z)
			trig_max.z = new_door->absmax [2];
		
		AddDoorToList (door, new_door);
		new_door->owner = door->owner;
		DoorFindTouching (new_door, trig_min, trig_max);
	}
}

///////////////////////////
//	DoorTargetted
//
/////////////////////////// 

int	DoorTargetted (userEntity_t *master)
{
	doorHook_t		*hook;
	userEntity_t	*door;

	//	just in case the real master isn't passed
	door = master->owner;

	while (door)
	{
		if (door->targetname)
			return	TRUE;

		hook = (doorHook_t *) door->userHook;
		door = hook->linked_door;
	}

	return	FALSE;
}

///////////////////////////////////////////////////////////////////////////////
//	door_parse_sounds
//
///////////////////////////////////////////////////////////////////////////////

void	door_parse_sounds (userEntity_t *self)
{
	int				i;
	char			*name;
	doorHook_t		*hook = (doorHook_t *) self->userHook;

	for (i = 0; self->epair [i].key; i++)
	{
		if (!stricmp (self->epair [i].key, "sound_open_finish"))
		{
			name = self->epair [i].value;
			hook->sound_top = gstate->SoundIndex (name);

			if (!hook->sound_top)
				hook->sound_top = gstate->SoundIndex ("null.wav");
		}	
		else if (!stricmp (self->epair [i].key, "sound_close_finish"))
		{
			name = self->epair [i].value;
			hook->sound_bottom = gstate->SoundIndex (name);

			if (!hook->sound_bottom)
				hook->sound_bottom = gstate->SoundIndex ("null.wav");
		}
		else if (!stricmp (self->epair [i].key, "sound_opening"))
		{
			name = self->epair [i].value;
			hook->sound_up = gstate->SoundIndex (name);

			if (!hook->sound_up)
				hook->sound_up = gstate->SoundIndex ("null.wav");
		}
		else if (!stricmp (self->epair [i].key, "sound_closing"))
		{
			name = self->epair [i].value;
			hook->sound_down = gstate->SoundIndex (name);

			if (!hook->sound_down)
				hook->sound_down = gstate->SoundIndex ("null.wav");
		}
	}
}

///////////////////////////////////////////////////////////////////////////////
//	door_init
//
//	sets up a door entity
//
//	finds associated func_areaportals
//	links touching doors
//	creates triggers
///////////////////////////////////////////////////////////////////////////////


void	door_init (userEntity_t *self)
{
	Vector			real_org, trig_min, trig_max, size;
	doorHook_t		*cur_hook, *last_hook, *self_hook;
	userEntity_t	*cur_door, *last_door, *touching_door, *target;
	int				i;

	self_hook = (doorHook_t *) self->userHook;

	///////////////////////////////////////////////////////////////////////////
	//	find an area portal for this door, if possible
	///////////////////////////////////////////////////////////////////////////

	if (self->target)
	{
		//	if door has a target, determine if it targets a func_areaportal Q2 style
		//	targetted func_areaportals ALWAYS override touching ones
		target = com->FindTarget (self->target);
		if (target)
		{
			if (!stricmp (target->className, "func_areaportal"))
			{
				if (target->owner)
				{
					//	if this area portal is already linked to a door, unlink it
					cur_hook = (doorHook_t *) target->owner->userHook;
					cur_hook->portal = NULL;
				}

				self_hook->portal = target;
				target->owner = self;
				
				//	clear door's target so UseTargets doesn't re-use the portal
				self->target = NULL;

				//	toggle portal off if door starts open
				if (self->spawnflags & DOOR_START_OPEN)
					areaportal_set (target, AREAPORTAL_OPEN);
			}
/*
			else
			{
				gstate->Con_Dprintf ("Bad target on %s.\n", self->className);
				self->target = "";
			}
*/
		}
		else
			//	not targeting a func_areaportal, so search for one touching
			door_find_areaportal (self);
	}
	else
		door_find_areaportal (self);
	
	if (self_hook->already_linked)
		return;

	//	extents of trigger
	trig_min = Vector (self->absmin);
	trig_max = Vector (self->absmax);

	self_hook->already_linked = TRUE;

	if (!(self->spawnflags & DOOR_DONT_LINK))
		DoorFindTouching (self, trig_min, trig_max);

	// if secret door or if door has a target, don't make a trigger field
	if (self->health || DoorTargetted (self))
		return;

	if (stricmp (self->className, "func_door_rotate") == 0)
	{
		if (!(self->spawnflags & ROTATE_AUTO_OPEN))
			return;
	}
	else if (stricmp (self->className, "func_door") == 0)
	{
		if (!(self->spawnflags & DOOR_AUTO_OPEN))
			return;
	}
			
	// if door requires keys, return
/*
	if (self->items)
		return;
*/

	self_hook->trigger_field = SpawnField (self, trig_min, trig_max);

	self->think = NULL;
	self->nextthink = -1;
}

///////////////////////////////////////////////////////////////////////////////
//	func_door
///////////////////////////////////////////////////////////////////////////////

void	func_door (userEntity_t *self)
{
	Vector		temp;
	Vector		min_size, trig_max;
	doorHook_t	*hook;
	float		lip;
	int			i;

	// get memory for userHook
	self->userHook = com->malloc_hook (sizeof (doorHook_t), MEM_HUNK);
	hook = (doorHook_t *) self->userHook;
	
	memset (hook, 0x00, sizeof (doorHook_t));
	lip = 0;
	hook->wait = 32767;

	self->targetname = self->target = self->killtarget = NULL;

	i = 0;
	while (self->epair [i].key != NULL)
	{
		if (!stricmp (self->epair [i].key, "dmg"))
			hook->damage = atof (self->epair [i].value);
		else if (!stricmp (self->epair [i].key, "wait"))
			hook->wait = atof (self->epair [i].value);
		else if (!stricmp (self->epair [i].key, "lip"))
			lip = atof (self->epair [i].value);
		else if (!stricmp (self->epair [i].key, "speed"))
			hook->speed = atof (self->epair [i].value);
		else if (!stricmp (self->epair [i].key, "targetname"))
			self->targetname = self->epair [i].value;
		else if (!stricmp (self->epair [i].key, "target"))
			self->target = self->epair [i].value;
		else if (!stricmp (self->epair [i].key, "killtarget"))
			self->killtarget = self->epair [i].value;
		else if (!stricmp (self->epair [i].key, "message"))
			self->message = self->epair [i].value;
		else if (!stricmp (self->epair [i].key, "delay"))
			self->delay = atof (self->epair [i].value);

		i++;
	}

	door_parse_sounds (self);

	com->SetMovedir (self);

	hook->max_health = self->health;
	self->solid = SOLID_BSP;
	self->movetype = MOVETYPE_PUSH;
	gstate->SetOrigin (self, self->s.origin [0], self->s.origin [1], self->s.origin [2]);
	gstate->SetModel (self, self->modelName);

	// precache the null sound so it can be used to cut off a sound playing when
	// a door opens but has no sound_top
	gstate->Precache_Sound ("null.wav");

	self->blocked = door_blocked;
	self->use = door_use;

	if (!hook->speed)
		hook->speed = 100.0;
	if (hook->wait == 32767)
		hook->wait = 3;
	if (!lip)
		lip = 8;
	if (!hook->damage)
		hook->damage = 2;

	// start position
	hook->pos1 = Vector (self->s.origin);
	
	// find ending position
	hook->pos2 = hook->pos1 + Vector (self->movedir) * (fabs (Vector (self->movedir) ^ self->size) - lip);
	
	if (self->spawnflags & DOOR_START_OPEN)
	{
		gstate->SetOrigin (self, hook->pos2.x, hook->pos2.y, hook->pos2.z);
		hook->state = STATE_TOP;
	}
	else
		hook->state = STATE_BOTTOM;

	hook->move_finished = 0.0;
//	hook->no_more_move_damnit = FALSE;

	if (self->health)
	{
		self->takedamage = DAMAGE_YES;
		self->die = door_killed;
	}

	//	Q2FIXME:	just for testing doors on Q2 maps
//	if (!self->targetname)
//		self->spawnflags |= DOOR_AUTO_OPEN;
	
	if ((self->targetname) || (!(self->spawnflags & DOOR_AUTO_OPEN)))
		self->touch = NULL;
	else
		self->touch = door_touch;

	// this must be set up if LinkDoors is not working
	self->owner = self;
	hook->linked_door = NULL;

	// door linking can't be done until all of the doors have
	// been spawned so the sizes can be detected properly
	self->think = door_init;
	self->nextthink = gstate->time + 0.1;
}

/*
================
func_plat

a func_plat is essentially a door, except the lip field is now the height field
and the START_OPEN flag is now the START_UP flag
================
*/

void	func_plat (userEntity_t *self)
{
	Vector		temp;
	doorHook_t	*hook;
	float		height, lip;
	int			i;

	// get memory for userHook
	self->userHook = com->malloc_hook (sizeof (doorHook_t), MEM_HUNK);
	hook = (doorHook_t *) self->userHook;
	lip = 8;
	height = 0.0;
	hook->wait = 32767;
	
	i = 0;
	while (self->epair [i].key != NULL)
	{
		if (stricmp (self->epair [i].key, "dmg") == 0)
			hook->damage = atof (self->epair [i].value);
		else if (stricmp (self->epair [i].key, "wait") == 0)
			hook->wait = atof (self->epair [i].value);
		else if (stricmp (self->epair [i].key, "height") == 0)
			height = atof (self->epair [i].value);
		else if (stricmp (self->epair [i].key, "speed") == 0)
			hook->speed = atof (self->epair [i].value);
		else if (stricmp (self->epair [i].key, "targetname") == 0)
			self->targetname = self->epair [i].value;
		else if (stricmp (self->epair [i].key, "target") == 0)
			self->target = self->epair [i].value;
		else if (stricmp (self->epair [i].key, "killtarget") == 0)
			self->killtarget = self->epair [i].value;
		else if (!stricmp (self->epair [i].key, "delay"))
			self->delay = atof (self->epair [i].value);
		else if (stricmp (self->epair [i].key, "lip") == 0)
			lip = atof (self->epair [i].value);
/*
		else if (stricmp (self->epair [i].key, "sound_top") == 0)
		{
			hook->sound_top = self->epair [i].value;
			gstate->Precache_Sound (hook->sound_top);
		}	
		else if (stricmp (self->epair [i].key, "sound_bottom") == 0)
		{
			hook->sound_bottom = self->epair [i].value;
			gstate->Precache_Sound (hook->sound_bottom);
		}
		else if (stricmp (self->epair [i].key, "sound_up") == 0)
		{
			hook->sound_up = self->epair [i].value;
			gstate->Precache_Sound (hook->sound_up);
		}
		else if (stricmp (self->epair [i].key, "sound_down") == 0)
		{
			hook->sound_down = self->epair [i].value;
			gstate->Precache_Sound (hook->sound_down);
		}
*/		
		i++;
	}

	door_parse_sounds (self);

	hook->max_health = self->health;
	self->solid = SOLID_BSP;
	self->movetype = MOVETYPE_PUSH;
	gstate->SetOrigin (self, self->s.origin [0], self->s.origin [1], self->s.origin [2]);
	gstate->SetModel (self, self->modelName);

	com->SetMovedir (self);

	self->blocked = door_blocked;
	self->use = door_use;

	// get size of entity
	hook->size = Vector (self->absmax) - Vector (self->absmin);

	if (!hook->speed)
		hook->speed = 100.0;
	if (hook->wait == 32767)
		hook->wait = 3;
	if (!hook->damage)
		hook->damage = 2;

	// start position
	hook->pos2 = Vector (self->s.origin);

	// find ending position
	temp = Vector (self->movedir);
	
	// if height is set then movement position is calculated based on the height value
	if (height != 0.0)
	{
		temp = temp * height;
		hook->pos1 = hook->pos2 + temp;
	}
	else
	{
		temp = temp * (self->size [2] - lip);
		// find ending position
		hook->pos1 = hook->pos2 + temp;
	}

	// now flip the start/end positions unless the plat should start in the down position
	if (!(self->spawnflags & PLAT_START_UP))
	{
		gstate->SetOrigin (self, hook->pos1.x, hook->pos1.y, hook->pos1.z);
		hook->state = STATE_BOTTOM;
	}
	else
		hook->state = STATE_TOP;

	hook->move_finished = 0.0;

	if (self->health)
	{
		self->takedamage = DAMAGE_YES;
		self->die = door_killed;
	}
/*
	if (self->items)
		hook->wait = -1;
*/
	if (self->targetname)
		self->touch = NULL;
	else
		self->touch = plat_touch;

	// this must be set up if LinkDoors is not working
	self->owner = self;
	hook->linked_door = NULL;

	if (!self->target && !(self->spawnflags & PLAT_START_UP))
		hook->trigger_field = SpawnPlatTrigger (self);
}

///////////////////////////////////////////////////////////////////////////////
//	func_door_rotate
///////////////////////////////////////////////////////////////////////////////

void	func_door_rotate (userEntity_t *self)
{
	doorHook_t	*hook;
	Vector		vec;
	float		distance = 0;
	int			i;

	self->userHook = com->malloc_hook (sizeof (doorHook_t), MEM_HUNK);
	hook = (doorHook_t *) self->userHook;

	hook->wait = 32767;
	com->ZeroVector (self->movedir);

	i = 0;
	while (self->epair [i].key != NULL)
	{
		if (stricmp (self->epair [i].key, "dmg") == 0)
			hook->damage = atof (self->epair [i].value);
		else if (stricmp (self->epair [i].key, "wait") == 0)
			hook->wait = atof (self->epair [i].value);
		else if (stricmp (self->epair [i].key, "speed") == 0)
			hook->speed = atof (self->epair [i].value);
		else if (stricmp (self->epair [i].key, "distance") == 0)
			distance = atof (self->epair [i].value);
		else if (stricmp (self->epair [i].key, "targetname") == 0)
			self->targetname = self->epair [i].value;
		else if (stricmp (self->epair [i].key, "target") == 0)
			self->target = self->epair [i].value;
		else if (stricmp (self->epair [i].key, "killtarget") == 0)
			self->killtarget = self->epair [i].value;
		else if (!stricmp (self->epair [i].key, "delay"))
			self->delay = atof (self->epair [i].value);
		else if (stricmp (self->epair [i].key, "flag") == 0)
		{
			if (stricmp (self->epair [i].value, "X_AXIS") == 0)
				self->movedir [0] = 1.0;
			else if (stricmp (self->epair [i].value, "Y_AXIS") == 0)
				self->movedir [2] = 1.0;
			else if (stricmp (self->epair [i].value, "Z_AXIS") == 0)
				self->movedir [1] = 1.0;
			else if (stricmp (self->epair [i].value, "AUTO_OPEN") == 0)
				self->spawnflags = (int)self->spawnflags | DOOR_AUTO_OPEN;
			else if (stricmp (self->epair [i].value, "START_OPEN") == 0)
				self->spawnflags = (int)self->spawnflags | DOOR_START_OPEN;
			else if (stricmp (self->epair [i].value, "REVERSE") == 0)
				self->spawnflags = (int)self->spawnflags | ROTATE_REVERSE;
			else if (stricmp (self->epair [i].value, "DOOR_DONT_LINK") == 0)
				self->spawnflags = (int)self->spawnflags | DOOR_START_OPEN;
			else if (stricmp (self->epair [i].value, "DOOR_TOGGLE") == 0)
				self->spawnflags = (int)self->spawnflags | DOOR_TOGGLE;
		}
/*
		else if (stricmp (self->epair [i].key, "sound_open_finish") == 0)
		{
			hook->sound_top = self->epair [i].value;
			if (hook->sound_top)
				gstate->Precache_Sound (hook->sound_top);
		}	
		else if (stricmp (self->epair [i].key, "sound_close_finish") == 0)
		{
			hook->sound_bottom = self->epair [i].value;
			if (hook->sound_bottom)
				gstate->Precache_Sound (hook->sound_bottom);
		}
		else if (stricmp (self->epair [i].key, "sound_opening") == 0)
		{
			hook->sound_up = self->epair [i].value;
			if (hook->sound_up)
				gstate->Precache_Sound (hook->sound_up);
		}
		else if (stricmp (self->epair [i].key, "sound_closing") == 0)
		{
			hook->sound_down = self->epair [i].value;
			if (hook->sound_down)
				gstate->Precache_Sound (hook->sound_down);
		}	
*/
		i++;
	}

	if (distance == 0)
		distance = 90.0;

	door_parse_sounds (self);

	hook->max_health = self->health;
	self->solid = SOLID_BSP;
	self->movetype = MOVETYPE_PUSH;
	gstate->SetOrigin (self, self->s.origin [0], self->s.origin [1], self->s.origin [2]);
	gstate->SetModel (self, self->modelName);
	self->className = "func_door_rotate";

	//self->spawnflags = self->spawnflags | DOOR_USE_TO_CLOSE;

	// precache the null sound so it can be used to cut off a sound playing when
	// a door opens but has no sound_top
	gstate->Precache_Sound ("null.wav");

	if (!hook->speed)
		hook->speed = 100.0;
	if (hook->wait == 32767)
		hook->wait = 3;
	if (!hook->damage)
		hook->damage = 2;

	if ((int)self->spawnflags & ROTATE_X_AXIS)
		self->movedir [2] = 1.0;
	else if ((int)self->spawnflags & ROTATE_Y_AXIS)
		self->movedir [0] = 1.0;
	else
		self->movedir [1] = 1.0;

	if ((int)self->spawnflags & ROTATE_REVERSE)
	{
		vec = Vector (self->movedir) * -1.0;
		vec.set (self->movedir);
	}

	hook->pos1 = Vector (self->s.angles);
	hook->pos2 = hook->pos1 + Vector (self->movedir) * distance;
//	hook->no_more_move_damnit = FALSE;

	// FIXME: set key types

	if (self->spawnflags & ROTATE_START_OPEN)
	{
		hook->pos2.set (self->s.angles);

		vec = Vector (self->movedir) * -1.0;
		vec.set (self->movedir);
		hook->state = STATE_TOP;
	}
	else
		hook->state = STATE_BOTTOM;

	if (self->health)
	{
		self->takedamage = DAMAGE_YES;
		self->die = door_killed;
	}
/*
	if (self->items)
		hook->wait = -1;
*/

	if (!self->targetname && (self->spawnflags & ROTATE_AUTO_OPEN))
		self->touch = door_touch;
	else
		self->touch = NULL;

	self->blocked = door_blocked;
	self->use = door_use;
	hook->move_finished = 0.0;

	// this must be set up if LinkDoors is not working
	self->owner = self;
	hook->linked_door = NULL;

	self->think = door_init;
	self->nextthink = gstate->time + 0.1;
}

///////////////////////////////////////////////////////////////////////////////
//	func_door_rotating
//
//	for compatibility with Q2 rotating doors
///////////////////////////////////////////////////////////////////////////////

#define	Q2_FDR_START_OPEN	1
#define	Q2_FDR_REVERSE		2
#define	Q2_FDR_TOGGLE		32
#define	Q2_FDR_X_AXIS		64
#define	Q2_FDR_Y_AXIS		128

void	func_door_rotating (userEntity_t *self)
{
	unsigned	long	sf = 0;

	if (self->spawnflags & Q2_FDR_START_OPEN)
		sf |= ROTATE_START_OPEN;
	if (self->spawnflags & Q2_FDR_REVERSE)
		sf |= ROTATE_REVERSE;
	if (self->spawnflags & Q2_FDR_TOGGLE)
		sf |= ROTATE_TOGGLE;
	if (self->spawnflags & Q2_FDR_X_AXIS)
		sf |= ROTATE_X_AXIS;
	if (self->spawnflags & Q2_FDR_Y_AXIS)
		sf |= ROTATE_Y_AXIS;

	self->spawnflags = sf;

	func_door_rotate (self);
}

/*
============
rotate_blocked

damages the blocking entity  
============
*/

void	rotate_blocked (userEntity_t *self, userEntity_t *other)
{
	doorHook_t	*hook = (doorHook_t *) self->userHook;
	
	if (!com->ValidTouch (self, other))
		return;

	com->Damage (other, self, self, zero_vector, zero_vector, hook->damage, DAMAGE_SQUISH | DAMAGE_NO_BLOOD);
}

/*
============
func_rotate

spawns a rotating brush

//FIXME: make rotation start on trigger when fired as a target
============
*/

void func_rotate(userEntity_t *self)
{
	float       speed = 100.0;
	int			i;
	doorHook_t	*hook;
	Vector		temp;


	self->userHook = com->malloc_hook (sizeof (doorHook_t), MEM_HUNK); 
	hook = (doorHook_t *) self->userHook;

	com->ZeroVector (self->movedir);

	i = 0;
	while (self->epair [i].key != NULL)
	{
		if (stricmp (self->epair [i].key, "dmg") == 0)
			hook->damage = atof (self->epair [i].value);
		else if (stricmp (self->epair [i].key, "speed") == 0)
		{
			hook->speed = atof (self->epair [i].value);
			speed = hook->speed;
		}
		else if (stricmp (self->epair [i].key, "targetname") == 0)
			self->targetname = self->epair [i].value;
		else if (stricmp (self->epair [i].key, "target") == 0)
			self->target = self->epair [i].value;
		else if (stricmp (self->epair [i].key, "killtarget") == 0)
			self->killtarget = self->epair [i].value;
		else if (!stricmp (self->epair [i].key, "delay"))
			self->delay = atof (self->epair [i].value);
		else if (stricmp (self->epair [i].key, "flag") == 0)
		{
			if (stricmp (self->epair [i].value, "X_AXIS") == 0)
				self->movedir [0] = 1.0;
			else if (stricmp (self->epair [i].value, "Y_AXIS") == 0)
				self->movedir [2] = 1.0;
			else if (stricmp (self->epair [i].value, "Z_AXIS") == 0)
				self->movedir [1] = 1.0;
		}

		i++;
	}

	// set axis of rotation
 	if (self->spawnflags & 4)
	{
		self->movedir [2] = 1.0;
	}
	else if (self->spawnflags & 8)
	{
		self->movedir [0] = 1.0;
	}
	else
	{
		self->movedir [1] = 1.0;
	}

	// check for reverse rotation
	if (self->spawnflags & 2)
	{
		temp = Vector (self->movedir) * -1.0;
		temp.set (self->movedir);
	}

	self->solid    = SOLID_BSP;
	self->movetype = MOVETYPE_PUSH;
	self->className = "func_rotate";
	
    // these two are required
	gstate->SetOrigin(self, self->s.origin[0], self->s.origin[1], self->s.origin[2]);
	gstate->SetModel(self,  self->modelName);

	// start rotating
	temp = Vector (self->movedir) * speed;
	temp.set (self->avelocity);
	
	self->nextthink = gstate->time + 1000000.0;
	self->blocked = rotate_blocked;
}

///////////////////////////////////////////////////////////////////////////////
///  Miscellaneous functions
///////////////////////////////////////////////////////////////////////////////
/*
void func_wall_use (userEntity_t *self, userEntity_t *other, userEntity_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;
	}
	
	gstate->LinkEntity(self);

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

///////////////////////////////////////////////////////////////////////////////
//
///////////////////////////////////////////////////////////////////////////////

#define	FUNCWALL_TRIGGER_SPAWN	0x01
#define	FUNCWALL_TOGGLE			0x02
#define	FUNCWALL_START_ON		0x04
#define	FUNCWALL_ANIM_ALL		0x08
#define	FUNCWALL_ANIM_FAST		0x10

void func_wall (edict_t *self)
{
	int		i;

	//	get	epairs
	for (i = 0; self->epair [i].key; i++)
	{
		if (!stricmp (self->epair [i].key, "target"))
			self->target = self->epair [i].value;
		else if (!stricmp (self->epair [i].key, "targetname"))
			self->targetname = self->epair [i].value;
		else if (!stricmp (self->epair [i].key, "killtarget"))
			self->killtarget = self->epair [i].value;
	}

	self->movetype = MOVETYPE_PUSH;
	gstate->SetModel (self, self->modelName);

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

	// just a wall
	if ((self->spawnflags & (FUNCWALL_TRIGGER_SPAWN | FUNCWALL_TOGGLE | FUNCWALL_START_ON)) == 0)
	{
		self->solid = SOLID_BSP;
		gstate->LinkEntity (self);
		return;
	}

	// it must be TRIGGER_SPAWN
	if (!(self->spawnflags & FUNCWALL_TRIGGER_SPAWN))
	{
//		gi.dprintf("func_wall missing TRIGGER_SPAWN\n");
		self->spawnflags |= FUNCWALL_TRIGGER_SPAWN;
	}

	// yell if the spawnflags are odd
	if (self->spawnflags & FUNCWALL_START_ON)
	{
		if (!(self->spawnflags & FUNCWALL_TOGGLE))
		{
			gstate->Con_Dprintf ("func_wall START_ON without TOGGLE\n");
			self->spawnflags |= FUNCWALL_TOGGLE;
		}
	}

	self->use = func_wall_use;
	
	if (self->spawnflags & FUNCWALL_START_ON)
	{
		self->solid = SOLID_BSP;
	}
	else
	{
		self->solid = SOLID_NOT;
		self->svflags |= SVF_NOCLIENT;
	}
	gstate->LinkEntity (self);
}
*/
///////////////////////////////////////////////////////////////////////////////
//	func_wall_use
///////////////////////////////////////////////////////////////////////////////

void	func_wall_use (userEntity_t *self, userEntity_t *other, userEntity_t *activator)
{
//	self->frameIndex = 1 - self->frameIndex;

	com->UseTargets (self, other, activator);


	gstate->RemoveEntity (self);
//	self->modelName = NULL;
//	gstate->SetModel (self, self->modelName);
}

///////////////////////////////////////////////////////////////////////////////
//	func_wall_die
///////////////////////////////////////////////////////////////////////////////

void	func_wall_die (userEntity_t *self, userEntity_t *inflictor, userEntity_t *attacker, int damage, vec3_t point)
{
	func_wall_use (self, attacker, attacker);
}

///////////////////////////////////////////////////////////////////////////////
//	func_wall
///////////////////////////////////////////////////////////////////////////////

void	func_wall (userEntity_t *self)
{
	int			i;
	doorHook_t	*hook;

	self->userHook = com->malloc_hook (sizeof (doorHook_t), MEM_HUNK); 
	hook = (doorHook_t *) self->userHook;

	com->ZeroVector (self->movedir);

	i = 0;
	while (self->epair [i].key != NULL)
	{
		if (stricmp (self->epair [i].key, "health") == 0)
			self->health = atof (self->epair [i].value);
		else if (stricmp (self->epair [i].key, "targetname") == 0)
			self->targetname = self->epair [i].value;
		else if (stricmp (self->epair [i].key, "target") == 0)
			self->target = self->epair [i].value;
		else if (stricmp (self->epair [i].key, "killtarget") == 0)
			self->killtarget = self->epair [i].value;
		else if (!stricmp (self->epair [i].key, "delay"))
			self->delay = atof (self->epair [i].value);

		i++;
	}

	com->ZeroVector (self->s.angles);
	
	self->movetype = MOVETYPE_PUSH;
	self->solid = SOLID_BSP;
	self->use = func_wall_use;
	self->die = func_wall_die;
	
	gstate->SetModel (self, self->modelName);
}

/* ----------------------------- func_train ------------------------------- */

void	train_next		(userEntity_t *self);
void	train_die_next (userEntity_t *self, userEntity_t *inflictor, userEntity_t *attacker, int damage, vec3_t point);

void	train_find		(userEntity_t *self);
void	train_wait		(userEntity_t *self);

void	train_rotate_think (userEntity_t *self);

///////////////////////////////////////////////////////////////////////////////
//	train_blocked
///////////////////////////////////////////////////////////////////////////////

void	train_blocked (userEntity_t *self, userEntity_t *other)
{
	doorHook_t	*hook = (doorHook_t *) self->userHook;

	if (gstate->time < hook->move_finished)
		return;

	hook->move_finished = gstate->time + 0.5;

	com->Damage (other, self, self, zero_vector, zero_vector, hook->damage, DAMAGE_SQUISH | DAMAGE_NO_BLOOD);
}

///////////////////////////////////////////////////////////////////////////////
//	train_use
///////////////////////////////////////////////////////////////////////////////

void	train_use (userEntity_t *self, userEntity_t *other, userEntity_t *activator)
{
	doorHook_t	*hook = (doorHook_t *) self->userHook;

	if (self->think != train_find && !(self->goalentity->spawnflags & WAITFORTRIGGER) && !self->targetname)
		return;

	if ((self->goalentity->spawnflags & WAITFORTRIGGER) && self->think != NULL)
		return;

	train_next (self);
}

///////////////////////////////////////////////////////////////////////////////
//	train_check_rotation
///////////////////////////////////////////////////////////////////////////////

void	train_check_rotation (userEntity_t *train, userEntity_t *path_corner, int check_targetname)
{
	doorHook_t	*hook = (doorHook_t *) train->userHook;
	doorHook_t	*phook = (doorHook_t *) path_corner->userHook;
	int			go_to_think;
	float		sign, last_think_time, move_time, shortest_rotation;

	go_to_think = FALSE;
	last_think_time = train->nextthink;

	//////////////////////////////////////////////
	// start a continual rotation about the axis
	//////////////////////////////////////////////
	if ((int)path_corner->spawnflags & X_AXIS)
		train->avelocity [2] = phook->x_speed;
	else
		train->avelocity [2] = 0;

	if ((int)path_corner->spawnflags & Y_AXIS)
		train->avelocity [0] = phook->y_speed;
	else
		train->avelocity [0] = 0;

	if ((int)path_corner->spawnflags & Z_AXIS)
		train->avelocity [1] = phook->z_speed;
	else
		train->avelocity [1] = 0;

	phook->end_x_rotation_time = phook->end_y_rotation_time = phook->end_z_rotation_time = 0;
	shortest_rotation = 0;

	///////////////////////////////////////////////////
	// check for a rotation around x axis
	//
	// x_distance is degrees to rotate on x axis
	// x_speed is speed degrees per second to rotate
	///////////////////////////////////////////////////
	
	if (phook->x_distance)
	{
		// if no x_speed is specified, and there is movement from one path to the next
		// then calculate the angular velocity to complete rotation when the move completes
		//	if (phook->x_speed == 0 && hook->end_move_time >= train->ltime)
		if (phook->x_speed == 0 && hook->end_move_time >= gstate->time)
		{
			//	move_time = hook->end_move_time - train->ltime;
			move_time = hook->end_move_time - gstate->time;
			phook->x_speed = phook->x_distance / move_time;
		}
		else if (phook->x_speed == 0) phook->x_speed = 90.0;

		// find time from start rotation to end of rotation (in bmodels local time)
		//	hook->end_x_rotation_time = train->ltime + (fabs (phook->x_distance) / phook->x_speed);
		hook->end_x_rotation_time = gstate->time + (fabs (phook->x_distance) / phook->x_speed);
		last_think_time = hook->end_x_rotation_time;
		if (hook->end_x_rotation_time < shortest_rotation || shortest_rotation == 0)
			shortest_rotation = hook->end_x_rotation_time;
		
		if (phook->x_distance > 0) sign = 1; else sign = -1;
		train->avelocity [2] = phook->x_speed * sign;
		
		// final angle after rotation
		hook->end_x_angle = com->AngleMod (train->s.angles [2] + phook->x_distance);
		hook->x_distance = phook->x_distance;
		
		if ((!train->targetname && check_targetname) || (!check_targetname))
			go_to_think = TRUE;
	}
	else hook->x_distance = 0;

	///////////////////////////////////////////////////
	// check for a rotation around y axis
	//
	// y_distance is degrees to rotate on y axis
	// y_speed is speed degrees per second to rotate
	///////////////////////////////////////////////////

	if (phook->y_distance)
	{
		// if no y_speed is specified, and there is movement from one path to the next
		// then calculate the angular velocity to complete rotation when the move completes
		//	if (phook->y_speed == 0 && hook->end_move_time >= train->ltime)
		if (phook->y_speed == 0 && hook->end_move_time >= gstate->time)
		{
			//	move_time = hook->end_move_time - train->ltime;
			move_time = hook->end_move_time - gstate->time;
			phook->y_speed = phook->y_distance / move_time;
		}
		else if (phook->y_speed == 0) phook->y_speed = 90.0;

		//	hook->end_y_rotation_time = train->ltime + (fabs (phook->y_distance) / phook->y_speed);
		hook->end_y_rotation_time = gstate->time + (fabs (phook->y_distance) / phook->y_speed);
		if (hook->end_y_rotation_time > last_think_time)
			last_think_time = hook->end_y_rotation_time;		
		if (hook->end_y_rotation_time < shortest_rotation || shortest_rotation == 0)
			shortest_rotation = hook->end_y_rotation_time;

		if (phook->y_distance > 0)
			sign = 1;
		else 
			sign = -1;
		train->avelocity [0] = phook->y_speed * sign;
		
		hook->end_y_angle = com->AngleMod (train->s.angles [0] + phook->y_distance);
		hook->y_distance = phook->y_distance;
		if ((!train->targetname && check_targetname) || (!check_targetname))		
			go_to_think = TRUE;
	}
	else hook->y_distance = 0;

	///////////////////////////////////////////////////
	// check for a rotation around z axis
	//
	// z_distance is degrees to rotate on z axis
	// z_speed is speed degrees per second to rotate
	///////////////////////////////////////////////////

	if (phook->z_distance)
	{
		// if no z_speed is specified, and there is movement from one path to the next
		// then calculate the angular velocity to complete rotation when the move completes
		//	if (phook->z_speed == 0 && hook->end_move_time >= train->ltime)
		if (phook->z_speed == 0 && hook->end_move_time >= gstate->time)
		{
			//	move_time = hook->end_move_time - train->ltime;
			move_time = hook->end_move_time - gstate->time;
			phook->z_speed = phook->z_distance / move_time;
		}
		else if (phook->z_speed == 0) phook->z_speed = 90.0;

		//	hook->end_z_rotation_time = train->ltime + (fabs (phook->z_distance) / phook->z_speed);
		hook->end_z_rotation_time = gstate->time + (fabs (phook->z_distance) / phook->z_speed);
		if (hook->end_z_rotation_time > last_think_time)
			last_think_time = hook->end_z_rotation_time;		
		if (hook->end_z_rotation_time < shortest_rotation || shortest_rotation == 0)
			shortest_rotation = hook->end_z_rotation_time;

		if (phook->z_distance > 0)
			sign = 1;
		else 
			sign = -1;

		train->avelocity [1] = phook->z_speed * sign;
		hook->end_z_angle = com->AngleMod (train->s.angles [1] + phook->z_distance);
	
		hook->z_distance = phook->z_distance;
		if ((!train->targetname && check_targetname) || (!check_targetname))
			go_to_think = TRUE;
	}
	else hook->z_distance = 0;

	//	make sure that if the end of movement is the first thing to occur that it
	//	gets done at the right time
	//	if (hook->end_move_time < shortest_rotation && hook->end_move_time > train->ltime)
	if (hook->end_move_time < shortest_rotation && hook->end_move_time > gstate->time)
		shortest_rotation = hook->end_move_time;

	if (go_to_think)
	{
		train->nextthink = shortest_rotation;
		train->think = train_rotate_think;
	}
}

///////////////////////////////////////////////////////////////////////////////
//	train_die
///////////////////////////////////////////////////////////////////////////////

void	train_die (userEntity_t *self, userEntity_t *inflictor, userEntity_t *attacker, int damage, vec3_t point)
{
	Vector	org;
	doorHook_t		*hook = (doorHook_t *) self->userHook;

	//	Q2FIXME:	attacker == other???
	com->UseTargets (self->goalentity, attacker, attacker);

	org = Vector (self->goalentity->s.origin);
	com_CalcMove (self, org, hook->speed, train_wait);

	train_check_rotation (self, self->goalentity, FALSE);
}

///////////////////////////////////////////////////////////////////////////////
//	train_next
//
//	find the next path_corner for func_train self
//	Use the current path_corner's values for control, etc.
//	the next path_corner should only be used as a destination origin until it is reached
///////////////////////////////////////////////////////////////////////////////

void	train_next (userEntity_t *self)
{
	userEntity_t	*targ;
	userEntity_t	*cur_path = self->goalentity;
	doorHook_t		*hook = (doorHook_t *) self->userHook;
	doorHook_t		*path_hook = (doorHook_t *) cur_path->userHook;
	doorHook_t		*targ_hook;
	Vector			org, temp;

	// reached the end of the path, nothing else to do
	if (hook->wait == -1)
		return;

	if (cur_path->target)
	{
		targ = gstate->FirstEntity ();

		while (targ)
		{
			if (targ->targetname)
			{
				if (stricmp (cur_path->target, targ->targetname) == 0)
				{
					if (targ->className)
					{
						if (stricmp (targ->className, "path_corner_train") == 0)
							break;
					}
				}
			}
	
			targ = gstate->NextEntity (targ);
		}

		if (!targ)
			return;

		targ_hook = (doorHook_t *) targ->userHook;
	
		// store for future use	
		self->goalentity = targ;
	}
	else
		targ = NULL;

	if (path_hook->wait)
		hook->wait = path_hook->wait;
	else
		hook->wait = 0;

	// if there is a sound on the current path_corner, play it
	if (path_hook->sound_up)
		gstate->PositionedSound (self->s.origin, self, CHAN_OVERRIDE, path_hook->sound_up, 1.0, ATTN_NORM, 0.0);

	// set the func_train's speed to the current path_corner's speed
	if (path_hook->speed)
		hook->speed = path_hook->speed;

	// make func_train's delay == path_corner's
	self->delay = cur_path->delay;

	// clear rotation flags on func_train
	if (self->spawnflags & X_AXIS)
		self->spawnflags = (int)self->spawnflags - X_AXIS;
	if (self->spawnflags & Y_AXIS)
		self->spawnflags = (int)self->spawnflags - Y_AXIS;
	if (self->spawnflags & Z_AXIS)
		self->spawnflags = (int)self->spawnflags - Z_AXIS;

	// set func_train's rotation flags to path_corner's
	if (cur_path->spawnflags & X_AXIS)
		self->spawnflags = (int)self->spawnflags | X_AXIS;
	if (cur_path->spawnflags & Y_AXIS)
		self->spawnflags = (int)self->spawnflags | Y_AXIS;
	if (cur_path->spawnflags & Z_AXIS)
		self->spawnflags = (int)self->spawnflags | Z_AXIS;

	// the the current path_corner has a health, then set it up 
	// so that it must be killed to activate
	if (cur_path->health)
	{
		self->health = targ->health;
		self->die = train_die;
		self->think = NULL;
		self->takedamage = DAMAGE_YES;
	}
	// otherwise, start the train moving
	else
	{
		// can't kill a train while it's moving
		self->takedamage = DAMAGE_NO;

		// use the current path_corner's targets (kill targets)
		//	Q2FIXME:	activator == self?
		com->UseTargets (cur_path, self, self);

		// if current path_corner has a target, then move to it
		if (targ)
		{
			org = Vector (targ->s.origin);
			com_CalcMove (self, org, hook->speed, train_wait);
		}

		// check rotation even if there is no target because rotation 
		// doesn't require a target path_corner
		train_check_rotation (self, cur_path, FALSE);
	}
}

///////////////////////////////////////////////////////////////////////////////
//	train_die_next
///////////////////////////////////////////////////////////////////////////////

void	train_die_next (userEntity_t *self, userEntity_t *inflictor, userEntity_t *attacker, int damage, vec3_t point)
{
	train_next (self);
}

///////////////////////////////////////////////////////////////////////////////
//	train_wait
///////////////////////////////////////////////////////////////////////////////

void	train_wait (userEntity_t *self)
{
	doorHook_t	*hook = (doorHook_t *) self->userHook;

	// if the path_corner the train is coming from has a wait
	// on it, then wait that amount of time before going to
	// the next path_corner
	if (hook->wait > 0)
	{
		com->ZeroVector (self->avelocity);
		//	self->nextthink = self->ltime + hook->wait;
		self->nextthink = gstate->time + hook->wait;
		self->think = train_next;
		
		return;
	}
	else if (self->goalentity->spawnflags & WAITFORTRIGGER)
	{
		// reassign the train's target to next path_corner
		self->target = self->goalentity->target;
		self->think = NULL;
		return;
	}

	train_next (self);
}

///////////////////////////////////////////////////////////////////////////////
//	train_rotate_think
///////////////////////////////////////////////////////////////////////////////

void	train_rotate_think (userEntity_t *self)
{
	doorHook_t		*hook = (doorHook_t *) self->userHook;
	float			next_think;

	//	if (self->ltime >= hook->end_x_rotation_time && !(self->spawnflags & X_AXIS) &&
	if (gstate->time >= hook->end_x_rotation_time && !(self->spawnflags & X_AXIS) &&
		hook->x_distance != 0)
	{
		self->avelocity [2] = 0;
		self->s.angles [2] = hook->end_x_angle;
		hook->end_x_rotation_time = 0;
	}
	
	//	if (self->ltime >= hook->end_y_rotation_time && !(self->spawnflags & Y_AXIS) &&
	if (gstate->time >= hook->end_y_rotation_time && !(self->spawnflags & Y_AXIS) &&
		hook->y_distance != 0)
	{
		self->avelocity [0] = 0;
		self->s.angles [0] = hook->end_y_angle;
		hook->end_y_rotation_time = 0;
	}
	
	//	if (self->ltime >= hook->end_z_rotation_time && !(self->spawnflags & Z_AXIS) &&
	if (gstate->time >= hook->end_z_rotation_time && !(self->spawnflags & Z_AXIS) &&
		hook->z_distance != 0)
	{
		self->avelocity [1] = 0;
		self->s.angles [1] = hook->end_z_angle;
		hook->end_z_rotation_time = 0;
	}

	//	if (self->ltime >= hook->end_move_time)
	if (gstate->time >= hook->end_move_time)
	{
		gstate->SetOrigin (self, hook->final_dest.x, hook->final_dest.y, hook->final_dest.z);

		com->ZeroVector (self->velocity);
	}

	next_think = hook->end_x_rotation_time;

	if (next_think < hook->end_y_rotation_time)
		next_think = hook->end_y_rotation_time;
	if (next_think < hook->end_z_rotation_time)
		next_think = hook->end_z_rotation_time;
	if (next_think < hook->end_move_time)
		next_think = hook->end_move_time;

	self->nextthink = next_think;
	self->think = train_rotate_think;

	// if all movement and rotation is complete, go to train_wait
	//	if (self->ltime >= hook->end_move_time && self->ltime >= hook->end_x_rotation_time &&
	//	self->ltime >= hook->end_y_rotation_time && self->ltime >= hook->end_z_rotation_time)
	if (gstate->time >= hook->end_move_time && gstate->time >= hook->end_x_rotation_time &&
		gstate->time >= hook->end_y_rotation_time && gstate->time >= hook->end_z_rotation_time)
		train_wait (self);
}

///////////////////////////////////////////////////////////////////////////////
//	train_find
//
//	find the first path corner that the train points to and
//	move the train's origin to it.  Point self->goalentity to
//	the current path_corner
//
//	if the train is not targeted, then go ahead and call train_next
//	to use the current path_corner and move to the next
///////////////////////////////////////////////////////////////////////////////

void	train_find (userEntity_t *self)
{
	doorHook_t		*hook = (doorHook_t *) self->userHook;
	doorHook_t		*thook;
	userEntity_t	*targ;
	Vector			org;

	if (!self->target)
		return;

	targ = gstate->FirstEntity ();

	while (targ)
	{
		if (targ->targetname)
		{
			if (stricmp (self->target, targ->targetname) == 0)
			{
				if (targ->className)
				{
					if (stricmp (targ->className, "path_corner_train") == 0)
						break;
				}
			}
		}

		targ = gstate->NextEntity (targ);
	}

	if (!targ)
	{
		gstate->Con_Printf ("train has no first target\n");
		return;
	}

	// point to path_corner for future reference
	self->goalentity = targ;
	gstate->SetOrigin2 (self, targ->s.origin);

	thook = (doorHook_t *) targ->userHook;

	self->delay = targ->delay;

	if (targ->health)
	{
		self->health = targ->health;
		self->die = train_die_next;
		self->takedamage = DAMAGE_YES;
		self->think = NULL;
	}
	
	if ((!self->targetname && !(targ->spawnflags & WAITFORTRIGGER)) || self->targetname && self->health)
	{
		train_next (self);

		train_check_rotation (self, targ, TRUE);
	}
	else
		self->think = NULL;
}

///////////////////////////////////////////////////////////////////////////////
//	func_train
///////////////////////////////////////////////////////////////////////////////

void	func_train (userEntity_t *self)
{
	int			i;
	doorHook_t	*hook;
	vec3_t		mins, maxs, org;

	self->userHook = com->malloc_hook (sizeof (doorHook_t), MEM_HUNK); 
	hook = (doorHook_t *) self->userHook;

	com->ZeroVector (self->movedir);
	self->health = 0;

	hook->sound_up = NULL;
	hook->sound_down = NULL;
	hook->sound_top = NULL;
	hook->sound_bottom = NULL;

	i = 0;
	while (self->epair [i].key != NULL)
	{
		//gstate->Con_Printf ("key = %s, value = %s\n", self->epair [i].key, self->epair [i].value);

		if (!stricmp (self->epair [i].key, "dmg"))
			hook->damage = atof (self->epair [i].value);
		else if (!stricmp (self->epair [i].key, "wait"))
			hook->wait = atof (self->epair [i].value);
		else if (!stricmp (self->epair [i].key, "speed"))
			hook->speed = atof (self->epair [i].value);
		else if (!stricmp (self->epair [i].key, "targetname"))
			self->targetname = self->epair [i].value;
		else if (!stricmp (self->epair [i].key, "target"))
			self->target = self->epair [i].value;
		else if (!stricmp (self->epair [i].key, "killtarget"))
			self->killtarget = self->epair [i].value;
		else if (!stricmp (self->epair [i].key, "x_distance"))
			hook->x_distance = atof (self->epair [i].value);
		else if (!stricmp (self->epair [i].key, "y_distance"))
			hook->y_distance = atof (self->epair [i].value);
		else if (!stricmp (self->epair [i].key, "z_distance"))
			hook->z_distance = atof (self->epair [i].value);
		else if (!stricmp (self->epair [i].key, "x_speed"))
			hook->x_speed = atof (self->epair [i].value);
		else if (!stricmp (self->epair [i].key, "y_speed"))
			hook->y_speed = atof (self->epair [i].value);
		else if (!stricmp (self->epair [i].key, "z_speed"))
			hook->z_speed = atof (self->epair [i].value);
		else if (!stricmp (self->epair [i].key, "delay"))
			self->delay = atof (self->epair [i].value);
		else if (!stricmp (self->epair [i].key, "flag"))
		{
			if (!stricmp (self->epair [i].value, "X_AXIS"))
				self->flags = (int)self->flags | X_AXIS;
			else if (!stricmp (self->epair [i].value, "Y_AXIS"))
				self->flags = (int)self->flags | Y_AXIS;
			else if (!stricmp (self->epair [i].value, "Z_AXIS"))
				self->flags = (int)self->flags | Z_AXIS;
		}
		i++;
	}

	if (!hook->speed)
		hook->speed = 100;
	if (!self->target)
		com->Warning ("func_train %s has no target\n", self->targetname);
	if (!hook->damage)
		hook->damage = 2;

	hook->count = 1;
	self->solid = SOLID_BSP;
	self->movetype = MOVETYPE_PUSH;
	self->blocked = train_blocked;
	self->takedamage = DAMAGE_NO;
	self->use = train_use;

	gstate->SetOrigin2 (self, self->s.origin);
	gstate->SetModel (self, self->modelName);

	// start trains on the second frame, to make sure their targets
	// have been spawned

	//	self->nextthink = self->ltime + 0.1;
	self->nextthink = gstate->time + 0.1;
	self->think = train_find;
}

///////////////////////////////////////////////////////////////////////////////
//	path_corner_train
///////////////////////////////////////////////////////////////////////////////

void	path_corner_train (userEntity_t *self)
{
	doorHook_t		*hook;
	int				i;

	self->userHook = com->malloc_hook (sizeof (doorHook_t), MEM_HUNK); 
	hook = (doorHook_t *) self->userHook;

	hook->sound_up = NULL;
	hook->sound_down = NULL;
	hook->sound_top = NULL;
	hook->sound_bottom = NULL;

	i = 0;
	while (self->epair [i].key != NULL)
	{
		if (stricmp (self->epair [i].key, "targetname") == 0)
			self->targetname = self->epair [i].value;
		else if (stricmp (self->epair [i].key, "target") == 0)
			self->target = self->epair [i].value;
		else if (stricmp (self->epair [i].key, "wait") == 0)
			hook->wait = atof (self->epair [i].value);
		else if (stricmp (self->epair [i].key, "killtarget") == 0)
			self->killtarget = self->epair [i].value;
		else if (stricmp (self->epair [i].key, "delay") == 0)
			self->delay = atof (self->epair [i].value);
/*
		else if (stricmp (self->epair [i].key, "sound") == 0)
		{
			hook->sound_up = self->epair [i].value;
			if (hook->sound_up)
				gstate->Precache_Sound (hook->sound_up);
		}
*/
		else if (stricmp (self->epair [i].key, "speed") == 0)
			hook->speed = atof (self->epair [i].value);
		else if (stricmp (self->epair [i].key, "health") == 0)
			self->health = atof (self->epair [i].value);
		else if (!stricmp (self->epair [i].key, "x_distance"))
			hook->x_distance = atof (self->epair [i].value);
		else if (!stricmp (self->epair [i].key, "y_distance"))
			hook->y_distance = atof (self->epair [i].value);
		else if (!stricmp (self->epair [i].key, "z_distance"))
			hook->z_distance = atof (self->epair [i].value);
		else if (!stricmp (self->epair [i].key, "x_speed"))
			hook->x_speed = atof (self->epair [i].value);
		else if (!stricmp (self->epair [i].key, "y_speed"))
			hook->y_speed = atof (self->epair [i].value);
		else if (!stricmp (self->epair [i].key, "z_speed"))
			hook->z_speed = atof (self->epair [i].value);
		else if (!stricmp (self->epair [i].key, "flag"))
		{
			if (!stricmp (self->epair [i].value, "X_AXIS"))
				self->flags = (int)self->flags | X_AXIS;
			else if (!stricmp (self->epair [i].value, "Y_AXIS"))
				self->flags = (int)self->flags | Y_AXIS;
			else if (!stricmp (self->epair [i].value, "Z_AXIS"))
				self->flags = (int)self->flags | Z_AXIS;
		}

		i++;
	}

	door_parse_sounds (self);

	self->flags = self->flags & FL_IGNOREUSETARGET;
}

///////////////////////////////////////////////////////////////
//	func_button routines
///////////////////////////////////////////////////////////////

void	button_return (userEntity_t *self);

///////////////////////
//	button_wait
///////////////////////

void	button_wait (userEntity_t *self)
{
	doorHook_t	*hook = (doorHook_t *) self->userHook;

	hook->state = STATE_TOP;
	//	self->nextthink = self->ltime + hook->wait;
	self->nextthink = gstate->time + hook->wait;
	self->think = button_return;

	//	Q2: now done in button_fire
	//	com->UseTargets (self, other, activator);

	self->s.frame = 1;
}

///////////////////////
//	button_done
///////////////////////

void	button_done (userEntity_t *self)
{
	doorHook_t	*hook = (doorHook_t *) self->userHook;

	hook->state = STATE_BOTTOM;
}

///////////////////////
//	button_return
///////////////////////

void	button_return (userEntity_t *self)
{
	doorHook_t	*hook = (doorHook_t *) self->userHook;

	com_CalcMove (self, hook->pos1, hook->speed, button_done);

	if (hook->sound_down)
		gstate->PositionedSound (self->s.origin, self, CHAN_OVERRIDE, hook->sound_up, 1.0, ATTN_NORM, 0.0);

	self->s.frame = 0;

	if (self->health)
		self->takedamage = DAMAGE_YES;
}

///////////////////////
//	button_blocked
///////////////////////

void	button_blocked (userEntity_t *self, userEntity_t *other)
{
}

///////////////////////
//	button_fire
///////////////////////

void	button_fire (userEntity_t *self, userEntity_t *other, userEntity_t *activator)
{
	doorHook_t	*hook = (doorHook_t *) self->userHook;
	
	if  (hook->state == STATE_UP || hook->state == STATE_TOP)
		return;

	if (hook->sound_up)
		gstate->PositionedSound (self->s.origin, self, CHAN_OVERRIDE, hook->sound_up, 1.0, ATTN_NORM, 0.0);

	hook->state = STATE_UP;

	//	Q2:	use targets on button activation, not button wait
	com->UseTargets (self, other, activator);

	com_CalcMove (self, hook->pos2, hook->speed, button_wait);
}

///////////////////////
//	button_use
///////////////////////

//	Q2FIXME:	activator?!?!?

void	button_use (userEntity_t *self, userEntity_t *other, userEntity_t *activator)
{
	doorHook_t	*hook = (doorHook_t *) self->userHook;

	hook->activator = gstate->activator;

	button_fire (self, other, activator);
}

///////////////////////
//	button_touch
///////////////////////

void	button_touch (userEntity_t *self, userEntity_t *other, cplane_t *plane, csurface_t *surf)
{
	doorHook_t	*hook = (doorHook_t *) self->userHook;

	if (!com->ValidTouch (self, other))
		return;

	hook->activator = other;

	button_fire (self, other, other);
}

///////////////////////
//	button_killed
///////////////////////

void	button_killed (userEntity_t *self, userEntity_t *inflictor, userEntity_t *attacker, int damage, vec3_t point)
{
	doorHook_t	*hook = (doorHook_t *) self->userHook;
		
	hook->activator = gstate->attacker;
	self->health = hook->max_health;
	self->takedamage = DAMAGE_NO;

	//	Q2FIXME:	other == attacker ?
	button_fire (self, attacker, attacker);
}

///////////////////////
//	func_button
///////////////////////

void	func_button (userEntity_t *self)
{
	float			gtemp, ftemp, lip;
	doorHook_t	*hook;
	Vector			temp;
	float			distance;
	int				i;

	self->userHook = com->malloc_hook (sizeof (doorHook_t), MEM_HUNK); 
	hook = (doorHook_t *) self->userHook;

	temp = Vector (0.0, 1.0, 0.0);
	temp.set (self->movedir);
	lip = 0;

	i = 0;
	while (self->epair [i].key != NULL)
	{
		if (stricmp (self->epair [i].key, "health") == 0)
			self->health = atof (self->epair [i].value);
		else if (stricmp (self->epair [i].key, "wait") == 0)
			hook->wait = atof (self->epair [i].value);
		else if (stricmp (self->epair [i].key, "speed") == 0)
			hook->speed = atof (self->epair [i].value);
		else if (stricmp (self->epair [i].key, "targetname") == 0)
			self->targetname = self->epair [i].value;
		else if (stricmp (self->epair [i].key, "target") == 0)
			self->target = self->epair [i].value;
		else if (stricmp (self->epair [i].key, "killtarget") == 0)
			self->killtarget = self->epair [i].value;
		else if (!stricmp (self->epair [i].key, "delay"))
			self->delay = atof (self->epair [i].value);
		else if (stricmp (self->epair [i].key, "sound_use") == 0)
		{
			hook->sound_top = gstate->SoundIndex (self->epair [i].value);

			if (!hook->sound_top)
				hook->sound_top = gstate->SoundIndex ("null.wav");
		}	
		else if (stricmp (self->epair [i].key, "sound_return") == 0)
		{
			hook->sound_bottom = gstate->SoundIndex (self->epair [i].value);

			if (!hook->sound_bottom)
				hook->sound_bottom = gstate->SoundIndex ("null.wav");
		}

		i++;
	}

	com->SetMovedir (self);

	self->movetype = MOVETYPE_PUSH;
	self->solid = SOLID_BSP;
	gstate->SetOrigin (self, self->s.origin [0], self->s.origin [1], self->s.origin [2]);
	gstate->SetModel (self, self->modelName);
	self->className = "func_button";

	self->blocked = button_blocked;
	self->use = button_use;

	if (self->health)
	{
		hook->max_health = self->health;
		self->die = button_killed;
		self->takedamage = DAMAGE_YES;
	}

	//	FIXME: touching buttons no longer activates them, they must be used
	else if ((int)self->spawnflags & PUSH_TOUCH)
		self->touch = button_touch;
	else
		self->touch = NULL;

	if (!hook->speed)
		hook->speed = 40;
	if (!hook->wait)
		hook->wait = 1;
	if (!lip)
		lip = 4;

	hook->state = STATE_BOTTOM;

	// start position
	hook->pos1 = Vector (self->s.origin);

	// find ending position
	hook->pos2 = hook->pos1 + Vector (self->movedir) * (fabs (Vector (self->movedir) ^ Vector (self->size)) - lip);
}

/////////////////////////////////////////////////////////
/// Secret X@!%^%!@$ doors
/////////////////////////////////////////////////////////

#define	SECRET_OPEN_ONCE	1	// stays open
#define	SECRET_1ST_LEFT		2	// 1st move is left of arrow
#define	SECRET_1ST_DOWN		4	// 1st move is down from arrow
#define	SECRET_NO_SHOOT		8	// only opened by trigger
#define	SECRET_YES_SHOOT	16	// shootable even if targeted

void	secret_move1 (	userEntity_t *self);
void	secret_move2 (	userEntity_t *self);
void	secret_move3 (	userEntity_t *self);
void	secret_move4 (	userEntity_t *self);
void	secret_move5 (	userEntity_t *self);
void	secret_move6 (	userEntity_t *self);
void	secret_done (userEntity_t *self);

///////////////////////////////////////////////////////////////////////////////
//	secret_use
///////////////////////////////////////////////////////////////////////////////

//	Q2FIXME: activator?!?!?

void	secret_use (userEntity_t *self, userEntity_t *other, userEntity_t *activator)
{
	float			temp;
	doorHook_t		*hook = (doorHook_t *) self->userHook;

	self->health = 10000;

	if (Vector (self->s.origin) != hook->old_origin)
		return;

	self->message = NULL;

	com->UseTargets (self, other, activator);

	if (!((int)self->spawnflags & SECRET_NO_SHOOT))
	{
		self->pain = NULL;
		self->takedamage = DAMAGE_NO;
	}

	com->ZeroVector (self->velocity);

	// make a sound, wait, wait, wait... I said wait, a little
	// it's really late

	if (hook->sound_up)
		gstate->PositionedSound (self->s.origin, self, CHAN_OVERRIDE, hook->sound_up, 1.0, ATTN_NORM, 0.0);
	
	//	self->nextthink = self->ltime + 0.1;
	self->nextthink = gstate->time + 0.1;

	temp = 1 - ((int)self->spawnflags & SECRET_1ST_LEFT);
	hook->mangle.AngleVectors (forward, right, up);
	
	if (!hook->t_width)
	{
		if ((int)self->spawnflags & SECRET_1ST_DOWN)
			hook->t_width = fabs (up ^ Vector (self->size));
		else
			hook->t_width = fabs (right ^ Vector (self->size));
	}

	if (!hook->t_length)
		hook->t_length = fabs (forward ^ Vector (self->size));

	if ((int)self->spawnflags & SECRET_1ST_DOWN)
		hook->pos1 = Vector (self->s.origin) - up * hook->t_width;
	else
		hook->pos1 = Vector (self->s.origin) + right * (hook->t_width * temp);

	hook->pos2 = hook->pos1 + forward * hook->t_length;

	com_CalcMove (self, hook->pos1, hook->speed, secret_move1);

	if (hook->sound_top)
		gstate->PositionedSound (self->s.origin, self, CHAN_OVERRIDE, hook->sound_top, 1.0, ATTN_NORM, 0.0);
}

///////////////////////////////////////////////////////////////////////////////
//	secret_pain
///////////////////////////////////////////////////////////////////////////////

void	secret_pain (userEntity_t *self, userEntity_t *other, float kick, int damage)
{
	secret_use (self, other, other);
}

///////////////////////////////////////////////////////////////////////////////
//	secret_die
///////////////////////////////////////////////////////////////////////////////

void	secret_die (userEntity_t *self, userEntity_t *inflictor, userEntity_t *attacker, int damage, vec3_t point)
{
	secret_use (self, attacker, attacker);
}

///////////////////////////////////////////////////////////////////////////////
//	secret_move1
//
//	wait after first movement
///////////////////////////////////////////////////////////////////////////////

void	secret_move1 (	userEntity_t *self)
{
	doorHook_t		*hook = (doorHook_t *) self->userHook;

	//	self->nextthink = self->ltime + 1.0;
	self->nextthink = gstate->time + 1.0;
	self->think = secret_move2;

	if (hook->sound_down)
		gstate->PositionedSound (self->s.origin, self, CHAN_OVERRIDE, hook->sound_down, 1.0, ATTN_NORM, 0.0);
}

///////////////////////////////////////////////////////////////////////////////
//	secret_move2
//
//	start moving sideways w/sound  
///////////////////////////////////////////////////////////////////////////////

void	secret_move2 (userEntity_t *self)
{
	doorHook_t		*hook = (doorHook_t *) self->userHook;
	
	if (hook->sound_top)
		gstate->PositionedSound (self->s.origin, self, CHAN_OVERRIDE, hook->sound_top, 1.0, ATTN_NORM, 0.0);

	com_CalcMove (self, hook->pos2, hook->speed, secret_move3);
}

///////////////////////////////////////////////////////////////////////////////
//	secret_move3
//
//	wait here until time to go back
///////////////////////////////////////////////////////////////////////////////

void	secret_move3 (userEntity_t *self)
{
	doorHook_t		*hook = (doorHook_t *) self->userHook;

	if (hook->sound_down)
		gstate->PositionedSound (self->s.origin, self, CHAN_OVERRIDE, hook->sound_down, 1.0, ATTN_NORM, 0.0);

	if (!((int)self->spawnflags & SECRET_OPEN_ONCE) && hook->wait >= 0)
	{
		//	self->nextthink = self->ltime + hook->wait;
		self->nextthink = gstate->time + hook->wait;
		self->think = secret_move4;
	}
	else
	{
		self->nextthink = -1;
		//wait until hell freezes over
	}
}

///////////////////////////////////////////////////////////////////////////////
//	secret_move4
//
//	move backwards
///////////////////////////////////////////////////////////////////////////////

void	secret_move4 (userEntity_t *self)
{
	doorHook_t		*hook = (doorHook_t *) self->userHook;

	if (hook->sound_top)
		gstate->PositionedSound (self->s.origin, self, CHAN_OVERRIDE, hook->sound_top, 1.0, ATTN_NORM, 0.0);

	com_CalcMove (self, hook->pos1, hook->speed, secret_move5);
}

///////////////////////////////////////////////////////////////////////////////
//	secret_move5
//
//	wait 1 second, please
///////////////////////////////////////////////////////////////////////////////

void	secret_move5 (userEntity_t *self)
{
	doorHook_t		*hook = (doorHook_t *) self->userHook;

	if (hook->sound_down)
		gstate->PositionedSound (self->s.origin, self, CHAN_OVERRIDE, hook->sound_down, 1.0, ATTN_NORM, 0.0);

	//	self->nextthink = self->ltime + 1.0;
	self->nextthink = gstate->time + 1.0;
	self->think = secret_move6;
}

///////////////////////////////////////////////////////////////////////////////
//	secret_move6
//
//	ach, ah, bleh, ugh, ich, SMACK!
///////////////////////////////////////////////////////////////////////////////

void	secret_move6 (userEntity_t *self)
{
	doorHook_t		*hook = (doorHook_t *) self->userHook;

	if (hook->sound_top)
		gstate->PositionedSound (self->s.origin, self, CHAN_OVERRIDE, hook->sound_top, 1.0, ATTN_NORM, 0.0);

	com_CalcMove (self, hook->old_origin, hook->speed, secret_done);
}

///////////////////////////////////////////////////////////////////////////////
//	secret_done
//
//	please let this end  
///////////////////////////////////////////////////////////////////////////////

void	secret_done (userEntity_t *self)
{
	doorHook_t		*hook = (doorHook_t *) self->userHook;

	if (!self->targetname || (int)self->spawnflags & SECRET_YES_SHOOT)
	{
		self->health = 10000;
		self->takedamage = DAMAGE_YES;
		self->pain = secret_pain;
	}

	if (hook->sound_down)
		gstate->PositionedSound (self->s.origin, self, CHAN_OVERRIDE, hook->sound_down, 1.0, ATTN_NORM, 0.0);
}

///////////////////////////////////////////////////////////////////////////////
//	secret_blocked
///////////////////////////////////////////////////////////////////////////////

void	secret_blocked (userEntity_t *self, userEntity_t *other)
{
	doorHook_t		*hook = (doorHook_t *) self->userHook;
	
	if (gstate->time < hook->move_finished)
		return;

	hook->move_finished = gstate->time + 0.5;

	com->Damage (other, self, self, zero_vector, zero_vector, hook->damage, DAMAGE_SQUISH | DAMAGE_NO_BLOOD);
}

///////////////////////////////////////////////////////////////////////////////
//	secret_touch
///////////////////////////////////////////////////////////////////////////////

void	secret_touch (userEntity_t *self, userEntity_t *other, cplane_t *plane, csurface_t *surf)
{
	doorHook_t		*hook = (doorHook_t *) self->userHook;

	if (!com->ValidTouch (self, other))
		return;
	if (hook->move_finished > gstate->time)
		return;

	hook->move_finished = gstate->time + 2.0;

	if (self->message && other->flags & FL_CLIENT)
		gstate->centerprintf (other, self->message);
}

///////////////////////////////////////////////////////////////////////////////
//	func_door_secret
///////////////////////////////////////////////////////////////////////////////

void	func_door_secret (userEntity_t *self)
{
	Vector		temp;
	doorHook_t	*hook;
	int			i;

	// get memory for userHook
	self->userHook = com->malloc_hook (sizeof (doorHook_t), MEM_HUNK); 
	hook = (doorHook_t *) self->userHook;
	
	i = 0;
	while (self->epair [i].key != NULL)
	{
		if (stricmp (self->epair [i].key, "dmg") == 0)
			hook->damage = atof (self->epair [i].value);
		else if (stricmp (self->epair [i].key, "wait") == 0)
			hook->wait = atof (self->epair [i].value);
		else if (stricmp (self->epair [i].key, "speed") == 0)
			hook->speed = atof (self->epair [i].value);
		else if (stricmp (self->epair [i].key, "targetname") == 0)
			self->targetname = self->epair [i].value;
		else if (stricmp (self->epair [i].key, "target") == 0)
			self->target = self->epair [i].value;
		else if (stricmp (self->epair [i].key, "killtarget") == 0)
			self->killtarget = self->epair [i].value;
		else if (!stricmp (self->epair [i].key, "delay"))
			self->delay = atof (self->epair [i].value);
		else if (stricmp (self->epair [i].key, "flag") == 0)
		{
			if (stricmp (self->epair [i].value, "SECRET_OPEN_ONCE") == 0)
				self->spawnflags = (int)self->spawnflags & SECRET_OPEN_ONCE;
			else if (stricmp (self->epair [i].value, "SECRET_1ST_LEFT") == 0)
				self->spawnflags = (int)self->spawnflags & SECRET_1ST_LEFT;
			else if (stricmp (self->epair [i].value, "SECRET_1ST_DOWN") == 0)
				self->spawnflags = (int)self->spawnflags & SECRET_1ST_DOWN;
			else if (stricmp (self->epair [i].value, "SECRET_NO_SHOOT") == 0)
				self->spawnflags = (int)self->spawnflags & SECRET_NO_SHOOT;
			else if (stricmp (self->epair [i].value, "SECRET_YES_SHOOT") == 0)
				self->spawnflags = (int)self->spawnflags & SECRET_YES_SHOOT;
		}
/*
		else if (stricmp (self->epair [i].key, "sound_open_finish") == 0)
		{
			hook->sound_top = self->epair [i].value;
			if (hook->sound_top)
				gstate->Precache_Sound (hook->sound_top);
		}	
		else if (stricmp (self->epair [i].key, "sound_close_finish") == 0)
		{
			hook->sound_bottom = self->epair [i].value;
			if (hook->sound_bottom)
				gstate->Precache_Sound (hook->sound_bottom);
		}
		else if (stricmp (self->epair [i].key, "sound_opening") == 0)
		{
			hook->sound_up = self->epair [i].value;
			if (hook->sound_up)
				gstate->Precache_Sound (hook->sound_up);
		}
		else if (stricmp (self->epair [i].key, "sound_closing") == 0)
		{
			hook->sound_down = self->epair [i].value;
			if (hook->sound_down)
				gstate->Precache_Sound (hook->sound_down);
		}
*/		
		i++;
	}

	if (!hook->damage)
		hook->damage = 2;

	hook->mangle = Vector (self->s.angles);
	com->ZeroVector (self->s.angles);
	self->solid = SOLID_BSP;
	self->movetype = MOVETYPE_PUSH;
	self->className = "func_door";
	gstate->SetModel (self, self->modelName);
	gstate->SetOrigin (self, self->s.origin [0], self->s.origin [1], self->s.origin [2]);

	self->touch = secret_touch;
	self->blocked = secret_blocked;
	hook->speed = 50;
	self->use = secret_use;

	if (!self->targetname || (int)self->spawnflags & SECRET_YES_SHOOT)
	{
		self->health = 10000;
		self->takedamage = DAMAGE_YES;
		self->pain = secret_pain;
		self->die = secret_die;
	}

	hook->old_origin = Vector (self->s.origin);

	if (!hook->wait)
		hook->wait = 5;
}

///////////////////////////////////////////////////////////////
//	func_multi_button routines
///////////////////////////////////////////////////////////////

void	multi_button_use (userEntity_t *self, userEntity_t *other, userEntity_t *activator);

///////////////////////
//	multi_button_wait
///////////////////////

void	multi_button_wait (userEntity_t *self)
{
	doorHook_t	*hook = (doorHook_t *) self->userHook;

	if (hook->count == hook->max_count - 1)
		hook->state = STATE_BOTTOM;
	else if (hook->count == 0)
		hook->state = STATE_TOP;

	//	Q2:	now done in multi_button_fire
	//	com->UseTargets (self, other, activator);

	self->s.frame = 1;
	self->use = multi_button_use;
}

///////////////////////
//	multi_button_blocked
///////////////////////

void	multi_button_blocked (userEntity_t *self, userEntity_t *other)
{
}

///////////////////////
//	multi_button_fire
///////////////////////

void	multi_button_fire (userEntity_t *self, userEntity_t *other, userEntity_t *activator)
{
	doorHook_t	*hook = (doorHook_t *) self->userHook;

	if (hook->state == STATE_DOWN || hook->state == STATE_TOP)
		hook->count++;
	else
		hook->count--;

	gstate->Con_Printf ("multi_button count = %i\n", hook->count);

	hook->pos2 = hook->pos1 + Vector (self->movedir) * hook->distance * hook->count;

	//	Q2: was done in multi_button_wait
	com->UseTargets (self, other, activator);

	com_CalcMove (self, hook->pos2, hook->speed, multi_button_wait);
}

///////////////////////
//	multi_button_use
///////////////////////

void	multi_button_use (userEntity_t *self, userEntity_t *other, userEntity_t *activator)
{
	doorHook_t	*hook = (doorHook_t *) self->userHook;

	hook->activator = gstate->activator;
	self->use = NULL;

	multi_button_fire (self, other, activator);
}

///////////////////////
//	multi_button_touch
///////////////////////

void	multi_button_touch (userEntity_t *self, userEntity_t *other, cplane_t *plane, csurface_t *surf)
{
	doorHook_t	*hook = (doorHook_t *) self->userHook;

	if (!com->ValidTouch (self, other))
		return;

	hook->activator = other;

	multi_button_fire (self, other, other);
}

///////////////////////
//	multi_button_killed
///////////////////////

void	multi_button_killed (userEntity_t *self, userEntity_t *infictor, userEntity_t *attacker, int damage, vec3_t point)
{
	doorHook_t	*hook = (doorHook_t *) self->userHook;
		
	hook->activator = gstate->attacker;
	self->health = hook->max_health;
	self->takedamage = DAMAGE_NO;

	//	Q2FIXME:	other == attacker???
	button_fire (self, attacker, attacker);
}

///////////////////////
//	func_multi_button
///////////////////////

void	func_multi_button (userEntity_t *self)
{
	float			gtemp, ftemp, lip;
	doorHook_t	*hook;
	Vector			temp;
	float			distance;
	int				i;

	self->userHook = com->malloc_hook (sizeof (doorHook_t), MEM_HUNK); 
	hook = (doorHook_t *) self->userHook;

	temp = Vector (0.0, 1.0, 0.0);
	temp.set (self->movedir);
	lip = 0;

	i = 0;
	while (self->epair [i].key != NULL)
	{
		if (stricmp (self->epair [i].key, "health") == 0)
			self->health = atof (self->epair [i].value);
		else if (stricmp (self->epair [i].key, "wait") == 0)
			hook->wait = atof (self->epair [i].value);
		else if (stricmp (self->epair [i].key, "speed") == 0)
			hook->speed = atof (self->epair [i].value);
		else if (stricmp (self->epair [i].key, "targetname") == 0)
			self->targetname = self->epair [i].value;
		else if (stricmp (self->epair [i].key, "target") == 0)
			self->target = self->epair [i].value;
		else if (stricmp (self->epair [i].key, "killtarget") == 0)
			self->killtarget = self->epair [i].value;
		else if (!stricmp (self->epair [i].key, "delay"))
			self->delay = atof (self->epair [i].value);
		else if (!stricmp (self->epair [i].key, "distance"))
			hook->distance = atof (self->epair [i].value);
		else if (!stricmp (self->epair [i].key, "count"))
			hook->max_count = atoi (self->epair [i].value);
		else if (stricmp (self->epair [i].key, "sound_use") == 0)
		{
			hook->sound_top = gstate->SoundIndex (self->epair [i].value);
			if (!hook->sound_top)
				gstate->SoundIndex ("null.wav");
		}	
		else if (stricmp (self->epair [i].key, "sound_return") == 0)
		{
			hook->sound_bottom = gstate->SoundIndex (self->epair [i].value);
			if (!hook->sound_bottom)
				gstate->SoundIndex ("null.wav");
		}

		i++;
	}

	com->SetMovedir (self);

	self->movetype = MOVETYPE_PUSH;
	self->solid = SOLID_BSP;
	gstate->SetOrigin (self, self->s.origin [0], self->s.origin [1], self->s.origin [2]);
	gstate->SetModel (self, self->modelName);
	self->className = "func_multi_button";

	self->blocked = multi_button_blocked;
	self->use = multi_button_use;

	if (self->health)
	{
		hook->max_health = self->health;
		self->die = multi_button_killed;
		self->takedamage = DAMAGE_YES;
	}

	//	FIXME: touching buttons no longer activates them, they must be used
	else if ((int)self->spawnflags & PUSH_TOUCH)
		self->touch = multi_button_touch;
	else
		self->touch = NULL;

	if (!hook->speed)
		hook->speed = 40;
	if (!hook->wait)
		hook->wait = 1;
	if (!lip)
		lip = 4;

	hook->state = STATE_TOP;
	hook->count = 0;

	// start position
	hook->pos1 = Vector (self->s.origin);

	// find ending position
//	hook->pos2 = hook->pos1 + Vector (self->movedir) * (fabs (Vector (self->movedir) ^ Vector (self->size)) - lip);
	hook->pos2 = hook->pos1 + Vector (self->movedir) * hook->distance;
}
