Main Page | Class Hierarchy | Alphabetical List | Data Structures | Directories | File List | Data Fields | Globals

g_mover.c

Go to the documentation of this file.
00001 /*
00002 ===========================================================================
00003 Copyright (C) 1999-2005 Id Software, Inc.
00004 
00005 This file is part of Quake III Arena source code.
00006 
00007 Quake III Arena source code is free software; you can redistribute it
00008 and/or modify it under the terms of the GNU General Public License as
00009 published by the Free Software Foundation; either version 2 of the License,
00010 or (at your option) any later version.
00011 
00012 Quake III Arena source code is distributed in the hope that it will be
00013 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
00014 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015 GNU General Public License for more details.
00016 
00017 You should have received a copy of the GNU General Public License
00018 along with Foobar; if not, write to the Free Software
00019 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
00020 ===========================================================================
00021 */
00022 //
00023 
00024 #include "g_local.h"
00025 
00026 
00027 
00028 /*
00029 ===============================================================================
00030 
00031 PUSHMOVE
00032 
00033 ===============================================================================
00034 */
00035 
00036 void MatchTeam( gentity_t *teamLeader, int moverState, int time );
00037 
00038 typedef struct {
00039     gentity_t   *ent;
00040     vec3_t  origin;
00041     vec3_t  angles;
00042     float   deltayaw;
00043 } pushed_t;
00044 pushed_t    pushed[MAX_GENTITIES], *pushed_p;
00045 
00046 
00047 /*
00048 ============
00049 G_TestEntityPosition
00050 
00051 ============
00052 */
00053 gentity_t   *G_TestEntityPosition( gentity_t *ent ) {
00054     trace_t tr;
00055     int     mask;
00056 
00057     if ( ent->clipmask ) {
00058         mask = ent->clipmask;
00059     } else {
00060         mask = MASK_SOLID;
00061     }
00062     if ( ent->client ) {
00063         trap_Trace( &tr, ent->client->ps.origin, ent->r.mins, ent->r.maxs, ent->client->ps.origin, ent->s.number, mask );
00064     } else {
00065         trap_Trace( &tr, ent->s.pos.trBase, ent->r.mins, ent->r.maxs, ent->s.pos.trBase, ent->s.number, mask );
00066     }
00067     
00068     if (tr.startsolid)
00069         return &g_entities[ tr.entityNum ];
00070         
00071     return NULL;
00072 }
00073 
00074 /*
00075 ================
00076 G_CreateRotationMatrix
00077 ================
00078 */
00079 void G_CreateRotationMatrix(vec3_t angles, vec3_t matrix[3]) {
00080     AngleVectors(angles, matrix[0], matrix[1], matrix[2]);
00081     VectorInverse(matrix[1]);
00082 }
00083 
00084 /*
00085 ================
00086 G_TransposeMatrix
00087 ================
00088 */
00089 void G_TransposeMatrix(vec3_t matrix[3], vec3_t transpose[3]) {
00090     int i, j;
00091     for (i = 0; i < 3; i++) {
00092         for (j = 0; j < 3; j++) {
00093             transpose[i][j] = matrix[j][i];
00094         }
00095     }
00096 }
00097 
00098 /*
00099 ================
00100 G_RotatePoint
00101 ================
00102 */
00103 void G_RotatePoint(vec3_t point, vec3_t matrix[3]) {
00104     vec3_t tvec;
00105 
00106     VectorCopy(point, tvec);
00107     point[0] = DotProduct(matrix[0], tvec);
00108     point[1] = DotProduct(matrix[1], tvec);
00109     point[2] = DotProduct(matrix[2], tvec);
00110 }
00111 
00112 /*
00113 ==================
00114 G_TryPushingEntity
00115 
00116 Returns qfalse if the move is blocked
00117 ==================
00118 */
00119 qboolean    G_TryPushingEntity( gentity_t *check, gentity_t *pusher, vec3_t move, vec3_t amove ) {
00120     vec3_t      matrix[3], transpose[3];
00121     vec3_t      org, org2, move2;
00122     gentity_t   *block;
00123 
00124     // EF_MOVER_STOP will just stop when contacting another entity
00125     // instead of pushing it, but entities can still ride on top of it
00126     if ( ( pusher->s.eFlags & EF_MOVER_STOP ) && 
00127         check->s.groundEntityNum != pusher->s.number ) {
00128         return qfalse;
00129     }
00130 
00131     // save off the old position
00132     if (pushed_p > &pushed[MAX_GENTITIES]) {
00133         G_Error( "pushed_p > &pushed[MAX_GENTITIES]" );
00134     }
00135     pushed_p->ent = check;
00136     VectorCopy (check->s.pos.trBase, pushed_p->origin);
00137     VectorCopy (check->s.apos.trBase, pushed_p->angles);
00138     if ( check->client ) {
00139         pushed_p->deltayaw = check->client->ps.delta_angles[YAW];
00140         VectorCopy (check->client->ps.origin, pushed_p->origin);
00141     }
00142     pushed_p++;
00143 
00144     // try moving the contacted entity 
00145     // figure movement due to the pusher's amove
00146     G_CreateRotationMatrix( amove, transpose );
00147     G_TransposeMatrix( transpose, matrix );
00148     if ( check->client ) {
00149         VectorSubtract (check->client->ps.origin, pusher->r.currentOrigin, org);
00150     }
00151     else {
00152         VectorSubtract (check->s.pos.trBase, pusher->r.currentOrigin, org);
00153     }
00154     VectorCopy( org, org2 );
00155     G_RotatePoint( org2, matrix );
00156     VectorSubtract (org2, org, move2);
00157     // add movement
00158     VectorAdd (check->s.pos.trBase, move, check->s.pos.trBase);
00159     VectorAdd (check->s.pos.trBase, move2, check->s.pos.trBase);
00160     if ( check->client ) {
00161         VectorAdd (check->client->ps.origin, move, check->client->ps.origin);
00162         VectorAdd (check->client->ps.origin, move2, check->client->ps.origin);
00163         // make sure the client's view rotates when on a rotating mover
00164         check->client->ps.delta_angles[YAW] += ANGLE2SHORT(amove[YAW]);
00165     }
00166 
00167     // may have pushed them off an edge
00168     if ( check->s.groundEntityNum != pusher->s.number ) {
00169         check->s.groundEntityNum = -1;
00170     }
00171 
00172     block = G_TestEntityPosition( check );
00173     if (!block) {
00174         // pushed ok
00175         if ( check->client ) {
00176             VectorCopy( check->client->ps.origin, check->r.currentOrigin );
00177         } else {
00178             VectorCopy( check->s.pos.trBase, check->r.currentOrigin );
00179         }
00180         trap_LinkEntity (check);
00181         return qtrue;
00182     }
00183 
00184     // if it is ok to leave in the old position, do it
00185     // this is only relevent for riding entities, not pushed
00186     // Sliding trapdoors can cause this.
00187     VectorCopy( (pushed_p-1)->origin, check->s.pos.trBase);
00188     if ( check->client ) {
00189         VectorCopy( (pushed_p-1)->origin, check->client->ps.origin);
00190     }
00191     VectorCopy( (pushed_p-1)->angles, check->s.apos.trBase );
00192     block = G_TestEntityPosition (check);
00193     if ( !block ) {
00194         check->s.groundEntityNum = -1;
00195         pushed_p--;
00196         return qtrue;
00197     }
00198 
00199     // blocked
00200     return qfalse;
00201 }
00202 
00203 /*
00204 ==================
00205 G_CheckProxMinePosition
00206 ==================
00207 */
00208 qboolean G_CheckProxMinePosition( gentity_t *check ) {
00209     vec3_t      start, end;
00210     trace_t tr;
00211 
00212     VectorMA(check->s.pos.trBase, 0.125, check->movedir, start);
00213     VectorMA(check->s.pos.trBase, 2, check->movedir, end);
00214     trap_Trace( &tr, start, NULL, NULL, end, check->s.number, MASK_SOLID );
00215     
00216     if (tr.startsolid || tr.fraction < 1)
00217         return qfalse;
00218 
00219     return qtrue;
00220 }
00221 
00222 /*
00223 ==================
00224 G_TryPushingProxMine
00225 ==================
00226 */
00227 qboolean G_TryPushingProxMine( gentity_t *check, gentity_t *pusher, vec3_t move, vec3_t amove ) {
00228     vec3_t      forward, right, up;
00229     vec3_t      org, org2, move2;
00230     int ret;
00231 
00232     // we need this for pushing things later
00233     VectorSubtract (vec3_origin, amove, org);
00234     AngleVectors (org, forward, right, up);
00235 
00236     // try moving the contacted entity 
00237     VectorAdd (check->s.pos.trBase, move, check->s.pos.trBase);
00238 
00239     // figure movement due to the pusher's amove
00240     VectorSubtract (check->s.pos.trBase, pusher->r.currentOrigin, org);
00241     org2[0] = DotProduct (org, forward);
00242     org2[1] = -DotProduct (org, right);
00243     org2[2] = DotProduct (org, up);
00244     VectorSubtract (org2, org, move2);
00245     VectorAdd (check->s.pos.trBase, move2, check->s.pos.trBase);
00246 
00247     ret = G_CheckProxMinePosition( check );
00248     if (ret) {
00249         VectorCopy( check->s.pos.trBase, check->r.currentOrigin );
00250         trap_LinkEntity (check);
00251     }
00252     return ret;
00253 }
00254 
00255 void G_ExplodeMissile( gentity_t *ent );
00256 
00257 /*
00258 ============
00259 G_MoverPush
00260 
00261 Objects need to be moved back on a failed push,
00262 otherwise riders would continue to slide.
00263 If qfalse is returned, *obstacle will be the blocking entity
00264 ============
00265 */
00266 qboolean G_MoverPush( gentity_t *pusher, vec3_t move, vec3_t amove, gentity_t **obstacle ) {
00267     int         i, e;
00268     gentity_t   *check;
00269     vec3_t      mins, maxs;
00270     pushed_t    *p;
00271     int         entityList[MAX_GENTITIES];
00272     int         listedEntities;
00273     vec3_t      totalMins, totalMaxs;
00274 
00275     *obstacle = NULL;
00276 
00277 
00278     // mins/maxs are the bounds at the destination
00279     // totalMins / totalMaxs are the bounds for the entire move
00280     if ( pusher->r.currentAngles[0] || pusher->r.currentAngles[1] || pusher->r.currentAngles[2]
00281         || amove[0] || amove[1] || amove[2] ) {
00282         float       radius;
00283 
00284         radius = RadiusFromBounds( pusher->r.mins, pusher->r.maxs );
00285         for ( i = 0 ; i < 3 ; i++ ) {
00286             mins[i] = pusher->r.currentOrigin[i] + move[i] - radius;
00287             maxs[i] = pusher->r.currentOrigin[i] + move[i] + radius;
00288             totalMins[i] = mins[i] - move[i];
00289             totalMaxs[i] = maxs[i] - move[i];
00290         }
00291     } else {
00292         for (i=0 ; i<3 ; i++) {
00293             mins[i] = pusher->r.absmin[i] + move[i];
00294             maxs[i] = pusher->r.absmax[i] + move[i];
00295         }
00296 
00297         VectorCopy( pusher->r.absmin, totalMins );
00298         VectorCopy( pusher->r.absmax, totalMaxs );
00299         for (i=0 ; i<3 ; i++) {
00300             if ( move[i] > 0 ) {
00301                 totalMaxs[i] += move[i];
00302             } else {
00303                 totalMins[i] += move[i];
00304             }
00305         }
00306     }
00307 
00308     // unlink the pusher so we don't get it in the entityList
00309     trap_UnlinkEntity( pusher );
00310 
00311     listedEntities = trap_EntitiesInBox( totalMins, totalMaxs, entityList, MAX_GENTITIES );
00312 
00313     // move the pusher to it's final position
00314     VectorAdd( pusher->r.currentOrigin, move, pusher->r.currentOrigin );
00315     VectorAdd( pusher->r.currentAngles, amove, pusher->r.currentAngles );
00316     trap_LinkEntity( pusher );
00317 
00318     // see if any solid entities are inside the final position
00319     for ( e = 0 ; e < listedEntities ; e++ ) {
00320         check = &g_entities[ entityList[ e ] ];
00321 
00322 #ifdef MISSIONPACK
00323         if ( check->s.eType == ET_MISSILE ) {
00324             // if it is a prox mine
00325             if ( !strcmp(check->classname, "prox mine") ) {
00326                 // if this prox mine is attached to this mover try to move it with the pusher
00327                 if ( check->enemy == pusher ) {
00328                     if (!G_TryPushingProxMine( check, pusher, move, amove )) {
00329                         //explode
00330                         check->s.loopSound = 0;
00331                         G_AddEvent( check, EV_PROXIMITY_MINE_TRIGGER, 0 );
00332                         G_ExplodeMissile(check);
00333                         if (check->activator) {
00334                             G_FreeEntity(check->activator);
00335                             check->activator = NULL;
00336                         }
00337                         //G_Printf("prox mine explodes\n");
00338                     }
00339                 }
00340                 else {
00341                     //check if the prox mine is crushed by the mover
00342                     if (!G_CheckProxMinePosition( check )) {
00343                         //explode
00344                         check->s.loopSound = 0;
00345                         G_AddEvent( check, EV_PROXIMITY_MINE_TRIGGER, 0 );
00346                         G_ExplodeMissile(check);
00347                         if (check->activator) {
00348                             G_FreeEntity(check->activator);
00349                             check->activator = NULL;
00350                         }
00351                         //G_Printf("prox mine explodes\n");
00352                     }
00353                 }
00354                 continue;
00355             }
00356         }
00357 #endif
00358         // only push items and players
00359         if ( check->s.eType != ET_ITEM && check->s.eType != ET_PLAYER && !check->physicsObject ) {
00360             continue;
00361         }
00362 
00363         // if the entity is standing on the pusher, it will definitely be moved
00364         if ( check->s.groundEntityNum != pusher->s.number ) {
00365             // see if the ent needs to be tested
00366             if ( check->r.absmin[0] >= maxs[0]
00367             || check->r.absmin[1] >= maxs[1]
00368             || check->r.absmin[2] >= maxs[2]
00369             || check->r.absmax[0] <= mins[0]
00370             || check->r.absmax[1] <= mins[1]
00371             || check->r.absmax[2] <= mins[2] ) {
00372                 continue;
00373             }
00374             // see if the ent's bbox is inside the pusher's final position
00375             // this does allow a fast moving object to pass through a thin entity...
00376             if (!G_TestEntityPosition (check)) {
00377                 continue;
00378             }
00379         }
00380 
00381         // the entity needs to be pushed
00382         if ( G_TryPushingEntity( check, pusher, move, amove ) ) {
00383             continue;
00384         }
00385 
00386         // the move was blocked an entity
00387 
00388         // bobbing entities are instant-kill and never get blocked
00389         if ( pusher->s.pos.trType == TR_SINE || pusher->s.apos.trType == TR_SINE ) {
00390             G_Damage( check, pusher, pusher, NULL, NULL, 99999, 0, MOD_CRUSH );
00391             continue;
00392         }
00393 
00394         
00395         // save off the obstacle so we can call the block function (crush, etc)
00396         *obstacle = check;
00397 
00398         // move back any entities we already moved
00399         // go backwards, so if the same entity was pushed
00400         // twice, it goes back to the original position
00401         for ( p=pushed_p-1 ; p>=pushed ; p-- ) {
00402             VectorCopy (p->origin, p->ent->s.pos.trBase);
00403             VectorCopy (p->angles, p->ent->s.apos.trBase);
00404             if ( p->ent->client ) {
00405                 p->ent->client->ps.delta_angles[YAW] = p->deltayaw;
00406                 VectorCopy (p->origin, p->ent->client->ps.origin);
00407             }
00408             trap_LinkEntity (p->ent);
00409         }
00410         return qfalse;
00411     }
00412 
00413     return qtrue;
00414 }
00415 
00416 
00417 /*
00418 =================
00419 G_MoverTeam
00420 =================
00421 */
00422 void G_MoverTeam( gentity_t *ent ) {
00423     vec3_t      move, amove;
00424     gentity_t   *part, *obstacle;
00425     vec3_t      origin, angles;
00426 
00427     obstacle = NULL;
00428 
00429     // make sure all team slaves can move before commiting
00430     // any moves or calling any think functions
00431     // if the move is blocked, all moved objects will be backed out
00432     pushed_p = pushed;
00433     for (part = ent ; part ; part=part->teamchain) {
00434         // get current position
00435         BG_EvaluateTrajectory( &part->s.pos, level.time, origin );
00436         BG_EvaluateTrajectory( &part->s.apos, level.time, angles );
00437         VectorSubtract( origin, part->r.currentOrigin, move );
00438         VectorSubtract( angles, part->r.currentAngles, amove );
00439         if ( !G_MoverPush( part, move, amove, &obstacle ) ) {
00440             break;  // move was blocked
00441         }
00442     }
00443 
00444     if (part) {
00445         // go back to the previous position
00446         for ( part = ent ; part ; part = part->teamchain ) {
00447             part->s.pos.trTime += level.time - level.previousTime;
00448             part->s.apos.trTime += level.time - level.previousTime;
00449             BG_EvaluateTrajectory( &part->s.pos, level.time, part->r.currentOrigin );
00450             BG_EvaluateTrajectory( &part->s.apos, level.time, part->r.currentAngles );
00451             trap_LinkEntity( part );
00452         }
00453 
00454         // if the pusher has a "blocked" function, call it
00455         if (ent->blocked) {
00456             ent->blocked( ent, obstacle );
00457         }
00458         return;
00459     }
00460 
00461     // the move succeeded
00462     for ( part = ent ; part ; part = part->teamchain ) {
00463         // call the reached function if time is at or past end point
00464         if ( part->s.pos.trType == TR_LINEAR_STOP ) {
00465             if ( level.time >= part->s.pos.trTime + part->s.pos.trDuration ) {
00466                 if ( part->reached ) {
00467                     part->reached( part );
00468                 }
00469             }
00470         }
00471     }
00472 }
00473 
00474 /*
00475 ================
00476 G_RunMover
00477 
00478 ================
00479 */
00480 void G_RunMover( gentity_t *ent ) {
00481     // if not a team captain, don't do anything, because
00482     // the captain will handle everything
00483     if ( ent->flags & FL_TEAMSLAVE ) {
00484         return;
00485     }
00486 
00487     // if stationary at one of the positions, don't move anything
00488     if ( ent->s.pos.trType != TR_STATIONARY || ent->s.apos.trType != TR_STATIONARY ) {
00489         G_MoverTeam( ent );
00490     }
00491 
00492     // check think function
00493     G_RunThink( ent );
00494 }
00495 
00496 /*
00497 ============================================================================
00498 
00499 GENERAL MOVERS
00500 
00501 Doors, plats, and buttons are all binary (two position) movers
00502 Pos1 is "at rest", pos2 is "activated"
00503 ============================================================================
00504 */
00505 
00506 /*
00507 ===============
00508 SetMoverState
00509 ===============
00510 */
00511 void SetMoverState( gentity_t *ent, moverState_t moverState, int time ) {
00512     vec3_t          delta;
00513     float           f;
00514 
00515     ent->moverState = moverState;
00516 
00517     ent->s.pos.trTime = time;
00518     switch( moverState ) {
00519     case MOVER_POS1:
00520         VectorCopy( ent->pos1, ent->s.pos.trBase );
00521         ent->s.pos.trType = TR_STATIONARY;
00522         break;
00523     case MOVER_POS2:
00524         VectorCopy( ent->pos2, ent->s.pos.trBase );
00525         ent->s.pos.trType = TR_STATIONARY;
00526         break;
00527     case MOVER_1TO2:
00528         VectorCopy( ent->pos1, ent->s.pos.trBase );
00529         VectorSubtract( ent->pos2, ent->pos1, delta );
00530         f = 1000.0 / ent->s.pos.trDuration;
00531         VectorScale( delta, f, ent->s.pos.trDelta );
00532         ent->s.pos.trType = TR_LINEAR_STOP;
00533         break;
00534     case MOVER_2TO1:
00535         VectorCopy( ent->pos2, ent->s.pos.trBase );
00536         VectorSubtract( ent->pos1, ent->pos2, delta );
00537         f = 1000.0 / ent->s.pos.trDuration;
00538         VectorScale( delta, f, ent->s.pos.trDelta );
00539         ent->s.pos.trType = TR_LINEAR_STOP;
00540         break;
00541     }
00542     BG_EvaluateTrajectory( &ent->s.pos, level.time, ent->r.currentOrigin ); 
00543     trap_LinkEntity( ent );
00544 }
00545 
00546 /*
00547 ================
00548 MatchTeam
00549 
00550 All entities in a mover team will move from pos1 to pos2
00551 in the same amount of time
00552 ================
00553 */
00554 void MatchTeam( gentity_t *teamLeader, int moverState, int time ) {
00555     gentity_t       *slave;
00556 
00557     for ( slave = teamLeader ; slave ; slave = slave->teamchain ) {
00558         SetMoverState( slave, moverState, time );
00559     }
00560 }
00561 
00562 
00563 
00564 /*
00565 ================
00566 ReturnToPos1
00567 ================
00568 */
00569 void ReturnToPos1( gentity_t *ent ) {
00570     MatchTeam( ent, MOVER_2TO1, level.time );
00571 
00572     // looping sound
00573     ent->s.loopSound = ent->soundLoop;
00574 
00575     // starting sound
00576     if ( ent->sound2to1 ) {
00577         G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound2to1 );
00578     }
00579 }
00580 
00581 
00582 /*
00583 ================
00584 Reached_BinaryMover
00585 ================
00586 */
00587 void Reached_BinaryMover( gentity_t *ent ) {
00588 
00589     // stop the looping sound
00590     ent->s.loopSound = ent->soundLoop;
00591 
00592     if ( ent->moverState == MOVER_1TO2 ) {
00593         // reached pos2
00594         SetMoverState( ent, MOVER_POS2, level.time );
00595 
00596         // play sound
00597         if ( ent->soundPos2 ) {
00598             G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundPos2 );
00599         }
00600 
00601         // return to pos1 after a delay
00602         ent->think = ReturnToPos1;
00603         ent->nextthink = level.time + ent->wait;
00604 
00605         // fire targets
00606         if ( !ent->activator ) {
00607             ent->activator = ent;
00608         }
00609         G_UseTargets( ent, ent->activator );
00610     } else if ( ent->moverState == MOVER_2TO1 ) {
00611         // reached pos1
00612         SetMoverState( ent, MOVER_POS1, level.time );
00613 
00614         // play sound
00615         if ( ent->soundPos1 ) {
00616             G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundPos1 );
00617         }
00618 
00619         // close areaportals
00620         if ( ent->teammaster == ent || !ent->teammaster ) {
00621             trap_AdjustAreaPortalState( ent, qfalse );
00622         }
00623     } else {
00624         G_Error( "Reached_BinaryMover: bad moverState" );
00625     }
00626 }
00627 
00628 
00629 /*
00630 ================
00631 Use_BinaryMover
00632 ================
00633 */
00634 void Use_BinaryMover( gentity_t *ent, gentity_t *other, gentity_t *activator ) {
00635     int     total;
00636     int     partial;
00637 
00638     // only the master should be used
00639     if ( ent->flags & FL_TEAMSLAVE ) {
00640         Use_BinaryMover( ent->teammaster, other, activator );
00641         return;
00642     }
00643 
00644     ent->activator = activator;
00645 
00646     if ( ent->moverState == MOVER_POS1 ) {
00647         // start moving 50 msec later, becase if this was player
00648         // triggered, level.time hasn't been advanced yet
00649         MatchTeam( ent, MOVER_1TO2, level.time + 50 );
00650 
00651         // starting sound
00652         if ( ent->sound1to2 ) {
00653             G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound1to2 );
00654         }
00655 
00656         // looping sound
00657         ent->s.loopSound = ent->soundLoop;
00658 
00659         // open areaportal
00660         if ( ent->teammaster == ent || !ent->teammaster ) {
00661             trap_AdjustAreaPortalState( ent, qtrue );
00662         }
00663         return;
00664     }
00665 
00666     // if all the way up, just delay before coming down
00667     if ( ent->moverState == MOVER_POS2 ) {
00668         ent->nextthink = level.time + ent->wait;
00669         return;
00670     }
00671 
00672     // only partway down before reversing
00673     if ( ent->moverState == MOVER_2TO1 ) {
00674         total = ent->s.pos.trDuration;
00675         partial = level.time - ent->s.pos.trTime;
00676         if ( partial > total ) {
00677             partial = total;
00678         }
00679 
00680         MatchTeam( ent, MOVER_1TO2, level.time - ( total - partial ) );
00681 
00682         if ( ent->sound1to2 ) {
00683             G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound1to2 );
00684         }
00685         return;
00686     }
00687 
00688     // only partway up before reversing
00689     if ( ent->moverState == MOVER_1TO2 ) {
00690         total = ent->s.pos.trDuration;
00691         partial = level.time - ent->s.pos.trTime;
00692         if ( partial > total ) {
00693             partial = total;
00694         }
00695 
00696         MatchTeam( ent, MOVER_2TO1, level.time - ( total - partial ) );
00697 
00698         if ( ent->sound2to1 ) {
00699             G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound2to1 );
00700         }
00701         return;
00702     }
00703 }
00704 
00705 
00706 
00707 /*
00708 ================
00709 InitMover
00710 
00711 "pos1", "pos2", and "speed" should be set before calling,
00712 so the movement delta can be calculated
00713 ================
00714 */
00715 void InitMover( gentity_t *ent ) {
00716     vec3_t      move;
00717     float       distance;
00718     float       light;
00719     vec3_t      color;
00720     qboolean    lightSet, colorSet;
00721     char        *sound;
00722 
00723     // if the "model2" key is set, use a seperate model
00724     // for drawing, but clip against the brushes
00725     if ( ent->model2 ) {
00726         ent->s.modelindex2 = G_ModelIndex( ent->model2 );
00727     }
00728 
00729     // if the "loopsound" key is set, use a constant looping sound when moving
00730     if ( G_SpawnString( "noise", "100", &sound ) ) {
00731         ent->s.loopSound = G_SoundIndex( sound );
00732     }
00733 
00734     // if the "color" or "light" keys are set, setup constantLight
00735     lightSet = G_SpawnFloat( "light", "100", &light );
00736     colorSet = G_SpawnVector( "color", "1 1 1", color );
00737     if ( lightSet || colorSet ) {
00738         int     r, g, b, i;
00739 
00740         r = color[0] * 255;
00741         if ( r > 255 ) {
00742             r = 255;
00743         }
00744         g = color[1] * 255;
00745         if ( g > 255 ) {
00746             g = 255;
00747         }
00748         b = color[2] * 255;
00749         if ( b > 255 ) {
00750             b = 255;
00751         }
00752         i = light / 4;
00753         if ( i > 255 ) {
00754             i = 255;
00755         }
00756         ent->s.constantLight = r | ( g << 8 ) | ( b << 16 ) | ( i << 24 );
00757     }
00758 
00759 
00760     ent->use = Use_BinaryMover;
00761     ent->reached = Reached_BinaryMover;
00762 
00763     ent->moverState = MOVER_POS1;
00764     ent->r.svFlags = SVF_USE_CURRENT_ORIGIN;
00765     ent->s.eType = ET_MOVER;
00766     VectorCopy (ent->pos1, ent->r.currentOrigin);
00767     trap_LinkEntity (ent);
00768 
00769     ent->s.pos.trType = TR_STATIONARY;
00770     VectorCopy( ent->pos1, ent->s.pos.trBase );
00771 
00772     // calculate time to reach second position from speed
00773     VectorSubtract( ent->pos2, ent->pos1, move );
00774     distance = VectorLength( move );
00775     if ( ! ent->speed ) {
00776         ent->speed = 100;
00777     }
00778     VectorScale( move, ent->speed, ent->s.pos.trDelta );
00779     ent->s.pos.trDuration = distance * 1000 / ent->speed;
00780     if ( ent->s.pos.trDuration <= 0 ) {
00781         ent->s.pos.trDuration = 1;
00782     }
00783 }
00784 
00785 
00786 /*
00787 ===============================================================================
00788 
00789 DOOR
00790 
00791 A use can be triggered either by a touch function, by being shot, or by being
00792 targeted by another entity.
00793 
00794 ===============================================================================
00795 */
00796 
00797 /*
00798 ================
00799 Blocked_Door
00800 ================
00801 */
00802 void Blocked_Door( gentity_t *ent, gentity_t *other ) {
00803     // remove anything other than a client
00804     if ( !other->client ) {
00805         // except CTF flags!!!!
00806         if( other->s.eType == ET_ITEM && other->item->giType == IT_TEAM ) {
00807             Team_DroppedFlagThink( other );
00808             return;
00809         }
00810         G_TempEntity( other->s.origin, EV_ITEM_POP );
00811         G_FreeEntity( other );
00812         return;
00813     }
00814 
00815     if ( ent->damage ) {
00816         G_Damage( other, ent, ent, NULL, NULL, ent->damage, 0, MOD_CRUSH );
00817     }
00818     if ( ent->spawnflags & 4 ) {
00819         return;     // crushers don't reverse
00820     }
00821 
00822     // reverse direction
00823     Use_BinaryMover( ent, ent, other );
00824 }
00825 
00826 /*
00827 ================
00828 Touch_DoorTriggerSpectator
00829 ================
00830 */
00831 static void Touch_DoorTriggerSpectator( gentity_t *ent, gentity_t *other, trace_t *trace ) {
00832     int i, axis;
00833     vec3_t origin, dir, angles;
00834 
00835     axis = ent->count;
00836     VectorClear(dir);
00837     if (fabs(other->s.origin[axis] - ent->r.absmax[axis]) <
00838         fabs(other->s.origin[axis] - ent->r.absmin[axis])) {
00839         origin[axis] = ent->r.absmin[axis] - 10;
00840         dir[axis] = -1;
00841     }
00842     else {
00843         origin[axis] = ent->r.absmax[axis] + 10;
00844         dir[axis] = 1;
00845     }
00846     for (i = 0; i < 3; i++) {
00847         if (i == axis) continue;
00848         origin[i] = (ent->r.absmin[i] + ent->r.absmax[i]) * 0.5;
00849     }
00850     vectoangles(dir, angles);
00851     TeleportPlayer(other, origin, angles );
00852 }
00853 
00854 /*
00855 ================
00856 Touch_DoorTrigger
00857 ================
00858 */
00859 void Touch_DoorTrigger( gentity_t *ent, gentity_t *other, trace_t *trace ) {
00860     if ( other->client && other->client->sess.sessionTeam == TEAM_SPECTATOR ) {
00861         // if the door is not open and not opening
00862         if ( ent->parent->moverState != MOVER_1TO2 &&
00863             ent->parent->moverState != MOVER_POS2) {
00864             Touch_DoorTriggerSpectator( ent, other, trace );
00865         }
00866     }
00867     else if ( ent->parent->moverState != MOVER_1TO2 ) {
00868         Use_BinaryMover( ent->parent, ent, other );
00869     }
00870 }
00871 
00872 
00873 /*
00874 ======================
00875 Think_SpawnNewDoorTrigger
00876 
00877 All of the parts of a door have been spawned, so create
00878 a trigger that encloses all of them
00879 ======================
00880 */
00881 void Think_SpawnNewDoorTrigger( gentity_t *ent ) {
00882     gentity_t       *other;
00883     vec3_t      mins, maxs;
00884     int         i, best;
00885 
00886     // set all of the slaves as shootable
00887     for ( other = ent ; other ; other = other->teamchain ) {
00888         other->takedamage = qtrue;
00889     }
00890 
00891     // find the bounds of everything on the team
00892     VectorCopy (ent->r.absmin, mins);
00893     VectorCopy (ent->r.absmax, maxs);
00894 
00895     for (other = ent->teamchain ; other ; other=other->teamchain) {
00896         AddPointToBounds (other->r.absmin, mins, maxs);
00897         AddPointToBounds (other->r.absmax, mins, maxs);
00898     }
00899 
00900     // find the thinnest axis, which will be the one we expand
00901     best = 0;
00902     for ( i = 1 ; i < 3 ; i++ ) {
00903         if ( maxs[i] - mins[i] < maxs[best] - mins[best] ) {
00904             best = i;
00905         }
00906     }
00907     maxs[best] += 120;
00908     mins[best] -= 120;
00909 
00910     // create a trigger with this size
00911     other = G_Spawn ();
00912     other->classname = "door_trigger";
00913     VectorCopy (mins, other->r.mins);
00914     VectorCopy (maxs, other->r.maxs);
00915     other->parent = ent;
00916     other->r.contents = CONTENTS_TRIGGER;
00917     other->touch = Touch_DoorTrigger;
00918     // remember the thinnest axis
00919     other->count = best;
00920     trap_LinkEntity (other);
00921 
00922     MatchTeam( ent, ent->moverState, level.time );
00923 }
00924 
00925 void Think_MatchTeam( gentity_t *ent ) {
00926     MatchTeam( ent, ent->moverState, level.time );
00927 }
00928 
00929 
00930 /*QUAKED func_door (0 .5 .8) ? START_OPEN x CRUSHER
00931 TOGGLE      wait in both the start and end states for a trigger event.
00932 START_OPEN  the door to moves to its destination when spawned, and operate in reverse.  It is used to temporarily or permanently close off an area when triggered (not useful for touch or takedamage doors).
00933 NOMONSTER   monsters will not trigger this door
00934 
00935 "model2"    .md3 model to also draw
00936 "angle"     determines the opening direction
00937 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
00938 "speed"     movement speed (100 default)
00939 "wait"      wait before returning (3 default, -1 = never return)
00940 "lip"       lip remaining at end of move (8 default)
00941 "dmg"       damage to inflict when blocked (2 default)
00942 "color"     constantLight color
00943 "light"     constantLight radius
00944 "health"    if set, the door must be shot open
00945 */
00946 void SP_func_door (gentity_t *ent) {
00947     vec3_t  abs_movedir;
00948     float   distance;
00949     vec3_t  size;
00950     float   lip;
00951 
00952     ent->sound1to2 = ent->sound2to1 = G_SoundIndex("sound/movers/doors/dr1_strt.wav");
00953     ent->soundPos1 = ent->soundPos2 = G_SoundIndex("sound/movers/doors/dr1_end.wav");
00954 
00955     ent->blocked = Blocked_Door;
00956 
00957     // default speed of 400
00958     if (!ent->speed)
00959         ent->speed = 400;
00960 
00961     // default wait of 2 seconds
00962     if (!ent->wait)
00963         ent->wait = 2;
00964     ent->wait *= 1000;
00965 
00966     // default lip of 8 units
00967     G_SpawnFloat( "lip", "8", &lip );
00968 
00969     // default damage of 2 points
00970     G_SpawnInt( "dmg", "2", &ent->damage );
00971 
00972     // first position at start
00973     VectorCopy( ent->s.origin, ent->pos1 );
00974 
00975     // calculate second position
00976     trap_SetBrushModel( ent, ent->model );
00977     G_SetMovedir (ent->s.angles, ent->movedir);
00978     abs_movedir[0] = fabs(ent->movedir[0]);
00979     abs_movedir[1] = fabs(ent->movedir[1]);
00980     abs_movedir[2] = fabs(ent->movedir[2]);
00981     VectorSubtract( ent->r.maxs, ent->r.mins, size );
00982     distance = DotProduct( abs_movedir, size ) - lip;
00983     VectorMA( ent->pos1, distance, ent->movedir, ent->pos2 );
00984 
00985     // if "start_open", reverse position 1 and 2
00986     if ( ent->spawnflags & 1 ) {
00987         vec3_t  temp;
00988 
00989         VectorCopy( ent->pos2, temp );
00990         VectorCopy( ent->s.origin, ent->pos2 );
00991         VectorCopy( temp, ent->pos1 );
00992     }
00993 
00994     InitMover( ent );
00995 
00996     ent->nextthink = level.time + FRAMETIME;
00997 
00998     if ( ! (ent->flags & FL_TEAMSLAVE ) ) {
00999         int health;
01000 
01001         G_SpawnInt( "health", "0", &health );
01002         if ( health ) {
01003             ent->takedamage = qtrue;
01004         }
01005         if ( ent->targetname || health ) {
01006             // non touch/shoot doors
01007             ent->think = Think_MatchTeam;
01008         } else {
01009             ent->think = Think_SpawnNewDoorTrigger;
01010         }
01011     }
01012 
01013 
01014 }
01015 
01016 /*
01017 ===============================================================================
01018 
01019 PLAT
01020 
01021 ===============================================================================
01022 */
01023 
01024 /*
01025 ==============
01026 Touch_Plat
01027 
01028 Don't allow decent if a living player is on it
01029 ===============
01030 */
01031 void Touch_Plat( gentity_t *ent, gentity_t *other, trace_t *trace ) {
01032     if ( !other->client || other->client->ps.stats[STAT_HEALTH] <= 0 ) {
01033         return;
01034     }
01035 
01036     // delay return-to-pos1 by one second
01037     if ( ent->moverState == MOVER_POS2 ) {
01038         ent->nextthink = level.time + 1000;
01039     }
01040 }
01041 
01042 /*
01043 ==============
01044 Touch_PlatCenterTrigger
01045 
01046 If the plat is at the bottom position, start it going up
01047 ===============
01048 */
01049 void Touch_PlatCenterTrigger(gentity_t *ent, gentity_t *other, trace_t *trace ) {
01050     if ( !other->client ) {
01051         return;
01052     }
01053 
01054     if ( ent->parent->moverState == MOVER_POS1 ) {
01055         Use_BinaryMover( ent->parent, ent, other );
01056     }
01057 }
01058 
01059 
01060 /*
01061 ================
01062 SpawnPlatTrigger
01063 
01064 Spawn a trigger in the middle of the plat's low position
01065 Elevator cars require that the trigger extend through the entire low position,
01066 not just sit on top of it.
01067 ================
01068 */
01069 void SpawnPlatTrigger( gentity_t *ent ) {
01070     gentity_t   *trigger;
01071     vec3_t  tmin, tmax;
01072 
01073     // the middle trigger will be a thin trigger just
01074     // above the starting position
01075     trigger = G_Spawn();
01076     trigger->classname = "plat_trigger";
01077     trigger->touch = Touch_PlatCenterTrigger;
01078     trigger->r.contents = CONTENTS_TRIGGER;
01079     trigger->parent = ent;
01080     
01081     tmin[0] = ent->pos1[0] + ent->r.mins[0] + 33;
01082     tmin[1] = ent->pos1[1] + ent->r.mins[1] + 33;
01083     tmin[2] = ent->pos1[2] + ent->r.mins[2];
01084 
01085     tmax[0] = ent->pos1[0] + ent->r.maxs[0] - 33;
01086     tmax[1] = ent->pos1[1] + ent->r.maxs[1] - 33;
01087     tmax[2] = ent->pos1[2] + ent->r.maxs[2] + 8;
01088 
01089     if ( tmax[0] <= tmin[0] ) {
01090         tmin[0] = ent->pos1[0] + (ent->r.mins[0] + ent->r.maxs[0]) *0.5;
01091         tmax[0] = tmin[0] + 1;
01092     }
01093     if ( tmax[1] <= tmin[1] ) {
01094         tmin[1] = ent->pos1[1] + (ent->r.mins[1] + ent->r.maxs[1]) *0.5;
01095         tmax[1] = tmin[1] + 1;
01096     }
01097     
01098     VectorCopy (tmin, trigger->r.mins);
01099     VectorCopy (tmax, trigger->r.maxs);
01100 
01101     trap_LinkEntity (trigger);
01102 }
01103 
01104 
01105 /*QUAKED func_plat (0 .5 .8) ?
01106 Plats are always drawn in the extended position so they will light correctly.
01107 
01108 "lip"       default 8, protrusion above rest position
01109 "height"    total height of movement, defaults to model height
01110 "speed"     overrides default 200.
01111 "dmg"       overrides default 2
01112 "model2"    .md3 model to also draw
01113 "color"     constantLight color
01114 "light"     constantLight radius
01115 */
01116 void SP_func_plat (gentity_t *ent) {
01117     float       lip, height;
01118 
01119     ent->sound1to2 = ent->sound2to1 = G_SoundIndex("sound/movers/plats/pt1_strt.wav");
01120     ent->soundPos1 = ent->soundPos2 = G_SoundIndex("sound/movers/plats/pt1_end.wav");
01121 
01122     VectorClear (ent->s.angles);
01123 
01124     G_SpawnFloat( "speed", "200", &ent->speed );
01125     G_SpawnInt( "dmg", "2", &ent->damage );
01126     G_SpawnFloat( "wait", "1", &ent->wait );
01127     G_SpawnFloat( "lip", "8", &lip );
01128 
01129     ent->wait = 1000;
01130 
01131     // create second position
01132     trap_SetBrushModel( ent, ent->model );
01133 
01134     if ( !G_SpawnFloat( "height", "0", &height ) ) {
01135         height = (ent->r.maxs[2] - ent->r.mins[2]) - lip;
01136     }
01137 
01138     // pos1 is the rest (bottom) position, pos2 is the top
01139     VectorCopy( ent->s.origin, ent->pos2 );
01140     VectorCopy( ent->pos2, ent->pos1 );
01141     ent->pos1[2] -= height;
01142 
01143     InitMover( ent );
01144 
01145     // touch function keeps the plat from returning while
01146     // a live player is standing on it
01147     ent->touch = Touch_Plat;
01148 
01149     ent->blocked = Blocked_Door;
01150 
01151     ent->parent = ent;  // so it can be treated as a door
01152 
01153     // spawn the trigger if one hasn't been custom made
01154     if ( !ent->targetname ) {
01155         SpawnPlatTrigger(ent);
0