// Copyright (C) 1999-2000 Id Software, Inc. // #include "g_local.h" extern void SP_misc_ammo_station( gentity_t *ent ); extern void ammo_station_finish_spawning ( gentity_t *self ); extern int borgQueenClientNum; extern qboolean BG_BorgTransporting( playerState_t *ps ); static int lastTimedMessage; //TiM - Moved here from g_local.h /* ============== TryUse Try and use an entity in the world, directly ahead of us ============== */ #define USE_DISTANCE 64.0f void TryUse( gentity_t *ent ) { gentity_t *target; //, *newent; trace_t trace; vec3_t src, dest, vf; int i; VectorCopy( ent->r.currentOrigin, src ); src[2] += ent->client->ps.viewheight; AngleVectors( ent->client->ps.viewangles, vf, NULL, NULL ); //extend to find end of use trace VectorMA( src, -6, vf, src );//in case we're inside something? VectorMA( src, 134, vf, dest );//128+6 //Trace ahead to find a valid target trap_Trace( &trace, src, vec3_origin, vec3_origin, dest, ent->s.number, MASK_OPAQUE|CONTENTS_BODY|CONTENTS_ITEM|CONTENTS_CORPSE ); if ( trace.fraction == 1.0f || trace.entityNum < 0 ) { //FIXME: Play a failure sound return; } target = &g_entities[trace.entityNum]; //Check for a use command if ( target->client ) {//if a bot, make it follow me, if a teammate, give stuff if ( target->client->sess.sessionTeam == ent->client->sess.sessionTeam ) {//on my team // gitem_t *regen = BG_FindItemForPowerup( PW_REGEN ); // gitem_t *invis = BG_FindItemForPowerup( PW_INVIS ); switch( ent->client->sess.sessionClass ) { case PC_MEDIC://medic gives regen break; case PC_TECH://tech gives invisibility break; } switch( target->client->sess.sessionClass ) { case PC_TECH://using tech gives you full ammo G_Sound(ent, G_SoundIndex("sound/player/suitenergy.wav") ); for ( i = 0 ; i < MAX_WEAPONS ; i++ ) { ent->client->ps.ammo[i] = Max_Ammo[i]; } return; break; } } } else if ( target->use && Q_stricmp("func_usable", target->classname) == 0 ) {//usable brush if ( target->team && atoi( target->team ) != 0 ) {//usable has a team if ( atoi( target->team ) != ent->client->sess.sessionTeam ) {//not on my team if ( ent->client->sess.sessionClass != PC_TECH ) {//only a tech can use enemy usables //FIXME: Play a failure sound return; } } } //FIXME: play sound? target->use( target, ent, ent ); return; } else if ( target->use && Q_stricmp("misc_ammo_station", target->classname) == 0 ) {//ammo station if ( ent->client->sess.sessionTeam ) { if ( target->team ) { if ( atoi( target->team ) != ent->client->sess.sessionTeam ) { //FIXME: play sound? return; } } } target->use( target, ent, ent ); return; } else if ( target->s.number == ENTITYNUM_WORLD || (target->s.pos.trType == TR_STATIONARY && !(trace.surfaceFlags & SURF_NOIMPACT) && !target->takedamage) ) { switch( ent->client->sess.sessionClass ) { case PC_TECH://tech can place an ammo station break; } //FIXME: play sound return; } //FIXME: Play a failure sound } /* =============== G_DamageFeedback Called just before a snapshot is sent to the given player. Totals up all damage and generates both the player_state_t damage values to that client for pain blends and kicks, and global pain sound events for all clients. =============== */ void P_DamageFeedback( gentity_t *player ) { gclient_t *client; float count; vec3_t angles; client = player->client; if ( client->ps.pm_type == PM_DEAD ) { return; } // total points of damage shot at the player this frame count = client->damage_blood + client->damage_armor; if ( count == 0 ) { return; // didn't take any damage } if ( count > 255 ) { count = 255; } // send the information to the client // world damage (falling, slime, etc) uses a special code // to make the blend blob centered instead of positional if ( client->damage_fromWorld ) { client->ps.damagePitch = 255; client->ps.damageYaw = 255; client->damage_fromWorld = qfalse; } else { vectoangles( client->damage_from, angles ); client->ps.damagePitch = angles[PITCH]/360.0 * 256; client->ps.damageYaw = angles[YAW]/360.0 * 256; } // play an apropriate pain sound if ( (level.time > player->pain_debounce_time) && !(player->flags & FL_GODMODE) ) { player->pain_debounce_time = level.time + 700; G_AddEvent( player, EV_PAIN, player->health ); client->ps.damageEvent++; } client->ps.damageCount = client->damage_blood; if (client->ps.damageCount > 255) { client->ps.damageCount = 255; } client->ps.damageShieldCount = client->damage_armor; if (client->ps.damageShieldCount > 255) { client->ps.damageShieldCount = 255; } // // clear totals // client->damage_blood = 0; client->damage_armor = 0; client->damage_knockback = 0; } /* ============= P_WorldEffects Check for lava / slime contents and drowning ============= */ void P_WorldEffects( gentity_t *ent ) { //qboolean envirosuit; int waterlevel; if ( ent->client->noclip ) { ent->client->airOutTime = level.time + 12000; // don't need air return; } waterlevel = ent->waterlevel; //envirosuit = ent->client->ps.powerups[PW_BOLTON] > level.time; // // check for drowning // if ( waterlevel == 3 && !(ent->watertype&CONTENTS_LADDER)) { // envirosuit give air, techs can't drown if ( /*envirosuit ||*/ ent->client->sess.sessionClass == PC_ALPHAOMEGA22 ) { ent->client->airOutTime = level.time + 10000; } // if out of air, start drowning if ( ent->client->airOutTime < level.time) { // drown! ent->client->airOutTime += 1000; if ( ent->health > 1 ) { //TiM : used to be 0, but to fix red's medic code // take more damage the longer underwater ent->damage += 2; if (ent->damage > 15) ent->damage = 15; // play a gurp sound instead of a normal pain sound if (ent->health <= ent->damage) { G_Sound(ent, G_SoundIndex("*drown.wav")); } else if (rand()&1) { G_Sound(ent, G_SoundIndex("sound/player/gurp1.wav")); } else { G_Sound(ent, G_SoundIndex("sound/player/gurp2.wav")); } // don't play a normal pain sound ent->pain_debounce_time = level.time + 200; G_Damage (ent, NULL, NULL, NULL, NULL, ent->damage, DAMAGE_NO_ARMOR, MOD_WATER); } } } else { ent->client->airOutTime = level.time + 12000; ent->damage = 2; } // // check for sizzle damage (move to pmove?) // if (waterlevel && (ent->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) ) { if (ent->health > 0 && ent->pain_debounce_time < level.time ) { /* if ( envirosuit ) { G_AddEvent( ent, EV_POWERUP_BATTLESUIT, 0 ); } else {*/ if (ent->watertype & CONTENTS_LAVA) { G_Damage (ent, NULL, NULL, NULL, NULL, 30*waterlevel, 0, MOD_LAVA); } if (ent->watertype & CONTENTS_SLIME) { G_Damage (ent, NULL, NULL, NULL, NULL, 10*waterlevel, 0, MOD_SLIME); } //} } } } /* =============== G_SetClientSound =============== */ void G_SetClientSound( gentity_t *ent ) { // 3/28/00 kef -- this is dumb. if (ent->waterlevel && (ent->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) ) ent->s.loopSound = level.snd_fry; else ent->s.loopSound = 0; } //============================================================== /* ============== ClientImpacts ============== */ void ClientImpacts( gentity_t *ent, pmove_t *pm ) { int i, j; trace_t trace; gentity_t *other; memset( &trace, 0, sizeof( trace ) ); for (i=0 ; inumtouch ; i++) { for (j=0 ; jtouchents[j] == pm->touchents[i] ) { break; } } if (j != i) { continue; // duplicated } other = &g_entities[ pm->touchents[i] ]; if ( ( ent->r.svFlags & SVF_BOT ) && ( ent->touch ) ) { ent->touch( ent, other, &trace ); } if ( !other->touch ) { continue; } other->touch( other, ent, &trace ); } } /* ============ G_TouchTriggers Find all trigger entities that ent's current position touches. Spectators will only interact with teleporters. ============ */ void G_TouchTriggers( gentity_t *ent ) { int i, num; int touch[MAX_GENTITIES]; gentity_t *hit; trace_t trace; vec3_t mins, maxs; static vec3_t range = { 40, 40, 52 }; if ( !ent->client ) { return; } // dead clients don't activate triggers! if ( ent->client->ps.stats[STAT_HEALTH] <= 0 ) { return; } VectorSubtract( ent->client->ps.origin, range, mins ); VectorAdd( ent->client->ps.origin, range, maxs ); num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); // can't use ent->absmin, because that has a one unit pad VectorAdd( ent->client->ps.origin, ent->r.mins, mins ); VectorAdd( ent->client->ps.origin, ent->r.maxs, maxs ); for ( i=0 ; itouch && !ent->touch ) { continue; } if ( !( hit->r.contents & CONTENTS_TRIGGER ) ) { continue; } // ignore most entities if a spectator if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR || (ent->client->ps.eFlags&EF_ELIMINATED) ) { //RPG-X: J2J - No clip spectating doesnt need to use transporter entities or door triggers! if( rpg_noclipspectating.integer == 1 ) continue; // this is ugly but adding a new ET_? type will // most likely cause network incompatibilities if ( hit->s.eType != ET_TELEPORT_TRIGGER && hit->touch != Touch_DoorTrigger) { continue; } } // use seperate code for determining if an item is picked up // so you don't have to actually contact its bounding box if ( hit->s.eType == ET_ITEM ) { if ( !BG_PlayerTouchesItem( &ent->client->ps, &hit->s, level.time ) ) { continue; } } else { if ( !trap_EntityContact( mins, maxs, hit ) ) { continue; } } memset( &trace, 0, sizeof(trace) ); if ( hit->touch ) { hit->touch (hit, ent, &trace); } if ( ( ent->r.svFlags & SVF_BOT ) && ( ent->touch ) ) { ent->touch( ent, hit, &trace ); } } } /* ================= SpectatorThink ================= */ void SpectatorThink( gentity_t *ent, usercmd_t *ucmd ) { pmove_t pm; gclient_t *client; client = ent->client; if ( client->sess.spectatorState != SPECTATOR_FOLLOW ) { client->ps.pm_type = PM_SPECTATOR; client->ps.speed = 400; // faster than normal // set up for pmove memset (&pm, 0, sizeof(pm)); pm.ps = &client->ps; pm.cmd = *ucmd; pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; // spectators can fly through bodies pm.trace = trap_Trace; pm.pointcontents = trap_PointContents; // perform a pmove Pmove (&pm); // save results of pmove VectorCopy( client->ps.origin, ent->s.origin ); G_TouchTriggers( ent ); trap_UnlinkEntity( ent ); } client->oldbuttons = client->buttons; client->buttons = ucmd->buttons; // attack button cycles through spectators if ( ( client->buttons & BUTTON_ATTACK ) && ! ( client->oldbuttons & BUTTON_ATTACK ) ) { Cmd_FollowCycle_f( ent, 1 ); } else if ( ( client->buttons & BUTTON_ALT_ATTACK ) && ! ( client->oldbuttons & BUTTON_ALT_ATTACK ) ) { if ( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) { StopFollowing( ent ); } } } /* ================= ClientInactivityTimer Returns qfalse if the client is dropped ================= */ qboolean ClientInactivityTimer( gclient_t *client ) { if ( ! g_inactivity.integer ) { // give everyone some time, so if the operator sets g_inactivity during // gameplay, everyone isn't kicked client->inactivityTime = level.time + 60 * 1000; client->inactivityWarning = qfalse; } else if ( client->pers.cmd.forwardmove || client->pers.cmd.rightmove || client->pers.cmd.upmove || (client->pers.cmd.buttons & BUTTON_ATTACK) || (client->pers.cmd.buttons & BUTTON_ALT_ATTACK) ) { client->inactivityTime = level.time + g_inactivity.integer * 1000; client->inactivityWarning = qfalse; } else if ( !client->pers.localClient ) { if ( level.time > client->inactivityTime ) { trap_DropClient( client - level.clients, "Dropped due to inactivity" ); return qfalse; } if ( level.time > client->inactivityTime - 10000 && !client->inactivityWarning ) { client->inactivityWarning = qtrue; trap_SendServerCommand( client - level.clients, "cp \"Ten seconds until inactivity drop!\n\"" ); } } return qtrue; } /* ================== TimedMessage RPG-X - RedTechie: Returns the message requested. If the message is blank go to next. (Based off of SFEFMOD) TiM: Huh... O_o. Damn Red, you're right. If the admin puts in values, but not in a consistent consecutive order, we'll get some mighty painful errors. >.< I guess what we really need is a for loop that goes thru, and checks to see if each and every CVAR has a value currently in it.... ================== */ //First off let's store the CVAR data in a local array. //This'll let us perform for loop operations on it. //Sigh, I wish I knew a quick way to convert variable names //to strings. Then this wouldn't be necessary O_o static char *rpg_message[] = { rpg_message1.string, rpg_message2.string, rpg_message3.string, rpg_message4.string, rpg_message5.string, rpg_message6.string, rpg_message7.string, rpg_message8.string, rpg_message9.string, rpg_message10.string }; char *TimedMessage( void ){ int i = lastTimedMessage; while ( 1 ) { //Okay, start from the number we want, and loop thru all of them if ( i == 11 ) { //reset loop i = 1; } if ( strlen( rpg_message[i-1] ) >= 1 ) { //i-1 coz arrays start @ 0, but we started @ 1 lastTimedMessage = i; return rpg_message[i-1]; //return the string } if ( i == lastTimedMessage - 1 ) { //okay, we've obviously gone thru the whole loop and come up with nothing. So screw it. return NULL; } //TiM: Cheap hack to stop it freezing if string0 is empty. THe above condition don't work here if ( ( i == lastTimedMessage == 1 ) && !strlen( rpg_message[i-1] ) ) { return NULL; } i++; } //TiM: Hopefully it'll never reach here, but we have this anyway to shut the compiler up return "^1RPG-X ERROR: No messages to display"; /* switch( Msg ){ case 1: if(strlen(rpg_message1.string) >= 1){ lastTimedMessage = 1; return rpg_message1.string; }else{ return TimedMessage( 2 ); } break; case 2: if(strlen(rpg_message2.string) >= 1){ lastTimedMessage = 2; return rpg_message2.string; }else{ return TimedMessage( 3 ); } break; case 3: if(strlen(rpg_message3.string) >= 1){ lastTimedMessage = 3; return rpg_message3.string; }else{ return TimedMessage( 4 ); } break; case 4: if(strlen(rpg_message4.string) >= 1){ lastTimedMessage = 4; return rpg_message4.string; }else{ return TimedMessage( 5 ); } break; case 5: if(strlen(rpg_message5.string) >= 1){ lastTimedMessage = 5; return rpg_message5.string; }else{ return TimedMessage( 6 ); } break; case 6: if(strlen(rpg_message6.string) >= 1){ lastTimedMessage = 6; return rpg_message6.string; }else{ return TimedMessage( 7 ); } break; case 7: if(strlen(rpg_message7.string) >= 1){ lastTimedMessage = 7; return rpg_message7.string; }else{ return TimedMessage( 8 ); } break; case 8: if(strlen(rpg_message8.string) >= 1){ lastTimedMessage = 8; return rpg_message8.string; }else{ return TimedMessage( 9 ); } break; case 9: if(strlen(rpg_message9.string) >= 1){ lastTimedMessage = 9; return rpg_message9.string; }else{ return TimedMessage( 10 ); } break; case 10: if(strlen(rpg_message10.string) >= 1){ lastTimedMessage = 0; //Start over again return rpg_message10.string; }else{ return TimedMessage( 1 ); } break; default: return "^1RPG-X ERROR: No messages to display"; break; }*/ } /* ================== ClientTimerActions Actions that happen once a second ================== */ void ClientTimerActions( gentity_t *ent, int msec ) { gclient_t *client; char *message; float messageTime; client = ent->client; client->timeResidual += msec; //RPG-X - RedTechie: Heavily Modifyed Message system (Based off of phenix's old code) /*if ( level.time >= (level.message + (rpg_timedmessagetime.integer * 60000)) ) { level.message = level.time; trap_SendServerCommand( -1, va("cp \"%s\n\"", rpg_timedmessage.string) ); //trap_SendServerCommand( -1, va( "print \"%s\n\"", rpg_timedmessage ) ); }*/ if( rpg_timedmessagetime.value ){ //Make sure its not less then one //TiM: Well... we can have under 1, just not toooo far under 1 //TiM : Init/reset TimedMessage's value if ( lastTimedMessage <= 0 || lastTimedMessage > 10 ) { lastTimedMessage = 1; } if(rpg_timedmessagetime.value < 0.2) { //1 messageTime = 0.2; }else{ messageTime = rpg_timedmessagetime.value; } if (level.time > (level.message + (messageTime * 60000)) ) { level.message = level.time; //TiM - There. So with this working in conjunction with that reset //code above, this should be more efficient. :) message = TimedMessage(); //Since we're working with a gloabl scope variable, there's no need for this thing to have parameters:) lastTimedMessage++; /*//Decide what timed message to display if( lastTimedMessage && lastTimedMessage != 0 ){ //A message has been displayed if(lastTimedMessage == 1){ //Display message 2 message = TimedMessage( 2 ); }else if(lastTimedMessage == 2){ //Display message 3 message = TimedMessage( 3 ); }else if(lastTimedMessage == 3){ //Display message 4 message = TimedMessage( 4 ); }else if(lastTimedMessage == 4){ //Display message 5 message = TimedMessage( 5 ); }else if(lastTimedMessage == 5){ //Display message 6 message = TimedMessage( 6 ); }else if(lastTimedMessage == 6){ //Display message 7 message = TimedMessage( 7 ); }else if(lastTimedMessage == 7){ //Display message 8 message = TimedMessage( 8 ); }else if(lastTimedMessage == 8){ //Display message 9 message = TimedMessage( 9 ); }else if(lastTimedMessage == 9){ //Display message 10 message = TimedMessage( 10 ); }else{ //BAD message = "^1RPG-X ERROR: lastTimedMessage not set correctly."; } }else{ //A message hasnt been displayed yet message = TimedMessage( 1 ); }*/ //Alright send the message now if ( message != NULL ) { trap_SendServerCommand( -1, va("cp \"%s\n\"", message) ); //Shows the message on their main screen } //trap_SendServerCommand( -1, va("print \"\n \n%s \n\"", message)); //Shows the message in console } } while ( client->timeResidual >= 1000 ) { client->timeResidual -= 1000; // regenerate //TiM - removed so we can use the REGEN powerup elsewhere /*if ( client->ps.powerups[PW_REGEN] ) { if ( client->sess.sessionClass != PC_NOCLASS && client->sess.sessionClass != PC_BORG && client->sess.sessionClass != PC_ACTIONHERO ) { if ( ent->health < client->ps.stats[STAT_MAX_HEALTH] ) { ent->health += 2; G_AddEvent( ent, EV_POWERUP_REGEN, 0 ); } } else if ( ent->health < client->ps.stats[STAT_MAX_HEALTH]) { ent->health += 15; if ( ent->health > client->ps.stats[STAT_MAX_HEALTH] * 1.1 ) { ent->health = client->ps.stats[STAT_MAX_HEALTH] * 1.1; } G_AddEvent( ent, EV_POWERUP_REGEN, 0 ); } else if ( ent->health < client->ps.stats[STAT_MAX_HEALTH] * 2) { ent->health += 5; if ( ent->health > client->ps.stats[STAT_MAX_HEALTH] * 2 ) { ent->health = client->ps.stats[STAT_MAX_HEALTH] * 2; } G_AddEvent( ent, EV_POWERUP_REGEN, 0 ); } }*/ /*else if (ent->flags & FL_CLOAK) { //RPG-X: RedTechie - Health dosnt matter if ( ent->health < 41) { ent->flags ^= FL_CLOAK; ent->client->ps.powerups[PW_INVIS] = level.time + 1000000000; } } else if (ent->flags & FL_FLY) { //RPG-X: RedTechie - Health dosnt matter if ( ent->health < 41) { ent->flags ^= FL_FLY; ent->client->ps.powerups[PW_FLIGHT] = level.time + 1000000000; } }*/ //else //{ // count down health when over max and not cloaked. if ( ent->health > client->ps.stats[STAT_MAX_HEALTH] ) { ent->health--; } //} // THIS USED TO count down armor when over max, more slowly now (every other second) // if ( client->ps.stats[STAT_ARMOR] > client->ps.stats[STAT_MAX_HEALTH] && ((int)(level.time/1000))&0x01) { // NOW IT ONCE AGAIN counts down armor when over max, once per second if ( client->ps.stats[STAT_ARMOR] > client->ps.stats[STAT_MAX_HEALTH]) { client->ps.stats[STAT_ARMOR]--; } // if we've got the seeker powerup, see if we can shoot it at someone //if ( client->ps.powerups[PW_SEEKER] ) //{ //vec3_t seekerPos; //TiM - Because I commented out the scav code, I'm going to remove all other ref to it /*if (SeekerAcquiresTarget(ent, seekerPos)) // set the client's enemy to a valid target { FireSeeker( ent, ent->enemy, seekerPos ); G_AddEvent( ent, EV_POWERUP_SEEKER_FIRE, 0 ); // draw the thingy }*/ //} if ( !client->ps.stats[STAT_HOLDABLE_ITEM] ) {//holding nothing... if ( client->ps.stats[STAT_USEABLE_PLACED] > 0 ) {//we're in some kind of countdown //so count down client->ps.stats[STAT_USEABLE_PLACED]--; } } } } /* ==================== ClientIntermissionThink ==================== */ void ClientIntermissionThink( gclient_t *client ) { client->ps.eFlags &= ~EF_TALK; client->ps.eFlags &= ~EF_FIRING; // the level will exit when everyone wants to or after timeouts // swap and latch button actions client->oldbuttons = client->buttons; client->buttons = client->pers.cmd.buttons; if (g_gametype.integer != GT_SINGLE_PLAYER) { if ( client->buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) & ( client->oldbuttons ^ client->buttons ) ) { client->readyToExit ^= 1; } } } /* ==================== Cmd_Ready_f ==================== This function is called from the ui_sp_postgame.c as a result of clicking on the "next" button in non GT_TOURNAMENT games. This replaces the old system of waiting for the user to click an ATTACK or USE button to signal ready (see ClientIntermissionThink() above) when all clients have signaled ready, the game continues to the next match. */ void Cmd_Ready_f (gentity_t *ent) { gclient_t *client; client = ent->client; client->readyToExit ^= 1; } typedef struct detHit_s { gentity_t *detpack; gentity_t *player; int time; } detHit_t; #define MAX_DETHITS 32 // never define this to be 0 detHit_t detHits[MAX_DETHITS]; qboolean bDetInit = qfalse; extern qboolean FinishSpawningDetpack( gentity_t *ent, int itemIndex ); //-----------------------------------------------------------------------------DECOY TEMP extern qboolean FinishSpawningDecoy( gentity_t *ent, int itemIndex ); //-----------------------------------------------------------------------------DECOY TEMP void DetonateDetpack(gentity_t *ent); #define DETPACK_DAMAGE 750 #define DETPACK_RADIUS 500 void detpack_shot( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath ) { int i = 0; gentity_t *ent = NULL; //so we can't be blown up by things we're blowing up self->takedamage = 0; G_TempEntity(self->s.origin, EV_GRENADE_EXPLODE); G_RadiusDamage( self->s.origin, self->parent?self->parent:self, DETPACK_DAMAGE*0.125, DETPACK_RADIUS*0.25, self, DAMAGE_ALL_TEAMS, MOD_DETPACK ); // we're blowing up cuz we've been shot, so make sure we remove ourselves //from our parent's inventory (so to speak) for (i = 0; i < MAX_CLIENTS; i++) { if (((ent = &g_entities[i])!=NULL) && ent->inuse && (self->parent == ent)) { ent->client->ps.stats[STAT_USEABLE_PLACED] = 0; ent->client->ps.stats[STAT_HOLDABLE_ITEM] = 0; break; } } G_FreeEntity(self); } qboolean PlaceDetpack(gentity_t *ent) { gentity_t *detpack = NULL; static gitem_t *detpackItem = NULL; float detDistance = 80; trace_t tr; vec3_t fwd, right, up, end, mins = {-16,-16,-16}, maxs = {16,16,16}; if (detpackItem == NULL) { detpackItem = BG_FindItemForHoldable(HI_DETPACK); } // make sure our detHit info is init'd if (!bDetInit) { memset(detHits, 0, MAX_DETHITS*sizeof(detHit_t)); bDetInit = 1; } // can we place this in front of us? AngleVectors (ent->client->ps.viewangles, fwd, right, up); fwd[2] = 0; VectorMA(ent->client->ps.origin, detDistance, fwd, end); trap_Trace (&tr, ent->client->ps.origin, mins, maxs, end, ent->s.number, MASK_SHOT ); if (tr.fraction > 0.9) { // got enough room so place the detpack detpack = G_Spawn(); G_SpawnItem(detpack, detpackItem); detpack->physicsBounce = 0.0f;//detpacks are *not* bouncy VectorMA(ent->client->ps.origin, detDistance + mins[0], fwd, detpack->s.origin); if ( !FinishSpawningDetpack(detpack, detpackItem - bg_itemlist) ) { return qfalse; } VectorNegate(fwd, fwd); vectoangles(fwd, detpack->s.angles); detpack->think = DetonateDetpack; detpack->nextthink = level.time + 120000; // if not used after 2 minutes it blows up anyway detpack->parent = ent; return qtrue; } else { // no room return qfalse; } } qboolean PlayerHitByDet(gentity_t *det, gentity_t *player) { int i = 0; if (!bDetInit) { // detHit stuff not initialized. who knows what's going on? return qfalse; } for (i = 0; i < MAX_DETHITS; i++) { if ( (detHits[i].detpack == det) && (detHits[i].player == player) ) { return qtrue; } } return qfalse; } void AddPlayerToDetHits(gentity_t *det, gentity_t *player) { int i = 0; detHit_t *lastHit = NULL, *curHit = NULL; for (i = 0; i < MAX_DETHITS; i++) { if (0 == detHits[i].time) { // empty slot. add our player here. detHits[i].detpack = det; detHits[i].player = player; detHits[i].time = level.time; } lastHit = &detHits[i]; } // getting here means we've filled our list of detHits, so begin recycling them, starting with the oldest hit. curHit = &detHits[0]; while (lastHit->time < curHit->time) { lastHit = curHit; curHit++; // just a safety check here if (curHit == &detHits[0]) { break; } } curHit->detpack = det; curHit->player = player; curHit->time = level.time; } void ClearThisDetpacksHits(gentity_t *det) { int i = 0; for (i = 0; i < MAX_DETHITS; i++) { if (detHits[i].detpack == det) { detHits[i].player = NULL; detHits[i].detpack = NULL; detHits[i].time = 0; } } } void DetpackBlammoThink(gentity_t *ent) { int i = 0, lifetime = 3000; // how long (in msec) the shockwave lasts int knockback = 400; float curBlastRadius = 50.0*ent->count, radius = 0; vec3_t vTemp; trace_t tr; gentity_t *pl = NULL; if (ent->count++ > (lifetime*0.01)) { ClearThisDetpacksHits(ent); G_FreeEntity(ent); return; } // get a list of players within the blast radius at this time. // only hit the ones we can see from the center of the explosion. for (i=0; is.pos.trBase, ent->s.origin, vTemp); radius = VectorNormalize(vTemp); if ( (radius <= curBlastRadius) && !PlayerHitByDet(ent, pl) ) { // within the proper radius. do we have LOS to the player? trap_Trace (&tr, ent->s.origin, NULL, NULL, pl->s.pos.trBase, ent->s.number, MASK_SHOT ); if (tr.entityNum == i) { // oh yeah. you're gettin' hit. AddPlayerToDetHits(ent, pl); VectorMA(pl->client->ps.velocity, knockback, vTemp, pl->client->ps.velocity); // make sure the player goes up some if (pl->client->ps.velocity[2] < 100) { pl->client->ps.velocity[2] = 100; } if ( !pl->client->ps.pm_time ) { int t; t = knockback * 2; if ( t < 50 ) { t = 50; } if ( t > 200 ) { t = 200; } pl->client->ps.pm_time = t; pl->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; } } } // this was just for testing. looks pretty neat, though. /* else { VectorMA(ent->s.origin, curBlastRadius, vTemp, vTemp); G_TempEntity(vTemp, EV_FX_STEAM); } */ } } ent->nextthink = level.time + FRAMETIME; } void DetonateDetpack(gentity_t *ent) { // find all detpacks. the one whose parent is ent...blow up gentity_t *detpack = NULL; char *classname = BG_FindClassnameForHoldable(HI_DETPACK); if (!classname) { return; } while ((detpack = G_Find (detpack, FOFS(classname), classname)) != NULL) { if (detpack->parent == ent) { // found it. BLAMMO! // play explosion sound to all clients gentity_t *te = NULL; te = G_TempEntity( detpack->s.pos.trBase, EV_GLOBAL_SOUND ); te->s.eventParm = G_SoundIndex( "sound/weapons/explosions/detpakexplode.wav" );//cgs.media.detpackExplodeSound te->r.svFlags |= SVF_BROADCAST; //so we can't be blown up by things we're blowing up detpack->takedamage = 0; G_AddEvent(detpack, EV_DETPACK, 0); G_RadiusDamage( detpack->s.origin, detpack->parent, DETPACK_DAMAGE, DETPACK_RADIUS, detpack, DAMAGE_HALF_NOTLOS|DAMAGE_ALL_TEAMS, MOD_DETPACK ); // just turn the model invisible and let the entity think for a bit to deliver a shockwave //G_FreeEntity(detpack); detpack->classname = NULL; detpack->s.modelindex = 0; detpack->think = DetpackBlammoThink; detpack->count = 1; detpack->nextthink = level.time + FRAMETIME; return; } else if (detpack == ent) // if detpack == ent, we're blowing up this detpack cuz it's been sitting too long { detpack_shot(detpack, NULL, NULL, 0, 0); return; } } // hmm. couldn't find it. detpack = NULL; } #define SHIELD_HEALTH 250 #define SHIELD_HEALTH_DEC 10 // 25 seconds #define MAX_SHIELD_HEIGHT 254 #define MAX_SHIELD_HALFWIDTH 255 #define SHIELD_HALFTHICKNESS 4 #define SHIELD_PLACEDIST 64 static qhandle_t shieldAttachSound=0; static qhandle_t shieldActivateSound=0; static qhandle_t shieldDamageSound=0; //RPG-X: - RedTechie Added shild ZAPZPZAAAAAAppPPP sound! static qhandle_t shieldMurderSound=0; void ShieldRemove(gentity_t *self) { self->think = G_FreeEntity; self->nextthink = level.time + 100; // Play raising sound... G_AddEvent(self, EV_GENERAL_SOUND, shieldActivateSound); return; } // Count down the health of the shield. void ShieldThink(gentity_t *self) { self->s.eFlags &= ~(EF_ITEMPLACEHOLDER | EF_NODRAW); // self->health -= SHIELD_HEALTH_DEC; self->nextthink = level.time + 1000; if (self->health <= 0) { ShieldRemove(self); } return; } // The shield was damaged to below zero health. void ShieldDie(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod) { // Play damaging sound... G_AddEvent(self, EV_GENERAL_SOUND, shieldDamageSound); ShieldRemove(self); } // The shield had damage done to it. Make it flicker. void ShieldPain(gentity_t *self, gentity_t *attacker, int damage) { // Set the itemplaceholder flag to indicate the the shield drawing that the shield pain should be drawn. self->s.eFlags |= EF_ITEMPLACEHOLDER; self->think = ShieldThink; self->nextthink = level.time + 400; // Play damaging sound... G_AddEvent(self, EV_GENERAL_SOUND, shieldDamageSound); return; } // Try to turn the shield back on after a delay. void ShieldGoSolid(gentity_t *self) { trace_t tr; //gentity_t *other; //other = G_Spawn(); // see if we're valid //self->health--; if (self->health <= 0) { ShieldRemove(self); return; } trap_Trace (&tr, self->r.currentOrigin, self->r.mins, self->r.maxs, self->r.currentOrigin, self->s.number, CONTENTS_BODY ); if(tr.startsolid) { // gah, we can't activate yet self->nextthink = level.time + 200; self->think = ShieldGoSolid; trap_LinkEntity(self); } else { // get hard... huh-huh... self->r.contents = CONTENTS_SOLID; self->s.eFlags &= ~(/*EF_NODRAW | */EF_ITEMPLACEHOLDER); self->nextthink = level.time + 1000; self->think = ShieldThink; self->takedamage = qfalse;//RPG-X: - RedTechie use to be qtrue trap_LinkEntity(self); // Play raising sound... G_AddEvent(self, EV_GENERAL_SOUND, shieldActivateSound); } return; } // Turn the shield off to allow a friend to pass through. //RPG-X J2J EDIT here: void ShieldGoNotSolid(gentity_t *self) { // make the shield non-solid very briefly self->r.contents = CONTENTS_NONE; // self->s.eFlags |= EF_NODRAW; //Commenting this should make it look like the player passes through.. // nextthink needs to have a large enough interval to avoid excess accumulation of Activate messages self->nextthink = level.time + 200; self->think = ShieldGoSolid; self->takedamage = qfalse; trap_LinkEntity(self); // Play raising sound... G_AddEvent(self, EV_GENERAL_SOUND, shieldActivateSound); } // Somebody (a player) has touched the shield. See if it is a "friend". void ShieldTouch(gentity_t *self, gentity_t *other, trace_t *trace) { /* if (g_gametype.integer >= GT_TEAM) { // let teammates through // compare the parent's team to the "other's" team if (( self->parent->client) && (other->client)) { if ( self->s.otherEntityNum2 == other->client->sess.sessionTeam || other->client->sess.sessionClass == PC_ALPHAOMEGA22 ) { ShieldGoNotSolid(self); } } } else*/ {//let the person who dropped the shield through if (other->client->sess.sessionClass == PC_ADMIN ) { ShieldGoNotSolid(self); } //RPG-X:J2J Damage for shields else { other->flags |= EF_MOVER_STOP; //Attempt to not let non admins thru. //RPG-X: RedTechie - Added code for zap sound also a cvar to control damage a non admin walks into if cvar is set to 0 disables health from being drained (happens every 1 second) //if ( level.time >= level.message + 1000 ) { // level.message = level.time; G_AddEvent(self, EV_GENERAL_SOUND, shieldMurderSound);//RPG-X: RedTechie - ZAPtacular! sound to my ears if(rpg_forcefielddamage.integer != 0){ if(rpg_forcefielddamage.integer > 999){ rpg_forcefielddamage.integer = 999; } other->health -= rpg_forcefielddamage.integer; //RPG-X: RedTechie - Fixed free ent if medic revive on if(rpg_medicsrevive.integer == 1){ if(other->health <= 1){ other->client->ps.stats[STAT_WEAPONS] = ( 1 << WP_NONE ); other->client->ps.stats[STAT_HOLDABLE_ITEM] = HI_NONE; other->client->ps.stats[STAT_HEALTH] = other->health = 1; player_die (other, other, other, 1, MOD_FORCEFIELD); } }else{ if(other->health <= 1){ other->client->ps.stats[STAT_HEALTH] = other->health = 0; player_die (other, other, other, 100000, MOD_FORCEFIELD); } } } //} } } } // After a short delay, create the shield by expanding in all directions. void CreateShield(gentity_t *ent) { trace_t tr; vec3_t mins, maxs, end, posTraceEnd, negTraceEnd, start; int height, posWidth, negWidth, halfWidth = 0; qboolean xaxis; int paramData = 0; static int shieldID; // trace upward to find height of shield VectorCopy(ent->r.currentOrigin, end); end[2] += MAX_SHIELD_HEIGHT; trap_Trace (&tr, ent->r.currentOrigin, NULL, NULL, end, ent->s.number, MASK_SHOT ); height = (int)(MAX_SHIELD_HEIGHT * tr.fraction); // use angles to find the proper axis along which to align the shield VectorSet(mins, -SHIELD_HALFTHICKNESS, -SHIELD_HALFTHICKNESS, 0); VectorSet(maxs, SHIELD_HALFTHICKNESS, SHIELD_HALFTHICKNESS, height); VectorCopy(ent->r.currentOrigin, posTraceEnd); VectorCopy(ent->r.currentOrigin, negTraceEnd); if ((int)(ent->s.angles[YAW]) == 0) // shield runs along y-axis { posTraceEnd[1]+=MAX_SHIELD_HALFWIDTH; negTraceEnd[1]-=MAX_SHIELD_HALFWIDTH; xaxis = qfalse; } else // shield runs along x-axis { posTraceEnd[0]+=MAX_SHIELD_HALFWIDTH; negTraceEnd[0]-=MAX_SHIELD_HALFWIDTH; xaxis = qtrue; } // trace horizontally to find extend of shield // positive trace VectorCopy(ent->r.currentOrigin, start); start[2] += (height>>1); trap_Trace (&tr, start, 0, 0, posTraceEnd, ent->s.number, MASK_SHOT ); posWidth = MAX_SHIELD_HALFWIDTH * tr.fraction; // negative trace trap_Trace (&tr, start, 0, 0, negTraceEnd, ent->s.number, MASK_SHOT ); negWidth = MAX_SHIELD_HALFWIDTH * tr.fraction; // kef -- monkey with dimensions and place origin in center halfWidth = (posWidth + negWidth)>>1; if (xaxis) { ent->r.currentOrigin[0] = ent->r.currentOrigin[0] - negWidth + halfWidth; } else { ent->r.currentOrigin[1] = ent->r.currentOrigin[1] - negWidth + halfWidth; } ent->r.currentOrigin[2] += (height>>1); // set entity's mins and maxs to new values, make it solid, and link it if (xaxis) { VectorSet(ent->r.mins, -halfWidth, -SHIELD_HALFTHICKNESS, -(height>>1)); VectorSet(ent->r.maxs, halfWidth, SHIELD_HALFTHICKNESS, height>>1); } else { VectorSet(ent->r.mins, -SHIELD_HALFTHICKNESS, -halfWidth, -(height>>1)); VectorSet(ent->r.maxs, SHIELD_HALFTHICKNESS, halfWidth, height>>1); } ent->clipmask = MASK_SHOT; // Information for shield rendering. // xaxis - 1 bit // height - 0-254 8 bits // posWidth - 0-255 8 bits // negWidth - 0 - 255 8 bits paramData = (xaxis << 24) | (height << 16) | (posWidth << 8) | (negWidth); ent->s.time2 = paramData; if ( ent->s.otherEntityNum2 == TEAM_RED ) { ent->team = "1"; } else if ( ent->s.otherEntityNum2 == TEAM_BLUE ) { ent->team = "2"; } //RPG-X: - RedTechie no shield count down if ( g_pModSpecialties.integer != 0 ) { //ent->health = ceil(SHIELD_HEALTH*4*g_dmgmult.value); } else { ent->health = ceil(SHIELD_HEALTH*g_dmgmult.value); } ent->s.time = ent->health;//??? ent->pain = ShieldPain; ent->die = ShieldDie; ent->touch = ShieldTouch; ent->r.svFlags |= SVF_SHIELD_BBOX; // see if we're valid trap_Trace (&tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, ent->r.currentOrigin, ent->s.number, CONTENTS_BODY ); if (tr.startsolid) { // Something in the way! // make the shield non-solid very briefly ent->r.contents = CONTENTS_NONE; ent->s.eFlags |= EF_NODRAW; // nextthink needs to have a large enough interval to avoid excess accumulation of Activate messages ent->nextthink = level.time + 200; ent->think = ShieldGoSolid; ent->takedamage = qfalse; trap_LinkEntity(ent); } else { // Get solid. ent->r.contents = CONTENTS_PLAYERCLIP|CONTENTS_SHOTCLIP;//CONTENTS_SOLID; ent->nextthink = level.time + 1000; ent->think = ShieldThink; ent->takedamage = qfalse;//RPG-X: - RedTechie Use to be qtrue trap_LinkEntity(ent); // Play raising sound... G_AddEvent(ent, EV_GENERAL_SOUND, shieldActivateSound); } return; } qboolean PlaceShield(gentity_t *playerent) { static const gitem_t *shieldItem = NULL; gentity_t *shield = NULL; trace_t tr; vec3_t fwd, pos, dest, mins = {-16,-16, 0}, maxs = {16,16,16}; if (shieldAttachSound==0) { shieldAttachSound = G_SoundIndex("sound/weapons/detpacklatch.wav"); shieldActivateSound = G_SoundIndex("sound/movers/forceup.wav"); shieldDamageSound = G_SoundIndex("sound/ambience/spark5.wav"); shieldMurderSound = G_SoundIndex("sound/world/electro.wav"); //RPG-X: - RedTechie Added shild ZAP! sound shieldItem = BG_FindItemForHoldable(HI_SHIELD); } // can we place this in front of us? AngleVectors (playerent->client->ps.viewangles, fwd, NULL, NULL); fwd[2] = 0; VectorMA(playerent->client->ps.origin, SHIELD_PLACEDIST, fwd, dest); trap_Trace (&tr, playerent->client->ps.origin, mins, maxs, dest, playerent->s.number, MASK_SHOT ); if (tr.fraction > 0.9) {//room in front VectorCopy(tr.endpos, pos); // drop to floor VectorSet( dest, pos[0], pos[1], pos[2] - 4096 ); trap_Trace( &tr, pos, mins, maxs, dest, playerent->s.number, MASK_SOLID ); if ( !tr.startsolid && !tr.allsolid ) { // got enough room so place the portable shield shield = G_Spawn(); // Figure out what direction the shield is facing. if (fabs(fwd[0]) > fabs(fwd[1])) { // shield is north/south, facing east. shield->s.angles[YAW] = 0; } else { // shield is along the east/west axis, facing north shield->s.angles[YAW] = 90; } shield->think = CreateShield; shield->nextthink = level.time + 500; // power up after .5 seconds shield->parent = playerent; // Set team number. shield->s.otherEntityNum2 = playerent->client->sess.sessionTeam; shield->s.eType = ET_USEABLE; shield->s.modelindex = HI_SHIELD; // this'll be used in CG_Useable() for rendering. shield->classname = shieldItem->classname; shield->r.contents = CONTENTS_TRIGGER; shield->touch = 0; // using an item causes it to respawn shield->use = 0; //Use_Item; // allow to ride movers shield->s.groundEntityNum = tr.entityNum; G_SetOrigin( shield, tr.endpos ); shield->s.eFlags &= ~EF_NODRAW; shield->r.svFlags &= ~SVF_NOCLIENT; trap_LinkEntity (shield); // Play placing sound... G_AddEvent(shield, EV_GENERAL_SOUND, shieldAttachSound); return qtrue; } } // no room return qfalse; } //-------------------------------------------------------------- DECOY ACTIVITIES void DecoyThink(gentity_t *ent) { ent->s.apos =(ent->parent)->s.apos; // Update Current Rotation ent->nextthink = level.time + irandom(2000, 6000); // Next think between 2 & 8 seconds (ent->count) --; // Count Down if (ent->count<0) G_FreeEntity(ent); // Time To Erase The Ent } qboolean PlaceDecoy(gentity_t *ent) { gentity_t *decoy = NULL; static gitem_t *decoyItem = NULL; float detDistance = 80; // Distance the object will be placed from player trace_t tr; vec3_t fwd, right, up, end, mins = {-16,-16,-24}, maxs = {16,16,16}; if (decoyItem == NULL) { decoyItem = BG_FindItemForHoldable(HI_DECOY); } // can we place this in front of us? AngleVectors (ent->client->ps.viewangles, fwd, right, up); fwd[2] = 0; VectorMA(ent->client->ps.origin, detDistance, fwd, end); trap_Trace (&tr, ent->client->ps.origin, mins, maxs, end, ent->s.number, MASK_SHOT ); if ( !tr.allsolid && !tr.startsolid && tr.fraction > 0.9 ) { //--------------------------- SPAWN AND PLACE DECOY ON GROUND decoy = G_Spawn(); G_SpawnItem(decoy, decoyItem); // Generate it as an item, temporarly decoy->physicsBounce = 0.0f;//decoys are *not* bouncy VectorMA(ent->client->ps.origin, detDistance + mins[0], fwd, decoy->s.origin); decoy->r.mins[2] = mins[2];//keep it off the floor VectorNegate(fwd, fwd); // ??? What does this do?? vectoangles(fwd, decoy->s.angles); if ( !FinishSpawningDecoy(decoy, decoyItem - bg_itemlist) ) { return qfalse; // Drop to ground and trap to clients } decoy->s.clientNum = ent->client->ps.clientNum; //--------------------------- SPECIALIZED DECOY SETUP decoy->think = DecoyThink; decoy->count = 12; // about 1 minute before dissapear decoy->nextthink = level.time + 4000; // think after 4 seconds decoy->parent = ent; (decoy->s).eType = (ent->s).eType; // set to type PLAYER (decoy->s).eFlags= (ent->s).eFlags; (decoy->s).eFlags |= EF_ITEMPLACEHOLDER;// set the HOLOGRAM FLAG to ON decoy->s.weapon = ent->s.weapon; // get Player's Wepon Type // decoy->s.constantLight = 10 + (10 << 8) + (10 << 16) + (9 << 24); //decoy->s.pos.trBase[2] += 24; // shift up to adjust origin of body decoy->s.apos = ent->s.apos; // copy angle of player to decoy decoy->s.legsAnim = BOTH_STAND1; // Just standing TORSO_STAND decoy->s.torsoAnim = BOTH_STAND1; //--------------------------- WEAPON ADJUST if (decoy->s.weapon==WP_PHASER || decoy->s.weapon==WP_DERMAL_REGEN) decoy->s.weapon = WP_COMPRESSION_RIFLE; // The Phaser and Dreadnought (Arc Welder) weapons are rendered on the // client side differently, and cannot be used by the decoy return qtrue; // SUCCESS } else { return qfalse; // FAILURE: no room } } //-------------------------------------------------------------- DECOY ACTIVITIES void G_Rematerialize( gentity_t *ent ) { if ( ent->s.number == borgQueenClientNum ) { ent->client->teleportTime = level.time + ( 60 * 1000 ); ent->client->ps.stats[STAT_USEABLE_PLACED] = 60; } else { ent->client->teleportTime = level.time + ( 15 * 1000 ); ent->client->ps.stats[STAT_USEABLE_PLACED] = 15; } ent->flags &= ~FL_NOTARGET; ent->takedamage = qtrue; ent->r.contents = MASK_PLAYERSOLID; ent->s.eFlags &= ~EF_NODRAW; ent->client->ps.eFlags &= ~EF_NODRAW; TeleportPlayer( ent, ent->client->ps.origin, ent->client->ps.viewangles, TP_BORG ); //ent->client->ps.stats[STAT_USEABLE_PLACED] = 0; //take it away ent->client->ps.stats[STAT_HOLDABLE_ITEM] = 0; } void G_GiveHoldable( gclient_t *client, holdable_t item ) { gitem_t *holdable = BG_FindItemForHoldable( item ); client->ps.stats[STAT_HOLDABLE_ITEM] = holdable - bg_itemlist;//teleport spots should be on other side of map RegisterItem( holdable ); } /* ================ ClientEvents Events will be passed on to the clients for presentation, but any server game effects are handled here ================ */ void ClientEvents( gentity_t *ent, int oldEventSequence ) { int i; int event; gclient_t *client; int damage; client = ent->client; if ( oldEventSequence < client->ps.eventSequence - MAX_PS_EVENTS ) { oldEventSequence = client->ps.eventSequence - MAX_PS_EVENTS; } for ( i = oldEventSequence ; i < client->ps.eventSequence ; i++ ) { event = client->ps.events[ i & (MAX_PS_EVENTS-1) ]; switch ( event ) { case EV_FALL_MEDIUM: case EV_FALL_FAR: if ( ent->s.eType != ET_PLAYER ) { break; // not in the player model } if ( g_dmflags.integer & DF_NO_FALLING ) { break; } if ( rpg_selfdamage.integer != 0 ) { if ( event == EV_FALL_FAR ) { damage = 110; //10 -TiM : Make the falling more realistc! } else { damage = 90; //5 } } else { damage = 0; } ent->pain_debounce_time = level.time + 200; // no normal pain sound G_Damage (ent, NULL, NULL, NULL, NULL, damage, DAMAGE_ARMOR_PIERCING, MOD_FALLING); break; case EV_FIRE_WEAPON: FireWeapon( ent, qfalse ); break; case EV_ALT_FIRE: FireWeapon( ent, qtrue ); break; case EV_FIRE_EMPTY_PHASER: FireWeapon( ent, qfalse ); break; case EV_USE_ITEM1: // transporter //------------------------------------------------------- DROP FLAGS if ( ent->client->ps.powerups[PW_REDFLAG] ) { Team_ReturnFlag(TEAM_RED); } else if ( ent->client->ps.powerups[PW_BLUEFLAG] ) { Team_ReturnFlag(TEAM_BLUE); } ent->client->ps.powerups[PW_BLUEFLAG] = 0; ent->client->ps.powerups[PW_REDFLAG] = 0; //------------------------------------------------------- DROP FLAGS if ( ent->client->sess.sessionClass == PC_BORG ) { if ( !ent->client->ps.stats[STAT_USEABLE_PLACED] ) {//go into free-roaming mode gentity_t *tent; //FIXME: limit this to 10 seconds ent->flags |= FL_NOTARGET; ent->takedamage = qfalse; ent->r.contents = CONTENTS_PLAYERCLIP; ent->s.eFlags |= EF_NODRAW; ent->client->ps.eFlags |= EF_NODRAW; ent->client->ps.stats[STAT_USEABLE_PLACED] = 2; ent->client->teleportTime = level.time + 10000; tent = G_TempEntity( ent->client->ps.origin, EV_BORG_TELEPORT ); tent->s.clientNum = ent->s.clientNum; } else {//come out of free-roaming mode, teleport to current position G_Rematerialize( ent ); } } else// get rid of transporter and go to random spawnpoint { //vec3_t origin, angles; //gentity_t *tent; //SelectSpawnPoint( ent->client->ps.origin, origin, angles ); //if(TransDat[ent->client->ps.clientNum].pUsed == qfalse) //TiM: Since we purge the vectors each cycle. I'll save us some memory by using the vectors themselves as a check. if ( TransDat[ent->client->ps.clientNum].beamTime == 0 && VectorCompare( vec3_origin, TransDat[ent->client->ps.clientNum].storedCoord[TPT_PORTABLE].origin ) && VectorCompare( vec3_origin, TransDat[ent->client->ps.clientNum].storedCoord[TPT_PORTABLE].angles ) ) { VectorCopy( ent->client->ps.origin, TransDat[ent->client->ps.clientNum].storedCoord[TPT_PORTABLE].origin ); VectorCopy( ent->client->ps.viewangles, TransDat[ent->client->ps.clientNum].storedCoord[TPT_PORTABLE].angles ); //VectorCopy(ent->client->ps.origin, TransDat[ent->client->ps.clientNum].pTransCoord); //VectorCopy(ent->client->ps.viewangles, TransDat[ent->client->ps.clientNum].pTransCoordRot); //TransDat[ent->client->ps.clientNum].pUsed = qtrue; trap_SendServerCommand( ent-g_entities, va("chat \"Site to Site Transporter Location Confirmed.\nPress again to Energize.\"", Q_COLOR_ESCAPE)); //trap_SendConsoleCommand( EXEC_APPEND, va("echo Site to Site Transporter Location Confirmed.\necho Press again to Energize.") ); ent->client->ps.stats[STAT_HOLDABLE_ITEM] = BG_FindItemForHoldable( HI_TRANSPORTER ) - bg_itemlist; ent->client->ps.stats[STAT_USEABLE_PLACED] = 2; // = 1 break; } //TeleportPlayer( ent, TransDat[ent->client->ps.clientNum].pTransCoord, TransDat[ent->client->ps.clientNum].pTransCoordRot, TP_NORMAL ); if ( TransDat[ent->client->ps.clientNum].beamTime == 0 && level.time > ent->client->ps.powerups[PW_QUAD] ) { G_InitTransport( ent->client->ps.clientNum, TransDat[ent->client->ps.clientNum].storedCoord[TPT_PORTABLE].origin, TransDat[ent->client->ps.clientNum].storedCoord[TPT_PORTABLE].angles ); memset( &TransDat[ent->client->ps.clientNum].storedCoord[TPT_PORTABLE], 0, sizeof( TransDat[ent->client->ps.clientNum].storedCoord[TPT_PORTABLE]) ); } else { trap_SendServerCommand( ent-g_entities, va("chat \"Unable to comply. Already within transport cycle.\"", Q_COLOR_ESCAPE)); } ent->client->ps.stats[STAT_USEABLE_PLACED] = 0; if ( ent->client->sess.sessionClass == PC_ALPHAOMEGA22 ) { ent->client->teleportTime = level.time + ( 1 * 1000 ); // 15 * 1000 ent->client->ps.stats[STAT_USEABLE_PLACED] = 1; // = 1 } } break; case EV_USE_ITEM2: // medkit // New set of rules. You get either 100 health, or an extra 25, whichever is higher. // Give 1/4 health. ent->health += ent->client->ps.stats[STAT_MAX_HEALTH]*0.25; if (ent->health < ent->client->ps.stats[STAT_MAX_HEALTH]) { // If that doesn't bring us up to 100, make it go up to 100. ent->health = ent->client->ps.stats[STAT_MAX_HEALTH]; } else if (ent->health > ent->client->ps.stats[STAT_MAX_HEALTH]*2) { // Otherwise, 25 is all you get. Just make sure we don't go above 200. ent->health = ent->client->ps.stats[STAT_MAX_HEALTH]*2; } break; case EV_USE_ITEM3: // detpack // if we haven't placed it yet, place it if (0 == ent->client->ps.stats[STAT_USEABLE_PLACED]) { if ( PlaceDetpack(ent) ) { ent->client->ps.stats[STAT_USEABLE_PLACED] = 1; trap_SendServerCommand( ent-g_entities, "cp \"CHARGE PLACED\"" ); } else {//couldn't place it ent->client->ps.stats[STAT_HOLDABLE_ITEM] = (BG_FindItemForHoldable( HI_DETPACK ) - bg_itemlist); trap_SendServerCommand( ent-g_entities, "cp \"NO ROOM TO PLACE CHARGE\"" ); } } else { //turn off invincibility since you are going to do massive damage //ent->client->ps.powerups[PW_GHOST] = 0;//NOTE: this means he can respawn and get a detpack 10 seconds later // ok, we placed it earlier. blow it up. ent->client->ps.stats[STAT_USEABLE_PLACED] = 0; DetonateDetpack(ent); } break; case EV_USE_ITEM4: // portable shield if ( !PlaceShield(ent) ) // fixme if we fail, perhaps just spawn it as a pickup {//couldn't place it ent->client->ps.stats[STAT_HOLDABLE_ITEM] = (BG_FindItemForHoldable( HI_SHIELD ) - bg_itemlist); trap_SendServerCommand( ent-g_entities, "cp \"NO ROOM TO PLACE FORCE FIELD\"" ); } else { trap_SendServerCommand( ent-g_entities, "cp \"FORCE FIELD PLACED\"" ); //if ( ent->client->sess.sessionClass == PC_ADMIN ) // { // ent->client->teleportTime = level.time + ( 30 * 1000 ); // ent->client->ps.stats[STAT_USEABLE_PLACED] = 30; //} } break; case EV_USE_ITEM5: // decoy if ( !PlaceDecoy(ent) ) {//couldn't place it ent->client->ps.stats[STAT_HOLDABLE_ITEM] = (BG_FindItemForHoldable( HI_DECOY ) - bg_itemlist); trap_SendServerCommand( ent-g_entities, "cp \"NO ROOM TO PLACE DECOY\"" ); } else { trap_SendServerCommand( ent-g_entities, "cp \"DECOY PLACED\"" ); } break; default: break; } } } void BotTestSolid(vec3_t origin); /* ============== SendPendingPredictableEvents ============== */ void SendPendingPredictableEvents( playerState_t *ps ) { gentity_t *t; int event, seq; int extEvent; // if there are still events pending if ( ps->entityEventSequence < ps->eventSequence ) { // create a temporary entity for this event which is sent to everyone // except the client generated the event seq = ps->entityEventSequence & (MAX_PS_EVENTS-1); event = ps->events[ seq ] | ( ( ps->entityEventSequence & 3 ) << 8 ); // set external event to zero before calling BG_PlayerStateToEntityState extEvent = ps->externalEvent; ps->externalEvent = 0; // create temporary entity for event t = G_TempEntity( ps->origin, event ); BG_PlayerStateToEntityState( ps, &t->s, qtrue ); t->s.eType = ET_EVENTS + event; // send to everyone except the client who generated the event t->r.svFlags |= SVF_NOTSINGLECLIENT; t->r.singleClient = ps->clientNum; // set back external event ps->externalEvent = extEvent; } } /* ============== ClientThink This will be called once for each client frame, which will usually be a couple times for each server frame on fast clients. If "g_synchronousClients 1" is set, this will be called exactly once for each server frame, which makes for smooth demo recording. ============== */ void ClientThink_real( gentity_t *ent ) { gclient_t *client; pmove_t pm; vec3_t oldOrigin; int oldEventSequence; int msec; usercmd_t *ucmd; client = ent->client; // don't think if the client is not yet connected (and thus not yet spawned in) if (client->pers.connected != CON_CONNECTED) { return; } // mark the time, so the connection sprite can be removed ucmd = &ent->client->pers.cmd; // sanity check the command time to prevent speedup cheating if ( ucmd->serverTime > level.time + 200 ) { ucmd->serverTime = level.time + 200; // G_Printf("serverTime <<<<<\n" ); } if ( ucmd->serverTime < level.time - 1000 ) { ucmd->serverTime = level.time - 1000; // G_Printf("serverTime >>>>>\n" ); } msec = ucmd->serverTime - client->ps.commandTime; // following others may result in bad times, but we still want // to check for follow toggles if ( msec < 1 && client->sess.spectatorState != SPECTATOR_FOLLOW ) { return; } if ( msec > 200 ) { msec = 200; } // // check for exiting intermission // if ( level.intermissiontime ) { ClientIntermissionThink( client ); return; } // Don't move while under intro sequence. /*if (client->ps.introTime > level.time) { // Don't be visible either. ent->s.eFlags |= EF_NODRAW; SetClientViewAngle( ent, ent->s.angles ); ucmd->buttons = 0; ucmd->weapon = 0; // ucmd->angles[0] = ucmd->angles[1] = ucmd->angles[2] = 0; ucmd->forwardmove = ucmd->rightmove = ucmd->upmove = 0; // return; }*/ // spectators don't do much if ( client->sess.sessionTeam == TEAM_SPECTATOR || (client->ps.eFlags&EF_ELIMINATED) ) { if ( client->sess.spectatorState == SPECTATOR_SCOREBOARD ) { return; } SpectatorThink( ent, ucmd ); return; } // check for inactivity timer, but never drop the local client of a non-dedicated server if ( !ClientInactivityTimer( client ) ) { return; } //TiM - If we're null content... see what's up if ( ent->r.contents == CONTENTS_NONE ) { if ( !G_MoveBox( ent ) && client->ps.stats[STAT_HEALTH] >= 1 ) { ent->r.contents = CONTENTS_BODY; } else { ent->r.contents = CONTENTS_NONE ; } } // clear the rewards if time /*if ( level.time > client->rewardTime ) { client->ps.eFlags &= ~EF_AWARD_MASK; }*/ //RPG-X: Checked to see if medics revive is on if so do as following if(rpg_medicsrevive.integer == 1){ if ( client->noclip ) { client->ps.pm_type = PM_NOCLIP; } else if ( client->ps.stats[STAT_HEALTH] == 1 ) { client->ps.pm_type = PM_DEAD; } else { client->ps.pm_type = PM_NORMAL; } }else{ if ( client->noclip ) { client->ps.pm_type = PM_NOCLIP; } else if ( client->ps.stats[STAT_HEALTH] <= 0 ) { client->ps.pm_type = PM_DEAD; } else { client->ps.pm_type = PM_NORMAL; } } //RPG-X: J2J & Phenix - For the gravity ent if(client->SpecialGrav != qtrue) { client->ps.gravity = g_gravity.value; } // set speed client->ps.speed = g_speed.value; if ( client->ps.powerups[PW_HASTE] ) { client->ps.speed *= 1.5; } else if (client->ps.powerups[PW_FLIGHT] ) {//flying around client->ps.speed *= 1.3; } else if ( client->ps.stats[STAT_HEALTH] <= 20 ) { client->ps.speed *= 0.55; } if (( client->ps.powerups[PW_EVOSUIT] ) && ( client->ps.gravity == 0 )) {//Evosuit time.. RPG-X | Phenix | 8/8/2004 client->ps.speed *= 1.3; } //RPG-X: Redtechie - n00bie stay.....good boy! if ( client->sess.sessionClass == PC_N00B ){ client->ps.speed = 0; } if ( client->sess.sessionClass == PC_HEAVY ) { client->ps.speed *= 0.75; } if ( client->sess.sessionClass == PC_BORG ) { if ( BG_BorgTransporting( &client->ps ) ) { client->ps.speed *= 1.5; } else if ( !BG_BorgTransporting( &client->ps ) ) { client->ps.speed *= 0.75; } } //TiM : SP Style Transporter. :) //first check to see if we should be beaming if ( level.time < TransDat[client->ps.clientNum].beamTime ) { //Hold players in place. Do we need?? //client->ps.speed = 0.0; //if we're past the mid point of each materialization cycle, make it //so bullets and other players will pass thru the transportee. :) if ( (level.time > TransDat[client->ps.clientNum].beamTime - 6000) && ( level.time < TransDat[client->ps.clientNum].beamTime - 2000 ) ) { if ( client->ps.stats[STAT_HEALTH] > 1 ) { ent->r.contents = CONTENTS_NONE; } //ent->r.contents = CONTENTS_CORPSE; } else { if ( client->ps.stats[STAT_HEALTH] > 1 ) { ent->r.contents = MASK_PLAYERSOLID; //ent->r.contents = CONTENTS_BODY; } } //If we're half-way thru the cycle, teleport the player now if ( level.time > TransDat[client->ps.clientNum].beamTime - 4000 && !TransDat[client->ps.clientNum].beamed ) { TeleportPlayer( ent, TransDat[client->ps.clientNum].currentCoord.origin, TransDat[client->ps.clientNum].currentCoord.angles, TP_TRI_TP ); TransDat[client->ps.clientNum].beamed = qtrue; } } else { //all done, let's reset :) if ( TransDat[client->ps.clientNum].beamTime > 0 ) { TransDat[client->ps.clientNum].beamTime = 0; client->ps.powerups[PW_BEAM_OUT] = 0; client->ps.powerups[PW_QUAD] = 0; TransDat[client->ps.clientNum].beamed = qfalse; memset( &TransDat[client->ps.clientNum].currentCoord, 0, sizeof( TransDat[client->ps.clientNum].currentCoord.origin ) ); } } //TiM : Freeze their movement if they're halfway through a transport cycle if ( level.time < TransDat[client->ps.clientNum].beamTime && level.time > TransDat[client->ps.clientNum].beamTime - 4000 ) { vec3_t endPoint; trace_t tr; VectorSet( endPoint, client->ps.origin[0], client->ps.origin[1], client->ps.origin[2] - 48 ); //Do a trace down. If we're near ground, just re-enable gravity. Else we we get weird animations O_o trap_Trace( &tr, client->ps.origin, NULL, NULL, endPoint, client->ps.clientNum, CONTENTS_SOLID ); if ( tr.fraction == 1.0 ) { client->ps.gravity = 0; client->ps.velocity[2] = 0; } client->ps.speed = 0; client->ps.velocity[0] = client->ps.velocity[1] = 0.0; } // set up for pmove oldEventSequence = client->ps.eventSequence; memset (&pm, 0, sizeof(pm)); pm.ps = &client->ps; pm.cmd = *ucmd; if ( pm.ps->pm_type == PM_DEAD ) { pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; } else { pm.tracemask = MASK_PLAYERSOLID; } pm.trace = trap_Trace; pm.pointcontents = trap_PointContents; pm.debugLevel = g_debugMove.integer; pm.noFootsteps = ( g_dmflags.integer & DF_NO_FOOTSTEPS ) > 0; pm.pModDisintegration = g_pModDisintegration.integer > 0; VectorCopy( client->ps.origin, oldOrigin ); // perform a pmove Pmove (&pm); // save results of pmove if ( ent->client->ps.eventSequence != oldEventSequence ) { ent->eventTime = level.time; } BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue ); SendPendingPredictableEvents( &ent->client->ps ); // use the snapped origin for linking so it matches client predicted versions VectorCopy( ent->s.pos.trBase, ent->r.currentOrigin ); VectorCopy (pm.mins, ent->r.mins); VectorCopy (pm.maxs, ent->r.maxs); ent->waterlevel = pm.waterlevel; ent->watertype = pm.watertype; // execute client events ClientEvents( ent, oldEventSequence ); if ( pm.useEvent ) { //TODO: Use TryUse( ent ); } // link entity now, after any personal teleporters have been used trap_LinkEntity (ent); // if ( !ent->client->noclip ) { G_TouchTriggers( ent ); // } // NOTE: now copy the exact origin over otherwise clients can be snapped into solid VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); //test for solid areas in the AAS file BotTestSolid(ent->r.currentOrigin); // touch other objects ClientImpacts( ent, &pm ); // save results of triggers and client events if (ent->client->ps.eventSequence != oldEventSequence) { ent->eventTime = level.time; } // swap and latch button actions client->oldbuttons = client->buttons; client->buttons = ucmd->buttons; client->latched_buttons |= client->buttons & ~client->oldbuttons; // check for respawning if ( client->ps.stats[STAT_HEALTH] <= 0 ) { // wait for the attack button to be pressed if ( level.time > client->respawnTime ) { // forcerespawn is to prevent users from waiting out powerups //RPG-X: RedTechie - No forcerespawn /*if ( g_forcerespawn.integer > 0 && ( level.time - client->respawnTime ) > g_forcerespawn.integer * 1000 ) { respawn( ent ); return; }*/ // pressing attack or use is the normal respawn method if ( ucmd->buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) ) { respawn( ent ); return; } //in assimilation and elimination, always force respawn if ( level.time - client->respawnTime > 1300 && //NOTE: when killed, client->respawnTime = level.time + 1700, so this is 3000 ms ( g_pModAssimilation.integer || g_pModElimination.integer ) ) { respawn( ent ); return; } } return; } // perform once-a-second actions ClientTimerActions( ent, msec ); if ( ent->client->teleportTime > 0 && ent->client->teleportTime < level.time ) { if ( ent->client->sess.sessionClass == PC_BORG ) { if ( BG_BorgTransporting( &ent->client->ps ) ) { G_Rematerialize( ent ); } else { G_GiveHoldable( ent->client, HI_TRANSPORTER ); ent->client->ps.stats[STAT_USEABLE_PLACED] = 0; ent->client->teleportTime = 0; } } else if ( ent->client->sess.sessionClass == PC_DEMO ) { G_GiveHoldable( ent->client, HI_DETPACK ); ent->client->ps.stats[STAT_USEABLE_PLACED] = 0; ent->client->teleportTime = 0; } else if ( ent->client->sess.sessionClass == PC_ALPHAOMEGA22 ) { G_GiveHoldable( ent->client, HI_TRANSPORTER ); ent->client->ps.stats[STAT_USEABLE_PLACED] = 0; ent->client->teleportTime = 0; } else if ( ent->client->sess.sessionClass == PC_ADMIN ) { G_GiveHoldable( ent->client, HI_SHIELD ); ent->client->ps.stats[STAT_USEABLE_PLACED] = 0; ent->client->teleportTime = 0; } } } /* ================== ClientThink A new command has arrived from the client ================== */ void ClientThink( int clientNum ) { gentity_t *ent; ent = g_entities + clientNum; trap_GetUsercmd( clientNum, &ent->client->pers.cmd ); // mark the time we got info, so we can display the // phone jack if they don't get any for a while ent->client->lastCmdTime = level.time; if ( !g_synchronousClients.integer ) { ClientThink_real( ent ); } } void G_RunClient( gentity_t *ent ) { if ( !g_synchronousClients.integer ) { return; } ent->client->pers.cmd.serverTime = level.time; ClientThink_real( ent ); } /* ================== SpectatorClientEndFrame ================== */ void SpectatorClientEndFrame( gentity_t *ent ) { gclient_t *cl; // if we are doing a chase cam or a remote view, grab the latest info if ( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) { int clientNum; clientNum = ent->client->sess.spectatorClient; // team follow1 and team follow2 go to whatever clients are playing if ( clientNum == -1 ) { clientNum = level.follow1; } else if ( clientNum == -2 ) { clientNum = level.follow2; } if ( clientNum >= 0 ) { cl = &level.clients[ clientNum ]; if ( cl->pers.connected == CON_CONNECTED && cl->sess.sessionTeam != TEAM_SPECTATOR ) { ent->client->ps = cl->ps; ent->client->ps.pm_flags |= PMF_FOLLOW; return; } else { // drop them to free spectators unless they are dedicated camera followers if ( ent->client->sess.spectatorClient >= 0 ) { ent->client->sess.spectatorState = SPECTATOR_FREE; ClientBegin( ent->client - level.clients, qfalse ); } } } } if ( ent->client->sess.spectatorState == SPECTATOR_SCOREBOARD ) { ent->client->ps.pm_flags |= PMF_SCOREBOARD; } else { ent->client->ps.pm_flags &= ~PMF_SCOREBOARD; } } /* ============== ClientEndFrame Called at the end of each server frame for each connected client A fast client will have multiple ClientThink for each ClientEdFrame, while a slow client may have multiple ClientEndFrame between ClientThink. ============== */ void ClientEndFrame( gentity_t *ent ) { int i; clientPersistant_t *pers; if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR || (ent->client->ps.eFlags&EF_ELIMINATED) ) { SpectatorClientEndFrame( ent ); ent->client->noclip = qtrue; return; } pers = &ent->client->pers; // turn off any expired powerups for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { if ( ent->client->ps.powerups[ i ] < level.time ) { ent->client->ps.powerups[ i ] = 0; } } // save network bandwidth #if 0 if ( !g_synchronousClients->integer && ent->client->ps.pm_type == PM_NORMAL ) { // FIXME: this must change eventually for non-sync demo recording VectorClear( ent->client->ps.viewangles ); } #endif // // If the end of unit layout is displayed, don't give // the player any normal movement attributes // if ( level.intermissiontime ) { return; } // burn from lava, etc P_WorldEffects (ent); // apply all the damage taken this frame P_DamageFeedback (ent); // add the EF_CONNECTION flag if we haven't gotten commands recently if ( level.time - ent->client->lastCmdTime > 1000 ) { ent->s.eFlags |= EF_CONNECTION; } else { ent->s.eFlags &= ~EF_CONNECTION; } ent->client->ps.stats[STAT_HEALTH] = ent->health; // FIXME: get rid of ent->health... G_SetClientSound (ent); // set the latest infor BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue ); SendPendingPredictableEvents( &ent->client->ps ); }