codemp/game/g_mover.c

Go to the documentation of this file.
00001 // Copyright (C) 1999-2000 Id Software, Inc.
00002 //
00003 
00004 #include "g_local.h"
00005 
00006 
00007 
00008 /*
00009 ===============================================================================
00010 
00011 PUSHMOVE
00012 
00013 ===============================================================================
00014 */
00015 
00016 void MatchTeam( gentity_t *teamLeader, int moverState, int time );
00017 
00018 typedef struct {
00019         gentity_t       *ent;
00020         vec3_t  origin;
00021         vec3_t  angles;
00022         float   deltayaw;
00023 } pushed_t;
00024 pushed_t        pushed[MAX_GENTITIES], *pushed_p;
00025 
00026 #define MOVER_START_ON          1
00027 #define MOVER_FORCE_ACTIVATE    2
00028 #define MOVER_CRUSHER           4
00029 #define MOVER_TOGGLE            8
00030 #define MOVER_LOCKED            16
00031 #define MOVER_GOODIE            32
00032 #define MOVER_PLAYER_USE        64
00033 #define MOVER_INACTIVE          128
00034 
00035 int     BMS_START = 0;
00036 int     BMS_MID = 1;
00037 int     BMS_END = 2;
00038 
00039 /*
00040 -------------------------
00041 G_PlayDoorLoopSound
00042 -------------------------
00043 */
00044 
00045 void G_PlayDoorLoopSound( gentity_t *ent )
00046 {
00047         if (!ent->soundSet || !ent->soundSet[0])
00048         {
00049                 return;
00050         }
00051 
00052         ent->s.soundSetIndex = G_SoundSetIndex(ent->soundSet);
00053         ent->s.loopIsSoundset = qtrue;
00054         ent->s.loopSound = BMS_MID;
00055         /*
00056         ent->s.soundSetIndex = G_SoundSetIndex(ent->soundSet);
00057         ent->loopingOnClient = qtrue;
00058         G_AddEvent(ent, EV_PLAYDOORLOOPSOUND, 0);
00059         */
00060 }
00061 
00062 /*
00063 -------------------------
00064 G_PlayDoorSound
00065 -------------------------
00066 */
00067 
00068 void G_PlayDoorSound( gentity_t *ent, int type )
00069 {
00070         if (!ent->soundSet || !ent->soundSet[0])
00071         {
00072                 return;
00073         }
00074 
00075         ent->s.soundSetIndex = G_SoundSetIndex(ent->soundSet);
00076 
00077         G_AddEvent(ent, EV_PLAYDOORSOUND, type);
00078 }
00079 
00080 /*
00081 ============
00082 G_TestEntityPosition
00083 
00084 ============
00085 */
00086 gentity_t       *G_TestEntityPosition( gentity_t *ent ) {
00087         trace_t tr;
00088         int             mask;
00089 
00090         if ( ent->clipmask ) {
00091                 mask = ent->clipmask;
00092         } else {
00093                 mask = MASK_SOLID;
00094         }
00095         if ( ent->client ) {
00096                 vec3_t vMax;
00097                 VectorCopy(ent->r.maxs, vMax);
00098                 if (vMax[2] < 1)
00099                 {
00100                         vMax[2] = 1;
00101                 }
00102                 trap_Trace( &tr, ent->client->ps.origin, ent->r.mins, vMax, ent->client->ps.origin, ent->s.number, mask );
00103         } else {
00104                 trap_Trace( &tr, ent->s.pos.trBase, ent->r.mins, ent->r.maxs, ent->s.pos.trBase, ent->s.number, mask );
00105         }
00106         
00107         if (tr.startsolid)
00108                 return &g_entities[ tr.entityNum ];
00109                 
00110         return NULL;
00111 }
00112 
00113 /*
00114 ================
00115 G_CreateRotationMatrix
00116 ================
00117 */
00118 void G_CreateRotationMatrix(vec3_t angles, vec3_t matrix[3]) {
00119         AngleVectors(angles, matrix[0], matrix[1], matrix[2]);
00120         VectorInverse(matrix[1]);
00121 }
00122 
00123 /*
00124 ================
00125 G_TransposeMatrix
00126 ================
00127 */
00128 void G_TransposeMatrix(vec3_t matrix[3], vec3_t transpose[3]) {
00129         int i, j;
00130         for (i = 0; i < 3; i++) {
00131                 for (j = 0; j < 3; j++) {
00132                         transpose[i][j] = matrix[j][i];
00133                 }
00134         }
00135 }
00136 
00137 /*
00138 ================
00139 G_RotatePoint
00140 ================
00141 */
00142 void G_RotatePoint(vec3_t point, vec3_t matrix[3]) {
00143         vec3_t tvec;
00144 
00145         VectorCopy(point, tvec);
00146         point[0] = DotProduct(matrix[0], tvec);
00147         point[1] = DotProduct(matrix[1], tvec);
00148         point[2] = DotProduct(matrix[2], tvec);
00149 }
00150 
00151 /*
00152 ==================
00153 G_TryPushingEntity
00154 
00155 Returns qfalse if the move is blocked
00156 ==================
00157 */
00158 qboolean        G_TryPushingEntity( gentity_t *check, gentity_t *pusher, vec3_t move, vec3_t amove ) {
00159         vec3_t          matrix[3], transpose[3];
00160         vec3_t          org, org2, move2;
00161         gentity_t       *block;
00162 
00163         //This was only serverside not to mention it was never set.
00164         /*
00165         // EF_MOVER_STOP will just stop when contacting another entity
00166         // instead of pushing it, but entities can still ride on top of it
00167         if ( ( pusher->s.eFlags & EF_MOVER_STOP ) && 
00168                 check->s.groundEntityNum != pusher->s.number ) {
00169                 return qfalse;
00170         }
00171         */
00172         if ( pusher->s.apos.trType != TR_STATIONARY//rotating
00173                 && (pusher->spawnflags&16) //IMPACT
00174                 && Q_stricmp( "func_rotating", pusher->classname ) == 0 )
00175         {//just blow the fuck out of them
00176                 G_Damage( check, pusher, pusher, NULL, NULL, pusher->damage, DAMAGE_NO_KNOCKBACK, MOD_CRUSH );
00177                 return qtrue;
00178         }
00179 
00180         // save off the old position
00181         if (pushed_p > &pushed[MAX_GENTITIES]) {
00182                 G_Error( "pushed_p > &pushed[MAX_GENTITIES]" );
00183         }
00184         pushed_p->ent = check;
00185         VectorCopy (check->s.pos.trBase, pushed_p->origin);
00186         VectorCopy (check->s.apos.trBase, pushed_p->angles);
00187         if ( check->client ) {
00188                 pushed_p->deltayaw = check->client->ps.delta_angles[YAW];
00189                 VectorCopy (check->client->ps.origin, pushed_p->origin);
00190         }
00191         pushed_p++;
00192 
00193         // try moving the contacted entity 
00194         // figure movement due to the pusher's amove
00195         G_CreateRotationMatrix( amove, transpose );
00196         G_TransposeMatrix( transpose, matrix );
00197         if ( check->client ) {
00198                 VectorSubtract (check->client->ps.origin, pusher->r.currentOrigin, org);
00199         }
00200         else {
00201                 VectorSubtract (check->s.pos.trBase, pusher->r.currentOrigin, org);
00202         }
00203         VectorCopy( org, org2 );
00204         G_RotatePoint( org2, matrix );
00205         VectorSubtract (org2, org, move2);
00206         // add movement
00207         VectorAdd (check->s.pos.trBase, move, check->s.pos.trBase);
00208         VectorAdd (check->s.pos.trBase, move2, check->s.pos.trBase);
00209         if ( check->client ) {
00210                 VectorAdd (check->client->ps.origin, move, check->client->ps.origin);
00211                 VectorAdd (check->client->ps.origin, move2, check->client->ps.origin);
00212                 // make sure the client's view rotates when on a rotating mover
00213                 check->client->ps.delta_angles[YAW] += ANGLE2SHORT(amove[YAW]);
00214         }
00215 
00216         // may have pushed them off an edge
00217         if ( check->s.groundEntityNum != pusher->s.number ) {
00218                 check->s.groundEntityNum = ENTITYNUM_NONE;//-1;
00219         }
00220 
00221         block = G_TestEntityPosition( check );
00222         if (!block) {
00223                 // pushed ok
00224                 if ( check->client ) {
00225                         VectorCopy( check->client->ps.origin, check->r.currentOrigin );
00226                 } else {
00227                         VectorCopy( check->s.pos.trBase, check->r.currentOrigin );
00228                 }
00229                 trap_LinkEntity (check);
00230                 return qtrue;
00231         }
00232 
00233         if (check->takedamage && !check->client && check->s.weapon && check->r.ownerNum < MAX_CLIENTS &&
00234                 check->health < 500)
00235         {
00236                 if (check->health > 0)
00237                 {
00238                         G_Damage(check, pusher, pusher, vec3_origin, check->r.currentOrigin, 999, 0, MOD_UNKNOWN);
00239                 }
00240                 return qfalse;
00241         }
00242         // if it is ok to leave in the old position, do it
00243         // this is only relevent for riding entities, not pushed
00244         // Sliding trapdoors can cause this.
00245         VectorCopy( (pushed_p-1)->origin, check->s.pos.trBase);
00246         if ( check->client ) {
00247                 VectorCopy( (pushed_p-1)->origin, check->client->ps.origin);
00248         }
00249         VectorCopy( (pushed_p-1)->angles, check->s.apos.trBase );
00250         block = G_TestEntityPosition (check);
00251         if ( !block ) {
00252                 check->s.groundEntityNum = -1;
00253                 pushed_p--;
00254                 return qtrue;
00255         }
00256 
00257         // blocked
00258         return qfalse;
00259 }
00260 
00261 
00262 void G_ExplodeMissile( gentity_t *ent );
00263 
00264 /*
00265 ============
00266 G_MoverPush
00267 
00268 Objects need to be moved back on a failed push,
00269 otherwise riders would continue to slide.
00270 If qfalse is returned, *obstacle will be the blocking entity
00271 ============
00272 */
00273 qboolean G_MoverPush( gentity_t *pusher, vec3_t move, vec3_t amove, gentity_t **obstacle ) {
00274         int                     i, e;
00275         gentity_t       *check;
00276         vec3_t          mins, maxs;
00277         pushed_t        *p;
00278         int                     entityList[MAX_GENTITIES];
00279         int                     listedEntities;
00280         vec3_t          totalMins, totalMaxs;
00281 
00282         *obstacle = NULL;
00283 
00284 
00285         // mins/maxs are the bounds at the destination
00286         // totalMins / totalMaxs are the bounds for the entire move
00287         if ( pusher->r.currentAngles[0] || pusher->r.currentAngles[1] || pusher->r.currentAngles[2]
00288                 || amove[0] || amove[1] || amove[2] ) {
00289                 float           radius;
00290 
00291                 radius = RadiusFromBounds( pusher->r.mins, pusher->r.maxs );
00292                 for ( i = 0 ; i < 3 ; i++ ) {
00293                         mins[i] = pusher->r.currentOrigin[i] + move[i] - radius;
00294                         maxs[i] = pusher->r.currentOrigin[i] + move[i] + radius;
00295                         totalMins[i] = mins[i] - move[i];
00296                         totalMaxs[i] = maxs[i] - move[i];
00297                 }
00298         } else {
00299                 for (i=0 ; i<3 ; i++) {
00300                         mins[i] = pusher->r.absmin[i] + move[i];
00301                         maxs[i] = pusher->r.absmax[i] + move[i];
00302                 }
00303 
00304                 VectorCopy( pusher->r.absmin, totalMins );
00305                 VectorCopy( pusher->r.absmax, totalMaxs );
00306                 for (i=0 ; i<3 ; i++) {
00307                         if ( move[i] > 0 ) {
00308                                 totalMaxs[i] += move[i];
00309                         } else {
00310                                 totalMins[i] += move[i];
00311                         }
00312                 }
00313         }
00314 
00315         // unlink the pusher so we don't get it in the entityList
00316         trap_UnlinkEntity( pusher );
00317 
00318         listedEntities = trap_EntitiesInBox( totalMins, totalMaxs, entityList, MAX_GENTITIES );
00319 
00320         // move the pusher to it's final position
00321         VectorAdd( pusher->r.currentOrigin, move, pusher->r.currentOrigin );
00322         VectorAdd( pusher->r.currentAngles, amove, pusher->r.currentAngles );
00323         trap_LinkEntity( pusher );
00324 
00325         // see if any solid entities are inside the final position
00326         for ( e = 0 ; e < listedEntities ; e++ ) {
00327                 check = &g_entities[ entityList[ e ] ];
00328 
00329                 // only push items and players
00330                 if ( /*check->s.eType != ET_ITEM &&*/ check->s.eType != ET_PLAYER && check->s.eType != ET_NPC && !check->physicsObject ) {
00331                         continue;
00332                 }
00333 
00334                 // if the entity is standing on the pusher, it will definitely be moved
00335                 if ( check->s.groundEntityNum != pusher->s.number ) {
00336                         // see if the ent needs to be tested
00337                         if ( check->r.absmin[0] >= maxs[0]
00338                         || check->r.absmin[1] >= maxs[1]
00339                         || check->r.absmin[2] >= maxs[2]
00340                         || check->r.absmax[0] <= mins[0]
00341                         || check->r.absmax[1] <= mins[1]
00342                         || check->r.absmax[2] <= mins[2] ) {
00343                                 continue;
00344                         }
00345                         // see if the ent's bbox is inside the pusher's final position
00346                         // this does allow a fast moving object to pass through a thin entity...
00347                         if (!G_TestEntityPosition (check)) {
00348                                 continue;
00349                         }
00350                 }
00351 
00352                 // the entity needs to be pushed
00353                 if ( G_TryPushingEntity( check, pusher, move, amove ) ) {
00354                         continue;
00355                 }
00356 
00357                 if (pusher->damage && check->client && (pusher->spawnflags & 32))
00358                 {
00359                         G_Damage( check, pusher, pusher, NULL, NULL, pusher->damage, 0, MOD_CRUSH );
00360                         continue;
00361                 }
00362 
00363                 if (check->s.eType == ET_BODY ||
00364                         (check->s.eType == ET_PLAYER && check->health < 1))
00365                 { //whatever, just crush it
00366                         G_Damage( check, pusher, pusher, NULL, NULL, 999, 0, MOD_CRUSH );
00367                         continue;
00368                 }
00369 
00370                 // the move was blocked an entity
00371 
00372                 // bobbing entities are instant-kill and never get blocked
00373                 if ( pusher->s.pos.trType == TR_SINE || pusher->s.apos.trType == TR_SINE ) {
00374                         G_Damage( check, pusher, pusher, NULL, NULL, 99999, 0, MOD_CRUSH );
00375                         continue;
00376                 }
00377 
00378                 
00379                 // save off the obstacle so we can call the block function (crush, etc)
00380                 *obstacle = check;
00381 
00382                 // move back any entities we already moved
00383                 // go backwards, so if the same entity was pushed
00384                 // twice, it goes back to the original position
00385                 for ( p=pushed_p-1 ; p>=pushed ; p-- ) {
00386                         VectorCopy (p->origin, p->ent->s.pos.trBase);
00387                         VectorCopy (p->angles, p->ent->s.apos.trBase);
00388                         if ( p->ent->client ) {
00389                                 p->ent->client->ps.delta_angles[YAW] = p->deltayaw;
00390                                 VectorCopy (p->origin, p->ent->client->ps.origin);
00391                         }
00392                         trap_LinkEntity (p->ent);
00393                 }
00394                 return qfalse;
00395         }
00396 
00397         return qtrue;
00398 }
00399 
00400 
00401 /*
00402 =================
00403 G_MoverTeam
00404 =================
00405 */
00406 void G_MoverTeam( gentity_t *ent ) {
00407         vec3_t          move, amove;
00408         gentity_t       *part, *obstacle;
00409         vec3_t          origin, angles;
00410 
00411         obstacle = NULL;
00412 
00413         // make sure all team slaves can move before commiting
00414         // any moves or calling any think functions
00415         // if the move is blocked, all moved objects will be backed out
00416         pushed_p = pushed;
00417         for (part = ent ; part ; part=part->teamchain) {
00418                 // get current position
00419                 BG_EvaluateTrajectory( &part->s.pos, level.time, origin );
00420                 BG_EvaluateTrajectory( &part->s.apos, level.time, angles );
00421                 VectorSubtract( origin, part->r.currentOrigin, move );
00422                 VectorSubtract( angles, part->r.currentAngles, amove );
00423                 if ( !VectorCompare( move, vec3_origin )
00424                         || !VectorCompare( amove, vec3_origin ) )
00425                 {//actually moved
00426                         if ( !G_MoverPush( part, move, amove, &obstacle ) ) {
00427                                 break;  // move was blocked
00428                         }
00429                 }
00430         }
00431 
00432         if (part) {
00433                 // go back to the previous position
00434                 for ( part = ent ; part ; part = part->teamchain ) {
00435                         part->s.pos.trTime += level.time - level.previousTime;
00436                         part->s.apos.trTime += level.time - level.previousTime;
00437                         BG_EvaluateTrajectory( &part->s.pos, level.time, part->r.currentOrigin );
00438                         BG_EvaluateTrajectory( &part->s.apos, level.time, part->r.currentAngles );
00439                         trap_LinkEntity( part );
00440                 }
00441 
00442                 // if the pusher has a "blocked" function, call it
00443                 if (ent->blocked) {
00444                         ent->blocked( ent, obstacle );
00445                 }
00446                 return;
00447         }
00448 
00449         // the move succeeded
00450         for ( part = ent ; part ; part = part->teamchain ) {
00451                 // call the reached function if time is at or past end point
00452                 if ( part->s.pos.trType == TR_LINEAR_STOP ||
00453                         part->s.pos.trType == TR_NONLINEAR_STOP) {
00454                         if ( level.time >= part->s.pos.trTime + part->s.pos.trDuration ) {
00455                                 if ( part->reached ) {
00456                                         part->reached( part );
00457                                 }
00458                         }
00459                 }
00460         }
00461 }
00462 
00463 /*
00464 ================
00465 G_RunMover
00466 
00467 ================
00468 */
00469 void G_RunMover( gentity_t *ent ) {
00470         // if not a team captain, don't do anything, because
00471         // the captain will handle everything
00472         if ( ent->flags & FL_TEAMSLAVE ) {
00473                 return;
00474         }
00475 
00476         // if stationary at one of the positions, don't move anything
00477         if ( ent->s.pos.trType != TR_STATIONARY || ent->s.apos.trType != TR_STATIONARY ) {
00478                 G_MoverTeam( ent );
00479         }
00480 
00481         // check think function
00482         G_RunThink( ent );
00483 }
00484 
00485 /*
00486 ============================================================================
00487 
00488 GENERAL MOVERS
00489 
00490 Doors, plats, and buttons are all binary (two position) movers
00491 Pos1 is "at rest", pos2 is "activated"
00492 ============================================================================
00493 */
00494 
00495 /*
00496 CalcTeamDoorCenter
00497 
00498 Finds all the doors of a team and returns their center position
00499 */
00500 
00501 void CalcTeamDoorCenter ( gentity_t *ent, vec3_t center ) 
00502 {
00503         vec3_t          slavecenter;
00504         gentity_t       *slave;
00505 
00506         //Start with our center
00507         VectorAdd(ent->r.mins, ent->r.maxs, center);
00508         VectorScale(center, 0.5, center);
00509         for ( slave = ent->teamchain ; slave ; slave = slave->teamchain ) 
00510         {
00511                 //Find slave's center
00512                 VectorAdd(slave->r.mins, slave->r.maxs, slavecenter);
00513                 VectorScale(slavecenter, 0.5, slavecenter);
00514                 //Add that to our own, find middle
00515                 VectorAdd(center, slavecenter, center);
00516                 VectorScale(center, 0.5, center);
00517         }
00518 }
00519 
00520 /*
00521 ===============
00522 SetMoverState
00523 ===============
00524 */
00525 void SetMoverState( gentity_t *ent, moverState_t moverState, int time ) {
00526         vec3_t                  delta;
00527         float                   f;
00528 
00529         ent->moverState = moverState;
00530 
00531         ent->s.pos.trTime = time;
00532 
00533         if ( ent->s.pos.trDuration <= 0 )
00534         {//Don't allow divide by zero!
00535                 ent->s.pos.trDuration = 1;
00536         }
00537 
00538         switch( moverState ) {
00539         case MOVER_POS1:
00540                 VectorCopy( ent->pos1, ent->s.pos.trBase );
00541                 ent->s.pos.trType = TR_STATIONARY;
00542                 break;
00543         case MOVER_POS2:
00544                 VectorCopy( ent->pos2, ent->s.pos.trBase );
00545                 ent->s.pos.trType = TR_STATIONARY;
00546                 break;
00547         case MOVER_1TO2:
00548                 VectorCopy( ent->pos1, ent->s.pos.trBase );
00549                 VectorSubtract( ent->pos2, ent->pos1, delta );
00550                 f = 1000.0 / ent->s.pos.trDuration;
00551                 VectorScale( delta, f, ent->s.pos.trDelta );
00552                 if ( ent->alt_fire )
00553                 {
00554                         ent->s.pos.trType = TR_LINEAR_STOP;
00555                 }
00556                 else
00557                 {
00558                         ent->s.pos.trType = TR_NONLINEAR_STOP;
00559                 }
00560                 //ent->s.eFlags &= ~EF_BLOCKED_MOVER;
00561                 break;
00562         case MOVER_2TO1:
00563                 VectorCopy( ent->pos2, ent->s.pos.trBase );
00564                 VectorSubtract( ent->pos1, ent->pos2, delta );
00565                 f = 1000.0 / ent->s.pos.trDuration;
00566                 VectorScale( delta, f, ent->s.pos.trDelta );
00567                 if ( ent->alt_fire )
00568                 {
00569                         ent->s.pos.trType = TR_LINEAR_STOP;
00570                 }
00571                 else
00572                 {
00573                         ent->s.pos.trType = TR_NONLINEAR_STOP;
00574                 }
00575                 //ent->s.eFlags &= ~EF_BLOCKED_MOVER;
00576                 break;
00577         }
00578         BG_EvaluateTrajectory( &ent->s.pos, level.time, ent->r.currentOrigin ); 
00579         trap_LinkEntity( ent );
00580 }
00581 
00582 /*
00583 ================
00584 MatchTeam
00585 
00586 All entities in a mover team will move from pos1 to pos2
00587 in the same amount of time
00588 ================
00589 */
00590 void MatchTeam( gentity_t *teamLeader, int moverState, int time ) {
00591         gentity_t               *slave;
00592 
00593         for ( slave = teamLeader ; slave ; slave = slave->teamchain ) {
00594                 SetMoverState( slave, (moverState_t) moverState, time );
00595         }
00596 }
00597 
00598 
00599 
00600 /*
00601 ================
00602 ReturnToPos1
00603 ================
00604 */
00605 void ReturnToPos1( gentity_t *ent ) {
00606         ent->think = 0;
00607         ent->nextthink = 0;
00608         ent->s.time = level.time;
00609 
00610         MatchTeam( ent, MOVER_2TO1, level.time );
00611 
00612         // starting sound
00613         G_PlayDoorLoopSound( ent );
00614         G_PlayDoorSound( ent, BMS_START );      //??
00615 }
00616 
00617 
00618 /*
00619 ================
00620 Reached_BinaryMover
00621 ================
00622 */
00623 
00624 void Reached_BinaryMover( gentity_t *ent ) 
00625 {
00626         // stop the looping sound
00627         ent->s.loopSound = 0;
00628         ent->s.loopIsSoundset = qfalse;
00629 
00630         if ( ent->moverState == MOVER_1TO2 ) 
00631         {//reached open
00632                 vec3_t  doorcenter;
00633 
00634                 // reached pos2
00635                 SetMoverState( ent, MOVER_POS2, level.time );
00636 
00637                 CalcTeamDoorCenter( ent, doorcenter );
00638 
00639                 // play sound
00640                 G_PlayDoorSound( ent, BMS_END );
00641 
00642                 if ( ent->wait < 0 )
00643                 {//Done for good
00644                         ent->think = 0;
00645                         ent->nextthink = 0;
00646                         ent->use = 0;
00647                 }
00648                 else
00649                 {
00650                         // return to pos1 after a delay
00651                         ent->think = ReturnToPos1;
00652                         if(ent->spawnflags & 8)
00653                         {//Toggle, keep think, wait for next use?
00654                                 ent->nextthink = -1;
00655                         }
00656                         else
00657                         {
00658                                 ent->nextthink = level.time + ent->wait;
00659                         }
00660                 }
00661 
00662                 // fire targets
00663                 if ( !ent->activator ) 
00664                 {
00665                         ent->activator = ent;
00666                 }
00667                 G_UseTargets2( ent, ent->activator, ent->opentarget );
00668         } 
00669         else if ( ent->moverState == MOVER_2TO1 ) 
00670         {//closed
00671                 vec3_t  doorcenter;
00672 
00673                 // reached pos1
00674                 SetMoverState( ent, MOVER_POS1, level.time );
00675 
00676                 CalcTeamDoorCenter( ent, doorcenter );
00677 
00678                 // play sound
00679                 G_PlayDoorSound( ent, BMS_END );
00680 
00681                 // close areaportals
00682                 if ( ent->teammaster == ent || !ent->teammaster ) 
00683                 {
00684                         trap_AdjustAreaPortalState( ent, qfalse );
00685                 }
00686                 G_UseTargets2( ent, ent->activator, ent->closetarget );
00687         } 
00688         else 
00689         {
00690                 G_Error( "Reached_BinaryMover: bad moverState" );
00691         }
00692 }
00693 
00694 
00695 /*
00696 ================
00697 Use_BinaryMover_Go
00698 ================
00699 */
00700 void Use_BinaryMover_Go( gentity_t *ent ) 
00701 {
00702         int             total;
00703         int             partial;
00704 //      gentity_t       *other = ent->enemy;
00705         gentity_t       *activator = ent->activator; 
00706 
00707         ent->activator = activator;
00708 
00709         if ( ent->moverState == MOVER_POS1 ) 
00710         {
00711                 vec3_t  doorcenter;
00712 
00713                 // start moving 50 msec later, becase if this was player
00714                 // triggered, level.time hasn't been advanced yet
00715                 MatchTeam( ent, MOVER_1TO2, level.time + 50 );
00716 
00717                 CalcTeamDoorCenter( ent, doorcenter );
00718 
00719                 // starting sound
00720                 G_PlayDoorLoopSound( ent );
00721                 G_PlayDoorSound( ent, BMS_START );
00722                 ent->s.time = level.time;
00723 
00724                 // open areaportal
00725                 if ( ent->teammaster == ent || !ent->teammaster ) {
00726                         trap_AdjustAreaPortalState( ent, qtrue );
00727                 }
00728                 G_UseTargets( ent, ent->activator );
00729                 return;
00730         }
00731 
00732         // if all the way up, just delay before coming down
00733         if ( ent->moverState == MOVER_POS2 ) {
00734                 //have to do this because the delay sets our think to Use_BinaryMover_Go
00735                 ent->think = ReturnToPos1;
00736                 if ( ent->spawnflags & 8 )
00737                 {//TOGGLE doors don't use wait!
00738                         ent->nextthink = level.time + FRAMETIME;
00739                 }
00740                 else
00741                 {
00742                         ent->nextthink = level.time + ent->wait;
00743                 }
00744                 G_UseTargets2( ent, ent->activator, ent->target2 );
00745                 return;
00746         }
00747 
00748         // only partway down before reversing
00749         if ( ent->moverState == MOVER_2TO1 ) 
00750         {
00751                 if ( ent->s.pos.trType == TR_NONLINEAR_STOP )
00752                 {
00753                         vec3_t curDelta;
00754                         float fPartial;
00755                         total = ent->s.pos.trDuration-50;
00756                         VectorSubtract( ent->r.currentOrigin, ent->pos1, curDelta );
00757                         fPartial = VectorLength( curDelta )/VectorLength( ent->s.pos.trDelta );
00758                         VectorScale( ent->s.pos.trDelta, fPartial, curDelta );
00759                         fPartial /= ent->s.pos.trDuration;
00760                         fPartial /= 0.001f;
00761                         fPartial = acos( fPartial );
00762                         fPartial = RAD2DEG( fPartial );
00763                         fPartial = (90.0f - fPartial)/90.0f*ent->s.pos.trDuration;
00764                         partial = total - floor( fPartial );
00765                 }
00766                 else
00767                 {
00768                         total = ent->s.pos.trDuration;
00769                         partial = level.time - ent->s.pos.trTime;
00770                 }
00771 
00772                 if ( partial > total ) {
00773                         partial = total;
00774                 }
00775                 ent->s.pos.trTime = level.time - ( total - partial );//ent->s.time;
00776 
00777                 MatchTeam( ent, MOVER_1TO2, ent->s.pos.trTime );
00778 
00779                 G_PlayDoorSound( ent, BMS_START );
00780 
00781                 return;
00782         }
00783 
00784         // only partway up before reversing
00785         if ( ent->moverState == MOVER_1TO2 ) 
00786         {
00787                 if ( ent->s.pos.trType == TR_NONLINEAR_STOP )
00788                 {
00789                         vec3_t curDelta;
00790                         float fPartial;
00791                         total = ent->s.pos.trDuration-50;
00792                         VectorSubtract( ent->r.currentOrigin, ent->pos2, curDelta );
00793                         fPartial = VectorLength( curDelta )/VectorLength( ent->s.pos.trDelta );
00794                         VectorScale( ent->s.pos.trDelta, fPartial, curDelta );
00795                         fPartial /= ent->s.pos.trDuration;
00796                         fPartial /= 0.001f;
00797                         fPartial = acos( fPartial );
00798                         fPartial = RAD2DEG( fPartial );
00799                         fPartial = (90.0f - fPartial)/90.0f*ent->s.pos.trDuration;
00800                         partial = total - floor( fPartial );
00801                 }
00802                 else
00803                 {
00804                         total = ent->s.pos.trDuration;
00805                         partial = level.time - ent->s.pos.trTime;
00806                 }
00807                 if ( partial > total ) {
00808                         partial = total;
00809                 }
00810 
00811                 ent->s.pos.trTime = level.time - ( total - partial );//ent->s.time;
00812                 MatchTeam( ent, MOVER_2TO1, ent->s.pos.trTime );
00813 
00814                 G_PlayDoorSound( ent, BMS_START );
00815 
00816                 return;
00817         }
00818 }
00819 
00820 void UnLockDoors(gentity_t *const ent)
00821 {
00822         //noise?
00823         //go through and unlock the door and all the slaves
00824         gentity_t       *slave = ent;
00825         do 
00826         {       // want to allow locked toggle doors, so keep the targetname
00827                 if( !(slave->spawnflags & MOVER_TOGGLE) )
00828                 {
00829                         slave->targetname = NULL;//not usable ever again
00830                 }
00831                 slave->spawnflags &= ~MOVER_LOCKED;
00832                 slave->s.frame = 1;//second stage of anim
00833                 slave = slave->teamchain;
00834         } while  ( slave );
00835 }
00836 void LockDoors(gentity_t *const ent)
00837 {
00838         //noise?
00839         //go through and lock the door and all the slaves
00840         gentity_t       *slave = ent;
00841         do 
00842         {
00843                 slave->spawnflags |= MOVER_LOCKED;
00844                 slave->s.frame = 0;//first stage of anim
00845                 slave = slave->teamchain;
00846         } while  ( slave );
00847 }
00848 /*
00849 ================
00850 Use_BinaryMover
00851 ================
00852 */
00853 void Use_BinaryMover( gentity_t *ent, gentity_t *other, gentity_t *activator ) 
00854 {
00855         if ( !ent->use )
00856         {//I cannot be used anymore, must be a door with a wait of -1 that's opened.
00857                 return;
00858         }
00859 
00860         // only the master should be used
00861         if ( ent->flags & FL_TEAMSLAVE ) 
00862         {
00863                 Use_BinaryMover( ent->teammaster, other, activator );
00864                 return;
00865         }
00866 
00867         if ( ent->flags & FL_INACTIVE )
00868         {
00869                 return;
00870         }
00871 
00872         if ( ent->spawnflags & MOVER_LOCKED )
00873         {//a locked door, unlock it
00874                 UnLockDoors(ent);
00875                 return;
00876         }
00877 
00878         G_ActivateBehavior(ent,BSET_USE);
00879 
00880         ent->enemy = other;
00881         ent->activator = activator;
00882         if(ent->delay)
00883         {
00884                 ent->think = Use_BinaryMover_Go;
00885                 ent->nextthink = level.time + ent->delay;
00886         }
00887         else
00888         {
00889                 Use_BinaryMover_Go(ent);
00890         }
00891 }
00892 
00893 
00894 
00895 /*
00896 ================
00897 InitMover
00898 
00899 "pos1", "pos2", and "speed" should be set before calling,
00900 so the movement delta can be calculated
00901 ================
00902 */
00903 void InitMoverTrData( gentity_t *ent )
00904 {
00905         vec3_t          move;
00906         float           distance;
00907 
00908         ent->s.pos.trType = TR_STATIONARY;
00909         VectorCopy( ent->pos1, ent->s.pos.trBase );
00910 
00911         // calculate time to reach second position from speed
00912         VectorSubtract( ent->pos2, ent->pos1, move );
00913         distance = VectorLength( move );
00914         if ( ! ent->speed ) 
00915         {
00916                 ent->speed = 100;
00917         }
00918         VectorScale( move, ent->speed, ent->s.pos.trDelta );
00919         ent->s.pos.trDuration = distance * 1000 / ent->speed;
00920         if ( ent->s.pos.trDuration <= 0 ) 
00921         {
00922                 ent->s.pos.trDuration = 1;
00923         }
00924 }
00925 
00926 void InitMover( gentity_t *ent ) 
00927 {
00928         float           light;
00929         vec3_t          color;
00930         qboolean        lightSet, colorSet;
00931 
00932         // if the "model2" key is set, use a seperate model
00933         // for drawing, but clip against the brushes
00934         if ( ent->model2 ) 
00935         {
00936                 if ( strstr( ent->model2, ".glm" ))
00937                 { //for now, not supported in MP.
00938                         ent->s.modelindex2 = 0;
00939                 }
00940                 else
00941                 {
00942                         ent->s.modelindex2 = G_ModelIndex( ent->model2 );
00943                 }
00944         }
00945 
00946         // if the "color" or "light" keys are set, setup constantLight
00947         lightSet = G_SpawnFloat( "light", "100", &light );
00948         colorSet = G_SpawnVector( "color", "1 1 1", color );
00949         if ( lightSet || colorSet ) {
00950                 int             r, g, b, i;
00951 
00952                 r = color[0] * 255;
00953                 if ( r > 255 ) {
00954                         r = 255;
00955                 }
00956                 g = color[1] * 255;
00957                 if ( g > 255 ) {
00958                         g = 255;
00959                 }
00960                 b = color[2] * 255;
00961                 if ( b > 255 ) {
00962                         b = 255;
00963                 }
00964                 i = light / 4;
00965                 if ( i > 255 ) {
00966                         i = 255;
00967                 }
00968                 ent->s.constantLight = r | ( g << 8 ) | ( b << 16 ) | ( i << 24 );
00969         }
00970 
00971         ent->use = Use_BinaryMover;
00972         ent->reached = Reached_BinaryMover;
00973 
00974         ent->moverState = MOVER_POS1;
00975         ent->r.svFlags = SVF_USE_CURRENT_ORIGIN;
00976         if ( ent->spawnflags & MOVER_INACTIVE )
00977         {// Make it inactive
00978                 ent->flags |= FL_INACTIVE;      
00979         }
00980         if(ent->spawnflags & MOVER_PLAYER_USE)
00981         {//Can be used by the player's BUTTON_USE
00982                 ent->r.svFlags |= SVF_PLAYER_USABLE;
00983         }
00984         ent->s.eType = ET_MOVER;
00985         VectorCopy( ent->pos1, ent->r.currentOrigin );
00986         trap_LinkEntity( ent );
00987 
00988         InitMoverTrData( ent );
00989 }
00990 
00991 
00992 /*
00993 ===============================================================================
00994 
00995 DOOR
00996 
00997 A use can be triggered either by a touch function, by being shot, or by being
00998 targeted by another entity.
00999 
01000 ===============================================================================
01001 */
01002 
01003 /*
01004 ================
01005 Blocked_Door
01006 ================
01007 */
01008 void Blocked_Door( gentity_t *ent, gentity_t *other )
01009 {
01010         if ( ent->damage ) {
01011                 G_Damage( other, ent, ent, NULL, NULL, ent->damage, 0, MOD_CRUSH );
01012         }
01013         if ( ent->spawnflags & MOVER_CRUSHER ) {
01014                 return;         // crushers don't reverse
01015         }
01016 
01017         // reverse direction
01018         Use_BinaryMover( ent, ent, other );
01019 }
01020 
01021 
01022 /*
01023 ================
01024 Touch_DoorTriggerSpectator
01025 ================
01026 */
01027 static void Touch_DoorTriggerSpectator( gentity_t *ent, gentity_t *other, trace_t *trace ) {
01028         int i, axis;
01029         trace_t tr;
01030         vec3_t pMins, pMaxs;
01031         vec3_t origin, dir, angles;
01032 
01033         axis = ent->count;
01034         VectorClear(dir);
01035         if (fabs(other->s.origin[axis] - ent->r.absmax[axis]) <
01036                 fabs(other->s.origin[axis] - ent->r.absmin[axis])) {
01037                 origin[axis] = ent->r.absmin[axis] - 10;
01038                 dir[axis] = -1;
01039         }
01040         else {
01041                 origin[axis] = ent->r.absmax[axis] + 10;
01042                 dir[axis] = 1;
01043         }
01044         for (i = 0; i < 3; i++) {
01045                 if (i == axis) continue;
01046                 origin[i] = (ent->r.absmin[i] + ent->r.absmax[i]) * 0.5;
01047         }
01048 
01049         vectoangles(dir, angles);
01050 
01051         VectorSet(pMins, -15.0f, -15.0f, DEFAULT_MINS_2);
01052         VectorSet(pMaxs, 15.0f, 15.0f, DEFAULT_MAXS_2);
01053         trap_Trace(&tr, origin, pMins, pMaxs, origin, other->s.number, other->clipmask);
01054         if (!tr.startsolid &&
01055                 !tr.allsolid &&
01056                 tr.fraction == 1.0f &&
01057                 tr.entityNum == ENTITYNUM_NONE)
01058         {
01059                 TeleportPlayer(other, origin, angles );
01060         }
01061 }
01062 
01063 /*
01064 ================
01065 Touch_DoorTrigger
01066 ================
01067 */
01068 void Touch_DoorTrigger( gentity_t *ent, gentity_t *other, trace_t *trace ) 
01069 {
01070         gentity_t *relockEnt = NULL;
01071 
01072         if ( other->client && other->client->sess.sessionTeam == TEAM_SPECTATOR ) 
01073         {
01074                 // if the door is not open and not opening
01075                 if ( ent->parent->moverState != MOVER_1TO2 &&
01076                         ent->parent->moverState != MOVER_POS2 ) 
01077                 {
01078                         Touch_DoorTriggerSpectator( ent, other, trace );
01079                 }
01080                 return;
01081         }
01082 
01083         if (!ent->genericValue14 &&
01084                 (!ent->parent || !ent->parent->genericValue14))
01085         {
01086                 if (other->client && other->s.number >= MAX_CLIENTS &&
01087                         other->s.eType == ET_NPC && other->s.NPC_class == CLASS_VEHICLE)
01088                 { //doors don't open for vehicles
01089                         return;
01090                 }
01091 
01092                 if (other->client && other->s.number < MAX_CLIENTS &&
01093                         other->client->ps.m_iVehicleNum)
01094                 { //can't open a door while on a vehicle
01095                         return;
01096                 }
01097         }
01098 
01099         if ( ent->flags & FL_INACTIVE )
01100         {
01101                 return;
01102         }
01103 
01104         if ( ent->parent->spawnflags & MOVER_LOCKED )
01105         {//don't even try to use the door if it's locked 
01106                 if ( !ent->parent->alliedTeam //we don't have a "teamallow" team
01107                         || !other->client //we do have a "teamallow" team, but this isn't a client
01108                         || other->client->sess.sessionTeam != ent->parent->alliedTeam )//it is a client, but it's not on the right team
01109                 {
01110                         return;
01111                 }
01112                 else
01113                 {//temporarily unlock us while we call Use_BinaryMover (so it doesn't unlock all the doors in this team)
01114                         if ( ent->parent->flags & FL_TEAMSLAVE ) 
01115                         {
01116                                 relockEnt = ent->parent->teammaster;
01117                         }
01118                         else
01119                         {
01120                                 relockEnt = ent->parent;
01121                         }
01122                         if ( relockEnt != NULL )
01123                         {
01124                                 relockEnt->spawnflags &= ~MOVER_LOCKED;
01125                         }
01126                 }
01127         }
01128 
01129         if ( ent->parent->moverState != MOVER_1TO2 ) 
01130         {//Door is not already opening
01131                 //if ( ent->parent->moverState == MOVER_POS1 || ent->parent->moverState == MOVER_2TO1 )
01132                 //{//only check these if closed or closing
01133 
01134                 //If door is closed, opening or open, check this
01135                 Use_BinaryMover( ent->parent, ent, other );
01136         }
01137         if ( relockEnt != NULL )
01138         {//re-lock us
01139                 relockEnt->spawnflags |= MOVER_LOCKED;
01140         }
01141 
01142         /*
01143         //Old style
01144         if ( ent->parent->moverState != MOVER_1TO2 ) {
01145                 Use_BinaryMover( ent->parent, ent, other );
01146         }
01147         */
01148 }
01149 
01150 /*
01151 ======================
01152 Think_SpawnNewDoorTrigger
01153 
01154 All of the parts of a door have been spawned, so create
01155 a trigger that encloses all of them
01156 ======================
01157 */
01158 void Think_SpawnNewDoorTrigger( gentity_t *ent ) 
01159 {
01160         gentity_t               *other;
01161         vec3_t          mins, maxs;
01162         int                     i, best;
01163 
01164         // set all of the slaves as shootable
01165         if ( ent->takedamage ) 
01166         {
01167                 for ( other = ent ; other ; other = other->teamchain ) 
01168                 {
01169                         other->takedamage = qtrue;
01170                 }
01171         }
01172 
01173         // find the bounds of everything on the team
01174         VectorCopy (ent->r.absmin, mins);
01175         VectorCopy (ent->r.absmax, maxs);
01176 
01177         for (other = ent->teamchain ; other ; other=other->teamchain) {
01178                 AddPointToBounds (other->r.absmin, mins, maxs);
01179                 AddPointToBounds (other->r.absmax, mins, maxs);
01180         }
01181 
01182         // find the thinnest axis, which will be the one we expand
01183         best = 0;
01184         for ( i = 1 ; i < 3 ; i++ ) {
01185                 if ( maxs[i] - mins[i] < maxs[best] - mins[best] ) {
01186                         best = i;
01187                 }
01188         }
01189         maxs[best] += 120;
01190         mins[best] -= 120;
01191 
01192         // create a trigger with this size
01193         other = G_Spawn ();
01194         VectorCopy (mins, other->r.mins);
01195         VectorCopy (maxs, other->r.maxs);
01196         other->parent = ent;
01197         other->r.contents = CONTENTS_TRIGGER;
01198         other->touch = Touch_DoorTrigger;
01199         trap_LinkEntity (other);
01200         other->classname = "trigger_door";
01201         // remember the thinnest axis
01202         other->count = best;
01203 
01204         MatchTeam( ent, ent->moverState, level.time );
01205 }
01206 
01207 void Think_MatchTeam( gentity_t *ent ) 
01208 {
01209         MatchTeam( ent, ent->moverState, level.time );
01210 }
01211 
01212 qboolean G_EntIsDoor( int entityNum )
01213 {
01214         gentity_t *ent;
01215 
01216         if ( entityNum < 0 || entityNum >= ENTITYNUM_WORLD )
01217         {
01218                 return qfalse;
01219         }
01220 
01221         ent = &g_entities[entityNum];
01222         if ( ent && !Q_stricmp( "func_door", ent->classname ) )
01223         {//blocked by a door
01224                 return qtrue;
01225         }
01226         return qfalse;
01227 }
01228 
01229 gentity_t *G_FindDoorTrigger( gentity_t *ent )
01230 {
01231         gentity_t *owner = NULL;
01232         gentity_t *door = ent;
01233         if ( door->flags & FL_TEAMSLAVE )
01234         {//not the master door, get the master door
01235                 while ( door->teammaster && (door->flags&FL_TEAMSLAVE))
01236                 {
01237                         door = door->teammaster;
01238                 }
01239         }
01240         if ( door->targetname )
01241         {//find out what is targeting it
01242                 //FIXME: if ent->targetname, check what kind of trigger/ent is targetting it?  If a normal trigger (active, etc), then it's okay?
01243                 while ( (owner = G_Find( owner, FOFS( target ), door->targetname )) != NULL )
01244                 {
01245                         if ( owner && (owner->r.contents&CONTENTS_TRIGGER</