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