DOOM-3-BFG

DOOM 3 BFG Edition
Log | Files | Refs

Mover.cpp (117817B)


      1 /*
      2 ===========================================================================
      3 
      4 Doom 3 BFG Edition GPL Source Code
      5 Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. 
      6 
      7 This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").  
      8 
      9 Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify
     10 it under the terms of the GNU General Public License as published by
     11 the Free Software Foundation, either version 3 of the License, or
     12 (at your option) any later version.
     13 
     14 Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful,
     15 but WITHOUT ANY WARRANTY; without even the implied warranty of
     16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     17 GNU General Public License for more details.
     18 
     19 You should have received a copy of the GNU General Public License
     20 along with Doom 3 BFG Edition Source Code.  If not, see <http://www.gnu.org/licenses/>.
     21 
     22 In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code.  If not, please request a copy in writing from id Software at the address below.
     23 
     24 If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
     25 
     26 ===========================================================================
     27 */
     28 
     29 #include "../idlib/precompiled.h"
     30 #pragma hdrstop
     31 
     32 #include "Game_local.h"
     33 
     34 // _D3XP : rename all gameLocal.time to gameLocal.slow.time for merge!
     35 
     36 // a mover will update any gui entities in it's target list with 
     37 // a key/val pair of "mover" "state" from below.. guis can represent
     38 // realtime info like this
     39 // binary only
     40 static const char *guiBinaryMoverStates[] = {
     41 	"1",	// pos 1
     42 	"2",	// pos 2
     43 	"3",	// moving 1 to 2
     44 	"4"		// moving 2 to 1
     45 };
     46 
     47 
     48 /*
     49 ===============================================================================
     50 
     51 idMover
     52 
     53 ===============================================================================
     54 */
     55 
     56 const idEventDef EV_FindGuiTargets( "<FindGuiTargets>", NULL );
     57 const idEventDef EV_TeamBlocked( "<teamblocked>", "ee" );
     58 const idEventDef EV_PartBlocked( "<partblocked>", "e" );
     59 const idEventDef EV_ReachedPos( "<reachedpos>", NULL );
     60 const idEventDef EV_ReachedAng( "<reachedang>", NULL );
     61 const idEventDef EV_PostRestore( "<postrestore>", "ddddd" );
     62 const idEventDef EV_StopMoving( "stopMoving", NULL );
     63 const idEventDef EV_StopRotating( "stopRotating", NULL );
     64 const idEventDef EV_Speed( "speed", "f" );
     65 const idEventDef EV_Time( "time", "f" );
     66 const idEventDef EV_AccelTime( "accelTime", "f" );
     67 const idEventDef EV_DecelTime( "decelTime", "f" );
     68 const idEventDef EV_MoveTo( "moveTo", "e" );
     69 const idEventDef EV_MoveToPos( "moveToPos", "v" );
     70 const idEventDef EV_Move( "move", "ff" );
     71 const idEventDef EV_MoveAccelerateTo( "accelTo", "ff" );
     72 const idEventDef EV_MoveDecelerateTo( "decelTo", "ff" );
     73 const idEventDef EV_RotateDownTo( "rotateDownTo", "df" );
     74 const idEventDef EV_RotateUpTo( "rotateUpTo", "df" );
     75 const idEventDef EV_RotateTo( "rotateTo", "v" );
     76 const idEventDef EV_Rotate( "rotate", "v" );
     77 const idEventDef EV_RotateOnce( "rotateOnce", "v" );
     78 const idEventDef EV_Bob( "bob", "ffv" );
     79 const idEventDef EV_Sway( "sway", "ffv" );
     80 const idEventDef EV_Mover_OpenPortal( "openPortal" );
     81 const idEventDef EV_Mover_ClosePortal( "closePortal" );
     82 const idEventDef EV_AccelSound( "accelSound", "s" );
     83 const idEventDef EV_DecelSound( "decelSound", "s" );
     84 const idEventDef EV_MoveSound( "moveSound", "s" );
     85 const idEventDef EV_Mover_InitGuiTargets( "<initguitargets>", NULL );
     86 const idEventDef EV_EnableSplineAngles( "enableSplineAngles", NULL );
     87 const idEventDef EV_DisableSplineAngles( "disableSplineAngles", NULL );
     88 const idEventDef EV_RemoveInitialSplineAngles( "removeInitialSplineAngles", NULL );
     89 const idEventDef EV_StartSpline( "startSpline", "e" );
     90 const idEventDef EV_StopSpline( "stopSpline", NULL );
     91 const idEventDef EV_IsMoving( "isMoving", NULL, 'd' );
     92 const idEventDef EV_IsRotating( "isRotating", NULL, 'd' );
     93 
     94 CLASS_DECLARATION( idEntity, idMover )
     95 	EVENT( EV_FindGuiTargets,		idMover::Event_FindGuiTargets )
     96 	EVENT( EV_Thread_SetCallback,	idMover::Event_SetCallback )
     97 	EVENT( EV_TeamBlocked,			idMover::Event_TeamBlocked )
     98 	EVENT( EV_PartBlocked,			idMover::Event_PartBlocked )
     99 	EVENT( EV_ReachedPos,			idMover::Event_UpdateMove )
    100 	EVENT( EV_ReachedAng,			idMover::Event_UpdateRotation )
    101 	EVENT( EV_PostRestore,			idMover::Event_PostRestore )
    102 	EVENT( EV_StopMoving,			idMover::Event_StopMoving )
    103 	EVENT( EV_StopRotating,			idMover::Event_StopRotating )
    104 	EVENT( EV_Speed,				idMover::Event_SetMoveSpeed )
    105 	EVENT( EV_Time,					idMover::Event_SetMoveTime )
    106 	EVENT( EV_AccelTime,			idMover::Event_SetAccellerationTime )
    107 	EVENT( EV_DecelTime,			idMover::Event_SetDecelerationTime )
    108 	EVENT( EV_MoveTo,				idMover::Event_MoveTo )
    109 	EVENT( EV_MoveToPos,			idMover::Event_MoveToPos )
    110 	EVENT( EV_Move,					idMover::Event_MoveDir )
    111 	EVENT( EV_MoveAccelerateTo,		idMover::Event_MoveAccelerateTo )
    112 	EVENT( EV_MoveDecelerateTo,		idMover::Event_MoveDecelerateTo )
    113 	EVENT( EV_RotateDownTo,			idMover::Event_RotateDownTo )
    114 	EVENT( EV_RotateUpTo,			idMover::Event_RotateUpTo )
    115 	EVENT( EV_RotateTo,				idMover::Event_RotateTo )
    116 	EVENT( EV_Rotate,				idMover::Event_Rotate )
    117 	EVENT( EV_RotateOnce,			idMover::Event_RotateOnce )
    118 	EVENT( EV_Bob,					idMover::Event_Bob )
    119 	EVENT( EV_Sway,					idMover::Event_Sway )
    120 	EVENT( EV_Mover_OpenPortal,		idMover::Event_OpenPortal )
    121 	EVENT( EV_Mover_ClosePortal,	idMover::Event_ClosePortal )
    122 	EVENT( EV_AccelSound,			idMover::Event_SetAccelSound )
    123 	EVENT( EV_DecelSound,			idMover::Event_SetDecelSound )
    124 	EVENT( EV_MoveSound,			idMover::Event_SetMoveSound )
    125 	EVENT( EV_Mover_InitGuiTargets,	idMover::Event_InitGuiTargets )
    126 	EVENT( EV_EnableSplineAngles,	idMover::Event_EnableSplineAngles )
    127 	EVENT( EV_DisableSplineAngles,	idMover::Event_DisableSplineAngles )
    128 	EVENT( EV_RemoveInitialSplineAngles, idMover::Event_RemoveInitialSplineAngles )
    129 	EVENT( EV_StartSpline,			idMover::Event_StartSpline )
    130 	EVENT( EV_StopSpline,			idMover::Event_StopSpline )
    131 	EVENT( EV_Activate,				idMover::Event_Activate )
    132 	EVENT( EV_IsMoving,				idMover::Event_IsMoving )
    133 	EVENT( EV_IsRotating,			idMover::Event_IsRotating )
    134 END_CLASS
    135 
    136 /*
    137 ================
    138 idMover::idMover
    139 ================
    140 */
    141 idMover::idMover() {
    142 	memset( &move, 0, sizeof( move ) );
    143 	memset( &rot, 0, sizeof( rot ) );
    144 	move_thread = 0;
    145 	rotate_thread = 0;
    146 	dest_angles.Zero();
    147 	angle_delta.Zero();
    148 	dest_position.Zero();
    149 	move_delta.Zero();
    150 	move_speed = 0.0f;
    151 	move_time = 0;
    152 	deceltime = 0;
    153 	acceltime = 0;
    154 	stopRotation = false;
    155 	useSplineAngles = true;
    156 	lastCommand = MOVER_NONE;
    157 	damage = 0.0f;
    158 	areaPortal = 0;
    159 	fl.networkSync = true;
    160 }
    161 
    162 /*
    163 ================
    164 idMover::Save
    165 ================
    166 */
    167 void idMover::Save( idSaveGame *savefile ) const {
    168 	int i;
    169 
    170 	savefile->WriteStaticObject( physicsObj );
    171 
    172 	savefile->WriteInt( move.stage );
    173 	savefile->WriteInt( move.acceleration );
    174 	savefile->WriteInt( move.movetime );
    175 	savefile->WriteInt( move.deceleration );
    176 	savefile->WriteVec3( move.dir );
    177 	
    178 	savefile->WriteInt( rot.stage );
    179 	savefile->WriteInt( rot.acceleration );
    180 	savefile->WriteInt( rot.movetime );
    181 	savefile->WriteInt( rot.deceleration );
    182 	savefile->WriteFloat( rot.rot.pitch );
    183 	savefile->WriteFloat( rot.rot.yaw );
    184 	savefile->WriteFloat( rot.rot.roll );
    185 	
    186 	savefile->WriteInt( move_thread );
    187 	savefile->WriteInt( rotate_thread );
    188 
    189 	savefile->WriteAngles( dest_angles );
    190 	savefile->WriteAngles( angle_delta );
    191 	savefile->WriteVec3( dest_position );
    192 	savefile->WriteVec3( move_delta );
    193 
    194 	savefile->WriteFloat( move_speed );
    195 	savefile->WriteInt( move_time );
    196 	savefile->WriteInt( deceltime );
    197 	savefile->WriteInt( acceltime );
    198 	savefile->WriteBool( stopRotation );
    199 	savefile->WriteBool( useSplineAngles );
    200 	savefile->WriteInt( lastCommand );
    201 	savefile->WriteFloat( damage );
    202 
    203 	savefile->WriteInt( areaPortal );
    204 	if ( areaPortal > 0 ) {
    205 		savefile->WriteInt( gameRenderWorld->GetPortalState( areaPortal ) );
    206 	}
    207 
    208 	savefile->WriteInt( guiTargets.Num() );
    209 	for( i = 0; i < guiTargets.Num(); i++ ) {
    210 		guiTargets[ i ].Save( savefile );
    211 	}
    212 
    213 	if ( splineEnt.GetEntity() && splineEnt.GetEntity()->GetSpline() ) {
    214 		idCurve_Spline<idVec3> *spline = physicsObj.GetSpline();
    215 
    216 		savefile->WriteBool( true );
    217 		splineEnt.Save( savefile );
    218 		savefile->WriteInt( spline->GetTime( 0 ) );
    219 		savefile->WriteInt( spline->GetTime( spline->GetNumValues() - 1 ) - spline->GetTime( 0 ) );
    220 		savefile->WriteInt( physicsObj.GetSplineAcceleration() );
    221 		savefile->WriteInt( physicsObj.GetSplineDeceleration() );
    222 		savefile->WriteInt( (int)physicsObj.UsingSplineAngles() );
    223 
    224 	} else {
    225 		savefile->WriteBool( false );
    226 	}
    227 }
    228 
    229 /*
    230 ================
    231 idMover::Restore
    232 ================
    233 */
    234 void idMover::Restore( idRestoreGame *savefile ) {
    235 	int i, num;
    236 	bool hasSpline = false;
    237 
    238 	savefile->ReadStaticObject( physicsObj );
    239 	RestorePhysics( &physicsObj );
    240 
    241 	savefile->ReadInt( (int&)move.stage );
    242 	savefile->ReadInt( move.acceleration );
    243 	savefile->ReadInt( move.movetime );
    244 	savefile->ReadInt( move.deceleration );
    245 	savefile->ReadVec3( move.dir );
    246 	
    247 	savefile->ReadInt( (int&)rot.stage );
    248 	savefile->ReadInt( rot.acceleration );
    249 	savefile->ReadInt( rot.movetime );
    250 	savefile->ReadInt( rot.deceleration );
    251 	savefile->ReadFloat( rot.rot.pitch );
    252 	savefile->ReadFloat( rot.rot.yaw );
    253 	savefile->ReadFloat( rot.rot.roll );
    254 	
    255 	savefile->ReadInt( move_thread );
    256 	savefile->ReadInt( rotate_thread );
    257 
    258 	savefile->ReadAngles( dest_angles );
    259 	savefile->ReadAngles( angle_delta );
    260 	savefile->ReadVec3( dest_position );
    261 	savefile->ReadVec3( move_delta );
    262 
    263 	savefile->ReadFloat( move_speed );
    264 	savefile->ReadInt( move_time );
    265 	savefile->ReadInt( deceltime );
    266 	savefile->ReadInt( acceltime );
    267 	savefile->ReadBool( stopRotation );
    268 	savefile->ReadBool( useSplineAngles );
    269 	savefile->ReadInt( (int &)lastCommand );
    270 	savefile->ReadFloat( damage );
    271 
    272 	savefile->ReadInt( areaPortal );
    273 	if ( areaPortal > 0 ) {
    274 		int portalState = 0;
    275 		savefile->ReadInt( portalState );
    276 		gameLocal.SetPortalState( areaPortal, portalState );
    277 	}
    278 
    279 	guiTargets.Clear();
    280 	savefile->ReadInt( num );
    281 	guiTargets.SetNum( num );
    282 	for( i = 0; i < num; i++ ) {
    283 		guiTargets[ i ].Restore( savefile );
    284 	}
    285 
    286 	savefile->ReadBool( hasSpline );
    287 	if ( hasSpline ) {
    288 		int starttime;
    289 		int totaltime;
    290 		int accel;
    291 		int decel;
    292 		int useAngles;
    293 
    294 		splineEnt.Restore( savefile );
    295 		savefile->ReadInt( starttime );
    296 		savefile->ReadInt( totaltime );
    297 		savefile->ReadInt( accel );
    298 		savefile->ReadInt( decel );
    299 		savefile->ReadInt( useAngles );
    300 
    301 		PostEventMS( &EV_PostRestore, 0, starttime, totaltime, accel, decel, useAngles );
    302 	} 
    303 }
    304 
    305 /*
    306 ================
    307 idMover::Event_PostRestore
    308 ================
    309 */
    310 void idMover::Event_PostRestore( int start, int total, int accel, int decel, int useSplineAng ) {
    311 	idCurve_Spline<idVec3> *spline;
    312 
    313 	idEntity *splineEntity = splineEnt.GetEntity();
    314 	if ( !splineEntity ) {
    315 		// We should never get this event if splineEnt is invalid
    316 		common->Warning( "Invalid spline entity during restore\n" );
    317 		return;
    318 	}
    319 
    320 	spline = splineEntity->GetSpline();
    321 
    322 	spline->MakeUniform( total );
    323 	spline->ShiftTime( start - spline->GetTime( 0 ) );
    324 
    325 	physicsObj.SetSpline( spline, accel, decel, ( useSplineAng != 0 ) );
    326 	physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, dest_position, vec3_origin, vec3_origin );
    327 }
    328 
    329 /*
    330 ================
    331 idMover::Spawn
    332 ================
    333 */
    334 void idMover::Spawn() {
    335 	move_thread		= 0;
    336 	rotate_thread	= 0;
    337 	stopRotation	= false;
    338 	lastCommand		= MOVER_NONE;
    339 
    340 	acceltime		= 1000.0f * spawnArgs.GetFloat( "accel_time", "0" );
    341 	deceltime		= 1000.0f * spawnArgs.GetFloat( "decel_time", "0" );
    342 	move_time		= 1000.0f * spawnArgs.GetFloat( "move_time", "1" );	// safe default value
    343 	move_speed		= spawnArgs.GetFloat( "move_speed", "0" );
    344 
    345 	spawnArgs.GetFloat( "damage" , "0", damage );
    346 
    347 	dest_position = GetPhysics()->GetOrigin();
    348 	dest_angles = GetPhysics()->GetAxis().ToAngles();
    349 
    350 	physicsObj.SetSelf( this );
    351 	physicsObj.SetClipModel( new (TAG_PHYSICS_CLIP_MOVER) idClipModel( GetPhysics()->GetClipModel() ), 1.0f );
    352 	physicsObj.SetOrigin( GetPhysics()->GetOrigin() );
    353 	physicsObj.SetAxis( GetPhysics()->GetAxis() );
    354 	physicsObj.SetClipMask( MASK_SOLID );
    355 	if ( !spawnArgs.GetBool( "solid", "1" ) ) {
    356 		physicsObj.SetContents( 0 );
    357 	}
    358 	if ( !renderEntity.hModel || !spawnArgs.GetBool( "nopush" ) ) {
    359 		physicsObj.SetPusher( 0 );
    360 	}
    361 	physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, dest_position, vec3_origin, vec3_origin );
    362 	physicsObj.SetAngularExtrapolation( EXTRAPOLATION_NONE, 0, 0, dest_angles, ang_zero, ang_zero );
    363 	SetPhysics( &physicsObj );
    364 
    365 	// see if we are on an areaportal
    366 	areaPortal = gameRenderWorld->FindPortal( GetPhysics()->GetAbsBounds() );
    367 
    368 	if ( spawnArgs.MatchPrefix( "guiTarget" ) ) {
    369 		if ( gameLocal.GameState() == GAMESTATE_STARTUP ) {
    370 			PostEventMS( &EV_FindGuiTargets, 0 );
    371 		} else {
    372 			// not during spawn, so it's ok to get the targets
    373 			FindGuiTargets();
    374 		}
    375 	}
    376 
    377 	health = spawnArgs.GetInt( "health" );
    378 	if ( health ) {
    379 		fl.takedamage = true;
    380 	}
    381 
    382 }
    383 
    384 /*
    385 ================
    386 idMover::Hide
    387 ================
    388 */
    389 void idMover::Hide() {
    390 	idEntity::Hide();
    391 	physicsObj.SetContents( 0 );
    392 }
    393 
    394 /*
    395 ================
    396 idMover::Show
    397 ================
    398 */
    399 void idMover::Show() {
    400 	idEntity::Show();
    401 	if ( spawnArgs.GetBool( "solid", "1" ) ) {
    402 		physicsObj.SetContents( CONTENTS_SOLID );
    403 	}
    404 	SetPhysics( &physicsObj );
    405 }
    406 
    407 /*
    408 ============
    409 idMover::Killed
    410 ============
    411 */
    412 void idMover::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
    413 	fl.takedamage = false;
    414 	ActivateTargets( this );
    415 }
    416 
    417 
    418 /*
    419 ================
    420 idMover::Event_SetCallback
    421 ================
    422 */
    423 void idMover::Event_SetCallback() {
    424 	if ( ( lastCommand == MOVER_ROTATING ) && !rotate_thread ) {
    425 		lastCommand	= MOVER_NONE;
    426 		rotate_thread = idThread::CurrentThreadNum();
    427 		idThread::ReturnInt( true );
    428 	} else if ( ( lastCommand == MOVER_MOVING || lastCommand == MOVER_SPLINE ) && !move_thread ) {
    429 		lastCommand	= MOVER_NONE;
    430 		move_thread = idThread::CurrentThreadNum();
    431 		idThread::ReturnInt( true );
    432 	} else {
    433 		idThread::ReturnInt( false );
    434 	}
    435 }
    436 
    437 /*
    438 ================
    439 idMover::VectorForDir
    440 ================
    441 */
    442 void idMover::VectorForDir( float angle, idVec3 &vec ) {
    443 	idAngles ang;
    444 
    445 	switch( ( int )angle ) {
    446 	case DIR_UP :
    447 		vec.Set( 0, 0, 1 );
    448 		break;
    449 
    450 	case DIR_DOWN :
    451 		vec.Set( 0, 0, -1 );
    452 		break;
    453 
    454 	case DIR_LEFT :
    455 		physicsObj.GetLocalAngles( ang );
    456 		ang.pitch	= 0;
    457 		ang.roll	= 0;
    458 		ang.yaw		+= 90;
    459 		vec			= ang.ToForward();
    460 		break;
    461 
    462 	case DIR_RIGHT :
    463 		physicsObj.GetLocalAngles( ang );
    464 		ang.pitch	= 0;
    465 		ang.roll	= 0;
    466 		ang.yaw		-= 90;
    467 		vec			= ang.ToForward();
    468 		break;
    469 
    470 	case DIR_FORWARD :
    471 		physicsObj.GetLocalAngles( ang );
    472 		ang.pitch	= 0;
    473 		ang.roll	= 0;
    474 		vec			= ang.ToForward();
    475 		break;
    476 
    477 	case DIR_BACK :
    478 		physicsObj.GetLocalAngles( ang );
    479 		ang.pitch	= 0;
    480 		ang.roll	= 0;
    481 		ang.yaw		+= 180;
    482 		vec			= ang.ToForward();
    483 		break;
    484 
    485 	case DIR_REL_UP :
    486 		vec.Set( 0, 0, 1 );
    487 		break;
    488 
    489 	case DIR_REL_DOWN :
    490 		vec.Set( 0, 0, -1 );
    491 		break;
    492 
    493 	case DIR_REL_LEFT :
    494 		physicsObj.GetLocalAngles( ang );
    495 		ang.ToVectors( NULL, &vec );
    496 		vec *= -1;
    497 		break;
    498 
    499 	case DIR_REL_RIGHT :
    500 		physicsObj.GetLocalAngles( ang );
    501 		ang.ToVectors( NULL, &vec );
    502 		break;
    503 
    504 	case DIR_REL_FORWARD :
    505 		physicsObj.GetLocalAngles( ang );
    506 		vec = ang.ToForward();
    507 		break;
    508 
    509 	case DIR_REL_BACK :
    510 		physicsObj.GetLocalAngles( ang );
    511 		vec = ang.ToForward() * -1;
    512 		break;
    513 
    514 	default:
    515 		ang.Set( 0, angle, 0 );
    516 		vec = GetWorldVector( ang.ToForward() );
    517 		break;
    518 	}
    519 }
    520 
    521 /*
    522 ================
    523 idMover::FindGuiTargets
    524 ================
    525 */
    526 void idMover::FindGuiTargets() {
    527    	gameLocal.GetTargets( spawnArgs, guiTargets, "guiTarget" );
    528 }
    529 
    530 /*
    531 ==============================
    532 idMover::ClientThink
    533 ==============================
    534 */
    535 void idMover::ClientThink( const int curTime, const float fraction, const bool predict ) {
    536 
    537 	// HACK. because I'm not sure all the other stuff this will screw up.
    538 	// There was a reason we weren't fully interpolating movers ( Which would evaluate bound objects ).
    539 	// I just cant remember what it was.
    540 
    541 	// Evaluating the Team will update the parts that bound to the entity. 
    542 	// but because we interpolate the master, we don't want to run evaluate on the mover itself.
    543 	// sending in true to the interpolatePhysicsOnly will run the TeamChain Evaluate, but only on
    544 	// Objects bound to the entity.
    545 	if( this->name == "blueshotty_door" || this->name == "redshotty_door" || 
    546 		this->name == "Red_blastshield_mover" || this->name == "Blue_blastshield_mover" ) {
    547 		InterpolatePhysicsOnly( fraction, true );
    548 	} else {
    549 		InterpolatePhysicsOnly( fraction );
    550 	}
    551 
    552 	Present();
    553 }
    554 
    555 /*
    556 ==============================
    557 idMover::SetGuiState
    558 
    559 key/val will be set to any renderEntity->gui's on the list
    560 ==============================
    561 */
    562 void idMover::SetGuiState( const char *key, const char *val ) const {
    563 	gameLocal.Printf( "Setting %s to %s\n", key, val );
    564 	for( int i = 0; i < guiTargets.Num(); i++ ) {
    565 		idEntity *ent = guiTargets[ i ].GetEntity();
    566 		if ( ent ) {
    567 			for ( int j = 0; j < MAX_RENDERENTITY_GUI; j++ ) {
    568 				if ( ent->GetRenderEntity() && ent->GetRenderEntity()->gui[ j ] ) {
    569 					ent->GetRenderEntity()->gui[ j ]->SetStateString( key, val );
    570 					ent->GetRenderEntity()->gui[ j ]->StateChanged( gameLocal.slow.time, true );
    571 				}
    572 			}
    573 			ent->UpdateVisuals();
    574 		}
    575 	}
    576 }
    577 
    578 /*
    579 ================
    580 idMover::Event_InitGuiTargets
    581 ================
    582 */
    583 void idMover::Event_FindGuiTargets() {
    584 	FindGuiTargets();
    585 }
    586 
    587 /*
    588 ================
    589 idMover::SetGuiStates
    590 ================
    591 */
    592 void idMover::SetGuiStates( const char *state ) {
    593 	int i;
    594 	if ( guiTargets.Num() ) {
    595 		SetGuiState( "movestate", state );
    596 	}
    597 	for ( i = 0; i < MAX_RENDERENTITY_GUI; i++ ) {
    598 		if ( renderEntity.gui[ i ] ) {
    599 			renderEntity.gui[ i ]->SetStateString( "movestate", state );
    600 			renderEntity.gui[ i ]->StateChanged( gameLocal.slow.time, true );
    601 		}
    602 	}
    603 }
    604 
    605 /*
    606 ================
    607 idMover::Event_InitGuiTargets
    608 ================
    609 */
    610 void idMover::Event_InitGuiTargets() {
    611 	SetGuiStates( guiBinaryMoverStates[MOVER_POS1] );
    612 }
    613 
    614 /***********************************************************************
    615 
    616 	Translation control functions
    617 	
    618 ***********************************************************************/
    619 
    620 /*
    621 ================
    622 idMover::Event_StopMoving
    623 ================
    624 */
    625 void idMover::Event_StopMoving() {
    626 	physicsObj.GetLocalOrigin( dest_position );
    627 	DoneMoving();
    628 }
    629 
    630 /*
    631 ================
    632 idMover::DoneMoving
    633 ================
    634 */
    635 void idMover::DoneMoving() {
    636 
    637 	if ( lastCommand != MOVER_SPLINE ) {
    638 		// set our final position so that we get rid of any numerical inaccuracy
    639 		physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, dest_position, vec3_origin, vec3_origin );
    640 	}
    641 
    642 	lastCommand	= MOVER_NONE;
    643 	idThread::ObjectMoveDone( move_thread, this );
    644 	move_thread = 0;
    645 
    646 	StopSound( SND_CHANNEL_BODY, false );
    647 }
    648 
    649 /*
    650 ================
    651 idMover::UpdateMoveSound
    652 ================
    653 */
    654 void idMover::UpdateMoveSound( moveStage_t stage ) {
    655 	switch( stage ) {
    656 		case ACCELERATION_STAGE: {
    657 			StartSound( "snd_accel", SND_CHANNEL_BODY2, 0, false, NULL );
    658 			StartSound( "snd_move", SND_CHANNEL_BODY, 0, false, NULL );
    659 			break;
    660 		}
    661 		case LINEAR_STAGE: {
    662 			StartSound( "snd_move", SND_CHANNEL_BODY, 0, false, NULL );
    663 			break;
    664 		}
    665 		case DECELERATION_STAGE: {
    666 			StopSound( SND_CHANNEL_BODY, false );
    667 			StartSound( "snd_decel", SND_CHANNEL_BODY2, 0, false, NULL );
    668 			break;
    669 		}
    670 		case FINISHED_STAGE: {
    671 			StopSound( SND_CHANNEL_BODY, false );
    672 			break;
    673 		}
    674 	}
    675 }
    676 
    677 /*
    678 ================
    679 idMover::Event_UpdateMove
    680 ================
    681 */
    682 void idMover::Event_UpdateMove() {
    683 	idVec3	org;
    684 
    685 	physicsObj.GetLocalOrigin( org );
    686 
    687 	UpdateMoveSound( move.stage );
    688 
    689 	switch( move.stage ) {
    690 		case ACCELERATION_STAGE: {
    691 			physicsObj.SetLinearExtrapolation( EXTRAPOLATION_ACCELLINEAR, gameLocal.slow.time, move.acceleration, org, move.dir, vec3_origin );
    692 			if ( move.movetime > 0 ) {
    693 				move.stage = LINEAR_STAGE;
    694 			} else if ( move.deceleration > 0 ) {
    695 				move.stage = DECELERATION_STAGE;
    696 			} else {
    697 				move.stage = FINISHED_STAGE;
    698 			}
    699 			break;
    700 		}
    701 		case LINEAR_STAGE: {
    702 			physicsObj.SetLinearExtrapolation( EXTRAPOLATION_LINEAR, gameLocal.slow.time, move.movetime, org, move.dir, vec3_origin );
    703 			if ( move.deceleration ) {
    704 				move.stage = DECELERATION_STAGE;
    705 			} else {
    706 				move.stage = FINISHED_STAGE;
    707 			}
    708 			break;
    709 		}
    710 		case DECELERATION_STAGE: {
    711 			physicsObj.SetLinearExtrapolation( EXTRAPOLATION_DECELLINEAR, gameLocal.slow.time, move.deceleration, org, move.dir, vec3_origin );
    712 			move.stage = FINISHED_STAGE;
    713 			break;
    714 		}
    715 		case FINISHED_STAGE: {
    716 			if ( g_debugMover.GetBool() ) {
    717 				gameLocal.Printf( "%d: '%s' move done\n", gameLocal.slow.time, name.c_str() );
    718 			}
    719 			DoneMoving();
    720 			break;
    721 		}
    722 	}
    723 }
    724 
    725 /*
    726 ================
    727 idMover::BeginMove
    728 ================
    729 */
    730 void idMover::BeginMove( idThread *thread ) {
    731 	moveStage_t stage;
    732 	idVec3		org;
    733 	float		dist;
    734 	float		acceldist;
    735 	int			totalacceltime;
    736 	int			at;
    737 	int			dt;
    738 
    739 	lastCommand	= MOVER_MOVING;
    740 	move_thread = 0;
    741 
    742 	physicsObj.GetLocalOrigin( org );
    743 
    744 	move_delta = dest_position - org;
    745 	if ( move_delta.Compare( vec3_zero ) ) {
    746 		DoneMoving();
    747 		return;
    748 	}
    749 
    750 	// scale times up to whole physics frames
    751 	at = idPhysics::SnapTimeToPhysicsFrame( acceltime );
    752 	move_time += at - acceltime;
    753 	acceltime = at;
    754 	dt = idPhysics::SnapTimeToPhysicsFrame( deceltime );
    755 	move_time += dt - deceltime;
    756 	deceltime = dt;
    757 
    758 	// if we're moving at a specific speed, we need to calculate the move time
    759 	if ( move_speed ) {
    760 		dist = move_delta.Length();
    761 
    762 		totalacceltime = acceltime + deceltime;
    763 
    764 		// calculate the distance we'll move during acceleration and deceleration
    765 		acceldist = totalacceltime * 0.5f * 0.001f * move_speed;
    766 		if ( acceldist >= dist ) {
    767 			// going too slow for this distance to move at a constant speed
    768 			move_time = totalacceltime;
    769 		} else {
    770 			// calculate move time taking acceleration into account
    771 			move_time = totalacceltime + 1000.0f * ( dist - acceldist ) / move_speed;
    772 		}
    773 	}
    774 
    775 	// scale time up to a whole physics frames
    776 	move_time = idPhysics::SnapTimeToPhysicsFrame( move_time );
    777 
    778 	if ( acceltime ) {
    779 		stage = ACCELERATION_STAGE;
    780 	} else if ( move_time <= deceltime ) {
    781 		stage = DECELERATION_STAGE;
    782 	} else {
    783 		stage = LINEAR_STAGE;
    784 	}
    785 
    786 	at = acceltime;
    787 	dt = deceltime;
    788 
    789 	if ( at + dt > move_time ) {
    790 		// there's no real correct way to handle this, so we just scale
    791 		// the times to fit into the move time in the same proportions
    792 		at = idPhysics::SnapTimeToPhysicsFrame( at * move_time / ( at + dt ) );
    793 		dt = move_time - at;
    794 	}
    795 
    796 	move_delta = move_delta * ( 1000.0f / ( (float) move_time - ( at + dt ) * 0.5f ) );
    797 
    798 	move.stage			= stage;
    799 	move.acceleration	= at;
    800 	move.movetime		= move_time - at - dt;
    801 	move.deceleration	= dt;
    802 	move.dir			= move_delta;
    803 
    804 	ProcessEvent( &EV_ReachedPos );
    805 }
    806 
    807 /***********************************************************************
    808 
    809 	Rotation control functions
    810 	
    811 ***********************************************************************/
    812 
    813 /*
    814 ================
    815 idMover::Event_StopRotating
    816 ================
    817 */
    818 void idMover::Event_StopRotating() {
    819 	physicsObj.GetLocalAngles( dest_angles );
    820 	physicsObj.SetAngularExtrapolation( EXTRAPOLATION_NONE, 0, 0, dest_angles, ang_zero, ang_zero );
    821 	DoneRotating();
    822 }
    823 
    824 /*
    825 ================
    826 idMover::DoneRotating
    827 ================
    828 */
    829 void idMover::DoneRotating() {
    830 	lastCommand	= MOVER_NONE;
    831 	idThread::ObjectMoveDone( rotate_thread, this );
    832 	rotate_thread = 0;
    833 
    834 	StopSound( SND_CHANNEL_BODY, false );
    835 }
    836 
    837 /*
    838 ================
    839 idMover::UpdateRotationSound
    840 ================
    841 */
    842 void idMover::UpdateRotationSound( moveStage_t stage ) {
    843 	switch( stage ) {
    844 		case ACCELERATION_STAGE: {
    845 			StartSound( "snd_accel", SND_CHANNEL_BODY2, 0, false, NULL );
    846 			StartSound( "snd_move", SND_CHANNEL_BODY, 0, false, NULL );
    847 			break;
    848 		}
    849 		case LINEAR_STAGE: {
    850 			StartSound( "snd_move", SND_CHANNEL_BODY, 0, false, NULL );
    851 			break;
    852 		}
    853 		case DECELERATION_STAGE: {
    854 			StopSound( SND_CHANNEL_BODY, false );
    855 			StartSound( "snd_decel", SND_CHANNEL_BODY2, 0, false, NULL );
    856 			break;
    857 		}
    858 		case FINISHED_STAGE: {
    859 			StopSound( SND_CHANNEL_BODY, false );
    860 			break;
    861 		}
    862 	}
    863 }
    864 
    865 /*
    866 ================
    867 idMover::Event_UpdateRotation
    868 ================
    869 */
    870 void idMover::Event_UpdateRotation() {
    871 	idAngles	ang;
    872 
    873 	physicsObj.GetLocalAngles( ang );
    874 
    875 	UpdateRotationSound( rot.stage );
    876 
    877 	switch( rot.stage ) {
    878 		case ACCELERATION_STAGE: {
    879 			physicsObj.SetAngularExtrapolation( EXTRAPOLATION_ACCELLINEAR, gameLocal.slow.time, rot.acceleration, ang, rot.rot, ang_zero );
    880 			if ( rot.movetime > 0 ) {
    881 				rot.stage = LINEAR_STAGE;
    882 			} else if ( rot.deceleration > 0 ) {
    883 				rot.stage = DECELERATION_STAGE;
    884 			} else {
    885 				rot.stage = FINISHED_STAGE;
    886 			}
    887 			break;
    888 		}
    889 		case LINEAR_STAGE: {
    890 			if ( !stopRotation && !rot.deceleration ) {
    891 				physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_LINEAR|EXTRAPOLATION_NOSTOP), gameLocal.slow.time, rot.movetime, ang, rot.rot, ang_zero );
    892 			} else {
    893 				physicsObj.SetAngularExtrapolation( EXTRAPOLATION_LINEAR, gameLocal.slow.time, rot.movetime, ang, rot.rot, ang_zero );
    894 			}
    895 
    896 			if ( rot.deceleration ) {
    897 				rot.stage = DECELERATION_STAGE;
    898 			} else {
    899 				rot.stage = FINISHED_STAGE;
    900 			}
    901 			break;
    902 		}
    903 		case DECELERATION_STAGE: {
    904 			physicsObj.SetAngularExtrapolation( EXTRAPOLATION_DECELLINEAR, gameLocal.slow.time, rot.deceleration, ang, rot.rot, ang_zero );
    905 			rot.stage = FINISHED_STAGE;
    906 			break;
    907 		}
    908 		case FINISHED_STAGE: {
    909 			lastCommand	= MOVER_NONE;
    910 			if ( stopRotation ) {
    911 				// set our final angles so that we get rid of any numerical inaccuracy
    912 				dest_angles.Normalize360();
    913 				physicsObj.SetAngularExtrapolation( EXTRAPOLATION_NONE, 0, 0, dest_angles, ang_zero, ang_zero );
    914 				stopRotation = false;
    915 			} else if ( physicsObj.GetAngularExtrapolationType() == EXTRAPOLATION_ACCELLINEAR ) {
    916 				// keep our angular velocity constant
    917 				physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_LINEAR|EXTRAPOLATION_NOSTOP), gameLocal.slow.time, 0, ang, rot.rot, ang_zero );
    918 			}
    919 
    920 			if ( g_debugMover.GetBool() ) {
    921 				gameLocal.Printf( "%d: '%s' rotation done\n", gameLocal.slow.time, name.c_str() );
    922 			}
    923 
    924 			DoneRotating();
    925 			break;
    926 		}
    927 	}
    928 }
    929 
    930 /*
    931 ================
    932 idMover::BeginRotation
    933 ================
    934 */
    935 void idMover::BeginRotation( idThread *thread, bool stopwhendone ) {
    936 	moveStage_t stage;
    937 	idAngles	ang;
    938 	int			at;
    939 	int			dt;
    940 
    941 	lastCommand	= MOVER_ROTATING;
    942 	rotate_thread = 0;
    943 
    944 	// rotation always uses move_time so that if a move was started before the rotation,
    945 	// the rotation will take the same amount of time as the move.  If no move has been
    946 	// started and no time is set, the rotation takes 1 second.
    947 	if ( !move_time ) {
    948 		move_time = 1;
    949 	}
    950 
    951 	physicsObj.GetLocalAngles( ang );
    952 	angle_delta = dest_angles - ang;
    953 	if ( angle_delta == ang_zero ) {
    954 		// set our final angles so that we get rid of any numerical inaccuracy
    955 		dest_angles.Normalize360();
    956 		physicsObj.SetAngularExtrapolation( EXTRAPOLATION_NONE, 0, 0, dest_angles, ang_zero, ang_zero );
    957 		stopRotation = false;
    958 		DoneRotating();
    959 		return;
    960 	}
    961 
    962 	// scale times up to whole physics frames
    963 	at = idPhysics::SnapTimeToPhysicsFrame( acceltime );
    964 	move_time += at - acceltime;
    965 	acceltime = at;
    966 	dt = idPhysics::SnapTimeToPhysicsFrame( deceltime );
    967 	move_time += dt - deceltime;
    968 	deceltime = dt;
    969 	move_time = idPhysics::SnapTimeToPhysicsFrame( move_time );
    970 
    971 	if ( acceltime ) {
    972 		stage = ACCELERATION_STAGE;
    973 	} else if ( move_time <= deceltime ) {
    974 		stage = DECELERATION_STAGE;
    975 	} else {
    976 		stage = LINEAR_STAGE;
    977 	}
    978 
    979 	at = acceltime;
    980 	dt = deceltime;
    981 
    982 	if ( at + dt > move_time ) {
    983 		// there's no real correct way to handle this, so we just scale
    984 		// the times to fit into the move time in the same proportions
    985 		at = idPhysics::SnapTimeToPhysicsFrame( at * move_time / ( at + dt ) );
    986 		dt = move_time - at;
    987 	}
    988 
    989 	angle_delta = angle_delta * ( 1000.0f / ( (float) move_time - ( at + dt ) * 0.5f ) );
    990 
    991 	stopRotation = stopwhendone || ( dt != 0 );
    992 
    993 	rot.stage			= stage;
    994 	rot.acceleration	= at;
    995 	rot.movetime		= move_time - at - dt;
    996 	rot.deceleration	= dt;
    997 	rot.rot				= angle_delta;
    998 
    999 	ProcessEvent( &EV_ReachedAng );
   1000 }
   1001 
   1002 
   1003 /***********************************************************************
   1004 
   1005 	Script callable routines  
   1006 	
   1007 ***********************************************************************/
   1008 
   1009 /*
   1010 ===============
   1011 idMover::Event_TeamBlocked
   1012 ===============
   1013 */
   1014 void idMover::Event_TeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ) {
   1015 	if ( g_debugMover.GetBool() ) {
   1016 		gameLocal.Printf( "%d: '%s' stopped due to team member '%s' blocked by '%s'\n", gameLocal.slow.time, name.c_str(), blockedEntity->name.c_str(), blockingEntity->name.c_str() );
   1017 	}
   1018 }
   1019 
   1020 /*
   1021 ===============
   1022 idMover::Event_PartBlocked
   1023 ===============
   1024 */
   1025 void idMover::Event_PartBlocked( idEntity *blockingEntity ) {
   1026 	if ( damage > 0.0f ) {
   1027 		blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", damage, INVALID_JOINT );
   1028 	}
   1029 	if ( g_debugMover.GetBool() ) {
   1030 		gameLocal.Printf( "%d: '%s' blocked by '%s'\n", gameLocal.slow.time, name.c_str(), blockingEntity->name.c_str() );
   1031 	}
   1032 }
   1033 
   1034 /*
   1035 ================
   1036 idMover::Event_SetMoveSpeed
   1037 ================
   1038 */
   1039 void idMover::Event_SetMoveSpeed( float speed ) {
   1040 	if ( speed <= 0 ) {
   1041 		gameLocal.Error( "Cannot set speed less than or equal to 0." );
   1042 	}
   1043 
   1044 	move_speed = speed;
   1045 	move_time = 0;			// move_time is calculated for each move when move_speed is non-0
   1046 }
   1047 
   1048 /*
   1049 ================
   1050 idMover::Event_SetMoveTime
   1051 ================
   1052 */
   1053 void idMover::Event_SetMoveTime( float time ) {
   1054 	if ( time <= 0 ) {
   1055 		gameLocal.Error( "Cannot set time less than or equal to 0." );
   1056 	}
   1057 
   1058 	move_speed = 0;
   1059 	move_time = SEC2MS( time );
   1060 }
   1061 
   1062 /*
   1063 ================
   1064 idMover::Event_SetAccellerationTime
   1065 ================
   1066 */
   1067 void idMover::Event_SetAccellerationTime( float time ) {
   1068 	if ( time < 0 ) {
   1069 		gameLocal.Error( "Cannot set acceleration time less than 0." );
   1070 	}
   1071 
   1072 	acceltime = SEC2MS( time );
   1073 }
   1074 
   1075 /*
   1076 ================
   1077 idMover::Event_SetDecelerationTime
   1078 ================
   1079 */
   1080 void idMover::Event_SetDecelerationTime( float time ) {
   1081 	if ( time < 0 ) {
   1082 		gameLocal.Error( "Cannot set deceleration time less than 0." );
   1083 	}
   1084 
   1085 	deceltime = SEC2MS( time );
   1086 }
   1087 
   1088 /*
   1089 ================
   1090 idMover::Event_MoveTo
   1091 ================
   1092 */
   1093 void idMover::Event_MoveTo( idEntity *ent ) {
   1094 	if ( ent == NULL ) {
   1095 		gameLocal.Warning( "Entity not found" );
   1096 		return;
   1097 	}
   1098 
   1099 	dest_position = GetLocalCoordinates( ent->GetPhysics()->GetOrigin() );
   1100 	BeginMove( idThread::CurrentThread() );
   1101 }
   1102 
   1103 /*
   1104 ================
   1105 idMover::MoveToPos
   1106 ================
   1107 */
   1108 void idMover::MoveToPos( const idVec3 &pos ) {
   1109 	dest_position = GetLocalCoordinates( pos );
   1110 	BeginMove( NULL );
   1111 }
   1112 
   1113 /*
   1114 ================
   1115 idMover::Event_MoveToPos
   1116 ================
   1117 */
   1118 void idMover::Event_MoveToPos( idVec3 &pos ) {
   1119 	MoveToPos( pos );
   1120 }
   1121 
   1122 /*
   1123 ================
   1124 idMover::Event_MoveDir
   1125 ================
   1126 */
   1127 void idMover::Event_MoveDir( float angle, float distance ) {
   1128 	idVec3 dir;
   1129 	idVec3 org;
   1130 
   1131 	physicsObj.GetLocalOrigin( org );
   1132 	VectorForDir( angle, dir );
   1133 	dest_position = org + dir * distance;
   1134 
   1135 	BeginMove( idThread::CurrentThread() );
   1136 }
   1137 
   1138 /*
   1139 ================
   1140 idMover::Event_MoveAccelerateTo
   1141 ================
   1142 */
   1143 void idMover::Event_MoveAccelerateTo( float speed, float time ) {
   1144 	float v;
   1145 	idVec3 org, dir;
   1146 	int at;
   1147 
   1148 	if ( time < 0 ) {
   1149 		gameLocal.Error( "idMover::Event_MoveAccelerateTo: cannot set acceleration time less than 0." );
   1150 	}
   1151 
   1152 	dir = physicsObj.GetLinearVelocity();
   1153 	v = dir.Normalize();
   1154 
   1155 	// if not moving already
   1156 	if ( v == 0.0f ) {
   1157 		gameLocal.Error( "idMover::Event_MoveAccelerateTo: not moving." );
   1158 	}
   1159 
   1160 	// if already moving faster than the desired speed
   1161 	if ( v >= speed ) {
   1162 		return;
   1163 	}
   1164 
   1165 	at = idPhysics::SnapTimeToPhysicsFrame( SEC2MS( time ) );
   1166 
   1167 	lastCommand	= MOVER_MOVING;
   1168 
   1169 	physicsObj.GetLocalOrigin( org );
   1170 
   1171 	move.stage			= ACCELERATION_STAGE;
   1172 	move.acceleration	= at;
   1173 	move.movetime		= 0;
   1174 	move.deceleration	= 0;
   1175 
   1176 	StartSound( "snd_accel", SND_CHANNEL_BODY2, 0, false, NULL );
   1177 	StartSound( "snd_move", SND_CHANNEL_BODY, 0, false, NULL );
   1178 	physicsObj.SetLinearExtrapolation( EXTRAPOLATION_ACCELLINEAR, gameLocal.slow.time, move.acceleration, org, dir * ( speed - v ), dir * v );
   1179 }
   1180 
   1181 /*
   1182 ================
   1183 idMover::Event_MoveDecelerateTo
   1184 ================
   1185 */
   1186 void idMover::Event_MoveDecelerateTo( float speed, float time ) {
   1187 	float v;
   1188 	idVec3 org, dir;
   1189 	int dt;
   1190 
   1191 	if ( time < 0 ) {
   1192 		gameLocal.Error( "idMover::Event_MoveDecelerateTo: cannot set deceleration time less than 0." );
   1193 	}
   1194 
   1195 	dir = physicsObj.GetLinearVelocity();
   1196 	v = dir.Normalize();
   1197 
   1198 	// if not moving already
   1199 	if ( v == 0.0f ) {
   1200 		gameLocal.Error( "idMover::Event_MoveDecelerateTo: not moving." );
   1201 	}
   1202 
   1203 	// if already moving slower than the desired speed
   1204 	if ( v <= speed ) {
   1205 		return;
   1206 	}
   1207 
   1208 	dt = idPhysics::SnapTimeToPhysicsFrame( SEC2MS( time ) );
   1209 
   1210 	lastCommand	= MOVER_MOVING;
   1211 
   1212 	physicsObj.GetLocalOrigin( org );
   1213 
   1214 	move.stage			= DECELERATION_STAGE;
   1215 	move.acceleration	= 0;
   1216 	move.movetime		= 0;
   1217 	move.deceleration	= dt;
   1218 
   1219 	StartSound( "snd_decel", SND_CHANNEL_BODY2, 0, false, NULL );
   1220 	StartSound( "snd_move", SND_CHANNEL_BODY, 0, false, NULL );
   1221 	physicsObj.SetLinearExtrapolation( EXTRAPOLATION_DECELLINEAR, gameLocal.slow.time, move.deceleration, org, dir * ( v - speed ), dir * speed );
   1222 }
   1223 
   1224 /*
   1225 ================
   1226 idMover::Event_RotateDownTo
   1227 ================
   1228 */
   1229 void idMover::Event_RotateDownTo( int axis, float angle ) {
   1230 	idAngles ang;
   1231 
   1232 	if ( ( axis < 0 ) || ( axis > 2 ) ) {
   1233 		gameLocal.Error( "Invalid axis" );
   1234 	}
   1235 
   1236 	physicsObj.GetLocalAngles( ang );
   1237 
   1238 	dest_angles[ axis ] = angle;
   1239 	if ( dest_angles[ axis ] > ang[ axis ] ) {
   1240 		dest_angles[ axis ] -= 360;
   1241 	}
   1242 
   1243 	BeginRotation( idThread::CurrentThread(), true );
   1244 }
   1245 
   1246 /*
   1247 ================
   1248 idMover::Event_RotateUpTo
   1249 ================
   1250 */
   1251 void idMover::Event_RotateUpTo( int axis, float angle ) {
   1252 	idAngles ang;
   1253 
   1254 	if ( ( axis < 0 ) || ( axis > 2 ) ) {
   1255 		gameLocal.Error( "Invalid axis" );
   1256 	}
   1257 
   1258 	physicsObj.GetLocalAngles( ang );
   1259 
   1260 	dest_angles[ axis ] = angle;
   1261 	if ( dest_angles[ axis ] < ang[ axis ] ) {
   1262 		dest_angles[ axis ] += 360;
   1263 	}
   1264 
   1265 	BeginRotation( idThread::CurrentThread(), true );
   1266 }
   1267 
   1268 /*
   1269 ================
   1270 idMover::Event_RotateTo
   1271 ================
   1272 */
   1273 void idMover::Event_RotateTo( idAngles &angles ) {
   1274 	dest_angles = angles;
   1275 	BeginRotation( idThread::CurrentThread(), true );
   1276 }
   1277 
   1278 /*
   1279 ================
   1280 idMover::Event_Rotate
   1281 ================
   1282 */
   1283 void idMover::Event_Rotate( idAngles &angles ) {
   1284 	idAngles ang;
   1285 
   1286 	if ( rotate_thread ) {
   1287 		DoneRotating();
   1288 	}
   1289 
   1290 	physicsObj.GetLocalAngles( ang );
   1291 	dest_angles = ang + angles * ( move_time - ( acceltime + deceltime ) / 2 ) * 0.001f;
   1292 
   1293 	BeginRotation( idThread::CurrentThread(), false );
   1294 }
   1295 
   1296 /*
   1297 ================
   1298 idMover::Event_RotateOnce
   1299 ================
   1300 */
   1301 void idMover::Event_RotateOnce( idAngles &angles ) {
   1302 	idAngles ang;
   1303 
   1304 	if ( rotate_thread ) {
   1305 		DoneRotating();
   1306 	}
   1307 
   1308 	physicsObj.GetLocalAngles( ang );
   1309 	dest_angles = ang + angles;
   1310 
   1311 	BeginRotation( idThread::CurrentThread(), true );
   1312 }
   1313 
   1314 /*
   1315 ================
   1316 idMover::Event_Bob
   1317 ================
   1318 */
   1319 void idMover::Event_Bob( float speed, float phase, idVec3 &depth ) {
   1320 	idVec3 org;
   1321 
   1322 	physicsObj.GetLocalOrigin( org );
   1323 	physicsObj.SetLinearExtrapolation( extrapolation_t(EXTRAPOLATION_DECELSINE|EXTRAPOLATION_NOSTOP), speed * 1000 * phase, speed * 500, org, depth * 2.0f, vec3_origin );
   1324 }
   1325 
   1326 /*
   1327 ================
   1328 idMover::Event_Sway
   1329 ================
   1330 */
   1331 void idMover::Event_Sway( float speed, float phase, idAngles &depth ) {
   1332 	idAngles ang, angSpeed;
   1333 	float duration;
   1334 
   1335 	physicsObj.GetLocalAngles( ang );
   1336 	assert ( speed > 0.0f );
   1337 	duration = idMath::Sqrt( depth[0] * depth[0] + depth[1] * depth[1] + depth[2] * depth[2] ) / speed;
   1338 	angSpeed = depth / ( duration * idMath::SQRT_1OVER2 );
   1339 	physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_DECELSINE|EXTRAPOLATION_NOSTOP), duration * 1000.0f * phase, duration * 1000.0f, ang, angSpeed, ang_zero );
   1340 }
   1341 
   1342 /*
   1343 ================
   1344 idMover::Event_OpenPortal
   1345 
   1346 Sets the portal associtated with this mover to be open
   1347 ================
   1348 */
   1349 void idMover::Event_OpenPortal() {
   1350 	if ( areaPortal ) {
   1351 		SetPortalState( true );
   1352 	}
   1353 }
   1354 
   1355 /*
   1356 ================
   1357 idMover::Event_ClosePortal
   1358 
   1359 Sets the portal associtated with this mover to be closed
   1360 ================
   1361 */
   1362 void idMover::Event_ClosePortal() {
   1363 	if ( areaPortal ) {
   1364 		SetPortalState( false );
   1365 	}
   1366 }
   1367 
   1368 /*
   1369 ================
   1370 idMover::Event_SetAccelSound
   1371 ================
   1372 */
   1373 void idMover::Event_SetAccelSound( const char *sound ) {
   1374 //	refSound.SetSound( "accel", sound );
   1375 }
   1376 
   1377 /*
   1378 ================
   1379 idMover::Event_SetDecelSound
   1380 ================
   1381 */
   1382 void idMover::Event_SetDecelSound( const char *sound ) {
   1383 //	refSound.SetSound( "decel", sound );
   1384 }
   1385 
   1386 /*
   1387 ================
   1388 idMover::Event_SetMoveSound
   1389 ================
   1390 */
   1391 void idMover::Event_SetMoveSound( const char *sound ) {
   1392 //	refSound.SetSound( "move", sound );
   1393 }
   1394 
   1395 /*
   1396 ================
   1397 idMover::Event_EnableSplineAngles
   1398 ================
   1399 */
   1400 void idMover::Event_EnableSplineAngles() {
   1401 	useSplineAngles = true;
   1402 }
   1403 
   1404 /*
   1405 ================
   1406 idMover::Event_DisableSplineAngles
   1407 ================
   1408 */
   1409 void idMover::Event_DisableSplineAngles() {
   1410 	useSplineAngles = false;
   1411 }
   1412 
   1413 /*
   1414 ================
   1415 idMover::Event_RemoveInitialSplineAngles
   1416 ================
   1417 */
   1418 void idMover::Event_RemoveInitialSplineAngles() {
   1419 	idCurve_Spline<idVec3> *spline;
   1420 	idAngles ang;
   1421 
   1422 	spline = physicsObj.GetSpline();
   1423 	if ( !spline ) {
   1424 		return;
   1425 	}
   1426 	ang = spline->GetCurrentFirstDerivative( 0 ).ToAngles();
   1427 	physicsObj.SetAngularExtrapolation( EXTRAPOLATION_NONE, 0, 0, -ang, ang_zero, ang_zero );
   1428 }
   1429 
   1430 /*
   1431 ================
   1432 idMover::Event_StartSpline
   1433 ================
   1434 */
   1435 void idMover::Event_StartSpline( idEntity *splineEntity ) {
   1436 	idCurve_Spline<idVec3> *spline;
   1437 
   1438 	if ( !splineEntity ) {
   1439 		return;
   1440 	}
   1441 
   1442 	// Needed for savegames
   1443 	splineEnt = splineEntity;
   1444 
   1445 	spline = splineEntity->GetSpline();
   1446 	if ( !spline ) {
   1447 		return;
   1448 	}
   1449 
   1450 	lastCommand = MOVER_SPLINE;
   1451 	move_thread = 0;
   1452 
   1453 	if ( acceltime + deceltime > move_time ) {
   1454 		acceltime = move_time / 2;
   1455 		deceltime = move_time - acceltime;
   1456 	}
   1457 	move.stage			= FINISHED_STAGE;
   1458 	move.acceleration	= acceltime;
   1459 	move.movetime		= move_time;
   1460 	move.deceleration	= deceltime;
   1461 
   1462 	spline->MakeUniform( move_time );
   1463 	spline->ShiftTime( gameLocal.slow.time - spline->GetTime( 0 ) );
   1464 
   1465 	physicsObj.SetSpline( spline, move.acceleration, move.deceleration, useSplineAngles );
   1466 	physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, dest_position, vec3_origin, vec3_origin );
   1467 }
   1468 
   1469 /*
   1470 ================
   1471 idMover::Event_StopSpline
   1472 ================
   1473 */
   1474 void idMover::Event_StopSpline() {
   1475 	physicsObj.SetSpline( NULL, 0, 0, useSplineAngles );
   1476 	splineEnt = NULL;
   1477 }
   1478 
   1479 /*
   1480 ================
   1481 idMover::Event_Activate
   1482 ================
   1483 */
   1484 void idMover::Event_Activate( idEntity *activator ) {
   1485 	Show();
   1486 	Event_StartSpline( this );
   1487 }
   1488 
   1489 /*
   1490 ================
   1491 idMover::Event_IsMoving
   1492 ================
   1493 */
   1494 void idMover::Event_IsMoving() {
   1495 	if ( physicsObj.GetLinearExtrapolationType() == EXTRAPOLATION_NONE ) {
   1496 		idThread::ReturnInt( false );
   1497 	} else {
   1498 		idThread::ReturnInt( true );
   1499 	}
   1500 }
   1501 
   1502 /*
   1503 ================
   1504 idMover::Event_IsRotating
   1505 ================
   1506 */
   1507 void idMover::Event_IsRotating() {
   1508 	if ( physicsObj.GetAngularExtrapolationType() == EXTRAPOLATION_NONE ) {
   1509 		idThread::ReturnInt( false );
   1510 	} else {
   1511 		idThread::ReturnInt( true );
   1512 	}
   1513 }
   1514 
   1515 /*
   1516 ================
   1517 idMover::WriteToSnapshot
   1518 ================
   1519 */
   1520 void idMover::WriteToSnapshot( idBitMsg &msg ) const {
   1521 	physicsObj.WriteToSnapshot( msg );
   1522 	msg.WriteBits( move.stage, 3 );
   1523 	msg.WriteBits( rot.stage, 3 );
   1524 	WriteBindToSnapshot( msg );
   1525 	WriteGUIToSnapshot( msg );
   1526 }
   1527 
   1528 /*
   1529 ================
   1530 idMover::ReadFromSnapshot
   1531 ================
   1532 */
   1533 void idMover::ReadFromSnapshot( const idBitMsg &msg ) {
   1534 	moveStage_t oldMoveStage = move.stage;
   1535 	moveStage_t oldRotStage = rot.stage;
   1536 
   1537 	physicsObj.ReadFromSnapshot( msg );
   1538 	move.stage = (moveStage_t) msg.ReadBits( 3 );
   1539 	rot.stage = (moveStage_t) msg.ReadBits( 3 );
   1540 	ReadBindFromSnapshot( msg );
   1541 	ReadGUIFromSnapshot( msg );
   1542 
   1543 	if ( msg.HasChanged() ) {
   1544 		if ( move.stage != oldMoveStage ) {
   1545 			UpdateMoveSound( oldMoveStage );
   1546 		}
   1547 		if ( rot.stage != oldRotStage ) {
   1548 			UpdateRotationSound( oldRotStage );
   1549 		}
   1550 		UpdateVisuals();
   1551 	}
   1552 }
   1553 
   1554 /*
   1555 ================
   1556 idMover::SetPortalState
   1557 ================
   1558 */
   1559 void idMover::SetPortalState( bool open ) {
   1560 	assert( areaPortal );
   1561 	gameLocal.SetPortalState( areaPortal, open ? PS_BLOCK_NONE : PS_BLOCK_ALL );
   1562 }
   1563 
   1564 /*
   1565 ===============================================================================
   1566 
   1567 	idSplinePath, holds a spline path to be used by an idMover
   1568 
   1569 ===============================================================================
   1570 */
   1571 
   1572 CLASS_DECLARATION( idEntity, idSplinePath )
   1573 END_CLASS
   1574 
   1575 /*
   1576 ================
   1577 idSplinePath::idSplinePath
   1578 ================
   1579 */
   1580 idSplinePath::idSplinePath() {
   1581 }
   1582 
   1583 /*
   1584 ================
   1585 idSplinePath::Spawn
   1586 ================
   1587 */
   1588 void idSplinePath::Spawn() {
   1589 }
   1590 
   1591 
   1592 /*
   1593 ===============================================================================
   1594 
   1595 idElevator
   1596 
   1597 ===============================================================================
   1598 */
   1599 const idEventDef EV_PostArrival( "postArrival", NULL );
   1600 const idEventDef EV_GotoFloor( "gotoFloor", "d" );
   1601 const idEventDef EV_SetGuiStates( "setGuiStates" );
   1602 
   1603 CLASS_DECLARATION( idMover, idElevator )
   1604 	EVENT( EV_Activate,				idElevator::Event_Activate )
   1605 	EVENT( EV_TeamBlocked,			idElevator::Event_TeamBlocked )
   1606 	EVENT( EV_PartBlocked,			idElevator::Event_PartBlocked )
   1607 	EVENT( EV_PostArrival,			idElevator::Event_PostFloorArrival )
   1608 	EVENT( EV_GotoFloor,			idElevator::Event_GotoFloor )
   1609 	EVENT( EV_Touch,				idElevator::Event_Touch )
   1610 	EVENT( EV_SetGuiStates,			idElevator::Event_SetGuiStates )
   1611 END_CLASS
   1612 
   1613 /*
   1614 ================
   1615 idElevator::idElevator
   1616 ================
   1617 */
   1618 idElevator::idElevator() {
   1619 	state = INIT;
   1620 	floorInfo.Clear();
   1621 	currentFloor = 0;
   1622 	pendingFloor = 0;
   1623 	lastFloor = 0;
   1624 	controlsDisabled = false;
   1625 	lastTouchTime = 0;
   1626 	returnFloor = 0;
   1627 	returnTime = 0;
   1628 }
   1629 
   1630 /*
   1631 ================
   1632 idElevator::Save
   1633 ================
   1634 */
   1635 void idElevator::Save( idSaveGame *savefile ) const {
   1636 	int i;
   1637 
   1638 	savefile->WriteInt( (int)state );
   1639 
   1640 	savefile->WriteInt( floorInfo.Num() );
   1641 	for ( i = 0; i < floorInfo.Num(); i++ ) {
   1642 		savefile->WriteVec3( floorInfo[ i ].pos );
   1643 		savefile->WriteString( floorInfo[ i ].door );
   1644 		savefile->WriteInt( floorInfo[ i ].floor );
   1645 	}
   1646 
   1647 	savefile->WriteInt( currentFloor );
   1648 	savefile->WriteInt( pendingFloor );
   1649 	savefile->WriteInt( lastFloor );
   1650 	savefile->WriteBool( controlsDisabled );
   1651 	savefile->WriteFloat( returnTime );
   1652 	savefile->WriteInt( returnFloor );
   1653 	savefile->WriteInt( lastTouchTime );
   1654 }
   1655 
   1656 /*
   1657 ================
   1658 idElevator::Restore
   1659 ================
   1660 */
   1661 void idElevator::Restore( idRestoreGame *savefile ) {
   1662 	int i, num;
   1663 
   1664 	savefile->ReadInt( (int &)state );
   1665 
   1666 	savefile->ReadInt( num );
   1667 	for ( i = 0; i < num; i++ ) {
   1668 		floorInfo_s floor;
   1669 
   1670 		savefile->ReadVec3( floor.pos );
   1671 		savefile->ReadString( floor.door );
   1672 		savefile->ReadInt( floor.floor );
   1673 
   1674 		floorInfo.Append( floor );
   1675 	}
   1676 
   1677 	savefile->ReadInt( currentFloor );
   1678 	savefile->ReadInt( pendingFloor );
   1679 	savefile->ReadInt( lastFloor );
   1680 	savefile->ReadBool( controlsDisabled );
   1681 	savefile->ReadFloat( returnTime );
   1682 	savefile->ReadInt( returnFloor );
   1683 	savefile->ReadInt( lastTouchTime );
   1684 }
   1685 
   1686 /*
   1687 ================
   1688 idElevator::Spawn
   1689 ================
   1690 */
   1691 void idElevator::Spawn() {
   1692 	idStr str;
   1693 	int len1;
   1694 
   1695 	lastFloor = 0;
   1696 	currentFloor = 0;
   1697 	pendingFloor = spawnArgs.GetInt( "floor", "1" );
   1698 	SetGuiStates( ( pendingFloor == 1 ) ? guiBinaryMoverStates[0] : guiBinaryMoverStates[1]);
   1699 
   1700 	returnTime = spawnArgs.GetFloat( "returnTime" );
   1701 	returnFloor = spawnArgs.GetInt( "returnFloor" );
   1702 
   1703 	len1 = strlen( "floorPos_" );
   1704 	const idKeyValue *kv = spawnArgs.MatchPrefix( "floorPos_", NULL );
   1705 	while( kv ) {
   1706 		str = kv->GetKey().Right( kv->GetKey().Length() - len1 );
   1707 		floorInfo_s fi;
   1708 		fi.floor = atoi( str );
   1709 		fi.door = spawnArgs.GetString( va( "floorDoor_%i", fi.floor ) );
   1710 		fi.pos = spawnArgs.GetVector( kv->GetKey() );
   1711 		floorInfo.Append( fi );
   1712 		kv = spawnArgs.MatchPrefix( "floorPos_", kv );
   1713 	}
   1714 	lastTouchTime = 0;
   1715 	state = INIT;
   1716 	BecomeActive( TH_THINK | TH_PHYSICS );
   1717 	PostEventMS( &EV_Mover_InitGuiTargets, 0 );
   1718 	controlsDisabled = false;
   1719 }
   1720 
   1721 /*
   1722 ==============
   1723 idElevator::Event_Touch
   1724 ===============
   1725 */
   1726 void idElevator::Event_Touch( idEntity *other, trace_t *trace ) {
   1727 	
   1728 	if ( common->IsClient() ) {
   1729 		return;
   1730 	}
   1731 
   1732 	if ( gameLocal.slow.time < lastTouchTime + 2000 ) {
   1733 		return;
   1734 	}
   1735 
   1736 	if ( !other->IsType( idPlayer::Type ) ) {
   1737 		return;
   1738 	}
   1739 
   1740 	lastTouchTime = gameLocal.slow.time;
   1741 
   1742 	if ( thinkFlags & TH_PHYSICS ) {
   1743 		return;
   1744 	}
   1745 
   1746 	int triggerFloor = spawnArgs.GetInt( "triggerFloor" );
   1747 	if ( spawnArgs.GetBool( "trigger" ) && triggerFloor != currentFloor ) {
   1748 		PostEventSec( &EV_GotoFloor, 0.25f, triggerFloor );
   1749 	}
   1750 }
   1751 
   1752 /*
   1753 ================
   1754 idElevator::Think
   1755 ================
   1756 */
   1757 void idElevator::Think() {
   1758 	idVec3 masterOrigin;
   1759 	idMat3 masterAxis;
   1760 	idDoor *doorent = GetDoor( spawnArgs.GetString( "innerdoor" ) );
   1761 	if ( state == INIT ) {
   1762 		state = IDLE;
   1763 		if ( doorent ) {
   1764 			doorent->BindTeam( this );
   1765 			doorent->spawnArgs.Set( "snd_open", "" );
   1766 			doorent->spawnArgs.Set( "snd_close", "" );
   1767 			doorent->spawnArgs.Set( "snd_opened", "" );
   1768 		}
   1769 		for ( int i = 0; i < floorInfo.Num(); i++ ) {
   1770 			idDoor *door = GetDoor( floorInfo[i].door );
   1771 			if ( door ) {
   1772 				door->SetCompanion( doorent );
   1773 			}
   1774 		}
   1775 
   1776 		Event_GotoFloor( pendingFloor );
   1777 		DisableAllDoors();
   1778 		SetGuiStates( ( pendingFloor == 1 ) ? guiBinaryMoverStates[0] : guiBinaryMoverStates[1] );
   1779 	} else if ( state == WAITING_ON_DOORS ) {
   1780 		state = IDLE;
   1781 		if ( doorent != NULL && doorent->IsOpen() ) {
   1782 			state = WAITING_ON_DOORS;
   1783 		} else {
   1784 			for ( int i = 0; i < floorInfo.Num(); i++ ) {
   1785 				idDoor *door = GetDoor( floorInfo[i].door );
   1786 				if ( door != NULL && door->IsOpen() ) {
   1787 					state = WAITING_ON_DOORS; 
   1788 					break;
   1789 				}
   1790 			}
   1791 		}
   1792 		if ( state == IDLE ) {
   1793 			lastFloor = currentFloor;
   1794 			currentFloor = pendingFloor;
   1795 			floorInfo_s *fi = GetFloorInfo( currentFloor );
   1796 			if ( fi ) {
   1797 				MoveToPos( fi->pos );
   1798 			}
   1799 		}
   1800 	} 
   1801 	RunPhysics();
   1802 	Present();
   1803 }
   1804 
   1805 /*
   1806 ================
   1807 idElevator::Event_Activate
   1808 ================
   1809 */
   1810 void idElevator::Event_Activate( idEntity *activator ) {
   1811 	int triggerFloor = spawnArgs.GetInt( "triggerFloor" );
   1812 	if ( spawnArgs.GetBool( "trigger" ) && triggerFloor != currentFloor ) {
   1813 		Event_GotoFloor( triggerFloor );
   1814 	}
   1815 }
   1816 
   1817 /*
   1818 ================
   1819 idElevator::Event_TeamBlocked
   1820 ================
   1821 */
   1822 void idElevator::Event_TeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ) {
   1823 	if ( blockedEntity == this ) {
   1824 		Event_GotoFloor( lastFloor );
   1825 	} else if ( blockedEntity && blockedEntity->IsType( idDoor::Type ) ) {
   1826 		// open the inner doors if one is blocked
   1827 		idDoor *blocked = static_cast<idDoor *>( blockedEntity );
   1828 		idDoor *door = GetDoor( spawnArgs.GetString( "innerdoor" ) );
   1829 		if ( door != NULL && blocked->GetMoveMaster() == door->GetMoveMaster() ) {
   1830 			door->SetBlocked(true);
   1831 			OpenInnerDoor();
   1832 			OpenFloorDoor( currentFloor );
   1833 		}
   1834 	}
   1835 }
   1836 
   1837 /*
   1838 ===============
   1839 idElevator::HandleSingleGuiCommand
   1840 ===============
   1841 */
   1842 bool idElevator::HandleSingleGuiCommand( idEntity *entityGui, idLexer *src ) {
   1843 	idToken token;
   1844 
   1845 	if ( controlsDisabled ) {
   1846 		return false;
   1847 	}
   1848 
   1849 	if ( !src->ReadToken( &token ) ) {
   1850 		return false;
   1851 	}
   1852 
   1853 	if ( token == ";" ) {
   1854 		return false;
   1855 	}
   1856 
   1857 	if ( token.Icmp( "changefloor" ) == 0 ) {
   1858 		if ( src->ReadToken( &token ) ) {
   1859 			int newFloor = atoi( token );
   1860 			if ( newFloor == currentFloor ) {
   1861 				// open currentFloor and interior doors
   1862 				OpenInnerDoor();
   1863 				OpenFloorDoor( currentFloor );
   1864 			} else {
   1865 				ProcessEvent( &EV_GotoFloor, newFloor );
   1866 			}
   1867 			return true;
   1868 		}
   1869 	}
   1870 
   1871 	src->UnreadToken( &token );
   1872 	return false;
   1873 }
   1874 
   1875 /*
   1876 ================
   1877 idElevator::OpenFloorDoor
   1878 ================
   1879 */
   1880 void idElevator::OpenFloorDoor( int floor ) {
   1881 	floorInfo_s *fi = GetFloorInfo( floor );
   1882 	if ( fi ) {
   1883 		idDoor *door = GetDoor( fi->door );
   1884 		if ( door ) {
   1885 			door->Open();
   1886 		}
   1887 	}
   1888 }
   1889 
   1890 /*
   1891 ================
   1892 idElevator::OpenInnerDoor
   1893 ================
   1894 */
   1895 void idElevator::OpenInnerDoor() {
   1896 	idDoor *door = GetDoor( spawnArgs.GetString( "innerdoor" ) );
   1897 	if ( door ) {
   1898 		door->Open();
   1899 	}
   1900 }
   1901 
   1902 /*
   1903 ================
   1904 idElevator::GetFloorInfo
   1905 ================
   1906 */
   1907 floorInfo_s *idElevator::GetFloorInfo( int floor ) {
   1908 	for ( int i = 0; i < floorInfo.Num(); i++ ) {
   1909 		if ( floorInfo[i].floor == floor ) {
   1910 			return &floorInfo[i];
   1911 		}
   1912 	}
   1913 	return NULL;
   1914 }
   1915 
   1916 /*
   1917 ================
   1918 idElevator::Event_GotoFloor
   1919 ================
   1920 */
   1921 void idElevator::Event_GotoFloor( int floor ) {
   1922 	floorInfo_s *fi = GetFloorInfo( floor );
   1923 	if ( fi ) {
   1924 		DisableAllDoors();
   1925 		CloseAllDoors();
   1926 		state = WAITING_ON_DOORS;
   1927 		pendingFloor = floor;
   1928 	}
   1929 	// If the inner door is blocked, repost this event
   1930 	idDoor *door = GetDoor( spawnArgs.GetString( "innerdoor" ) );
   1931 	if ( door ) {
   1932 		if ( door->IsBlocked() || door->IsOpen() ) {
   1933 			PostEventSec( &EV_GotoFloor, 0.5f, floor );
   1934 		}
   1935 	}
   1936 }
   1937 
   1938 /*
   1939 ================
   1940 idElevator::BeginMove
   1941 ================
   1942 */
   1943 void idElevator::BeginMove( idThread *thread ) {
   1944 	controlsDisabled = true;
   1945 	CloseAllDoors();
   1946 	DisableAllDoors();
   1947 	const idKeyValue *kv = spawnArgs.MatchPrefix( "statusGui" );
   1948 	while( kv ) {
   1949 		idEntity *ent = gameLocal.FindEntity( kv->GetValue() );
   1950 		if ( ent ) {
   1951 			for ( int j = 0; j < MAX_RENDERENTITY_GUI; j++ ) {
   1952 				if ( ent->GetRenderEntity() && ent->GetRenderEntity()->gui[ j ] ) {
   1953 					ent->GetRenderEntity()->gui[ j ]->SetStateString( "floor", "" );
   1954 					ent->GetRenderEntity()->gui[ j ]->StateChanged( gameLocal.slow.time, true );
   1955 				}
   1956 			}
   1957 			ent->UpdateVisuals();
   1958 		}
   1959 		kv = spawnArgs.MatchPrefix( "statusGui", kv );
   1960 	}
   1961 	SetGuiStates( ( pendingFloor == 1 ) ? guiBinaryMoverStates[3] : guiBinaryMoverStates[2] );
   1962 	idMover::BeginMove( thread );
   1963 }
   1964 
   1965 /*
   1966 ================
   1967 idElevator::GetDoor
   1968 ================
   1969 */
   1970 idDoor *idElevator::GetDoor( const char *name ) {
   1971 	idEntity	*ent;
   1972 	idEntity	*master;
   1973 	idDoor		*doorEnt;
   1974 
   1975 	doorEnt = NULL;
   1976 	if ( name && *name ) {
   1977 		ent = gameLocal.FindEntity( name );
   1978 		if ( ent && ent->IsType( idDoor::Type ) ) {
   1979 			doorEnt = static_cast<idDoor*>( ent );
   1980 			master = doorEnt->GetMoveMaster();
   1981 			if ( master != doorEnt ) {
   1982 				if ( master->IsType( idDoor::Type ) ) {
   1983 					doorEnt = static_cast<idDoor*>( master );
   1984 				} else {
   1985 					doorEnt = NULL;
   1986 				}
   1987 			}
   1988 		}
   1989 	}
   1990 
   1991 	return doorEnt;
   1992 }
   1993 
   1994 /*
   1995 ================
   1996 idElevator::Event_PostFloorArrival
   1997 ================
   1998 */
   1999 void idElevator::Event_PostFloorArrival() {
   2000 	OpenFloorDoor( currentFloor );
   2001 	OpenInnerDoor();
   2002 	SetGuiStates( ( currentFloor == 1 ) ? guiBinaryMoverStates[0] : guiBinaryMoverStates[1] );
   2003 	controlsDisabled = false;
   2004 	if ( returnTime > 0.0f && returnFloor != currentFloor ) {
   2005 		PostEventSec( &EV_GotoFloor, returnTime, returnFloor );
   2006 	}
   2007 }
   2008 
   2009 void idElevator::Event_SetGuiStates() {
   2010 	SetGuiStates( ( currentFloor == 1 ) ? guiBinaryMoverStates[0] : guiBinaryMoverStates[1] );
   2011 }
   2012 
   2013 /*
   2014 ================
   2015 idElevator::DoneMoving
   2016 ================
   2017 */
   2018 void idElevator::DoneMoving() {
   2019 	idMover::DoneMoving();
   2020 	EnableProperDoors();
   2021 	const idKeyValue *kv = spawnArgs.MatchPrefix( "statusGui" );
   2022 	while( kv ) {
   2023 		idEntity *ent = gameLocal.FindEntity( kv->GetValue() );
   2024 		if ( ent ) {
   2025 			for ( int j = 0; j < MAX_RENDERENTITY_GUI; j++ ) {
   2026 				if ( ent->GetRenderEntity() && ent->GetRenderEntity()->gui[ j ] ) {
   2027 					ent->GetRenderEntity()->gui[ j ]->SetStateString( "floor", va( "%i", currentFloor ) );
   2028 					ent->GetRenderEntity()->gui[ j ]->StateChanged( gameLocal.slow.time, true );
   2029 				}
   2030 			}
   2031 			ent->UpdateVisuals();
   2032 		}
   2033 		kv = spawnArgs.MatchPrefix( "statusGui", kv );
   2034 	}
   2035 	if ( spawnArgs.GetInt( "pauseOnFloor", "-1" ) == currentFloor ) {
   2036 		PostEventSec( &EV_PostArrival, spawnArgs.GetFloat( "pauseTime" ) );
   2037 	} else {
   2038 		Event_PostFloorArrival();
   2039 	}
   2040 }
   2041 
   2042 /*
   2043 ================
   2044 idElevator::CloseAllDoors
   2045 ================
   2046 */
   2047 void idElevator::CloseAllDoors() {
   2048 	idDoor *door = GetDoor( spawnArgs.GetString( "innerdoor" ) );
   2049 	if ( door ) {
   2050 		door->Close();
   2051 	}
   2052 	for ( int i = 0; i < floorInfo.Num(); i++ ) {
   2053 		door = GetDoor( floorInfo[i].door );
   2054 		if ( door ) {
   2055 			door->Close();
   2056 		}
   2057 	}
   2058 }
   2059 
   2060 /*
   2061 ================
   2062 idElevator::DisableAllDoors
   2063 ================
   2064 */
   2065 void idElevator::DisableAllDoors() {
   2066 	idDoor *door = GetDoor( spawnArgs.GetString( "innerdoor" ) );
   2067 	if ( door ) {
   2068 		door->Enable( false );
   2069 	}
   2070 	for ( int i = 0; i < floorInfo.Num(); i++ ) {
   2071 		door = GetDoor( floorInfo[i].door );
   2072 		if ( door ) {
   2073 			door->Enable( false );
   2074 		}
   2075 	}
   2076 }
   2077 
   2078 /*
   2079 ================
   2080 idElevator::EnableProperDoors
   2081 ================
   2082 */
   2083 void idElevator::EnableProperDoors() {
   2084 	idDoor *door = GetDoor( spawnArgs.GetString( "innerdoor" ) );
   2085 	if ( door ) {
   2086 		door->Enable( true );
   2087 	}
   2088 	for ( int i = 0; i < floorInfo.Num(); i++ ) {
   2089 		if ( floorInfo[i].floor == currentFloor ) {
   2090 			door = GetDoor( floorInfo[i].door );
   2091 			if ( door ) {
   2092 				door->Enable( true );
   2093 				break;
   2094 			}
   2095 		}
   2096 	}
   2097 }
   2098 
   2099 
   2100 /*
   2101 ===============================================================================
   2102 
   2103 idMover_Binary
   2104 
   2105 Doors, plats, and buttons are all binary (two position) movers
   2106 Pos1 is "at rest", pos2 is "activated"
   2107 
   2108 ===============================================================================
   2109 */
   2110 
   2111 const idEventDef EV_Mover_ReturnToPos1( "<returntopos1>", NULL );
   2112 const idEventDef EV_Mover_MatchTeam( "<matchteam>", "dd" );
   2113 const idEventDef EV_Mover_Enable( "enable", NULL );
   2114 const idEventDef EV_Mover_Disable( "disable", NULL );
   2115 
   2116 CLASS_DECLARATION( idEntity, idMover_Binary )
   2117 	EVENT( EV_FindGuiTargets,			idMover_Binary::Event_FindGuiTargets )
   2118 	EVENT( EV_Thread_SetCallback,		idMover_Binary::Event_SetCallback )
   2119 	EVENT( EV_Mover_ReturnToPos1,		idMover_Binary::Event_ReturnToPos1 )
   2120 	EVENT( EV_Activate,					idMover_Binary::Event_Use_BinaryMover )
   2121 	EVENT( EV_ReachedPos,				idMover_Binary::Event_Reached_BinaryMover )
   2122 	EVENT( EV_Mover_MatchTeam,			idMover_Binary::Event_MatchActivateTeam )
   2123 	EVENT( EV_Mover_Enable,				idMover_Binary::Event_Enable )
   2124 	EVENT( EV_Mover_Disable,			idMover_Binary::Event_Disable )
   2125 	EVENT( EV_Mover_OpenPortal,			idMover_Binary::Event_OpenPortal )
   2126 	EVENT( EV_Mover_ClosePortal,		idMover_Binary::Event_ClosePortal )
   2127 	EVENT( EV_Mover_InitGuiTargets,		idMover_Binary::Event_InitGuiTargets )
   2128 END_CLASS
   2129 
   2130 /*
   2131 ================
   2132 idMover_Binary::idMover_Binary()
   2133 ================
   2134 */
   2135 idMover_Binary::idMover_Binary() {
   2136 	pos1.Zero();
   2137 	pos2.Zero();
   2138 	moverState = MOVER_POS1;
   2139 	moveMaster = NULL;
   2140 	activateChain = NULL;
   2141 	soundPos1 = 0;
   2142 	sound1to2 = 0;
   2143 	sound2to1 = 0;
   2144 	soundPos2 = 0;
   2145 	soundLoop = 0;
   2146 	wait = 0.0f;
   2147 	damage = 0.0f;
   2148 	duration = 0;
   2149 	accelTime = 0;
   2150 	decelTime = 0;
   2151 	activatedBy = this;
   2152 	stateStartTime = 0;
   2153 	team.Clear();
   2154 	enabled = false;
   2155 	move_thread = 0;
   2156 	updateStatus = 0;
   2157 	areaPortal = 0;
   2158 	blocked = false;
   2159 	playerOnly = false;
   2160 	fl.networkSync = true;
   2161 }
   2162 
   2163 /*
   2164 ================
   2165 idMover_Binary::~idMover_Binary
   2166 ================
   2167 */
   2168 idMover_Binary::~idMover_Binary() {
   2169 	idMover_Binary *mover;
   2170 
   2171 	// if this is the mover master
   2172 	if ( this == moveMaster ) {
   2173 		// make the next mover in the chain the move master
   2174 		for ( mover = moveMaster; mover; mover = mover->activateChain ) {
   2175 			mover->moveMaster = this->activateChain;
   2176 		}
   2177 	}
   2178 	else {
   2179 		// remove mover from the activate chain
   2180 		for ( mover = moveMaster; mover; mover = mover->activateChain ) {
   2181 			if ( mover->activateChain == this ) {
   2182 				mover->activateChain = this->activateChain;
   2183 				break;
   2184 			}
   2185 		}
   2186 	}
   2187 }
   2188 
   2189 /*
   2190 ================
   2191 idMover_Binary::Save
   2192 ================
   2193 */
   2194 void idMover_Binary::Save( idSaveGame *savefile ) const {
   2195 	int i;
   2196 
   2197 	savefile->WriteVec3( pos1 );
   2198 	savefile->WriteVec3( pos2 );
   2199 	savefile->WriteInt( (moverState_t)moverState );
   2200 
   2201 	savefile->WriteObject( moveMaster );
   2202 	savefile->WriteObject( activateChain );
   2203 
   2204 	savefile->WriteInt( soundPos1 );
   2205 	savefile->WriteInt( sound1to2 );
   2206 	savefile->WriteInt( sound2to1 );
   2207 	savefile->WriteInt( soundPos2 );
   2208 	savefile->WriteInt( soundLoop );
   2209 
   2210 	savefile->WriteFloat( wait );
   2211 	savefile->WriteFloat( damage );
   2212 
   2213 	savefile->WriteInt( duration );
   2214 	savefile->WriteInt( accelTime );
   2215 	savefile->WriteInt( decelTime );
   2216 
   2217 	activatedBy.Save( savefile );
   2218 
   2219 	savefile->WriteInt( stateStartTime );
   2220 	savefile->WriteString( team );
   2221 	savefile->WriteBool( enabled );
   2222 
   2223 	savefile->WriteInt( move_thread );
   2224 	savefile->WriteInt( updateStatus );
   2225 
   2226 	savefile->WriteInt( buddies.Num() );
   2227 	for ( i = 0; i < buddies.Num(); i++ ) {
   2228 		savefile->WriteString( buddies[ i ] );
   2229 	}
   2230 
   2231 	savefile->WriteStaticObject( physicsObj );
   2232 
   2233 	savefile->WriteInt( areaPortal );
   2234 	if ( areaPortal ) {
   2235 		savefile->WriteInt( gameRenderWorld->GetPortalState( areaPortal ) );
   2236 	}
   2237 	savefile->WriteBool( blocked );
   2238 	savefile->WriteBool( playerOnly );
   2239 
   2240 	savefile->WriteInt( guiTargets.Num() );
   2241 	for( i = 0; i < guiTargets.Num(); i++ ) {
   2242 		guiTargets[ i ].Save( savefile );
   2243 	}
   2244 }
   2245 
   2246 /*
   2247 ================
   2248 idMover_Binary::Restore
   2249 ================
   2250 */
   2251 void idMover_Binary::Restore( idRestoreGame *savefile ) {
   2252 	int		i, num, portalState;
   2253 	idStr	temp;
   2254 
   2255 	savefile->ReadVec3( pos1 );
   2256 	savefile->ReadVec3( pos2 );
   2257 	savefile->ReadInt( (int &)moverState );
   2258 
   2259 	savefile->ReadObject( reinterpret_cast<idClass *&>( moveMaster ) );
   2260 	savefile->ReadObject( reinterpret_cast<idClass *&>( activateChain ) );
   2261 
   2262 	savefile->ReadInt( soundPos1 );
   2263 	savefile->ReadInt( sound1to2 );
   2264 	savefile->ReadInt( sound2to1 );
   2265 	savefile->ReadInt( soundPos2 );
   2266 	savefile->ReadInt( soundLoop );
   2267 
   2268 	savefile->ReadFloat( wait );
   2269 	savefile->ReadFloat( damage );
   2270 
   2271 	savefile->ReadInt( duration );
   2272 	savefile->ReadInt( accelTime );
   2273 	savefile->ReadInt( decelTime );
   2274 
   2275 	activatedBy.Restore( savefile );
   2276 
   2277 	savefile->ReadInt( stateStartTime );
   2278 
   2279 	savefile->ReadString( team );
   2280 	savefile->ReadBool( enabled );
   2281 
   2282 	savefile->ReadInt( move_thread );
   2283 	savefile->ReadInt( updateStatus );
   2284 
   2285 	savefile->ReadInt( num );
   2286 	for ( i = 0; i < num; i++ ) {
   2287 		savefile->ReadString( temp );
   2288 		buddies.Append( temp );
   2289 	}
   2290 
   2291 	savefile->ReadStaticObject( physicsObj );
   2292 	RestorePhysics( &physicsObj );
   2293 
   2294 	savefile->ReadInt( areaPortal );
   2295 	if ( areaPortal ) {
   2296 		savefile->ReadInt( portalState );
   2297 		gameLocal.SetPortalState( areaPortal, portalState );
   2298 	}
   2299 	savefile->ReadBool( blocked );
   2300 	savefile->ReadBool( playerOnly );
   2301 
   2302 	guiTargets.Clear();
   2303 	savefile->ReadInt( num );
   2304 	guiTargets.SetNum( num );
   2305 	for( i = 0; i < num; i++ ) {
   2306 		guiTargets[ i ].Restore( savefile );
   2307 	}
   2308 }
   2309 
   2310 /*
   2311 ================
   2312 idMover_Binary::Spawn
   2313 
   2314 Base class for all movers.
   2315 
   2316 "wait"		wait before returning (3 default, -1 = never return)
   2317 "speed"		movement speed
   2318 ================
   2319 */
   2320 void idMover_Binary::Spawn() {
   2321 	idEntity	*ent;
   2322 	const char	*temp;
   2323 
   2324 	move_thread		= 0;
   2325 	enabled			= true;
   2326 	areaPortal		= 0;
   2327 
   2328 	activateChain = NULL;
   2329 
   2330 	spawnArgs.GetFloat( "wait", "0", wait );
   2331 
   2332 	spawnArgs.GetInt( "updateStatus", "0", updateStatus );
   2333 
   2334 	const idKeyValue *kv = spawnArgs.MatchPrefix( "buddy", NULL );
   2335 	while( kv ) {
   2336 		buddies.Append( kv->GetValue() );
   2337 		kv = spawnArgs.MatchPrefix( "buddy", kv );
   2338 	}
   2339 
   2340 	spawnArgs.GetString( "team", "", &temp );
   2341 	team = temp;
   2342 
   2343 	if ( !team.Length() ) {
   2344 		ent = this;
   2345 	} else {
   2346 		// find the first entity spawned on this team (which could be us)
   2347 		for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) {
   2348 			if ( ent->IsType( idMover_Binary::Type ) && !idStr::Icmp( static_cast<idMover_Binary *>(ent)->team.c_str(), temp ) ) {
   2349 				break;
   2350 			}
   2351 		}
   2352 		if ( !ent ) {
   2353 			ent = this;
   2354 		}
   2355 	}
   2356 	moveMaster = static_cast<idMover_Binary *>(ent);
   2357 
   2358 	// create a physics team for the binary mover parts
   2359 	if ( ent != this ) {
   2360 		JoinTeam( ent );
   2361 	}
   2362 
   2363 	physicsObj.SetSelf( this );
   2364 	physicsObj.SetClipModel( new (TAG_PHYSICS_CLIP_MOVER) idClipModel( GetPhysics()->GetClipModel() ), 1.0f );
   2365 	physicsObj.SetOrigin( GetPhysics()->GetOrigin() );
   2366 	physicsObj.SetAxis( GetPhysics()->GetAxis() );
   2367 	physicsObj.SetClipMask( MASK_SOLID );
   2368 	if ( !spawnArgs.GetBool( "solid", "1" ) ) {
   2369 		physicsObj.SetContents( 0 );
   2370 	}
   2371 	if ( !spawnArgs.GetBool( "nopush" ) ) {
   2372 		physicsObj.SetPusher( 0 );
   2373 	}
   2374 	physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, GetPhysics()->GetOrigin(), vec3_origin, vec3_origin );
   2375 	physicsObj.SetAngularExtrapolation( EXTRAPOLATION_NONE, 0, 0, GetPhysics()->GetAxis().ToAngles(), ang_zero, ang_zero );
   2376 	SetPhysics( &physicsObj );
   2377 
   2378 	if ( moveMaster != this ) {
   2379 		JoinActivateTeam( moveMaster );
   2380 	}
   2381 
   2382 	idBounds soundOrigin;
   2383 	idMover_Binary *slave;
   2384 
   2385 	soundOrigin.Clear();
   2386 	for ( slave = moveMaster; slave != NULL; slave = slave->activateChain ) {
   2387 		soundOrigin += slave->GetPhysics()->GetAbsBounds();
   2388 	}
   2389 	moveMaster->refSound.origin = soundOrigin.GetCenter();
   2390 
   2391 	if ( spawnArgs.MatchPrefix( "guiTarget" ) ) {
   2392 		if ( gameLocal.GameState() == GAMESTATE_STARTUP ) {
   2393 			PostEventMS( &EV_FindGuiTargets, 0 );
   2394 		} else {
   2395 			// not during spawn, so it's ok to get the targets
   2396 			FindGuiTargets();
   2397 		}
   2398 	}
   2399 }
   2400 
   2401 /*
   2402 ===============
   2403 idMover_Binary::GetMovedir
   2404 
   2405 The editor only specifies a single value for angles (yaw),
   2406 but we have special constants to generate an up or down direction.
   2407 Angles will be cleared, because it is being used to represent a direction
   2408 instead of an orientation.
   2409 ===============
   2410 */
   2411 void idMover_Binary::GetMovedir( float angle, idVec3 &movedir ) {
   2412 	if ( angle == -1 ) {
   2413 		movedir.Set( 0, 0, 1 );
   2414 	} else if ( angle == -2 ) {
   2415 		movedir.Set( 0, 0, -1 );
   2416 	} else {
   2417 		movedir = idAngles( 0, angle, 0 ).ToForward();
   2418 	}
   2419 }
   2420 
   2421 /*
   2422 ================
   2423 idMover_Binary::Event_SetCallback
   2424 ================
   2425 */
   2426 void idMover_Binary::Event_SetCallback() {
   2427 	if ( ( moverState == MOVER_1TO2 ) || ( moverState == MOVER_2TO1 ) ) {
   2428 		move_thread = idThread::CurrentThreadNum();
   2429 		idThread::ReturnInt( true );
   2430 	} else {
   2431 		idThread::ReturnInt( false );
   2432 	}
   2433 }
   2434 
   2435 /*
   2436 ===============
   2437 idMover_Binary::UpdateMoverSound
   2438 ===============
   2439 */
   2440 void idMover_Binary::UpdateMoverSound( moverState_t state ) {
   2441 	if ( moveMaster == this ) {
   2442 		switch( state ) {
   2443 			case MOVER_POS1:
   2444 				break;
   2445 			case MOVER_POS2:
   2446 				break;
   2447 			case MOVER_1TO2:
   2448 				StartSound( "snd_open", SND_CHANNEL_ANY, 0, false, NULL );
   2449 				break;
   2450 			case MOVER_2TO1:
   2451 				StartSound( "snd_close", SND_CHANNEL_ANY, 0, false, NULL );
   2452 				break;
   2453 		}
   2454 	}
   2455 }
   2456 
   2457 /*
   2458 ===============
   2459 idMover_Binary::SetMoverState
   2460 ===============
   2461 */
   2462 void idMover_Binary::SetMoverState( moverState_t newstate, int time ) {
   2463 	idVec3 	delta;
   2464 
   2465 	moverState = newstate;
   2466 	move_thread = 0;
   2467 
   2468 	UpdateMoverSound( newstate );
   2469 
   2470 	stateStartTime = time;
   2471 	switch( moverState ) {
   2472 		case MOVER_POS1: {
   2473 			Signal( SIG_MOVER_POS1 );
   2474 			physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, time, 0, pos1, vec3_origin, vec3_origin );
   2475 			break;
   2476 		}
   2477 		case MOVER_POS2: {
   2478 			Signal( SIG_MOVER_POS2 );
   2479 			physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, time, 0, pos2, vec3_origin, vec3_origin );
   2480 			break;
   2481 		}
   2482 		case MOVER_1TO2: {
   2483 			Signal( SIG_MOVER_1TO2 );
   2484 			physicsObj.SetLinearExtrapolation( EXTRAPOLATION_LINEAR, time, duration, pos1, ( pos2 - pos1 ) * 1000.0f / duration, vec3_origin );
   2485 			if ( accelTime != 0 || decelTime != 0 ) {
   2486 				physicsObj.SetLinearInterpolation( time, accelTime, decelTime, duration, pos1, pos2 );
   2487 			} else {
   2488 				physicsObj.SetLinearInterpolation( 0, 0, 0, 0, pos1, pos2 );
   2489 			}
   2490 			break;
   2491 		}
   2492 		case MOVER_2TO1: {
   2493 			Signal( SIG_MOVER_2TO1 );
   2494 			physicsObj.SetLinearExtrapolation( EXTRAPOLATION_LINEAR, time, duration, pos2, ( pos1 - pos2 ) * 1000.0f / duration, vec3_origin );
   2495 			if ( accelTime != 0 || decelTime != 0 ) {
   2496 				physicsObj.SetLinearInterpolation( time, accelTime, decelTime, duration, pos2, pos1 );
   2497 			} else {
   2498 				physicsObj.SetLinearInterpolation( 0, 0, 0, 0, pos1, pos2 );
   2499 			}
   2500 			break;
   2501 		}
   2502 	}
   2503 }
   2504 
   2505 /*
   2506 ================
   2507 idMover_Binary::MatchActivateTeam
   2508 
   2509 All entities in a mover team will move from pos1 to pos2
   2510 in the same amount of time
   2511 ================
   2512 */
   2513 void idMover_Binary::MatchActivateTeam( moverState_t newstate, int time ) {
   2514 	idMover_Binary *slave;
   2515 
   2516 	for ( slave = this; slave != NULL; slave = slave->activateChain ) {
   2517 		slave->SetMoverState( newstate, time );
   2518 	}
   2519 }
   2520 
   2521 /*
   2522 ================
   2523 idMover_Binary::Enable
   2524 ================
   2525 */
   2526 void idMover_Binary::Enable( bool b ) {
   2527 	enabled = b;
   2528 }
   2529 
   2530 /*
   2531 ================
   2532 idMover_Binary::Event_MatchActivateTeam
   2533 ================
   2534 */
   2535 void idMover_Binary::Event_MatchActivateTeam( moverState_t newstate, int time ) {
   2536 	MatchActivateTeam( newstate, time );
   2537 }
   2538 
   2539 /*
   2540 ================
   2541 idMover_Binary::BindTeam
   2542 
   2543 All entities in a mover team will be bound 
   2544 ================
   2545 */
   2546 void idMover_Binary::BindTeam( idEntity *bindTo ) {
   2547 	idMover_Binary *slave;
   2548 
   2549 	for ( slave = this; slave != NULL; slave = slave->activateChain ) {
   2550 		slave->Bind( bindTo, true );
   2551 	}
   2552 }
   2553 
   2554 /*
   2555 ================
   2556 idMover_Binary::JoinActivateTeam
   2557 
   2558 Set all entities in a mover team to be enabled
   2559 ================
   2560 */
   2561 void idMover_Binary::JoinActivateTeam( idMover_Binary *master ) {
   2562 	this->activateChain = master->activateChain;
   2563 	master->activateChain = this;
   2564 }
   2565 
   2566 /*
   2567 ================
   2568 idMover_Binary::Event_Enable
   2569 
   2570 Set all entities in a mover team to be enabled
   2571 ================
   2572 */
   2573 void idMover_Binary::Event_Enable() {
   2574 	idMover_Binary *slave;
   2575 
   2576 	for ( slave = moveMaster; slave != NULL; slave = slave->activateChain ) {
   2577 		slave->Enable( false );
   2578 	}
   2579 }
   2580 
   2581 /*
   2582 ================
   2583 idMover_Binary::Event_Disable
   2584 
   2585 Set all entities in a mover team to be disabled
   2586 ================
   2587 */
   2588 void idMover_Binary::Event_Disable() {
   2589 	idMover_Binary *slave;
   2590 
   2591 	for ( slave = moveMaster; slave != NULL; slave = slave->activateChain ) {
   2592 		slave->Enable( false );
   2593 	}
   2594 }
   2595 
   2596 /*
   2597 ================
   2598 idMover_Binary::Event_OpenPortal
   2599 
   2600 Sets the portal associtated with this mover to be open
   2601 ================
   2602 */
   2603 void idMover_Binary::Event_OpenPortal() {
   2604 	idMover_Binary *slave;
   2605 
   2606 	for ( slave = moveMaster; slave != NULL; slave = slave->activateChain ) {
   2607 		if ( slave->areaPortal ) {
   2608 			slave->SetPortalState( true );
   2609 		}
   2610 		if ( slave->playerOnly ) {
   2611 			gameLocal.SetAASAreaState( slave->GetPhysics()->GetAbsBounds(), AREACONTENTS_CLUSTERPORTAL, false );
   2612 		}
   2613 	}
   2614 }
   2615 
   2616 /*
   2617 ================
   2618 idMover_Binary::Event_ClosePortal
   2619 
   2620 Sets the portal associtated with this mover to be closed
   2621 ================
   2622 */
   2623 void idMover_Binary::Event_ClosePortal() {
   2624 	idMover_Binary *slave;
   2625 
   2626 	for ( slave = moveMaster; slave != NULL; slave = slave->activateChain ) {
   2627 		if ( !slave->IsHidden() ) {
   2628 			if ( slave->areaPortal ) {
   2629 				slave->SetPortalState( false );
   2630 			}
   2631 			if ( slave->playerOnly ) {
   2632 				gameLocal.SetAASAreaState( slave->GetPhysics()->GetAbsBounds(), AREACONTENTS_CLUSTERPORTAL, true );
   2633 			}
   2634 		}
   2635 	}
   2636 }
   2637 
   2638 /*
   2639 ================
   2640 idMover_Binary::Event_ReturnToPos1
   2641 ================
   2642 */
   2643 void idMover_Binary::Event_ReturnToPos1() {
   2644 	MatchActivateTeam( MOVER_2TO1, gameLocal.slow.time );
   2645 }
   2646 
   2647 /*
   2648 ================
   2649 idMover_Binary::Event_Reached_BinaryMover
   2650 ================
   2651 */
   2652 void idMover_Binary::Event_Reached_BinaryMover() {
   2653 
   2654 	if ( moverState == MOVER_1TO2 ) {
   2655 		// reached pos2
   2656 		idThread::ObjectMoveDone( move_thread, this );
   2657 		move_thread = 0;
   2658 
   2659 		if ( moveMaster == this ) {
   2660 			StartSound( "snd_opened", SND_CHANNEL_ANY, 0, false, NULL );
   2661 		}
   2662 
   2663 		SetMoverState( MOVER_POS2, gameLocal.slow.time );
   2664 
   2665 		SetGuiStates( guiBinaryMoverStates[MOVER_POS2] );
   2666 
   2667 		UpdateBuddies( 1 );
   2668 
   2669 		if ( enabled && wait >= 0 && !spawnArgs.GetBool( "toggle" ) ) {
   2670 			// return to pos1 after a delay
   2671 			PostEventSec( &EV_Mover_ReturnToPos1, wait );
   2672 		}
   2673 		
   2674 		// fire targets
   2675 		ActivateTargets( moveMaster->GetActivator() );
   2676 		
   2677 		SetBlocked(false);
   2678 	} else if ( moverState == MOVER_2TO1 ) {
   2679 		// reached pos1
   2680 		idThread::ObjectMoveDone( move_thread, this );
   2681 		move_thread = 0;
   2682 
   2683 		SetMoverState( MOVER_POS1, gameLocal.slow.time );
   2684 
   2685 		SetGuiStates( guiBinaryMoverStates[MOVER_POS1] );
   2686 
   2687 		UpdateBuddies( 0 );
   2688 
   2689 		// close areaportals
   2690 		if ( moveMaster == this ) {
   2691 			ProcessEvent( &EV_Mover_ClosePortal );
   2692 		}
   2693 
   2694 		if ( enabled && wait >= 0 && spawnArgs.GetBool( "continuous" ) ) {
   2695 			PostEventSec( &EV_Activate, wait, this );
   2696 		}
   2697 		SetBlocked(false);
   2698 	} else {
   2699 		gameLocal.Error( "Event_Reached_BinaryMover: bad moverState" );
   2700 	}
   2701 }
   2702 
   2703 /*
   2704 ================
   2705 idMover_Binary::GotoPosition1
   2706 ================
   2707 */
   2708 void idMover_Binary::GotoPosition1() {
   2709 	idMover_Binary *slave;
   2710 	int	partial;
   2711 
   2712 	// only the master should control this
   2713 	if ( moveMaster != this ) {
   2714 		moveMaster->GotoPosition1();
   2715 		return;
   2716 	}
   2717 
   2718 	SetGuiStates( guiBinaryMoverStates[MOVER_2TO1] );
   2719 
   2720 	if ( ( moverState == MOVER_POS1 ) || ( moverState == MOVER_2TO1 ) ) {
   2721 		// already there, or on the way
   2722 		return;
   2723 	}
   2724 
   2725 	if ( moverState == MOVER_POS2 ) {
   2726 		for ( slave = this; slave != NULL; slave = slave->activateChain ) {
   2727 			slave->CancelEvents( &EV_Mover_ReturnToPos1 );
   2728 		}
   2729 		ProcessEvent( &EV_Mover_ReturnToPos1 );
   2730 		return;
   2731 	}
   2732 
   2733 	// only partway up before reversing
   2734 	if ( moverState == MOVER_1TO2 ) {
   2735 		// use the physics times because this might be executed during the physics simulation
   2736 		partial = physicsObj.GetLinearEndTime() - physicsObj.GetTime();
   2737 		assert( partial >= 0 );
   2738 		if ( partial < 0 ) {
   2739 			partial = 0;
   2740 		}
   2741 		MatchActivateTeam( MOVER_2TO1, physicsObj.GetTime() - partial );
   2742 		// if already at at position 1 (partial == duration) execute the reached event
   2743 		if ( partial >= duration ) {
   2744 			Event_Reached_BinaryMover();
   2745 		}
   2746 	}
   2747 }
   2748 
   2749 /*
   2750 ================
   2751 idMover_Binary::GotoPosition2
   2752 ================
   2753 */
   2754 void idMover_Binary::GotoPosition2() {
   2755 	int	partial;
   2756 
   2757 	// only the master should control this
   2758 	if ( moveMaster != this ) {
   2759 		moveMaster->GotoPosition2();
   2760 		return;
   2761 	}
   2762 
   2763 	SetGuiStates( guiBinaryMoverStates[MOVER_1TO2] );
   2764 
   2765 	if ( ( moverState == MOVER_POS2 ) || ( moverState == MOVER_1TO2 ) ) {
   2766 		// already there, or on the way
   2767 		return;
   2768 	}
   2769 
   2770 	if ( moverState == MOVER_POS1 ) {
   2771 		MatchActivateTeam( MOVER_1TO2, gameLocal.slow.time );
   2772 
   2773 		// open areaportal
   2774 		ProcessEvent( &EV_Mover_OpenPortal );
   2775 		return;
   2776 	}
   2777 
   2778 
   2779 	// only partway up before reversing
   2780 	if ( moverState == MOVER_2TO1 ) {
   2781 		// use the physics times because this might be executed during the physics simulation
   2782 		partial = physicsObj.GetLinearEndTime() - physicsObj.GetTime();
   2783 		assert( partial >= 0 );
   2784 		if ( partial < 0 ) {
   2785 			partial = 0;
   2786 		}
   2787 		MatchActivateTeam( MOVER_1TO2, physicsObj.GetTime() - partial );
   2788 		// if already at at position 2 (partial == duration) execute the reached event
   2789 		if ( partial >= duration ) {
   2790 			Event_Reached_BinaryMover();
   2791 		}
   2792 	}
   2793 }
   2794 
   2795 /*
   2796 ================
   2797 idMover_Binary::UpdateBuddies
   2798 ================
   2799 */
   2800 void idMover_Binary::UpdateBuddies( int val ) {
   2801 	int i, c;
   2802 
   2803 	if ( updateStatus == 2 ) {
   2804 		 c = buddies.Num();
   2805 		for ( i = 0; i < c; i++ ) {
   2806 			idEntity *buddy = gameLocal.FindEntity( buddies[i] );
   2807 			if ( buddy ) {
   2808 				buddy->SetShaderParm( SHADERPARM_MODE, val );
   2809 				buddy->UpdateVisuals();
   2810 			}
   2811 		}
   2812 	}
   2813 }
   2814 
   2815 /*
   2816 ================
   2817 idMover_Binary::SetGuiStates
   2818 ================
   2819 */
   2820 void idMover_Binary::SetGuiStates( const char *state ) {
   2821 	if ( guiTargets.Num() ) {
   2822 		SetGuiState( "movestate", state );
   2823 	}
   2824 
   2825 	idMover_Binary *mb = activateChain;
   2826 	while( mb ) {
   2827 		if ( mb->guiTargets.Num() ) {
   2828 			mb->SetGuiState( "movestate", state );
   2829 		}
   2830 		mb = mb->activateChain;
   2831 	}
   2832 }
   2833 
   2834 /*
   2835 ================
   2836 idMover_Binary::Use_BinaryMover
   2837 ================
   2838 */
   2839 void idMover_Binary::Use_BinaryMover( idEntity *activator ) {
   2840 	// only the master should be used
   2841 	if ( moveMaster != this ) {
   2842 		moveMaster->Use_BinaryMover( activator );
   2843 		return;
   2844 	}
   2845 
   2846 	if ( !enabled ) {
   2847 		return;
   2848 	}
   2849 
   2850 	activatedBy = activator;
   2851 
   2852 	if ( moverState == MOVER_POS1 ) {
   2853 		// FIXME: start moving 1 ms later, because if this was player
   2854 		// triggered, gameLocal.time hasn't been advanced yet
   2855 		MatchActivateTeam( MOVER_1TO2, gameLocal.slow.time + 1 );
   2856 
   2857 		SetGuiStates( guiBinaryMoverStates[MOVER_1TO2] );
   2858 		// open areaportal
   2859 		ProcessEvent( &EV_Mover_OpenPortal );
   2860 		return;
   2861 	}
   2862 
   2863 	// if all the way up, just delay before coming down
   2864 	if ( moverState == MOVER_POS2 ) {
   2865 		idMover_Binary *slave;
   2866 
   2867 		if ( wait == -1 ) {
   2868 			return;
   2869 		}
   2870 
   2871 		SetGuiStates( guiBinaryMoverStates[MOVER_2TO1] );
   2872 
   2873 		for ( slave = this; slave != NULL; slave = slave->activateChain ) {
   2874 			slave->CancelEvents( &EV_Mover_ReturnToPos1 );
   2875 			slave->PostEventSec( &EV_Mover_ReturnToPos1, spawnArgs.GetBool( "toggle" ) ? 0 : wait );
   2876 		}
   2877 		return;
   2878 	}
   2879 
   2880 	// only partway down before reversing
   2881 	if ( moverState == MOVER_2TO1 ) {
   2882 		GotoPosition2();
   2883 		return;
   2884 	}
   2885 
   2886 	// only partway up before reversing
   2887 	if ( moverState == MOVER_1TO2 ) {
   2888 		GotoPosition1();
   2889 		return;
   2890 	}
   2891 }
   2892 
   2893 /*
   2894 ================
   2895 idMover_Binary::Event_Use_BinaryMover
   2896 ================
   2897 */
   2898 void idMover_Binary::Event_Use_BinaryMover( idEntity *activator ) {
   2899 	Use_BinaryMover( activator );
   2900 }
   2901 
   2902 /*
   2903 ================
   2904 idMover_Binary::PreBind
   2905 ================
   2906 */
   2907 void idMover_Binary::PreBind() {
   2908 	pos1 = GetWorldCoordinates( pos1 );
   2909 	pos2 = GetWorldCoordinates( pos2 );
   2910 }
   2911 
   2912 /*
   2913 ================
   2914 idMover_Binary::PostBind
   2915 ================
   2916 */
   2917 void idMover_Binary::PostBind() {
   2918 	pos1 = GetLocalCoordinates( pos1 );
   2919 	pos2 = GetLocalCoordinates( pos2 );
   2920 }
   2921 
   2922 /*
   2923 ================
   2924 idMover_Binary::FindGuiTargets
   2925 ================
   2926 */
   2927 void idMover_Binary::FindGuiTargets() {
   2928    	gameLocal.GetTargets( spawnArgs, guiTargets, "guiTarget" );
   2929 }
   2930 
   2931 /*
   2932 ==============================
   2933 idMover_Binary::SetGuiState
   2934 
   2935 key/val will be set to any renderEntity->gui's on the list
   2936 ==============================
   2937 */
   2938 void idMover_Binary::SetGuiState( const char *key, const char *val ) const {
   2939 	int i;
   2940 
   2941 	for( i = 0; i < guiTargets.Num(); i++ ) {
   2942 		idEntity *ent = guiTargets[ i ].GetEntity();
   2943 		if ( ent ) {
   2944 			for ( int j = 0; j < MAX_RENDERENTITY_GUI; j++ ) {
   2945 				if ( ent->GetRenderEntity() && ent->GetRenderEntity()->gui[ j ] ) {
   2946 					ent->GetRenderEntity()->gui[ j ]->SetStateString( key, val );
   2947 					ent->GetRenderEntity()->gui[ j ]->StateChanged( gameLocal.slow.time, true );
   2948 				}
   2949 			}
   2950 			ent->UpdateVisuals();
   2951 		}
   2952 	}
   2953 }
   2954 
   2955 /*
   2956 ================
   2957 idMover_Binary::Event_InitGuiTargets
   2958 ================
   2959 */
   2960 void idMover_Binary::Event_FindGuiTargets() {
   2961 	FindGuiTargets();
   2962 }
   2963 
   2964 /*
   2965 ================
   2966 idMover_Binary::Event_InitGuiTargets
   2967 ================
   2968 */
   2969 void idMover_Binary::Event_InitGuiTargets() {
   2970 	if ( guiTargets.Num() ) {
   2971 		SetGuiState( "movestate", guiBinaryMoverStates[MOVER_POS1] );
   2972 	}
   2973 }
   2974 
   2975 /*
   2976 ================
   2977 idMover_Binary::InitSpeed
   2978 
   2979 pos1, pos2, and speed are passed in so the movement delta can be calculated
   2980 ================
   2981 */
   2982 void idMover_Binary::InitSpeed( idVec3 &mpos1, idVec3 &mpos2, float mspeed, float maccelTime, float mdecelTime ) {
   2983 	idVec3		move;
   2984 	float		distance;
   2985 	float		speed;
   2986 
   2987 	pos1		= mpos1;
   2988 	pos2		= mpos2;
   2989 
   2990 	accelTime	= idPhysics::SnapTimeToPhysicsFrame( SEC2MS( maccelTime ) );
   2991 	decelTime	= idPhysics::SnapTimeToPhysicsFrame( SEC2MS( mdecelTime ) );
   2992 
   2993 	speed		= mspeed ? mspeed : 100;
   2994 
   2995 	// calculate time to reach second position from speed
   2996 	move = pos2 - pos1;
   2997 	distance = move.Length();
   2998 	duration = idPhysics::SnapTimeToPhysicsFrame( distance * 1000 / speed );
   2999 	if ( duration <= 0 ) {
   3000 		duration = 1;
   3001 	}
   3002 
   3003 	moverState = MOVER_POS1;
   3004 
   3005 	physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, pos1, vec3_origin, vec3_origin );
   3006 	physicsObj.SetLinearInterpolation( 0, 0, 0, 0, vec3_origin, vec3_origin );
   3007 	SetOrigin( pos1 );
   3008 
   3009 	PostEventMS( &EV_Mover_InitGuiTargets, 0 );
   3010 }
   3011 
   3012 /*
   3013 ================
   3014 idMover_Binary::InitTime
   3015 
   3016 pos1, pos2, and time are passed in so the movement delta can be calculated
   3017 ================
   3018 */
   3019 void idMover_Binary::InitTime( idVec3 &mpos1, idVec3 &mpos2, float mtime, float maccelTime, float mdecelTime ) {
   3020 
   3021 	pos1		= mpos1;
   3022 	pos2		= mpos2;
   3023 
   3024 	accelTime	= idPhysics::SnapTimeToPhysicsFrame( SEC2MS( maccelTime ) );
   3025 	decelTime	= idPhysics::SnapTimeToPhysicsFrame( SEC2MS( mdecelTime ) );
   3026 
   3027 	duration	= idPhysics::SnapTimeToPhysicsFrame( SEC2MS( mtime ) );
   3028 	if ( duration <= 0 ) {
   3029 		duration = 1;
   3030 	}
   3031 
   3032 	moverState = MOVER_POS1;
   3033 
   3034 	physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, pos1, vec3_origin, vec3_origin );
   3035 	physicsObj.SetLinearInterpolation( 0, 0, 0, 0, vec3_origin, vec3_origin );
   3036 	SetOrigin( pos1 );
   3037 
   3038 	PostEventMS( &EV_Mover_InitGuiTargets, 0 );
   3039 }
   3040 
   3041 /*
   3042 ================
   3043 idMover_Binary::SetBlocked
   3044 ================
   3045 */
   3046 void idMover_Binary::SetBlocked( bool b ) {
   3047 	for ( idMover_Binary *slave = moveMaster; slave != NULL; slave = slave->activateChain ) {
   3048 		slave->blocked = b;
   3049 		if ( b ) {
   3050 			const idKeyValue *kv = slave->spawnArgs.MatchPrefix( "triggerBlocked" );
   3051 			while( kv ) {
   3052 				idEntity *ent = gameLocal.FindEntity( kv->GetValue() );
   3053 				if ( ent ) {
   3054 					ent->PostEventMS( &EV_Activate, 0, moveMaster->GetActivator() );
   3055 				}
   3056 				kv = slave->spawnArgs.MatchPrefix( "triggerBlocked", kv );
   3057 			}
   3058 		}
   3059 	}
   3060 }
   3061 
   3062 /*
   3063 ================
   3064 idMover_Binary::IsBlocked
   3065 ================
   3066 */
   3067 bool idMover_Binary::IsBlocked() {
   3068 	return blocked;
   3069 }
   3070 
   3071 /*
   3072 ================
   3073 idMover_Binary::GetActivator
   3074 ================
   3075 */
   3076 idEntity *idMover_Binary::GetActivator() const {
   3077 	return activatedBy.GetEntity();
   3078 }
   3079 
   3080 /*
   3081 ================
   3082 idMover_Binary::WriteToSnapshot
   3083 ================
   3084 */
   3085 void idMover_Binary::WriteToSnapshot( idBitMsg &msg ) const {
   3086 	physicsObj.WriteToSnapshot( msg );
   3087 	msg.WriteBits( moverState, 3 );
   3088 	WriteBindToSnapshot( msg );
   3089 }
   3090 
   3091 /*
   3092 ================
   3093 idMover_Binary::ReadFromSnapshot
   3094 ================
   3095 */
   3096 void idMover_Binary::ReadFromSnapshot( const idBitMsg &msg ) {
   3097 	moverState_t oldMoverState = moverState;
   3098 
   3099 	physicsObj.ReadFromSnapshot( msg );
   3100 	moverState = (moverState_t) msg.ReadBits( 3 );
   3101 	ReadBindFromSnapshot( msg );
   3102 
   3103 	if ( msg.HasChanged() ) {
   3104 		if ( moverState != oldMoverState ) {
   3105 			UpdateMoverSound( moverState );
   3106 			MatchActivateTeam( moverState, gameLocal.slow.time );
   3107 		}
   3108 		UpdateVisuals();
   3109 	}
   3110 }
   3111 
   3112 /*
   3113 ================
   3114 idMover_Binary::SetPortalState
   3115 ================
   3116 */
   3117 void idMover_Binary::SetPortalState( bool open ) {
   3118 	assert( areaPortal );
   3119 	gameLocal.SetPortalState( areaPortal, open ? PS_BLOCK_NONE : PS_BLOCK_ALL );
   3120 }
   3121 
   3122 /*
   3123 ===============================================================================
   3124 
   3125 idDoor
   3126 
   3127 A use can be triggered either by a touch function, by being shot, or by being
   3128 targeted by another entity.
   3129 
   3130 ===============================================================================
   3131 */
   3132 
   3133 const idEventDef EV_Door_StartOpen( "<startOpen>", NULL );
   3134 const idEventDef EV_Door_SpawnDoorTrigger( "<spawnDoorTrigger>", NULL );
   3135 const idEventDef EV_Door_SpawnSoundTrigger( "<spawnSoundTrigger>", NULL );
   3136 const idEventDef EV_Door_Open( "open", NULL );
   3137 const idEventDef EV_Door_Close( "close", NULL );
   3138 const idEventDef EV_Door_Lock( "lock", "d" );
   3139 const idEventDef EV_Door_IsOpen( "isOpen", NULL, 'f' );
   3140 const idEventDef EV_Door_IsLocked( "isLocked", NULL, 'f' );
   3141 
   3142 CLASS_DECLARATION( idMover_Binary, idDoor )
   3143 	EVENT( EV_TeamBlocked,				idDoor::Event_TeamBlocked )
   3144 	EVENT( EV_PartBlocked,				idDoor::Event_PartBlocked )
   3145 	EVENT( EV_Touch,					idDoor::Event_Touch )
   3146 	EVENT( EV_Activate,					idDoor::Event_Activate )
   3147 	EVENT( EV_Door_StartOpen,			idDoor::Event_StartOpen )
   3148 	EVENT( EV_Door_SpawnDoorTrigger,	idDoor::Event_SpawnDoorTrigger )
   3149 	EVENT( EV_Door_SpawnSoundTrigger,	idDoor::Event_SpawnSoundTrigger )
   3150 	EVENT( EV_Door_Open,				idDoor::Event_Open )
   3151 	EVENT( EV_Door_Close,				idDoor::Event_Close )
   3152 	EVENT( EV_Door_Lock,				idDoor::Event_Lock )
   3153 	EVENT( EV_Door_IsOpen,				idDoor::Event_IsOpen )
   3154 	EVENT( EV_Door_IsLocked,			idDoor::Event_Locked )
   3155 	EVENT( EV_ReachedPos,				idDoor::Event_Reached_BinaryMover )
   3156 	EVENT( EV_SpectatorTouch,			idDoor::Event_SpectatorTouch )
   3157 	EVENT( EV_Mover_OpenPortal,			idDoor::Event_OpenPortal )
   3158 	EVENT( EV_Mover_ClosePortal,		idDoor::Event_ClosePortal )
   3159 END_CLASS
   3160 
   3161 /*
   3162 ================
   3163 idDoor::idDoor
   3164 ================
   3165 */
   3166 idDoor::idDoor() {
   3167 	triggersize = 1.0f;
   3168 	crusher = false;
   3169 	noTouch = false;
   3170 	aas_area_closed = false;
   3171 	buddyStr.Clear();
   3172 	trigger = NULL;
   3173 	sndTrigger = NULL;
   3174 	nextSndTriggerTime = 0;
   3175 	localTriggerOrigin.Zero();
   3176 	localTriggerAxis.Identity();
   3177 	requires.Clear();
   3178 	removeItem = 0;
   3179 	syncLock.Clear();
   3180 	companionDoor = NULL;
   3181 	normalAxisIndex = 0;
   3182 }
   3183 
   3184 /*
   3185 ================
   3186 idDoor::~idDoor
   3187 ================
   3188 */
   3189 idDoor::~idDoor() {
   3190 	if ( trigger ) {
   3191 		delete trigger;
   3192 	}
   3193 	if ( sndTrigger ) {
   3194 		delete sndTrigger;
   3195 	}
   3196 }
   3197 
   3198 /*
   3199 ================
   3200 idDoor::Save
   3201 ================
   3202 */
   3203 void idDoor::Save( idSaveGame *savefile ) const {
   3204 
   3205 	savefile->WriteFloat( triggersize );
   3206 	savefile->WriteBool( crusher );
   3207 	savefile->WriteBool( noTouch );
   3208 	savefile->WriteBool( aas_area_closed );
   3209 	savefile->WriteString( buddyStr );
   3210 	savefile->WriteInt( nextSndTriggerTime );
   3211 
   3212 	savefile->WriteVec3( localTriggerOrigin );
   3213 	savefile->WriteMat3( localTriggerAxis );
   3214 
   3215 	savefile->WriteString( requires );
   3216 	savefile->WriteInt( removeItem );
   3217 	savefile->WriteString( syncLock );
   3218 	savefile->WriteInt( normalAxisIndex );
   3219 
   3220 	savefile->WriteClipModel( trigger );
   3221 	savefile->WriteClipModel( sndTrigger );
   3222 
   3223 	savefile->WriteObject( companionDoor );
   3224 }
   3225 
   3226 /*
   3227 ================
   3228 idDoor::Restore
   3229 ================
   3230 */
   3231 void idDoor::Restore( idRestoreGame *savefile ) {
   3232 
   3233 	savefile->ReadFloat( triggersize );
   3234 	savefile->ReadBool( crusher );
   3235 	savefile->ReadBool( noTouch );
   3236 	savefile->ReadBool( aas_area_closed );
   3237 	SetAASAreaState( aas_area_closed );
   3238 	savefile->ReadString( buddyStr );
   3239 	savefile->ReadInt( nextSndTriggerTime );
   3240 
   3241 	savefile->ReadVec3( localTriggerOrigin );
   3242 	savefile->ReadMat3( localTriggerAxis );
   3243 
   3244 	savefile->ReadString( requires );
   3245 	savefile->ReadInt( removeItem );
   3246 	savefile->ReadString( syncLock );
   3247 	savefile->ReadInt( normalAxisIndex );
   3248 
   3249 	savefile->ReadClipModel( trigger );
   3250 	savefile->ReadClipModel( sndTrigger );
   3251 
   3252 	savefile->ReadObject( reinterpret_cast<idClass *&>( companionDoor ) );
   3253 }
   3254 
   3255 /*
   3256 ================
   3257 idDoor::Spawn
   3258 ================
   3259 */
   3260 void idDoor::Spawn() {
   3261 	idVec3		abs_movedir;
   3262 	float		distance;
   3263 	idVec3		size;
   3264 	idVec3		movedir;
   3265 	float		dir;
   3266 	float		lip;
   3267 	bool		start_open;
   3268 	float		time;
   3269 	float		speed;
   3270 
   3271 	// get the direction to move
   3272 	if ( !spawnArgs.GetFloat( "movedir", "0", dir ) ) {
   3273 		// no movedir, so angle defines movement direction and not orientation,
   3274 		// a la oldschool Quake
   3275 		SetAngles( ang_zero );
   3276 		spawnArgs.GetFloat( "angle", "0", dir );
   3277 	}
   3278 	GetMovedir( dir, movedir );
   3279 
   3280 	// default speed of 400
   3281 	spawnArgs.GetFloat( "speed", "400", speed );
   3282 
   3283 	// default wait of 2 seconds
   3284 	spawnArgs.GetFloat( "wait", "3", wait );
   3285 
   3286 	// default lip of 8 units
   3287 	spawnArgs.GetFloat( "lip", "8", lip );
   3288 
   3289 	// by default no damage
   3290 	spawnArgs.GetFloat( "damage", "0", damage );
   3291 
   3292 	// trigger size
   3293 	spawnArgs.GetFloat( "triggersize", "120", triggersize );
   3294 
   3295 	spawnArgs.GetBool( "crusher", "0", crusher );
   3296 	spawnArgs.GetBool( "start_open", "0", start_open );
   3297 	spawnArgs.GetBool( "no_touch", "0", noTouch );
   3298 	spawnArgs.GetBool( "player_only", "0", playerOnly );
   3299 
   3300 	// expects syncLock to be a door that must be closed before this door will open
   3301 	spawnArgs.GetString( "syncLock", "", syncLock );
   3302 
   3303 	spawnArgs.GetString( "buddy", "", buddyStr );
   3304 
   3305 	spawnArgs.GetString( "requires", "", requires );
   3306 	spawnArgs.GetInt( "removeItem", "0", removeItem );
   3307 
   3308 	// ever separate piece of a door is considered solid when other team mates push entities
   3309 	fl.solidForTeam = true;
   3310 
   3311 	// first position at start
   3312 	pos1 = GetPhysics()->GetOrigin();
   3313 
   3314 	// calculate second position
   3315 	abs_movedir[0] = idMath::Fabs( movedir[ 0 ] );
   3316 	abs_movedir[1] = idMath::Fabs( movedir[ 1 ] );
   3317 	abs_movedir[2] = idMath::Fabs( movedir[ 2 ] );
   3318 	size = GetPhysics()->GetAbsBounds()[1] - GetPhysics()->GetAbsBounds()[0];
   3319 	distance = ( abs_movedir * size ) - lip;
   3320 	pos2 = pos1 + distance * movedir;
   3321 
   3322 	// if "start_open", reverse position 1 and 2
   3323 	if ( start_open ) {
   3324 		// post it after EV_SpawnBind
   3325 		PostEventMS( &EV_Door_StartOpen, 1 );		
   3326 	}
   3327 
   3328 	if ( spawnArgs.GetFloat( "time", "1", time ) ) {
   3329 		InitTime( pos1, pos2, time, 0, 0 );
   3330 	} else {
   3331 		InitSpeed( pos1, pos2, speed, 0, 0 );
   3332 	}
   3333 
   3334 	if ( moveMaster == this ) {
   3335 		if ( health ) {
   3336 			fl.takedamage = true;
   3337 		}
   3338 		if ( noTouch || health ) {
   3339 			// non touch/shoot doors
   3340 			PostEventMS( &EV_Mover_MatchTeam, 0, moverState, gameLocal.slow.time );
   3341 
   3342 			const char *sndtemp = spawnArgs.GetString( "snd_locked" );
   3343 			if ( spawnArgs.GetInt( "locked" ) && sndtemp && *sndtemp ) {
   3344 				PostEventMS( &EV_Door_SpawnSoundTrigger, 0 );
   3345 			}
   3346 		} else {
   3347 			// spawn trigger
   3348 			PostEventMS( &EV_Door_SpawnDoorTrigger, 0 );
   3349 		}
   3350 	}
   3351 
   3352 	// see if we are on an areaportal
   3353 	areaPortal = gameRenderWorld->FindPortal( GetPhysics()->GetAbsBounds() );
   3354 	if ( !start_open ) {
   3355 		// start closed
   3356 		ProcessEvent( &EV_Mover_ClosePortal );
   3357 
   3358 		if ( playerOnly ) {
   3359 			gameLocal.SetAASAreaState( GetPhysics()->GetAbsBounds(), AREACONTENTS_CLUSTERPORTAL, true );
   3360 		}
   3361 	}
   3362 
   3363 	int locked = spawnArgs.GetInt( "locked" );
   3364 	if ( locked ) {
   3365 		// make sure all members of the team get locked
   3366 		PostEventMS( &EV_Door_Lock, 0, locked );
   3367 	}
   3368 
   3369 	if ( spawnArgs.GetBool( "continuous" ) ) {
   3370 		PostEventSec( &EV_Activate, spawnArgs.GetFloat( "delay" ), this );
   3371 	}
   3372 
   3373 	// sounds have a habit of stuttering when portals close, so make them unoccluded
   3374 	refSound.parms.soundShaderFlags |= SSF_NO_OCCLUSION;
   3375 
   3376 	companionDoor = NULL;
   3377 
   3378 	enabled = true;
   3379 	blocked = false;
   3380 }
   3381 
   3382 /*
   3383 ================
   3384 idDoor::Think
   3385 ================
   3386 */
   3387 void idDoor::ClientThink( const int curTime, const float fraction, const bool predict ) {
   3388 	idVec3 masterOrigin;
   3389 	idMat3 masterAxis;
   3390 
   3391 	idMover_Binary::ClientThink( curTime, fraction, predict );
   3392 
   3393 	if ( thinkFlags & TH_PHYSICS ) {
   3394 		// update trigger position
   3395 		if ( GetMasterPosition( masterOrigin, masterAxis ) ) {
   3396 			if ( trigger ) {
   3397 				trigger->Link( gameLocal.clip, this, 0, masterOrigin + localTriggerOrigin * masterAxis, localTriggerAxis * masterAxis );
   3398 			}
   3399 			if ( sndTrigger ) {
   3400 				sndTrigger->Link( gameLocal.clip, this, 0, masterOrigin + localTriggerOrigin * masterAxis, localTriggerAxis * masterAxis );
   3401 			}
   3402 		}
   3403 	}
   3404 }
   3405 
   3406 /*
   3407 ================
   3408 idDoor::Think
   3409 ================
   3410 */
   3411 void idDoor::Think() {
   3412 	idVec3 masterOrigin;
   3413 	idMat3 masterAxis;
   3414 
   3415 	idMover_Binary::Think();
   3416 
   3417 	if ( thinkFlags & TH_PHYSICS ) {
   3418 		// update trigger position
   3419 		if ( GetMasterPosition( masterOrigin, masterAxis ) ) {
   3420 			if ( trigger ) {
   3421 				trigger->Link( gameLocal.clip, this, 0, masterOrigin + localTriggerOrigin * masterAxis, localTriggerAxis * masterAxis );
   3422 			}
   3423 			if ( sndTrigger ) {
   3424 				sndTrigger->Link( gameLocal.clip, this, 0, masterOrigin + localTriggerOrigin * masterAxis, localTriggerAxis * masterAxis );
   3425 			}
   3426 		}
   3427 	}
   3428 }
   3429 
   3430 /*
   3431 ================
   3432 idDoor::PreBind
   3433 ================
   3434 */
   3435 void idDoor::PreBind() {
   3436 	idMover_Binary::PreBind();
   3437 }
   3438 
   3439 /*
   3440 ================
   3441 idDoor::PostBind
   3442 ================
   3443 */
   3444 void idDoor::PostBind() {
   3445 	idMover_Binary::PostBind();
   3446 	GetLocalTriggerPosition( trigger ? trigger : sndTrigger );
   3447 }
   3448 
   3449 /*
   3450 ================
   3451 idDoor::SetAASAreaState
   3452 ================
   3453 */
   3454 void idDoor::SetAASAreaState( bool closed ) {
   3455 	aas_area_closed = closed;
   3456 	gameLocal.SetAASAreaState( physicsObj.GetAbsBounds(), AREACONTENTS_CLUSTERPORTAL|AREACONTENTS_OBSTACLE, closed );
   3457 }
   3458 
   3459 /*
   3460 ================
   3461 idDoor::Hide
   3462 ================
   3463 */
   3464 void idDoor::Hide() {
   3465 	idMover_Binary *slave;
   3466 	idMover_Binary *master;
   3467 	idDoor *slaveDoor;
   3468 	idDoor *companion;
   3469 
   3470 	master = GetMoveMaster();
   3471 	if ( this != master ) {
   3472 		master->Hide();
   3473 	} else {
   3474 		for ( slave = this; slave != NULL; slave = slave->GetActivateChain() ) {
   3475 			if ( slave->IsType( idDoor::Type ) ) {
   3476 				slaveDoor = static_cast<idDoor *>( slave );
   3477 				companion = slaveDoor->companionDoor;
   3478 				if ( companion && ( companion != master ) && ( companion->GetMoveMaster() != master ) ) {
   3479 					companion->Hide();
   3480 				}
   3481 				if ( slaveDoor->trigger ) {
   3482 					slaveDoor->trigger->Disable();
   3483 				}
   3484 				if ( slaveDoor->sndTrigger ) {
   3485 					slaveDoor->sndTrigger->Disable();
   3486 				}
   3487 				if ( slaveDoor->areaPortal ) {
   3488 					slaveDoor->SetPortalState( true );
   3489 				}
   3490 				slaveDoor->SetAASAreaState( false );
   3491 			}
   3492 			slave->GetPhysics()->GetClipModel()->Disable();
   3493 			slave->idMover_Binary::Hide();
   3494 		}
   3495 	}
   3496 }
   3497 
   3498 /*
   3499 ================
   3500 idDoor::Show
   3501 ================
   3502 */
   3503 void idDoor::Show() {
   3504 	idMover_Binary *slave;
   3505 	idMover_Binary *master;
   3506 	idDoor *slaveDoor;
   3507 	idDoor *companion;
   3508 
   3509 	master = GetMoveMaster();
   3510 	if ( this != master ) {
   3511 		master->Show();
   3512 	} else {
   3513 		for ( slave = this; slave != NULL; slave = slave->GetActivateChain() ) {
   3514 			if ( slave->IsType( idDoor::Type ) ) {
   3515 				slaveDoor = static_cast<idDoor *>( slave );
   3516 				companion = slaveDoor->companionDoor;
   3517 				if ( companion && ( companion != master ) && ( companion->GetMoveMaster() != master ) ) {
   3518 					companion->Show();
   3519 				}
   3520 				if ( slaveDoor->trigger ) {
   3521 					slaveDoor->trigger->Enable();
   3522 				}
   3523 				if ( slaveDoor->sndTrigger ) {
   3524 					slaveDoor->sndTrigger->Enable();
   3525 				}
   3526 				if ( slaveDoor->areaPortal && ( slaveDoor->moverState == MOVER_POS1 ) ) {
   3527 					slaveDoor->SetPortalState( false );
   3528 				}
   3529 				slaveDoor->SetAASAreaState( IsLocked() || IsNoTouch() );
   3530 			}
   3531 			slave->GetPhysics()->GetClipModel()->Enable();
   3532 			slave->idMover_Binary::Show();
   3533 		}
   3534 	}
   3535 }
   3536 
   3537 /*
   3538 ================
   3539 idDoor::GetLocalTriggerPosition
   3540 ================
   3541 */
   3542 void idDoor::GetLocalTriggerPosition( const idClipModel *trigger ) {
   3543 	idVec3 origin;
   3544 	idMat3 axis;
   3545 
   3546 	if ( !trigger ) {
   3547 		return;
   3548 	}
   3549 
   3550 	GetMasterPosition( origin, axis );
   3551 	localTriggerOrigin = ( trigger->GetOrigin() - origin ) * axis.Transpose();
   3552 	localTriggerAxis = trigger->GetAxis() * axis.Transpose();
   3553 }
   3554 
   3555 /*
   3556 ================
   3557 idDoor::Use
   3558 ================
   3559 */
   3560 void idDoor::Use( idEntity *other, idEntity *activator ) {
   3561 	if ( gameLocal.RequirementMet( activator, requires, removeItem ) ) {
   3562 		if ( syncLock.Length() ) {
   3563 			idEntity *sync = gameLocal.FindEntity( syncLock );
   3564 			if ( sync != NULL && sync->IsType( idDoor::Type ) ) {
   3565 				if ( static_cast<idDoor *>( sync )->IsOpen() ) {
   3566 					return;
   3567 				}
   3568 			}
   3569 		}
   3570 		ActivateTargets( activator );
   3571 		Use_BinaryMover( activator );
   3572 	} 
   3573 }
   3574 
   3575 /*
   3576 ================
   3577 idDoor::Open
   3578 ================
   3579 */
   3580 void idDoor::Open() {
   3581 	GotoPosition2();
   3582 }
   3583 
   3584 /*
   3585 ================
   3586 idDoor::Close
   3587 ================
   3588 */
   3589 void idDoor::Close() {
   3590 	GotoPosition1();
   3591 }
   3592 
   3593 /*
   3594 ================
   3595 idDoor::Lock
   3596 ================
   3597 */
   3598 void idDoor::Lock( int f ) {
   3599 	idMover_Binary *other;
   3600 
   3601 	// lock all the doors on the team
   3602 	for( other = moveMaster; other != NULL; other = other->GetActivateChain() ) {
   3603 		if ( other->IsType( idDoor::Type ) ) {
   3604 			idDoor *door = static_cast<idDoor *>( other );
   3605 			if ( other == moveMaster ) {
   3606 				if ( door->sndTrigger == NULL ) {
   3607 					// in this case the sound trigger never got spawned
   3608 					const char *sndtemp = door->spawnArgs.GetString( "snd_locked" );
   3609 					if ( sndtemp != NULL && *sndtemp != NULL ) {
   3610 						door->PostEventMS( &EV_Door_SpawnSoundTrigger, 0 );
   3611 					}
   3612 				}
   3613 				if ( !f && ( door->spawnArgs.GetInt( "locked" ) != 0 ) ) {
   3614 					door->StartSound( "snd_unlocked", SND_CHANNEL_ANY, 0, false, NULL );
   3615 				}
   3616 			}
   3617 			door->spawnArgs.SetInt( "locked", f );
   3618 			if ( ( f == 0 ) || ( !IsHidden() && ( door->moverState == MOVER_POS1 ) ) ) {
   3619 				door->SetAASAreaState( f != 0 );
   3620 			}
   3621 		}
   3622 	}
   3623 
   3624 	if ( f ) {
   3625 		Close();
   3626 	}
   3627 }
   3628 
   3629 /*
   3630 ================
   3631 idDoor::IsLocked
   3632 ================
   3633 */
   3634 int idDoor::IsLocked() {
   3635 	return spawnArgs.GetInt( "locked" );
   3636 }
   3637 
   3638 /*
   3639 ================
   3640 idDoor::IsOpen
   3641 ================
   3642 */
   3643 bool idDoor::IsOpen() {
   3644 	return ( moverState != MOVER_POS1 );
   3645 }
   3646 
   3647 /*
   3648 ================
   3649 idDoor::IsNoTouch
   3650 ================
   3651 */
   3652 bool idDoor::IsNoTouch() {
   3653 	return noTouch;
   3654 }
   3655 
   3656 /*
   3657 ================
   3658 idDoor::AllowPlayerOnly
   3659 ================
   3660 */
   3661 bool idDoor::AllowPlayerOnly( idEntity *ent ) {
   3662 	if ( playerOnly && !ent->IsType(idPlayer::Type) ) {
   3663 		return false;
   3664 	}
   3665 
   3666 	return true;
   3667 }
   3668 
   3669 /*
   3670 ======================
   3671 idDoor::CalcTriggerBounds
   3672 
   3673 Calcs bounds for a trigger.
   3674 ======================
   3675 */
   3676 void idDoor::CalcTriggerBounds( float size, idBounds &bounds ) {
   3677 	idMover_Binary	*other;
   3678 	int				i;
   3679 	int				best;
   3680 
   3681 	// find the bounds of everything on the team
   3682 	bounds = GetPhysics()->GetAbsBounds();
   3683 	
   3684 	fl.takedamage = true;
   3685 	for( other = activateChain; other != NULL; other = other->GetActivateChain() ) {
   3686 		if ( other->IsType( idDoor::Type ) ) {
   3687 			// find the bounds of everything on the team
   3688 			bounds.AddBounds( other->GetPhysics()->GetAbsBounds() );
   3689 
   3690 			// set all of the slaves as shootable
   3691 			other->fl.takedamage = true;
   3692 		}
   3693 	}
   3694 
   3695 	// find the thinnest axis, which will be the one we expand
   3696 	best = 0;
   3697 	for ( i = 1 ; i < 3 ; i++ ) {
   3698 		if ( bounds[1][ i ] - bounds[0][ i ] < bounds[1][ best ] - bounds[0][ best ] ) {
   3699 			best = i;
   3700 		}
   3701 	}
   3702 	normalAxisIndex = best;
   3703 	bounds[0][ best ] -= size;
   3704 	bounds[1][ best ] += size;
   3705 	bounds[0] -= GetPhysics()->GetOrigin();
   3706 	bounds[1] -= GetPhysics()->GetOrigin();
   3707 }
   3708 
   3709 /*
   3710 ======================
   3711 idDoor::Event_StartOpen
   3712 
   3713 if "start_open", reverse position 1 and 2
   3714 ======================
   3715 */
   3716 void idDoor::Event_StartOpen() {
   3717 	float time;
   3718 	float speed;
   3719 
   3720 	// if "start_open", reverse position 1 and 2
   3721 	pos1 = pos2;
   3722 	pos2 = GetPhysics()->GetOrigin();
   3723 
   3724 	spawnArgs.GetFloat( "speed", "400", speed );
   3725 
   3726 	if ( spawnArgs.GetFloat( "time", "1", time ) ) {
   3727 		InitTime( pos1, pos2, time, 0, 0 );
   3728 	} else {
   3729 		InitSpeed( pos1, pos2, speed, 0, 0 );
   3730 	}
   3731 }
   3732 
   3733 /*
   3734 ======================
   3735 idDoor::Event_SpawnDoorTrigger
   3736 
   3737 All of the parts of a door have been spawned, so create
   3738 a trigger that encloses all of them.
   3739 ======================
   3740 */
   3741 void idDoor::Event_SpawnDoorTrigger() {
   3742 	idBounds		bounds;
   3743 	idMover_Binary	*other;
   3744 	bool			toggle;
   3745 
   3746 	if ( trigger ) {
   3747 		// already have a trigger, so don't spawn a new one.
   3748 		return;
   3749 	}
   3750 
   3751 	// check if any of the doors are marked as toggled
   3752 	toggle = false;
   3753 	for( other = moveMaster; other != NULL; other = other->GetActivateChain() ) {
   3754 		if ( other->IsType( idDoor::Type ) && other->spawnArgs.GetBool( "toggle" ) ) {
   3755 			toggle = true;
   3756 			break;
   3757 		}
   3758 	}
   3759 
   3760 	if ( toggle ) {
   3761 		// mark them all as toggled
   3762 		for( other = moveMaster; other != NULL; other = other->GetActivateChain() ) {
   3763 			if ( other->IsType( idDoor::Type ) ) {
   3764 				other->spawnArgs.Set( "toggle", "1" );
   3765 			}
   3766 		}
   3767 		// don't spawn trigger
   3768 		return;
   3769 	}
   3770 
   3771 	const char *sndtemp = spawnArgs.GetString( "snd_locked" );
   3772 	if ( spawnArgs.GetInt( "locked" ) && sndtemp != NULL && *sndtemp != NULL ) {
   3773 		PostEventMS( &EV_Door_SpawnSoundTrigger, 0 );
   3774 	}
   3775 
   3776 	CalcTriggerBounds( triggersize, bounds );
   3777 
   3778 	// create a trigger clip model
   3779 	trigger = new (TAG_PHYSICS_CLIP_MOVER) idClipModel( idTraceModel( bounds ) );
   3780 	trigger->Link( gameLocal.clip, this, 255, GetPhysics()->GetOrigin(), mat3_identity );
   3781 	trigger->SetContents( CONTENTS_TRIGGER );
   3782 
   3783 	GetLocalTriggerPosition( trigger );
   3784 
   3785 	MatchActivateTeam( moverState, gameLocal.slow.time );
   3786 }
   3787 
   3788 /*
   3789 ======================
   3790 idDoor::Event_SpawnSoundTrigger
   3791 
   3792 Spawn a sound trigger to activate locked sound if it exists.
   3793 ======================
   3794 */
   3795 void idDoor::Event_SpawnSoundTrigger() {
   3796 	idBounds bounds;
   3797 
   3798 	if ( sndTrigger ) {
   3799 		return;
   3800 	}
   3801 
   3802 	CalcTriggerBounds( triggersize * 0.5f, bounds );
   3803 
   3804 	// create a trigger clip model
   3805 	sndTrigger = new (TAG_PHYSICS_CLIP_MOVER) idClipModel( idTraceModel( bounds ) );
   3806 	sndTrigger->Link( gameLocal.clip, this, 254, GetPhysics()->GetOrigin(), mat3_identity );
   3807 	sndTrigger->SetContents( CONTENTS_TRIGGER );
   3808 
   3809 	GetLocalTriggerPosition( sndTrigger );
   3810 }
   3811 
   3812 /*
   3813 ================
   3814 idDoor::Event_Reached_BinaryMover
   3815 ================
   3816 */
   3817 void idDoor::Event_Reached_BinaryMover() {
   3818 	if ( moverState == MOVER_2TO1 ) {
   3819 		SetBlocked( false );
   3820 		const idKeyValue *kv = spawnArgs.MatchPrefix( "triggerClosed" );
   3821 		while( kv ) {
   3822 			idEntity *ent = gameLocal.FindEntity( kv->GetValue() );
   3823 			if ( ent ) {
   3824 				ent->PostEventMS( &EV_Activate, 0, moveMaster->GetActivator() );
   3825 			}
   3826 			kv = spawnArgs.MatchPrefix( "triggerClosed", kv );
   3827 		}
   3828 	} else if ( moverState == MOVER_1TO2 ) {
   3829 		const idKeyValue *kv = spawnArgs.MatchPrefix( "triggerOpened" );
   3830 		while( kv ) {
   3831 			idEntity *ent = gameLocal.FindEntity( kv->GetValue() );
   3832 			if ( ent ) {
   3833 				ent->PostEventMS( &EV_Activate, 0, moveMaster->GetActivator() );
   3834 			}
   3835 			kv = spawnArgs.MatchPrefix( "triggerOpened", kv );
   3836 		}
   3837 	}
   3838 	idMover_Binary::Event_Reached_BinaryMover();
   3839 }
   3840 
   3841 /*
   3842 ================
   3843 idDoor::Blocked_Door
   3844 ================
   3845 */
   3846 void idDoor::Event_TeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ) {
   3847 	SetBlocked( true );
   3848 
   3849 	if ( crusher ) {
   3850 		return;		// crushers don't reverse
   3851 	}
   3852 
   3853 	// reverse direction
   3854 	Use_BinaryMover( moveMaster->GetActivator() );
   3855 
   3856 	if ( companionDoor ) {
   3857 		companionDoor->ProcessEvent( &EV_TeamBlocked, blockedEntity, blockingEntity );
   3858 	}
   3859 }
   3860 
   3861 /*
   3862 ===============
   3863 idDoor::SetCompanion
   3864 ===============
   3865 */
   3866 void idDoor::SetCompanion( idDoor *door ) {
   3867 	companionDoor = door;
   3868 }
   3869 
   3870 /*
   3871 ===============
   3872 idDoor::Event_PartBlocked
   3873 ===============
   3874 */
   3875 void idDoor::Event_PartBlocked( idEntity *blockingEntity ) {
   3876 	if ( damage > 0.0f ) {
   3877 		blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", damage, INVALID_JOINT );
   3878 	}
   3879 }
   3880 
   3881 /*
   3882 ================
   3883 idDoor::Event_Touch
   3884 ================
   3885 */
   3886 void idDoor::Event_Touch( idEntity *other, trace_t *trace ) {
   3887 	idVec3		contact, translate;
   3888 	idVec3		planeaxis1, planeaxis2, normal;
   3889 	idBounds	bounds;
   3890 
   3891 	if ( common->IsClient() ) {
   3892 		return;
   3893 	}
   3894 
   3895 	if ( !enabled ) {
   3896 		return;
   3897 	}
   3898 
   3899 	if ( trigger && trace->c.id == trigger->GetId() ) {
   3900 		if ( !IsNoTouch() && !IsLocked() && GetMoverState() != MOVER_1TO2 ) {
   3901 			if ( AllowPlayerOnly( other ) ) {
   3902 				Use( this, other );
   3903 			}
   3904 		}
   3905 	} else if ( sndTrigger && trace->c.id == sndTrigger->GetId() ) {
   3906 		if ( other && other->IsType( idPlayer::Type ) && IsLocked() && gameLocal.slow.time > nextSndTriggerTime ) {
   3907 			StartSound( "snd_locked", SND_CHANNEL_ANY, 0, false, NULL );
   3908 			nextSndTriggerTime = gameLocal.slow.time + 10000;
   3909 		}
   3910 	}
   3911 }
   3912 
   3913 /*
   3914 ================
   3915 idDoor::Event_SpectatorTouch
   3916 ================
   3917 */
   3918 void idDoor::Event_SpectatorTouch( idEntity *other, trace_t *trace ) {
   3919 	idVec3		contact, translate, normal;
   3920 	idBounds	bounds;
   3921 	idPlayer	*p;
   3922 
   3923 	assert( other && other->IsType( idPlayer::Type ) && static_cast< idPlayer * >( other )->spectating );
   3924 
   3925 	p = static_cast< idPlayer * >( other );
   3926 	// avoid flicker when stopping right at clip box boundaries
   3927 	if ( p->lastSpectateTeleport > gameLocal.slow.time - 1000 ) {
   3928 		return;
   3929 	}
   3930 	if ( trigger && !IsOpen() ) {
   3931 		// teleport to the other side, center to the middle of the trigger brush
   3932 		bounds = trigger->GetAbsBounds();
   3933 		contact = trace->endpos - bounds.GetCenter();
   3934 		translate = bounds.GetCenter();
   3935 		normal.Zero();
   3936 		normal[ normalAxisIndex ] = 1.0f;
   3937 		if ( normal * contact > 0 ) {
   3938 			translate[ normalAxisIndex ] += ( bounds[ 0 ][ normalAxisIndex ] - translate[ normalAxisIndex ] ) * 0.5f;
   3939 		} else {
   3940 			translate[ normalAxisIndex ] += ( bounds[ 1 ][ normalAxisIndex ] - translate[ normalAxisIndex ] ) * 0.5f;
   3941 		}
   3942 		p->SetOrigin( translate );
   3943 		p->lastSpectateTeleport = gameLocal.slow.time;
   3944 	}
   3945 }
   3946 
   3947 /*
   3948 ================
   3949 idDoor::Event_Activate
   3950 ================
   3951 */
   3952 void idDoor::Event_Activate( idEntity *activator ) {
   3953 	int old_lock;
   3954 
   3955 	if ( spawnArgs.GetInt( "locked" ) ) {
   3956 		if ( !trigger ) {
   3957 			PostEventMS( &EV_Door_SpawnDoorTrigger, 0 );
   3958 		}
   3959 		if ( buddyStr.Length() ) {
   3960 			idEntity *buddy = gameLocal.FindEntity( buddyStr );
   3961 			if ( buddy ) {
   3962 				buddy->SetShaderParm( SHADERPARM_MODE, 1 );
   3963 				buddy->UpdateVisuals();
   3964 			}
   3965 		}
   3966 
   3967 		old_lock = spawnArgs.GetInt( "locked" );
   3968 		Lock( 0 );
   3969 		if ( old_lock == 2 ) {
   3970 			return;
   3971 		}
   3972 	}
   3973 
   3974   	if ( syncLock.Length() ) {
   3975 		idEntity *sync = gameLocal.FindEntity( syncLock );
   3976 		if ( sync != NULL && sync->IsType( idDoor::Type ) ) {
   3977 			if ( static_cast<idDoor *>( sync )->IsOpen() ) {
   3978   				return;
   3979   			}
   3980   		}
   3981 	}
   3982 
   3983 	ActivateTargets( activator );
   3984 
   3985 	renderEntity.shaderParms[ SHADERPARM_MODE ] = 1;
   3986 	UpdateVisuals();
   3987 
   3988 	Use_BinaryMover( activator );
   3989 }
   3990 
   3991 /*
   3992 ================
   3993 idDoor::Event_Open
   3994 ================
   3995 */
   3996 void idDoor::Event_Open() {
   3997 	Open();
   3998 }
   3999 
   4000 /*
   4001 ================
   4002 idDoor::Event_Close
   4003 ================
   4004 */
   4005 void idDoor::Event_Close() {
   4006 	Close();
   4007 }
   4008 
   4009 /*
   4010 ================
   4011 idDoor::Event_Lock
   4012 ================
   4013 */
   4014 void idDoor::Event_Lock( int f ) {
   4015 	Lock( f );
   4016 }
   4017 
   4018 /*
   4019 ================
   4020 idDoor::Event_IsOpen
   4021 ================
   4022 */
   4023 void idDoor::Event_IsOpen() {
   4024 	bool state;
   4025 
   4026 	state = IsOpen();
   4027 	idThread::ReturnFloat( state );
   4028 }
   4029 
   4030 /*
   4031 ================
   4032 idDoor::Event_Locked
   4033 ================
   4034 */
   4035 void idDoor::Event_Locked() {
   4036 	idThread::ReturnFloat( spawnArgs.GetInt("locked") );
   4037 }
   4038 
   4039 /*
   4040 ================
   4041 idDoor::Event_OpenPortal
   4042 
   4043 Sets the portal associtated with this door to be open
   4044 ================
   4045 */
   4046 void idDoor::Event_OpenPortal() {
   4047 	idMover_Binary *slave;
   4048 	idDoor *slaveDoor;
   4049 
   4050 	for ( slave = this; slave != NULL; slave = slave->GetActivateChain() ) {
   4051 		if ( slave->IsType( idDoor::Type ) ) {
   4052 			slaveDoor = static_cast<idDoor *>( slave );
   4053 			if ( slaveDoor->areaPortal ) {
   4054 				slaveDoor->SetPortalState( true );
   4055 			}
   4056 			slaveDoor->SetAASAreaState( false );
   4057 		}
   4058 	}
   4059 }
   4060 
   4061 /*
   4062 ================
   4063 idDoor::Event_ClosePortal
   4064 
   4065 Sets the portal associtated with this door to be closed
   4066 ================
   4067 */
   4068 void idDoor::Event_ClosePortal() {
   4069 	idMover_Binary *slave;
   4070 	idDoor *slaveDoor;
   4071 
   4072 	for ( slave = this; slave != NULL; slave = slave->GetActivateChain() ) {
   4073 		if ( !slave->IsHidden() ) {
   4074 			if ( slave->IsType( idDoor::Type ) ) {
   4075 				slaveDoor = static_cast<idDoor *>( slave );
   4076 				if ( slaveDoor->areaPortal ) {
   4077 					slaveDoor->SetPortalState( false );
   4078 				}
   4079 				slaveDoor->SetAASAreaState( IsLocked() || IsNoTouch() );
   4080 			}
   4081 		}
   4082 	}
   4083 }
   4084 
   4085 
   4086 /*
   4087 ===============================================================================
   4088 
   4089 idPlat
   4090 
   4091 ===============================================================================
   4092 */
   4093 
   4094 CLASS_DECLARATION( idMover_Binary, idPlat )
   4095 	EVENT( EV_Touch,			idPlat::Event_Touch )
   4096 	EVENT( EV_TeamBlocked,		idPlat::Event_TeamBlocked )
   4097 	EVENT( EV_PartBlocked,		idPlat::Event_PartBlocked )
   4098 END_CLASS
   4099 
   4100 /*
   4101 ===============
   4102 idPlat::idPlat
   4103 ===============
   4104 */
   4105 idPlat::idPlat() {
   4106 	trigger = NULL;
   4107 	localTriggerOrigin.Zero();
   4108 	localTriggerAxis.Identity();
   4109 }
   4110 
   4111 /*
   4112 ===============
   4113 idPlat::~idPlat
   4114 ===============
   4115 */
   4116 idPlat::~idPlat() {
   4117 	if ( trigger ) {
   4118 		delete trigger;
   4119 	}
   4120 }
   4121 
   4122 /*
   4123 ===============
   4124 idPlat::Save
   4125 ===============
   4126 */
   4127 void idPlat::Save( idSaveGame *savefile ) const {
   4128 	savefile->WriteClipModel( trigger );
   4129 	savefile->WriteVec3( localTriggerOrigin );
   4130 	savefile->WriteMat3( localTriggerAxis );
   4131 }
   4132 
   4133 /*
   4134 ===============
   4135 idPlat::Restore
   4136 ===============
   4137 */
   4138 void idPlat::Restore( idRestoreGame *savefile ) {
   4139 	savefile->ReadClipModel( trigger );
   4140 	savefile->ReadVec3( localTriggerOrigin );
   4141 	savefile->ReadMat3( localTriggerAxis );
   4142 }
   4143 
   4144 /*
   4145 ===============
   4146 idPlat::Spawn
   4147 ===============
   4148 */
   4149 void idPlat::Spawn() {
   4150 	float	lip;
   4151 	float	height;
   4152 	float	time;
   4153 	float	speed;
   4154 	float	accel;
   4155 	float	decel;
   4156 	bool	noTouch;
   4157 
   4158 	spawnArgs.GetFloat( "speed", "100", speed );
   4159 	spawnArgs.GetFloat( "damage", "0", damage );
   4160 	spawnArgs.GetFloat( "wait", "1", wait );
   4161 	spawnArgs.GetFloat( "lip", "8", lip );
   4162 	spawnArgs.GetFloat( "accel_time", "0.25", accel );
   4163 	spawnArgs.GetFloat( "decel_time", "0.25", decel );
   4164 
   4165 	// create second position
   4166 	if ( !spawnArgs.GetFloat( "height", "0", height ) ) {
   4167 		height = ( GetPhysics()->GetBounds()[1][2] - GetPhysics()->GetBounds()[0][2] ) - lip;
   4168 	}
   4169 
   4170 	spawnArgs.GetBool( "no_touch", "0", noTouch );
   4171 
   4172 	// pos1 is the rest (bottom) position, pos2 is the top
   4173 	pos2 = GetPhysics()->GetOrigin();
   4174 	pos1 = pos2;
   4175 	pos1[2] -= height;
   4176 
   4177 	if ( spawnArgs.GetFloat( "time", "1", time ) ) {
   4178 		InitTime( pos1, pos2, time, accel, decel );
   4179 	} else {
   4180 		InitSpeed( pos1, pos2, speed, accel, decel );
   4181 	}
   4182 
   4183 	SetMoverState( MOVER_POS1, gameLocal.slow.time );
   4184 	UpdateVisuals();
   4185 
   4186 	// spawn the trigger if one hasn't been custom made
   4187 	if ( !noTouch ) {
   4188 		// spawn trigger
   4189 		SpawnPlatTrigger( pos1 );
   4190 	}
   4191 }
   4192 
   4193 /*
   4194 ================
   4195 idPlat::RunPhysics_NoBlocking
   4196 ================
   4197 */
   4198 void idPlat::RunPhysics_NoBlocking() {
   4199 	int			i, startTime, endTime;
   4200 	idEntity *	part = NULL, *blockedPart = NULL, *blockingEntity = NULL;
   4201 	trace_t		results;
   4202 	bool		moved;
   4203 
   4204 	// don't run physics if not enabled
   4205 	if ( !( thinkFlags & TH_PHYSICS ) ) {
   4206 		// however do update any animation controllers
   4207 		if ( UpdateAnimationControllers() ) {
   4208 			BecomeActive( TH_ANIMATE );
   4209 		}
   4210 		return;
   4211 	}
   4212 
   4213 	/*
   4214 	// if this entity is a team slave don't do anything because the team master will handle everything
   4215 	if ( teamMaster && teamMaster != this ) {
   4216 		return false;
   4217 	}
   4218 	*/
   4219 	startTime = gameLocal.previousTime;
   4220 	endTime = gameLocal.time;
   4221 
   4222 	gameLocal.push.InitSavingPushedEntityPositions();
   4223 	blockedPart = NULL;
   4224 
   4225 	// save the physics state of the whole team and disable the team for collision detection
   4226 	for ( part = this; part != NULL; part = part->GetTeamChain() ) {
   4227 		if ( part->GetPhysics() ) {
   4228 			if ( !part->fl.solidForTeam ) {
   4229 				part->GetPhysics()->DisableClip();
   4230 			}
   4231 			part->GetPhysics()->SaveState();
   4232 		}
   4233 	}
   4234 
   4235 
   4236 	// move the whole team
   4237 	for ( part = this; part != NULL; part = part->GetTeamChain() ) {
   4238 
   4239 		if ( part->GetPhysics() ) {
   4240 
   4241 			// run physics
   4242 			moved = part->GetPhysics()->Evaluate( endTime - startTime, endTime );
   4243 
   4244 			// check if the object is blocked
   4245 			blockingEntity = part->GetPhysics()->GetBlockingEntity();
   4246 			if ( blockingEntity ) {
   4247 				blockedPart = part;
   4248 				break;
   4249 			}
   4250 
   4251 			// if moved or forced to update the visual position and orientation from the physics
   4252 			if ( moved || part->fl.forcePhysicsUpdate ) {
   4253 				part->UpdateVisuals();
   4254 			}
   4255 
   4256 			// update any animation controllers here so an entity bound
   4257 			// to a joint of this entity gets the correct position
   4258 			if ( part->UpdateAnimationControllers() ) {
   4259 				part->BecomeActive( TH_ANIMATE );
   4260 			}
   4261 		}
   4262 	}
   4263 
   4264 	// enable the whole team for collision detection
   4265 	for ( part = this; part != NULL; part = part->GetTeamChain() ) {
   4266 		if ( part->GetPhysics() ) {
   4267 			if ( !part->fl.solidForTeam ) {
   4268 				part->GetPhysics()->EnableClip();
   4269 			}
   4270 		}
   4271 	}
   4272 
   4273 	// set pushed
   4274 	for ( i = 0; i < gameLocal.push.GetNumPushedEntities(); i++ ) {
   4275 		idEntity *ent = gameLocal.push.GetPushedEntity( i );
   4276 		ent->GetPhysics()->SetPushed( endTime - startTime );
   4277 	}
   4278 }
   4279 
   4280 /*
   4281 ================
   4282 idPlat::ClientThink
   4283 ================
   4284 */
   4285 void idPlat::ClientThink( const int curTime, const float fraction, const bool predict ) {
   4286 	InterpolatePhysicsOnly( fraction );
   4287 
   4288 	Present();
   4289 
   4290 
   4291 
   4292 	//idMover_Binary::ClientThink( curTime, fraction, predict );
   4293 
   4294 	/*
   4295 	idVec3 masterOrigin;
   4296 	idMat3 masterAxis;
   4297 
   4298 	// Dont bother with blocking entities on clients.. host tells us our move state.
   4299 	RunPhysics_NoBlocking();
   4300 
   4301 	Present();
   4302 	
   4303 	if ( thinkFlags & TH_PHYSICS ) {
   4304 		// update trigger position
   4305 		if ( GetMasterPosition( masterOrigin, masterAxis ) ) {
   4306 			if ( trigger ) {
   4307 				trigger->Link( gameLocal.clip, this, 0, masterOrigin + localTriggerOrigin * masterAxis, localTriggerAxis * masterAxis );
   4308 			}
   4309 		}
   4310 	}
   4311 	*/
   4312 }
   4313 
   4314 /*
   4315 ================
   4316 idPlat::Think
   4317 ================
   4318 */
   4319 void idPlat::Think() {
   4320 	idVec3 masterOrigin;
   4321 	idMat3 masterAxis;
   4322 
   4323 	idMover_Binary::Think();
   4324 
   4325 	if ( thinkFlags & TH_PHYSICS ) {
   4326 		// update trigger position
   4327 		if ( GetMasterPosition( masterOrigin, masterAxis ) ) {
   4328 			if ( trigger ) {
   4329 				trigger->Link( gameLocal.clip, this, 0, masterOrigin + localTriggerOrigin * masterAxis, localTriggerAxis * masterAxis );
   4330 			}
   4331 		}
   4332 	}
   4333 }
   4334 
   4335 /*
   4336 ================
   4337 idPlat::PreBind
   4338 ================
   4339 */
   4340 void idPlat::PreBind() {
   4341 	idMover_Binary::PreBind();
   4342 }
   4343 
   4344 /*
   4345 ================
   4346 idPlat::PostBind
   4347 ================
   4348 */
   4349 void idPlat::PostBind() {
   4350 	idMover_Binary::PostBind();
   4351 	GetLocalTriggerPosition( trigger );
   4352 }
   4353 
   4354 /*
   4355 ================
   4356 idPlat::GetLocalTriggerPosition
   4357 ================
   4358 */
   4359 void idPlat::GetLocalTriggerPosition( const idClipModel *trigger ) {
   4360 	idVec3 origin;
   4361 	idMat3 axis;
   4362 
   4363 	if ( !trigger ) {
   4364 		return;
   4365 	}
   4366 
   4367 	GetMasterPosition( origin, axis );
   4368 	localTriggerOrigin = ( trigger->GetOrigin() - origin ) * axis.Transpose();
   4369 	localTriggerAxis = trigger->GetAxis() * axis.Transpose();
   4370 }
   4371 
   4372 /*
   4373 ==============
   4374 idPlat::SpawnPlatTrigger
   4375 ===============
   4376 */
   4377 void idPlat::SpawnPlatTrigger( idVec3 &pos ) {
   4378 	idBounds		bounds;
   4379 	idVec3			tmin;
   4380 	idVec3			tmax;
   4381 
   4382 	// the middle trigger will be a thin trigger just
   4383 	// above the starting position
   4384 
   4385 	bounds = GetPhysics()->GetBounds();
   4386 
   4387 	tmin[0] = bounds[0][0] + 33;
   4388 	tmin[1] = bounds[0][1] + 33;
   4389 	tmin[2] = bounds[0][2];
   4390 
   4391 	tmax[0] = bounds[1][0] - 33;
   4392 	tmax[1] = bounds[1][1] - 33;
   4393 	tmax[2] = bounds[1][2] + 8;
   4394 
   4395 	if ( tmax[0] <= tmin[0] ) {
   4396 		tmin[0] = ( bounds[0][0] + bounds[1][0] ) * 0.5f;
   4397 		tmax[0] = tmin[0] + 1;
   4398 	}
   4399 	if ( tmax[1] <= tmin[1] ) {
   4400 		tmin[1] = ( bounds[0][1] + bounds[1][1] ) * 0.5f;
   4401 		tmax[1] = tmin[1] + 1;
   4402 	}
   4403 	
   4404 	trigger = new (TAG_PHYSICS_CLIP_MOVER) idClipModel( idTraceModel( idBounds( tmin, tmax ) ) );
   4405 	trigger->Link( gameLocal.clip, this, 255, GetPhysics()->GetOrigin(), mat3_identity );
   4406 	trigger->SetContents( CONTENTS_TRIGGER );
   4407 }
   4408 
   4409 /*
   4410 ==============
   4411 idPlat::Event_Touch
   4412 ===============
   4413 */
   4414 void idPlat::Event_Touch( idEntity *other, trace_t *trace ) {
   4415 	if ( common->IsClient() ) {
   4416 		return;
   4417 	}
   4418 	
   4419 	if ( !other->IsType( idPlayer::Type ) ) {
   4420 		return;
   4421 	}
   4422 
   4423 	if ( ( GetMoverState() == MOVER_POS1 ) && trigger && ( trace->c.id == trigger->GetId() ) && ( other->health > 0 ) ) {
   4424 		Use_BinaryMover( other );
   4425 	}
   4426 }
   4427 
   4428 /*
   4429 ================
   4430 idPlat::Event_TeamBlocked
   4431 ================
   4432 */
   4433 void idPlat::Event_TeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ) {
   4434 	// reverse direction
   4435 	Use_BinaryMover( activatedBy.GetEntity() );
   4436 }
   4437 
   4438 /*
   4439 ===============
   4440 idPlat::Event_PartBlocked
   4441 ===============
   4442 */
   4443 void idPlat::Event_PartBlocked( idEntity *blockingEntity ) {
   4444 	if ( damage > 0.0f ) {
   4445 		blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", damage, INVALID_JOINT );
   4446 	}
   4447 }
   4448 
   4449 
   4450 /*
   4451 ===============================================================================
   4452 
   4453 idMover_Periodic
   4454 
   4455 ===============================================================================
   4456 */
   4457 
   4458 CLASS_DECLARATION( idEntity, idMover_Periodic )
   4459 	EVENT( EV_TeamBlocked,		idMover_Periodic::Event_TeamBlocked )
   4460 	EVENT( EV_PartBlocked,		idMover_Periodic::Event_PartBlocked )
   4461 END_CLASS
   4462 
   4463 /*
   4464 ===============
   4465 idMover_Periodic::idMover_Periodic
   4466 ===============
   4467 */
   4468 idMover_Periodic::idMover_Periodic() {
   4469 	damage = 0.0f;
   4470 	fl.neverDormant	= false;
   4471 }
   4472 
   4473 /*
   4474 ===============
   4475 idMover_Periodic::Spawn
   4476 ===============
   4477 */
   4478 void idMover_Periodic::Spawn() {
   4479 	spawnArgs.GetFloat( "damage", "0", damage );
   4480 	if ( !spawnArgs.GetBool( "solid", "1" ) ) {
   4481 		GetPhysics()->SetContents( 0 );
   4482 	}
   4483 }
   4484 
   4485 /*
   4486 ===============
   4487 idMover_Periodic::Save
   4488 ===============
   4489 */
   4490 void idMover_Periodic::Save( idSaveGame *savefile ) const {
   4491 	savefile->WriteFloat( damage );
   4492 	savefile->WriteStaticObject( physicsObj );
   4493 }
   4494 
   4495 /*
   4496 ===============
   4497 idMover_Periodic::Restore
   4498 ===============
   4499 */
   4500 void idMover_Periodic::Restore( idRestoreGame *savefile ) {
   4501 	savefile->ReadFloat( damage );
   4502 	savefile->ReadStaticObject( physicsObj );
   4503 	RestorePhysics( &physicsObj );
   4504 }
   4505 
   4506 
   4507 /*
   4508 ================
   4509 idMover_Periodic::Think
   4510 ================
   4511 */
   4512 void idMover_Periodic::Think() {
   4513 	// if we are completely closed off from the player, don't do anything at all
   4514 	if ( CheckDormant() ) {
   4515 		return;
   4516 	}
   4517 
   4518 	RunPhysics();
   4519 	Present();
   4520 }
   4521 
   4522 /*
   4523 ===============
   4524 idMover_Periodic::Event_TeamBlocked
   4525 ===============
   4526 */
   4527 void idMover_Periodic::Event_TeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ) {
   4528 }
   4529 
   4530 /*
   4531 ===============
   4532 idMover_Periodic::Event_PartBlocked
   4533 ===============
   4534 */
   4535 void idMover_Periodic::Event_PartBlocked( idEntity *blockingEntity ) {
   4536 	if ( damage > 0.0f ) {
   4537 		blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", damage, INVALID_JOINT );
   4538 	}
   4539 }
   4540 
   4541 /*
   4542 ================
   4543 idMover_Periodic::WriteToSnapshot
   4544 ================
   4545 */
   4546 void idMover_Periodic::WriteToSnapshot( idBitMsg &msg ) const {
   4547 	physicsObj.WriteToSnapshot( msg );
   4548 	WriteBindToSnapshot( msg );
   4549 }
   4550 
   4551 /*
   4552 ================
   4553 idMover_Periodic::ReadFromSnapshot
   4554 ================
   4555 */
   4556 void idMover_Periodic::ReadFromSnapshot( const idBitMsg &msg ) {
   4557 	physicsObj.ReadFromSnapshot( msg );
   4558 	ReadBindFromSnapshot( msg );
   4559 
   4560 	if ( msg.HasChanged() ) {
   4561 		UpdateVisuals();
   4562 	}
   4563 }
   4564 
   4565 
   4566 /*
   4567 ===============================================================================
   4568 
   4569 idRotater
   4570 
   4571 ===============================================================================
   4572 */
   4573 
   4574 CLASS_DECLARATION( idMover_Periodic, idRotater )
   4575 	EVENT( EV_Activate,			idRotater::Event_Activate )
   4576 END_CLASS
   4577 
   4578 /*
   4579 ===============
   4580 idRotater::idRotater
   4581 ===============
   4582 */
   4583 idRotater::idRotater() {
   4584 	activatedBy = this;
   4585 }
   4586 
   4587 /*
   4588 ===============
   4589 idRotater::Spawn
   4590 ===============
   4591 */
   4592 void idRotater::Spawn() {
   4593 	physicsObj.SetSelf( this );
   4594 	physicsObj.SetClipModel( new (TAG_PHYSICS_CLIP_MOVER) idClipModel( GetPhysics()->GetClipModel() ), 1.0f );
   4595 	physicsObj.SetOrigin( GetPhysics()->GetOrigin() );
   4596 	physicsObj.SetAxis( GetPhysics()->GetAxis() );
   4597 	physicsObj.SetClipMask( MASK_SOLID );
   4598 	if ( !spawnArgs.GetBool( "nopush" ) ) {
   4599 		physicsObj.SetPusher( 0 );
   4600 	}
   4601 	physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, gameLocal.slow.time, 0, GetPhysics()->GetOrigin(), vec3_origin, vec3_origin );
   4602 	physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_LINEAR|EXTRAPOLATION_NOSTOP), gameLocal.slow.time, 0, GetPhysics()->GetAxis().ToAngles(), ang_zero, ang_zero );
   4603 	SetPhysics( &physicsObj );
   4604 
   4605 	if ( spawnArgs.GetBool( "start_on" ) ) {
   4606 		ProcessEvent( &EV_Activate, this );
   4607 	}
   4608 }
   4609 
   4610 /*
   4611 ===============
   4612 idRotater::Save
   4613 ===============
   4614 */
   4615 void idRotater::Save( idSaveGame *savefile ) const {
   4616 	activatedBy.Save( savefile );
   4617 }
   4618 
   4619 /*
   4620 ===============
   4621 idRotater::Restore
   4622 ===============
   4623 */
   4624 void idRotater::Restore( idRestoreGame *savefile ) {
   4625 	activatedBy.Restore( savefile );
   4626 }
   4627 
   4628 /*
   4629 ===============
   4630 idRotater::Event_Activate
   4631 ===============
   4632 */
   4633 void idRotater::Event_Activate( idEntity *activator ) {
   4634 	float		speed;
   4635 	bool		x_axis;
   4636 	bool		y_axis;
   4637 	idAngles	delta;
   4638 
   4639 	activatedBy = activator;
   4640 
   4641 	delta.Zero();
   4642 
   4643 	if ( !spawnArgs.GetBool( "rotate" ) ) {
   4644 		spawnArgs.Set( "rotate", "1" );
   4645 		spawnArgs.GetFloat( "speed", "100", speed );
   4646 		spawnArgs.GetBool( "x_axis", "0", x_axis );
   4647 		spawnArgs.GetBool( "y_axis", "0", y_axis );
   4648 		
   4649 		// set the axis of rotation
   4650 		if ( x_axis ) {
   4651 			delta[2] = speed;
   4652 		} else if ( y_axis ) {
   4653 			delta[0] = speed;
   4654 		} else {
   4655 			delta[1] = speed;
   4656 		}
   4657 	} else {
   4658 		spawnArgs.Set( "rotate", "0" );
   4659 	}
   4660 
   4661 	physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_LINEAR|EXTRAPOLATION_NOSTOP), gameLocal.slow.time, 0, physicsObj.GetAxis().ToAngles(), delta, ang_zero );
   4662 }
   4663 
   4664 
   4665 /*
   4666 ===============================================================================
   4667 
   4668 idBobber
   4669 
   4670 ===============================================================================
   4671 */
   4672 
   4673 CLASS_DECLARATION( idMover_Periodic, idBobber )
   4674 END_CLASS
   4675 
   4676 /*
   4677 ===============
   4678 idBobber::idBobber
   4679 ===============
   4680 */
   4681 idBobber::idBobber() {
   4682 }
   4683 
   4684 /*
   4685 ===============
   4686 idBobber::Spawn
   4687 ===============
   4688 */
   4689 void idBobber::Spawn() {
   4690 	float	speed;
   4691 	float	height;
   4692 	float	phase;
   4693 	bool	x_axis;
   4694 	bool	y_axis;
   4695 	idVec3	delta;
   4696 
   4697 	spawnArgs.GetFloat( "speed", "4", speed );
   4698 	spawnArgs.GetFloat( "height", "32", height );
   4699 	spawnArgs.GetFloat( "phase", "0", phase );
   4700 	spawnArgs.GetBool( "x_axis", "0", x_axis );
   4701 	spawnArgs.GetBool( "y_axis", "0", y_axis );
   4702 
   4703 	// set the axis of bobbing
   4704 	delta = vec3_origin;
   4705 	if ( x_axis ) {
   4706 		delta[ 0 ] = height;
   4707 	} else if ( y_axis ) {
   4708 		delta[ 1 ] = height;
   4709 	} else {
   4710 		delta[ 2 ] = height;
   4711 	}
   4712 
   4713 	physicsObj.SetSelf( this );
   4714 	physicsObj.SetClipModel( new (TAG_PHYSICS_CLIP_MOVER) idClipModel( GetPhysics()->GetClipModel() ), 1.0f );
   4715 	physicsObj.SetOrigin( GetPhysics()->GetOrigin() );
   4716 	physicsObj.SetAxis( GetPhysics()->GetAxis() );
   4717 	physicsObj.SetClipMask( MASK_SOLID );
   4718 	if ( !spawnArgs.GetBool( "nopush" ) ) {
   4719 		physicsObj.SetPusher( 0 );
   4720 	}
   4721 	physicsObj.SetLinearExtrapolation( extrapolation_t(EXTRAPOLATION_DECELSINE|EXTRAPOLATION_NOSTOP), phase * 1000, speed * 500, GetPhysics()->GetOrigin(), delta * 2.0f, vec3_origin );
   4722 	SetPhysics( &physicsObj );
   4723 }
   4724 
   4725 
   4726 /*
   4727 ===============================================================================
   4728 
   4729 idPendulum
   4730 
   4731 ===============================================================================
   4732 */
   4733 
   4734 CLASS_DECLARATION( idMover_Periodic, idPendulum )
   4735 END_CLASS
   4736 
   4737 /*
   4738 ===============
   4739 idPendulum::idPendulum
   4740 ===============
   4741 */
   4742 idPendulum::idPendulum() {
   4743 }
   4744 
   4745 /*
   4746 ===============
   4747 idPendulum::Spawn
   4748 ===============
   4749 */
   4750 void idPendulum::Spawn() {
   4751 	float	speed;
   4752 	float	freq;
   4753 	float	length;
   4754 	float	phase;
   4755 
   4756 	spawnArgs.GetFloat( "speed", "30", speed );
   4757 	spawnArgs.GetFloat( "phase", "0", phase );
   4758 
   4759 	if ( spawnArgs.GetFloat( "freq", "", freq ) ) {
   4760 		if ( freq <= 0.0f ) {
   4761 			gameLocal.Error( "Invalid frequency on entity '%s'", GetName() );
   4762 		}
   4763 	} else {
   4764 		// find pendulum length
   4765 		length = idMath::Fabs( GetPhysics()->GetBounds()[0][2] );
   4766 		if ( length < 8 ) {
   4767 			length = 8;
   4768 		}
   4769 
   4770 		freq = 1 / ( idMath::TWO_PI ) * idMath::Sqrt( g_gravity.GetFloat() / ( 3 * length ) );
   4771 	}
   4772 
   4773 	physicsObj.SetSelf( this );
   4774 	physicsObj.SetClipModel( new (TAG_PHYSICS_CLIP_MOVER) idClipModel( GetPhysics()->GetClipModel() ), 1.0f );
   4775 	physicsObj.SetOrigin( GetPhysics()->GetOrigin() );
   4776 	physicsObj.SetAxis( GetPhysics()->GetAxis() );
   4777 	physicsObj.SetClipMask( MASK_SOLID );
   4778 	if ( !spawnArgs.GetBool( "nopush" ) ) {
   4779 		physicsObj.SetPusher( 0 );
   4780 	}
   4781 	physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, GetPhysics()->GetOrigin(), vec3_origin, vec3_origin );
   4782 	physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_DECELSINE|EXTRAPOLATION_NOSTOP), phase * 1000, 500/freq, GetPhysics()->GetAxis().ToAngles(), idAngles( 0, 0, speed * 2.0f ), ang_zero );
   4783 	SetPhysics( &physicsObj );
   4784 }
   4785 
   4786 
   4787 /*
   4788 ===============================================================================
   4789 
   4790 idBobber
   4791 
   4792 ===============================================================================
   4793 */
   4794 
   4795 CLASS_DECLARATION( idMover_Periodic, idRiser )
   4796 EVENT( EV_Activate,				idRiser::Event_Activate )
   4797 END_CLASS
   4798 
   4799 /*
   4800 ===============
   4801 idRiser::idRiser
   4802 ===============
   4803 */
   4804 idRiser::idRiser() {
   4805 }
   4806 
   4807 /*
   4808 ===============
   4809 idRiser::Spawn
   4810 ===============
   4811 */
   4812 void idRiser::Spawn() {
   4813 	physicsObj.SetSelf( this );
   4814 	physicsObj.SetClipModel( new (TAG_PHYSICS_CLIP_MOVER) idClipModel( GetPhysics()->GetClipModel() ), 1.0f );
   4815 	physicsObj.SetOrigin( GetPhysics()->GetOrigin() );
   4816 	physicsObj.SetAxis( GetPhysics()->GetAxis() );
   4817 
   4818 	physicsObj.SetClipMask( MASK_SOLID );
   4819 	if ( !spawnArgs.GetBool( "solid", "1" ) ) {
   4820 		physicsObj.SetContents( 0 );
   4821 	}
   4822 	if ( !spawnArgs.GetBool( "nopush" ) ) {
   4823 		physicsObj.SetPusher( 0 );
   4824 	}
   4825 	physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, GetPhysics()->GetOrigin(), vec3_origin, vec3_origin );
   4826 	SetPhysics( &physicsObj );
   4827 }
   4828 
   4829 /*
   4830 ================
   4831 idRiser::Event_Activate
   4832 ================
   4833 */
   4834 void idRiser::Event_Activate( idEntity *activator ) {
   4835 
   4836 	if ( !IsHidden() && spawnArgs.GetBool("hide")  ) {
   4837 		Hide();
   4838 	} else {
   4839 		Show();
   4840 		float	time;
   4841 		float	height;
   4842 		idVec3	delta;
   4843 
   4844 		spawnArgs.GetFloat( "time", "4", time );
   4845 		spawnArgs.GetFloat( "height", "32", height );
   4846 
   4847 		delta = vec3_origin;
   4848 		delta[ 2 ] = height;
   4849 
   4850 		physicsObj.SetLinearExtrapolation( EXTRAPOLATION_LINEAR, gameLocal.slow.time, time * 1000, physicsObj.GetOrigin(), delta, vec3_origin );
   4851 	}
   4852 }