Quake-2

Quake 2 GPL Source Release
Log | Files | Refs

g_func.c (51138B)


      1 /*
      2 Copyright (C) 1997-2001 Id Software, Inc.
      3 
      4 This program is free software; you can redistribute it and/or
      5 modify it under the terms of the GNU General Public License
      6 as published by the Free Software Foundation; either version 2
      7 of the License, or (at your option) any later version.
      8 
      9 This program is distributed in the hope that it will be useful,
     10 but WITHOUT ANY WARRANTY; without even the implied warranty of
     11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
     12 
     13 See the GNU General Public License for more details.
     14 
     15 You should have received a copy of the GNU General Public License
     16 along with this program; if not, write to the Free Software
     17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
     18 
     19 */
     20 #include "g_local.h"
     21 
     22 /*
     23 =========================================================
     24 
     25   PLATS
     26 
     27   movement options:
     28 
     29   linear
     30   smooth start, hard stop
     31   smooth start, smooth stop
     32 
     33   start
     34   end
     35   acceleration
     36   speed
     37   deceleration
     38   begin sound
     39   end sound
     40   target fired when reaching end
     41   wait at end
     42 
     43   object characteristics that use move segments
     44   ---------------------------------------------
     45   movetype_push, or movetype_stop
     46   action when touched
     47   action when blocked
     48   action when used
     49 	disabled?
     50   auto trigger spawning
     51 
     52 
     53 =========================================================
     54 */
     55 
     56 #define PLAT_LOW_TRIGGER	1
     57 
     58 #define	STATE_TOP			0
     59 #define	STATE_BOTTOM		1
     60 #define STATE_UP			2
     61 #define STATE_DOWN			3
     62 
     63 #define DOOR_START_OPEN		1
     64 #define DOOR_REVERSE		2
     65 #define DOOR_CRUSHER		4
     66 #define DOOR_NOMONSTER		8
     67 #define DOOR_TOGGLE			32
     68 #define DOOR_X_AXIS			64
     69 #define DOOR_Y_AXIS			128
     70 
     71 
     72 //
     73 // Support routines for movement (changes in origin using velocity)
     74 //
     75 
     76 void Move_Done (edict_t *ent)
     77 {
     78 	VectorClear (ent->velocity);
     79 	ent->moveinfo.endfunc (ent);
     80 }
     81 
     82 void Move_Final (edict_t *ent)
     83 {
     84 	if (ent->moveinfo.remaining_distance == 0)
     85 	{
     86 		Move_Done (ent);
     87 		return;
     88 	}
     89 
     90 	VectorScale (ent->moveinfo.dir, ent->moveinfo.remaining_distance / FRAMETIME, ent->velocity);
     91 
     92 	ent->think = Move_Done;
     93 	ent->nextthink = level.time + FRAMETIME;
     94 }
     95 
     96 void Move_Begin (edict_t *ent)
     97 {
     98 	float	frames;
     99 
    100 	if ((ent->moveinfo.speed * FRAMETIME) >= ent->moveinfo.remaining_distance)
    101 	{
    102 		Move_Final (ent);
    103 		return;
    104 	}
    105 	VectorScale (ent->moveinfo.dir, ent->moveinfo.speed, ent->velocity);
    106 	frames = floor((ent->moveinfo.remaining_distance / ent->moveinfo.speed) / FRAMETIME);
    107 	ent->moveinfo.remaining_distance -= frames * ent->moveinfo.speed * FRAMETIME;
    108 	ent->nextthink = level.time + (frames * FRAMETIME);
    109 	ent->think = Move_Final;
    110 }
    111 
    112 void Think_AccelMove (edict_t *ent);
    113 
    114 void Move_Calc (edict_t *ent, vec3_t dest, void(*func)(edict_t*))
    115 {
    116 	VectorClear (ent->velocity);
    117 	VectorSubtract (dest, ent->s.origin, ent->moveinfo.dir);
    118 	ent->moveinfo.remaining_distance = VectorNormalize (ent->moveinfo.dir);
    119 	ent->moveinfo.endfunc = func;
    120 
    121 	if (ent->moveinfo.speed == ent->moveinfo.accel && ent->moveinfo.speed == ent->moveinfo.decel)
    122 	{
    123 		if (level.current_entity == ((ent->flags & FL_TEAMSLAVE) ? ent->teammaster : ent))
    124 		{
    125 			Move_Begin (ent);
    126 		}
    127 		else
    128 		{
    129 			ent->nextthink = level.time + FRAMETIME;
    130 			ent->think = Move_Begin;
    131 		}
    132 	}
    133 	else
    134 	{
    135 		// accelerative
    136 		ent->moveinfo.current_speed = 0;
    137 		ent->think = Think_AccelMove;
    138 		ent->nextthink = level.time + FRAMETIME;
    139 	}
    140 }
    141 
    142 
    143 //
    144 // Support routines for angular movement (changes in angle using avelocity)
    145 //
    146 
    147 void AngleMove_Done (edict_t *ent)
    148 {
    149 	VectorClear (ent->avelocity);
    150 	ent->moveinfo.endfunc (ent);
    151 }
    152 
    153 void AngleMove_Final (edict_t *ent)
    154 {
    155 	vec3_t	move;
    156 
    157 	if (ent->moveinfo.state == STATE_UP)
    158 		VectorSubtract (ent->moveinfo.end_angles, ent->s.angles, move);
    159 	else
    160 		VectorSubtract (ent->moveinfo.start_angles, ent->s.angles, move);
    161 
    162 	if (VectorCompare (move, vec3_origin))
    163 	{
    164 		AngleMove_Done (ent);
    165 		return;
    166 	}
    167 
    168 	VectorScale (move, 1.0/FRAMETIME, ent->avelocity);
    169 
    170 	ent->think = AngleMove_Done;
    171 	ent->nextthink = level.time + FRAMETIME;
    172 }
    173 
    174 void AngleMove_Begin (edict_t *ent)
    175 {
    176 	vec3_t	destdelta;
    177 	float	len;
    178 	float	traveltime;
    179 	float	frames;
    180 
    181 	// set destdelta to the vector needed to move
    182 	if (ent->moveinfo.state == STATE_UP)
    183 		VectorSubtract (ent->moveinfo.end_angles, ent->s.angles, destdelta);
    184 	else
    185 		VectorSubtract (ent->moveinfo.start_angles, ent->s.angles, destdelta);
    186 	
    187 	// calculate length of vector
    188 	len = VectorLength (destdelta);
    189 	
    190 	// divide by speed to get time to reach dest
    191 	traveltime = len / ent->moveinfo.speed;
    192 
    193 	if (traveltime < FRAMETIME)
    194 	{
    195 		AngleMove_Final (ent);
    196 		return;
    197 	}
    198 
    199 	frames = floor(traveltime / FRAMETIME);
    200 
    201 	// scale the destdelta vector by the time spent traveling to get velocity
    202 	VectorScale (destdelta, 1.0 / traveltime, ent->avelocity);
    203 
    204 	// set nextthink to trigger a think when dest is reached
    205 	ent->nextthink = level.time + frames * FRAMETIME;
    206 	ent->think = AngleMove_Final;
    207 }
    208 
    209 void AngleMove_Calc (edict_t *ent, void(*func)(edict_t*))
    210 {
    211 	VectorClear (ent->avelocity);
    212 	ent->moveinfo.endfunc = func;
    213 	if (level.current_entity == ((ent->flags & FL_TEAMSLAVE) ? ent->teammaster : ent))
    214 	{
    215 		AngleMove_Begin (ent);
    216 	}
    217 	else
    218 	{
    219 		ent->nextthink = level.time + FRAMETIME;
    220 		ent->think = AngleMove_Begin;
    221 	}
    222 }
    223 
    224 
    225 /*
    226 ==============
    227 Think_AccelMove
    228 
    229 The team has completed a frame of movement, so
    230 change the speed for the next frame
    231 ==============
    232 */
    233 #define AccelerationDistance(target, rate)	(target * ((target / rate) + 1) / 2)
    234 
    235 void plat_CalcAcceleratedMove(moveinfo_t *moveinfo)
    236 {
    237 	float	accel_dist;
    238 	float	decel_dist;
    239 
    240 	moveinfo->move_speed = moveinfo->speed;
    241 
    242 	if (moveinfo->remaining_distance < moveinfo->accel)
    243 	{
    244 		moveinfo->current_speed = moveinfo->remaining_distance;
    245 		return;
    246 	}
    247 
    248 	accel_dist = AccelerationDistance (moveinfo->speed, moveinfo->accel);
    249 	decel_dist = AccelerationDistance (moveinfo->speed, moveinfo->decel);
    250 
    251 	if ((moveinfo->remaining_distance - accel_dist - decel_dist) < 0)
    252 	{
    253 		float	f;
    254 
    255 		f = (moveinfo->accel + moveinfo->decel) / (moveinfo->accel * moveinfo->decel);
    256 		moveinfo->move_speed = (-2 + sqrt(4 - 4 * f * (-2 * moveinfo->remaining_distance))) / (2 * f);
    257 		decel_dist = AccelerationDistance (moveinfo->move_speed, moveinfo->decel);
    258 	}
    259 
    260 	moveinfo->decel_distance = decel_dist;
    261 };
    262 
    263 void plat_Accelerate (moveinfo_t *moveinfo)
    264 {
    265 	// are we decelerating?
    266 	if (moveinfo->remaining_distance <= moveinfo->decel_distance)
    267 	{
    268 		if (moveinfo->remaining_distance < moveinfo->decel_distance)
    269 		{
    270 			if (moveinfo->next_speed)
    271 			{
    272 				moveinfo->current_speed = moveinfo->next_speed;
    273 				moveinfo->next_speed = 0;
    274 				return;
    275 			}
    276 			if (moveinfo->current_speed > moveinfo->decel)
    277 				moveinfo->current_speed -= moveinfo->decel;
    278 		}
    279 		return;
    280 	}
    281 
    282 	// are we at full speed and need to start decelerating during this move?
    283 	if (moveinfo->current_speed == moveinfo->move_speed)
    284 		if ((moveinfo->remaining_distance - moveinfo->current_speed) < moveinfo->decel_distance)
    285 		{
    286 			float	p1_distance;
    287 			float	p2_distance;
    288 			float	distance;
    289 
    290 			p1_distance = moveinfo->remaining_distance - moveinfo->decel_distance;
    291 			p2_distance = moveinfo->move_speed * (1.0 - (p1_distance / moveinfo->move_speed));
    292 			distance = p1_distance + p2_distance;
    293 			moveinfo->current_speed = moveinfo->move_speed;
    294 			moveinfo->next_speed = moveinfo->move_speed - moveinfo->decel * (p2_distance / distance);
    295 			return;
    296 		}
    297 
    298 	// are we accelerating?
    299 	if (moveinfo->current_speed < moveinfo->speed)
    300 	{
    301 		float	old_speed;
    302 		float	p1_distance;
    303 		float	p1_speed;
    304 		float	p2_distance;
    305 		float	distance;
    306 
    307 		old_speed = moveinfo->current_speed;
    308 
    309 		// figure simple acceleration up to move_speed
    310 		moveinfo->current_speed += moveinfo->accel;
    311 		if (moveinfo->current_speed > moveinfo->speed)
    312 			moveinfo->current_speed = moveinfo->speed;
    313 
    314 		// are we accelerating throughout this entire move?
    315 		if ((moveinfo->remaining_distance - moveinfo->current_speed) >= moveinfo->decel_distance)
    316 			return;
    317 
    318 		// during this move we will accelrate from current_speed to move_speed
    319 		// and cross over the decel_distance; figure the average speed for the
    320 		// entire move
    321 		p1_distance = moveinfo->remaining_distance - moveinfo->decel_distance;
    322 		p1_speed = (old_speed + moveinfo->move_speed) / 2.0;
    323 		p2_distance = moveinfo->move_speed * (1.0 - (p1_distance / p1_speed));
    324 		distance = p1_distance + p2_distance;
    325 		moveinfo->current_speed = (p1_speed * (p1_distance / distance)) + (moveinfo->move_speed * (p2_distance / distance));
    326 		moveinfo->next_speed = moveinfo->move_speed - moveinfo->decel * (p2_distance / distance);
    327 		return;
    328 	}
    329 
    330 	// we are at constant velocity (move_speed)
    331 	return;
    332 };
    333 
    334 void Think_AccelMove (edict_t *ent)
    335 {
    336 	ent->moveinfo.remaining_distance -= ent->moveinfo.current_speed;
    337 
    338 	if (ent->moveinfo.current_speed == 0)		// starting or blocked
    339 		plat_CalcAcceleratedMove(&ent->moveinfo);
    340 
    341 	plat_Accelerate (&ent->moveinfo);
    342 
    343 	// will the entire move complete on next frame?
    344 	if (ent->moveinfo.remaining_distance <= ent->moveinfo.current_speed)
    345 	{
    346 		Move_Final (ent);
    347 		return;
    348 	}
    349 
    350 	VectorScale (ent->moveinfo.dir, ent->moveinfo.current_speed*10, ent->velocity);
    351 	ent->nextthink = level.time + FRAMETIME;
    352 	ent->think = Think_AccelMove;
    353 }
    354 
    355 
    356 void plat_go_down (edict_t *ent);
    357 
    358 void plat_hit_top (edict_t *ent)
    359 {
    360 	if (!(ent->flags & FL_TEAMSLAVE))
    361 	{
    362 		if (ent->moveinfo.sound_end)
    363 			gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_end, 1, ATTN_STATIC, 0);
    364 		ent->s.sound = 0;
    365 	}
    366 	ent->moveinfo.state = STATE_TOP;
    367 
    368 	ent->think = plat_go_down;
    369 	ent->nextthink = level.time + 3;
    370 }
    371 
    372 void plat_hit_bottom (edict_t *ent)
    373 {
    374 	if (!(ent->flags & FL_TEAMSLAVE))
    375 	{
    376 		if (ent->moveinfo.sound_end)
    377 			gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_end, 1, ATTN_STATIC, 0);
    378 		ent->s.sound = 0;
    379 	}
    380 	ent->moveinfo.state = STATE_BOTTOM;
    381 }
    382 
    383 void plat_go_down (edict_t *ent)
    384 {
    385 	if (!(ent->flags & FL_TEAMSLAVE))
    386 	{
    387 		if (ent->moveinfo.sound_start)
    388 			gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_start, 1, ATTN_STATIC, 0);
    389 		ent->s.sound = ent->moveinfo.sound_middle;
    390 	}
    391 	ent->moveinfo.state = STATE_DOWN;
    392 	Move_Calc (ent, ent->moveinfo.end_origin, plat_hit_bottom);
    393 }
    394 
    395 void plat_go_up (edict_t *ent)
    396 {
    397 	if (!(ent->flags & FL_TEAMSLAVE))
    398 	{
    399 		if (ent->moveinfo.sound_start)
    400 			gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_start, 1, ATTN_STATIC, 0);
    401 		ent->s.sound = ent->moveinfo.sound_middle;
    402 	}
    403 	ent->moveinfo.state = STATE_UP;
    404 	Move_Calc (ent, ent->moveinfo.start_origin, plat_hit_top);
    405 }
    406 
    407 void plat_blocked (edict_t *self, edict_t *other)
    408 {
    409 	if (!(other->svflags & SVF_MONSTER) && (!other->client) )
    410 	{
    411 		// give it a chance to go away on it's own terms (like gibs)
    412 		T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH);
    413 		// if it's still there, nuke it
    414 		if (other)
    415 			BecomeExplosion1 (other);
    416 		return;
    417 	}
    418 
    419 	T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
    420 
    421 	if (self->moveinfo.state == STATE_UP)
    422 		plat_go_down (self);
    423 	else if (self->moveinfo.state == STATE_DOWN)
    424 		plat_go_up (self);
    425 }
    426 
    427 
    428 void Use_Plat (edict_t *ent, edict_t *other, edict_t *activator)
    429 { 
    430 	if (ent->think)
    431 		return;		// already down
    432 	plat_go_down (ent);
    433 }
    434 
    435 
    436 void Touch_Plat_Center (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
    437 {
    438 	if (!other->client)
    439 		return;
    440 		
    441 	if (other->health <= 0)
    442 		return;
    443 
    444 	ent = ent->enemy;	// now point at the plat, not the trigger
    445 	if (ent->moveinfo.state == STATE_BOTTOM)
    446 		plat_go_up (ent);
    447 	else if (ent->moveinfo.state == STATE_TOP)
    448 		ent->nextthink = level.time + 1;	// the player is still on the plat, so delay going down
    449 }
    450 
    451 void plat_spawn_inside_trigger (edict_t *ent)
    452 {
    453 	edict_t	*trigger;
    454 	vec3_t	tmin, tmax;
    455 
    456 //
    457 // middle trigger
    458 //	
    459 	trigger = G_Spawn();
    460 	trigger->touch = Touch_Plat_Center;
    461 	trigger->movetype = MOVETYPE_NONE;
    462 	trigger->solid = SOLID_TRIGGER;
    463 	trigger->enemy = ent;
    464 	
    465 	tmin[0] = ent->mins[0] + 25;
    466 	tmin[1] = ent->mins[1] + 25;
    467 	tmin[2] = ent->mins[2];
    468 
    469 	tmax[0] = ent->maxs[0] - 25;
    470 	tmax[1] = ent->maxs[1] - 25;
    471 	tmax[2] = ent->maxs[2] + 8;
    472 
    473 	tmin[2] = tmax[2] - (ent->pos1[2] - ent->pos2[2] + st.lip);
    474 
    475 	if (ent->spawnflags & PLAT_LOW_TRIGGER)
    476 		tmax[2] = tmin[2] + 8;
    477 	
    478 	if (tmax[0] - tmin[0] <= 0)
    479 	{
    480 		tmin[0] = (ent->mins[0] + ent->maxs[0]) *0.5;
    481 		tmax[0] = tmin[0] + 1;
    482 	}
    483 	if (tmax[1] - tmin[1] <= 0)
    484 	{
    485 		tmin[1] = (ent->mins[1] + ent->maxs[1]) *0.5;
    486 		tmax[1] = tmin[1] + 1;
    487 	}
    488 	
    489 	VectorCopy (tmin, trigger->mins);
    490 	VectorCopy (tmax, trigger->maxs);
    491 
    492 	gi.linkentity (trigger);
    493 }
    494 
    495 
    496 /*QUAKED func_plat (0 .5 .8) ? PLAT_LOW_TRIGGER
    497 speed	default 150
    498 
    499 Plats are always drawn in the extended position, so they will light correctly.
    500 
    501 If the plat is the target of another trigger or button, it will start out disabled in the extended position until it is trigger, when it will lower and become a normal plat.
    502 
    503 "speed"	overrides default 200.
    504 "accel" overrides default 500
    505 "lip"	overrides default 8 pixel lip
    506 
    507 If the "height" key is set, that will determine the amount the plat moves, instead of being implicitly determoveinfoned by the model's height.
    508 
    509 Set "sounds" to one of the following:
    510 1) base fast
    511 2) chain slow
    512 */
    513 void SP_func_plat (edict_t *ent)
    514 {
    515 	VectorClear (ent->s.angles);
    516 	ent->solid = SOLID_BSP;
    517 	ent->movetype = MOVETYPE_PUSH;
    518 
    519 	gi.setmodel (ent, ent->model);
    520 
    521 	ent->blocked = plat_blocked;
    522 
    523 	if (!ent->speed)
    524 		ent->speed = 20;
    525 	else
    526 		ent->speed *= 0.1;
    527 
    528 	if (!ent->accel)
    529 		ent->accel = 5;
    530 	else
    531 		ent->accel *= 0.1;
    532 
    533 	if (!ent->decel)
    534 		ent->decel = 5;
    535 	else
    536 		ent->decel *= 0.1;
    537 
    538 	if (!ent->dmg)
    539 		ent->dmg = 2;
    540 
    541 	if (!st.lip)
    542 		st.lip = 8;
    543 
    544 	// pos1 is the top position, pos2 is the bottom
    545 	VectorCopy (ent->s.origin, ent->pos1);
    546 	VectorCopy (ent->s.origin, ent->pos2);
    547 	if (st.height)
    548 		ent->pos2[2] -= st.height;
    549 	else
    550 		ent->pos2[2] -= (ent->maxs[2] - ent->mins[2]) - st.lip;
    551 
    552 	ent->use = Use_Plat;
    553 
    554 	plat_spawn_inside_trigger (ent);	// the "start moving" trigger	
    555 
    556 	if (ent->targetname)
    557 	{
    558 		ent->moveinfo.state = STATE_UP;
    559 	}
    560 	else
    561 	{
    562 		VectorCopy (ent->pos2, ent->s.origin);
    563 		gi.linkentity (ent);
    564 		ent->moveinfo.state = STATE_BOTTOM;
    565 	}
    566 
    567 	ent->moveinfo.speed = ent->speed;
    568 	ent->moveinfo.accel = ent->accel;
    569 	ent->moveinfo.decel = ent->decel;
    570 	ent->moveinfo.wait = ent->wait;
    571 	VectorCopy (ent->pos1, ent->moveinfo.start_origin);
    572 	VectorCopy (ent->s.angles, ent->moveinfo.start_angles);
    573 	VectorCopy (ent->pos2, ent->moveinfo.end_origin);
    574 	VectorCopy (ent->s.angles, ent->moveinfo.end_angles);
    575 
    576 	ent->moveinfo.sound_start = gi.soundindex ("plats/pt1_strt.wav");
    577 	ent->moveinfo.sound_middle = gi.soundindex ("plats/pt1_mid.wav");
    578 	ent->moveinfo.sound_end = gi.soundindex ("plats/pt1_end.wav");
    579 }
    580 
    581 //====================================================================
    582 
    583 /*QUAKED func_rotating (0 .5 .8) ? START_ON REVERSE X_AXIS Y_AXIS TOUCH_PAIN STOP ANIMATED ANIMATED_FAST
    584 You need to have an origin brush as part of this entity.  The center of that brush will be
    585 the point around which it is rotated. It will rotate around the Z axis by default.  You can
    586 check either the X_AXIS or Y_AXIS box to change that.
    587 
    588 "speed" determines how fast it moves; default value is 100.
    589 "dmg"	damage to inflict when blocked (2 default)
    590 
    591 REVERSE will cause the it to rotate in the opposite direction.
    592 STOP mean it will stop moving instead of pushing entities
    593 */
    594 
    595 void rotating_blocked (edict_t *self, edict_t *other)
    596 {
    597 	T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
    598 }
    599 
    600 void rotating_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
    601 {
    602 	if (self->avelocity[0] || self->avelocity[1] || self->avelocity[2])
    603 		T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
    604 }
    605 
    606 void rotating_use (edict_t *self, edict_t *other, edict_t *activator)
    607 {
    608 	if (!VectorCompare (self->avelocity, vec3_origin))
    609 	{
    610 		self->s.sound = 0;
    611 		VectorClear (self->avelocity);
    612 		self->touch = NULL;
    613 	}
    614 	else
    615 	{
    616 		self->s.sound = self->moveinfo.sound_middle;
    617 		VectorScale (self->movedir, self->speed, self->avelocity);
    618 		if (self->spawnflags & 16)
    619 			self->touch = rotating_touch;
    620 	}
    621 }
    622 
    623 void SP_func_rotating (edict_t *ent)
    624 {
    625 	ent->solid = SOLID_BSP;
    626 	if (ent->spawnflags & 32)
    627 		ent->movetype = MOVETYPE_STOP;
    628 	else
    629 		ent->movetype = MOVETYPE_PUSH;
    630 
    631 	// set the axis of rotation
    632 	VectorClear(ent->movedir);
    633 	if (ent->spawnflags & 4)
    634 		ent->movedir[2] = 1.0;
    635 	else if (ent->spawnflags & 8)
    636 		ent->movedir[0] = 1.0;
    637 	else // Z_AXIS
    638 		ent->movedir[1] = 1.0;
    639 
    640 	// check for reverse rotation
    641 	if (ent->spawnflags & 2)
    642 		VectorNegate (ent->movedir, ent->movedir);
    643 
    644 	if (!ent->speed)
    645 		ent->speed = 100;
    646 	if (!ent->dmg)
    647 		ent->dmg = 2;
    648 
    649 //	ent->moveinfo.sound_middle = "doors/hydro1.wav";
    650 
    651 	ent->use = rotating_use;
    652 	if (ent->dmg)
    653 		ent->blocked = rotating_blocked;
    654 
    655 	if (ent->spawnflags & 1)
    656 		ent->use (ent, NULL, NULL);
    657 
    658 	if (ent->spawnflags & 64)
    659 		ent->s.effects |= EF_ANIM_ALL;
    660 	if (ent->spawnflags & 128)
    661 		ent->s.effects |= EF_ANIM_ALLFAST;
    662 
    663 	gi.setmodel (ent, ent->model);
    664 	gi.linkentity (ent);
    665 }
    666 
    667 /*
    668 ======================================================================
    669 
    670 BUTTONS
    671 
    672 ======================================================================
    673 */
    674 
    675 /*QUAKED func_button (0 .5 .8) ?
    676 When a button is touched, it moves some distance in the direction of it's angle, triggers all of it's targets, waits some time, then returns to it's original position where it can be triggered again.
    677 
    678 "angle"		determines the opening direction
    679 "target"	all entities with a matching targetname will be used
    680 "speed"		override the default 40 speed
    681 "wait"		override the default 1 second wait (-1 = never return)
    682 "lip"		override the default 4 pixel lip remaining at end of move
    683 "health"	if set, the button must be killed instead of touched
    684 "sounds"
    685 1) silent
    686 2) steam metal
    687 3) wooden clunk
    688 4) metallic click
    689 5) in-out
    690 */
    691 
    692 void button_done (edict_t *self)
    693 {
    694 	self->moveinfo.state = STATE_BOTTOM;
    695 	self->s.effects &= ~EF_ANIM23;
    696 	self->s.effects |= EF_ANIM01;
    697 }
    698 
    699 void button_return (edict_t *self)
    700 {
    701 	self->moveinfo.state = STATE_DOWN;
    702 
    703 	Move_Calc (self, self->moveinfo.start_origin, button_done);
    704 
    705 	self->s.frame = 0;
    706 
    707 	if (self->health)
    708 		self->takedamage = DAMAGE_YES;
    709 }
    710 
    711 void button_wait (edict_t *self)
    712 {
    713 	self->moveinfo.state = STATE_TOP;
    714 	self->s.effects &= ~EF_ANIM01;
    715 	self->s.effects |= EF_ANIM23;
    716 
    717 	G_UseTargets (self, self->activator);
    718 	self->s.frame = 1;
    719 	if (self->moveinfo.wait >= 0)
    720 	{
    721 		self->nextthink = level.time + self->moveinfo.wait;
    722 		self->think = button_return;
    723 	}
    724 }
    725 
    726 void button_fire (edict_t *self)
    727 {
    728 	if (self->moveinfo.state == STATE_UP || self->moveinfo.state == STATE_TOP)
    729 		return;
    730 
    731 	self->moveinfo.state = STATE_UP;
    732 	if (self->moveinfo.sound_start && !(self->flags & FL_TEAMSLAVE))
    733 		gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0);
    734 	Move_Calc (self, self->moveinfo.end_origin, button_wait);
    735 }
    736 
    737 void button_use (edict_t *self, edict_t *other, edict_t *activator)
    738 {
    739 	self->activator = activator;
    740 	button_fire (self);
    741 }
    742 
    743 void button_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
    744 {
    745 	if (!other->client)
    746 		return;
    747 
    748 	if (other->health <= 0)
    749 		return;
    750 
    751 	self->activator = other;
    752 	button_fire (self);
    753 }
    754 
    755 void button_killed (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
    756 {
    757 	self->activator = attacker;
    758 	self->health = self->max_health;
    759 	self->takedamage = DAMAGE_NO;
    760 	button_fire (self);
    761 }
    762 
    763 void SP_func_button (edict_t *ent)
    764 {
    765 	vec3_t	abs_movedir;
    766 	float	dist;
    767 
    768 	G_SetMovedir (ent->s.angles, ent->movedir);
    769 	ent->movetype = MOVETYPE_STOP;
    770 	ent->solid = SOLID_BSP;
    771 	gi.setmodel (ent, ent->model);
    772 
    773 	if (ent->sounds != 1)
    774 		ent->moveinfo.sound_start = gi.soundindex ("switches/butn2.wav");
    775 	
    776 	if (!ent->speed)
    777 		ent->speed = 40;
    778 	if (!ent->accel)
    779 		ent->accel = ent->speed;
    780 	if (!ent->decel)
    781 		ent->decel = ent->speed;
    782 
    783 	if (!ent->wait)
    784 		ent->wait = 3;
    785 	if (!st.lip)
    786 		st.lip = 4;
    787 
    788 	VectorCopy (ent->s.origin, ent->pos1);
    789 	abs_movedir[0] = fabs(ent->movedir[0]);
    790 	abs_movedir[1] = fabs(ent->movedir[1]);
    791 	abs_movedir[2] = fabs(ent->movedir[2]);
    792 	dist = abs_movedir[0] * ent->size[0] + abs_movedir[1] * ent->size[1] + abs_movedir[2] * ent->size[2] - st.lip;
    793 	VectorMA (ent->pos1, dist, ent->movedir, ent->pos2);
    794 
    795 	ent->use = button_use;
    796 	ent->s.effects |= EF_ANIM01;
    797 
    798 	if (ent->health)
    799 	{
    800 		ent->max_health = ent->health;
    801 		ent->die = button_killed;
    802 		ent->takedamage = DAMAGE_YES;
    803 	}
    804 	else if (! ent->targetname)
    805 		ent->touch = button_touch;
    806 
    807 	ent->moveinfo.state = STATE_BOTTOM;
    808 
    809 	ent->moveinfo.speed = ent->speed;
    810 	ent->moveinfo.accel = ent->accel;
    811 	ent->moveinfo.decel = ent->decel;
    812 	ent->moveinfo.wait = ent->wait;
    813 	VectorCopy (ent->pos1, ent->moveinfo.start_origin);
    814 	VectorCopy (ent->s.angles, ent->moveinfo.start_angles);
    815 	VectorCopy (ent->pos2, ent->moveinfo.end_origin);
    816 	VectorCopy (ent->s.angles, ent->moveinfo.end_angles);
    817 
    818 	gi.linkentity (ent);
    819 }
    820 
    821 /*
    822 ======================================================================
    823 
    824 DOORS
    825 
    826   spawn a trigger surrounding the entire team unless it is
    827   allready targeted by another
    828 
    829 ======================================================================
    830 */
    831 
    832 /*QUAKED func_door (0 .5 .8) ? START_OPEN x CRUSHER NOMONSTER ANIMATED TOGGLE ANIMATED_FAST
    833 TOGGLE		wait in both the start and end states for a trigger event.
    834 START_OPEN	the door to moves to its destination when spawned, and operate in reverse.  It is used to temporarily or permanently close off an area when triggered (not useful for touch or takedamage doors).
    835 NOMONSTER	monsters will not trigger this door
    836 
    837 "message"	is printed when the door is touched if it is a trigger door and it hasn't been fired yet
    838 "angle"		determines the opening direction
    839 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
    840 "health"	if set, door must be shot open
    841 "speed"		movement speed (100 default)
    842 "wait"		wait before returning (3 default, -1 = never return)
    843 "lip"		lip remaining at end of move (8 default)
    844 "dmg"		damage to inflict when blocked (2 default)
    845 "sounds"
    846 1)	silent
    847 2)	light
    848 3)	medium
    849 4)	heavy
    850 */
    851 
    852 void door_use_areaportals (edict_t *self, qboolean open)
    853 {
    854 	edict_t	*t = NULL;
    855 
    856 	if (!self->target)
    857 		return;
    858 
    859 	while ((t = G_Find (t, FOFS(targetname), self->target)))
    860 	{
    861 		if (Q_stricmp(t->classname, "func_areaportal") == 0)
    862 		{
    863 			gi.SetAreaPortalState (t->style, open);
    864 		}
    865 	}
    866 }
    867 
    868 void door_go_down (edict_t *self);
    869 
    870 void door_hit_top (edict_t *self)
    871 {
    872 	if (!(self->flags & FL_TEAMSLAVE))
    873 	{
    874 		if (self->moveinfo.sound_end)
    875 			gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC, 0);
    876 		self->s.sound = 0;
    877 	}
    878 	self->moveinfo.state = STATE_TOP;
    879 	if (self->spawnflags & DOOR_TOGGLE)
    880 		return;
    881 	if (self->moveinfo.wait >= 0)
    882 	{
    883 		self->think = door_go_down;
    884 		self->nextthink = level.time + self->moveinfo.wait;
    885 	}
    886 }
    887 
    888 void door_hit_bottom (edict_t *self)
    889 {
    890 	if (!(self->flags & FL_TEAMSLAVE))
    891 	{
    892 		if (self->moveinfo.sound_end)
    893 			gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC, 0);
    894 		self->s.sound = 0;
    895 	}
    896 	self->moveinfo.state = STATE_BOTTOM;
    897 	door_use_areaportals (self, false);
    898 }
    899 
    900 void door_go_down (edict_t *self)
    901 {
    902 	if (!(self->flags & FL_TEAMSLAVE))
    903 	{
    904 		if (self->moveinfo.sound_start)
    905 			gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0);
    906 		self->s.sound = self->moveinfo.sound_middle;
    907 	}
    908 	if (self->max_health)
    909 	{
    910 		self->takedamage = DAMAGE_YES;
    911 		self->health = self->max_health;
    912 	}
    913 	
    914 	self->moveinfo.state = STATE_DOWN;
    915 	if (strcmp(self->classname, "func_door") == 0)
    916 		Move_Calc (self, self->moveinfo.start_origin, door_hit_bottom);
    917 	else if (strcmp(self->classname, "func_door_rotating") == 0)
    918 		AngleMove_Calc (self, door_hit_bottom);
    919 }
    920 
    921 void door_go_up (edict_t *self, edict_t *activator)
    922 {
    923 	if (self->moveinfo.state == STATE_UP)
    924 		return;		// already going up
    925 
    926 	if (self->moveinfo.state == STATE_TOP)
    927 	{	// reset top wait time
    928 		if (self->moveinfo.wait >= 0)
    929 			self->nextthink = level.time + self->moveinfo.wait;
    930 		return;
    931 	}
    932 	
    933 	if (!(self->flags & FL_TEAMSLAVE))
    934 	{
    935 		if (self->moveinfo.sound_start)
    936 			gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0);
    937 		self->s.sound = self->moveinfo.sound_middle;
    938 	}
    939 	self->moveinfo.state = STATE_UP;
    940 	if (strcmp(self->classname, "func_door") == 0)
    941 		Move_Calc (self, self->moveinfo.end_origin, door_hit_top);
    942 	else if (strcmp(self->classname, "func_door_rotating") == 0)
    943 		AngleMove_Calc (self, door_hit_top);
    944 
    945 	G_UseTargets (self, activator);
    946 	door_use_areaportals (self, true);
    947 }
    948 
    949 void door_use (edict_t *self, edict_t *other, edict_t *activator)
    950 {
    951 	edict_t	*ent;
    952 
    953 	if (self->flags & FL_TEAMSLAVE)
    954 		return;
    955 
    956 	if (self->spawnflags & DOOR_TOGGLE)
    957 	{
    958 		if (self->moveinfo.state == STATE_UP || self->moveinfo.state == STATE_TOP)
    959 		{
    960 			// trigger all paired doors
    961 			for (ent = self ; ent ; ent = ent->teamchain)
    962 			{
    963 				ent->message = NULL;
    964 				ent->touch = NULL;
    965 				door_go_down (ent);
    966 			}
    967 			return;
    968 		}
    969 	}
    970 	
    971 	// trigger all paired doors
    972 	for (ent = self ; ent ; ent = ent->teamchain)
    973 	{
    974 		ent->message = NULL;
    975 		ent->touch = NULL;
    976 		door_go_up (ent, activator);
    977 	}
    978 };
    979 
    980 void Touch_DoorTrigger (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
    981 {
    982 	if (other->health <= 0)
    983 		return;
    984 
    985 	if (!(other->svflags & SVF_MONSTER) && (!other->client))
    986 		return;
    987 
    988 	if ((self->owner->spawnflags & DOOR_NOMONSTER) && (other->svflags & SVF_MONSTER))
    989 		return;
    990 
    991 	if (level.time < self->touch_debounce_time)
    992 		return;
    993 	self->touch_debounce_time = level.time + 1.0;
    994 
    995 	door_use (self->owner, other, other);
    996 }
    997 
    998 void Think_CalcMoveSpeed (edict_t *self)
    999 {
   1000 	edict_t	*ent;
   1001 	float	min;
   1002 	float	time;
   1003 	float	newspeed;
   1004 	float	ratio;
   1005 	float	dist;
   1006 
   1007 	if (self->flags & FL_TEAMSLAVE)
   1008 		return;		// only the team master does this
   1009 
   1010 	// find the smallest distance any member of the team will be moving
   1011 	min = fabs(self->moveinfo.distance);
   1012 	for (ent = self->teamchain; ent; ent = ent->teamchain)
   1013 	{
   1014 		dist = fabs(ent->moveinfo.distance);
   1015 		if (dist < min)
   1016 			min = dist;
   1017 	}
   1018 
   1019 	time = min / self->moveinfo.speed;
   1020 
   1021 	// adjust speeds so they will all complete at the same time
   1022 	for (ent = self; ent; ent = ent->teamchain)
   1023 	{
   1024 		newspeed = fabs(ent->moveinfo.distance) / time;
   1025 		ratio = newspeed / ent->moveinfo.speed;
   1026 		if (ent->moveinfo.accel == ent->moveinfo.speed)
   1027 			ent->moveinfo.accel = newspeed;
   1028 		else
   1029 			ent->moveinfo.accel *= ratio;
   1030 		if (ent->moveinfo.decel == ent->moveinfo.speed)
   1031 			ent->moveinfo.decel = newspeed;
   1032 		else
   1033 			ent->moveinfo.decel *= ratio;
   1034 		ent->moveinfo.speed = newspeed;
   1035 	}
   1036 }
   1037 
   1038 void Think_SpawnDoorTrigger (edict_t *ent)
   1039 {
   1040 	edict_t		*other;
   1041 	vec3_t		mins, maxs;
   1042 
   1043 	if (ent->flags & FL_TEAMSLAVE)
   1044 		return;		// only the team leader spawns a trigger
   1045 
   1046 	VectorCopy (ent->absmin, mins);
   1047 	VectorCopy (ent->absmax, maxs);
   1048 
   1049 	for (other = ent->teamchain ; other ; other=other->teamchain)
   1050 	{
   1051 		AddPointToBounds (other->absmin, mins, maxs);
   1052 		AddPointToBounds (other->absmax, mins, maxs);
   1053 	}
   1054 
   1055 	// expand 
   1056 	mins[0] -= 60;
   1057 	mins[1] -= 60;
   1058 	maxs[0] += 60;
   1059 	maxs[1] += 60;
   1060 
   1061 	other = G_Spawn ();
   1062 	VectorCopy (mins, other->mins);
   1063 	VectorCopy (maxs, other->maxs);
   1064 	other->owner = ent;
   1065 	other->solid = SOLID_TRIGGER;
   1066 	other->movetype = MOVETYPE_NONE;
   1067 	other->touch = Touch_DoorTrigger;
   1068 	gi.linkentity (other);
   1069 
   1070 	if (ent->spawnflags & DOOR_START_OPEN)
   1071 		door_use_areaportals (ent, true);
   1072 
   1073 	Think_CalcMoveSpeed (ent);
   1074 }
   1075 
   1076 void door_blocked  (edict_t *self, edict_t *other)
   1077 {
   1078 	edict_t	*ent;
   1079 
   1080 	if (!(other->svflags & SVF_MONSTER) && (!other->client) )
   1081 	{
   1082 		// give it a chance to go away on it's own terms (like gibs)
   1083 		T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH);
   1084 		// if it's still there, nuke it
   1085 		if (other)
   1086 			BecomeExplosion1 (other);
   1087 		return;
   1088 	}
   1089 
   1090 	T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
   1091 
   1092 	if (self->spawnflags & DOOR_CRUSHER)
   1093 		return;
   1094 
   1095 
   1096 // if a door has a negative wait, it would never come back if blocked,
   1097 // so let it just squash the object to death real fast
   1098 	if (self->moveinfo.wait >= 0)
   1099 	{
   1100 		if (self->moveinfo.state == STATE_DOWN)
   1101 		{
   1102 			for (ent = self->teammaster ; ent ; ent = ent->teamchain)
   1103 				door_go_up (ent, ent->activator);
   1104 		}
   1105 		else
   1106 		{
   1107 			for (ent = self->teammaster ; ent ; ent = ent->teamchain)
   1108 				door_go_down (ent);
   1109 		}
   1110 	}
   1111 }
   1112 
   1113 void door_killed (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
   1114 {
   1115 	edict_t	*ent;
   1116 
   1117 	for (ent = self->teammaster ; ent ; ent = ent->teamchain)
   1118 	{
   1119 		ent->health = ent->max_health;
   1120 		ent->takedamage = DAMAGE_NO;
   1121 	}
   1122 	door_use (self->teammaster, attacker, attacker);
   1123 }
   1124 
   1125 void door_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
   1126 {
   1127 	if (!other->client)
   1128 		return;
   1129 
   1130 	if (level.time < self->touch_debounce_time)
   1131 		return;
   1132 	self->touch_debounce_time = level.time + 5.0;
   1133 
   1134 	gi.centerprintf (other, "%s", self->message);
   1135 	gi.sound (other, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0);
   1136 }
   1137 
   1138 void SP_func_door (edict_t *ent)
   1139 {
   1140 	vec3_t	abs_movedir;
   1141 
   1142 	if (ent->sounds != 1)
   1143 	{
   1144 		ent->moveinfo.sound_start = gi.soundindex  ("doors/dr1_strt.wav");
   1145 		ent->moveinfo.sound_middle = gi.soundindex  ("doors/dr1_mid.wav");
   1146 		ent->moveinfo.sound_end = gi.soundindex  ("doors/dr1_end.wav");
   1147 	}
   1148 
   1149 	G_SetMovedir (ent->s.angles, ent->movedir);
   1150 	ent->movetype = MOVETYPE_PUSH;
   1151 	ent->solid = SOLID_BSP;
   1152 	gi.setmodel (ent, ent->model);
   1153 
   1154 	ent->blocked = door_blocked;
   1155 	ent->use = door_use;
   1156 	
   1157 	if (!ent->speed)
   1158 		ent->speed = 100;
   1159 	if (deathmatch->value)
   1160 		ent->speed *= 2;
   1161 
   1162 	if (!ent->accel)
   1163 		ent->accel = ent->speed;
   1164 	if (!ent->decel)
   1165 		ent->decel = ent->speed;
   1166 
   1167 	if (!ent->wait)
   1168 		ent->wait = 3;
   1169 	if (!st.lip)
   1170 		st.lip = 8;
   1171 	if (!ent->dmg)
   1172 		ent->dmg = 2;
   1173 
   1174 	// calculate second position
   1175 	VectorCopy (ent->s.origin, ent->pos1);
   1176 	abs_movedir[0] = fabs(ent->movedir[0]);
   1177 	abs_movedir[1] = fabs(ent->movedir[1]);
   1178 	abs_movedir[2] = fabs(ent->movedir[2]);
   1179 	ent->moveinfo.distance = abs_movedir[0] * ent->size[0] + abs_movedir[1] * ent->size[1] + abs_movedir[2] * ent->size[2] - st.lip;
   1180 	VectorMA (ent->pos1, ent->moveinfo.distance, ent->movedir, ent->pos2);
   1181 
   1182 	// if it starts open, switch the positions
   1183 	if (ent->spawnflags & DOOR_START_OPEN)
   1184 	{
   1185 		VectorCopy (ent->pos2, ent->s.origin);
   1186 		VectorCopy (ent->pos1, ent->pos2);
   1187 		VectorCopy (ent->s.origin, ent->pos1);
   1188 	}
   1189 
   1190 	ent->moveinfo.state = STATE_BOTTOM;
   1191 
   1192 	if (ent->health)
   1193 	{
   1194 		ent->takedamage = DAMAGE_YES;
   1195 		ent->die = door_killed;
   1196 		ent->max_health = ent->health;
   1197 	}
   1198 	else if (ent->targetname && ent->message)
   1199 	{
   1200 		gi.soundindex ("misc/talk.wav");
   1201 		ent->touch = door_touch;
   1202 	}
   1203 	
   1204 	ent->moveinfo.speed = ent->speed;
   1205 	ent->moveinfo.accel = ent->accel;
   1206 	ent->moveinfo.decel = ent->decel;
   1207 	ent->moveinfo.wait = ent->wait;
   1208 	VectorCopy (ent->pos1, ent->moveinfo.start_origin);
   1209 	VectorCopy (ent->s.angles, ent->moveinfo.start_angles);
   1210 	VectorCopy (ent->pos2, ent->moveinfo.end_origin);
   1211 	VectorCopy (ent->s.angles, ent->moveinfo.end_angles);
   1212 
   1213 	if (ent->spawnflags & 16)
   1214 		ent->s.effects |= EF_ANIM_ALL;
   1215 	if (ent->spawnflags & 64)
   1216 		ent->s.effects |= EF_ANIM_ALLFAST;
   1217 
   1218 	// to simplify logic elsewhere, make non-teamed doors into a team of one
   1219 	if (!ent->team)
   1220 		ent->teammaster = ent;
   1221 
   1222 	gi.linkentity (ent);
   1223 
   1224 	ent->nextthink = level.time + FRAMETIME;
   1225 	if (ent->health || ent->targetname)
   1226 		ent->think = Think_CalcMoveSpeed;
   1227 	else
   1228 		ent->think = Think_SpawnDoorTrigger;
   1229 }
   1230 
   1231 
   1232 /*QUAKED func_door_rotating (0 .5 .8) ? START_OPEN REVERSE CRUSHER NOMONSTER ANIMATED TOGGLE X_AXIS Y_AXIS
   1233 TOGGLE causes the door to wait in both the start and end states for a trigger event.
   1234 
   1235 START_OPEN	the door to moves to its destination when spawned, and operate in reverse.  It is used to temporarily or permanently close off an area when triggered (not useful for touch or takedamage doors).
   1236 NOMONSTER	monsters will not trigger this door
   1237 
   1238 You need to have an origin brush as part of this entity.  The center of that brush will be
   1239 the point around which it is rotated. It will rotate around the Z axis by default.  You can
   1240 check either the X_AXIS or Y_AXIS box to change that.
   1241 
   1242 "distance" is how many degrees the door will be rotated.
   1243 "speed" determines how fast the door moves; default value is 100.
   1244 
   1245 REVERSE will cause the door to rotate in the opposite direction.
   1246 
   1247 "message"	is printed when the door is touched if it is a trigger door and it hasn't been fired yet
   1248 "angle"		determines the opening direction
   1249 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
   1250 "health"	if set, door must be shot open
   1251 "speed"		movement speed (100 default)
   1252 "wait"		wait before returning (3 default, -1 = never return)
   1253 "dmg"		damage to inflict when blocked (2 default)
   1254 "sounds"
   1255 1)	silent
   1256 2)	light
   1257 3)	medium
   1258 4)	heavy
   1259 */
   1260 
   1261 void SP_func_door_rotating (edict_t *ent)
   1262 {
   1263 	VectorClear (ent->s.angles);
   1264 
   1265 	// set the axis of rotation
   1266 	VectorClear(ent->movedir);
   1267 	if (ent->spawnflags & DOOR_X_AXIS)
   1268 		ent->movedir[2] = 1.0;
   1269 	else if (ent->spawnflags & DOOR_Y_AXIS)
   1270 		ent->movedir[0] = 1.0;
   1271 	else // Z_AXIS
   1272 		ent->movedir[1] = 1.0;
   1273 
   1274 	// check for reverse rotation
   1275 	if (ent->spawnflags & DOOR_REVERSE)
   1276 		VectorNegate (ent->movedir, ent->movedir);
   1277 
   1278 	if (!st.distance)
   1279 	{
   1280 		gi.dprintf("%s at %s with no distance set\n", ent->classname, vtos(ent->s.origin));
   1281 		st.distance = 90;
   1282 	}
   1283 
   1284 	VectorCopy (ent->s.angles, ent->pos1);
   1285 	VectorMA (ent->s.angles, st.distance, ent->movedir, ent->pos2);
   1286 	ent->moveinfo.distance = st.distance;
   1287 
   1288 	ent->movetype = MOVETYPE_PUSH;
   1289 	ent->solid = SOLID_BSP;
   1290 	gi.setmodel (ent, ent->model);
   1291 
   1292 	ent->blocked = door_blocked;
   1293 	ent->use = door_use;
   1294 
   1295 	if (!ent->speed)
   1296 		ent->speed = 100;
   1297 	if (!ent->accel)
   1298 		ent->accel = ent->speed;
   1299 	if (!ent->decel)
   1300 		ent->decel = ent->speed;
   1301 
   1302 	if (!ent->wait)
   1303 		ent->wait = 3;
   1304 	if (!ent->dmg)
   1305 		ent->dmg = 2;
   1306 
   1307 	if (ent->sounds != 1)
   1308 	{
   1309 		ent->moveinfo.sound_start = gi.soundindex  ("doors/dr1_strt.wav");
   1310 		ent->moveinfo.sound_middle = gi.soundindex  ("doors/dr1_mid.wav");
   1311 		ent->moveinfo.sound_end = gi.soundindex  ("doors/dr1_end.wav");
   1312 	}
   1313 
   1314 	// if it starts open, switch the positions
   1315 	if (ent->spawnflags & DOOR_START_OPEN)
   1316 	{
   1317 		VectorCopy (ent->pos2, ent->s.angles);
   1318 		VectorCopy (ent->pos1, ent->pos2);
   1319 		VectorCopy (ent->s.angles, ent->pos1);
   1320 		VectorNegate (ent->movedir, ent->movedir);
   1321 	}
   1322 
   1323 	if (ent->health)
   1324 	{
   1325 		ent->takedamage = DAMAGE_YES;
   1326 		ent->die = door_killed;
   1327 		ent->max_health = ent->health;
   1328 	}
   1329 	
   1330 	if (ent->targetname && ent->message)
   1331 	{
   1332 		gi.soundindex ("misc/talk.wav");
   1333 		ent->touch = door_touch;
   1334 	}
   1335 
   1336 	ent->moveinfo.state = STATE_BOTTOM;
   1337 	ent->moveinfo.speed = ent->speed;
   1338 	ent->moveinfo.accel = ent->accel;
   1339 	ent->moveinfo.decel = ent->decel;
   1340 	ent->moveinfo.wait = ent->wait;
   1341 	VectorCopy (ent->s.origin, ent->moveinfo.start_origin);
   1342 	VectorCopy (ent->pos1, ent->moveinfo.start_angles);
   1343 	VectorCopy (ent->s.origin, ent->moveinfo.end_origin);
   1344 	VectorCopy (ent->pos2, ent->moveinfo.end_angles);
   1345 
   1346 	if (ent->spawnflags & 16)
   1347 		ent->s.effects |= EF_ANIM_ALL;
   1348 
   1349 	// to simplify logic elsewhere, make non-teamed doors into a team of one
   1350 	if (!ent->team)
   1351 		ent->teammaster = ent;
   1352 
   1353 	gi.linkentity (ent);
   1354 
   1355 	ent->nextthink = level.time + FRAMETIME;
   1356 	if (ent->health || ent->targetname)
   1357 		ent->think = Think_CalcMoveSpeed;
   1358 	else
   1359 		ent->think = Think_SpawnDoorTrigger;
   1360 }
   1361 
   1362 
   1363 /*QUAKED func_water (0 .5 .8) ? START_OPEN
   1364 func_water is a moveable water brush.  It must be targeted to operate.  Use a non-water texture at your own risk.
   1365 
   1366 START_OPEN causes the water to move to its destination when spawned and operate in reverse.
   1367 
   1368 "angle"		determines the opening direction (up or down only)
   1369 "speed"		movement speed (25 default)
   1370 "wait"		wait before returning (-1 default, -1 = TOGGLE)
   1371 "lip"		lip remaining at end of move (0 default)
   1372 "sounds"	(yes, these need to be changed)
   1373 0)	no sound
   1374 1)	water
   1375 2)	lava
   1376 */
   1377 
   1378 void SP_func_water (edict_t *self)
   1379 {
   1380 	vec3_t	abs_movedir;
   1381 
   1382 	G_SetMovedir (self->s.angles, self->movedir);
   1383 	self->movetype = MOVETYPE_PUSH;
   1384 	self->solid = SOLID_BSP;
   1385 	gi.setmodel (self, self->model);
   1386 
   1387 	switch (self->sounds)
   1388 	{
   1389 		default:
   1390 			break;
   1391 
   1392 		case 1: // water
   1393 			self->moveinfo.sound_start = gi.soundindex  ("world/mov_watr.wav");
   1394 			self->moveinfo.sound_end = gi.soundindex  ("world/stp_watr.wav");
   1395 			break;
   1396 
   1397 		case 2: // lava
   1398 			self->moveinfo.sound_start = gi.soundindex  ("world/mov_watr.wav");
   1399 			self->moveinfo.sound_end = gi.soundindex  ("world/stp_watr.wav");
   1400 			break;
   1401 	}
   1402 
   1403 	// calculate second position
   1404 	VectorCopy (self->s.origin, self->pos1);
   1405 	abs_movedir[0] = fabs(self->movedir[0]);
   1406 	abs_movedir[1] = fabs(self->movedir[1]);
   1407 	abs_movedir[2] = fabs(self->movedir[2]);
   1408 	self->moveinfo.distance = abs_movedir[0] * self->size[0] + abs_movedir[1] * self->size[1] + abs_movedir[2] * self->size[2] - st.lip;
   1409 	VectorMA (self->pos1, self->moveinfo.distance, self->movedir, self->pos2);
   1410 
   1411 	// if it starts open, switch the positions
   1412 	if (self->spawnflags & DOOR_START_OPEN)
   1413 	{
   1414 		VectorCopy (self->pos2, self->s.origin);
   1415 		VectorCopy (self->pos1, self->pos2);
   1416 		VectorCopy (self->s.origin, self->pos1);
   1417 	}
   1418 
   1419 	VectorCopy (self->pos1, self->moveinfo.start_origin);
   1420 	VectorCopy (self->s.angles, self->moveinfo.start_angles);
   1421 	VectorCopy (self->pos2, self->moveinfo.end_origin);
   1422 	VectorCopy (self->s.angles, self->moveinfo.end_angles);
   1423 
   1424 	self->moveinfo.state = STATE_BOTTOM;
   1425 
   1426 	if (!self->speed)
   1427 		self->speed = 25;
   1428 	self->moveinfo.accel = self->moveinfo.decel = self->moveinfo.speed = self->speed;
   1429 
   1430 	if (!self->wait)
   1431 		self->wait = -1;
   1432 	self->moveinfo.wait = self->wait;
   1433 
   1434 	self->use = door_use;
   1435 
   1436 	if (self->wait == -1)
   1437 		self->spawnflags |= DOOR_TOGGLE;
   1438 
   1439 	self->classname = "func_door";
   1440 
   1441 	gi.linkentity (self);
   1442 }
   1443 
   1444 
   1445 #define TRAIN_START_ON		1
   1446 #define TRAIN_TOGGLE		2
   1447 #define TRAIN_BLOCK_STOPS	4
   1448 
   1449 /*QUAKED func_train (0 .5 .8) ? START_ON TOGGLE BLOCK_STOPS
   1450 Trains are moving platforms that players can ride.
   1451 The targets origin specifies the min point of the train at each corner.
   1452 The train spawns at the first target it is pointing at.
   1453 If the train is the target of a button or trigger, it will not begin moving until activated.
   1454 speed	default 100
   1455 dmg		default	2
   1456 noise	looping sound to play when the train is in motion
   1457 
   1458 */
   1459 void train_next (edict_t *self);
   1460 
   1461 void train_blocked (edict_t *self, edict_t *other)
   1462 {
   1463 	if (!(other->svflags & SVF_MONSTER) && (!other->client) )
   1464 	{
   1465 		// give it a chance to go away on it's own terms (like gibs)
   1466 		T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH);
   1467 		// if it's still there, nuke it
   1468 		if (other)
   1469 			BecomeExplosion1 (other);
   1470 		return;
   1471 	}
   1472 
   1473 	if (level.time < self->touch_debounce_time)
   1474 		return;
   1475 
   1476 	if (!self->dmg)
   1477 		return;
   1478 	self->touch_debounce_time = level.time + 0.5;
   1479 	T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
   1480 }
   1481 
   1482 void train_wait (edict_t *self)
   1483 {
   1484 	if (self->target_ent->pathtarget)
   1485 	{
   1486 		char	*savetarget;
   1487 		edict_t	*ent;
   1488 
   1489 		ent = self->target_ent;
   1490 		savetarget = ent->target;
   1491 		ent->target = ent->pathtarget;
   1492 		G_UseTargets (ent, self->activator);
   1493 		ent->target = savetarget;
   1494 
   1495 		// make sure we didn't get killed by a killtarget
   1496 		if (!self->inuse)
   1497 			return;
   1498 	}
   1499 
   1500 	if (self->moveinfo.wait)
   1501 	{
   1502 		if (self->moveinfo.wait > 0)
   1503 		{
   1504 			self->nextthink = level.time + self->moveinfo.wait;
   1505 			self->think = train_next;
   1506 		}
   1507 		else if (self->spawnflags & TRAIN_TOGGLE)  // && wait < 0
   1508 		{
   1509 			train_next (self);
   1510 			self->spawnflags &= ~TRAIN_START_ON;
   1511 			VectorClear (self->velocity);
   1512 			self->nextthink = 0;
   1513 		}
   1514 
   1515 		if (!(self->flags & FL_TEAMSLAVE))
   1516 		{
   1517 			if (self->moveinfo.sound_end)
   1518 				gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC, 0);
   1519 			self->s.sound = 0;
   1520 		}
   1521 	}
   1522 	else
   1523 	{
   1524 		train_next (self);
   1525 	}
   1526 	
   1527 }
   1528 
   1529 void train_next (edict_t *self)
   1530 {
   1531 	edict_t		*ent;
   1532 	vec3_t		dest;
   1533 	qboolean	first;
   1534 
   1535 	first = true;
   1536 again:
   1537 	if (!self->target)
   1538 	{
   1539 //		gi.dprintf ("train_next: no next target\n");
   1540 		return;
   1541 	}
   1542 
   1543 	ent = G_PickTarget (self->target);
   1544 	if (!ent)
   1545 	{
   1546 		gi.dprintf ("train_next: bad target %s\n", self->target);
   1547 		return;
   1548 	}
   1549 
   1550 	self->target = ent->target;
   1551 
   1552 	// check for a teleport path_corner
   1553 	if (ent->spawnflags & 1)
   1554 	{
   1555 		if (!first)
   1556 		{
   1557 			gi.dprintf ("connected teleport path_corners, see %s at %s\n", ent->classname, vtos(ent->s.origin));
   1558 			return;
   1559 		}
   1560 		first = false;
   1561 		VectorSubtract (ent->s.origin, self->mins, self->s.origin);
   1562 		VectorCopy (self->s.origin, self->s.old_origin);
   1563 		gi.linkentity (self);
   1564 		goto again;
   1565 	}
   1566 
   1567 	self->moveinfo.wait = ent->wait;
   1568 	self->target_ent = ent;
   1569 
   1570 	if (!(self->flags & FL_TEAMSLAVE))
   1571 	{
   1572 		if (self->moveinfo.sound_start)
   1573 			gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0);
   1574 		self->s.sound = self->moveinfo.sound_middle;
   1575 	}
   1576 
   1577 	VectorSubtract (ent->s.origin, self->mins, dest);
   1578 	self->moveinfo.state = STATE_TOP;
   1579 	VectorCopy (self->s.origin, self->moveinfo.start_origin);
   1580 	VectorCopy (dest, self->moveinfo.end_origin);
   1581 	Move_Calc (self, dest, train_wait);
   1582 	self->spawnflags |= TRAIN_START_ON;
   1583 }
   1584 
   1585 void train_resume (edict_t *self)
   1586 {
   1587 	edict_t	*ent;
   1588 	vec3_t	dest;
   1589 
   1590 	ent = self->target_ent;
   1591 
   1592 	VectorSubtract (ent->s.origin, self->mins, dest);
   1593 	self->moveinfo.state = STATE_TOP;
   1594 	VectorCopy (self->s.origin, self->moveinfo.start_origin);
   1595 	VectorCopy (dest, self->moveinfo.end_origin);
   1596 	Move_Calc (self, dest, train_wait);
   1597 	self->spawnflags |= TRAIN_START_ON;
   1598 }
   1599 
   1600 void func_train_find (edict_t *self)
   1601 {
   1602 	edict_t *ent;
   1603 
   1604 	if (!self->target)
   1605 	{
   1606 		gi.dprintf ("train_find: no target\n");
   1607 		return;
   1608 	}
   1609 	ent = G_PickTarget (self->target);
   1610 	if (!ent)
   1611 	{
   1612 		gi.dprintf ("train_find: target %s not found\n", self->target);
   1613 		return;
   1614 	}
   1615 	self->target = ent->target;
   1616 
   1617 	VectorSubtract (ent->s.origin, self->mins, self->s.origin);
   1618 	gi.linkentity (self);
   1619 
   1620 	// if not triggered, start immediately
   1621 	if (!self->targetname)
   1622 		self->spawnflags |= TRAIN_START_ON;
   1623 
   1624 	if (self->spawnflags & TRAIN_START_ON)
   1625 	{
   1626 		self->nextthink = level.time + FRAMETIME;
   1627 		self->think = train_next;
   1628 		self->activator = self;
   1629 	}
   1630 }
   1631 
   1632 void train_use (edict_t *self, edict_t *other, edict_t *activator)
   1633 {
   1634 	self->activator = activator;
   1635 
   1636 	if (self->spawnflags & TRAIN_START_ON)
   1637 	{
   1638 		if (!(self->spawnflags & TRAIN_TOGGLE))
   1639 			return;
   1640 		self->spawnflags &= ~TRAIN_START_ON;
   1641 		VectorClear (self->velocity);
   1642 		self->nextthink = 0;
   1643 	}
   1644 	else
   1645 	{
   1646 		if (self->target_ent)
   1647 			train_resume(self);
   1648 		else
   1649 			train_next(self);
   1650 	}
   1651 }
   1652 
   1653 void SP_func_train (edict_t *self)
   1654 {
   1655 	self->movetype = MOVETYPE_PUSH;
   1656 
   1657 	VectorClear (self->s.angles);
   1658 	self->blocked = train_blocked;
   1659 	if (self->spawnflags & TRAIN_BLOCK_STOPS)
   1660 		self->dmg = 0;
   1661 	else
   1662 	{
   1663 		if (!self->dmg)
   1664 			self->dmg = 100;
   1665 	}
   1666 	self->solid = SOLID_BSP;
   1667 	gi.setmodel (self, self->model);
   1668 
   1669 	if (st.noise)
   1670 		self->moveinfo.sound_middle = gi.soundindex  (st.noise);
   1671 
   1672 	if (!self->speed)
   1673 		self->speed = 100;
   1674 
   1675 	self->moveinfo.speed = self->speed;
   1676 	self->moveinfo.accel = self->moveinfo.decel = self->moveinfo.speed;
   1677 
   1678 	self->use = train_use;
   1679 
   1680 	gi.linkentity (self);
   1681 
   1682 	if (self->target)
   1683 	{
   1684 		// start trains on the second frame, to make sure their targets have had
   1685 		// a chance to spawn
   1686 		self->nextthink = level.time + FRAMETIME;
   1687 		self->think = func_train_find;
   1688 	}
   1689 	else
   1690 	{
   1691 		gi.dprintf ("func_train without a target at %s\n", vtos(self->absmin));
   1692 	}
   1693 }
   1694 
   1695 
   1696 /*QUAKED trigger_elevator (0.3 0.1 0.6) (-8 -8 -8) (8 8 8)
   1697 */
   1698 void trigger_elevator_use (edict_t *self, edict_t *other, edict_t *activator)
   1699 {
   1700 	edict_t *target;
   1701 
   1702 	if (self->movetarget->nextthink)
   1703 	{
   1704 //		gi.dprintf("elevator busy\n");
   1705 		return;
   1706 	}
   1707 
   1708 	if (!other->pathtarget)
   1709 	{
   1710 		gi.dprintf("elevator used with no pathtarget\n");
   1711 		return;
   1712 	}
   1713 
   1714 	target = G_PickTarget (other->pathtarget);
   1715 	if (!target)
   1716 	{
   1717 		gi.dprintf("elevator used with bad pathtarget: %s\n", other->pathtarget);
   1718 		return;
   1719 	}
   1720 
   1721 	self->movetarget->target_ent = target;
   1722 	train_resume (self->movetarget);
   1723 }
   1724 
   1725 void trigger_elevator_init (edict_t *self)
   1726 {
   1727 	if (!self->target)
   1728 	{
   1729 		gi.dprintf("trigger_elevator has no target\n");
   1730 		return;
   1731 	}
   1732 	self->movetarget = G_PickTarget (self->target);
   1733 	if (!self->movetarget)
   1734 	{
   1735 		gi.dprintf("trigger_elevator unable to find target %s\n", self->target);
   1736 		return;
   1737 	}
   1738 	if (strcmp(self->movetarget->classname, "func_train") != 0)
   1739 	{
   1740 		gi.dprintf("trigger_elevator target %s is not a train\n", self->target);
   1741 		return;
   1742 	}
   1743 
   1744 	self->use = trigger_elevator_use;
   1745 	self->svflags = SVF_NOCLIENT;
   1746 
   1747 }
   1748 
   1749 void SP_trigger_elevator (edict_t *self)
   1750 {
   1751 	self->think = trigger_elevator_init;
   1752 	self->nextthink = level.time + FRAMETIME;
   1753 }
   1754 
   1755 
   1756 /*QUAKED func_timer (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) START_ON
   1757 "wait"			base time between triggering all targets, default is 1
   1758 "random"		wait variance, default is 0
   1759 
   1760 so, the basic time between firing is a random time between
   1761 (wait - random) and (wait + random)
   1762 
   1763 "delay"			delay before first firing when turned on, default is 0
   1764 
   1765 "pausetime"		additional delay used only the very first time
   1766 				and only if spawned with START_ON
   1767 
   1768 These can used but not touched.
   1769 */
   1770 void func_timer_think (edict_t *self)
   1771 {
   1772 	G_UseTargets (self, self->activator);
   1773 	self->nextthink = level.time + self->wait + crandom() * self->random;
   1774 }
   1775 
   1776 void func_timer_use (edict_t *self, edict_t *other, edict_t *activator)
   1777 {
   1778 	self->activator = activator;
   1779 
   1780 	// if on, turn it off
   1781 	if (self->nextthink)
   1782 	{
   1783 		self->nextthink = 0;
   1784 		return;
   1785 	}
   1786 
   1787 	// turn it on
   1788 	if (self->delay)
   1789 		self->nextthink = level.time + self->delay;
   1790 	else
   1791 		func_timer_think (self);
   1792 }
   1793 
   1794 void SP_func_timer (edict_t *self)
   1795 {
   1796 	if (!self->wait)
   1797 		self->wait = 1.0;
   1798 
   1799 	self->use = func_timer_use;
   1800 	self->think = func_timer_think;
   1801 
   1802 	if (self->random >= self->wait)
   1803 	{
   1804 		self->random = self->wait - FRAMETIME;
   1805 		gi.dprintf("func_timer at %s has random >= wait\n", vtos(self->s.origin));
   1806 	}
   1807 
   1808 	if (self->spawnflags & 1)
   1809 	{
   1810 		self->nextthink = level.time + 1.0 + st.pausetime + self->delay + self->wait + crandom() * self->random;
   1811 		self->activator = self;
   1812 	}
   1813 
   1814 	self->svflags = SVF_NOCLIENT;
   1815 }
   1816 
   1817 
   1818 /*QUAKED func_conveyor (0 .5 .8) ? START_ON TOGGLE
   1819 Conveyors are stationary brushes that move what's on them.
   1820 The brush should be have a surface with at least one current content enabled.
   1821 speed	default 100
   1822 */
   1823 
   1824 void func_conveyor_use (edict_t *self, edict_t *other, edict_t *activator)
   1825 {
   1826 	if (self->spawnflags & 1)
   1827 	{
   1828 		self->speed = 0;
   1829 		self->spawnflags &= ~1;
   1830 	}
   1831 	else
   1832 	{
   1833 		self->speed = self->count;
   1834 		self->spawnflags |= 1;
   1835 	}
   1836 
   1837 	if (!(self->spawnflags & 2))
   1838 		self->count = 0;
   1839 }
   1840 
   1841 void SP_func_conveyor (edict_t *self)
   1842 {
   1843 	if (!self->speed)
   1844 		self->speed = 100;
   1845 
   1846 	if (!(self->spawnflags & 1))
   1847 	{
   1848 		self->count = self->speed;
   1849 		self->speed = 0;
   1850 	}
   1851 
   1852 	self->use = func_conveyor_use;
   1853 
   1854 	gi.setmodel (self, self->model);
   1855 	self->solid = SOLID_BSP;
   1856 	gi.linkentity (self);
   1857 }
   1858 
   1859 
   1860 /*QUAKED func_door_secret (0 .5 .8) ? always_shoot 1st_left 1st_down
   1861 A secret door.  Slide back and then to the side.
   1862 
   1863 open_once		doors never closes
   1864 1st_left		1st move is left of arrow
   1865 1st_down		1st move is down from arrow
   1866 always_shoot	door is shootebale even if targeted
   1867 
   1868 "angle"		determines the direction
   1869 "dmg"		damage to inflic when blocked (default 2)
   1870 "wait"		how long to hold in the open position (default 5, -1 means hold)
   1871 */
   1872 
   1873 #define SECRET_ALWAYS_SHOOT	1
   1874 #define SECRET_1ST_LEFT		2
   1875 #define SECRET_1ST_DOWN		4
   1876 
   1877 void door_secret_move1 (edict_t *self);
   1878 void door_secret_move2 (edict_t *self);
   1879 void door_secret_move3 (edict_t *self);
   1880 void door_secret_move4 (edict_t *self);
   1881 void door_secret_move5 (edict_t *self);
   1882 void door_secret_move6 (edict_t *self);
   1883 void door_secret_done (edict_t *self);
   1884 
   1885 void door_secret_use (edict_t *self, edict_t *other, edict_t *activator)
   1886 {
   1887 	// make sure we're not already moving
   1888 	if (!VectorCompare(self->s.origin, vec3_origin))
   1889 		return;
   1890 
   1891 	Move_Calc (self, self->pos1, door_secret_move1);
   1892 	door_use_areaportals (self, true);
   1893 }
   1894 
   1895 void door_secret_move1 (edict_t *self)
   1896 {
   1897 	self->nextthink = level.time + 1.0;
   1898 	self->think = door_secret_move2;
   1899 }
   1900 
   1901 void door_secret_move2 (edict_t *self)
   1902 {
   1903 	Move_Calc (self, self->pos2, door_secret_move3);
   1904 }
   1905 
   1906 void door_secret_move3 (edict_t *self)
   1907 {
   1908 	if (self->wait == -1)
   1909 		return;
   1910 	self->nextthink = level.time + self->wait;
   1911 	self->think = door_secret_move4;
   1912 }
   1913 
   1914 void door_secret_move4 (edict_t *self)
   1915 {
   1916 	Move_Calc (self, self->pos1, door_secret_move5);
   1917 }
   1918 
   1919 void door_secret_move5 (edict_t *self)
   1920 {
   1921 	self->nextthink = level.time + 1.0;
   1922 	self->think = door_secret_move6;
   1923 }
   1924 
   1925 void door_secret_move6 (edict_t *self)
   1926 {
   1927 	Move_Calc (self, vec3_origin, door_secret_done);
   1928 }
   1929 
   1930 void door_secret_done (edict_t *self)
   1931 {
   1932 	if (!(self->targetname) || (self->spawnflags & SECRET_ALWAYS_SHOOT))
   1933 	{
   1934 		self->health = 0;
   1935 		self->takedamage = DAMAGE_YES;
   1936 	}
   1937 	door_use_areaportals (self, false);
   1938 }
   1939 
   1940 void door_secret_blocked  (edict_t *self, edict_t *other)
   1941 {
   1942 	if (!(other->svflags & SVF_MONSTER) && (!other->client) )
   1943 	{
   1944 		// give it a chance to go away on it's own terms (like gibs)
   1945 		T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH);
   1946 		// if it's still there, nuke it
   1947 		if (other)
   1948 			BecomeExplosion1 (other);
   1949 		return;
   1950 	}
   1951 
   1952 	if (level.time < self->touch_debounce_time)
   1953 		return;
   1954 	self->touch_debounce_time = level.time + 0.5;
   1955 
   1956 	T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
   1957 }
   1958 
   1959 void door_secret_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
   1960 {
   1961 	self->takedamage = DAMAGE_NO;
   1962 	door_secret_use (self, attacker, attacker);
   1963 }
   1964 
   1965 void SP_func_door_secret (edict_t *ent)
   1966 {
   1967 	vec3_t	forward, right, up;
   1968 	float	side;
   1969 	float	width;
   1970 	float	length;
   1971 
   1972 	ent->moveinfo.sound_start = gi.soundindex  ("doors/dr1_strt.wav");
   1973 	ent->moveinfo.sound_middle = gi.soundindex  ("doors/dr1_mid.wav");
   1974 	ent->moveinfo.sound_end = gi.soundindex  ("doors/dr1_end.wav");
   1975 
   1976 	ent->movetype = MOVETYPE_PUSH;
   1977 	ent->solid = SOLID_BSP;
   1978 	gi.setmodel (ent, ent->model);
   1979 
   1980 	ent->blocked = door_secret_blocked;
   1981 	ent->use = door_secret_use;
   1982 
   1983 	if (!(ent->targetname) || (ent->spawnflags & SECRET_ALWAYS_SHOOT))
   1984 	{
   1985 		ent->health = 0;
   1986 		ent->takedamage = DAMAGE_YES;
   1987 		ent->die = door_secret_die;
   1988 	}
   1989 
   1990 	if (!ent->dmg)
   1991 		ent->dmg = 2;
   1992 
   1993 	if (!ent->wait)
   1994 		ent->wait = 5;
   1995 
   1996 	ent->moveinfo.accel =
   1997 	ent->moveinfo.decel =
   1998 	ent->moveinfo.speed = 50;
   1999 
   2000 	// calculate positions
   2001 	AngleVectors (ent->s.angles, forward, right, up);
   2002 	VectorClear (ent->s.angles);
   2003 	side = 1.0 - (ent->spawnflags & SECRET_1ST_LEFT);
   2004 	if (ent->spawnflags & SECRET_1ST_DOWN)
   2005 		width = fabs(DotProduct(up, ent->size));
   2006 	else
   2007 		width = fabs(DotProduct(right, ent->size));
   2008 	length = fabs(DotProduct(forward, ent->size));
   2009 	if (ent->spawnflags & SECRET_1ST_DOWN)
   2010 		VectorMA (ent->s.origin, -1 * width, up, ent->pos1);
   2011 	else
   2012 		VectorMA (ent->s.origin, side * width, right, ent->pos1);
   2013 	VectorMA (ent->pos1, length, forward, ent->pos2);
   2014 
   2015 	if (ent->health)
   2016 	{
   2017 		ent->takedamage = DAMAGE_YES;
   2018 		ent->die = door_killed;
   2019 		ent->max_health = ent->health;
   2020 	}
   2021 	else if (ent->targetname && ent->message)
   2022 	{
   2023 		gi.soundindex ("misc/talk.wav");
   2024 		ent->touch = door_touch;
   2025 	}
   2026 	
   2027 	ent->classname = "func_door";
   2028 
   2029 	gi.linkentity (ent);
   2030 }
   2031 
   2032 
   2033 /*QUAKED func_killbox (1 0 0) ?
   2034 Kills everything inside when fired, irrespective of protection.
   2035 */
   2036 void use_killbox (edict_t *self, edict_t *other, edict_t *activator)
   2037 {
   2038 	KillBox (self);
   2039 }
   2040 
   2041 void SP_func_killbox (edict_t *ent)
   2042 {
   2043 	gi.setmodel (ent, ent->model);
   2044 	ent->use = use_killbox;
   2045 	ent->svflags = SVF_NOCLIENT;
   2046 }
   2047