Quake-III-Arena

Quake III Arena GPL Source Release
Log | Files | Refs

g_mover.c (40950B)


      1 /*
      2 ===========================================================================
      3 Copyright (C) 1999-2005 Id Software, Inc.
      4 
      5 This file is part of Quake III Arena source code.
      6 
      7 Quake III Arena source code is free software; you can redistribute it
      8 and/or modify it under the terms of the GNU General Public License as
      9 published by the Free Software Foundation; either version 2 of the License,
     10 or (at your option) any later version.
     11 
     12 Quake III Arena source code is distributed in the hope that it will be
     13 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
     14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     15 GNU General Public License for more details.
     16 
     17 You should have received a copy of the GNU General Public License
     18 along with Foobar; if not, write to the Free Software
     19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
     20 ===========================================================================
     21 */
     22 //
     23 
     24 #include "g_local.h"
     25 
     26 
     27 
     28 /*
     29 ===============================================================================
     30 
     31 PUSHMOVE
     32 
     33 ===============================================================================
     34 */
     35 
     36 void MatchTeam( gentity_t *teamLeader, int moverState, int time );
     37 
     38 typedef struct {
     39 	gentity_t	*ent;
     40 	vec3_t	origin;
     41 	vec3_t	angles;
     42 	float	deltayaw;
     43 } pushed_t;
     44 pushed_t	pushed[MAX_GENTITIES], *pushed_p;
     45 
     46 
     47 /*
     48 ============
     49 G_TestEntityPosition
     50 
     51 ============
     52 */
     53 gentity_t	*G_TestEntityPosition( gentity_t *ent ) {
     54 	trace_t	tr;
     55 	int		mask;
     56 
     57 	if ( ent->clipmask ) {
     58 		mask = ent->clipmask;
     59 	} else {
     60 		mask = MASK_SOLID;
     61 	}
     62 	if ( ent->client ) {
     63 		trap_Trace( &tr, ent->client->ps.origin, ent->r.mins, ent->r.maxs, ent->client->ps.origin, ent->s.number, mask );
     64 	} else {
     65 		trap_Trace( &tr, ent->s.pos.trBase, ent->r.mins, ent->r.maxs, ent->s.pos.trBase, ent->s.number, mask );
     66 	}
     67 	
     68 	if (tr.startsolid)
     69 		return &g_entities[ tr.entityNum ];
     70 		
     71 	return NULL;
     72 }
     73 
     74 /*
     75 ================
     76 G_CreateRotationMatrix
     77 ================
     78 */
     79 void G_CreateRotationMatrix(vec3_t angles, vec3_t matrix[3]) {
     80 	AngleVectors(angles, matrix[0], matrix[1], matrix[2]);
     81 	VectorInverse(matrix[1]);
     82 }
     83 
     84 /*
     85 ================
     86 G_TransposeMatrix
     87 ================
     88 */
     89 void G_TransposeMatrix(vec3_t matrix[3], vec3_t transpose[3]) {
     90 	int i, j;
     91 	for (i = 0; i < 3; i++) {
     92 		for (j = 0; j < 3; j++) {
     93 			transpose[i][j] = matrix[j][i];
     94 		}
     95 	}
     96 }
     97 
     98 /*
     99 ================
    100 G_RotatePoint
    101 ================
    102 */
    103 void G_RotatePoint(vec3_t point, vec3_t matrix[3]) {
    104 	vec3_t tvec;
    105 
    106 	VectorCopy(point, tvec);
    107 	point[0] = DotProduct(matrix[0], tvec);
    108 	point[1] = DotProduct(matrix[1], tvec);
    109 	point[2] = DotProduct(matrix[2], tvec);
    110 }
    111 
    112 /*
    113 ==================
    114 G_TryPushingEntity
    115 
    116 Returns qfalse if the move is blocked
    117 ==================
    118 */
    119 qboolean	G_TryPushingEntity( gentity_t *check, gentity_t *pusher, vec3_t move, vec3_t amove ) {
    120 	vec3_t		matrix[3], transpose[3];
    121 	vec3_t		org, org2, move2;
    122 	gentity_t	*block;
    123 
    124 	// EF_MOVER_STOP will just stop when contacting another entity
    125 	// instead of pushing it, but entities can still ride on top of it
    126 	if ( ( pusher->s.eFlags & EF_MOVER_STOP ) && 
    127 		check->s.groundEntityNum != pusher->s.number ) {
    128 		return qfalse;
    129 	}
    130 
    131 	// save off the old position
    132 	if (pushed_p > &pushed[MAX_GENTITIES]) {
    133 		G_Error( "pushed_p > &pushed[MAX_GENTITIES]" );
    134 	}
    135 	pushed_p->ent = check;
    136 	VectorCopy (check->s.pos.trBase, pushed_p->origin);
    137 	VectorCopy (check->s.apos.trBase, pushed_p->angles);
    138 	if ( check->client ) {
    139 		pushed_p->deltayaw = check->client->ps.delta_angles[YAW];
    140 		VectorCopy (check->client->ps.origin, pushed_p->origin);
    141 	}
    142 	pushed_p++;
    143 
    144 	// try moving the contacted entity 
    145 	// figure movement due to the pusher's amove
    146 	G_CreateRotationMatrix( amove, transpose );
    147 	G_TransposeMatrix( transpose, matrix );
    148 	if ( check->client ) {
    149 		VectorSubtract (check->client->ps.origin, pusher->r.currentOrigin, org);
    150 	}
    151 	else {
    152 		VectorSubtract (check->s.pos.trBase, pusher->r.currentOrigin, org);
    153 	}
    154 	VectorCopy( org, org2 );
    155 	G_RotatePoint( org2, matrix );
    156 	VectorSubtract (org2, org, move2);
    157 	// add movement
    158 	VectorAdd (check->s.pos.trBase, move, check->s.pos.trBase);
    159 	VectorAdd (check->s.pos.trBase, move2, check->s.pos.trBase);
    160 	if ( check->client ) {
    161 		VectorAdd (check->client->ps.origin, move, check->client->ps.origin);
    162 		VectorAdd (check->client->ps.origin, move2, check->client->ps.origin);
    163 		// make sure the client's view rotates when on a rotating mover
    164 		check->client->ps.delta_angles[YAW] += ANGLE2SHORT(amove[YAW]);
    165 	}
    166 
    167 	// may have pushed them off an edge
    168 	if ( check->s.groundEntityNum != pusher->s.number ) {
    169 		check->s.groundEntityNum = -1;
    170 	}
    171 
    172 	block = G_TestEntityPosition( check );
    173 	if (!block) {
    174 		// pushed ok
    175 		if ( check->client ) {
    176 			VectorCopy( check->client->ps.origin, check->r.currentOrigin );
    177 		} else {
    178 			VectorCopy( check->s.pos.trBase, check->r.currentOrigin );
    179 		}
    180 		trap_LinkEntity (check);
    181 		return qtrue;
    182 	}
    183 
    184 	// if it is ok to leave in the old position, do it
    185 	// this is only relevent for riding entities, not pushed
    186 	// Sliding trapdoors can cause this.
    187 	VectorCopy( (pushed_p-1)->origin, check->s.pos.trBase);
    188 	if ( check->client ) {
    189 		VectorCopy( (pushed_p-1)->origin, check->client->ps.origin);
    190 	}
    191 	VectorCopy( (pushed_p-1)->angles, check->s.apos.trBase );
    192 	block = G_TestEntityPosition (check);
    193 	if ( !block ) {
    194 		check->s.groundEntityNum = -1;
    195 		pushed_p--;
    196 		return qtrue;
    197 	}
    198 
    199 	// blocked
    200 	return qfalse;
    201 }
    202 
    203 /*
    204 ==================
    205 G_CheckProxMinePosition
    206 ==================
    207 */
    208 qboolean G_CheckProxMinePosition( gentity_t *check ) {
    209 	vec3_t		start, end;
    210 	trace_t	tr;
    211 
    212 	VectorMA(check->s.pos.trBase, 0.125, check->movedir, start);
    213 	VectorMA(check->s.pos.trBase, 2, check->movedir, end);
    214 	trap_Trace( &tr, start, NULL, NULL, end, check->s.number, MASK_SOLID );
    215 	
    216 	if (tr.startsolid || tr.fraction < 1)
    217 		return qfalse;
    218 
    219 	return qtrue;
    220 }
    221 
    222 /*
    223 ==================
    224 G_TryPushingProxMine
    225 ==================
    226 */
    227 qboolean G_TryPushingProxMine( gentity_t *check, gentity_t *pusher, vec3_t move, vec3_t amove ) {
    228 	vec3_t		forward, right, up;
    229 	vec3_t		org, org2, move2;
    230 	int ret;
    231 
    232 	// we need this for pushing things later
    233 	VectorSubtract (vec3_origin, amove, org);
    234 	AngleVectors (org, forward, right, up);
    235 
    236 	// try moving the contacted entity 
    237 	VectorAdd (check->s.pos.trBase, move, check->s.pos.trBase);
    238 
    239 	// figure movement due to the pusher's amove
    240 	VectorSubtract (check->s.pos.trBase, pusher->r.currentOrigin, org);
    241 	org2[0] = DotProduct (org, forward);
    242 	org2[1] = -DotProduct (org, right);
    243 	org2[2] = DotProduct (org, up);
    244 	VectorSubtract (org2, org, move2);
    245 	VectorAdd (check->s.pos.trBase, move2, check->s.pos.trBase);
    246 
    247 	ret = G_CheckProxMinePosition( check );
    248 	if (ret) {
    249 		VectorCopy( check->s.pos.trBase, check->r.currentOrigin );
    250 		trap_LinkEntity (check);
    251 	}
    252 	return ret;
    253 }
    254 
    255 void G_ExplodeMissile( gentity_t *ent );
    256 
    257 /*
    258 ============
    259 G_MoverPush
    260 
    261 Objects need to be moved back on a failed push,
    262 otherwise riders would continue to slide.
    263 If qfalse is returned, *obstacle will be the blocking entity
    264 ============
    265 */
    266 qboolean G_MoverPush( gentity_t *pusher, vec3_t move, vec3_t amove, gentity_t **obstacle ) {
    267 	int			i, e;
    268 	gentity_t	*check;
    269 	vec3_t		mins, maxs;
    270 	pushed_t	*p;
    271 	int			entityList[MAX_GENTITIES];
    272 	int			listedEntities;
    273 	vec3_t		totalMins, totalMaxs;
    274 
    275 	*obstacle = NULL;
    276 
    277 
    278 	// mins/maxs are the bounds at the destination
    279 	// totalMins / totalMaxs are the bounds for the entire move
    280 	if ( pusher->r.currentAngles[0] || pusher->r.currentAngles[1] || pusher->r.currentAngles[2]
    281 		|| amove[0] || amove[1] || amove[2] ) {
    282 		float		radius;
    283 
    284 		radius = RadiusFromBounds( pusher->r.mins, pusher->r.maxs );
    285 		for ( i = 0 ; i < 3 ; i++ ) {
    286 			mins[i] = pusher->r.currentOrigin[i] + move[i] - radius;
    287 			maxs[i] = pusher->r.currentOrigin[i] + move[i] + radius;
    288 			totalMins[i] = mins[i] - move[i];
    289 			totalMaxs[i] = maxs[i] - move[i];
    290 		}
    291 	} else {
    292 		for (i=0 ; i<3 ; i++) {
    293 			mins[i] = pusher->r.absmin[i] + move[i];
    294 			maxs[i] = pusher->r.absmax[i] + move[i];
    295 		}
    296 
    297 		VectorCopy( pusher->r.absmin, totalMins );
    298 		VectorCopy( pusher->r.absmax, totalMaxs );
    299 		for (i=0 ; i<3 ; i++) {
    300 			if ( move[i] > 0 ) {
    301 				totalMaxs[i] += move[i];
    302 			} else {
    303 				totalMins[i] += move[i];
    304 			}
    305 		}
    306 	}
    307 
    308 	// unlink the pusher so we don't get it in the entityList
    309 	trap_UnlinkEntity( pusher );
    310 
    311 	listedEntities = trap_EntitiesInBox( totalMins, totalMaxs, entityList, MAX_GENTITIES );
    312 
    313 	// move the pusher to it's final position
    314 	VectorAdd( pusher->r.currentOrigin, move, pusher->r.currentOrigin );
    315 	VectorAdd( pusher->r.currentAngles, amove, pusher->r.currentAngles );
    316 	trap_LinkEntity( pusher );
    317 
    318 	// see if any solid entities are inside the final position
    319 	for ( e = 0 ; e < listedEntities ; e++ ) {
    320 		check = &g_entities[ entityList[ e ] ];
    321 
    322 #ifdef MISSIONPACK
    323 		if ( check->s.eType == ET_MISSILE ) {
    324 			// if it is a prox mine
    325 			if ( !strcmp(check->classname, "prox mine") ) {
    326 				// if this prox mine is attached to this mover try to move it with the pusher
    327 				if ( check->enemy == pusher ) {
    328 					if (!G_TryPushingProxMine( check, pusher, move, amove )) {
    329 						//explode
    330 						check->s.loopSound = 0;
    331 						G_AddEvent( check, EV_PROXIMITY_MINE_TRIGGER, 0 );
    332 						G_ExplodeMissile(check);
    333 						if (check->activator) {
    334 							G_FreeEntity(check->activator);
    335 							check->activator = NULL;
    336 						}
    337 						//G_Printf("prox mine explodes\n");
    338 					}
    339 				}
    340 				else {
    341 					//check if the prox mine is crushed by the mover
    342 					if (!G_CheckProxMinePosition( check )) {
    343 						//explode
    344 						check->s.loopSound = 0;
    345 						G_AddEvent( check, EV_PROXIMITY_MINE_TRIGGER, 0 );
    346 						G_ExplodeMissile(check);
    347 						if (check->activator) {
    348 							G_FreeEntity(check->activator);
    349 							check->activator = NULL;
    350 						}
    351 						//G_Printf("prox mine explodes\n");
    352 					}
    353 				}
    354 				continue;
    355 			}
    356 		}
    357 #endif
    358 		// only push items and players
    359 		if ( check->s.eType != ET_ITEM && check->s.eType != ET_PLAYER && !check->physicsObject ) {
    360 			continue;
    361 		}
    362 
    363 		// if the entity is standing on the pusher, it will definitely be moved
    364 		if ( check->s.groundEntityNum != pusher->s.number ) {
    365 			// see if the ent needs to be tested
    366 			if ( check->r.absmin[0] >= maxs[0]
    367 			|| check->r.absmin[1] >= maxs[1]
    368 			|| check->r.absmin[2] >= maxs[2]
    369 			|| check->r.absmax[0] <= mins[0]
    370 			|| check->r.absmax[1] <= mins[1]
    371 			|| check->r.absmax[2] <= mins[2] ) {
    372 				continue;
    373 			}
    374 			// see if the ent's bbox is inside the pusher's final position
    375 			// this does allow a fast moving object to pass through a thin entity...
    376 			if (!G_TestEntityPosition (check)) {
    377 				continue;
    378 			}
    379 		}
    380 
    381 		// the entity needs to be pushed
    382 		if ( G_TryPushingEntity( check, pusher, move, amove ) ) {
    383 			continue;
    384 		}
    385 
    386 		// the move was blocked an entity
    387 
    388 		// bobbing entities are instant-kill and never get blocked
    389 		if ( pusher->s.pos.trType == TR_SINE || pusher->s.apos.trType == TR_SINE ) {
    390 			G_Damage( check, pusher, pusher, NULL, NULL, 99999, 0, MOD_CRUSH );
    391 			continue;
    392 		}
    393 
    394 		
    395 		// save off the obstacle so we can call the block function (crush, etc)
    396 		*obstacle = check;
    397 
    398 		// move back any entities we already moved
    399 		// go backwards, so if the same entity was pushed
    400 		// twice, it goes back to the original position
    401 		for ( p=pushed_p-1 ; p>=pushed ; p-- ) {
    402 			VectorCopy (p->origin, p->ent->s.pos.trBase);
    403 			VectorCopy (p->angles, p->ent->s.apos.trBase);
    404 			if ( p->ent->client ) {
    405 				p->ent->client->ps.delta_angles[YAW] = p->deltayaw;
    406 				VectorCopy (p->origin, p->ent->client->ps.origin);
    407 			}
    408 			trap_LinkEntity (p->ent);
    409 		}
    410 		return qfalse;
    411 	}
    412 
    413 	return qtrue;
    414 }
    415 
    416 
    417 /*
    418 =================
    419 G_MoverTeam
    420 =================
    421 */
    422 void G_MoverTeam( gentity_t *ent ) {
    423 	vec3_t		move, amove;
    424 	gentity_t	*part, *obstacle;
    425 	vec3_t		origin, angles;
    426 
    427 	obstacle = NULL;
    428 
    429 	// make sure all team slaves can move before commiting
    430 	// any moves or calling any think functions
    431 	// if the move is blocked, all moved objects will be backed out
    432 	pushed_p = pushed;
    433 	for (part = ent ; part ; part=part->teamchain) {
    434 		// get current position
    435 		BG_EvaluateTrajectory( &part->s.pos, level.time, origin );
    436 		BG_EvaluateTrajectory( &part->s.apos, level.time, angles );
    437 		VectorSubtract( origin, part->r.currentOrigin, move );
    438 		VectorSubtract( angles, part->r.currentAngles, amove );
    439 		if ( !G_MoverPush( part, move, amove, &obstacle ) ) {
    440 			break;	// move was blocked
    441 		}
    442 	}
    443 
    444 	if (part) {
    445 		// go back to the previous position
    446 		for ( part = ent ; part ; part = part->teamchain ) {
    447 			part->s.pos.trTime += level.time - level.previousTime;
    448 			part->s.apos.trTime += level.time - level.previousTime;
    449 			BG_EvaluateTrajectory( &part->s.pos, level.time, part->r.currentOrigin );
    450 			BG_EvaluateTrajectory( &part->s.apos, level.time, part->r.currentAngles );
    451 			trap_LinkEntity( part );
    452 		}
    453 
    454 		// if the pusher has a "blocked" function, call it
    455 		if (ent->blocked) {
    456 			ent->blocked( ent, obstacle );
    457 		}
    458 		return;
    459 	}
    460 
    461 	// the move succeeded
    462 	for ( part = ent ; part ; part = part->teamchain ) {
    463 		// call the reached function if time is at or past end point
    464 		if ( part->s.pos.trType == TR_LINEAR_STOP ) {
    465 			if ( level.time >= part->s.pos.trTime + part->s.pos.trDuration ) {
    466 				if ( part->reached ) {
    467 					part->reached( part );
    468 				}
    469 			}
    470 		}
    471 	}
    472 }
    473 
    474 /*
    475 ================
    476 G_RunMover
    477 
    478 ================
    479 */
    480 void G_RunMover( gentity_t *ent ) {
    481 	// if not a team captain, don't do anything, because
    482 	// the captain will handle everything
    483 	if ( ent->flags & FL_TEAMSLAVE ) {
    484 		return;
    485 	}
    486 
    487 	// if stationary at one of the positions, don't move anything
    488 	if ( ent->s.pos.trType != TR_STATIONARY || ent->s.apos.trType != TR_STATIONARY ) {
    489 		G_MoverTeam( ent );
    490 	}
    491 
    492 	// check think function
    493 	G_RunThink( ent );
    494 }
    495 
    496 /*
    497 ============================================================================
    498 
    499 GENERAL MOVERS
    500 
    501 Doors, plats, and buttons are all binary (two position) movers
    502 Pos1 is "at rest", pos2 is "activated"
    503 ============================================================================
    504 */
    505 
    506 /*
    507 ===============
    508 SetMoverState
    509 ===============
    510 */
    511 void SetMoverState( gentity_t *ent, moverState_t moverState, int time ) {
    512 	vec3_t			delta;
    513 	float			f;
    514 
    515 	ent->moverState = moverState;
    516 
    517 	ent->s.pos.trTime = time;
    518 	switch( moverState ) {
    519 	case MOVER_POS1:
    520 		VectorCopy( ent->pos1, ent->s.pos.trBase );
    521 		ent->s.pos.trType = TR_STATIONARY;
    522 		break;
    523 	case MOVER_POS2:
    524 		VectorCopy( ent->pos2, ent->s.pos.trBase );
    525 		ent->s.pos.trType = TR_STATIONARY;
    526 		break;
    527 	case MOVER_1TO2:
    528 		VectorCopy( ent->pos1, ent->s.pos.trBase );
    529 		VectorSubtract( ent->pos2, ent->pos1, delta );
    530 		f = 1000.0 / ent->s.pos.trDuration;
    531 		VectorScale( delta, f, ent->s.pos.trDelta );
    532 		ent->s.pos.trType = TR_LINEAR_STOP;
    533 		break;
    534 	case MOVER_2TO1:
    535 		VectorCopy( ent->pos2, ent->s.pos.trBase );
    536 		VectorSubtract( ent->pos1, ent->pos2, delta );
    537 		f = 1000.0 / ent->s.pos.trDuration;
    538 		VectorScale( delta, f, ent->s.pos.trDelta );
    539 		ent->s.pos.trType = TR_LINEAR_STOP;
    540 		break;
    541 	}
    542 	BG_EvaluateTrajectory( &ent->s.pos, level.time, ent->r.currentOrigin );	
    543 	trap_LinkEntity( ent );
    544 }
    545 
    546 /*
    547 ================
    548 MatchTeam
    549 
    550 All entities in a mover team will move from pos1 to pos2
    551 in the same amount of time
    552 ================
    553 */
    554 void MatchTeam( gentity_t *teamLeader, int moverState, int time ) {
    555 	gentity_t		*slave;
    556 
    557 	for ( slave = teamLeader ; slave ; slave = slave->teamchain ) {
    558 		SetMoverState( slave, moverState, time );
    559 	}
    560 }
    561 
    562 
    563 
    564 /*
    565 ================
    566 ReturnToPos1
    567 ================
    568 */
    569 void ReturnToPos1( gentity_t *ent ) {
    570 	MatchTeam( ent, MOVER_2TO1, level.time );
    571 
    572 	// looping sound
    573 	ent->s.loopSound = ent->soundLoop;
    574 
    575 	// starting sound
    576 	if ( ent->sound2to1 ) {
    577 		G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound2to1 );
    578 	}
    579 }
    580 
    581 
    582 /*
    583 ================
    584 Reached_BinaryMover
    585 ================
    586 */
    587 void Reached_BinaryMover( gentity_t *ent ) {
    588 
    589 	// stop the looping sound
    590 	ent->s.loopSound = ent->soundLoop;
    591 
    592 	if ( ent->moverState == MOVER_1TO2 ) {
    593 		// reached pos2
    594 		SetMoverState( ent, MOVER_POS2, level.time );
    595 
    596 		// play sound
    597 		if ( ent->soundPos2 ) {
    598 			G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundPos2 );
    599 		}
    600 
    601 		// return to pos1 after a delay
    602 		ent->think = ReturnToPos1;
    603 		ent->nextthink = level.time + ent->wait;
    604 
    605 		// fire targets
    606 		if ( !ent->activator ) {
    607 			ent->activator = ent;
    608 		}
    609 		G_UseTargets( ent, ent->activator );
    610 	} else if ( ent->moverState == MOVER_2TO1 ) {
    611 		// reached pos1
    612 		SetMoverState( ent, MOVER_POS1, level.time );
    613 
    614 		// play sound
    615 		if ( ent->soundPos1 ) {
    616 			G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundPos1 );
    617 		}
    618 
    619 		// close areaportals
    620 		if ( ent->teammaster == ent || !ent->teammaster ) {
    621 			trap_AdjustAreaPortalState( ent, qfalse );
    622 		}
    623 	} else {
    624 		G_Error( "Reached_BinaryMover: bad moverState" );
    625 	}
    626 }
    627 
    628 
    629 /*
    630 ================
    631 Use_BinaryMover
    632 ================
    633 */
    634 void Use_BinaryMover( gentity_t *ent, gentity_t *other, gentity_t *activator ) {
    635 	int		total;
    636 	int		partial;
    637 
    638 	// only the master should be used
    639 	if ( ent->flags & FL_TEAMSLAVE ) {
    640 		Use_BinaryMover( ent->teammaster, other, activator );
    641 		return;
    642 	}
    643 
    644 	ent->activator = activator;
    645 
    646 	if ( ent->moverState == MOVER_POS1 ) {
    647 		// start moving 50 msec later, becase if this was player
    648 		// triggered, level.time hasn't been advanced yet
    649 		MatchTeam( ent, MOVER_1TO2, level.time + 50 );
    650 
    651 		// starting sound
    652 		if ( ent->sound1to2 ) {
    653 			G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound1to2 );
    654 		}
    655 
    656 		// looping sound
    657 		ent->s.loopSound = ent->soundLoop;
    658 
    659 		// open areaportal
    660 		if ( ent->teammaster == ent || !ent->teammaster ) {
    661 			trap_AdjustAreaPortalState( ent, qtrue );
    662 		}
    663 		return;
    664 	}
    665 
    666 	// if all the way up, just delay before coming down
    667 	if ( ent->moverState == MOVER_POS2 ) {
    668 		ent->nextthink = level.time + ent->wait;
    669 		return;
    670 	}
    671 
    672 	// only partway down before reversing
    673 	if ( ent->moverState == MOVER_2TO1 ) {
    674 		total = ent->s.pos.trDuration;
    675 		partial = level.time - ent->s.pos.trTime;
    676 		if ( partial > total ) {
    677 			partial = total;
    678 		}
    679 
    680 		MatchTeam( ent, MOVER_1TO2, level.time - ( total - partial ) );
    681 
    682 		if ( ent->sound1to2 ) {
    683 			G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound1to2 );
    684 		}
    685 		return;
    686 	}
    687 
    688 	// only partway up before reversing
    689 	if ( ent->moverState == MOVER_1TO2 ) {
    690 		total = ent->s.pos.trDuration;
    691 		partial = level.time - ent->s.pos.trTime;
    692 		if ( partial > total ) {
    693 			partial = total;
    694 		}
    695 
    696 		MatchTeam( ent, MOVER_2TO1, level.time - ( total - partial ) );
    697 
    698 		if ( ent->sound2to1 ) {
    699 			G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound2to1 );
    700 		}
    701 		return;
    702 	}
    703 }
    704 
    705 
    706 
    707 /*
    708 ================
    709 InitMover
    710 
    711 "pos1", "pos2", and "speed" should be set before calling,
    712 so the movement delta can be calculated
    713 ================
    714 */
    715 void InitMover( gentity_t *ent ) {
    716 	vec3_t		move;
    717 	float		distance;
    718 	float		light;
    719 	vec3_t		color;
    720 	qboolean	lightSet, colorSet;
    721 	char		*sound;
    722 
    723 	// if the "model2" key is set, use a seperate model
    724 	// for drawing, but clip against the brushes
    725 	if ( ent->model2 ) {
    726 		ent->s.modelindex2 = G_ModelIndex( ent->model2 );
    727 	}
    728 
    729 	// if the "loopsound" key is set, use a constant looping sound when moving
    730 	if ( G_SpawnString( "noise", "100", &sound ) ) {
    731 		ent->s.loopSound = G_SoundIndex( sound );
    732 	}
    733 
    734 	// if the "color" or "light" keys are set, setup constantLight
    735 	lightSet = G_SpawnFloat( "light", "100", &light );
    736 	colorSet = G_SpawnVector( "color", "1 1 1", color );
    737 	if ( lightSet || colorSet ) {
    738 		int		r, g, b, i;
    739 
    740 		r = color[0] * 255;
    741 		if ( r > 255 ) {
    742 			r = 255;
    743 		}
    744 		g = color[1] * 255;
    745 		if ( g > 255 ) {
    746 			g = 255;
    747 		}
    748 		b = color[2] * 255;
    749 		if ( b > 255 ) {
    750 			b = 255;
    751 		}
    752 		i = light / 4;
    753 		if ( i > 255 ) {
    754 			i = 255;
    755 		}
    756 		ent->s.constantLight = r | ( g << 8 ) | ( b << 16 ) | ( i << 24 );
    757 	}
    758 
    759 
    760 	ent->use = Use_BinaryMover;
    761 	ent->reached = Reached_BinaryMover;
    762 
    763 	ent->moverState = MOVER_POS1;
    764 	ent->r.svFlags = SVF_USE_CURRENT_ORIGIN;
    765 	ent->s.eType = ET_MOVER;
    766 	VectorCopy (ent->pos1, ent->r.currentOrigin);
    767 	trap_LinkEntity (ent);
    768 
    769 	ent->s.pos.trType = TR_STATIONARY;
    770 	VectorCopy( ent->pos1, ent->s.pos.trBase );
    771 
    772 	// calculate time to reach second position from speed
    773 	VectorSubtract( ent->pos2, ent->pos1, move );
    774 	distance = VectorLength( move );
    775 	if ( ! ent->speed ) {
    776 		ent->speed = 100;
    777 	}
    778 	VectorScale( move, ent->speed, ent->s.pos.trDelta );
    779 	ent->s.pos.trDuration = distance * 1000 / ent->speed;
    780 	if ( ent->s.pos.trDuration <= 0 ) {
    781 		ent->s.pos.trDuration = 1;
    782 	}
    783 }
    784 
    785 
    786 /*
    787 ===============================================================================
    788 
    789 DOOR
    790 
    791 A use can be triggered either by a touch function, by being shot, or by being
    792 targeted by another entity.
    793 
    794 ===============================================================================
    795 */
    796 
    797 /*
    798 ================
    799 Blocked_Door
    800 ================
    801 */
    802 void Blocked_Door( gentity_t *ent, gentity_t *other ) {
    803 	// remove anything other than a client
    804 	if ( !other->client ) {
    805 		// except CTF flags!!!!
    806 		if( other->s.eType == ET_ITEM && other->item->giType == IT_TEAM ) {
    807 			Team_DroppedFlagThink( other );
    808 			return;
    809 		}
    810 		G_TempEntity( other->s.origin, EV_ITEM_POP );
    811 		G_FreeEntity( other );
    812 		return;
    813 	}
    814 
    815 	if ( ent->damage ) {
    816 		G_Damage( other, ent, ent, NULL, NULL, ent->damage, 0, MOD_CRUSH );
    817 	}
    818 	if ( ent->spawnflags & 4 ) {
    819 		return;		// crushers don't reverse
    820 	}
    821 
    822 	// reverse direction
    823 	Use_BinaryMover( ent, ent, other );
    824 }
    825 
    826 /*
    827 ================
    828 Touch_DoorTriggerSpectator
    829 ================
    830 */
    831 static void Touch_DoorTriggerSpectator( gentity_t *ent, gentity_t *other, trace_t *trace ) {
    832 	int i, axis;
    833 	vec3_t origin, dir, angles;
    834 
    835 	axis = ent->count;
    836 	VectorClear(dir);
    837 	if (fabs(other->s.origin[axis] - ent->r.absmax[axis]) <
    838 		fabs(other->s.origin[axis] - ent->r.absmin[axis])) {
    839 		origin[axis] = ent->r.absmin[axis] - 10;
    840 		dir[axis] = -1;
    841 	}
    842 	else {
    843 		origin[axis] = ent->r.absmax[axis] + 10;
    844 		dir[axis] = 1;
    845 	}
    846 	for (i = 0; i < 3; i++) {
    847 		if (i == axis) continue;
    848 		origin[i] = (ent->r.absmin[i] + ent->r.absmax[i]) * 0.5;
    849 	}
    850 	vectoangles(dir, angles);
    851 	TeleportPlayer(other, origin, angles );
    852 }
    853 
    854 /*
    855 ================
    856 Touch_DoorTrigger
    857 ================
    858 */
    859 void Touch_DoorTrigger( gentity_t *ent, gentity_t *other, trace_t *trace ) {
    860 	if ( other->client && other->client->sess.sessionTeam == TEAM_SPECTATOR ) {
    861 		// if the door is not open and not opening
    862 		if ( ent->parent->moverState != MOVER_1TO2 &&
    863 			ent->parent->moverState != MOVER_POS2) {
    864 			Touch_DoorTriggerSpectator( ent, other, trace );
    865 		}
    866 	}
    867 	else if ( ent->parent->moverState != MOVER_1TO2 ) {
    868 		Use_BinaryMover( ent->parent, ent, other );
    869 	}
    870 }
    871 
    872 
    873 /*
    874 ======================
    875 Think_SpawnNewDoorTrigger
    876 
    877 All of the parts of a door have been spawned, so create
    878 a trigger that encloses all of them
    879 ======================
    880 */
    881 void Think_SpawnNewDoorTrigger( gentity_t *ent ) {
    882 	gentity_t		*other;
    883 	vec3_t		mins, maxs;
    884 	int			i, best;
    885 
    886 	// set all of the slaves as shootable
    887 	for ( other = ent ; other ; other = other->teamchain ) {
    888 		other->takedamage = qtrue;
    889 	}
    890 
    891 	// find the bounds of everything on the team
    892 	VectorCopy (ent->r.absmin, mins);
    893 	VectorCopy (ent->r.absmax, maxs);
    894 
    895 	for (other = ent->teamchain ; other ; other=other->teamchain) {
    896 		AddPointToBounds (other->r.absmin, mins, maxs);
    897 		AddPointToBounds (other->r.absmax, mins, maxs);
    898 	}
    899 
    900 	// find the thinnest axis, which will be the one we expand
    901 	best = 0;
    902 	for ( i = 1 ; i < 3 ; i++ ) {
    903 		if ( maxs[i] - mins[i] < maxs[best] - mins[best] ) {
    904 			best = i;
    905 		}
    906 	}
    907 	maxs[best] += 120;
    908 	mins[best] -= 120;
    909 
    910 	// create a trigger with this size
    911 	other = G_Spawn ();
    912 	other->classname = "door_trigger";
    913 	VectorCopy (mins, other->r.mins);
    914 	VectorCopy (maxs, other->r.maxs);
    915 	other->parent = ent;
    916 	other->r.contents = CONTENTS_TRIGGER;
    917 	other->touch = Touch_DoorTrigger;
    918 	// remember the thinnest axis
    919 	other->count = best;
    920 	trap_LinkEntity (other);
    921 
    922 	MatchTeam( ent, ent->moverState, level.time );
    923 }
    924 
    925 void Think_MatchTeam( gentity_t *ent ) {
    926 	MatchTeam( ent, ent->moverState, level.time );
    927 }
    928 
    929 
    930 /*QUAKED func_door (0 .5 .8) ? START_OPEN x CRUSHER
    931 TOGGLE		wait in both the start and end states for a trigger event.
    932 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).
    933 NOMONSTER	monsters will not trigger this door
    934 
    935 "model2"	.md3 model to also draw
    936 "angle"		determines the opening direction
    937 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
    938 "speed"		movement speed (100 default)
    939 "wait"		wait before returning (3 default, -1 = never return)
    940 "lip"		lip remaining at end of move (8 default)
    941 "dmg"		damage to inflict when blocked (2 default)
    942 "color"		constantLight color
    943 "light"		constantLight radius
    944 "health"	if set, the door must be shot open
    945 */
    946 void SP_func_door (gentity_t *ent) {
    947 	vec3_t	abs_movedir;
    948 	float	distance;
    949 	vec3_t	size;
    950 	float	lip;
    951 
    952 	ent->sound1to2 = ent->sound2to1 = G_SoundIndex("sound/movers/doors/dr1_strt.wav");
    953 	ent->soundPos1 = ent->soundPos2 = G_SoundIndex("sound/movers/doors/dr1_end.wav");
    954 
    955 	ent->blocked = Blocked_Door;
    956 
    957 	// default speed of 400
    958 	if (!ent->speed)
    959 		ent->speed = 400;
    960 
    961 	// default wait of 2 seconds
    962 	if (!ent->wait)
    963 		ent->wait = 2;
    964 	ent->wait *= 1000;
    965 
    966 	// default lip of 8 units
    967 	G_SpawnFloat( "lip", "8", &lip );
    968 
    969 	// default damage of 2 points
    970 	G_SpawnInt( "dmg", "2", &ent->damage );
    971 
    972 	// first position at start
    973 	VectorCopy( ent->s.origin, ent->pos1 );
    974 
    975 	// calculate second position
    976 	trap_SetBrushModel( ent, ent->model );
    977 	G_SetMovedir (ent->s.angles, ent->movedir);
    978 	abs_movedir[0] = fabs(ent->movedir[0]);
    979 	abs_movedir[1] = fabs(ent->movedir[1]);
    980 	abs_movedir[2] = fabs(ent->movedir[2]);
    981 	VectorSubtract( ent->r.maxs, ent->r.mins, size );
    982 	distance = DotProduct( abs_movedir, size ) - lip;
    983 	VectorMA( ent->pos1, distance, ent->movedir, ent->pos2 );
    984 
    985 	// if "start_open", reverse position 1 and 2
    986 	if ( ent->spawnflags & 1 ) {
    987 		vec3_t	temp;
    988 
    989 		VectorCopy( ent->pos2, temp );
    990 		VectorCopy( ent->s.origin, ent->pos2 );
    991 		VectorCopy( temp, ent->pos1 );
    992 	}
    993 
    994 	InitMover( ent );
    995 
    996 	ent->nextthink = level.time + FRAMETIME;
    997 
    998 	if ( ! (ent->flags & FL_TEAMSLAVE ) ) {
    999 		int health;
   1000 
   1001 		G_SpawnInt( "health", "0", &health );
   1002 		if ( health ) {
   1003 			ent->takedamage = qtrue;
   1004 		}
   1005 		if ( ent->targetname || health ) {
   1006 			// non touch/shoot doors
   1007 			ent->think = Think_MatchTeam;
   1008 		} else {
   1009 			ent->think = Think_SpawnNewDoorTrigger;
   1010 		}
   1011 	}
   1012 
   1013 
   1014 }
   1015 
   1016 /*
   1017 ===============================================================================
   1018 
   1019 PLAT
   1020 
   1021 ===============================================================================
   1022 */
   1023 
   1024 /*
   1025 ==============
   1026 Touch_Plat
   1027 
   1028 Don't allow decent if a living player is on it
   1029 ===============
   1030 */
   1031 void Touch_Plat( gentity_t *ent, gentity_t *other, trace_t *trace ) {
   1032 	if ( !other->client || other->client->ps.stats[STAT_HEALTH] <= 0 ) {
   1033 		return;
   1034 	}
   1035 
   1036 	// delay return-to-pos1 by one second
   1037 	if ( ent->moverState == MOVER_POS2 ) {
   1038 		ent->nextthink = level.time + 1000;
   1039 	}
   1040 }
   1041 
   1042 /*
   1043 ==============
   1044 Touch_PlatCenterTrigger
   1045 
   1046 If the plat is at the bottom position, start it going up
   1047 ===============
   1048 */
   1049 void Touch_PlatCenterTrigger(gentity_t *ent, gentity_t *other, trace_t *trace ) {
   1050 	if ( !other->client ) {
   1051 		return;
   1052 	}
   1053 
   1054 	if ( ent->parent->moverState == MOVER_POS1 ) {
   1055 		Use_BinaryMover( ent->parent, ent, other );
   1056 	}
   1057 }
   1058 
   1059 
   1060 /*
   1061 ================
   1062 SpawnPlatTrigger
   1063 
   1064 Spawn a trigger in the middle of the plat's low position
   1065 Elevator cars require that the trigger extend through the entire low position,
   1066 not just sit on top of it.
   1067 ================
   1068 */
   1069 void SpawnPlatTrigger( gentity_t *ent ) {
   1070 	gentity_t	*trigger;
   1071 	vec3_t	tmin, tmax;
   1072 
   1073 	// the middle trigger will be a thin trigger just
   1074 	// above the starting position
   1075 	trigger = G_Spawn();
   1076 	trigger->classname = "plat_trigger";
   1077 	trigger->touch = Touch_PlatCenterTrigger;
   1078 	trigger->r.contents = CONTENTS_TRIGGER;
   1079 	trigger->parent = ent;
   1080 	
   1081 	tmin[0] = ent->pos1[0] + ent->r.mins[0] + 33;
   1082 	tmin[1] = ent->pos1[1] + ent->r.mins[1] + 33;
   1083 	tmin[2] = ent->pos1[2] + ent->r.mins[2];
   1084 
   1085 	tmax[0] = ent->pos1[0] + ent->r.maxs[0] - 33;
   1086 	tmax[1] = ent->pos1[1] + ent->r.maxs[1] - 33;
   1087 	tmax[2] = ent->pos1[2] + ent->r.maxs[2] + 8;
   1088 
   1089 	if ( tmax[0] <= tmin[0] ) {
   1090 		tmin[0] = ent->pos1[0] + (ent->r.mins[0] + ent->r.maxs[0]) *0.5;
   1091 		tmax[0] = tmin[0] + 1;
   1092 	}
   1093 	if ( tmax[1] <= tmin[1] ) {
   1094 		tmin[1] = ent->pos1[1] + (ent->r.mins[1] + ent->r.maxs[1]) *0.5;
   1095 		tmax[1] = tmin[1] + 1;
   1096 	}
   1097 	
   1098 	VectorCopy (tmin, trigger->r.mins);
   1099 	VectorCopy (tmax, trigger->r.maxs);
   1100 
   1101 	trap_LinkEntity (trigger);
   1102 }
   1103 
   1104 
   1105 /*QUAKED func_plat (0 .5 .8) ?
   1106 Plats are always drawn in the extended position so they will light correctly.
   1107 
   1108 "lip"		default 8, protrusion above rest position
   1109 "height"	total height of movement, defaults to model height
   1110 "speed"		overrides default 200.
   1111 "dmg"		overrides default 2
   1112 "model2"	.md3 model to also draw
   1113 "color"		constantLight color
   1114 "light"		constantLight radius
   1115 */
   1116 void SP_func_plat (gentity_t *ent) {
   1117 	float		lip, height;
   1118 
   1119 	ent->sound1to2 = ent->sound2to1 = G_SoundIndex("sound/movers/plats/pt1_strt.wav");
   1120 	ent->soundPos1 = ent->soundPos2 = G_SoundIndex("sound/movers/plats/pt1_end.wav");
   1121 
   1122 	VectorClear (ent->s.angles);
   1123 
   1124 	G_SpawnFloat( "speed", "200", &ent->speed );
   1125 	G_SpawnInt( "dmg", "2", &ent->damage );
   1126 	G_SpawnFloat( "wait", "1", &ent->wait );
   1127 	G_SpawnFloat( "lip", "8", &lip );
   1128 
   1129 	ent->wait = 1000;
   1130 
   1131 	// create second position
   1132 	trap_SetBrushModel( ent, ent->model );
   1133 
   1134 	if ( !G_SpawnFloat( "height", "0", &height ) ) {
   1135 		height = (ent->r.maxs[2] - ent->r.mins[2]) - lip;
   1136 	}
   1137 
   1138 	// pos1 is the rest (bottom) position, pos2 is the top
   1139 	VectorCopy( ent->s.origin, ent->pos2 );
   1140 	VectorCopy( ent->pos2, ent->pos1 );
   1141 	ent->pos1[2] -= height;
   1142 
   1143 	InitMover( ent );
   1144 
   1145 	// touch function keeps the plat from returning while
   1146 	// a live player is standing on it
   1147 	ent->touch = Touch_Plat;
   1148 
   1149 	ent->blocked = Blocked_Door;
   1150 
   1151 	ent->parent = ent;	// so it can be treated as a door
   1152 
   1153 	// spawn the trigger if one hasn't been custom made
   1154 	if ( !ent->targetname ) {
   1155 		SpawnPlatTrigger(ent);
   1156 	}
   1157 }
   1158 
   1159 
   1160 /*
   1161 ===============================================================================
   1162 
   1163 BUTTON
   1164 
   1165 ===============================================================================
   1166 */
   1167 
   1168 /*
   1169 ==============
   1170 Touch_Button
   1171 
   1172 ===============
   1173 */
   1174 void Touch_Button(gentity_t *ent, gentity_t *other, trace_t *trace ) {
   1175 	if ( !other->client ) {
   1176 		return;
   1177 	}
   1178 
   1179 	if ( ent->moverState == MOVER_POS1 ) {
   1180 		Use_BinaryMover( ent, other, other );
   1181 	}
   1182 }
   1183 
   1184 
   1185 /*QUAKED func_button (0 .5 .8) ?
   1186 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.
   1187 
   1188 "model2"	.md3 model to also draw
   1189 "angle"		determines the opening direction
   1190 "target"	all entities with a matching targetname will be used
   1191 "speed"		override the default 40 speed
   1192 "wait"		override the default 1 second wait (-1 = never return)
   1193 "lip"		override the default 4 pixel lip remaining at end of move
   1194 "health"	if set, the button must be killed instead of touched
   1195 "color"		constantLight color
   1196 "light"		constantLight radius
   1197 */
   1198 void SP_func_button( gentity_t *ent ) {
   1199 	vec3_t		abs_movedir;
   1200 	float		distance;
   1201 	vec3_t		size;
   1202 	float		lip;
   1203 
   1204 	ent->sound1to2 = G_SoundIndex("sound/movers/switches/butn2.wav");
   1205 	
   1206 	if ( !ent->speed ) {
   1207 		ent->speed = 40;
   1208 	}
   1209 
   1210 	if ( !ent->wait ) {
   1211 		ent->wait = 1;
   1212 	}
   1213 	ent->wait *= 1000;
   1214 
   1215 	// first position
   1216 	VectorCopy( ent->s.origin, ent->pos1 );
   1217 
   1218 	// calculate second position
   1219 	trap_SetBrushModel( ent, ent->model );
   1220 
   1221 	G_SpawnFloat( "lip", "4", &lip );
   1222 
   1223 	G_SetMovedir( ent->s.angles, ent->movedir );
   1224 	abs_movedir[0] = fabs(ent->movedir[0]);
   1225 	abs_movedir[1] = fabs(ent->movedir[1]);
   1226 	abs_movedir[2] = fabs(ent->movedir[2]);
   1227 	VectorSubtract( ent->r.maxs, ent->r.mins, size );
   1228 	distance = abs_movedir[0] * size[0] + abs_movedir[1] * size[1] + abs_movedir[2] * size[2] - lip;
   1229 	VectorMA (ent->pos1, distance, ent->movedir, ent->pos2);
   1230 
   1231 	if (ent->health) {
   1232 		// shootable button
   1233 		ent->takedamage = qtrue;
   1234 	} else {
   1235 		// touchable button
   1236 		ent->touch = Touch_Button;
   1237 	}
   1238 
   1239 	InitMover( ent );
   1240 }
   1241 
   1242 
   1243 
   1244 /*
   1245 ===============================================================================
   1246 
   1247 TRAIN
   1248 
   1249 ===============================================================================
   1250 */
   1251 
   1252 
   1253 #define TRAIN_START_ON		1
   1254 #define TRAIN_TOGGLE		2
   1255 #define TRAIN_BLOCK_STOPS	4
   1256 
   1257 /*
   1258 ===============
   1259 Think_BeginMoving
   1260 
   1261 The wait time at a corner has completed, so start moving again
   1262 ===============
   1263 */
   1264 void Think_BeginMoving( gentity_t *ent ) {
   1265 	ent->s.pos.trTime = level.time;
   1266 	ent->s.pos.trType = TR_LINEAR_STOP;
   1267 }
   1268 
   1269 /*
   1270 ===============
   1271 Reached_Train
   1272 ===============
   1273 */
   1274 void Reached_Train( gentity_t *ent ) {
   1275 	gentity_t		*next;
   1276 	float			speed;
   1277 	vec3_t			move;
   1278 	float			length;
   1279 
   1280 	// copy the apropriate values
   1281 	next = ent->nextTrain;
   1282 	if ( !next || !next->nextTrain ) {
   1283 		return;		// just stop
   1284 	}
   1285 
   1286 	// fire all other targets
   1287 	G_UseTargets( next, NULL );
   1288 
   1289 	// set the new trajectory
   1290 	ent->nextTrain = next->nextTrain;
   1291 	VectorCopy( next->s.origin, ent->pos1 );
   1292 	VectorCopy( next->nextTrain->s.origin, ent->pos2 );
   1293 
   1294 	// if the path_corner has a speed, use that
   1295 	if ( next->speed ) {
   1296 		speed = next->speed;
   1297 	} else {
   1298 		// otherwise use the train's speed
   1299 		speed = ent->speed;
   1300 	}
   1301 	if ( speed < 1 ) {
   1302 		speed = 1;
   1303 	}
   1304 
   1305 	// calculate duration
   1306 	VectorSubtract( ent->pos2, ent->pos1, move );
   1307 	length = VectorLength( move );
   1308 
   1309 	ent->s.pos.trDuration = length * 1000 / speed;
   1310 
   1311 	// looping sound
   1312 	ent->s.loopSound = next->soundLoop;
   1313 
   1314 	// start it going
   1315 	SetMoverState( ent, MOVER_1TO2, level.time );
   1316 
   1317 	// if there is a "wait" value on the target, don't start moving yet
   1318 	if ( next->wait ) {
   1319 		ent->nextthink = level.time + next->wait * 1000;
   1320 		ent->think = Think_BeginMoving;
   1321 		ent->s.pos.trType = TR_STATIONARY;
   1322 	}
   1323 }
   1324 
   1325 
   1326 /*
   1327 ===============
   1328 Think_SetupTrainTargets
   1329 
   1330 Link all the corners together
   1331 ===============
   1332 */
   1333 void Think_SetupTrainTargets( gentity_t *ent ) {
   1334 	gentity_t		*path, *next, *start;
   1335 
   1336 	ent->nextTrain = G_Find( NULL, FOFS(targetname), ent->target );
   1337 	if ( !ent->nextTrain ) {
   1338 		G_Printf( "func_train at %s with an unfound target\n",
   1339 			vtos(ent->r.absmin) );
   1340 		return;
   1341 	}
   1342 
   1343 	start = NULL;
   1344 	for ( path = ent->nextTrain ; path != start ; path = next ) {
   1345 		if ( !start ) {
   1346 			start = path;
   1347 		}
   1348 
   1349 		if ( !path->target ) {
   1350 			G_Printf( "Train corner at %s without a target\n",
   1351 				vtos(path->s.origin) );
   1352 			return;
   1353 		}
   1354 
   1355 		// find a path_corner among the targets
   1356 		// there may also be other targets that get fired when the corner
   1357 		// is reached
   1358 		next = NULL;
   1359 		do {
   1360 			next = G_Find( next, FOFS(targetname), path->target );
   1361 			if ( !next ) {
   1362 				G_Printf( "Train corner at %s without a target path_corner\n",
   1363 					vtos(path->s.origin) );
   1364 				return;
   1365 			}
   1366 		} while ( strcmp( next->classname, "path_corner" ) );
   1367 
   1368 		path->nextTrain = next;
   1369 	}
   1370 
   1371 	// start the train moving from the first corner
   1372 	Reached_Train( ent );
   1373 }
   1374 
   1375 
   1376 
   1377 /*QUAKED path_corner (.5 .3 0) (-8 -8 -8) (8 8 8)
   1378 Train path corners.
   1379 Target: next path corner and other targets to fire
   1380 "speed" speed to move to the next corner
   1381 "wait" seconds to wait before behining move to next corner
   1382 */
   1383 void SP_path_corner( gentity_t *self ) {
   1384 	if ( !self->targetname ) {
   1385 		G_Printf ("path_corner with no targetname at %s\n", vtos(self->s.origin));
   1386 		G_FreeEntity( self );
   1387 		return;
   1388 	}
   1389 	// path corners don't need to be linked in
   1390 }
   1391 
   1392 
   1393 
   1394 /*QUAKED func_train (0 .5 .8) ? START_ON TOGGLE BLOCK_STOPS
   1395 A train is a mover that moves between path_corner target points.
   1396 Trains MUST HAVE AN ORIGIN BRUSH.
   1397 The train spawns at the first target it is pointing at.
   1398 "model2"	.md3 model to also draw
   1399 "speed"		default 100
   1400 "dmg"		default	2
   1401 "noise"		looping sound to play when the train is in motion
   1402 "target"	next path corner
   1403 "color"		constantLight color
   1404 "light"		constantLight radius
   1405 */
   1406 void SP_func_train (gentity_t *self) {
   1407 	VectorClear (self->s.angles);
   1408 
   1409 	if (self->spawnflags & TRAIN_BLOCK_STOPS) {
   1410 		self->damage = 0;
   1411 	} else {
   1412 		if (!self->damage) {
   1413 			self->damage = 2;
   1414 		}
   1415 	}
   1416 
   1417 	if ( !self->speed ) {
   1418 		self->speed = 100;
   1419 	}
   1420 
   1421 	if ( !self->target ) {
   1422 		G_Printf ("func_train without a target at %s\n", vtos(self->r.absmin));
   1423 		G_FreeEntity( self );
   1424 		return;
   1425 	}
   1426 
   1427 	trap_SetBrushModel( self, self->model );
   1428 	InitMover( self );
   1429 
   1430 	self->reached = Reached_Train;
   1431 
   1432 	// start trains on the second frame, to make sure their targets have had
   1433 	// a chance to spawn
   1434 	self->nextthink = level.time + FRAMETIME;
   1435 	self->think = Think_SetupTrainTargets;
   1436 }
   1437 
   1438 /*
   1439 ===============================================================================
   1440 
   1441 STATIC
   1442 
   1443 ===============================================================================
   1444 */
   1445 
   1446 
   1447 /*QUAKED func_static (0 .5 .8) ?
   1448 A bmodel that just sits there, doing nothing.  Can be used for conditional walls and models.
   1449 "model2"	.md3 model to also draw
   1450 "color"		constantLight color
   1451 "light"		constantLight radius
   1452 */
   1453 void SP_func_static( gentity_t *ent ) {
   1454 	trap_SetBrushModel( ent, ent->model );
   1455 	InitMover( ent );
   1456 	VectorCopy( ent->s.origin, ent->s.pos.trBase );
   1457 	VectorCopy( ent->s.origin, ent->r.currentOrigin );
   1458 }
   1459 
   1460 
   1461 /*
   1462 ===============================================================================
   1463 
   1464 ROTATING
   1465 
   1466 ===============================================================================
   1467 */
   1468 
   1469 
   1470 /*QUAKED func_rotating (0 .5 .8) ? START_ON - X_AXIS Y_AXIS
   1471 You need to have an origin brush as part of this entity.  The center of that brush will be
   1472 the point around which it is rotated. It will rotate around the Z axis by default.  You can
   1473 check either the X_AXIS or Y_AXIS box to change that.
   1474 
   1475 "model2"	.md3 model to also draw
   1476 "speed"		determines how fast it moves; default value is 100.
   1477 "dmg"		damage to inflict when blocked (2 default)
   1478 "color"		constantLight color
   1479 "light"		constantLight radius
   1480 */
   1481 void SP_func_rotating (gentity_t *ent) {
   1482 	if ( !ent->speed ) {
   1483 		ent->speed = 100;
   1484 	}
   1485 
   1486 	// set the axis of rotation
   1487 	ent->s.apos.trType = TR_LINEAR;
   1488 	if ( ent->spawnflags & 4 ) {
   1489 		ent->s.apos.trDelta[2] = ent->speed;
   1490 	} else if ( ent->spawnflags & 8 ) {
   1491 		ent->s.apos.trDelta[0] = ent->speed;
   1492 	} else {
   1493 		ent->s.apos.trDelta[1] = ent->speed;
   1494 	}
   1495 
   1496 	if (!ent->damage) {
   1497 		ent->damage = 2;
   1498 	}
   1499 
   1500 	trap_SetBrushModel( ent, ent->model );
   1501 	InitMover( ent );
   1502 
   1503 	VectorCopy( ent->s.origin, ent->s.pos.trBase );
   1504 	VectorCopy( ent->s.pos.trBase, ent->r.currentOrigin );
   1505 	VectorCopy( ent->s.apos.trBase, ent->r.currentAngles );
   1506 
   1507 	trap_LinkEntity( ent );
   1508 }
   1509 
   1510 
   1511 /*
   1512 ===============================================================================
   1513 
   1514 BOBBING
   1515 
   1516 ===============================================================================
   1517 */
   1518 
   1519 
   1520 /*QUAKED func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS
   1521 Normally bobs on the Z axis
   1522 "model2"	.md3 model to also draw
   1523 "height"	amplitude of bob (32 default)
   1524 "speed"		seconds to complete a bob cycle (4 default)
   1525 "phase"		the 0.0 to 1.0 offset in the cycle to start at
   1526 "dmg"		damage to inflict when blocked (2 default)
   1527 "color"		constantLight color
   1528 "light"		constantLight radius
   1529 */
   1530 void SP_func_bobbing (gentity_t *ent) {
   1531 	float		height;
   1532 	float		phase;
   1533 
   1534 	G_SpawnFloat( "speed", "4", &ent->speed );
   1535 	G_SpawnFloat( "height", "32", &height );
   1536 	G_SpawnInt( "dmg", "2", &ent->damage );
   1537 	G_SpawnFloat( "phase", "0", &phase );
   1538 
   1539 	trap_SetBrushModel( ent, ent->model );
   1540 	InitMover( ent );
   1541 
   1542 	VectorCopy( ent->s.origin, ent->s.pos.trBase );
   1543 	VectorCopy( ent->s.origin, ent->r.currentOrigin );
   1544 
   1545 	ent->s.pos.trDuration = ent->speed * 1000;
   1546 	ent->s.pos.trTime = ent->s.pos.trDuration * phase;
   1547 	ent->s.pos.trType = TR_SINE;
   1548 
   1549 	// set the axis of bobbing
   1550 	if ( ent->spawnflags & 1 ) {
   1551 		ent->s.pos.trDelta[0] = height;
   1552 	} else if ( ent->spawnflags & 2 ) {
   1553 		ent->s.pos.trDelta[1] = height;
   1554 	} else {
   1555 		ent->s.pos.trDelta[2] = height;
   1556 	}
   1557 }
   1558 
   1559 /*
   1560 ===============================================================================
   1561 
   1562 PENDULUM
   1563 
   1564 ===============================================================================
   1565 */
   1566 
   1567 
   1568 /*QUAKED func_pendulum (0 .5 .8) ?
   1569 You need to have an origin brush as part of this entity.
   1570 Pendulums always swing north / south on unrotated models.  Add an angles field to the model to allow rotation in other directions.
   1571 Pendulum frequency is a physical constant based on the length of the beam and gravity.
   1572 "model2"	.md3 model to also draw
   1573 "speed"		the number of degrees each way the pendulum swings, (30 default)
   1574 "phase"		the 0.0 to 1.0 offset in the cycle to start at
   1575 "dmg"		damage to inflict when blocked (2 default)
   1576 "color"		constantLight color
   1577 "light"		constantLight radius
   1578 */
   1579 void SP_func_pendulum(gentity_t *ent) {
   1580 	float		freq;
   1581 	float		length;
   1582 	float		phase;
   1583 	float		speed;
   1584 
   1585 	G_SpawnFloat( "speed", "30", &speed );
   1586 	G_SpawnInt( "dmg", "2", &ent->damage );
   1587 	G_SpawnFloat( "phase", "0", &phase );
   1588 
   1589 	trap_SetBrushModel( ent, ent->model );
   1590 
   1591 	// find pendulum length
   1592 	length = fabs( ent->r.mins[2] );
   1593 	if ( length < 8 ) {
   1594 		length = 8;
   1595 	}
   1596 
   1597 	freq = 1 / ( M_PI * 2 ) * sqrt( g_gravity.value / ( 3 * length ) );
   1598 
   1599 	ent->s.pos.trDuration = ( 1000 / freq );
   1600 
   1601 	InitMover( ent );
   1602 
   1603 	VectorCopy( ent->s.origin, ent->s.pos.trBase );
   1604 	VectorCopy( ent->s.origin, ent->r.currentOrigin );
   1605 
   1606 	VectorCopy( ent->s.angles, ent->s.apos.trBase );
   1607 
   1608 	ent->s.apos.trDuration = 1000 / freq;
   1609 	ent->s.apos.trTime = ent->s.apos.trDuration * phase;
   1610 	ent->s.apos.trType = TR_SINE;
   1611 	ent->s.apos.trDelta[2] = speed;
   1612 }