codemp/game/g_misc.c

Go to the documentation of this file.
00001 // Copyright (C) 1999-2000 Id Software, Inc.
00002 //
00003 // g_misc.c
00004 
00005 #include "g_local.h"
00006 #include "../ghoul2/G2.h"
00007 
00008 #include "ai_main.h" //for the g2animents
00009 
00010 #define HOLOCRON_RESPAWN_TIME 30000
00011 #define MAX_AMMO_GIVE 2
00012 #define STATION_RECHARGE_TIME 100
00013 
00014 void HolocronThink(gentity_t *ent);
00015 extern vmCvar_t g_MaxHolocronCarry;
00016 
00017 /*QUAKED func_group (0 0 0) ?
00018 Used to group brushes together just for editor convenience.  They are turned into normal brushes by the utilities.
00019 */
00020 
00021 
00022 /*QUAKED info_camp (0 0.5 0) (-4 -4 -4) (4 4 4)
00023 Used as a positional target for calculations in the utilities (spotlights, etc), but removed during gameplay.
00024 */
00025 void SP_info_camp( gentity_t *self ) {
00026         G_SetOrigin( self, self->s.origin );
00027 }
00028 
00029 
00030 /*QUAKED info_null (0 0.5 0) (-4 -4 -4) (4 4 4)
00031 Used as a positional target for calculations in the utilities (spotlights, etc), but removed during gameplay.
00032 */
00033 void SP_info_null( gentity_t *self ) {
00034         G_FreeEntity( self );
00035 }
00036 
00037 
00038 /*QUAKED info_notnull (0 0.5 0) (-4 -4 -4) (4 4 4)
00039 Used as a positional target for in-game calculation, like jumppad targets.
00040 target_position does the same thing
00041 */
00042 void SP_info_notnull( gentity_t *self ){
00043         G_SetOrigin( self, self->s.origin );
00044 }
00045 
00046 
00047 /*QUAKED lightJunior (0 0.7 0.3) (-8 -8 -8) (8 8 8) nonlinear angle negative_spot negative_point
00048 Non-displayed light that only affects dynamic game models, but does not contribute to lightmaps
00049 "light" overrides the default 300 intensity.
00050 Nonlinear checkbox gives inverse square falloff instead of linear 
00051 Angle adds light:surface angle calculations (only valid for "Linear" lights) (wolf)
00052 Lights pointed at a target will be spotlights.
00053 "radius" overrides the default 64 unit radius of a spotlight at the target point.
00054 "fade" falloff/radius adjustment value. multiply the run of the slope by "fade" (1.0f default) (only valid for "Linear" lights) (wolf)
00055 */
00056 
00057 /*QUAKED light (0 1 0) (-8 -8 -8) (8 8 8) linear noIncidence START_OFF
00058 Non-displayed light.
00059 "light" overrides the default 300 intensity. - affects size
00060 a negative "light" will subtract the light's color
00061 'Linear' checkbox gives linear falloff instead of inverse square
00062 'noIncidence' checkbox makes lighting smoother
00063 Lights pointed at a target will be spotlights.
00064 "radius" overrides the default 64 unit radius of a spotlight at the target point.
00065 "scale" multiplier for the light intensity - does not affect size (default 1)
00066                 greater than 1 is brighter, between 0 and 1 is dimmer.
00067 "color" sets the light's color
00068 "targetname" to indicate a switchable light - NOTE that all lights with the same targetname will be grouped together and act as one light (ie: don't mix colors, styles or start_off flag)
00069 "style" to specify a specify light style, even for switchable lights!
00070 "style_off" light style to use when switched off (Only for switchable lights)
00071 
00072    1 FLICKER (first variety)
00073    2 SLOW STRONG PULSE
00074    3 CANDLE (first variety)
00075    4 FAST STROBE
00076    5 GENTLE PULSE 1
00077    6 FLICKER (second variety)
00078    7 CANDLE (second variety)
00079    8 CANDLE (third variety)
00080    9 SLOW STROBE (fourth variety)
00081    10 FLUORESCENT FLICKER
00082    11 SLOW PULSE NOT FADE TO BLACK
00083    12 FAST PULSE FOR JEREMY
00084    13 Test Blending
00085 */
00086 static void misc_lightstyle_set ( gentity_t *ent)
00087 {
00088         const int mLightStyle = ent->count;
00089         const int mLightSwitchStyle = ent->bounceCount;
00090         const int mLightOffStyle = ent->fly_sound_debounce_time;
00091         if (!ent->alt_fire)
00092         {       //turn off
00093                 if (mLightOffStyle)     //i have a light style i'd like to use when off
00094                 {
00095                         char lightstyle[32];
00096                         trap_GetConfigstring(CS_LIGHT_STYLES + (mLightOffStyle*3)+0, lightstyle, 32);
00097                         trap_SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+0, lightstyle);
00098 
00099                         trap_GetConfigstring(CS_LIGHT_STYLES + (mLightOffStyle*3)+1, lightstyle, 32);
00100                         trap_SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+1, lightstyle);
00101 
00102                         trap_GetConfigstring(CS_LIGHT_STYLES + (mLightOffStyle*3)+2, lightstyle, 32);
00103                         trap_SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+2, lightstyle);
00104                 }else
00105                 {
00106                         trap_SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+0, "a");
00107                         trap_SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+1, "a");
00108                         trap_SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+2, "a");
00109                 }
00110         } 
00111         else
00112         {       //Turn myself on now
00113                 if (mLightSwitchStyle)  //i have a light style i'd like to use when on
00114                 {
00115                         char lightstyle[32];
00116                         trap_GetConfigstring(CS_LIGHT_STYLES + (mLightSwitchStyle*3)+0, lightstyle, 32);
00117                         trap_SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+0, lightstyle);
00118 
00119                         trap_GetConfigstring(CS_LIGHT_STYLES + (mLightSwitchStyle*3)+1, lightstyle, 32);
00120                         trap_SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+1, lightstyle);
00121 
00122                         trap_GetConfigstring(CS_LIGHT_STYLES + (mLightSwitchStyle*3)+2, lightstyle, 32);
00123                         trap_SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+2, lightstyle);
00124                 }
00125                 else
00126                 {
00127                         trap_SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+0, "z");
00128                         trap_SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+1, "z");
00129                         trap_SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+2, "z");
00130                 }
00131         }
00132 }
00133 
00134 void misc_dlight_use ( gentity_t *ent, gentity_t *other, gentity_t *activator )
00135 {
00136         G_ActivateBehavior(ent,BSET_USE);
00137 
00138         ent->alt_fire = !ent->alt_fire; //toggle
00139         misc_lightstyle_set (ent);
00140 }
00141 
00142 void SP_light( gentity_t *self ) {
00143         if (!self->targetname )
00144         {//if i don't have a light style switch, the i go away
00145                 G_FreeEntity( self );
00146                 return;
00147         }
00148 
00149         G_SpawnInt( "style", "0", &self->count );
00150         G_SpawnInt( "switch_style", "0", &self->bounceCount );
00151         G_SpawnInt( "style_off", "0", &self->fly_sound_debounce_time );
00152         G_SetOrigin( self, self->s.origin );
00153         trap_LinkEntity( self );
00154 
00155         self->use = misc_dlight_use;
00156 
00157         self->s.eType = ET_GENERAL;
00158         self->alt_fire = qfalse;
00159         self->r.svFlags |= SVF_NOCLIENT;
00160 
00161         if ( !(self->spawnflags & 4) )
00162         {       //turn myself on now
00163                 self->alt_fire = qtrue;
00164         }
00165         misc_lightstyle_set (self);
00166 }
00167 
00168 
00169 /*
00170 =================================================================================
00171 
00172 TELEPORTERS
00173 
00174 =================================================================================
00175 */
00176 
00177 void TeleportPlayer( gentity_t *player, vec3_t origin, vec3_t angles ) {
00178         gentity_t       *tent;
00179         qboolean        isNPC = qfalse;
00180         if (player->s.eType == ET_NPC)
00181         {
00182                 isNPC = qtrue;
00183         }
00184 
00185         // use temp events at source and destination to prevent the effect
00186         // from getting dropped by a second player event
00187         if ( player->client->sess.sessionTeam != TEAM_SPECTATOR ) {
00188                 tent = G_TempEntity( player->client->ps.origin, EV_PLAYER_TELEPORT_OUT );
00189                 tent->s.clientNum = player->s.clientNum;
00190 
00191                 tent = G_TempEntity( origin, EV_PLAYER_TELEPORT_IN );
00192                 tent->s.clientNum = player->s.clientNum;
00193         }
00194 
00195         // unlink to make sure it can't possibly interfere with G_KillBox
00196         trap_UnlinkEntity (player);
00197 
00198         VectorCopy ( origin, player->client->ps.origin );
00199         player->client->ps.origin[2] += 1;
00200 
00201         // spit the player out
00202         AngleVectors( angles, player->client->ps.velocity, NULL, NULL );
00203         VectorScale( player->client->ps.velocity, 400, player->client->ps.velocity );
00204         player->client->ps.pm_time = 160;               // hold time
00205         player->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
00206 
00207         // toggle the teleport bit so the client knows to not lerp
00208         player->client->ps.eFlags ^= EF_TELEPORT_BIT;
00209 
00210         // set angles
00211         SetClientViewAngle( player, angles );
00212 
00213         // kill anything at the destination
00214         if ( player->client->sess.sessionTeam != TEAM_SPECTATOR ) {
00215                 G_KillBox (player);
00216         }
00217 
00218         // save results of pmove
00219         BG_PlayerStateToEntityState( &player->client->ps, &player->s, qtrue );
00220         if (isNPC)
00221         {
00222                 player->s.eType = ET_NPC;
00223         }
00224 
00225         // use the precise origin for linking
00226         VectorCopy( player->client->ps.origin, player->r.currentOrigin );
00227 
00228         if ( player->client->sess.sessionTeam != TEAM_SPECTATOR ) {
00229                 trap_LinkEntity (player);
00230         }
00231 }
00232 
00233 
00234 /*QUAKED misc_teleporter_dest (1 0 0) (-32 -32 -24) (32 32 -16)
00235 Point teleporters at these.
00236 Now that we don't have teleport destination pads, this is just
00237 an info_notnull
00238 */
00239 void SP_misc_teleporter_dest( gentity_t *ent ) {
00240 }
00241 
00242 
00243 //===========================================================
00244 
00245 /*QUAKED misc_model (1 0 0) (-16 -16 -16) (16 16 16)
00246 "model"         arbitrary .md3 or .ase file to display
00247 turns into map triangles - not solid
00248 */
00249 void SP_misc_model( gentity_t *ent ) {
00250 
00251 #if 0
00252         ent->s.modelindex = G_ModelIndex( ent->model );
00253         VectorSet (ent->r.mins, -16, -16, -16);
00254         VectorSet (ent->r.maxs, 16, 16, 16);
00255         trap_LinkEntity (ent);
00256 
00257         G_SetOrigin( ent, ent->s.origin );
00258         VectorCopy( ent->s.angles, ent->s.apos.trBase );
00259 #else
00260         G_FreeEntity( ent );
00261 #endif
00262 }
00263 
00264 /*QUAKED misc_model_static (1 0 0) (-16 -16 0) (16 16 16)
00265 "model"         arbitrary .md3 file to display
00266 "zoffset"       units to offset vertical culling position by, can be
00267                         negative or positive. This does not affect the actual
00268                         position of the model, only the culling position. Use
00269                         it for models with stupid origins that go below the
00270                         ground and whatnot.
00271 "modelscale" scale on all axis
00272 "modelscale_vec" scale difference axis
00273 
00274 loaded as a model in the renderer - does not take up precious
00275 bsp space!
00276 */
00277 void SP_misc_model_static(gentity_t *ent)
00278 {
00279         G_FreeEntity( ent );
00280 }
00281 
00282 /*QUAKED misc_G2model (1 0 0) (-16 -16 -16) (16 16 16)
00283 "model"         arbitrary .glm file to display
00284 */
00285 void SP_misc_G2model( gentity_t *ent ) {
00286 
00287 #if 0
00288         char name1[200] = "models/players/kyle/modelmp.glm";
00289         trap_G2API_InitGhoul2Model(&ent->s, name1, G_ModelIndex( name1 ), 0, 0, 0, 0);
00290         trap_G2API_SetBoneAnim(ent->s.ghoul2, 0, "model_root", 0, 12, BONE_ANIM_OVERRIDE_LOOP, 1.0f, level.time, -1, -1);
00291         ent->s.radius = 150;
00292 //      VectorSet (ent->r.mins, -16, -16, -16);
00293 //      VectorSet (ent->r.maxs, 16, 16, 16);
00294         trap_LinkEntity (ent);
00295 
00296         G_SetOrigin( ent, ent->s.origin );
00297         VectorCopy( ent->s.angles, ent->s.apos.trBase );
00298 #else
00299         G_FreeEntity( ent );
00300 #endif
00301 }
00302 
00303 //===========================================================
00304 
00305 void locateCamera( gentity_t *ent ) {
00306         vec3_t          dir;
00307         gentity_t       *target;
00308         gentity_t       *owner;
00309 
00310         owner = G_PickTarget( ent->target );
00311         if ( !owner ) {
00312                 G_Printf( "Couldn't find target for misc_partal_surface\n" );
00313                 G_FreeEntity( ent );
00314                 return;
00315         }
00316         ent->r.ownerNum = owner->s.number;
00317 
00318         // frame holds the rotate speed
00319         if ( owner->spawnflags & 1 ) {
00320                 ent->s.frame = 25;
00321         } else if ( owner->spawnflags & 2 ) {
00322                 ent->s.frame = 75;
00323         }
00324 
00325         // swing camera ?
00326         if ( owner->spawnflags & 4 ) {
00327                 // set to 0 for no rotation at all
00328                 ent->s.powerups = 0;
00329         }
00330         else {
00331                 ent->s.powerups = 1;
00332         }
00333 
00334         // clientNum holds the rotate offset
00335         ent->s.clientNum = owner->s.clientNum;
00336 
00337         VectorCopy( owner->s.origin, ent->s.origin2 );
00338 
00339         // see if the portal_camera has a target
00340         target = G_PickTarget( owner->target );
00341         if ( target ) {
00342                 VectorSubtract( target->s.origin, owner->s.origin, dir );
00343                 VectorNormalize( dir );
00344         } else {
00345                 G_SetMovedir( owner->s.angles, dir );
00346         }
00347 
00348         ent->s.eventParm = DirToByte( dir );
00349 }
00350 
00351 /*QUAKED misc_portal_surface (0 0 1) (-8 -8 -8) (8 8 8)
00352 The portal surface nearest this entity will show a view from the targeted misc_portal_camera, or a mirror view if untargeted.
00353 This must be within 64 world units of the surface!
00354 */
00355 void SP_misc_portal_surface(gentity_t *ent) {
00356         VectorClear( ent->r.mins );
00357         VectorClear( ent->r.maxs );
00358         trap_LinkEntity (ent);
00359 
00360         ent->r.svFlags = SVF_PORTAL;
00361         ent->s.eType = ET_PORTAL;
00362 
00363         if ( !ent->target ) {
00364                 VectorCopy( ent->s.origin, ent->s.origin2 );
00365         } else {
00366                 ent->think = locateCamera;
00367                 ent->nextthink = level.time + 100;
00368         }
00369 }
00370 
00371 /*QUAKED misc_portal_camera (0 0 1) (-8 -8 -8) (8 8 8) slowrotate fastrotate noswing
00372 The target for a misc_portal_director.  You can set either angles or target another entity to determine the direction of view.
00373 "roll" an angle modifier to orient the camera around the target vector;
00374 */
00375 void SP_misc_portal_camera(gentity_t *ent) {
00376         float   roll;
00377 
00378         VectorClear( ent->r.mins );
00379         VectorClear( ent->r.maxs );
00380         trap_LinkEntity (ent);
00381 
00382         G_SpawnFloat( "roll", "0", &roll );
00383 
00384         ent->s.clientNum = roll/360.0 * 256;
00385 }
00386 
00387 /*QUAKED misc_bsp (1 0 0) (-16 -16 -16) (16 16 16)
00388 "bspmodel"              arbitrary .bsp file to display
00389 */
00390 void SP_misc_bsp(gentity_t *ent) 
00391 {
00392         char    temp[MAX_QPATH];
00393         char    *out;
00394         float   newAngle;
00395         int             tempint;
00396 
00397         G_SpawnFloat( "angle", "0", &newAngle );
00398         if (newAngle != 0.0)
00399         {
00400                 ent->s.angles[1] = newAngle;
00401         }
00402         // don't support rotation any other way
00403         ent->s.angles[0] = 0.0;
00404         ent->s.angles[2] = 0.0;
00405         
00406         G_SpawnString("bspmodel", "", &out);
00407 
00408         ent->s.eFlags = EF_PERMANENT;
00409 
00410         // Mainly for debugging
00411         G_SpawnInt( "spacing", "0", &tempint);
00412         ent->s.time2 = tempint;
00413         G_SpawnInt( "flatten", "0", &tempint);
00414         ent->s.time = tempint;
00415 
00416         Com_sprintf(temp, MAX_QPATH, "#%s", out);
00417         trap_SetBrushModel( ent, temp );  // SV_SetBrushModel -- sets mins and maxs
00418         G_BSPIndex(temp);
00419 
00420         level.mNumBSPInstances++;
00421         Com_sprintf(temp, MAX_QPATH, "%d-", level.mNumBSPInstances);
00422         VectorCopy(ent->s.origin, level.mOriginAdjust);
00423         level.mRotationAdjust = ent->s.angles[1];
00424         level.mTargetAdjust = temp;
00425         //level.hasBspInstances = qtrue; //rww - also not referenced anywhere.
00426         level.mBSPInstanceDepth++;
00427         /*
00428         G_SpawnString("filter", "", &out);
00429         strcpy(level.mFilter, out);
00430         */
00431         G_SpawnString("teamfilter", "", &out);
00432         strcpy(level.mTeamFilter, out);
00433 
00434         VectorCopy( ent->s.origin, ent->s.pos.trBase );
00435         VectorCopy( ent->s.origin, ent->r.currentOrigin );
00436         VectorCopy( ent->s.angles, ent->s.apos.trBase );
00437         VectorCopy( ent->s.angles, ent->r.currentAngles );
00438 
00439         ent->s.eType = ET_MOVER;
00440 
00441         trap_LinkEntity (ent);
00442 
00443         trap_SetActiveSubBSP(ent->s.modelindex);
00444         G_SpawnEntitiesFromString(qtrue);
00445         trap_SetActiveSubBSP(-1);
00446 
00447         level.mBSPInstanceDepth--;
00448         //level.mFilter[0] = level.mTeamFilter[0] = 0;
00449         level.mTeamFilter[0] = 0;
00450 
00451         /*
00452         if ( g_debugRMG.integer )
00453         {
00454                 G_SpawnDebugCylinder ( ent->s.origin, ent->s.time2, &g_entities[0], 2000, COLOR_WHITE );
00455 
00456                 if ( ent->s.time )
00457                 {
00458                         G_SpawnDebugCylinder ( ent->s.origin, ent->s.time, &g_entities[0], 2000, COLOR_RED );
00459                 }
00460         }
00461         */
00462 }
00463 
00464 /*QUAKED terrain (1.0 1.0 1.0) ? NOVEHDMG
00465 
00466 NOVEHDMG - don't damage vehicles upon impact with this terrain
00467 
00468 Terrain entity
00469 It will stretch to the full height of the brush
00470 
00471 numPatches - integer number of patches to split the terrain brush into (default 200)
00472 terxels - integer number of terxels on a patch side (default 4) (2 <= count <= 8)
00473 seed - integer seed for random terrain generation (default 0)
00474 textureScale - float scale of texture (default 0.005)
00475 heightmap - name of heightmap data image to use, located in heightmaps/*.png. (must be PNG format)
00476 terrainDef - defines how the game textures the terrain (file is base/ext_data/rmg/*.terrain - default is grassyhills)
00477 instanceDef - defines which bsp instances appear
00478 miscentDef - defines which client models spawn on the terrain (file is base/ext_data/rmg/*.miscents)
00479 densityMap - how dense the client models are packed
00480 
00481 */
00482 void AddSpawnField(char *field, char *value);
00483 #define MAX_INSTANCE_TYPES              16
00484 void SP_terrain(gentity_t *ent) 
00485 {
00486         char                            temp[MAX_INFO_STRING];
00487         char                            final[MAX_QPATH];
00488         char                            seed[MAX_QPATH];
00489         char                            missionType[MAX_QPATH];
00490         //char                          soundSet[MAX_QPATH];
00491         int                                     shaderNum, i;
00492         char                            *value;
00493         int                                     terrainID;
00494 
00495         //Force it to 1 when there is terrain on the level.
00496         trap_Cvar_Set("RMG", "1");
00497         g_RMG.integer = 1;
00498 
00499         VectorClear (ent->s.angles);
00500         trap_SetBrushModel( ent, ent->model );
00501 
00502         // Get the shader from the top of the brush
00503 //      shaderNum = gi.CM_GetShaderNum(s.modelindex);
00504         shaderNum = 0;
00505 
00506         if (g_RMG.integer)
00507         {
00508                 /*
00509                 // Grab the default terrain file from the RMG cvar
00510                 trap_Cvar_VariableStringBuffer("RMG_terrain", temp, MAX_QPATH);
00511                 Com_sprintf(final, MAX_QPATH, "%s", temp);
00512                 AddSpawnField("terrainDef", temp);
00513  
00514                 trap_Cvar_VariableStringBuffer("RMG_instances", temp, MAX_QPATH);
00515                 Com_sprintf(final, MAX_QPATH, "%s", temp);
00516                 AddSpawnField("instanceDef", temp);
00517 
00518                 trap_Cvar_VariableStringBuffer("RMG_miscents", temp, MAX_QPATH);
00519                 Com_sprintf(final, MAX_QPATH, "%s", temp);
00520                 AddSpawnField("miscentDef", temp);
00521                 */
00522                 //rww - disabled for now, don't want cvar overrides.
00523 
00524                 trap_Cvar_VariableStringBuffer("RMG_seed", seed, MAX_QPATH);
00525                 trap_Cvar_VariableStringBuffer("RMG_mission", missionType, MAX_QPATH);
00526 
00527                 //rww - May want to implement these at some point.
00528                 //trap_Cvar_VariableStringBuffer("RMG_soundset", soundSet, MAX_QPATH);
00529                 //trap_SetConfigstring(CS_AMBIENT_SOUNDSETS, soundSet );
00530         }
00531 
00532         // Get info required for the common init
00533         temp[0] = 0;
00534         G_SpawnString("heightmap", "", &value);
00535         Info_SetValueForKey(temp, "heightMap", value);
00536 
00537         G_SpawnString("numpatches", "400", &value);
00538         Info_SetValueForKey(temp, "numPatches", va("%d", atoi(value)));
00539 
00540         G_SpawnString("terxels", "4", &value);
00541         Info_SetValueForKey(temp, "terxels", va("%d", atoi(value)));
00542 
00543         Info_SetValueForKey(temp, "seed", seed);
00544         Info_SetValueForKey(temp, "minx", va("%f", ent->r.mins[0]));
00545         Info_SetValueForKey(temp, "miny", va("%f", ent->r.mins[1]));
00546         Info_SetValueForKey(temp, "minz", va("%f", ent->r.mins[2]));
00547         Info_SetValueForKey(temp, "maxx", va("%f", ent->r.maxs[0]));
00548         Info_SetValueForKey(temp, "maxy", va("%f", ent->r.maxs[1]));
00549         Info_SetValueForKey(temp, "maxz", va("%f", ent->r.maxs[2]));
00550 
00551         Info_SetValueForKey(temp, "modelIndex", va("%d", ent->s.modelindex));
00552 
00553         G_SpawnString("terraindef", "grassyhills", &value);
00554         Info_SetValueForKey(temp, "terrainDef", value);
00555 
00556         G_SpawnString("instancedef", "", &value);
00557         Info_SetValueForKey(temp, "instanceDef", value);
00558 
00559         G_SpawnString("miscentdef", "", &value);
00560         Info_SetValueForKey(temp, "miscentDef", value);
00561 
00562         Info_SetValueForKey(temp, "missionType", missionType);
00563         
00564         for(i = 0; i < MAX_INSTANCE_TYPES; i++)
00565         {
00566                 trap_Cvar_VariableStringBuffer(va("RMG_instance%d", i), final, MAX_QPATH);
00567                 if(strlen(final))
00568                 {
00569                         Info_SetValueForKey(temp, va("inst%d", i), final);
00570                 }
00571         }
00572 
00573         // Set additional data required on the client only
00574         G_SpawnString("densitymap", "", &value);
00575         Info_SetValueForKey(temp, "densityMap", value);
00576 
00577         Info_SetValueForKey(temp, "shader", va("%d", shaderNum));
00578         G_SpawnString("texturescale", "0.005", &value);
00579         Info_SetValueForKey(temp, "texturescale", va("%f", atof(value)));
00580 
00581         // Initialise the common aspects of the terrain
00582         terrainID = trap_CM_RegisterTerrain(temp);
00583 //      SetCommon(common);
00584 
00585         Info_SetValueForKey(temp, "terrainId", va("%d", terrainID));
00586 
00587         // Let the entity know if it is random generated or not
00588 //      SetIsRandom(common->GetIsRandom());
00589 
00590         // Let the game remember everything
00591         //level.landScapes[terrainID] = ent; //rww - also not referenced
00592 
00593         // Send all the data down to the client
00594         trap_SetConfigstring(CS_TERRAINS + terrainID, temp);
00595 
00596         // Make sure the contents are properly set
00597         ent->r.contents = CONTENTS_TERRAIN;
00598         ent->r.svFlags = SVF_NOCLIENT;
00599         ent->s.eFlags = EF_PERMANENT;
00600         ent->s.eType = ET_TERRAIN;
00601 
00602         // Hook into the world so physics will work
00603         trap_LinkEntity(ent);
00604 
00605         // If running RMG then initialize the terrain and handle team skins
00606         if ( g_RMG.integer ) 
00607         {
00608                 trap_RMG_Init(terrainID);
00609 
00610                 /*
00611                 if ( level.gametypeData->teams )
00612                 {
00613                         char temp[MAX_QPATH];
00614 
00615                         // Red team change from RMG ?
00616                         trap_GetConfigstring ( CS_GAMETYPE_REDTEAM, temp, MAX_QPATH );
00617                         if ( Q_stricmp ( temp, level.gametypeTeam[TEAM_RED] ) )
00618                         {
00619                                 level.gametypeTeam[TEAM_RED] = trap_VM_LocalStringAlloc ( temp );
00620                         }
00621 
00622                         // Blue team change from RMG ?
00623                         trap_GetConfigstring ( CS_GAMETYPE_BLUETEAM, temp, MAX_QPATH );
00624                         if ( Q_stricmp ( temp, level.gametypeTeam[TEAM_BLUE] ) )
00625                         {
00626                                 level.gametypeTeam[TEAM_BLUE] = trap_VM_LocalStringAlloc ( temp );
00627                         }
00628                 }
00629                 */
00630         }
00631 }
00632 
00633 //rww - Called by skyportal entities. This will check through entities and flag them
00634 //as portal ents if they are in the same pvs as a skyportal entity and pass
00635 //a direct point trace check between origins. I really wanted to use an eFlag for
00636 //flagging portal entities, but too many entities like to reset their eFlags.
00637 //Note that this was not part of the original wolf sky portal stuff.
00638 void G_PortalifyEntities(gentity_t *ent)
00639 {
00640         int i = 0;
00641         gentity_t *scan = NULL;
00642 
00643         while (i < MAX_GENTITIES)
00644         {
00645                 scan = &g_entities[i];
00646 
00647                 if (scan && scan->inuse && scan->s.number != ent->s.number && trap_InPVS(ent->s.origin, scan->r.currentOrigin))
00648                 {
00649                         trace_t tr;
00650 
00651                         trap_Trace(&tr, ent->s.origin, vec3_origin, vec3_origin, scan->r.currentOrigin, ent->s.number, CONTENTS_SOLID);
00652 
00653                         if (tr.fraction == 1.0 || (tr.entityNum == scan->s.number && tr.entityNum != ENTITYNUM_NONE && tr.entityNum != ENTITYNUM_WORLD))
00654                         {
00655                                 if (!scan->client || scan->s.eType == ET_NPC)
00656                                 { //making a client a portal entity would be bad.
00657                                         scan->s.isPortalEnt = qtrue; //he's flagged now
00658                                 }
00659                         }
00660                 }
00661 
00662                 i++;
00663         }
00664 
00665         ent->think = G_FreeEntity; //the portal entity is no longer needed because its information is stored in a config string.
00666         ent->nextthink = level.time;
00667 }
00668 
00669 /*QUAKED misc_skyportal_orient (.6 .7 .7) (-8 -8 0) (8 8 16)
00670 point from which to orient the sky portal cam in relation
00671 to the regular view position.
00672 
00673 "modelscale"                    the scale at which to scale positions
00674 */
00675 void SP_misc_skyportal_orient (gentity_t *ent)
00676 {
00677         G_FreeEntity(ent);
00678 }
00679 
00680 
00681 /*QUAKED misc_skyportal (.6 .7 .7) (-8 -8 0) (8 8 16)
00682 "fov" for the skybox default is 80
00683 To have the portal sky fogged, enter any of the following values:
00684 "onlyfoghere" if non-0 allows you to set a global fog, but will only use that fog within this sky portal.
00685 
00686 Also note that entities in the same PVS and visible (via point trace) from this
00687 object will be flagged as portal entities. This means they will be sent and
00688 updated from the server for every client every update regardless of where
00689 they are, and they will essentially be added to the scene twice if the client
00690 is in the same PVS as them (only once otherwise, but still once no matter
00691 where the client is). In other words, don't go overboard with it or everything
00692 will explode.
00693 */
00694 void SP_misc_skyportal (gentity_t *ent)
00695 {
00696         char    *fov;
00697         vec3_t  fogv;   //----(SA)      
00698         int             fogn;   //----(SA)      
00699         int             fogf;   //----(SA)      
00700         int             isfog = 0;      // (SA)
00701 
00702         float   fov_x;
00703 
00704         G_SpawnString ("fov", "80", &fov);
00705         fov_x = atof (fov);
00706 
00707         isfog += G_SpawnVector ("fogcolor", "0 0 0", fogv);
00708         isfog += G_SpawnInt ("fognear", "0", &fogn);
00709         isfog += G_SpawnInt ("fogfar", "300", &fogf);
00710 
00711         trap_SetConfigstring( CS_SKYBOXORG, va("%.2f %.2f %.2f %.1f %i %.2f %.2f %.2f %i %i", ent->s.origin[0], ent->s.origin[1], ent->s.origin[2], fov_x, (int)isfog, fogv[0], fogv[1], fogv[2], fogn, fogf ) );
00712 
00713         ent->think = G_PortalifyEntities;
00714         ent->nextthink = level.time + 1050; //give it some time first so that all other entities are spawned.
00715 }
00716 
00717 /*QUAKED misc_holocron (0 0 1) (-8 -8 -8) (8 8 8)
00718 count   Set to type of holocron (based on force power value)
00719         HEAL = 0
00720         JUMP = 1
00721         SPEED = 2
00722         PUSH = 3
00723         PULL = 4
00724         TELEPATHY = 5
00725         GRIP = 6
00726         LIGHTNING = 7
00727         RAGE = 8
00728         PROTECT = 9
00729         ABSORB = 10
00730         TEAM HEAL = 11
00731         TEAM FORCE = 12
00732         DRAIN = 13
00733         SEE = 14
00734         SABERATTACK = 15
00735         SABERDEFEND = 16
00736         SABERTHROW = 17
00737 */
00738 
00739 /*char *holocronTypeModels[] = {
00740         "models/chunks/rock/rock_big.md3",//FP_HEAL,
00741         "models/chunks/rock/rock_big.md3",//FP_LEVITATION,
00742         "models/chunks/rock/rock_big.md3",//FP_SPEED,
00743         "models/chunks/rock/rock_big.md3",//FP_PUSH,
00744         "models/chunks/rock/rock_big.md3",//FP_PULL,
00745         "models/chunks/rock/rock_big.md3",//FP_TELEPATHY,
00746         "models/chunks/rock/rock_big.md3",//FP_GRIP,
00747         "models/chunks/rock/rock_big.md3",//FP_LIGHTNING,
00748         "models/chunks/rock/rock_big.md3",//FP_RAGE,
00749         "models/chunks/rock/rock_big.md3",//FP_PROTECT,
00750         "models/chunks/rock/rock_big.md3",//FP_ABSORB,
00751         "models/chunks/rock/rock_big.md3",//FP_TEAM_HEAL,
00752         "models/chunks/rock/rock_big.md3",//FP_TEAM_FORCE,
00753         "models/chunks/rock/rock_big.md3",//FP_DRAIN,
00754         "models/chunks/rock/rock_big.md3",//FP_SEE
00755         "models/chunks/rock/rock_big.md3",//FP_SABER_OFFENSE
00756         "models/chunks/rock/rock_big.md3",//FP_SABER_DEFENSE
00757         "models/chunks/rock/rock_big.md3"//FP_SABERTHROW
00758 };*/
00759 
00760 void HolocronRespawn(gentity_t *self)
00761 {
00762         self->s.modelindex = (self->count - 128);
00763 }
00764 
00765 void HolocronPopOut(gentity_t *self)
00766 {
00767         if (Q_irand(1, 10) < 5)
00768         {
00769                 self->s.pos.trDelta[0] = 150 + Q_irand(1, 100);
00770         }
00771         else
00772         {
00773                 self->s.pos.trDelta[0] = -150 - Q_irand(1, 100);
00774         }
00775         if (Q_irand(1, 10) < 5)
00776         {
00777                 self->s.pos.trDelta[1] = 150 + Q_irand(1, 100);
00778         }
00779         else
00780         {
00781                 self->s.pos.trDelta[1] = -150 - Q_irand(1, 100);
00782         }
00783         self->s.pos.trDelta[2] = 150 + Q_irand(1, 100);
00784 }
00785 
00786 void HolocronTouch(gentity_t *self, gentity_t *other, trace_t *trace)
00787 {
00788         int i = 0;
00789         int othercarrying = 0;
00790         float time_lowest = 0;
00791         int index_lowest = -1;
00792         int hasall = 1;
00793         int forceReselect = WP_NONE;
00794 
00795         if (trace)
00796         {
00797                 self->s.groundEntityNum = trace->entityNum;
00798         }
00799 
00800         if (!other || !other->client || other->health < 1)
00801         {
00802                 return;
00803         }
00804 
00805         if (!self->s.modelindex)
00806         {
00807                 return;
00808         }
00809 
00810         if (self->enemy)
00811         {
00812                 return;
00813         }
00814 
00815         if (other->client->ps.holocronsCarried[self->count])
00816         {
00817                 return;
00818         }
00819 
00820         if (other->client->ps.holocronCantTouch == self->s.number && other->client->ps.holocronCantTouchTime > level.time)
00821         {
00822                 return;
00823         }
00824 
00825         while (i < NUM_FORCE_POWERS)
00826         {
00827                 if (other->client->ps.holocronsCarried[i])
00828                 {
00829                         othercarrying++;
00830 
00831                         if (index_lowest == -1 || other->client->ps.holocronsCarried[i] < time_lowest)
00832                         {
00833                                 index_lowest = i;
00834                                 time_lowest = other->client->ps.holocronsCarried[i];
00835                         }
00836                 }
00837                 else if (i != self->count)
00838                 {
00839                         hasall = 0;
00840                 }
00841                 i++;
00842         }
00843 
00844         if (hasall)
00845         { //once we pick up this holocron we'll have all of them, so give us super special best prize!
00846                 //G_Printf("You deserve a pat on the back.\n");
00847         }
00848 
00849         if (!(other->client->ps.fd.forcePowersActive & (1 << other->client->ps.fd.forcePowerSelected)))
00850         { //If the player isn't using his currently selected force power, select this one
00851                 if (self->count != FP_SABER_OFFENSE && self->count != FP_SABER_DEFENSE && self->count != FP_SABERTHROW && self->count != FP_LEVITATION)
00852                 {
00853                         other->client->ps.fd.forcePowerSelected = self->count;
00854                 }
00855         }
00856 
00857         if (g_MaxHolocronCarry.integer && othercarrying >= g_MaxHolocronCarry.integer)
00858         { //make the oldest holocron carried by the player pop out to make room for this one
00859                 other->client->ps.holocronsCarried[index_lowest] = 0;
00860 
00861                 /*
00862                 if (index_lowest == FP_SABER_OFFENSE && !HasSetSaberOnly())
00863                 { //you lost your saberattack holocron, so no more saber for you
00864                         other->client->ps.stats[STAT_WEAPONS] |= (1 << WP_STUN_BATON);
00865                         other->client->ps.stats[STAT_WEAPONS] &= ~(1 << WP_SABER);
00866 
00867                         if (other->client->ps.weapon == WP_SABER)
00868                         {
00869                                 forceReselect = WP_SABER;
00870                         }
00871                 }
00872                 */
00873                 //NOTE: No longer valid as we are now always giving a force level 1 saber attack level in holocron
00874         }
00875 
00876         //G_Sound(other, CHAN_AUTO, G_SoundIndex("sound/weapons/w_pkup.wav"));
00877         G_AddEvent( other, EV_ITEM_PICKUP, self->s.number );
00878 
00879         other->client->ps.holocronsCarried[self->count] = level.time;
00880         self->s.modelindex = 0;
00881         self->enemy = other;
00882 
00883         self->pos2[0] = 1;
00884         self->pos2[1] = level.time + HOLOCRON_RESPAWN_TIME;
00885 
00886         /*
00887         if (self->count == FP_SABER_OFFENSE && !HasSetSaberOnly())
00888         { //player gets a saber
00889                 other->client->ps.stats[STAT_WEAPONS] |= (1 << WP_SABER);
00890                 other->client->ps.stats[STAT_WEAPONS] &= ~(1 << WP_STUN_BATON);
00891 
00892                 if (other->client->ps.weapon == WP_STUN_BATON)
00893                 {
00894                         forceReselect = WP_STUN_BATON;
00895                 }
00896         }
00897         */
00898 
00899         if (forceReselect != WP_NONE)
00900         {
00901                 G_AddEvent(other, EV_NOAMMO, forceReselect);
00902         }
00903 
00904         //G_Printf("DON'T TOUCH ME\n");
00905 }
00906 
00907 void HolocronThink(gentity_t *ent)
00908 {
00909         if (ent->pos2[0] && (!ent->enemy || !ent->enemy->client || ent->enemy->health < 1))
00910         {
00911                 if (ent->enemy && ent->enemy->client)
00912                 {
00913                         HolocronRespawn(ent);
00914                         VectorCopy(ent->enemy->client->ps.origin, ent->s.pos.trBase);
00915                         VectorCopy(ent->enemy->client->ps.origin, ent->s.origin);
00916                         VectorCopy(ent->enemy->client->ps.origin, ent->r.currentOrigin);
00917                         //copy to person carrying's origin before popping out of them
00918                         HolocronPopOut(ent);
00919                         ent->enemy->client->ps.holocronsCarried[ent->count] = 0;
00920                         ent->enemy = NULL;
00921                         
00922                         goto justthink;
00923                 }
00924         }
00925         else if (ent->pos2[0] && ent->enemy && ent->enemy->client)
00926         {
00927                 ent->pos2[1] = level.time + HOLOCRON_RESPAWN_TIME;
00928         }
00929 
00930         if (ent->enemy && ent->enemy->client)
00931         {
00932                 if (!ent->enemy->client->ps.holocronsCarried[ent->count])
00933                 {
00934                         ent->enemy->client->ps.holocronCantTouch = ent->s.number;
00935                         ent->enemy->client->ps.holocronCantTouchTime = level.time + 5000;
00936 
00937                         HolocronRespawn(ent);
00938                         VectorCopy(ent->enemy->client->ps.origin, ent->s.pos.trBase);
00939                         VectorCopy(ent->enemy->client->ps.origin, ent->s.origin);
00940                         VectorCopy(ent->enemy->client->ps.origin, ent->r.currentOrigin);
00941                         //copy to person carrying's origin before popping out of them
00942                         HolocronPopOut(ent);
00943                         ent->enemy = NULL;
00944 
00945                         goto justthink;
00946                 }
00947 
00948                 if (!ent->enemy->inuse || (ent->enemy->client && ent->enemy->client->ps.fallingToDeath))
00949                 {
00950                         if (ent->enemy->inuse && ent->enemy->client)
00951                         {
00952                                 ent->enemy->client->ps.holocronBits &= ~(1 << ent->count);
00953                                 ent->enemy->client->ps.holocronsCarried[ent->count] = 0;
00954                         }
00955                         ent->enemy = NULL;
00956                         HolocronRespawn(ent);
00957                         VectorCopy(ent->s.origin2, ent->s.pos.trBase);
00958                         VectorCopy(ent->s.origin2, ent->s.origin);
00959                         VectorCopy(ent->s.origin2, ent->r.currentOrigin);
00960 
00961                         ent->s.pos.trTime = level.time;
00962 
00963                         ent->pos2[0] = 0;
00964 
00965                         trap_LinkEntity(ent);
00966 
00967                         goto justthink;
00968                 }
00969         }
00970 
00971         if (ent->pos2[0] && ent->pos2[1] < level.time)
00972         { //isn't in original place and has been there for (HOLOCRON_RESPAWN_TIME) seconds without being picked up, so respawn
00973                 VectorCopy(ent->s.origin2, ent->s.pos.trBase);
00974                 VectorCopy(ent->s.origin2, ent->s.origin);
00975                 VectorCopy(ent->s.origin2, ent->r.currentOrigin);
00976 
00977                 ent->s.pos.trTime = level.time;
00978 
00979                 ent->pos2[0] = 0;
00980 
00981                 trap_LinkEntity(ent);
00982         }
00983 
00984 justthink:
00985         ent->nextthink = level.time + 50;
00986 
00987         if (ent->s.pos.trDelta[0] || ent->s.pos.trDelta[1] || ent->s.pos.trDelta[2])
00988         {
00989                 G_RunObject(ent);
00990         }
00991 }
00992 
00993 void SP_misc_holocron(gentity_t *ent)
00994 {
00995         vec3_t dest;
00996         trace_t tr;
00997 
00998         if (g_gametype.integer != GT_HOLOCRON)
00999         {
01000                 G_FreeEntity(ent);
01001                 return;
01002         }
01003 
01004         if (HasSetSaberOnly())
01005         {
01006                 if (ent->count == FP_SABER_OFFENSE ||
01007                         ent->count == FP_SABER_DEFENSE ||
01008                         ent->count == FP_SABERTHROW)
01009                 { //having saber holocrons in saber only mode is pointless
01010                         G_FreeEntity(ent);
01011                         return;
01012                 }
01013         }
01014 
01015         ent->s.isJediMaster = qtrue;
01016 
01017         VectorSet( ent->r.maxs, 8, 8, 8 );
01018         VectorSet( ent->r.mins, -8, -8, -8 );
01019 
01020         ent->s.origin[2] += 0.1;
01021         ent->r.maxs[2] -= 0.1;
01022 
01023         VectorSet( dest, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2] - 4096 );
01024         trap_Trace( &tr, ent->s.origin, ent->r.mins, ent->r.maxs, dest, ent->s.number, MASK_SOLID );
01025         if ( tr.startsolid )
01026         {
01027                 G_Printf ("SP_misc_holocron: misc_holocron startsolid at %s\n", vtos(ent->s.origin));
01028                 G_FreeEntity( ent );
01029                 return;
01030         }
01031 
01032         //add the 0.1 back after the trace
01033         ent->r.maxs[2] += 0.1;
01034 
01035         // allow to ride movers
01036 //      ent->s.groundEntityNum = tr.entityNum;
01037 
01038         G_SetOrigin( ent, tr.endpos );
01039 
01040         if (ent->count < 0)
01041         {
01042                 ent->count = 0;
01043         }
01044 
01045         if (ent->count >= NUM_FORCE_POWERS)
01046         {
01047                 ent->count = NUM_FORCE_POWERS-1;
01048         }
01049 /*
01050         if (g_forcePowerDisable.integer &&
01051                 (g_forcePowerDisable.integer & (1 << ent->count)))
01052         {
01053                 G_FreeEntity(ent);
01054                 return;
01055         }
01056 */
01057         //No longer doing this, causing too many complaints about accidentally setting no force powers at all
01058         //and starting a holocron game (making it basically just FFA)
01059 
01060         ent->enemy = NULL;
01061 
01062         ent->flags = FL_BOUNCE_HALF;
01063 
01064         ent->s.modelindex = (ent->count - 128);//G_ModelIndex(holocronTypeModels[ent->count]);
01065         ent->s.eType = ET_HOLOCRON;
01066         ent->s.pos.trType = TR_GRAVITY;
01067         ent->s.pos.trTime = level.time;
01068 
01069         ent->r.contents = CONTENTS_TRIGGER;
01070         ent->clipmask = MASK_SOLID;
01071 
01072         ent->s.trickedentindex4 = ent->count;
01073 
01074         if (forcePowerDarkLight[ent->count] == FORCE_DARKSIDE)
01075         {
01076                 ent->s.trickedentindex3 = 1;
01077         }
01078         else if (forcePowerDarkLight[ent->count] == FORCE_LIGHTSIDE)
01079         {
01080                 ent->s.trickedentindex3 = 2;
01081         }
01082         else
01083         {
01084                 ent->s.trickedentindex3 = 3;
01085         }
01086 
01087         ent->physicsObject = qtrue;
01088 
01089         VectorCopy(ent->s.pos.trBase, ent->s.origin2); //remember the spawn spot
01090 
01091         ent->touch = HolocronTouch;
01092 
01093         trap_LinkEntity(ent);
01094 
01095         ent->think = HolocronThink;
01096         ent->nextthink = level.time + 50;
01097 }
01098 
01099 /*
01100 ======================================================================
01101 
01102   SHOOTERS
01103 
01104 ======================================================================
01105 */
01106 
01107 void Use_Shooter( gentity_t *ent, gentity_t *other, gentity_t *activator ) {
01108         vec3_t          dir;
01109         float           deg;
01110         vec3_t          up, right;
01111 
01112         // see if we have a target
01113         if ( ent->enemy ) {
01114                 VectorSubtract( ent->enemy->r.currentOrigin, ent->s.origin, dir );
01115                 VectorNormalize( dir );
01116         } else {
01117                 VectorCopy( ent->movedir, dir );
01118         }
01119 
01120         // randomize a bit
01121         PerpendicularVector( up, dir );
01122         CrossProduct( up, dir, right );
01123 
01124         deg = crandom() * ent->random;
01125         VectorMA( dir, deg, up, dir );
01126 
01127         deg = crandom() * ent->random;
01128         VectorMA( dir, deg, right, dir );
01129 
01130         VectorNormalize( dir );
01131 
01132         switch ( ent->s.weapon ) {
01133         case WP_BLASTER:
01134                 WP_FireBlasterMissile( ent, ent->s.origin, dir, qfalse );
01135                 break;
01136         }
01137 
01138         G_AddEvent( ent, EV_FIRE_WEAPON, 0 );
01139 }
01140 
01141 
01142 static void InitShooter_Finish( gentity_t *ent ) {
01143         ent->enemy = G_PickTarget( ent->target );
01144         ent->think = 0;
01145         ent->nextthink = 0;
01146 }
01147 
01148 void InitShooter( gentity_t *ent, int weapon ) {
01149         ent->use = Use_Shooter;
01150         ent->s.weapon = weapon;
01151 
01152         RegisterItem( BG_FindItemForWeapon( weapon ) );
01153 
01154         G_SetMovedir( ent->s.angles, ent->movedir );
01155 
01156         if ( !ent->random ) {
01157                 ent->random = 1.0;
01158         }
01159         ent->random = sin( M_PI * ent->random / 180 );
01160         // target might be a moving object, so we can't set movedir for it
01161         if ( ent->target ) {
01162                 ent->think = InitShooter_Finish;
01163                 ent->nextthink = level.time + 500;
01164         }
01165         trap_LinkEntity( ent );
01166 }
01167 
01168 /*QUAKED shooter_blaster (1 0 0) (-16 -16 -16) (16 16 16)
01169 Fires at either the target or the current direction.
01170 "random" is the number of degrees of deviance from the taget. (1.0 default)
01171 */
01172 void SP_shooter_blaster( gentity_t *ent ) {
01173         InitShooter( ent, WP_BLASTER);
01174 }
01175 
01176 void check_recharge(gentity_t *ent)
01177 {
01178         if (ent->fly_sound_debounce_time < level.time ||
01179                 !ent->activator ||
01180                 !ent->activator->client ||
01181                 !(ent->activator->client->pers.cmd.buttons & BUTTON_USE))
01182         {
01183                 if (ent->activator)
01184                 {
01185                         G_Sound(ent, CHAN_AUTO, ent->genericValue7);
01186                 }
01187                 ent->s.loopSound = 0;
01188                 ent->s.loopIsSoundset = qfalse;
01189                 ent->activator = NULL;
01190                 ent->fly_sound_debounce_time = 0;
01191         }
01192         
01193         if (!ent->activator)
01194         { //don't recharge during use
01195                 if (ent->genericValue8 < level.time)
01196                 {
01197                         if (ent->count < ent->genericValue4)
01198                         {
01199                                 ent->count++;
01200                         }
01201                         ent->genericValue8 = level.time + ent->genericValue5;
01202                 }
01203         }
01204         ent->s.health = ent->count; //the "health bar" is gonna be how full we are
01205         ent->nextthink = level.time;
01206 }
01207 
01208 /*
01209 ================
01210 EnergyShieldStationSettings
01211 ================
01212 */
01213 void EnergyShieldStationSettings(gentity_t *ent)
01214 {
01215         G_SpawnInt( "count", "200", &ent->count );
01216 
01217         G_SpawnInt("chargerate", "0", &ent->genericValue5);
01218 
01219         if (!ent->genericValue5)
01220         {
01221                 ent->genericValue5 = STATION_RECHARGE_TIME;
01222         }
01223 }
01224 
01225 /*
01226 ================
01227 shield_power_converter_use
01228 ================
01229 */
01230 void shield_power_converter_use( gentity_t *self, gentity_t *other, gentity_t *activator)
01231 {
01232         int dif,add;
01233         int stop = 1;
01234 
01235         if (!activator || !activator->client)
01236         {
01237                 return;
01238         }
01239 
01240         if ( g_gametype.integer == GT_SIEGE 
01241                 && other 
01242                 && other->client 
01243                 && other->client->siegeClass )
01244         {
01245                 if ( !bgSiegeClasses[other->client->siegeClass].maxarmor )
01246                 {//can't use it!
01247                         G_Sound(self, CHAN_AUTO, G_SoundIndex("sound/interface/shieldcon_empty"));
01248                         return;
01249                 }
01250         }
01251 
01252         if (self->setTime < level.time)
01253         {
01254                 int     maxArmor;
01255                 if (!self->s.loopSound)
01256                 {
01257                         self->s.loopSound = G_SoundIndex("sound/interface/shieldcon_run");
01258                         self->s.loopIsSoundset = qfalse;
01259                 }
01260                 self->setTime = level.time + 100;
01261 
01262                 if ( g_gametype.integer == GT_SIEGE 
01263                         && other 
01264                         && other->client 
01265                         && other->client->siegeClass != -1 )
01266                 {
01267                         maxArmor = bgSiegeClasses[other->client->siegeClass].maxarmor;
01268                 }
01269                 else
01270                 {
01271                         maxArmor = activator->client->ps.stats[STAT_MAX_HEALTH];
01272                 }
01273                 dif = maxArmor - activator->client->ps.stats[STAT_ARMOR];
01274 
01275                 if (dif > 0)                                    // Already at full armor?
01276                 {
01277                         if (dif >MAX_AMMO_GIVE)
01278                         {
01279                                 add = MAX_AMMO_GIVE;
01280                         }
01281                         else
01282                         {
01283                                 add = dif;
01284                         }
01285 
01286                         if (self->count<add)
01287                         {
01288                                 add = self->count;
01289                         }
01290 
01291                     if (!self->genericValue12)
01292                         {
01293                                 self->count -= add;
01294                         }
01295                         if (self->count <= 0)
01296                         {
01297                                 self->setTime = 0;
01298                         }
01299                         stop = 0;
01300 
01301                         self->fly_sound_debounce_time = level.time + 500;
01302                         self->activator = activator;
01303 
01304                         activator->client->ps.stats[STAT_ARMOR] += add;
01305                 }
01306         }
01307 
01308         if (stop || self->count <= 0)
01309         {
01310                 if (self->s.loopSound && self->setTime < level.time)
01311                 {
01312                         if (self->count <= 0)
01313                         {
01314                                 G_Sound(self, CHAN_AUTO, G_SoundIndex("sound/interface/shieldcon_empty"));
01315                         }
01316                         else
01317                         {
01318                                 G_Sound(self, CHAN_AUTO, self->genericValue7);
01319                         }
01320                 }
01321                 self->s.loopSound = 0;
01322                 self->s.loopIsSoundset = qfalse;
01323                 if (self->setTime < level.time)
01324                 {
01325                         self->setTime = level.time + self->genericValue5+100;
01326                 }
01327         }
01328 }
01329 
01330 //dispense generic ammo
01331 void ammo_generic_power_converter_use( gentity_t *self, gentity_t *other, gentity_t *activator)
01332 {
01333         int /*dif,*/ add;
01334         //int ammoType;
01335         int stop = 1;
01336 
01337         if (!activator || !activator->client)
01338         {
01339                 return;
01340         }
01341 
01342         if (self->setTime < level.time)
01343         {
01344                 qboolean gaveSome = qfalse;
01345                 /*
01346                 while (i < 3)
01347                 {
01348                         if (!self->s.loopSound)
01349                         {
01350                                 self->s.loopSound = G_SoundIndex("sound/interface/ammocon_run");
01351                                 self->s.loopIsSoundset = qfalse;
01352                         }
01353                         self->setTime = level.time + 100;
01354 
01355                         //dif = activator->client->ps.stats[STAT_MAX_HEALTH] - activator->client->ps.stats[STAT_ARMOR];
01356                         switch (i)
01357                         { //don't give rockets I guess
01358                         case 0:
01359                                 ammoType = AMMO_BLASTER;
01360                                 break;
01361                         case 1:
01362                                 ammoType = AMMO_POWERCELL;
01363                                 break;
01364                         case 2:
01365                                 ammoType = AMMO_METAL_BOLTS;
01366                                 break;
01367                         default:
01368                                 ammoType = -1;
01369                                 break;
01370                         }
01371 
01372                         if (ammoType != -1)
01373                         {
01374                                 dif = ammoData[ammoType].max - activator->client->ps.ammo[ammoType];
01375                         }
01376                         else
01377                         {
01378                                 dif = 0;
01379                         }
01380 
01381                         if (dif > 0)
01382                         { //only give if not full
01383                                 if (dif > MAX_AMMO_GIVE)
01384                                 {
01385                                         add = MAX_AMMO_GIVE;
01386                                 }
01387                                 else
01388                                 {
01389                                         add = dif;
01390                                 }
01391 
01392                                 if (self->count<add)
01393                                 {
01394                                         add = self->count;
01395                                 }
01396 
01397                                 self->count -= add;
01398                                 if (self->count <= 0)
01399                                 {
01400                                         self->setTime = 0;
01401                                         break;
01402                                 }
01403                                 stop = 0;
01404 
01405                                 self->fly_sound_debounce_time = level.time + 500;
01406                                 self->activator = activator;
01407 
01408                                 activator->client->ps.ammo[ammoType] += add;
01409                         }
01410 
01411                         i++;
01412                 }
01413                 */
01414                 int i = AMMO_BLASTER;
01415                 if (!self->s.loopSound)
01416                 {
01417                         self->s.loopSound = G_SoundIndex("sound/interface/ammocon_run");
01418                         self->s.loopIsSoundset = qfalse;
01419                 }
01420                 //self->setTime = level.time + 100;
01421                 self->fly_sound_debounce_time = level.time + 500;
01422                 self->activator = activator;
01423                 while (i < AMMO_MAX)
01424                 {
01425                         add = ammoData[i].max*0.05;
01426                         if (add < 1)
01427                         {
01428                                 add = 1;
01429                         }
01430                         if ( ( (activator->client->ps.eFlags & EF_DOUBLE_AMMO) && (activator->client->ps.ammo[i] < ammoData[i].max*2)) ||
01431                                 ( activator->client->ps.ammo[i] < ammoData[i].max ) )
01432                         {
01433                                 gaveSome = qtrue;
01434                                 if ( g_gametype.integer == GT_SIEGE  && i == AMMO_ROCKETS && activator->client->ps.ammo[i] >= 10 )
01435                                 { //this stuff is already a freaking mess, so..
01436                                         gaveSome = qfalse;
01437                                 }
01438                                 activator->client->ps.ammo[i] += add;
01439                                 if ( g_gametype.integer == GT_SIEGE  && i == AMMO_ROCKETS && activator->client->ps.ammo[i] >= 10 )
01440                                 {       // fixme - this should SERIOUSLY be externed.
01441                                         activator->client->ps.ammo[i] = 10;
01442                                 }
01443                                 else if ( activator->client->ps.eFlags & EF_DOUBLE_AMMO )
01444                                 {
01445                                         if (activator->client->ps.ammo[i] >= ammoData[i].max * 2)
01446                                         {       // yuck.
01447                                                 activator->client->ps.ammo[i] = ammoData[i].max * 2;
01448                                         }
01449                                         else
01450                                         {
01451                                                 stop = 0;
01452                                         }
01453                                 }
01454                                 else
01455                                 {
01456                                         if (activator->client->ps.ammo[i] >= ammoData[i].max)
01457                                         {
01458                                                 activator->client->ps.ammo[i] = ammoData[i].max;
01459                                         }
01460                                         else
01461                                         {
01462                                                 stop = 0;
01463                                         }
01464                                 }
01465                         }
01466                         i++;
01467                         if (!self->genericValue12 && gaveSome)
01468                         {
01469                                 int sub = (add*0.2);
01470                                 if (sub < 1)
01471                                 {
01472                                         sub = 1;
01473                                 }
01474                                 self->count -= sub;
01475                                 if (self->count <= 0)
01476                                 {
01477                                         self->count = 0;
01478                                         stop = 1;
01479                                         break;
01480                                 }
01481                         }
01482                 }
01483         }
01484 
01485         if (stop || self->count <= 0)
01486         {
01487                 if (self->s.loopSound && self->setTime < level.time)
01488                 {
01489                         if (self->count <= 0)
01490                         {
01491                                 G_Sound(self, CHAN_AUTO, G_SoundIndex("sound/interface/ammocon_empty"));
01492                         }
01493                         else
01494                         {
01495                                 G_Sound(self, CHAN_AUTO, self->genericValue7);
01496                         }
01497                 }
01498                 self->s.loopSound = 0;
01499                 self->s.loopIsSoundset = qfalse;
01500                 if (self->setTime < level.time)
01501                 {
01502                         self->setTime = level.time + self->genericValue5+100;
01503                 }
01504         }
01505 }
01506 
01507 /*QUAKED misc_ammo_floor_unit (1 0 0) (-16 -16 0) (16 16 40)
01508 model="/models/items/a_pwr_converter.md3"
01509 Gives generic ammo when used
01510 
01511 "count" - max charge value (default 200)
01512 "chargerate" - rechage 1 point every this many milliseconds (default 2000)
01513 "nodrain" - don't drain power from station if 1
01514 */
01515 void SP_misc_ammo_floor_unit(gentity_t *ent)
01516 {
01517         vec3_t dest;
01518         trace_t tr;
01519 
01520         VectorSet( ent->r.mins, -16, -16, 0 );
01521         VectorSet( ent->r.maxs, 16, 16, 40 );
01522 
01523         ent->s.origin[2] += 0.1f;
01524         ent->r.maxs[2] -= 0.1f;
01525 
01526         VectorSet( dest, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2] - 4096 );
01527         trap_Trace( &tr, ent->s.origin, ent->r.mins, ent->r.maxs, dest, ent->s.number, MASK_SOLID );
01528         if ( tr.startsolid )
01529         {
01530                 G_Printf ("SP_misc_ammo_floor_unit: misc_ammo_floor_unit startsolid at %s\n", vtos(ent->s.origin));
01531                 G_FreeEntity( ent );
01532                 return;
01533         }
01534 
01535         //add the 0.1 back after the trace
01536         ent->r.maxs[2] += 0.1f;
01537 
01538         // allow to ride movers
01539         ent->s.groundEntityNum = tr.entityNum;
01540 
01541         G_SetOrigin( ent, tr.endpos );
01542 
01543         if (!ent->health)
01544         {
01545                 ent->health = 60;
01546         }
01547 
01548         if (!ent->model || !ent->model[0])
01549         {
01550                 ent->model = "/models/items/a_pwr_converter.md3";
01551         }
01552 
01553         ent->s.modelindex = G_ModelIndex( ent->model );
01554 
01555         ent->s.eFlags = 0;
01556         ent->r.svFlags |= SVF_PLAYER_USABLE;
01557         ent->r.contents = CONTENTS_SOLID;
01558         ent->clipmask = MASK_SOLID;
01559 
01560         EnergyShieldStationSettings(ent);
01561 
01562         ent->genericValue4 = ent->count; //initial value
01563         ent->think = check_recharge;
01564 
01565         G_SpawnInt("nodrain", "0", &ent->genericValue12);
01566 
01567         if (!ent->genericValue12)
01568         {
01569                 ent->s.maxhealth = ent->s.health = ent->count;
01570         }
01571         ent->s.shouldtarget = qtrue;
01572         ent->s.teamowner = 0;
01573         ent->s.owner = ENTITYNUM_NONE;
01574 
01575         ent->nextthink = level.time + 200;// + STATION_RECHARGE_TIME;
01576 
01577         ent->use = ammo_generic_power_converter_use;
01578 
01579         VectorCopy( ent->s.angles, ent->s.apos.trBase );
01580         trap_LinkEntity (ent);
01581 
01582         G_SoundIndex("sound/interface/ammocon_run");
01583         ent->genericValue7 = G_SoundIndex("sound/interface/ammocon_done");
01584         G_SoundIndex("sound/interface/ammocon_empty");
01585 
01586         if (g_gametype.integer == GT_SIEGE)
01587         { //show on radar from everywhere
01588                 ent->r.svFlags |= SVF_BROADCAST;
01589                 ent->s.eFlags |= EF_RADAROBJECT;
01590                 ent->s.genericenemyindex = G_IconIndex("gfx/mp/siegeicons/desert/weapon_recharge");
01591         }
01592 }
01593 
01594 /*QUAKED misc_shield_floor_unit (1 0 0) (-16 -16 0) (16 16 40)
01595 model="/models/items/a_shield_converter.md3"
01596 Gives shield energy when used.
01597 
01598 "count" - max charge value (default 50)
01599 "chargerate" - rechage 1 point every this many milliseconds (default 3000)
01600 "nodrain" - don't drain power from me
01601 */
01602 void SP_misc_shield_floor_unit( gentity_t *ent )
01603 {
01604         vec3_t dest;
01605         trace_t tr;
01606 
01607         if (g_gametype.integer != GT_CTF &&
01608                 g_gametype.integer != GT_CTY &&
01609                 g_gametype.integer != GT_SIEGE)
01610         {
01611                 G_FreeEntity( ent );
01612                 return;
01613         }
01614 
01615         VectorSet( ent->r.mins, -16, -16, 0 );
01616         VectorSet( ent->r.maxs, 16, 16, 40 );
01617 
01618         ent->s.origin[2] += 0.1;
01619         ent->r.maxs[2] -= 0.1;
01620 
01621         VectorSet( dest, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2] - 4096 );
01622         trap_Trace( &tr, ent->s.origin, ent->r.mins, ent->r.maxs, dest, ent->s.number, MASK_SOLID );
01623         if ( tr.startsolid )
01624         {
01625                 G_Printf ("SP_misc_shield_floor_unit: misc_shield_floor_unit startsolid at %s\n", vtos(ent->s.origin));
01626                 G_FreeEntity( ent );
01627                 return;
01628         }
01629 
01630         //add the 0.1 back after the trace
01631         ent->r.maxs[2] += 0.1;
01632 
01633         // allow to ride movers
01634         ent->s.groundEntityNum = tr.entityNum;
01635 
01636         G_SetOrigin( ent, tr.endpos );
01637 
01638         if (!ent->health)
01639         {
01640                 ent->health = 60;
01641         }
01642 
01643         if (!ent->model || !ent->model[0])
01644         {
01645                 ent->model = "/models/items/a_shield_converter.md3";
01646         }
01647 
01648         ent->s.modelindex = G_ModelIndex( ent->model );
01649 
01650         ent->s.eFlags = 0;
01651         ent->r.svFlags |= SVF_PLAYER_USABLE;
01652         ent->r.contents = CONTENTS_SOLID;
01653         ent->clipmask = MASK_SOLID;
01654 
01655         EnergyShieldStationSettings(ent);
01656 
01657         ent->genericValue4 = ent->count; //initial value
01658         ent->think = check_recharge;
01659 
01660         G_SpawnInt("nodrain", "0", &ent->genericValue12);
01661 
01662     if (!ent->genericValue12)
01663         {
01664                 ent->s.maxhealth = ent->s.health = ent->count;
01665         }
01666         ent->s.shouldtarget = qtrue;
01667         ent->s.teamowner = 0;
01668         ent->s.owner = ENTITYNUM_NONE;
01669 
01670         ent->nextthink = level.time + 200;// + STATION_RECHARGE_TIME;
01671 
01672         ent->use = shield_power_converter_use;
01673 
01674         VectorCopy( ent->s.angles, ent->s.apos.trBase );
01675         trap_LinkEntity (ent);
01676 
01677         G_SoundIndex("sound/interface/shieldcon_run");
01678         ent->genericValue7 = G_SoundIndex("sound/interface/shieldcon_done");
01679         G_SoundIndex("sound/interface/shieldcon_empty");
01680 
01681         if (g_gametype.integer == GT_SIEGE)
01682         { //show on radar from everywhere
01683                 ent->r.svFlags |= SVF_BROADCAST;
01684                 ent->s.eFlags |= EF_RADAROBJECT;
01685                 ent->s.genericenemyindex = G_IconIndex("gfx/mp/siegeicons/desert/shield_recharge");
01686         }
01687 }
01688 
01689 
01690 /*QUAKED misc_model_shield_power_converter (1 0 0) (-16 -16 -16) (16 16 16)
01691 model="models/items/psd_big.md3"
01692 Gives shield energy when used.
01693 
01694 "count" - the amount of ammo given when used (default 200)
01695 */
01696 //------------------------------------------------------------
01697 void SP_misc_model_shield_power_converter( gentity_t *ent )
01698 {
01699         if (!ent->health)
01700         {
01701                 ent->health = 60;
01702         }
01703 
01704         VectorSet (ent->r.mins, -16, -16, -16);
01705         VectorSet (ent->r.maxs, 16, 16, 16);
01706 
01707         ent->s.modelindex = G_ModelIndex( ent->model );
01708 
01709         ent->s.eFlags = 0;
01710         ent->r.svFlags |= SVF_PLAYER_USABLE;
01711         ent->r.contents = CONTENTS_SOLID;
01712         ent->clipmask = MASK_SOLID;
01713 
01714         EnergyShieldStationSettings(ent);
01715 
01716         ent->genericValue4 = ent->count; //initial value
01717         ent->think = check_recharge;
01718 
01719         ent->s.maxhealth = ent->s.health = ent->count;
01720         ent->s.shouldtarget = qtrue;
01721         ent->s.teamowner = 0;
01722         ent->s.owner = ENTITYNUM_NONE;
01723 
01724         ent->nextthink = level.time + 200;// + STATION_RECHARGE_TIME;
01725 
01726         ent->use = shield_power_converter_use;
01727 
01728         G_SetOrigin( ent, ent->s.origin );
01729         VectorCopy( ent->s.angles, ent->s.apos.trBase );
01730         trap_LinkEntity (ent);
01731 
01732         //G_SoundIndex("sound/movers/objects/useshieldstation.wav");
01733 
01734         ent->s.modelindex2 = G_ModelIndex("/models/items/psd_big.md3"); // Precache model
01735 }
01736 
01737 
01738 /*
01739 ================
01740 EnergyAmmoShieldStationSettings
01741 ================
01742 */
01743 void EnergyAmmoStationSettings(gentity_t *ent)
01744 {
01745         G_SpawnInt( "count", "200", &ent->count );
01746 }
01747 
01748 /*
01749 ================
01750 ammo_power_converter_use
01751 ================
01752 */
01753 void ammo_power_converter_use( gentity_t *self, gentity_t *other, gentity_t *activator)
01754 {
01755         int                     add = 0.0f;//,highest;
01756         qboolean        overcharge;
01757 //      int                     difBlaster,difPowerCell,difMetalBolts;
01758         int                     stop = 1;
01759 
01760         if (!activator || !activator->client)
01761         {
01762                 return;
01763         }
01764 
01765         if (self->setTime < level.time)
01766         {
01767                 overcharge = qfalse;
01768 
01769                 if (!self->s.loopSound)
01770                 {
01771                         self->s.loopSound = G_SoundIndex("sound/player/pickupshield.wav");
01772                 }
01773 
01774                 self->setTime = level.time + 100;
01775 
01776                 if (self->count)        // Has it got any power left?
01777                 {
01778                         int i = AMMO_BLASTER;
01779                         while (i < AMMO_MAX)
01780                         {
01781                                 add = ammoData[i].max*0.1;
01782                                 if (add < 1)
01783                                 {
01784                                         add = 1;
01785                                 }
01786                                 if (activator->client->ps.ammo[i] < ammoData[i].max)
01787                                 {
01788                                         activator->client->ps.ammo[i] += add;
01789                                         if (activator->client->ps.ammo[i] > ammoData[i].max)
01790                                         {
01791                                                 activator->client->ps.ammo[i] = ammoData[i].max;
01792                                         }
01793                                 }
01794                                 i++;
01795                         }
01796                         if (!self->genericValue12)
01797                         {
01798                                 self->count -= add;
01799                         }
01800                         stop = 0;
01801 
01802                         self->fly_sound_debounce_time = level.time + 500;
01803                         self->activator = activator;
01804 
01805                         /*
01806                         if (self->count > MAX_AMMO_GIVE)
01807                         {
01808                                 add = MAX_AMMO_GIVE;
01809                         }
01810                         else if (self->count<0)
01811                         {
01812                                 add = 0;
01813                         }
01814                         else
01815                         {
01816                                 add = self->count;
01817                         }
01818 
01819                         activator->client->ps.ammo[AMMO_BLASTER] += add;
01820                         activator->client->ps.ammo[AMMO_POWERCELL] += add;
01821                         activator->client->ps.ammo[AMMO_METAL_BOLTS] += add;
01822 
01823                         self->count -= add;
01824                         stop = 0;
01825 
01826                         self->fly_sound_debounce_time = level.time + 500;
01827                         self->activator = activator;
01828 
01829                         difBlaster = activator->client->ps.ammo[AMMO_BLASTER] - ammoData[AMMO_BLASTER].max;
01830                         difPowerCell = activator->client->ps.ammo[AMMO_POWERCELL] - ammoData[AMMO_POWERCELL].max;
01831                         difMetalBolts = activator->client->ps.ammo[AMMO_METAL_BOLTS] - ammoData[AMMO_METAL_BOLTS].max;
01832 
01833                         // Find the highest one
01834                         highest = difBlaster;
01835                         if (difPowerCell>difBlaster)
01836                         {
01837                                 highest = difPowerCell;
01838                         }
01839 
01840                         if (difMetalBolts > highest)
01841                         {
01842                                 highest = difMetalBolts;
01843                         }
01844                         */
01845                 }
01846         }
01847 
01848         if (stop)
01849         {
01850                 self->s.loopSound = 0;
01851                 self->s.loopIsSoundset = qfalse;
01852         }
01853 }
01854 
01855 
01856 /*QUAKED misc_model_ammo_power_converter (1 0 0) (-16 -16 -16) (16 16 16)
01857 model="models/items/power_converter.md3"
01858 Gives ammo energy when used.
01859 
01860 "count" - the amount of ammo given when used (default 200)
01861 "nodrain" - don't drain power from me
01862 */
01863 //------------------------------------------------------------
01864 void SP_misc_model_ammo_power_converter( gentity_t *ent )
01865 {
01866         if (!ent->health)
01867         {
01868                 ent->health = 60;
01869         }
01870 
01871         VectorSet (ent->r.mins, -16, -16, -16);
01872         VectorSet (ent->r.maxs, 16, 16, 16);
01873 
01874         ent->s.modelindex = G_ModelIndex( ent->model );
01875 
01876         ent->s.eFlags = 0;
01877         ent->r.svFlags |= SVF_PLAYER_USABLE;
01878         ent->r.contents = CONTENTS_SOLID;
01879         ent->clipmask = MASK_SOLID;
01880 
01881         G_SpawnInt("nodrain", "0", &ent->genericValue12);
01882         ent->use = ammo_power_converter_use;
01883 
01884         EnergyAmmoStationSettings(ent);
01885 
01886         ent->genericValue4 = ent->count; //initial value
01887         ent->think = check_recharge;
01888 
01889         if (!ent->genericValue12)
01890         {
01891                 ent->s.maxhealth = ent->s.health = ent->count;
01892         }
01893         ent->s.shouldtarget = qtrue;
01894         ent->s.teamowner = 0;
01895         ent->s.owner = ENTITYNUM_NONE;
01896 
01897         ent->nextthink = level.time + 200;// + STATION_RECHARGE_TIME;
01898 
01899         G_SetOrigin( ent, ent->s.origin );
01900         VectorCopy( ent->s.angles, ent->s.apos.trBase );
01901         trap_LinkEntity (ent);
01902 
01903         //G_SoundIndex("sound/movers/objects/useshieldstation.wav");
01904 }
01905 
01906 /*
01907 ================
01908 EnergyHealthStationSettings
01909 ================
01910 */
01911 void EnergyHealthStationSettings(gentity_t *ent)
01912 {
01913         G_SpawnInt( "count", "200", &ent->count );
01914 }
01915 
01916 /*
01917 ================
01918 health_power_converter_use
01919 ================
01920 */
01921 void health_power_converter_use( gentity_t *self, gentity_t *other, gentity_t *activator)
01922 {
01923         int dif,add;
01924         int stop = 1;
01925 
01926         if (!activator || !activator->client)
01927         {
01928                 return;
01929         }
01930 
01931         if (self->setTime < level.time)
01932         {
01933                 if (!self->s.loopSound)
01934                 {
01935                         self->s.loopSound = G_SoundIndex("sound/player/pickuphealth.wav");
01936                 }
01937                 self->setTime = level.time + 100;
01938 
01939                 dif = activator->client->ps.stats[STAT_MAX_HEALTH] - activator->health;
01940 
01941                 if (dif > 0)                                    // Already at full armor?
01942                 {
01943                         if (dif >/*MAX_AMMO_GIVE*/5)
01944                         {
01945                                 add = 5;//MAX_AMMO_GIVE;
01946                         }
01947                         else
01948                         {
01949                                 add = dif;
01950                         }
01951 
01952                         if (self->count<add)
01953                         {
01954                                 add = self->count;
01955                         }
01956 
01957                         //self->count -= add;
01958                         stop = 0;
01959 
01960                         self->fly_sound_debounce_time = level.time + 500;
01961                         self->activator = activator;
01962 
01963                         activator->health += add;
01964                 }
01965         }
01966 
01967         if (stop)
01968         {
01969                 self->s.loopSound = 0;
01970                 self->s.loopIsSoundset = qfalse;
01971         }
01972 }
01973 
01974 
01975 /*QUAKED misc_model_health_power_converter (1 0 0) (-16 -16 -16) (16 16 16)
01976 model="models/items/power_converter.md3"
01977 Gives ammo energy when used.
01978 
01979 "count" - the amount of ammo given when used (default 200)
01980 */
01981 //------------------------------------------------------------
01982 void SP_misc_model_health_power_converter( gentity_t *ent )
01983 {
01984         if (!ent->health)
01985         {
01986                 ent->health = 60;
01987         }
01988 
01989         VectorSet (ent->r.mins, -16, -16, -16);
01990         VectorSet (ent->r.maxs, 16, 16, 16);
01991 
01992         ent->s.modelindex = G_ModelIndex( ent->model );
01993 
01994         ent->s.eFlags = 0;
01995         ent->r.svFlags |= SVF_PLAYER_USABLE;
01996         ent->r.contents = CONTENTS_SOLID;
01997         ent->clipmask = MASK_SOLID;
01998 
01999         ent->use = health_power_converter_use;
02000 
02001         EnergyHealthStationSettings(ent);
02002 
02003         ent->genericValue4 = ent->count; //initial value
02004         ent->think = check_recharge;
02005 
02006         //ent->s.maxhealth = ent->s.health = ent->count;
02007         ent->s.shouldtarget = qtrue;
02008         ent->s.teamowner = 0;
02009         ent->s.owner = ENTITYNUM_NONE;
02010 
02011         ent->nextthink = level.time + 200;// + STATION_RECHARGE_TIME;
02012 
02013         G_SetOrigin( ent, ent->s.origin );
02014         VectorCopy( ent->s.angles, ent->s.apos.trBase );
02015         trap_LinkEntity (ent);
02016 
02017         //G_SoundIndex("sound/movers/objects/useshieldstation.wav");
02018         G_SoundIndex("sound/player/pickuphealth.wav");
02019         ent->genericValue7 = G_SoundIndex("sound/interface/shieldcon_done");
02020 
02021         if (g_gametype.integer == GT_SIEGE)
02022         { //show on radar from everywhere
02023                 ent->r.svFlags |= SVF_BROADCAST;
02024                 ent->s.eFlags |= EF_RADAROBJECT;
02025                 ent->s.genericenemyindex = G_IconIndex("gfx/mp/siegeicons/desert/bacta");
02026         }
02027 }
02028 
02029 #if 0 //damage box stuff
02030 void DmgBoxHit( gentity_t *self, gentity_t *other, trace_t *trace )
02031 {
02032         return;
02033 }
02034 
02035 void DmgBoxUpdateSelf(gentity_t *self)
02036 {
02037         gentity_t *owner = &g_entities[self->r.ownerNum];
02038 
02039         if (!owner || !owner->client || !owner->inuse)
02040         {
02041                 goto killMe;
02042         }
02043 
02044         if (self->damageRedirect == DAMAGEREDIRECT_HEAD &&
02045                 owner->client->damageBoxHandle_Head != self->s.number)
02046         {
02047                 goto killMe;
02048         }
02049 
02050         if (self->damageRedirect == DAMAGEREDIRECT_RLEG &&
02051                 owner->client->damageBoxHandle_RLeg != self->s.number)
02052         {
02053                 goto killMe;
02054         }
02055 
02056         if (self->damageRedirect == DAMAGEREDIRECT_LLEG &&
02057                 owner->client->damageBoxHandle_LLeg != self->s.number)
02058         {
02059                 goto killMe;
02060         }
02061 
02062         if (owner->health < 1)
02063         {
02064                 goto killMe;
02065         }
02066 
02067         //G_TestLine(self->r.currentOrigin, owner->client->ps.origin, 0x0000ff, 100);
02068 
02069         trap_LinkEntity(self);
02070 
02071         self->nextthink = level.time;
02072         return;
02073 
02074 killMe:
02075         self->think = G_FreeEntity;
02076         self->nextthink = level.time;
02077 }
02078 
02079 void DmgBoxAbsorb_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod )
02080 {
02081         self->health = 1;
02082 }
02083 
02084 void DmgBoxAbsorb_Pain(gentity_t *self, gentity_t *attacker, int damage)
02085 {
02086         self->health = 1;
02087 }
02088 
02089 gentity_t *CreateNewDamageBox( gentity_t *ent )
02090 {
02091         gentity_t *dmgBox;
02092 
02093         //We do not want the client to have any real knowledge of the entity whatsoever. It will only
02094         //ever be used on the server.
02095         dmgBox = G_Spawn();
02096         dmgBox->classname = "dmg_box";
02097                         
02098         dmgBox->r.svFlags = SVF_USE_CURRENT_ORIGIN;
02099         dmgBox->r.ownerNum = ent->s.number;
02100 
02101         dmgBox->clipmask = 0;
02102         dmgBox->r.contents = MASK_PLAYERSOLID;
02103 
02104         dmgBox->mass = 5000;
02105 
02106         dmgBox->s.eFlags |= EF_NODRAW;
02107         dmgBox->r.svFlags |= SVF_NOCLIENT;
02108 
02109         dmgBox->touch = DmgBoxHit;
02110 
02111         dmgBox->takedamage = qtrue;
02112 
02113         dmgBox->health = 1;
02114 
02115         dmgBox->pain = DmgBoxAbsorb_Pain;
02116         dmgBox->die = DmgBoxAbsorb_Die;
02117 
02118         dmgBox->think = DmgBoxUpdateSelf;
02119         dmgBox->nextthink = level.time + 50;
02120 
02121         return dmgBox;
02122 }
02123 
02124 void ATST_ManageDamageBoxes(gentity_t *ent)
02125 {
02126         vec3_t headOrg, lLegOrg, rLegOrg;
02127         vec3_t fwd, right, up, flatAngle;
02128 
02129         if (!ent->client->damageBoxHandle_Head)
02130         {
02131                 gentity_t *dmgBox = CreateNewDamageBox(ent);
02132 
02133                 if (dmgBox)
02134                 {
02135                         VectorSet( dmgBox->r.mins, ATST_MINS0, ATST_MINS1, ATST_MINS2 );
02136                         VectorSet( dmgBox->r.maxs, ATST_MAXS0, ATST_MAXS1, ATST_HEADSIZE );
02137 
02138                         ent->client->damageBoxHandle_Head = dmgBox->s.number;
02139                         dmgBox->damageRedirect = DAMAGEREDIRECT_HEAD;
02140                         dmgBox->damageRedirectTo = ent->s.number;
02141                 }
02142         }
02143         if (!ent->client->damageBoxHandle_RLeg)
02144         {
02145                 gentity_t *dmgBox = CreateNewDamageBox(ent);
02146 
02147                 if (dmgBox)
02148                 {
02149                         VectorSet( dmgBox->r.mins, ATST_MINS0/4, ATST_MINS1/4, ATST_MINS2 );
02150                         VectorSet( dmgBox->r.maxs, ATST_MAXS0/4, ATST_MAXS1/4, ATST_MAXS2-ATST_HEADSIZE );
02151 
02152                         ent->client->damageBoxHandle_RLeg = dmgBox->s.number;
02153                         dmgBox->damageRedirect = DAMAGEREDIRECT_RLEG;
02154                         dmgBox->damageRedirectTo = ent->s.number;
02155                 }
02156         }
02157         if (!ent->client->damageBoxHandle_LLeg)
02158         {
02159                 gentity_t *dmgBox = CreateNewDamageBox(ent);
02160 
02161                 if (dmgBox)
02162                 {
02163                         VectorSet( dmgBox->r.mins, ATST_MINS0/4, ATST_MINS1/4, ATST_MINS2 );
02164                         VectorSet( dmgBox->r.maxs, ATST_MAXS0/4, ATST_MAXS1/4, ATST_MAXS2-ATST_HEADSIZE );
02165 
02166                         ent->client->damageBoxHandle_LLeg = dmgBox->s.number;
02167                         dmgBox->damageRedirect = DAMAGEREDIRECT_LLEG;
02168                         dmgBox->damageRedirectTo = ent->s.number;
02169                 }
02170         }
02171 
02172         if (!ent->client->damageBoxHandle_Head ||
02173                 !ent->client->damageBoxHandle_LLeg ||
02174                 !ent->client->damageBoxHandle_RLeg)
02175         {
02176                 return;
02177         }
02178 
02179         VectorCopy(ent->client->ps.origin, headOrg);
02180         headOrg[2] += (ATST_MAXS2-ATST_HEADSIZE);
02181 
02182         VectorCopy(ent->client->ps.viewangles, flatAngle);
02183         flatAngle[PITCH] = 0;
02184         flatAngle[ROLL] = 0;
02185 
02186         AngleVectors(flatAngle, fwd, right, up);
02187 
02188         VectorCopy(ent->client->ps.origin, lLegOrg);
02189         VectorCopy(ent->client->ps.origin, rLegOrg);
02190 
02191         lLegOrg[0] -= right[0]*32;
02192         lLegOrg[1] -= right[1]*32;
02193         lLegOrg[2] -= right[2]*32;
02194 
02195         rLegOrg[0] += right[0]*32;
02196         rLegOrg[1] += right[1]*32;
02197         rLegOrg[2] += right[2]*32;
02198 
02199         G_SetOrigin(&g_entities[ent->client->damageBoxHandle_Head], headOrg);
02200         G_SetOrigin(&g_entities[ent->client->damageBoxHandle_LLeg], lLegOrg);
02201         G_SetOrigin(&g_entities[ent->client->damageBoxHandle_RLeg], rLegOrg);
02202 }
02203 
02204 int G_PlayerBecomeATST(gentity_t *ent)
02205 {
02206         if (!ent || !ent->client)
02207         {
02208                 return 0;
02209         }
02210 
02211         if (ent->client->ps.weaponTime > 0)
02212         {
02213                 return 0;
02214         }
02215 
02216         if (ent->client->ps.forceHandExtend != HANDEXTEND_NONE)
02217         {
02218                 return 0;
02219         }
02220 
02221         if (ent->client->ps.zoomMode)
02222         {
02223                 return 0;
02224         }
02225 
02226         if (ent->client->ps.usingATST)
02227         {
02228                 ent->client->ps.usingATST = qfalse;
02229                 ent->client->ps.forceHandExtend = HANDEXTEND_WEAPONREADY;
02230         }
02231         else
02232         {
02233                 ent->client->ps.usingATST = qtrue;
02234         }
02235 
02236         ent->client->ps.weaponTime = 1000;
02237 
02238         return 1;
02239 }
02240 #endif
02241 
02242 //----------------------------------------------------------
02243 
02244 /*QUAKED fx_runner (0 0 1) (-8 -8 -8) (8 8 8) STARTOFF ONESHOT DAMAGE
02245 Runs the specified effect, can also be targeted at an info_notnull to orient the effect
02246 
02247         STARTOFF - effect starts off, toggles on/off when used
02248         ONESHOT - effect fires only when used
02249         DAMAGE - does radius damage around effect every "delay" milliseonds
02250 
02251         "fxFile" - name of the effect file to play
02252         "target" - direction to aim the effect in, otherwise defaults to up
02253         "target2" - uses its target2 when the fx gets triggered
02254         "delay"  - how often to call the effect, don't over-do this ( default 200 )
02255         "random" - random amount of time to add to delay, ( default 0, 200 = 0ms to 200ms )
02256         "splashRadius" - only works when damage is checked ( default 16 )
02257         "splashDamage" - only works when damage is checked ( default 5 )
02258         "soundset"      - bmodel set to use, plays start sound when toggled on, loop sound while on ( doesn't play on a oneshot), and a stop sound when turned off
02259 */
02260 #define FX_RUNNER_RESERVED 0x800000
02261 #define FX_ENT_RADIUS 32
02262 extern int      BMS_START;
02263 extern int      BMS_MID;
02264 extern int      BMS_END;
02265 //----------------------------------------------------------
02266 void fx_runner_think( gentity_t *ent )
02267 {
02268         BG_EvaluateTrajectory( &ent->s.pos, level.time, ent->r.currentOrigin );
02269         BG_EvaluateTrajectory( &ent->s.apos, level.time, ent->r.currentAngles );
02270 
02271         // call the effect with the desired position and orientation
02272         if (ent->s.isPortalEnt)
02273         {
02274 //              G_AddEvent( ent, EV_PLAY_PORTAL_EFFECT_ID, ent->genericValue5 );
02275         }
02276         else
02277         {
02278 //              G_AddEvent( ent, EV_PLAY_EFFECT_ID, ent->genericValue5 );
02279         }
02280 
02281         // start the fx on the client (continuous)
02282         ent->s.modelindex2 = FX_STATE_CONTINUOUS;
02283 
02284         VectorCopy(ent->r.currentAngles, ent->s.angles);
02285         VectorCopy(ent->r.currentOrigin, ent->s.origin);
02286 
02287         ent->nextthink = level.time + ent->delay + random() * ent->random;
02288 
02289         if ( ent->spawnflags & 4 ) // damage
02290         {
02291                 G_RadiusDamage( ent->r.currentOrigin, ent, ent->splashDamage, ent->splashRadius, ent, ent, MOD_UNKNOWN );
02292         }
02293 
02294         if ( ent->target2 && ent->target2[0] )
02295         {
02296                 // let our target know that we have spawned an effect
02297                 G_UseTargets2( ent, ent, ent->target2 );
02298         }
02299 
02300         if ( !(ent->spawnflags & 2 ) && !ent->s.loopSound ) // NOT ONESHOT...this is an assy thing to do
02301         {
02302                 if ( ent->soundSet && ent->soundSet[0] )
02303                 {
02304                         ent->s.soundSetIndex = G_SoundSetIndex(ent->soundSet);
02305                         ent->s.loopIsSoundset = qtrue;
02306                         ent->s.loopSound = BMS_MID;
02307                 }
02308         }
02309 
02310 }
02311 
02312 //----------------------------------------------------------
02313 void fx_runner_use( gentity_t *self, gentity_t *other, gentity_t *activator )
02314 {
02315         if (self->s.isPortalEnt)
02316         { //rww - mark it as broadcast upon first use if it's within the area of a skyportal
02317                 self->r.svFlags |= SVF_BROADCAST;
02318         }
02319 
02320         if ( self->spawnflags & 2 ) // ONESHOT
02321         {
02322                 // call the effect with the desired position and orientation, as a safety thing,
02323                 //      make sure we aren't thinking at all.
02324                 int             saveState = self->s.modelindex2 + 1;
02325 
02326                 fx_runner_think( self );
02327                 self->nextthink = -1;
02328                 // one shot indicator
02329                 self->s.modelindex2 = saveState;
02330                 if (self->s.modelindex2 > FX_STATE_ONE_SHOT_LIMIT)
02331                 {
02332                         self->s.modelindex2 = FX_STATE_ONE_SHOT;
02333                 }
02334 
02335                 if ( self->target2 )
02336                 {
02337                         // let our target know that we have spawned an effect
02338                         G_UseTargets2( self, self, self->target2 );
02339                 }
02340 
02341                 if ( self->soundSet && self->soundSet[0] )
02342                 {
02343                         self->s.soundSetIndex = G_SoundSetIndex(self->soundSet);
02344                         G_AddEvent( self, EV_BMODEL_SOUND, BMS_START);
02345                 }
02346         }
02347         else
02348         {
02349                 // ensure we are working with the right think function
02350                 self->think = fx_runner_think;
02351 
02352                 // toggle our state
02353                 if ( self->nextthink == -1 )
02354                 {
02355                         // NOTE: we fire the effect immediately on use, the fx_runner_think func will set
02356                         //      up the nextthink time.
02357                         fx_runner_think( self );
02358 
02359                         if ( self->soundSet && self->soundSet[0] )
02360                         {
02361                                 self->s.soundSetIndex = G_SoundSetIndex(self->soundSet);
02362                                 G_AddEvent( self, EV_BMODEL_SOUND, BMS_START);
02363                                 self->s.loopSound = BMS_MID;
02364                                 self->s.loopIsSoundset = qtrue;
02365                         }
02366                 }
02367                 else
02368                 {
02369                         // turn off for now
02370                         self->nextthink = -1;
02371 
02372                         // turn off fx on client
02373                         self->s.modelindex2 = FX_STATE_OFF;
02374 
02375                         if ( self->soundSet && self->soundSet[0] )
02376                         {
02377                                 self->s.soundSetIndex = G_SoundSetIndex(self->soundSet);
02378                                 G_AddEvent( self, EV_BMODEL_SOUND, BMS_END );
02379                                 self->s.loopSound = 0;
02380                                 self->s.loopIsSoundset = qfalse;
02381                         }
02382                 }
02383         }
02384 }
02385 
02386 //----------------------------------------------------------
02387 void fx_runner_link( gentity_t *ent )
02388 {
02389         vec3_t  dir;
02390 
02391         if ( ent->target && ent->target[0] )
02392         {
02393                 // try to use the target to override the orientation
02394                 gentity_t       *target = NULL;
02395 
02396                 target = G_Find( target, FOFS(targetname), ent->target );
02397 
02398                 if ( !target )
02399                 {
02400                         // Bah, no good, dump a warning, but continue on and use the UP vector
02401                         Com_Printf( "fx_runner_link: target specified but not found: %s\n", ent->target );
02402                         Com_Printf( "  -assuming UP orientation.\n" );
02403                 }
02404                 else
02405                 {
02406                         // Our target is valid so let's override the default UP vector
02407                         VectorSubtract( target->s.origin, ent->s.origin, dir );
02408                         VectorNormalize( dir );
02409                         vectoangles( dir, ent->s.angles );
02410                 }
02411         }
02412 
02413         // don't really do anything with this right now other than do a check to warn the designers if the target2 is bogus
02414         if ( ent->target2 && ent->target2[0] )
02415         {
02416                 gentity_t       *target = NULL;
02417 
02418                 target = G_Find( target, FOFS(targetname), ent->target2 );
02419 
02420                 if ( !target )
02421                 {
02422                         // Target2 is bogus, but we can still continue
02423                         Com_Printf( "fx_runner_link: target2 was specified but is not valid: %s\n", ent->target2 );
02424                 }
02425         }
02426 
02427         G_SetAngles( ent, ent->s.angles );
02428 
02429         if ( ent->spawnflags & 1 || ent->spawnflags & 2 ) // STARTOFF || ONESHOT
02430         {
02431                 // We won't even consider thinking until we are used
02432                 ent->nextthink = -1;
02433         }
02434         else
02435         {
02436                 if ( ent->soundSet && ent->soundSet[0] )
02437                 {
02438                         ent->s.soundSetIndex = G_SoundSetIndex(ent->soundSet);
02439                         ent->s.loopSound = BMS_MID;
02440                         ent->s.loopIsSoundset = qtrue;
02441                 }
02442 
02443                 // Let's get to work right now!
02444                 ent->think = fx_runner_think;
02445                 ent->nextthink = level.time + 200; // wait a small bit, then start working
02446         }
02447 
02448         // make us useable if we can be targeted
02449         if ( ent->targetname && ent->targetname[0] )
02450         {
02451                 ent->use = fx_runner_use;
02452         }
02453 }
02454 
02455 //----------------------------------------------------------
02456 void SP_fx_runner( gentity_t *ent )
02457 {
02458         char *fxFile;
02459 
02460         G_SpawnString( "fxFile", "", &fxFile );
02461         // Get our defaults
02462         G_SpawnInt( "delay", "200", &ent->delay );
02463         G_SpawnFloat( "random", "0", &ent->random );
02464         G_SpawnInt( "splashRadius", "16", &ent->splashRadius );
02465         G_SpawnInt( "splashDamage", "5", &ent->splashDamage );
02466 
02467         if (!ent->s.angles[0] && !ent->s.angles[1] && !ent->s.angles[2])
02468         {
02469                 // didn't have angles, so give us the default of up
02470                 VectorSet( ent->s.angles, -90, 0, 0 );
02471         }
02472 
02473         if ( !fxFile || !fxFile[0] )
02474         {
02475                 Com_Printf( S_COLOR_RED"ERROR: fx_runner %s at %s has no fxFile specified\n", ent->targetname, vtos(ent->s.origin) );
02476                 G_FreeEntity( ent );
02477                 return;
02478         }
02479 
02480         // Try and associate an effect file, unfortunately we won't know if this worked or not 
02481         //      until the CGAME trys to register it...
02482         ent->s.modelindex = G_EffectIndex( fxFile );
02483 
02484         // important info transmitted
02485         ent->s.eType = ET_FX;
02486         ent->s.speed = ent->delay;
02487         ent->s.time = ent->random;
02488         ent->s.modelindex2 = FX_STATE_OFF;
02489 
02490         // Give us a bit of time to spawn in the other entities, since we may have to target one of 'em
02491         ent->think = fx_runner_link; 
02492         ent->nextthink = level.time + 400;
02493 
02494         // Save our position and link us up!
02495         G_SetOrigin( ent, ent->s.origin );
02496 
02497         VectorSet( ent->r.maxs, FX_ENT_RADIUS, FX_ENT_RADIUS, FX_ENT_RADIUS );
02498         VectorScale( ent->r.maxs, -1, ent->r.mins );
02499 
02500         trap_LinkEntity( ent );
02501 }
02502 
02503 /*QUAKED fx_spacedust (1 0 0) (-16 -16 -16) (16 16 16)
02504 This world effect will spawn space dust globally into the level.
02505 
02506 "count" the number of snow particles (default of 1000)
02507 */
02508 //----------------------------------------------------------
02509 void SP_CreateSpaceDust( gentity_t *ent )
02510 { 
02511         G_EffectIndex(va("*spacedust %i", ent->count));
02512         //G_EffectIndex("*constantwind ( 10 -10 0 )");
02513 }
02514 
02515 
02516 /*QUAKED fx_snow (1 0 0) (-16 -16 -16) (16 16 16)
02517 This world effect will spawn snow globally into the level.
02518 
02519 "count" the number of snow particles (default of 1000)
02520 */
02521 //----------------------------------------------------------
02522 void SP_CreateSnow( gentity_t *ent )
02523 { 
02524         G_EffectIndex("*snow");
02525         G_EffectIndex("*fog");
02526         G_EffectIndex("*constantwind (100 100 -100)");
02527 }
02528 
02529 /*QUAKED fx_rain (1 0 0) (-16 -16 -16) (16 16 16)
02530 This world effect will spawn rain globally into the level.
02531 
02532 "count" the number of rain particles (default of 500)
02533 */
02534 //----------------------------------------------------------
02535 void SP_CreateRain( gentity_t *ent )
02536 { 
02537         G_EffectIndex(va("*rain init %i", ent->count));
02538 }
02539 
02540 qboolean gEscaping = qfalse;
02541 int gEscapeTime = 0;
02542 
02543 void Use_Target_Screenshake( gentity_t *ent, gentity_t *other, gentity_t *activator )
02544 {
02545         qboolean bGlobal = qfalse;
02546 
02547         if (ent->genericValue6)
02548         {
02549                 bGlobal = qtrue;
02550         }
02551 
02552         G_ScreenShake(ent->s.origin, NULL, ent->speed, ent->genericValue5, bGlobal);
02553 }
02554 
02555 void SP_target_screenshake(gentity_t *ent)
02556 {
02557         G_SpawnFloat( "intensity", "10", &ent->speed );
02558         //intensity of the shake
02559         G_SpawnInt( "duration", "800", &ent->genericValue5 );
02560         //duration of the shake
02561         G_SpawnInt( "globalshake", "1", &ent->genericValue6 );
02562         //non-0 if shake should be global (all clients). Otherwise, only in the PVS.
02563 
02564         ent->use = Use_Target_Screenshake;
02565 }
02566 
02567 void LogExit( const char *string );
02568 
02569 void Use_Target_Escapetrig( gentity_t *ent, gentity_t *other, gentity_t *activator )
02570 {
02571         if (!ent->genericValue6)
02572         {
02573                 gEscaping = qtrue;
02574                 gEscapeTime = level.time + ent->genericValue5;
02575         }
02576         else if (gEscaping)
02577         {
02578                 int i = 0;
02579                 gEscaping = qfalse;
02580                 while (i < MAX_CLIENTS)
02581                 { //all of the survivors get 100 points!
02582                         if (g_entities[i].inuse && g_entities[i].client && g_entities[i].health > 0 &&
02583                                 g_entities[i].client->sess.sessionTeam != TEAM_SPECTATOR &&
02584                                 !(g_entities[i].client->ps.pm_flags & PMF_FOLLOW))
02585                         {
02586                                 AddScore(&g_entities[i], g_entities[i].client->ps.origin, 100);
02587                         }
02588                         i++;
02589                 }
02590                 if (activator && activator->inuse && activator->client)
02591                 { //the one who escaped gets 500
02592                         AddScore(activator, activator->client->ps.origin, 500);
02593                 }
02594 
02595                 LogExit("Escaped!");
02596         }
02597 }
02598 
02599 void SP_target_escapetrig(gentity_t *ent)
02600 {
02601         if (g_gametype.integer != GT_SINGLE_PLAYER)
02602         {
02603                 G_FreeEntity(ent);
02604                 return;
02605         }
02606 
02607         G_SpawnInt( "escapetime", "60000", &ent->genericValue5);
02608         //time given (in ms) for the escape
02609         G_SpawnInt( "escapegoal", "0", &ent->genericValue6);
02610         //if non-0, when used, will end an ongoing escape instead of start it
02611 
02612         ent->use = Use_Target_Escapetrig;
02613 }
02614 
02615 /*QUAKED misc_maglock (0 .5 .8) (-8 -8 -8) (8 8 8) x x x x x x x x
02616 Place facing a door (using the angle, not a targetname) and it will lock that door.  Can only be destroyed by lightsaber and will automatically unlock the door it's attached to
02617 
02618 NOTE: place these half-way in the door to make it flush with the door's surface.
02619 
02620 "target"        thing to use when destoryed (not doors - it automatically unlocks the door it was angled at)
02621 "health"        default is 10
02622 */
02623 void maglock_die(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod)
02624 {
02625         //unlock our door if we're the last lock pointed at the door
02626         if ( self->activator )
02627         {
02628                 self->activator->lockCount--;
02629                 if ( !self->activator->lockCount )
02630                 {
02631                         self->activator->flags &= ~FL_INACTIVE;
02632                 }
02633         }
02634         
02635         //use targets
02636         G_UseTargets( self, attacker );
02637         //die
02638         //rwwFIXMEFIXME - weap expl func
02639 //      WP_Explode( self );
02640 }
02641 
02642 void maglock_link( gentity_t *self );
02643 gentity_t *G_FindDoorTrigger( gentity_t *ent );
02644 
02645 void SP_misc_maglock ( gentity_t *self )
02646 {
02647         //NOTE: May have to make these only work on doors that are either untargeted 
02648         //              or are targeted by a trigger, not doors fired off by scripts, counters 
02649         //              or other such things?
02650         self->s.modelindex = G_ModelIndex( "models/map_objects/imp_detention/door_lock.md3" );
02651         self->genericValue1 = G_EffectIndex( "maglock/explosion" );
02652 
02653         G_SetOrigin( self, self->s.origin );
02654 
02655         self->think = maglock_link;
02656         //FIXME: for some reason, when you re-load a level, these fail to find their doors...?  Random?  Testing an additional 200ms after the START_TIME_FIND_LINKS
02657         self->nextthink = level.time + START_TIME_FIND_LINKS+200;//START_TIME_FIND_LINKS;//because we need to let the doors link up and spawn their triggers first!
02658 }
02659 void maglock_link( gentity_t *self )
02660 {
02661         //find what we're supposed to be attached to
02662         vec3_t  forward, start, end;
02663         trace_t trace;
02664         gentity_t *traceEnt;
02665 
02666         AngleVectors( self->s.angles, forward, NULL, NULL );
02667         VectorMA( self->s.origin, 128, forward, end );
02668         VectorMA( self->s.origin, -4, forward, start );
02669 
02670         trap_Trace( &trace, start, vec3_origin, vec3_origin, end, self->s.number, MASK_SHOT );
02671 
02672         if ( trace.allsolid || trace.startsolid )
02673         {
02674                 Com_Error( ERR_DROP,"misc_maglock at %s in solid\n", vtos(self->s.origin) );
02675                 G_FreeEntity( self );
02676                 return;
02677         }
02678         if ( trace.fraction == 1.0 )
02679         {
02680                 self->think = maglock_link;
02681                 self->nextthink = level.time + 100;
02682                 /*
02683                 Com_Error( ERR_DROP,"misc_maglock at %s pointed at no surface\n", vtos(self->s.origin) );
02684                 G_FreeEntity( self );
02685                 */
02686                 return;
02687         }
02688         traceEnt = &g_entities[trace.entityNum];
02689         if ( trace.entityNum >= ENTITYNUM_WORLD || !traceEnt || Q_stricmp( "func_door", traceEnt->classname ) )
02690         {
02691                 self->think = maglock_link;
02692                 self->nextthink = level.time + 100;
02693                 //Com_Error( ERR_DROP,"misc_maglock at %s not pointed at a door\n", vtos(self->s.origin) );
02694                 //G_FreeEntity( self );
02695                 return;
02696         }
02697 
02698         //check the traceEnt, make sure it's a door and give it a lockCount and deactivate it
02699         //find the trigger for the door
02700         self->activator = G_FindDoorTrigger( traceEnt );
02701         if ( !self->activator )
02702         {
02703                 self->activator = traceEnt;
02704         }
02705         self->activator->lockCount++;
02706         self->activator->flags |= FL_INACTIVE;
02707 
02708         //now position and orient it
02709         vectoangles( trace.plane.normal, end );
02710         G_SetOrigin( self, trace.endpos );
02711         G_SetAngles( self, end );
02712 
02713         //make it hittable
02714         //FIXME: if rotated/inclined this bbox may be off... but okay if we're a ghoul model?
02715         //self->s.modelindex = G_ModelIndex( "models/map_objects/imp_detention/door_lock.md3" );
02716         VectorSet( self->r.mins, -8, -8, -8 );
02717         VectorSet( self->r.maxs, 8, 8, 8 );
02718         self->r.contents = CONTENTS_CORPSE;
02719 
02720         //make it destroyable
02721         self->flags |= FL_SHIELDED;//only damagable by lightsabers
02722         self->takedamage = qtrue;
02723         self->health = 10;
02724         self->die = maglock_die;
02725         //self->fxID = G_EffectIndex( "maglock/explosion" );
02726 
02727         trap_LinkEntity( self );
02728 }
02729 
02730 void faller_touch(gentity_t *self, gentity_t *other, trace_t *trace)
02731 {
02732         if (self->epVelocity[2] < -100 && self->genericValue7 < level.time)
02733         {
02734                 int r = Q_irand(1, 3);
02735 
02736                 if (r == 1)
02737                 {
02738                         self->genericValue11 = G_SoundIndex("sound/chars/stofficer1/misc/pain25");
02739                 }
02740                 else if (r == 2)
02741                 {
02742                         self->genericValue11 = G_SoundIndex("sound/chars/stofficer1/misc/pain50");
02743                 }
02744                 else
02745                 {
02746                         self->genericValue11 = G_SoundIndex("sound/chars/stofficer1/misc/pain75");
02747                 }
02748 
02749                 G_EntitySound(self, CHAN_VOICE, self->genericValue11);
02750                 G_EntitySound(self, CHAN_AUTO, self->genericValue10);
02751 
02752                 self->genericValue6 = level.time + 3000;
02753 
02754                 self->genericValue7 = level.time + 200;
02755         }
02756 }
02757 
02758 void faller_think(gentity_t *ent)
02759 {
02760         float gravity = 3.0f;
02761         float mass = 0.09f;
02762         float bounce = 1.1f;
02763 
02764         if (ent->genericValue6 < level.time)
02765         {
02766                 ent->think = G_FreeEntity;
02767                 ent->nextthink = level.time;
02768                 return;
02769         }
02770 
02771         if (ent->epVelocity[2] < -100)
02772         {
02773                 if (!ent->genericValue8)
02774                 {
02775                         G_EntitySound(ent, CHAN_VOICE, ent->genericValue9);
02776                         ent->genericValue8 = 1;
02777                 }
02778         }
02779         else
02780         {
02781                 ent->genericValue8 = 0;
02782         }
02783 
02784         G_RunExPhys(ent, gravity, mass, bounce, qtrue, NULL, 0);
02785         VectorScale(ent->epVelocity, 10.0f, ent->s.pos.trDelta);
02786         ent->nextthink = level.time + 25;
02787 }
02788 
02789 void misc_faller_create( gentity_t *ent, gentity_t *other, gentity_t *activator )
02790 {
02791         gentity_t *faller = G_Spawn();
02792 
02793         faller->genericValue10 = G_SoundIndex("sound/player/fallsplat");
02794         faller->genericValue9 = G_SoundIndex("sound/chars/stofficer1/misc/falling1");
02795         faller->genericValue8 = 0;
02796         faller->genericValue7 = 0;
02797 
02798         faller->genericValue6 = level.time + 15000;
02799 
02800         G_SetOrigin(faller, ent->s.origin);
02801 
02802         faller->s.modelGhoul2 = 1;
02803         faller->s.modelindex = G_ModelIndex("models/players/stormtrooper/model.glm");
02804         faller->s.g2radius = 100;
02805         
02806         faller->s.customRGBA[0]=Q_irand(1,255);
02807         faller->s.customRGBA[1]=Q_irand(1,255);
02808         faller->s.customRGBA[2]=Q_irand(1,255);
02809         faller->s.customRGBA[3]=255;
02810 
02811         VectorSet(faller->r.mins, -15, -15, DEFAULT_MINS_2);
02812         VectorSet(faller->r.maxs, 15, 15, DEFAULT_MAXS_2);
02813         
02814         faller->clipmask = MASK_PLAYERSOLID;
02815         faller->r.contents = MASK_PLAYERSOLID;
02816 
02817         faller->s.eFlags = (EF_RAG|EF_CLIENTSMOOTH);
02818 
02819         faller->think = faller_think;
02820         faller->nextthink = level.time;
02821 
02822         faller->touch = faller_touch;
02823 
02824         faller->epVelocity[0] = flrand(-256.0f, 256.0f);
02825         faller->epVelocity[1] = flrand(-256.0f, 256.0f);
02826 
02827         trap_LinkEntity(faller);
02828 }
02829 
02830 void misc_faller_think(gentity_t *ent)
02831 {
02832         misc_faller_create(ent, ent, ent);
02833         ent->nextthink = level.time + ent->genericValue1 + Q_irand(0, ent->genericValue2);
02834 }
02835 
02836 /*QUAKED misc_faller (1 0 0) (-8 -8 -8) (8 8 8)
02837 Falling stormtrooper - spawned every interval+random fudgefactor,
02838 or if specified, when used.
02839 
02840 targetname      - if specified, will only spawn when used
02841 interval        - spawn every so often (milliseconds)
02842 fudgefactor     - milliseconds between 0 and this number randomly added to interval
02843 */
02844 void SP_misc_faller(gentity_t *ent)
02845 {
02846         G_ModelIndex("models/players/stormtrooper/model.glm");
02847         G_SoundIndex("sound/chars/stofficer1/misc/pain25");
02848         G_SoundIndex("sound/chars/stofficer1/misc/pain50");
02849         G_SoundIndex("sound/chars/stofficer1/misc/pain75");
02850         G_SoundIndex("sound/chars/stofficer1/misc/falling1");
02851         G_SoundIndex("sound/player/fallsplat");
02852 
02853         G_SpawnInt("interval", "500", &ent->genericValue1);
02854         G_SpawnInt("fudgefactor", "0", &ent->genericValue2);
02855 
02856         if (!ent->targetname || !ent->targetname[0])
02857         {
02858                 ent->think = misc_faller_think;
02859                 ent->nextthink = level.time + ent->genericValue1 + Q_irand(0, ent->genericValue2);
02860         }
02861         else
02862         {
02863                 ent->use = misc_faller_create;
02864         }
02865 }
02866 
02867 //rww - ref tag stuff ported from SP (and C-ified)
02868 #define TAG_GENERIC_NAME        "__WORLD__"     //If a designer chooses this name, cut a finger off as an example to the others
02869 
02870 //MAX_TAG_OWNERS is 16 for now in order to not use too much VM memory.
02871 //Each tag owner has preallocated space for tags up to MAX_TAGS.
02872 //As is this means 16*256 sizeof(reference_tag_t)'s in addition to name+inuse*16.
02873 #define MAX_TAGS 256
02874 #define MAX_TAG_OWNERS 16
02875 
02876 //Maybe I should use my trap_TrueMalloc/trap_TrueFree stuff with this.
02877 //But I am not yet confident that it can be used without exploding at some point.
02878 
02879 typedef struct tagOwner_s
02880 {
02881         char                    name[MAX_REFNAME];
02882         reference_tag_t tags[MAX_TAGS];
02883         qboolean                inuse;
02884 } tagOwner_t;
02885 
02886 tagOwner_t refTagOwnerMap[MAX_TAG_OWNERS];
02887 
02888 tagOwner_t *FirstFreeTagOwner(void)
02889 {
02890         int i = 0;
02891 
02892         while (i < MAX_TAG_OWNERS)
02893         {
02894                 if (!refTagOwnerMap[i].inuse)
02895                 {
02896                         return &refTagOwnerMap[i];
02897                 }
02898                 i++;
02899         }
02900 
02901         Com_Printf("WARNING: MAX_TAG_OWNERS (%i) REF TAG LIMIT HIT\n", MAX_TAG_OWNERS);
02902         return NULL;
02903 }
02904 
02905 reference_tag_t *FirstFreeRefTag(tagOwner_t *tagOwner)
02906 {
02907         int i = 0;
02908 
02909         assert(tagOwner);
02910 
02911         while (i < MAX_TAGS)
02912         {
02913                 if (!tagOwner->tags[i].inuse)
02914                 {
02915                         return &tagOwner->tags[i];
02916                 }
02917                 i++;
02918         }
02919 
02920         Com_Printf("WARNING: MAX_TAGS (%i) REF TAG LIMIT HIT\n", MAX_TAGS);
02921         return NULL;
02922 }
02923 
02924 /*
02925 -------------------------
02926 TAG_Init
02927 -------------------------
02928 */
02929 
02930 void TAG_Init( void )
02931 {
02932         int i = 0;
02933         int x = 0;
02934 
02935         while (i < MAX_TAG_OWNERS)
02936         {
02937                 while (x < MAX_TAGS)
02938                 {
02939                         memset(&refTagOwnerMap[i].tags[x], 0, sizeof(refTagOwnerMap[i].tags[x]));
02940                         x++;
02941                 }
02942                 memset(&refTagOwnerMap[i], 0, sizeof(refTagOwnerMap[i]));
02943                 i++;
02944         }
02945 }
02946 
02947 /*
02948 -------------------------
02949 TAG_FindOwner
02950 -------------------------
02951 */
02952 
02953 tagOwner_t      *TAG_FindOwner( const char *owner )
02954 {
02955         int i = 0;
02956 
02957         while (i < MAX_TAG_OWNERS)
02958         {
02959                 if (refTagOwnerMap[i].inuse && !Q_stricmp(refTagOwnerMap[i].name, owner))
02960                 {
02961                         return &refTagOwnerMap[i];
02962                 }
02963                 i++;
02964         }
02965 
02966         return NULL;
02967 }
02968 
02969 /*
02970 -------------------------
02971 TAG_Find
02972 -------------------------
02973 */
02974 
02975 reference_tag_t *TAG_Find( const char *owner, const char *name )
02976 {
02977         tagOwner_t      *tagOwner = NULL;
02978         int i = 0;
02979 
02980         if (owner && owner[0])
02981         {
02982                 tagOwner = TAG_FindOwner(owner);
02983         }
02984         if (!tagOwner)
02985         {
02986                 tagOwner = TAG_FindOwner(TAG_GENERIC_NAME);
02987         }
02988 
02989         //Not found...
02990         if (!tagOwner)
02991         {
02992                 tagOwner = TAG_FindOwner( TAG_GENERIC_NAME );
02993 
02994                 if (!tagOwner)
02995                 {
02996                         return NULL;
02997                 }
02998         }
02999 
03000         while (i < MAX_TAGS)
03001         {
03002                 if (tagOwner->tags[i].inuse && !Q_stricmp(tagOwner->tags[i].name, name))
03003                 {
03004                         return &tagOwner->tags[i];
03005                 }
03006                 i++;
03007         }
03008 
03009         //Try the generic owner instead
03010         tagOwner = TAG_FindOwner( TAG_GENERIC_NAME );
03011 
03012         if (!tagOwner)
03013         {
03014                 return NULL;
03015         }
03016 
03017         i = 0;
03018         while (i < MAX_TAGS)
03019         {
03020                 if (tagOwner->tags[i].inuse && !Q_stricmp(tagOwner->tags[i].name, name))
03021                 {
03022                         return &tagOwner->tags[i];
03023                 }
03024                 i++;
03025         }
03026 
03027         return NULL;
03028 }
03029 
03030 /*
03031 -------------------------
03032 TAG_Add
03033 -------------------------
03034 */
03035 
03036 reference_tag_t *TAG_Add( const char *name, const char *owner, vec3_t origin, vec3_t angles, int radius, int flags )
03037 {
03038         reference_tag_t *tag = NULL;
03039         tagOwner_t      *tagOwner = NULL;
03040 
03041         //Make sure this tag's name isn't alread in use
03042         if ( TAG_Find( owner, name ) )
03043         {
03044                 Com_Printf(S_COLOR_RED"Duplicate tag name \"%s\"\n", name );
03045                 return NULL;
03046         }
03047 
03048         //Attempt to add this to the owner's list
03049         if ( !owner || !owner[0] )
03050         {
03051                 //If the owner isn't found, use the generic world name
03052                 owner = TAG_GENERIC_NAME;
03053         }
03054 
03055         tagOwner = TAG_FindOwner( owner );
03056         
03057         if (!tagOwner)
03058         {
03059                 //Create a new owner list
03060                 tagOwner = FirstFreeTagOwner();//new    tagOwner_t;
03061 
03062                 if (!tagOwner)
03063                 {
03064                         assert(0);
03065                         return 0;
03066                 }
03067         }
03068         
03069         //This is actually reverse order of how SP does it because of the way we're storing/allocating.
03070         //Now that we have the owner, we want to get the first free reftag on the owner itself.
03071         tag = FirstFreeRefTag(tagOwner);
03072 
03073         if (!tag)
03074         {
03075                 assert(0);
03076                 return NULL;
03077         }
03078 
03079         //Copy the information
03080         VectorCopy( origin, tag->origin );
03081         VectorCopy( angles, tag->angles );
03082         tag->radius = radius;
03083         tag->flags      = flags;
03084 
03085         if ( !name || !name[0] )
03086         {
03087                 Com_Printf(S_COLOR_RED"ERROR: Nameless ref_tag found at (%i %i %i)\n", (int)origin[0], (int)origin[1], (int)origin[2]);
03088                 return NULL;
03089         }
03090 
03091 
03092         //Copy the name
03093         Q_strncpyz( (char *) tagOwner->name, owner, MAX_REFNAME );
03094         Q_strlwr( (char *) tagOwner->name );    //NOTENOTE: For case insensitive searches on a map
03095 
03096         //Copy the name
03097         Q_strncpyz( (char *) tag->name, name, MAX_REFNAME );
03098         Q_strlwr( (char *) tag->name ); //NOTENOTE: For case insensitive searches on a map
03099 
03100         tagOwner->inuse = qtrue;
03101         tag->inuse = qtrue;
03102 
03103         return tag;
03104 }
03105 
03106 /*
03107 -------------------------
03108 TAG_GetOrigin
03109 -------------------------
03110 */
03111 
03112 int     TAG_GetOrigin( const char *owner, const char *name, vec3_t origin )
03113 {
03114         reference_tag_t *tag = TAG_Find( owner, name );
03115 
03116         if (!tag)
03117         {
03118                 VectorClear(origin);
03119                 return 0;
03120         }
03121 
03122         VectorCopy( tag->origin, origin );
03123 
03124         return 1;
03125 }
03126 
03127 /*
03128 -------------------------
03129 TAG_GetOrigin2
03130 Had to get rid of that damn assert for dev
03131 -------------------------
03132 */
03133 
03134 int     TAG_GetOrigin2( const char *owner, const char *name, vec3_t origin )
03135 {
03136         reference_tag_t *tag = TAG_Find( owner, name );
03137 
03138         if( tag == NULL )
03139         {
03140                 return 0;
03141         }
03142 
03143         VectorCopy( tag->origin, origin );
03144 
03145         return 1;
03146 }
03147 /*
03148 -------------------------
03149 TAG_GetAngles
03150 -------------------------
03151 */
03152 
03153 int     TAG_GetAngles( const char *owner, const char *name, vec3_t angles )
03154 {
03155         reference_tag_t *tag = TAG_Find( owner, name );
03156 
03157         if (!tag)
03158         {
03159                 assert(0);
03160                 return 0;
03161         }
03162 
03163         VectorCopy( tag->angles, angles );
03164         
03165         return 1;
03166 }
03167 
03168 /*
03169 -------------------------
03170 TAG_GetRadius
03171 -------------------------
03172 */
03173 
03174 int TAG_GetRadius( const char *owner, const char *name )
03175 {
03176         reference_tag_t *tag = TAG_Find( owner, name );
03177 
03178         if (!tag)
03179         {
03180                 assert(0);
03181                 return 0;
03182         }
03183 
03184         return tag->radius;
03185 }
03186 
03187 /*
03188 -------------------------
03189 TAG_GetFlags
03190 -------------------------
03191 */
03192 
03193 int TAG_GetFlags( const char *owner, const char *name )
03194 {
03195         reference_tag_t *tag = TAG_Find( owner, name );
03196 
03197         if (!tag)
03198         {
03199                 assert(0);
03200                 return 0;
03201         }
03202 
03203         return tag->flags;
03204 }
03205 
03206 /*
03207 ==============================================================================
03208 
03209 Spawn functions
03210 
03211 ==============================================================================
03212 */
03213 
03214 /*QUAKED ref_tag_huge (0.5 0.5 1) (-128 -128 -128) (128 128 128)
03215 SAME AS ref_tag, JUST BIGGER SO YOU CAN SEE THEM IN EDITOR ON HUGE MAPS!
03216 
03217 Reference tags which can be positioned throughout the level.
03218 These tags can later be refered to by the scripting system
03219 so that their origins and angles can be referred to.
03220 
03221 If you set angles on the tag, these will be retained.
03222 
03223 If you target a ref_tag at an entity, that will set the ref_tag's
03224 angles toward that entity.
03225 
03226 If you set the ref_tag's ownername to the ownername of an entity,
03227 it makes that entity is the owner of the ref_tag.  This means 
03228 that the owner, and only the owner, may refer to that tag.
03229 
03230 Tags may not have the same name as another tag with the same
03231 owner.  However, tags with different owners may have the same
03232 name as one another.  In this way, scripts can generically 
03233 refer to tags by name, and their owners will automatically
03234 specifiy which tag is being referred to.
03235 
03236 targetname      - the name of this tag
03237 ownername       - the owner of this tag
03238 target          - use to point the tag at something for angles
03239 */
03240 
03241 /*QUAKED ref_tag (0.5 0.5 1) (-8 -8 -8) (8 8 8)
03242 
03243 Reference tags which can be positioned throughout the level.
03244 These tags can later be refered to by the scripting system
03245 so that their origins and angles can be referred to.
03246 
03247 If you set angles on the tag, these will be retained.
03248 
03249 If you target a ref_tag at an entity, that will set the ref_tag's
03250 angles toward that entity.
03251 
03252 If you set the ref_tag's ownername to the ownername of an entity,
03253 it makes that entity is the owner of the ref_tag.  This means 
03254 that the owner, and only the owner, may refer to that tag.
03255 
03256 Tags may not have the same name as another tag with the same
03257 owner.  However, tags with different owners may have the same
03258 name as one another.  In this way, scripts can generically 
03259 refer to tags by name, and their owners will automatically
03260 specifiy which tag is being referred to.
03261 
03262 targetname      - the name of this tag
03263 ownername       - the owner of this tag
03264 target          - use to point the tag at something for angles
03265 */
03266 
03267 void ref_link ( gentity_t *ent )
03268 {
03269         reference_tag_t *tag;
03270 
03271         if ( ent->target )
03272         {
03273                 //TODO: Find the target and set our angles to that direction
03274                 gentity_t       *target = G_Find( NULL, FOFS(targetname), ent->target );
03275                 vec3_t  dir;
03276 
03277                 if ( target )
03278                 {
03279                         //Find the direction to the target
03280                         VectorSubtract( target->s.origin, ent->s.origin, dir );
03281                         VectorNormalize( dir );
03282                         vectoangles( dir, ent->s.angles );
03283                         
03284                         //FIXME: Does pitch get flipped?
03285                 }
03286                 else
03287                 {
03288                         Com_Printf( S_COLOR_RED"ERROR: ref_tag (%s) has invalid target (%s)", ent->targetname, ent->target );
03289                 }
03290         }
03291         
03292         //Add the tag
03293         tag = TAG_Add( ent->targetname, ent->ownername, ent->s.origin, ent->s.angles, 16, 0 );
03294 
03295         //Delete immediately, cannot be refered to as an entity again
03296         //NOTE: this means if you wanted to link them in a chain for, say, a path, you can't
03297         G_FreeEntity( ent );
03298 }
03299 
03300 void SP_reference_tag ( gentity_t *ent )
03301 {
03302         if ( ent->target )
03303         {
03304                 //Init cannot occur until all entities have been spawned
03305                 ent->think = ref_link;
03306                 ent->nextthink = level.time + START_TIME_LINK_ENTS;
03307         }
03308         else
03309         {
03310                 ref_link( ent );
03311         }
03312 }
03313 
03314 /*QUAKED misc_weapon_shooter (1 0 0) (-8 -8 -8) (8 8 8) ALTFIRE TOGGLE
03315 ALTFIRE - fire the alt-fire of the chosen weapon
03316 TOGGLE - keep firing until used again (fires at intervals of "wait")
03317 
03318 "wait" - debounce time between refires (defaults to 500)
03319 
03320 "target" - what to aim at (will update aim every frame if it's a moving target)  
03321 
03322 "weapon" - specify the weapon to use (default is WP_BLASTER)
03323         WP_BRYAR_PISTOL
03324         WP_BLASTER
03325         WP_DISRUPTOR
03326         WP_BOWCASTER
03327         WP_REPEATER
03328         WP_DEMP2
03329         WP_FLECHETTE
03330         WP_ROCKET_LAUNCHER
03331         WP_THERMAL
03332         WP_TRIP_MINE
03333         WP_DET_PACK
03334         WP_STUN_BATON
03335         WP_EMPLACED_GUN
03336         WP_BOT_LASER
03337         WP_TURRET
03338         WP_ATST_MAIN
03339         WP_ATST_SIDE
03340         WP_TIE_FIGHTER
03341         WP_RAPID_FIRE_CONC
03342         WP_BLASTER_PISTOL
03343 */
03344 //kind of hacky, but we have to do this with no dynamic allocation
03345 #define MAX_SHOOTERS            16
03346 typedef struct shooterClient_s
03347 {
03348         gclient_t               cl;
03349         qboolean                inuse;
03350 } shooterClient_t;
03351 static shooterClient_t g_shooterClients[MAX_SHOOTERS];
03352 static qboolean g_shooterClientInit = qfalse;
03353 
03354 gclient_t *G_ClientForShooter(void)
03355 {
03356         int i = 0;
03357 
03358         if (!g_shooterClientInit)
03359         { //in theory it should be initialized to 0 on the stack, but just in case.
03360                 memset(g_shooterClients, 0, sizeof(shooterClient_t)*MAX_SHOOTERS);
03361                 g_shooterClientInit = qtrue;
03362         }
03363 
03364         while (i < MAX_SHOOTERS)
03365         {
03366                 if (!g_shooterClients[i].inuse)
03367                 {
03368                         return &g_shooterClients[i].cl;
03369                 }
03370                 i++;
03371         }
03372 
03373         Com_Error(ERR_DROP, "No free shooter clients - hit MAX_SHOOTERS");
03374         return NULL;
03375 }
03376 
03377 void G_FreeClientForShooter(gclient_t *cl)
03378 {
03379         int i = 0;
03380         while (i < MAX_SHOOTERS)
03381         {
03382                 if (&g_shooterClients[i].cl == cl)
03383                 {
03384                         g_shooterClients[i].inuse = qfalse;
03385                         return;
03386                 }
03387                 i++;
03388         }
03389 }
03390 
03391 void misc_weapon_shooter_fire( gentity_t *self )
03392 {
03393         FireWeapon( self, (self->spawnflags&1) );
03394         if ( (self->spawnflags&2) )
03395         {//repeat
03396                 self->think = misc_weapon_shooter_fire;
03397                 self->nextthink = level.time + self->wait;
03398         }
03399 }
03400 
03401 void misc_weapon_shooter_use ( gentity_t *self, gentity_t *other, gentity_t *activator )
03402 {
03403         if ( self->think == misc_weapon_shooter_fire )
03404         {//repeating fire, stop
03405                 /*
03406                 G_FreeClientForShooter(self->client);
03407                 self->think = G_FreeEntity;
03408                 self->nextthink = level.time;
03409                 */
03410                 self->nextthink = 0;
03411                 return;
03412         }
03413         //otherwise, fire
03414         misc_weapon_shooter_fire( self );
03415 }
03416 
03417 void misc_weapon_shooter_aim( gentity_t *self )
03418 {
03419         //update my aim
03420         if ( self->target )
03421         {
03422                 gentity_t *targ = G_Find( NULL, FOFS(targetname), self->target );
03423                 if ( targ )
03424                 {
03425                         self->enemy = targ;
03426                         VectorSubtract( targ->r.currentOrigin, self->r.currentOrigin, self->pos1 );
03427                         VectorCopy( targ->r.currentOrigin, self->pos1 );
03428                         vectoangles( self->pos1, self->client->ps.viewangles );
03429                         SetClientViewAngle( self, self->client->ps.viewangles );
03430                         //FIXME: don't keep doing this unless target is a moving target?
03431                         self->nextthink = level.time + FRAMETIME;
03432                 }
03433                 else
03434                 {
03435                         self->enemy = NULL;
03436                 }
03437         }
03438 }
03439 
03440 #include "../namespace_begin.h"
03441 extern stringID_table_t WPTable[];
03442 #include "../namespace_end.h"
03443 
03444 void SP_misc_weapon_shooter( gentity_t *self )
03445 {
03446         char *s;
03447 
03448         //alloc a client just for the weapon code to use
03449         self->client = G_ClientForShooter();//(gclient_s *)gi.Malloc(sizeof(gclient_s), TAG_G_ALLOC, qtrue);
03450 
03451         G_SpawnString("weapon", "", &s);
03452 
03453         //set weapon
03454         self->s.weapon = self->client->ps.weapon = WP_BLASTER;
03455         if ( s && s[0] )
03456         {//use a different weapon
03457                 self->s.weapon = self->client->ps.weapon = GetIDForString( WPTable, s );
03458         }
03459 
03460         RegisterItem(BG_FindItemForWeapon(self->s.weapon));
03461 
03462         //set where our muzzle is
03463         VectorCopy( self->s.origin, self->client->renderInfo.muzzlePoint );
03464         //permanently updated (don't need for MP)
03465         //self->client->renderInfo.mPCalcTime = Q3_INFINITE;
03466 
03467         //set up to link
03468         if ( self->target )
03469         {
03470         self->think = misc_weapon_shooter_aim;
03471                 self->nextthink = level.time + START_TIME_LINK_ENTS;
03472         }
03473         else
03474         {//just set aim angles
03475                 VectorCopy( self->s.angles, self->client->ps.viewangles );
03476                 AngleVectors( self->s.angles, self->pos1, NULL, NULL );
03477         }
03478 
03479         //set up to fire when used
03480     self->use = misc_weapon_shooter_use;
03481 
03482         if ( !self->wait )
03483         {
03484                 self->wait = 500;
03485         }
03486 }
03487 
03488 /*QUAKED misc_weather_zone (0 .5 .8) ? 
03489 Determines a region to check for weather contents - will significantly reduce load time
03490 */
03491 void SP_misc_weather_zone( gentity_t *ent )
03492 {
03493         G_FreeEntity(ent);
03494 }