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) )
01246                         {
01247                                 return owner;
01248                         }
01249                 }
01250                 owner = NULL;
01251                 while ( (owner = G_Find( owner, FOFS( target2 ), door->targetname )) != NULL )
01252                 {
01253                         if ( owner && (owner->r.contents&CONTENTS_TRIGGER) )
01254                         {
01255                                 return owner;
01256                         }
01257                 }
01258         }
01259 
01260         owner = NULL;
01261         while ( (owner = G_Find( owner, FOFS( classname ), "trigger_door" )) != NULL )
01262         {
01263                 if ( owner->parent == door )
01264                 {
01265                         return owner;
01266                 }
01267         }
01268 
01269         return NULL;
01270 }
01271 
01272 qboolean G_EntIsUnlockedDoor( int entityNum )
01273 {
01274         if ( entityNum < 0 || entityNum >= ENTITYNUM_WORLD )
01275         {
01276                 return qfalse;
01277         }
01278 
01279         if ( G_EntIsDoor( entityNum ) )
01280         {
01281                 gentity_t *ent = &g_entities[entityNum];
01282                 gentity_t *owner = NULL;
01283                 if ( ent->flags & FL_TEAMSLAVE )
01284                 {//not the master door, get the master door
01285                         while ( ent->teammaster && (ent->flags&FL_TEAMSLAVE))
01286                         {
01287                                 ent = ent->teammaster;
01288                         }
01289                 }
01290                 if ( ent->targetname )
01291                 {//find out what is targetting it
01292                         owner = NULL;
01293                         //FIXME: if ent->targetname, check what kind of trigger/ent is targetting it?  If a normal trigger (active, etc), then it's okay?
01294                         while ( (owner = G_Find( owner, FOFS( target ), ent->targetname )) != NULL )
01295                         {
01296                                 if ( !Q_stricmp( "trigger_multiple", owner->classname ) )//FIXME: other triggers okay too?
01297                                 {
01298                                         if ( !(owner->flags & FL_INACTIVE) )
01299                                         {
01300                                                 return qtrue;
01301                                         }
01302                                 }
01303                         }
01304                         owner = NULL;
01305                         while ( (owner = G_Find( owner, FOFS( target2 ), ent->targetname )) != NULL )
01306                         {
01307                                 if ( !Q_stricmp( "trigger_multiple", owner->classname ) )//FIXME: other triggers okay too?
01308                                 {
01309                                         if ( !(owner->flags & FL_INACTIVE) )
01310                                         {
01311                                                 return qtrue;
01312                                         }
01313                                 }
01314                         }
01315                         return qfalse;
01316                 }
01317                 else
01318                 {//check the door's auto-created trigger instead
01319                         owner = G_FindDoorTrigger( ent );
01320                         if ( owner && (owner->flags&FL_INACTIVE) )
01321                         {//owning auto-created trigger is inactive
01322                                 return qfalse;
01323                         }
01324                 }
01325                 if ( !(ent->flags & FL_INACTIVE) && //assumes that the reactivate trigger isn't right next to the door!
01326                         !ent->health &&
01327                         !(ent->spawnflags & MOVER_PLAYER_USE) &&
01328                         !(ent->spawnflags & MOVER_FORCE_ACTIVATE) &&
01329                         !(ent->spawnflags & MOVER_LOCKED))
01330                         //FIXME: what about MOVER_GOODIE?
01331                 {
01332                         return qtrue;
01333                 }
01334         }
01335         return qfalse;
01336 }
01337 
01338 
01339 /*QUAKED func_door (0 .5 .8) ? START_OPEN FORCE_ACTIVATE CRUSHER TOGGLE LOCKED x PLAYER_USE INACTIVE
01340 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).
01341 FORCE_ACTIVATE  Can only be activated by a force push or pull
01342 CRUSHER         ?
01343 TOGGLE          wait in both the start and end states for a trigger event - does NOT work on Trek doors.
01344 LOCKED          Starts locked, with the shader animmap at the first frame and inactive.  Once used, the animmap changes to the second frame and the door operates normally.  Note that you cannot use the door again after this.
01345 PLAYER_USE      Player can use it with the use button
01346 INACTIVE        must be used by a target_activate before it can be used
01347 
01348 "target"     Door fires this when it starts moving from it's closed position to its open position.
01349 "opentarget" Door fires this after reaching its "open" position
01350 "target2"    Door fires this when it starts moving from it's open position to its closed position.
01351 "closetarget" Door fires this after reaching its "closed" position
01352 "model2"        .md3 model to also draw
01353 "angle"         determines the opening direction
01354 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
01355 "speed"         movement speed (100 default)
01356 "wait"          wait before returning (3 default, -1 = never return)
01357 "delay"         when used, how many seconds to wait before moving - default is none
01358 "lip"           lip remaining at end of move (8 default)
01359 "dmg"           damage to inflict when blocked (2 default, set to negative for no damage)
01360 "color"         constantLight color
01361 "light"         constantLight radius
01362 "health"        if set, the door must be shot open
01363 "linear"        set to 1 and it will move linearly rather than with acceleration (default is 0)
01364 "teamallow" even if locked, this team can always open and close it just by walking up to it
01365         0 - none (locked to everyone)
01366         1 - red
01367         2 - blue
01368 "vehopen"       if non-0, vehicles/players riding vehicles can open
01369 */
01370 void SP_func_door (gentity_t *ent) 
01371 {
01372         vec3_t  abs_movedir;
01373         float   distance;
01374         vec3_t  size;
01375         float   lip;
01376 
01377         G_SpawnInt("vehopen", "0", &ent->genericValue14);
01378 
01379         ent->blocked = Blocked_Door;
01380 
01381         // default speed of 400
01382         if (!ent->speed)
01383                 ent->speed = 400;
01384 
01385         // default wait of 2 seconds
01386         if (!ent->wait)
01387                 ent->wait = 2;
01388         ent->wait *= 1000;
01389 
01390         ent->delay *= 1000;
01391 
01392         // default lip of 8 units
01393         G_SpawnFloat( "lip", "8", &lip );
01394 
01395         // default damage of 2 points
01396         G_SpawnInt( "dmg", "2", &ent->damage );
01397         if ( ent->damage < 0 )
01398         {
01399                 ent->damage = 0;
01400         }
01401 
01402         G_SpawnInt( "teamallow", "0", &ent->alliedTeam );
01403 
01404         // first position at start
01405         VectorCopy( ent->s.origin, ent->pos1 );
01406 
01407         // calculate second position
01408         trap_SetBrushModel( ent, ent->model );
01409         G_SetMovedir( ent->s.angles, ent->movedir );
01410         abs_movedir[0] = fabs( ent->movedir[0] );
01411         abs_movedir[1] = fabs( ent->movedir[1] );
01412         abs_movedir[2] = fabs( ent->movedir[2] );
01413         VectorSubtract( ent->r.maxs, ent->r.mins, size );
01414         distance = DotProduct( abs_movedir, size ) - lip;
01415         VectorMA( ent->pos1, distance, ent->movedir, ent->pos2 );
01416 
01417         // if "start_open", reverse position 1 and 2
01418         if ( ent->spawnflags & 1 ) 
01419         {
01420                 vec3_t  temp;
01421 
01422                 VectorCopy( ent->pos2, temp );
01423                 VectorCopy( ent->s.origin, ent->pos2 );
01424                 VectorCopy( temp, ent->pos1 );
01425         }
01426 
01427         if ( ent->spawnflags & MOVER_LOCKED )
01428         {//a locked door, set up as locked until used directly
01429                 ent->s.eFlags |= EF_SHADER_ANIM;//use frame-controlled shader anim
01430                 ent->s.frame = 0;//first stage of anim
01431         }
01432         InitMover( ent );
01433 
01434         ent->nextthink = level.time + FRAMETIME;
01435 
01436         if ( !(ent->flags&FL_TEAMSLAVE) ) 
01437         {
01438                 int health;
01439 
01440                 G_SpawnInt( "health", "0", &health );
01441                 
01442                 if ( health ) 
01443                 {
01444                         ent->takedamage = qtrue;
01445                 }
01446                 
01447                 if ( !(ent->spawnflags&MOVER_LOCKED) && (ent->targetname || health || ent->spawnflags & MOVER_PLAYER_USE || ent->spawnflags & MOVER_FORCE_ACTIVATE) )
01448                 {
01449                         // non touch/shoot doors
01450                         ent->think = Think_MatchTeam;
01451 
01452                         if (ent->spawnflags & MOVER_FORCE_ACTIVATE)
01453                         { //so we know it's push/pullable on the client
01454                                 ent->s.bolt1 = 1;
01455                         }
01456                 } 
01457                 else 
01458                 {//locked doors still spawn a trigger
01459                         ent->think = Think_SpawnNewDoorTrigger;
01460                 }
01461         }
01462 }
01463 
01464 /*
01465 ===============================================================================
01466 
01467 PLAT
01468 
01469 ===============================================================================
01470 */
01471 
01472 /*
01473 ==============
01474 Touch_Plat
01475 
01476 Don't allow decent if a living player is on it
01477 ===============
01478 */
01479 void Touch_Plat( gentity_t *ent, gentity_t *other, trace_t *trace ) {
01480         if ( !other->client || other->client->ps.stats[STAT_HEALTH] <= 0 ) {
01481                 return;
01482         }
01483 
01484         // delay return-to-pos1 by one second
01485         if ( ent->moverState == MOVER_POS2 ) {
01486                 ent->nextthink = level.time + 1000;
01487         }
01488 }
01489 
01490 /*
01491 ==============
01492 Touch_PlatCenterTrigger
01493 
01494 If the plat is at the bottom position, start it going up
01495 ===============
01496 */
01497 void Touch_PlatCenterTrigger(gentity_t *ent, gentity_t *other, trace_t *trace ) {
01498         if ( !other->client ) {
01499                 return;
01500         }
01501 
01502         if ( ent->parent->moverState == MOVER_POS1 ) {
01503                 Use_BinaryMover( ent->parent, ent, other );
01504         }
01505 }
01506 
01507 
01508 /*
01509 ================
01510 SpawnPlatTrigger
01511 
01512 Spawn a trigger in the middle of the plat's low position
01513 Elevator cars require that the trigger extend through the entire low position,
01514 not just sit on top of it.
01515 ================
01516 */
01517 void SpawnPlatTrigger( gentity_t *ent ) {
01518         gentity_t       *trigger;
01519         vec3_t  tmin, tmax;
01520 
01521         // the middle trigger will be a thin trigger just
01522         // above the starting position
01523         trigger = G_Spawn();
01524         trigger->touch = Touch_PlatCenterTrigger;
01525         trigger->r.contents = CONTENTS_TRIGGER;
01526         trigger->parent = ent;
01527         
01528         tmin[0] = ent->pos1[0] + ent->r.mins[0] + 33;
01529         tmin[1] = ent->pos1[1] + ent->r.mins[1] + 33;
01530         tmin[2] = ent->pos1[2] + ent->r.mins[2];
01531 
01532         tmax[0] = ent->pos1[0] + ent->r.maxs[0] - 33;
01533         tmax[1] = ent->pos1[1] + ent->r.maxs[1] - 33;
01534         tmax[2] = ent->pos1[2] + ent->r.maxs[2] + 8;
01535 
01536         if ( tmax[0] <= tmin[0] ) {
01537                 tmin[0] = ent->pos1[0] + (ent->r.mins[0] + ent->r.maxs[0]) *0.5;
01538                 tmax[0] = tmin[0] + 1;
01539         }
01540         if ( tmax[1] <= tmin[1] ) {
01541                 tmin[1] = ent->pos1[1] + (ent->r.mins[1] + ent->r.maxs[1]) *0.5;
01542                 tmax[1] = tmin[1] + 1;
01543         }
01544         
01545         VectorCopy (tmin, trigger->r.mins);
01546         VectorCopy (tmax, trigger->r.maxs);
01547 
01548         trap_LinkEntity (trigger);
01549 }
01550 
01551 
01552 /*QUAKED func_plat (0 .5 .8) ? x x x x x x PLAYER_USE INACTIVE
01553 PLAYER_USE      Player can use it with the use button
01554 INACTIVE        must be used by a target_activate before it can be used
01555 
01556 Plats are always drawn in the extended position so they will light correctly.
01557 
01558 "lip"           default 8, protrusion above rest position
01559 "height"        total height of movement, defaults to model height
01560 "speed"         overrides default 200.
01561 "dmg"           overrides default 2
01562 "model2"        .md3 model to also draw
01563 "color"         constantLight color
01564 "light"         constantLight radius
01565 */
01566 void SP_func_plat (gentity_t *ent) {
01567         float           lip, height;
01568 
01569 //      ent->sound1to2 = ent->sound2to1 = G_SoundIndex("sound/movers/plats/pt1_strt.wav");
01570 //      ent->soundPos1 = ent->soundPos2 = G_SoundIndex("sound/movers/plats/pt1_end.wav");
01571 
01572         VectorClear (ent->s.angles);
01573 
01574         G_SpawnFloat( "speed", "200", &ent->speed );
01575         G_SpawnInt( "dmg", "2", &ent->damage );
01576         G_SpawnFloat( "wait", "1", &ent->wait );
01577         G_SpawnFloat( "lip", "8", &lip );
01578 
01579         ent->wait = 1000;
01580 
01581         // create second position
01582         trap_SetBrushModel( ent, ent->model );
01583 
01584         if ( !G_SpawnFloat( "height", "0", &height ) ) {
01585                 height = (ent->r.maxs[2] - ent->r.mins[2]) - lip;
01586         }
01587 
01588         // pos1 is the rest (bottom) position, pos2 is the top
01589         VectorCopy( ent->s.origin, ent->pos2 );
01590         VectorCopy( ent->pos2, ent->pos1 );
01591         ent->pos1[2] -= height;
01592 
01593         InitMover( ent );
01594 
01595         // touch function keeps the plat from returning while
01596         // a live player is standing on it
01597         ent->touch = Touch_Plat;
01598 
01599         ent->blocked = Blocked_Door;
01600 
01601         ent->parent = ent;      // so it can be treated as a door
01602 
01603         // spawn the trigger if one hasn't been custom made
01604         if ( !ent->targetname ) {
01605                 SpawnPlatTrigger(ent);
01606         }
01607 }
01608 
01609 /*
01610 ===============================================================================
01611 
01612 BUTTON
01613 
01614 ===============================================================================
01615 */
01616 
01617 /*
01618 ==============
01619 Touch_Button
01620 
01621 ===============
01622 */
01623 void Touch_Button(gentity_t *ent, gentity_t *other, trace_t *trace ) {
01624         if ( !other->client ) {
01625                 return;
01626         }
01627 
01628         if ( ent->moverState == MOVER_POS1 ) {
01629                 Use_BinaryMover( ent, other, other );
01630         }
01631 }
01632 
01633 
01634 /*QUAKED func_button (0 .5 .8) ? x x x x x x PLAYER_USE INACTIVE
01635 PLAYER_USE      Player can use it with the use button
01636 INACTIVE        must be used by a target_activate before it can be used
01637 
01638 When a button is touched, it moves some distance in the direction of it's angle, triggers all of it's targets, waits some time, then returns to it's original position where it can be triggered again.
01639 
01640 "model2"        .md3 model to also draw
01641 "angle"         determines the opening direction
01642 "target"        all entities with a matching targetname will be used
01643 "speed"         override the default 40 speed
01644 "wait"          override the default 1 second wait (-1 = never return)
01645 "lip"           override the default 4 pixel lip remaining at end of move
01646 "health"        if set, the button must be killed instead of touched
01647 "color"         constantLight color
01648 "light"         constantLight radius
01649 */
01650 void SP_func_button( gentity_t *ent ) {
01651         vec3_t          abs_movedir;
01652         float           distance;
01653         vec3_t          size;
01654         float           lip;
01655 
01656 //      ent->sound1to2 = G_SoundIndex("sound/movers/switches/butn2.wav");
01657         
01658         if ( !ent->speed ) {
01659                 ent->speed = 40;
01660         }
01661 
01662         if ( !ent->wait ) {
01663                 ent->wait = 1;
01664         }
01665         ent->wait *= 1000;
01666 
01667         // first position
01668         VectorCopy( ent->s.origin, ent->pos1 );
01669 
01670         // calculate second position
01671         trap_SetBrushModel( ent, ent->model );
01672 
01673         G_SpawnFloat( "lip", "4", &lip );
01674 
01675         G_SetMovedir( ent->s.angles, ent->movedir );
01676         abs_movedir[0] = fabs(ent->movedir[0]);
01677         abs_movedir[1] = fabs(ent->movedir[1]);
01678         abs_movedir[2] = fabs(ent->movedir[2]);
01679         VectorSubtract( ent->r.maxs, ent->r.mins, size );
01680         distance = abs_movedir[0] * size[0] + abs_movedir[1] * size[1] + abs_movedir[2] * size[2] - lip;
01681         VectorMA (ent->pos1, distance, ent->movedir, ent->pos2);
01682 
01683         if (ent->health) {
01684                 // shootable button
01685                 ent->takedamage = qtrue;
01686         } else {
01687                 // touchable button
01688                 ent->touch = Touch_Button;
01689         }
01690 
01691         InitMover( ent );
01692 }
01693 
01694 
01695 
01696 
01697 /*
01698 ===============================================================================
01699 
01700 TRAIN
01701 
01702 ===============================================================================
01703 */
01704 
01705 
01706 #define TRAIN_START_ON          1
01707 #define TRAIN_TOGGLE            2
01708 #define TRAIN_BLOCK_STOPS       4
01709 
01710 /*
01711 ===============
01712 Think_BeginMoving
01713 
01714 The wait time at a corner has completed, so start moving again
01715 ===============
01716 */
01717 void Think_BeginMoving( gentity_t *ent ) {
01718         G_PlayDoorSound( ent, BMS_START );
01719         G_PlayDoorLoopSound( ent );
01720         ent->s.pos.trTime = level.time;
01721         ent->s.pos.trType = TR_LINEAR_STOP;
01722 }
01723 
01724 /*
01725 ===============
01726 Reached_Train
01727 ===============
01728 */
01729 void Reached_Train( gentity_t *ent ) {
01730         gentity_t               *next;
01731         float                   speed;
01732         vec3_t                  move;
01733         float                   length;
01734 
01735         // copy the apropriate values
01736         next = ent->nextTrain;
01737         if ( !next || !next->nextTrain ) {
01738                 return;         // just stop
01739         }
01740 
01741         // fire all other targets
01742         G_UseTargets( next, NULL );
01743 
01744         // set the new trajectory
01745         ent->nextTrain = next->nextTrain;
01746         VectorCopy( next->s.origin, ent->pos1 );
01747         VectorCopy( next->nextTrain->s.origin, ent->pos2 );
01748 
01749         // if the path_corner has a speed, use that
01750         if ( next->speed ) {
01751                 speed = next->speed;
01752         } else {
01753                 // otherwise use the train's speed
01754                 speed = ent->speed;
01755         }
01756         if ( speed < 1 ) {
01757                 speed = 1;
01758         }
01759 
01760         // calculate duration
01761         VectorSubtract( ent->pos2, ent->pos1, move );
01762         length = VectorLength( move );
01763 
01764         ent->s.pos.trDuration = length * 1000 / speed;
01765 
01766         // start it going
01767         SetMoverState( ent, MOVER_1TO2, level.time );
01768 
01769         G_PlayDoorSound( ent, BMS_END );
01770 
01771         // if there is a "wait" value on the target, don't start moving yet
01772         if ( next->wait ) {
01773                 ent->s.loopSound = 0;
01774                 ent->s.loopIsSoundset = qfalse;
01775                 ent->nextthink = level.time + next->wait * 1000;
01776                 ent->think = Think_BeginMoving;
01777                 ent->s.pos.trType = TR_STATIONARY;
01778         }
01779         else
01780         {
01781                 G_PlayDoorLoopSound( ent );
01782         }
01783 }
01784 
01785 /*
01786 ===============
01787 Think_SetupTrainTargets
01788 
01789 Link all the corners together
01790 ===============
01791 */
01792 void Think_SetupTrainTargets( gentity_t *ent ) {
01793         gentity_t               *path, *next, *start;
01794 
01795         ent->nextTrain = G_Find( NULL, FOFS(targetname), ent->target );
01796         if ( !ent->nextTrain ) {
01797                 Com_Printf( "func_train at %s with an unfound target\n",
01798                         vtos(ent->r.absmin) );
01799                 //Free me?`
01800                 return;
01801         }
01802 
01803         //FIXME: this can go into an infinite loop if last path_corner doesn't link to first
01804         //path_corner, like so:
01805         // t1---->t2---->t3
01806         //         ^      |
01807         //          \_____|
01808         start = NULL;
01809         for ( path = ent->nextTrain ; path != start ; path = next ) {
01810                 if ( !start ) {
01811                         start = path;
01812                 }
01813 
01814                 if ( !path->target ) {
01815 //                      gi.Printf( "Train corner at %s without a target\n",
01816 //                              vtos(path->s.origin) );
01817                         //end of path
01818                         break;
01819                 }
01820 
01821                 // find a path_corner among the targets
01822                 // there may also be other targets that get fired when the corner
01823                 // is reached
01824                 next = NULL;
01825                 do {
01826                         next = G_Find( next, FOFS(targetname), path->target );
01827                         if ( !next ) {
01828 //                              gi.Printf( "Train corner at %s without a target path_corner\n",
01829 //                                      vtos(path->s.origin) );
01830                                 //end of path
01831                                 break;
01832                         }
01833                 } while ( strcmp( next->classname, "path_corner" ) );
01834 
01835                 if ( next )
01836                 {
01837                         path->nextTrain = next;
01838                 }
01839                 else
01840                 {
01841                         break;
01842                 }
01843         }
01844 
01845         if ( !ent->targetname || (ent->spawnflags&1) /*start on*/)
01846         {
01847                 // start the train moving from the first corner
01848                 Reached_Train( ent );
01849         }
01850         else
01851         {
01852                 G_SetOrigin( ent, ent->s.origin );
01853         }
01854 }
01855 
01856 
01857 /*QUAKED path_corner (.5 .3 0) (-8 -8 -8) (8 8 8)
01858 Train path corners.
01859 Target: next path corner and other targets to fire
01860 "speed" speed to move to the next corner
01861 "wait" seconds to wait before behining move to next corner
01862 */
01863 void SP_path_corner( gentity_t *self ) {
01864         if ( !self->targetname ) {
01865                 G_Printf ("path_corner with no targetname at %s\n", vtos(self->s.origin));
01866                 G_FreeEntity( self );
01867                 return;
01868         }
01869         // path corners don't need to be linked in
01870 }
01871 
01872 
01873 
01874 /*QUAKED func_train (0 .5 .8) ? START_ON TOGGLE BLOCK_STOPS ? ? CRUSH_THROUGH PLAYER_USE INACTIVE
01875 A train is a mover that moves between path_corner target points.
01876 Trains MUST HAVE AN ORIGIN BRUSH.
01877 The train spawns at the first target it is pointing at.
01878 CRUSH_THROUGH spawnflag combined with a dmg value will make the train pass through
01879 entities and damage them on contact as well.
01880 "model2"        .md3 model to also draw
01881 "speed"         default 100
01882 "dmg"           default 2
01883 "target"        next path corner
01884 "color"         constantLight color
01885 "light"         constantLight radius
01886 */
01887 void SP_func_train (gentity_t *self) {
01888         VectorClear (self->s.angles);
01889 
01890         if (self->spawnflags & TRAIN_BLOCK_STOPS) {
01891                 self->damage = 0;
01892         } else {
01893                 if (!self->damage) {
01894                         self->damage = 2;
01895                 }
01896         }
01897 
01898         if ( !self->speed ) {
01899                 self->speed = 100;
01900         }
01901 
01902         if ( !self->target ) {
01903                 G_Printf ("func_train without a target at %s\n", vtos(self->r.absmin));
01904                 G_FreeEntity( self );
01905                 return;
01906         }
01907 
01908         trap_SetBrushModel( self, self->model );
01909         InitMover( self );
01910 
01911         self->reached = Reached_Train;
01912 
01913         // start trains on the second frame, to make sure their targets have had
01914         // a chance to spawn
01915         self->nextthink = level.time + FRAMETIME;
01916         self->think = Think_SetupTrainTargets;
01917 }
01918 
01919 /*
01920 ===============================================================================
01921 
01922 STATIC
01923 
01924 ===============================================================================
01925 */
01926 
01927 void func_static_use ( gentity_t *self, gentity_t *other, gentity_t *activator );
01928 /*QUAKED func_static (0 .5 .8) ? F_PUSH F_PULL SWITCH_SHADER CRUSHER IMPACT x PLAYER_USE INACTIVE BROADCAST
01929 F_PUSH          Will be used when you Force-Push it
01930 F_PULL          Will be used when you Force-Pull it
01931 SWITCH_SHADER   Toggle the shader anim frame between 1 and 2 when used
01932 CRUSHER         Make it do damage when it's blocked
01933 IMPACT          Make it do damage when it hits any entity
01934 PLAYER_USE      Player can use it with the use button
01935 INACTIVE        must be used by a target_activate before it can be used
01936 BROADCAST   don't ever use this, it's evil
01937 
01938 A bmodel that just sits there, doing nothing.  Can be used for conditional walls and models.
01939 "model2"        .md3 model to also draw
01940 "model2scale"   percent of normal scale (on all x y and z axii) to scale the model2 if there is one. 100 is normal scale, min is 1 (100 times smaller than normal), max is 1000 (ten times normal).
01941 "color"         constantLight color
01942 "light"         constantLight radius
01943 "dmg"           how much damage to do when it crushes (use with CRUSHER spawnflag)
01944 "linear" set to 1 and it will move linearly rather than with acceleration (default is 0)
01945 */
01946 void SP_func_static( gentity_t *ent ) 
01947 {
01948         int             test;
01949         trap_SetBrushModel( ent, ent->model );
01950 
01951         VectorCopy( ent->s.origin, ent->pos1 );
01952         VectorCopy( ent->s.origin, ent->pos2 );
01953 
01954         InitMover( ent );
01955 
01956         ent->use = func_static_use;
01957         ent->reached = 0;
01958 
01959         G_SetOrigin( ent, ent->s.origin );
01960         G_SetAngles( ent, ent->s.angles );
01961 
01962         if( ent->spawnflags & 2048 )
01963         {                                                                  // yes this is very very evil, but for now (pre-alpha) it's a solution
01964                 ent->r.svFlags |= SVF_BROADCAST; // I need to rotate something that is huge and it's touching too many area portals...
01965         }
01966 
01967         if ( ent->spawnflags & 4/*SWITCH_SHADER*/ )
01968         {
01969                 ent->s.eFlags |= EF_SHADER_ANIM;//use frame-controlled shader anim
01970                 ent->s.frame = 0;//first stage of anim
01971         }
01972 
01973         if ((ent->spawnflags & 1) || (ent->spawnflags & 2))
01974         { //so we know it's push/pullable on the client
01975                 ent->s.bolt1 = 1;
01976         }
01977 
01978 #ifdef _XBOX
01979         int     tempModelScale;
01980         G_SpawnInt("model2scale", "0", &tempModelScale);
01981         ent->s.iModelScale = tempModelScale;
01982 #else
01983         G_SpawnInt("model2scale", "0", &ent->s.iModelScale);
01984 #endif
01985         if (ent->s.iModelScale < 0)
01986         {
01987                 ent->s.iModelScale = 0;
01988         }
01989         else if (ent->s.iModelScale > 1023)
01990         {
01991                 ent->s.iModelScale = 1023;
01992         }
01993 
01994         G_SpawnInt( "hyperspace", "0", &test );
01995         if ( test )
01996         {
01997                 ent->r.svFlags |= SVF_BROADCAST; // I need to rotate something that is huge and it's touching too many area portals...
01998                 ent->s.eFlags2 |= EF2_HYPERSPACE;
01999         }
02000 
02001         trap_LinkEntity( ent );
02002 
02003         if (level.mBSPInstanceDepth)
02004         {       // this means that this guy will never be updated, moved, changed, etc.
02005                 ent->s.eFlags = EF_PERMANENT;
02006         }
02007 }
02008 
02009 void func_static_use ( gentity_t *self, gentity_t *other, gentity_t *activator )
02010 {
02011         G_ActivateBehavior( self, BSET_USE );
02012 
02013         if ( self->spawnflags & 4/*SWITCH_SHADER*/ )
02014         {
02015                 self->s.frame = self->s.frame ? 0 : 1;//toggle frame
02016         }
02017         G_UseTargets( self, activator );
02018 }
02019 
02020 /*
02021 ===============================================================================
02022 
02023 ROTATING
02024 
02025 ===============================================================================
02026 */
02027 
02028 void func_rotating_use( gentity_t *self, gentity_t *other, gentity_t *activator )
02029 {
02030         if(     self->s.apos.trType == TR_LINEAR )
02031         {
02032                 self->s.apos.trType = TR_STATIONARY;
02033                 // stop the sound if it stops moving
02034                 self->s.loopSound = 0;
02035                 self->s.loopIsSoundset = qfalse;
02036                 // play stop sound too?
02037                 if ( self->soundSet && self->soundSet[0] )
02038                 {
02039                         self->s.soundSetIndex = G_SoundSetIndex(self->soundSet);
02040                         G_AddEvent( self, EV_BMODEL_SOUND, BMS_END );
02041                 }
02042         }
02043         else
02044         {
02045                 if ( self->soundSet && self->soundSet[0] )
02046                 {
02047                         self->s.soundSetIndex = G_SoundSetIndex(self->soundSet);
02048                         G_AddEvent( self, EV_BMODEL_SOUND, BMS_START );
02049                         self->s.loopSound = BMS_MID;
02050                         self->s.loopIsSoundset = qtrue;
02051                 }
02052                 self->s.apos.trType = TR_LINEAR;
02053         }
02054 }
02055 
02056 /*QUAKED func_rotating (0 .5 .8) ? START_ON RADAR X_AXIS Y_AXIS IMPACT x PLAYER_USE INACTIVE
02057 RADAR - shows up on Radar in Siege mode (good for asteroids when you're in a ship)
02058 IMPACT - Kills anything on impact when rotating (10000 points of damage, unless specified otherwise)
02059 
02060 You need to have an origin brush as part of this entity.  The center of that brush will be
02061 the point around which it is rotated. It will rotate around the Z axis by default.  You can
02062 check either the X_AXIS or Y_AXIS box to change that.
02063 
02064 "model2"                .md3 model to also draw
02065 "model2scale"   percent of normal scale (on all x y and z axii) to scale the model2 if there is one. 100 is normal scale, min is 1 (100 times smaller than normal), max is 1000 (ten times normal).
02066 "speed"                 determines how fast it moves; default value is 100.
02067 "dmg"                   damage to inflict when blocked (2 default)
02068 "color"                 constantLight color
02069 "light"                 constantLight radius
02070 "spinangles"    instead of using "speed", you can set use this to set rotation on all 3 axes (pitch yaw and roll)
02071 
02072 ==================================================
02073 "health"        default is 0
02074 
02075 If health is set, the following key/values are available:
02076 
02077 "numchunks" Multiplies the number of chunks spawned.  Chunk code tries to pick a good volume of chunks, but you can alter this to scale the number of spawned chunks. (default 1)  (.5) is half as many chunks, (2) is twice as many chunks
02078 "chunksize"     scales up/down the chunk size by this number (default is 1)
02079 
02080 "showhealth" if non-0, will display the health bar on the hud when the crosshair is over this ent (in siege)
02081 "teamowner" in siege this will specify which team this thing is "owned" by. To that team the crosshair will
02082 highlight green, to the other team it will highlight red.
02083 
02084 Damage: default is none
02085 "splashDamage" - damage to do
02086 "splashRadius" - radius for above damage
02087 
02088 "team" - If set, only this team can trip this trigger
02089         0 - any
02090         1 - red
02091         2 - blue
02092 
02093 Don't know if these work:  
02094 "color"         constantLight color
02095 "light"         constantLight radius
02096 
02097 "material" - default is "0 - MAT_METAL" - choose from this list:
02098 0 = MAT_METAL           (basic blue-grey scorched-DEFAULT)
02099 1 = MAT_GLASS           
02100 2 = MAT_ELECTRICAL      (sparks only)
02101 3 = MAT_ELEC_METAL      (METAL2 chunks and sparks)
02102 4 =     MAT_DRK_STONE   (brown stone chunks)
02103 5 =     MAT_LT_STONE    (tan stone chunks)
02104 6 =     MAT_GLASS_METAL (glass and METAL2 chunks)
02105 7 = MAT_METAL2          (electronic type of metal)
02106 8 = MAT_NONE            (no chunks)
02107 9 = MAT_GREY_STONE      (grey colored stone)
02108 10 = MAT_METAL3         (METAL and METAL2 chunk combo)
02109 11 = MAT_CRATE1         (yellow multi-colored crate chunks)
02110 12 = MAT_GRATE1         (grate chunks--looks horrible right now)
02111 13 = MAT_ROPE           (for yavin_trial, no chunks, just wispy bits )
02112 14 = MAT_CRATE2         (red multi-colored crate chunks)
02113 15 = MAT_WHITE_METAL (white angular chunks for Stu, NS_hideout )
02114 16 = MAT_SNOWY_ROCK      (mix of gray and brown rocks)
02115 
02116 Applicable only during Siege gametype:
02117 teamnodmg - if 1, team 1 can't damage this. If 2, team 2 can't damage this.
02118 
02119 */
02120 void SP_func_breakable( gentity_t *self );
02121 void SP_func_rotating (gentity_t *ent) {
02122         vec3_t spinangles;
02123         if ( ent->health )
02124         {
02125                 int sav_spawnflags = ent->spawnflags;
02126                 ent->spawnflags = 0;
02127                 SP_func_breakable( ent );
02128                 ent->spawnflags = sav_spawnflags;
02129         }
02130         else
02131         {
02132                 trap_SetBrushModel( ent, ent->model );
02133                 InitMover( ent );
02134 
02135                 VectorCopy( ent->s.origin, ent->s.pos.trBase );
02136                 VectorCopy( ent->s.pos.trBase, ent->r.currentOrigin );
02137                 VectorCopy( ent->s.apos.trBase, ent->r.currentAngles );
02138 
02139                 trap_LinkEntity( ent );
02140         }
02141 
02142 #ifdef _XBOX
02143         int     tempModelScale;
02144         G_SpawnInt("model2scale", "0", &tempModelScale);
02145         ent->s.iModelScale = tempModelScale;
02146 #else
02147         G_SpawnInt("model2scale", "0", &ent->s.iModelScale);
02148 #endif
02149         if (ent->s.iModelScale < 0)
02150         {
02151                 ent->s.iModelScale = 0;
02152         }
02153         else if (ent->s.iModelScale > 1023)
02154         {
02155                 ent->s.iModelScale = 1023;
02156         }
02157 
02158         if ( G_SpawnVector( "spinangles", "0 0 0", spinangles ) )
02159         {
02160                 ent->speed = VectorLength( spinangles );
02161                 // set the axis of rotation
02162                 VectorCopy( spinangles, ent->s.apos.trDelta );
02163         }
02164         else
02165         {
02166                 if ( !ent->speed ) {
02167                         ent->speed = 100;
02168                 }
02169                 // set the axis of rotation
02170                 if ( ent->spawnflags & 4 ) {
02171                         ent->s.apos.trDelta[2] = ent->speed;
02172                 } else if ( ent->spawnflags & 8 ) {
02173                         ent->s.apos.trDelta[0] = ent->speed;
02174                 } else {
02175                         ent->s.apos.trDelta[1] = ent->speed;
02176                 }
02177         }
02178         ent->s.apos.trType = TR_LINEAR;
02179 
02180         if (!ent->damage) {
02181                 if ( (ent->spawnflags&16) )//IMPACT
02182                 {
02183                         ent->damage = 10000;
02184                 }
02185                 else
02186                 {
02187                         ent->damage = 2;
02188                 }
02189         }
02190         if ( (ent->spawnflags&2) )//RADAR
02191         {//show up on Radar at close range and play impact sound when close...?  Range based on my size
02192                 ent->s.speed = Distance( ent->r.absmin, ent->r.absmax )*0.5f;
02193                 ent->s.eFlags |= EF_RADAROBJECT;
02194         }
02195 }
02196 
02197 
02198 /*
02199 ===============================================================================
02200 
02201 BOBBING
02202 
02203 ===============================================================================
02204 */
02205 
02206 
02207 /*QUAKED func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS x x x x PLAYER_USE INACTIVE
02208 Normally bobs on the Z axis
02209 "model2"        .md3 model to also draw
02210 "height"        amplitude of bob (32 default)
02211 "speed"         seconds to complete a bob cycle (4 default)
02212 "phase"         the 0.0 to 1.0 offset in the cycle to start at
02213 "dmg"           damage to inflict when blocked (2 default)
02214 "color"         constantLight color
02215 "light"         constantLight radius
02216 */
02217 void SP_func_bobbing (gentity_t *ent) {
02218         float           height;
02219         float           phase;
02220 
02221         G_SpawnFloat( "speed", "4", &ent->speed );
02222         G_SpawnFloat( "height", "32", &height );
02223         G_SpawnInt( "dmg", "2", &ent->damage );
02224         G_SpawnFloat( "phase", "0", &phase );
02225 
02226         trap_SetBrushModel( ent, ent->model );
02227         InitMover( ent );
02228 
02229         VectorCopy( ent->s.origin, ent->s.pos.trBase );
02230         VectorCopy( ent->s.origin, ent->r.currentOrigin );
02231 
02232         ent->s.pos.trDuration = ent->speed * 1000;
02233         ent->s.pos.trTime = ent->s.pos.trDuration * phase;
02234         ent->s.pos.trType = TR_SINE;
02235 
02236         // set the axis of bobbing
02237         if ( ent->spawnflags & 1 ) {
02238                 ent->s.pos.trDelta[0] = height;
02239         } else if ( ent->spawnflags & 2 ) {
02240                 ent->s.pos.trDelta[1] = height;
02241         } else {
02242                 ent->s.pos.trDelta[2] = height;
02243         }
02244 }
02245 
02246 /*
02247 ===============================================================================
02248 
02249 PENDULUM
02250 
02251 ===============================================================================
02252 */
02253 
02254 
02255 /*QUAKED func_pendulum (0 .5 .8) ? x x x x x x PLAYER_USE INACTIVE
02256 You need to have an origin brush as part of this entity.
02257 Pendulums always swing north / south on unrotated models.  Add an angles field to the model to allow rotation in other directions.
02258 Pendulum frequency is a physical constant based on the length of the beam and gravity.
02259 "model2"        .md3 model to also draw
02260 "speed"         the number of degrees each way the pendulum swings, (30 default)
02261 "phase"         the 0.0 to 1.0 offset in the cycle to start at
02262 "dmg"           damage to inflict when blocked (2 default)
02263 "color"         constantLight color
02264 "light"         constantLight radius
02265 */
02266 void SP_func_pendulum(gentity_t *ent) {
02267         float           freq;
02268         float           length;
02269         float           phase;
02270         float           speed;
02271 
02272         G_SpawnFloat( "speed", "30", &speed );
02273         G_SpawnInt( "dmg", "2", &ent->damage );
02274         G_SpawnFloat( "phase", "0", &phase );
02275 
02276         trap_SetBrushModel( ent, ent->model );
02277 
02278         // find pendulum length
02279         length = fabs( ent->r.mins[2] );
02280         if ( length < 8 ) {
02281                 length = 8;
02282         }
02283 
02284         freq = 1 / ( M_PI * 2 ) * sqrt( g_gravity.value / ( 3 * length ) );
02285 
02286         ent->s.pos.trDuration = ( 1000 / freq );
02287 
02288         InitMover( ent );
02289 
02290         VectorCopy( ent->s.origin, ent->s.pos.trBase );
02291         VectorCopy( ent->s.origin, ent->r.currentOrigin );
02292 
02293         VectorCopy( ent->s.angles, ent->s.apos.trBase );
02294 
02295         ent->s.apos.trDuration = 1000 / freq;
02296         ent->s.apos.trTime = ent->s.apos.trDuration * phase;
02297         ent->s.apos.trType = TR_SINE;
02298         ent->s.apos.trDelta[2] = speed;
02299 }
02300 
02301 /*
02302 ===============================================================================
02303 
02304 BREAKABLE BRUSH
02305 
02306 ===============================================================================
02307 */
02308 //---------------------------------------------------
02309 static void CacheChunkEffects( material_t material )
02310 {
02311         switch( material )
02312         {
02313         case MAT_GLASS:
02314                 G_EffectIndex( "chunks/glassbreak" );
02315                 break;
02316         case MAT_GLASS_METAL:
02317                 G_EffectIndex( "chunks/glassbreak" );
02318                 G_EffectIndex( "chunks/metalexplode" );
02319                 break;
02320         case MAT_ELECTRICAL:
02321         case MAT_ELEC_METAL:
02322                 G_EffectIndex( "chunks/sparkexplode" );
02323                 break;
02324         case MAT_METAL:
02325         case MAT_METAL2:
02326         case MAT_METAL3:
02327         case MAT_CRATE1:
02328         case MAT_CRATE2:
02329                 G_EffectIndex( "chunks/metalexplode" );
02330                 break;
02331         case MAT_GRATE1:
02332                 G_EffectIndex( "chunks/grateexplode" );
02333                 break;
02334         case MAT_DRK_STONE:
02335         case MAT_LT_STONE:
02336         case MAT_GREY_STONE:
02337         case MAT_WHITE_METAL: // what is this crap really supposed to be??
02338         case MAT_SNOWY_ROCK:
02339                 G_EffectIndex( "chunks/rockbreaklg" );
02340                 G_EffectIndex( "chunks/rockbreakmed" );
02341                 break;
02342         case MAT_ROPE:
02343                 G_EffectIndex( "chunks/ropebreak" );
02344 //              G_SoundIndex(); // FIXME: give it a sound
02345                 break;
02346         }
02347 }
02348 
02349 void G_MiscModelExplosion( vec3_t mins, vec3_t maxs, int size, material_t chunkType )
02350 {
02351         gentity_t *te;
02352         vec3_t mid;
02353         
02354         VectorAdd( mins, maxs, mid );
02355         VectorScale( mid, 0.5f, mid );
02356 
02357         te = G_TempEntity( mid, EV_MISC_MODEL_EXP );
02358 
02359         VectorCopy(maxs, te->s.origin2);
02360         VectorCopy(mins, te->s.angles2);
02361         te->s.time = size;
02362         te->s.eventParm = chunkType;
02363 }
02364 
02365 void G_Chunks( int owner, vec3_t origin, const vec3_t normal, const vec3_t mins, const vec3_t maxs, 
02366                                                 float speed, int numChunks, material_t chunkType, int customChunk, float baseScale )
02367 {
02368         gentity_t *te = G_TempEntity( origin, EV_DEBRIS );
02369 
02370         //Now it's time to cram everything horribly into the entitystate of an event entity.
02371         te->s.owner = owner;
02372         VectorCopy(origin, te->s.origin);
02373         VectorCopy(normal, te->s.angles);
02374         VectorCopy(maxs, te->s.origin2);
02375         VectorCopy(mins, te->s.angles2);
02376         te->s.speed = speed;
02377         te->s.eventParm = numChunks;
02378         te->s.trickedentindex = chunkType;
02379         te->s.modelindex = customChunk;
02380         te->s.apos.trBase[0] = baseScale;
02381 }
02382 
02383 //--------------------------------------
02384 void funcBBrushDieGo (gentity_t *self)
02385 {
02386         vec3_t          org, dir, up;
02387         gentity_t       *attacker = self->enemy;
02388         float           scale;
02389         int                     i, numChunks, size = 0;
02390         material_t      chunkType = self->material;
02391         
02392         // if a missile is stuck to us, blow it up so we don't look dumb
02393         for ( i = 0; i < MAX_GENTITIES; i++ )
02394         {
02395                 if ( g_entities[i].s.groundEntityNum == self->s.number && ( g_entities[i].s.eFlags & EF_MISSILE_STICK ))
02396                 {
02397                         G_Damage( &g_entities[i], self, self, NULL, NULL, 99999, 0, MOD_CRUSH ); //?? MOD?
02398                 }
02399         }
02400 
02401         //So chunks don't get stuck inside me
02402         self->s.solid = 0;
02403         self->r.contents = 0;
02404         self->clipmask = 0;
02405         trap_LinkEntity(self); 
02406 
02407         VectorSet(up, 0, 0, 1);
02408 
02409         if ( self->target && attacker != NULL )
02410         {
02411                 G_UseTargets(self, attacker);
02412         }
02413 
02414         VectorSubtract( self->r.absmax, self->r.absmin, org );// size
02415 
02416         numChunks = random() * 6 + 18;
02417 
02418         // This formula really has no logical basis other than the fact that it seemed to be the closest to yielding the results that I wanted.
02419         // Volume is length * width * height...then break that volume down based on how many chunks we have
02420         scale = sqrt( sqrt( org[0] * org[1] * org[2] )) * 1.75f;
02421 
02422         if ( scale > 48 )
02423         {
02424                 size = 2;
02425         }
02426         else if ( scale > 24 )
02427         {
02428                 size = 1;
02429         }
02430 
02431         scale = scale / numChunks;
02432 
02433         if ( self->radius > 0.0f )
02434         {
02435                 // designer wants to scale number of chunks, helpful because the above scale code is far from perfect
02436                 //      I do this after the scale calculation because it seems that the chunk size generally seems to be very close, it's just the number of chunks is a bit weak
02437                 numChunks *= self->radius;
02438         }
02439 
02440         VectorMA( self->r.absmin, 0.5, org, org );
02441         VectorAdd( self->r.absmin,self->r.absmax, org );
02442         VectorScale( org, 0.5f, org );
02443 
02444         if ( attacker != NULL && attacker->client )
02445         {
02446                 VectorSubtract( org, attacker->r.currentOrigin, dir );
02447                 VectorNormalize( dir );
02448         }
02449         else
02450         {
02451                 VectorCopy(up, dir);
02452         } 
02453 
02454         if ( !(self->spawnflags & 2048) ) // NO_EXPLOSION
02455         {
02456                 // we are allowed to explode
02457                 G_MiscModelExplosion( self->r.absmin, self->r.absmax, size, chunkType );
02458         }
02459 
02460         if (self->genericValue15)
02461         { //a custom effect to play
02462                 vec3_t ang;
02463                 VectorSet(ang, 0.0f, 1.0f, 0.0f);
02464                 G_PlayEffectID(self->genericValue15, org, ang);
02465         }
02466 
02467         if ( self->splashDamage > 0 && self->splashRadius > 0 )
02468         {
02469                 gentity_t *te;
02470                 //explode
02471                 G_RadiusDamage( org, self, self->splashDamage, self->splashRadius, self, NULL, MOD_UNKNOWN );
02472 
02473                 te = G_TempEntity( org, EV_GENERAL_SOUND );
02474                 te->s.eventParm = G_SoundIndex("sound/weapons/explosions/cargoexplode.wav");
02475         }
02476 
02477         //FIXME: base numChunks off size?
02478         G_Chunks( self->s.number, org, dir, self->r.absmin, self->r.absmax, 300, numChunks, chunkType, 0, (scale*self->mass) );
02479 
02480         trap_AdjustAreaPortalState( self, qtrue );
02481         self->think = G_FreeEntity;
02482         self->nextthink = level.time + 50;
02483         //G_FreeEntity( self );
02484 }
02485 
02486 void funcBBrushDie (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod)
02487 {
02488         self->takedamage = qfalse;//stop chain reaction runaway loops
02489 
02490         self->enemy = attacker;
02491 
02492         if(self->delay)
02493         {
02494                 self->think = funcBBrushDieGo;
02495                 self->nextthink = level.time + floor(self->delay * 1000.0f);
02496                 return;
02497         }
02498 
02499         funcBBrushDieGo(self);
02500 }
02501 
02502 void funcBBrushUse (gentity_t *self, gentity_t *other, gentity_t *activator)
02503 {
02504         G_ActivateBehavior( self, BSET_USE );
02505         if(self->spawnflags & 64)
02506         {//Using it doesn't break it, makes it use it's targets
02507                 if(self->target && self->target[0])
02508                 {
02509                         G_UseTargets(self, activator);
02510                 }
02511         }
02512         else
02513         {
02514                 funcBBrushDie(self, other, activator, self->health, MOD_UNKNOWN);
02515         }
02516 }
02517 
02518 void funcBBrushPain(gentity_t *self, gentity_t *attacker, int damage)
02519 {
02520         if ( self->painDebounceTime > level.time )
02521         {
02522                 return;
02523         }
02524 
02525         if ( self->paintarget && self->paintarget[0] )
02526         {
02527                 if (!self->activator)
02528                 {
02529                         if (attacker && attacker->inuse && attacker->client)
02530                         {
02531                                 G_UseTargets2 (self, attacker, self->paintarget);
02532                         }
02533                 }
02534                 else
02535                 {
02536                         G_UseTargets2 (self, self->activator, self->paintarget);
02537                 }
02538         }
02539 
02540         G_ActivateBehavior( self, BSET_PAIN );
02541 
02542         if ( self->material == MAT_DRK_STONE
02543                 || self->material == MAT_LT_STONE
02544                 || self->material == MAT_GREY_STONE 
02545                 || self->material == MAT_SNOWY_ROCK )
02546         {
02547                 vec3_t  org, dir;
02548                 float   scale;
02549                 int             numChunks;
02550                 VectorSubtract( self->r.absmax, self->r.absmin, org );// size
02551                 // This formula really has no logical basis other than the fact that it seemed to be the closest to yielding the results that I wanted.
02552                 // Volume is length * width * height...then break that volume down based on how many chunks we have
02553                 scale = VectorLength( org ) / 100.0f;
02554                 VectorMA( self->r.absmin, 0.5, org, org );
02555                 VectorAdd( self->r.absmin,self->r.absmax, org );
02556                 VectorScale( org, 0.5f, org );
02557                 if ( attacker != NULL && attacker->client )
02558                 {
02559                         VectorSubtract( attacker->r.currentOrigin, org, dir );
02560                         VectorNormalize( dir );
02561                 }
02562                 else
02563                 {
02564                         VectorSet( dir, 0, 0, 1 );
02565                 } 
02566                 numChunks = Q_irand( 1, 3 );
02567                 if ( self->radius > 0.0f )
02568                 {
02569                         // designer wants to scale number of chunks, helpful because the above scale code is far from perfect
02570                         //      I do this after the scale calculation because it seems that the chunk size generally seems to be very close, it's just the number of chunks is a bit weak
02571                         numChunks = ceil(numChunks*self->radius);
02572                 }
02573                 G_Chunks( self->s.number, org, dir, self->r.absmin, self->r.absmax, 300, numChunks, self->material, 0, (scale*self->mass) );
02574         }
02575 
02576         if ( self->wait == -1 )
02577         {
02578                 self->pain = 0;
02579                 return;
02580         }
02581 
02582         self->painDebounceTime = level.time + self->wait;
02583 }
02584 
02585 static void InitBBrush ( gentity_t *ent ) 
02586 {
02587         float           light;
02588         vec3_t          color;
02589         qboolean        lightSet, colorSet;
02590 
02591         VectorCopy( ent->s.origin, ent->pos1 );
02592         
02593         trap_SetBrushModel( ent, ent->model );
02594 
02595         ent->die = funcBBrushDie;
02596         
02597         ent->flags |= FL_BBRUSH;
02598 
02599         //This doesn't have to be an svFlag, can just be a flag.
02600         //And it might not be needed anyway.
02601         //ent->r.svFlags |= SVF_BBRUSH;
02602 
02603 
02604         // if the "model2" key is set, use a seperate model
02605         // for drawing, but clip against the brushes
02606         if ( ent->model2 && ent->model2[0] ) 
02607         {
02608                 ent->s.modelindex2 = G_ModelIndex( ent->model2 );
02609         }
02610 
02611         // if the "color" or "light" keys are set, setup constantLight
02612         lightSet = G_SpawnFloat( "light", "100", &light );
02613         colorSet = G_SpawnVector( "color", "1 1 1", color );
02614         if ( lightSet || colorSet ) 
02615         {
02616                 int             r, g, b, i;
02617 
02618                 r = color[0] * 255;
02619                 if ( r > 255 ) {
02620                         r = 255;
02621                 }
02622                 g = color[1] * 255;
02623                 if ( g > 255 ) {
02624                         g = 255;
02625                 }
02626                 b = color[2] * 255;
02627                 if ( b > 255 ) {
02628                         b = 255;
02629                 }
02630                 i = light / 4;
02631                 if ( i > 255 ) {
02632                         i = 255;
02633                 }
02634                 ent->s.constantLight = r | ( g << 8 ) | ( b << 16 ) | ( i << 24 );
02635         }
02636 
02637         if(ent->spawnflags & 128)
02638         {//Can be used by the player's BUTTON_USE
02639                 ent->r.svFlags |= SVF_PLAYER_USABLE;
02640         }
02641 
02642         ent->s.eType = ET_MOVER;
02643         trap_LinkEntity(ent);
02644 
02645         ent->s.pos.trType = TR_STATIONARY;
02646         VectorCopy( ent->pos1, ent->s.pos.trBase );
02647 }
02648 
02649 void funcBBrushTouch( gentity_t *ent, gentity_t *other, trace_t *trace )
02650 {
02651 }
02652 
02653 /*QUAKED func_breakable (0 .8 .5) ? INVINCIBLE IMPACT CRUSHER THIN SABERONLY HEAVY_WEAP USE_NOT_BREAK PLAYER_USE NO_EXPLOSION
02654 INVINCIBLE - can only be broken by being used
02655 IMPACT - does damage on impact
02656 CRUSHER - won't reverse movement when hit an obstacle
02657 THIN - can be broken by impact damage, like glass
02658 SABERONLY - only takes damage from sabers
02659 HEAVY_WEAP - only takes damage by a heavy weapon, like an emplaced gun or AT-ST gun.
02660 USE_NOT_BREAK - Using it doesn't make it break, still can be destroyed by damage
02661 PLAYER_USE - Player can use it with the use button
02662 NO_EXPLOSION - Does not play an explosion effect, though will still create chunks if specified
02663 
02664 When destroyed, fires it's trigger and chunks and plays sound "noise" or sound for type if no noise specified
02665 
02666 "targetname" entities with matching target will fire it
02667 "paintarget" target to fire when hit (but not destroyed)
02668 "wait"          how long minimum to wait between firing paintarget each time hit
02669 "delay"         When killed or used, how long (in seconds) to wait before blowing up (none by default)
02670 "model2"        .md3 model to also draw
02671 "target"        all entities with a matching targetname will be used when this is destoryed
02672 "health"        default is 10
02673 "numchunks" Multiplies the number of chunks spawned.  Chunk code tries to pick a good volume of chunks, but you can alter this to scale the number of spawned chunks. (default 1)  (.5) is half as many chunks, (2) is twice as many chunks
02674 "chunksize"     scales up/down the chunk size by this number (default is 1)
02675 "playfx"        path of effect to play on death
02676 
02677 "showhealth" if non-0, will display the health bar on the hud when the crosshair is over this ent (in siege)
02678 "teamowner" in siege this will specify which team this thing is "owned" by. To that team the crosshair will
02679 highlight green, to the other team it will highlight red.
02680 
02681 Damage: default is none
02682 "splashDamage" - damage to do
02683 "splashRadius" - radius for above damage
02684 
02685 "team" - If set, only this team can trip this trigger
02686         0 - any
02687         1 - red
02688         2 - blue
02689 
02690 Don't know if these work:  
02691 "color"         constantLight color
02692 "light"         constantLight radius
02693 
02694 "material" - default is "0 - MAT_METAL" - choose from this list:
02695 0 = MAT_METAL           (basic blue-grey scorched-DEFAULT)
02696 1 = MAT_GLASS           
02697 2 = MAT_ELECTRICAL      (sparks only)
02698 3 = MAT_ELEC_METAL      (METAL2 chunks and sparks)
02699 4 =     MAT_DRK_STONE   (brown stone chunks)
02700 5 =     MAT_LT_STONE    (tan stone chunks)
02701 6 =     MAT_GLASS_METAL (glass and METAL2 chunks)
02702 7 = MAT_METAL2          (electronic type of metal)
02703 8 = MAT_NONE            (no chunks)
02704 9 = MAT_GREY_STONE      (grey colored stone)
02705 10 = MAT_METAL3         (METAL and METAL2 chunk combo)
02706 11 = MAT_CRATE1         (yellow multi-colored crate chunks)
02707 12 = MAT_GRATE1         (grate chunks--looks horrible right now)
02708 13 = MAT_ROPE           (for yavin_trial, no chunks, just wispy bits )
02709 14 = MAT_CRATE2         (red multi-colored crate chunks)
02710 15 = MAT_WHITE_METAL (white angular chunks for Stu, NS_hideout )
02711 16 = MAT_SNOWY_ROCK      (mix of gray and brown rocks)
02712 
02713 Applicable only during Siege gametype:
02714 teamnodmg - if 1, team 1 can't damage this. If 2, team 2 can't damage this.
02715 
02716 */
02717 void SP_func_breakable( gentity_t *self ) 
02718 {
02719         int t;
02720         char *s = NULL;
02721 
02722         G_SpawnString("playfx", "", &s);
02723 
02724         if (s && s[0])
02725         { //should we play a special death effect?
02726                 self->genericValue15 = G_EffectIndex(s);
02727         }
02728         else
02729         {
02730                 self->genericValue15 = 0;
02731         }
02732 
02733         if(!(self->spawnflags & 1))
02734         {
02735                 if(!self->health)
02736                 {
02737                         self->health = 10;
02738                 }
02739         }
02740 
02741         G_SpawnInt( "showhealth", "0", &t );
02742 
02743         if (t)
02744         { //a non-0 maxhealth value will mean we want to show the health on the hud
02745                 self->maxHealth = self->health;
02746                 G_ScaleNetHealth(self);
02747         }
02748 
02749         //NOTE: g_spawn.c does this automatically now
02750         //G_SpawnInt( "teamowner", "0", &t );
02751         //self->s.teamowner = t;
02752 
02753         if ( self->spawnflags & 16 ) // saber only
02754         {
02755                 self->flags |= FL_DMG_BY_SABER_ONLY;
02756         }
02757         else if ( self->spawnflags & 32 ) // heavy weap
02758         {
02759                 self->flags |= FL_DMG_BY_HEAVY_WEAP_ONLY;
02760         }
02761 
02762         if (self->health) 
02763         {
02764                 self->takedamage = qtrue;
02765         }
02766 
02767         G_SoundIndex("sound/weapons/explosions/cargoexplode.wav");//precaching
02768         G_SpawnFloat( "radius", "1", &self->radius ); // used to scale chunk code if desired by a designer
02769         G_SpawnInt( "material", "0", (int*)&self->material );
02770 
02771         G_SpawnInt( "splashDamage", "0", &self->splashDamage );
02772         G_SpawnInt( "splashRadius", "0", &self->splashRadius );
02773 
02774         CacheChunkEffects( self->material );
02775 
02776         self->use = funcBBrushUse;
02777 
02778         //if ( self->paintarget )
02779         {
02780                 self->pain = funcBBrushPain;
02781         }
02782 
02783         self->touch = funcBBrushTouch;
02784 
02785         /*
02786         if ( self->team && self->team[0] )
02787         {
02788                 self->alliedTeam = TranslateTeamName( self->team );
02789                 if(self->alliedTeam == TEAM_FREE)
02790                 {
02791                         G_Error("team name %s not recognized\n", self->team);
02792                 }
02793         }
02794         */
02795         if ( self->team && self->team[0] && g_gametype.integer == GT_SIEGE &&
02796                 !self->teamnodmg)
02797         {
02798                 self->teamnodmg = atoi(self->team);
02799         }
02800         self->team = NULL;
02801         if (!self->model) {
02802                 G_Error("func_breakable with NULL model\n");
02803         }
02804         InitBBrush( self );
02805 
02806         if ( !self->radius )
02807         {//numchunks multiplier
02808                 self->radius = 1.0f;
02809         }
02810         if ( !self->mass )
02811         {//chunksize multiplier
02812                 self->mass = 1.0f;
02813         }
02814         self->genericValue4 = 1; //so damage sys knows it's a bbrush
02815 }
02816 
02817 qboolean G_EntIsBreakable( int entityNum )
02818 {
02819         gentity_t *ent;
02820 
02821         if ( entityNum < 0 || entityNum >= ENTITYNUM_WORLD )
02822         {
02823                 return qfalse;
02824         }
02825 
02826         ent = &g_entities[entityNum];
02827         if ( (ent->r.svFlags & SVF_GLASS_BRUSH) )
02828         {
02829                 return qtrue;
02830         }
02831         /*
02832         if ( (ent->svFlags&SVF_BBRUSH) )
02833         {
02834                 return qtrue;
02835         }
02836         */
02837         if ( !Q_stricmp( "func_breakable", ent->classname ) )
02838         {
02839                 return qtrue;
02840         }
02841 
02842         if ( !Q_stricmp( "misc_model_breakable", ent->classname ) )
02843         {
02844                 return qtrue;
02845         }
02846         if ( !Q_stricmp( "misc_maglock", ent->classname ) )
02847         {
02848                 return qtrue;
02849         }
02850 
02851         return qfalse;
02852 }
02853 
02854 /*
02855 ===============================================================================
02856 
02857 GLASS
02858 
02859 ===============================================================================
02860 */
02861 void GlassDie(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod)
02862 {
02863         gentity_t *te;
02864         vec3_t dif;
02865 
02866         if (self->genericValue5)
02867         { //was already destroyed, do not retrigger it
02868                 return;
02869         }
02870 
02871         self->genericValue5 = 1;
02872 
02873         dif[0] = (self->r.absmax[0]+self->r.absmin[0])/2;
02874         dif[1] = (self->r.absmax[1]+self->r.absmin[1])/2;
02875         dif[2] = (self->r.absmax[2]+self->r.absmin[2])/2;
02876 
02877         G_UseTargets(self, attacker);
02878 
02879         self->splashRadius = 40; // ?? some random number, maybe it's ok?
02880 
02881         te = G_TempEntity( dif, EV_GLASS_SHATTER );
02882         te->s.genericenemyindex = self->s.number;
02883         VectorCopy(self->pos1, te->s.origin);
02884         VectorCopy(self->pos2, te->s.angles);
02885         te->s.trickedentindex = (int)self->splashRadius;
02886         te->s.pos.trTime = (int)self->genericValue3;
02887 
02888         G_FreeEntity(self);
02889 }
02890 
02891 void GlassDie_Old(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod)
02892 {
02893         gentity_t *te;
02894         vec3_t dif;
02895 
02896         dif[0] = (self->r.absmax[0]+self->r.absmin[0])/2;
02897         dif[1] = (self->r.absmax[1]+self->r.absmin[1])/2;
02898         dif[2] = (self->r.absmax[2]+self->r.absmin[2])/2;
02899 
02900         G_UseTargets(self, attacker);
02901 
02902         te = G_TempEntity( dif, EV_GLASS_SHATTER );
02903         te->s.genericenemyindex = self->s.number;
02904         VectorCopy(self->r.maxs, te->s.origin);
02905         VectorCopy(self->r.mins, te->s.angles);
02906 
02907         G_FreeEntity(self);
02908 }
02909 
02910 void GlassPain(gentity_t *self, gentity_t *attacker, int damage)
02911 {
02912         //G_Printf("Mr. Glass says: PLZ NO IT HURTS\n");
02913         //Make "cracking" sound?
02914 }
02915 
02916 void GlassUse(gentity_t *self, gentity_t *other, gentity_t *activator)
02917 {
02918         vec3_t temp1, temp2;
02919 
02920         //no direct object to blame for the break, so fill the values with whatever
02921         VectorAdd( self->r.mins, self->r.maxs, temp1 );
02922         VectorScale( temp1, 0.5f, temp1 );
02923 
02924         VectorAdd( other->r.mins, other->r.maxs, temp2 );
02925         VectorScale( temp2, 0.5f, temp2 );
02926 
02927         VectorSubtract( temp1, temp2, self->pos2 );
02928         VectorCopy( temp1, self->pos1 );
02929 
02930         VectorNormalize( self->pos2 );
02931         VectorScale( self->pos2, 390, self->pos2 );
02932 
02933         GlassDie(self, other, activator, 100, MOD_UNKNOWN);
02934 }
02935 
02936 /*QUAKED func_glass (0 .5 .8) ? x x x x x x PLAYER_USE INACTIVE
02937 Breakable glass
02938 "model2"        .md3 model to also draw
02939 "color"         constantLight color
02940 "light"         constantLight radius
02941 "maxshards"     Max number of shards to spawn on glass break
02942 */
02943 void SP_func_glass( gentity_t *ent ) {
02944         trap_SetBrushModel( ent, ent->model );
02945         InitMover( ent );
02946 
02947         ent->r.svFlags = SVF_GLASS_BRUSH;
02948 
02949         VectorCopy( ent->s.origin, ent->s.pos.trBase );
02950         VectorCopy( ent->s.origin, ent->r.currentOrigin );
02951         if (!ent->health)
02952         {
02953                 ent->health = 1;
02954         }
02955 
02956         G_SpawnInt("maxshards", "0", &ent->genericValue3);
02957 
02958         ent->genericValue1 = 0;
02959 
02960         ent->genericValue4 = 1;
02961 
02962         ent->moverState = MOVER_POS1;
02963 
02964         if (ent->spawnflags & 1)
02965         {
02966                 ent->takedamage = qfalse;
02967         }
02968         else
02969         {
02970                 ent->takedamage = qtrue;
02971         }
02972 
02973         ent->die = GlassDie;
02974         ent->use = GlassUse;
02975         ent->pain = GlassPain;
02976 }
02977 
02978 void func_usable_use (gentity_t *self, gentity_t *other, gentity_t *activator);
02979 
02980 extern gentity_t        *G_TestEntityPosition( gentity_t *ent );
02981 void func_wait_return_solid( gentity_t *self )
02982 {
02983         //once a frame, see if it's clear.
02984         self->clipmask = CONTENTS_BODY;
02985         if ( !(self->spawnflags&16) || G_TestEntityPosition( self ) == NULL )
02986         {
02987                 trap_SetBrushModel( self, self->model );
02988                 InitMover( self );
02989                 VectorCopy( self->s.origin, self->s.pos.trBase );
02990                 VectorCopy( self->s.origin, self->r.currentOrigin );
02991                 self->r.svFlags &= ~SVF_NOCLIENT;
02992                 self->s.eFlags &= ~EF_NODRAW;
02993                 self->use = func_usable_use;
02994                 self->clipmask = 0;
02995                 if ( self->target2 && self->target2[0] )
02996                 {
02997                         G_UseTargets2( self, self->activator, self->target2 );
02998                 }
02999                 //FIXME: Animations?
03000                 /*if ( self->s.eFlags & EF_ANIM_ONCE )
03001                 {//Start our anim
03002                         self->s.frame = 0;
03003                 }*/
03004         }
03005         else
03006         {
03007                 self->clipmask = 0;
03008                 self->think = func_wait_return_solid;
03009                 self->nextthink = level.time + FRAMETIME;
03010         }
03011 }
03012 
03013 void func_usable_think( gentity_t *self )
03014 {
03015         if ( self->spawnflags & 8 )
03016         {
03017                 self->r.svFlags |= SVF_PLAYER_USABLE;   //Replace the usable flag
03018                 self->use = func_usable_use;
03019                 self->think = 0;
03020         }
03021 }
03022 
03023 qboolean G_EntIsRemovableUsable( int entNum )
03024 {
03025         gentity_t *ent = &g_entities[entNum];
03026         if ( ent->classname && !Q_stricmp( "func_usable", ent->classname ) )
03027         {
03028                 if ( !(ent->s.eFlags&EF_SHADER_ANIM) && !(ent->spawnflags&8) && ent->targetname )
03029                 {//not just a shader-animator and not ALWAYS_ON, so it must be removable somehow
03030                         return qtrue;
03031                 }
03032         }
03033         return qfalse;
03034 }
03035 
03036 void func_usable_use (gentity_t *self, gentity_t *other, gentity_t *activator)
03037 {//Toggle on and off
03038         G_ActivateBehavior( self, BSET_USE );
03039         if ( self->s.eFlags & EF_SHADER_ANIM )
03040         {//animate shader when used
03041                 self->s.frame++;//inc frame
03042                 if ( self->s.frame > self->genericValue5 )
03043                 {//wrap around
03044                         self->s.frame = 0;
03045                 }
03046                 if ( self->target && self->target[0] )
03047                 {
03048                         G_UseTargets( self, activator );
03049                 }
03050         }
03051         else if ( self->spawnflags & 8 )
03052         {//ALWAYS_ON
03053                 //Remove the ability to use the entity directly
03054                 self->r.svFlags &= ~SVF_PLAYER_USABLE;
03055                 //also remove ability to call any use func at all!
03056                 self->use = 0;
03057                 
03058                 if(self->target && self->target[0])
03059                 {
03060                         G_UseTargets(self, activator);
03061                 }
03062                 
03063                 if ( self->wait )
03064                 {
03065                         self->think = func_usable_think;
03066                         self->nextthink = level.time + ( self->wait * 1000 );
03067                 }
03068 
03069                 return;
03070         }
03071         else if ( !self->count )
03072         {//become solid again
03073                 self->count = 1;
03074                 func_wait_return_solid( self );
03075         }
03076         else
03077         {
03078                 self->s.solid = 0;
03079                 self->r.contents = 0;
03080                 self->clipmask = 0;
03081                 self->r.svFlags |= SVF_NOCLIENT;
03082                 self->s.eFlags |= EF_NODRAW;
03083                 self->count = 0;
03084 
03085                 if(self->target && self->target[0])
03086                 {
03087                         G_UseTargets(self, activator);
03088                 }
03089                 self->think = 0;
03090                 self->nextthink = -1;
03091         }
03092 }
03093 
03094 void func_usable_pain(gentity_t *self, gentity_t *attacker, int damage)
03095 {
03096         GlobalUse(self, attacker, attacker);
03097 }
03098 
03099 void func_usable_die(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod)
03100 {
03101         self->takedamage = qfalse;
03102         GlobalUse(self, inflictor, attacker);
03103 }
03104 
03105 /*QUAKED func_usable (0 .5 .8) ? STARTOFF x x ALWAYS_ON x x PLAYER_USE INACTIVE
03106 START_OFF - the wall will not be there
03107 ALWAYS_ON - Doesn't toggle on and off when used, just runs usescript and fires target
03108 
03109 A bmodel that just sits there, doing nothing.  Can be used for conditional walls and models.
03110 "targetname" - When used, will toggle on and off
03111 "target"        Will fire this target every time it is toggled OFF
03112 "model2"        .md3 model to also draw
03113 "color"         constantLight color
03114 "light"         constantLight radius
03115 "usescript" script to run when turned on
03116 "deathscript"  script to run when turned off
03117 "wait"          amount of time before the object is usable again (only valid with ALWAYS_ON flag)
03118 "health"        if it has health, it will be used whenever shot at/killed - if you want it to only be used once this way, set health to 1
03119 "endframe"      Will make it animate to next shader frame when used, not turn on/off... set this to number of frames in the shader, minus 1
03120 
03121 Applicable only during Siege gametype:
03122 teamuser - if 1, team 2 can't use this. If 2, team 1 can't use this.
03123 
03124 */
03125 
03126 void SP_func_usable( gentity_t *self ) 
03127 {
03128         trap_SetBrushModel( self, self->model );
03129         InitMover( self );
03130         VectorCopy( self->s.origin, self->s.pos.trBase );
03131         VectorCopy( self->s.origin, self->r.currentOrigin );
03132         VectorCopy( self->s.origin, self->pos1 );
03133 
03134         G_SpawnInt("endframe", "0", &self->genericValue5);
03135 
03136         if ( self->model2 && self->model2[0] ) 
03137         {
03138                 if ( strstr( self->model2, ".glm" ))
03139                 { //for now, not supported in MP.
03140                         self->s.modelindex2 = 0;
03141                 }
03142                 else
03143                 {
03144                         self->s.modelindex2 = G_ModelIndex( self->model2 );
03145                 }
03146         }
03147 
03148         self->count = 1;
03149         if (self->spawnflags & 1)
03150         {
03151                 self->s.solid = 0;
03152                 self->r.contents = 0;
03153                 self->clipmask = 0;
03154                 self->r.svFlags |= SVF_NOCLIENT;
03155                 self->s.eFlags |= EF_NODRAW;
03156                 self->count = 0;
03157         }
03158 
03159         //FIXME: Animation?
03160         /*
03161         if (self->spawnflags & 2)
03162         {
03163                 self->s.eFlags |= EF_ANIM_ALLFAST;
03164         }
03165 
03166         if (self->spawnflags & 4)
03167         {//FIXME: need to be able to do change to something when it's done?  Or not be usable until it's done?
03168                 self->s.eFlags |= EF_ANIM_ONCE;
03169         }
03170         */
03171 
03172         self->use = func_usable_use;
03173 
03174         if ( self->health )
03175         {
03176                 self->takedamage = qtrue;
03177                 self->die = func_usable_die;
03178                 self->pain = func_usable_pain;
03179         }
03180 
03181         if ( self->genericValue5 > 0 )
03182         {
03183                 self->s.frame = 0;
03184                 self->s.eFlags |= EF_SHADER_ANIM;
03185                 self->s.time = self->genericValue5 + 1;
03186         }
03187 
03188         trap_LinkEntity (self);
03189 }
03190 
03191 
03192 /*
03193 ===============================================================================
03194 
03195 WALL
03196 
03197 ===============================================================================
03198 */
03199 
03200 //static -slc
03201 void use_wall( gentity_t *ent, gentity_t *other, gentity_t *activator )
03202 {
03203         G_ActivateBehavior(ent,BSET_USE);
03204 
03205         // Not there so make it there
03206         if (!(ent->r.contents & CONTENTS_SOLID))
03207         {
03208                 ent->r.svFlags &= ~SVF_NOCLIENT;
03209                 ent->s.eFlags &= ~EF_NODRAW;
03210                 ent->r.contents = CONTENTS_SOLID;
03211                 if ( !(ent->spawnflags&1) )
03212                 {//START_OFF doesn't effect area portals
03213                         trap_AdjustAreaPortalState( ent, qfalse );
03214                 }
03215         }
03216         // Make it go away
03217         else
03218         {
03219                 ent->r.contents = 0;
03220                 ent->r.svFlags |= SVF_NOCLIENT;
03221                 ent->s.eFlags |= EF_NODRAW;
03222                 if ( !(ent->spawnflags&1) )
03223                 {//START_OFF doesn't effect area portals
03224                         trap_AdjustAreaPortalState( ent, qtrue );
03225                 }
03226         }
03227 }
03228 
03229 #define FUNC_WALL_OFF   1
03230 
03231 /*QUAKED func_wall (0 .5 .8) ? START_OFF x x x x x PLAYER_USE INACTIVE
03232 PLAYER_USE      Player can use it with the use button
03233 INACTIVE        must be used by a target_activate before it can be used
03234 
03235 A bmodel that just sits there, doing nothing.  Can be used for conditional walls and models.
03236 "model2"        .md3 model to also draw
03237 "color"         constantLight color
03238 "light"         constantLight radius
03239 
03240 START_OFF - the wall will not be there
03241 */
03242 void SP_func_wall( gentity_t *ent ) 
03243 {
03244         trap_SetBrushModel( ent, ent->model );
03245 
03246         VectorCopy( ent->s.origin, ent->pos1 );
03247         VectorCopy( ent->s.origin, ent->pos2 );
03248 
03249         InitMover( ent );
03250         VectorCopy( ent->s.origin, ent->s.pos.trBase );
03251         VectorCopy( ent->s.origin, ent->r.currentOrigin );
03252 
03253         // it must be START_OFF
03254         if (ent->spawnflags & FUNC_WALL_OFF)
03255         {
03256                 ent->r.contents = 0;
03257                 ent->r.svFlags |= SVF_NOCLIENT;
03258                 ent->s.eFlags |= EF_NODRAW;
03259         }
03260 
03261         ent->use = use_wall;
03262 
03263         trap_LinkEntity (ent);
03264 
03265 }