Quake-2

Quake 2 GPL Source Release
Log | Files | Refs

g_func.c (51174B)


      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   already 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 		self->s.event = EV_OTHER_TELEPORT;
   1564 		gi.linkentity (self);
   1565 		goto again;
   1566 	}
   1567 
   1568 	self->moveinfo.wait = ent->wait;
   1569 	self->target_ent = ent;
   1570 
   1571 	if (!(self->flags & FL_TEAMSLAVE))
   1572 	{
   1573 		if (self->moveinfo.sound_start)
   1574 			gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0);
   1575 		self->s.sound = self->moveinfo.sound_middle;
   1576 	}
   1577 
   1578 	VectorSubtract (ent->s.origin, self->mins, dest);
   1579 	self->moveinfo.state = STATE_TOP;
   1580 	VectorCopy (self->s.origin, self->moveinfo.start_origin);
   1581 	VectorCopy (dest, self->moveinfo.end_origin);
   1582 	Move_Calc (self, dest, train_wait);
   1583 	self->spawnflags |= TRAIN_START_ON;
   1584 }
   1585 
   1586 void train_resume (edict_t *self)
   1587 {
   1588 	edict_t	*ent;
   1589 	vec3_t	dest;
   1590 
   1591 	ent = self->target_ent;
   1592 
   1593 	VectorSubtract (ent->s.origin, self->mins, dest);
   1594 	self->moveinfo.state = STATE_TOP;
   1595 	VectorCopy (self->s.origin, self->moveinfo.start_origin);
   1596 	VectorCopy (dest, self->moveinfo.end_origin);
   1597 	Move_Calc (self, dest, train_wait);
   1598 	self->spawnflags |= TRAIN_START_ON;
   1599 }
   1600 
   1601 void func_train_find (edict_t *self)
   1602 {
   1603 	edict_t *ent;
   1604 
   1605 	if (!self->target)
   1606 	{
   1607 		gi.dprintf ("train_find: no target\n");
   1608 		return;
   1609 	}
   1610 	ent = G_PickTarget (self->target);
   1611 	if (!ent)
   1612 	{
   1613 		gi.dprintf ("train_find: target %s not found\n", self->target);
   1614 		return;
   1615 	}
   1616 	self->target = ent->target;
   1617 
   1618 	VectorSubtract (ent->s.origin, self->mins, self->s.origin);
   1619 	gi.linkentity (self);
   1620 
   1621 	// if not triggered, start immediately
   1622 	if (!self->targetname)
   1623 		self->spawnflags |= TRAIN_START_ON;
   1624 
   1625 	if (self->spawnflags & TRAIN_START_ON)
   1626 	{
   1627 		self->nextthink = level.time + FRAMETIME;
   1628 		self->think = train_next;
   1629 		self->activator = self;
   1630 	}
   1631 }
   1632 
   1633 void train_use (edict_t *self, edict_t *other, edict_t *activator)
   1634 {
   1635 	self->activator = activator;
   1636 
   1637 	if (self->spawnflags & TRAIN_START_ON)
   1638 	{
   1639 		if (!(self->spawnflags & TRAIN_TOGGLE))
   1640 			return;
   1641 		self->spawnflags &= ~TRAIN_START_ON;
   1642 		VectorClear (self->velocity);
   1643 		self->nextthink = 0;
   1644 	}
   1645 	else
   1646 	{
   1647 		if (self->target_ent)
   1648 			train_resume(self);
   1649 		else
   1650 			train_next(self);
   1651 	}
   1652 }
   1653 
   1654 void SP_func_train (edict_t *self)
   1655 {
   1656 	self->movetype = MOVETYPE_PUSH;
   1657 
   1658 	VectorClear (self->s.angles);
   1659 	self->blocked = train_blocked;
   1660 	if (self->spawnflags & TRAIN_BLOCK_STOPS)
   1661 		self->dmg = 0;
   1662 	else
   1663 	{
   1664 		if (!self->dmg)
   1665 			self->dmg = 100;
   1666 	}
   1667 	self->solid = SOLID_BSP;
   1668 	gi.setmodel (self, self->model);
   1669 
   1670 	if (st.noise)
   1671 		self->moveinfo.sound_middle = gi.soundindex  (st.noise);
   1672 
   1673 	if (!self->speed)
   1674 		self->speed = 100;
   1675 
   1676 	self->moveinfo.speed = self->speed;
   1677 	self->moveinfo.accel = self->moveinfo.decel = self->moveinfo.speed;
   1678 
   1679 	self->use = train_use;
   1680 
   1681 	gi.linkentity (self);
   1682 
   1683 	if (self->target)
   1684 	{
   1685 		// start trains on the second frame, to make sure their targets have had
   1686 		// a chance to spawn
   1687 		self->nextthink = level.time + FRAMETIME;
   1688 		self->think = func_train_find;
   1689 	}
   1690 	else
   1691 	{
   1692 		gi.dprintf ("func_train without a target at %s\n", vtos(self->absmin));
   1693 	}
   1694 }
   1695 
   1696 
   1697 /*QUAKED trigger_elevator (0.3 0.1 0.6) (-8 -8 -8) (8 8 8)
   1698 */
   1699 void trigger_elevator_use (edict_t *self, edict_t *other, edict_t *activator)
   1700 {
   1701 	edict_t *target;
   1702 
   1703 	if (self->movetarget->nextthink)
   1704 	{
   1705 //		gi.dprintf("elevator busy\n");
   1706 		return;
   1707 	}
   1708 
   1709 	if (!other->pathtarget)
   1710 	{
   1711 		gi.dprintf("elevator used with no pathtarget\n");
   1712 		return;
   1713 	}
   1714 
   1715 	target = G_PickTarget (other->pathtarget);
   1716 	if (!target)
   1717 	{
   1718 		gi.dprintf("elevator used with bad pathtarget: %s\n", other->pathtarget);
   1719 		return;
   1720 	}
   1721 
   1722 	self->movetarget->target_ent = target;
   1723 	train_resume (self->movetarget);
   1724 }
   1725 
   1726 void trigger_elevator_init (edict_t *self)
   1727 {
   1728 	if (!self->target)
   1729 	{
   1730 		gi.dprintf("trigger_elevator has no target\n");
   1731 		return;
   1732 	}
   1733 	self->movetarget = G_PickTarget (self->target);
   1734 	if (!self->movetarget)
   1735 	{
   1736 		gi.dprintf("trigger_elevator unable to find target %s\n", self->target);
   1737 		return;
   1738 	}
   1739 	if (strcmp(self->movetarget->classname, "func_train") != 0)
   1740 	{
   1741 		gi.dprintf("trigger_elevator target %s is not a train\n", self->target);
   1742 		return;
   1743 	}
   1744 
   1745 	self->use = trigger_elevator_use;
   1746 	self->svflags = SVF_NOCLIENT;
   1747 
   1748 }
   1749 
   1750 void SP_trigger_elevator (edict_t *self)
   1751 {
   1752 	self->think = trigger_elevator_init;
   1753 	self->nextthink = level.time + FRAMETIME;
   1754 }
   1755 
   1756 
   1757 /*QUAKED func_timer (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) START_ON
   1758 "wait"			base time between triggering all targets, default is 1
   1759 "random"		wait variance, default is 0
   1760 
   1761 so, the basic time between firing is a random time between
   1762 (wait - random) and (wait + random)
   1763 
   1764 "delay"			delay before first firing when turned on, default is 0
   1765 
   1766 "pausetime"		additional delay used only the very first time
   1767 				and only if spawned with START_ON
   1768 
   1769 These can used but not touched.
   1770 */
   1771 void func_timer_think (edict_t *self)
   1772 {
   1773 	G_UseTargets (self, self->activator);
   1774 	self->nextthink = level.time + self->wait + crandom() * self->random;
   1775 }
   1776 
   1777 void func_timer_use (edict_t *self, edict_t *other, edict_t *activator)
   1778 {
   1779 	self->activator = activator;
   1780 
   1781 	// if on, turn it off
   1782 	if (self->nextthink)
   1783 	{
   1784 		self->nextthink = 0;
   1785 		return;
   1786 	}
   1787 
   1788 	// turn it on
   1789 	if (self->delay)
   1790 		self->nextthink = level.time + self->delay;
   1791 	else
   1792 		func_timer_think (self);
   1793 }
   1794 
   1795 void SP_func_timer (edict_t *self)
   1796 {
   1797 	if (!self->wait)
   1798 		self->wait = 1.0;
   1799 
   1800 	self->use = func_timer_use;
   1801 	self->think = func_timer_think;
   1802 
   1803 	if (self->random >= self->wait)
   1804 	{
   1805 		self->random = self->wait - FRAMETIME;
   1806 		gi.dprintf("func_timer at %s has random >= wait\n", vtos(self->s.origin));
   1807 	}
   1808 
   1809 	if (self->spawnflags & 1)
   1810 	{
   1811 		self->nextthink = level.time + 1.0 + st.pausetime + self->delay + self->wait + crandom() * self->random;
   1812 		self->activator = self;
   1813 	}
   1814 
   1815 	self->svflags = SVF_NOCLIENT;
   1816 }
   1817 
   1818 
   1819 /*QUAKED func_conveyor (0 .5 .8) ? START_ON TOGGLE
   1820 Conveyors are stationary brushes that move what's on them.
   1821 The brush should be have a surface with at least one current content enabled.
   1822 speed	default 100
   1823 */
   1824 
   1825 void func_conveyor_use (edict_t *self, edict_t *other, edict_t *activator)
   1826 {
   1827 	if (self->spawnflags & 1)
   1828 	{
   1829 		self->speed = 0;
   1830 		self->spawnflags &= ~1;
   1831 	}
   1832 	else
   1833 	{
   1834 		self->speed = self->count;
   1835 		self->spawnflags |= 1;
   1836 	}
   1837 
   1838 	if (!(self->spawnflags & 2))
   1839 		self->count = 0;
   1840 }
   1841 
   1842 void SP_func_conveyor (edict_t *self)
   1843 {
   1844 	if (!self->speed)
   1845 		self->speed = 100;
   1846 
   1847 	if (!(self->spawnflags & 1))
   1848 	{
   1849 		self->count = self->speed;
   1850 		self->speed = 0;
   1851 	}
   1852 
   1853 	self->use = func_conveyor_use;
   1854 
   1855 	gi.setmodel (self, self->model);
   1856 	self->solid = SOLID_BSP;
   1857 	gi.linkentity (self);
   1858 }
   1859 
   1860 
   1861 /*QUAKED func_door_secret (0 .5 .8) ? always_shoot 1st_left 1st_down
   1862 A secret door.  Slide back and then to the side.
   1863 
   1864 open_once		doors never closes
   1865 1st_left		1st move is left of arrow
   1866 1st_down		1st move is down from arrow
   1867 always_shoot	door is shootebale even if targeted
   1868 
   1869 "angle"		determines the direction
   1870 "dmg"		damage to inflic when blocked (default 2)
   1871 "wait"		how long to hold in the open position (default 5, -1 means hold)
   1872 */
   1873 
   1874 #define SECRET_ALWAYS_SHOOT	1
   1875 #define SECRET_1ST_LEFT		2
   1876 #define SECRET_1ST_DOWN		4
   1877 
   1878 void door_secret_move1 (edict_t *self);
   1879 void door_secret_move2 (edict_t *self);
   1880 void door_secret_move3 (edict_t *self);
   1881 void door_secret_move4 (edict_t *self);
   1882 void door_secret_move5 (edict_t *self);
   1883 void door_secret_move6 (edict_t *self);
   1884 void door_secret_done (edict_t *self);
   1885 
   1886 void door_secret_use (edict_t *self, edict_t *other, edict_t *activator)
   1887 {
   1888 	// make sure we're not already moving
   1889 	if (!VectorCompare(self->s.origin, vec3_origin))
   1890 		return;
   1891 
   1892 	Move_Calc (self, self->pos1, door_secret_move1);
   1893 	door_use_areaportals (self, true);
   1894 }
   1895 
   1896 void door_secret_move1 (edict_t *self)
   1897 {
   1898 	self->nextthink = level.time + 1.0;
   1899 	self->think = door_secret_move2;
   1900 }
   1901 
   1902 void door_secret_move2 (edict_t *self)
   1903 {
   1904 	Move_Calc (self, self->pos2, door_secret_move3);
   1905 }
   1906 
   1907 void door_secret_move3 (edict_t *self)
   1908 {
   1909 	if (self->wait == -1)
   1910 		return;
   1911 	self->nextthink = level.time + self->wait;
   1912 	self->think = door_secret_move4;
   1913 }
   1914 
   1915 void door_secret_move4 (edict_t *self)
   1916 {
   1917 	Move_Calc (self, self->pos1, door_secret_move5);
   1918 }
   1919 
   1920 void door_secret_move5 (edict_t *self)
   1921 {
   1922 	self->nextthink = level.time + 1.0;
   1923 	self->think = door_secret_move6;
   1924 }
   1925 
   1926 void door_secret_move6 (edict_t *self)
   1927 {
   1928 	Move_Calc (self, vec3_origin, door_secret_done);
   1929 }
   1930 
   1931 void door_secret_done (edict_t *self)
   1932 {
   1933 	if (!(self->targetname) || (self->spawnflags & SECRET_ALWAYS_SHOOT))
   1934 	{
   1935 		self->health = 0;
   1936 		self->takedamage = DAMAGE_YES;
   1937 	}
   1938 	door_use_areaportals (self, false);
   1939 }
   1940 
   1941 void door_secret_blocked  (edict_t *self, edict_t *other)
   1942 {
   1943 	if (!(other->svflags & SVF_MONSTER) && (!other->client) )
   1944 	{
   1945 		// give it a chance to go away on it's own terms (like gibs)
   1946 		T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH);
   1947 		// if it's still there, nuke it
   1948 		if (other)
   1949 			BecomeExplosion1 (other);
   1950 		return;
   1951 	}
   1952 
   1953 	if (level.time < self->touch_debounce_time)
   1954 		return;
   1955 	self->touch_debounce_time = level.time + 0.5;
   1956 
   1957 	T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
   1958 }
   1959 
   1960 void door_secret_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
   1961 {
   1962 	self->takedamage = DAMAGE_NO;
   1963 	door_secret_use (self, attacker, attacker);
   1964 }
   1965 
   1966 void SP_func_door_secret (edict_t *ent)
   1967 {
   1968 	vec3_t	forward, right, up;
   1969 	float	side;
   1970 	float	width;
   1971 	float	length;
   1972 
   1973 	ent->moveinfo.sound_start = gi.soundindex  ("doors/dr1_strt.wav");
   1974 	ent->moveinfo.sound_middle = gi.soundindex  ("doors/dr1_mid.wav");
   1975 	ent->moveinfo.sound_end = gi.soundindex  ("doors/dr1_end.wav");
   1976 
   1977 	ent->movetype = MOVETYPE_PUSH;
   1978 	ent->solid = SOLID_BSP;
   1979 	gi.setmodel (ent, ent->model);
   1980 
   1981 	ent->blocked = door_secret_blocked;
   1982 	ent->use = door_secret_use;
   1983 
   1984 	if (!(ent->targetname) || (ent->spawnflags & SECRET_ALWAYS_SHOOT))
   1985 	{
   1986 		ent->health = 0;
   1987 		ent->takedamage = DAMAGE_YES;
   1988 		ent->die = door_secret_die;
   1989 	}
   1990 
   1991 	if (!ent->dmg)
   1992 		ent->dmg = 2;
   1993 
   1994 	if (!ent->wait)
   1995 		ent->wait = 5;
   1996 
   1997 	ent->moveinfo.accel =
   1998 	ent->moveinfo.decel =
   1999 	ent->moveinfo.speed = 50;
   2000 
   2001 	// calculate positions
   2002 	AngleVectors (ent->s.angles, forward, right, up);
   2003 	VectorClear (ent->s.angles);
   2004 	side = 1.0 - (ent->spawnflags & SECRET_1ST_LEFT);
   2005 	if (ent->spawnflags & SECRET_1ST_DOWN)
   2006 		width = fabs(DotProduct(up, ent->size));
   2007 	else
   2008 		width = fabs(DotProduct(right, ent->size));
   2009 	length = fabs(DotProduct(forward, ent->size));
   2010 	if (ent->spawnflags & SECRET_1ST_DOWN)
   2011 		VectorMA (ent->s.origin, -1 * width, up, ent->pos1);
   2012 	else
   2013 		VectorMA (ent->s.origin, side * width, right, ent->pos1);
   2014 	VectorMA (ent->pos1, length, forward, ent->pos2);
   2015 
   2016 	if (ent->health)
   2017 	{
   2018 		ent->takedamage = DAMAGE_YES;
   2019 		ent->die = door_killed;
   2020 		ent->max_health = ent->health;
   2021 	}
   2022 	else if (ent->targetname && ent->message)
   2023 	{
   2024 		gi.soundindex ("misc/talk.wav");
   2025 		ent->touch = door_touch;
   2026 	}
   2027 	
   2028 	ent->classname = "func_door";
   2029 
   2030 	gi.linkentity (ent);
   2031 }
   2032 
   2033 
   2034 /*QUAKED func_killbox (1 0 0) ?
   2035 Kills everything inside when fired, irrespective of protection.
   2036 */
   2037 void use_killbox (edict_t *self, edict_t *other, edict_t *activator)
   2038 {
   2039 	KillBox (self);
   2040 }
   2041 
   2042 void SP_func_killbox (edict_t *ent)
   2043 {
   2044 	gi.setmodel (ent, ent->model);
   2045 	ent->use = use_killbox;
   2046 	ent->svflags = SVF_NOCLIENT;
   2047 }
   2048