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