/** * @brief Refills jetpack fuel. * @param self * @param triggerEvent Trigger an event when fuel was modified. * @return Whether fuel was modified. */ qboolean G_RefillFuel( gentity_t *self, qboolean triggerEvent ) { if ( !self || !self->client ) { return qfalse; } // needs a human with jetpack if ( self->client->ps.persistant[ PERS_TEAM ] != TEAM_HUMANS || !BG_InventoryContainsUpgrade( UP_JETPACK, self->client->ps.stats ) ) { return qfalse; } if ( self->client->ps.stats[ STAT_FUEL ] != JETPACK_FUEL_MAX ) { self->client->ps.stats[ STAT_FUEL ] = JETPACK_FUEL_MAX; self->client->lastFuelRefillTime = level.time; if ( triggerEvent ) { G_AddEvent( self, EV_FUEL_REFILL, 0 ); } return qtrue; } else { return qfalse; } }
/* =============== CG_SetUIVars Set some cvars used by the UI =============== */ static void CG_SetUIVars( void ) { int i; char carriageCvar[ MAX_TOKEN_CHARS ]; *carriageCvar = 0; //determine what the player is carrying for ( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ ) { if ( BG_InventoryContainsWeapon( i, cg.snap->ps.stats ) && BG_FindPurchasableForWeapon( i ) ) { strcat( carriageCvar, va( "W%d ", i ) ); } } for ( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) { if ( BG_InventoryContainsUpgrade( i, cg.snap->ps.stats ) && BG_FindPurchasableForUpgrade( i ) ) { strcat( carriageCvar, va( "U%d ", i ) ); } } strcat( carriageCvar, "$" ); trap_Cvar_Set( "ui_carriage", carriageCvar ); trap_Cvar_Set( "ui_stages", va( "%d %d", cgs.alienStage, cgs.humanStage ) ); }
/* =============== CG_UpgradeSelectable =============== */ static qboolean CG_UpgradeSelectable( upgrade_t upgrade ) { if( !BG_InventoryContainsUpgrade( upgrade, cg.snap->ps.stats ) ) return qfalse; return qtrue; }
/* =============== trigger_equipment_match =============== */ qboolean trigger_equipment_match( gentity_t *self, gentity_t *activator ) { int i = 0; if( !activator ) return qfalse; //if there is no equipment list all equipment triggers (stupid case) if( self->wTriggers[ i ] == WP_NONE && self->uTriggers[ i ] == UP_NONE ) return qtrue; else { //otherwise check against the lists for( i = 0; self->wTriggers[ i ] != WP_NONE; i++ ) { if( BG_InventoryContainsWeapon( self->wTriggers[ i ], activator->client->ps.stats ) ) return qtrue; } for( i = 0; self->uTriggers[ i ] != UP_NONE; i++ ) { if( BG_InventoryContainsUpgrade( self->uTriggers[ i ], activator->client->ps.stats ) ) return qtrue; } } return qfalse; }
/* =============== poisonCloud =============== */ void poisonCloud( gentity_t *ent ) { int entityList[ MAX_GENTITIES ]; vec3_t range = { LEVEL1_PCLOUD_RANGE, LEVEL1_PCLOUD_RANGE, LEVEL1_PCLOUD_RANGE }; vec3_t mins, maxs; int i, num; gentity_t *humanPlayer; trace_t tr; VectorAdd( ent->client->ps.origin, range, maxs ); VectorSubtract( ent->client->ps.origin, range, mins ); G_UnlaggedOn( ent, ent->client->ps.origin, LEVEL1_PCLOUD_RANGE ); num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); for( i = 0; i < num; i++ ) { humanPlayer = &g_entities[ entityList[ i ] ]; if( humanPlayer->client && humanPlayer->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) { if( BG_InventoryContainsUpgrade( UP_LIGHTARMOUR, humanPlayer->client->ps.stats ) ) continue; if( BG_InventoryContainsUpgrade( UP_BATTLESUIT, humanPlayer->client->ps.stats ) ) continue; trap_Trace( &tr, muzzle, NULL, NULL, humanPlayer->s.origin, humanPlayer->s.number, MASK_SHOT ); //can't see target from here if( tr.entityNum == ENTITYNUM_WORLD ) continue; if( !( humanPlayer->client->ps.stats[ STAT_STATE ] & SS_POISONCLOUDED ) ) { humanPlayer->client->ps.stats[ STAT_STATE ] |= SS_POISONCLOUDED; humanPlayer->client->lastPoisonCloudedTime = level.time; humanPlayer->client->lastPoisonCloudedClient = ent; trap_SendServerCommand( humanPlayer->client->ps.clientNum, "poisoncloud" ); } } } G_UnlaggedOff( ); }
/* =============== trigger_equipment_trigger =============== */ void trigger_equipment_trigger(gentity_t * self, gentity_t * activator) { int i = 0; //sanity check if(!activator->client) return; if(activator->client->ps.stats[STAT_PTEAM] != PTE_HUMANS) return; self->activator = activator; if(self->nextthink) return; // can't retrigger until the wait is over //if there is no equipment list all equipment triggers (stupid case) if(self->wTriggers[i] == WP_NONE && self->wTriggers[i] == UP_NONE) G_UseTargets(self, activator); else { //otherwise check against the lists for(i = 0; self->wTriggers[i] != WP_NONE; i++) { if(BG_InventoryContainsWeapon(self->wTriggers[i], activator->client->ps.stats)) { G_UseTargets(self, activator); return; } } for(i = 0; self->uTriggers[i] != UP_NONE; i++) { if(BG_InventoryContainsUpgrade(self->uTriggers[i], activator->client->ps.stats)) { G_UseTargets(self, activator); return; } } } if(self->wait > 0) { self->think = multi_wait; self->nextthink = level.time + (self->wait + self->random * crandom()) * 1000; } else { // we can't just remove (self) here, because this is a touch function // called while looping through area links... self->touch = 0; self->nextthink = level.time + FRAMETIME; self->think = G_FreeEntity; } }
/* ================= G_GiveClientMaxAmmo ================= */ void G_GiveClientMaxAmmo( gentity_t *ent, qboolean buyingEnergyAmmo ) { int i; int maxAmmo, maxClips; qboolean weaponType, restoredAmmo = qfalse; for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ ) { if( buyingEnergyAmmo ) weaponType = BG_FindUsesEnergyForWeapon( i ); else weaponType = !BG_FindUsesEnergyForWeapon( i ); if( BG_InventoryContainsWeapon( i, ent->client->ps.stats ) && weaponType && !BG_FindInfinteAmmoForWeapon( i ) && !BG_WeaponIsFull( i, ent->client->ps.stats, ent->client->ps.ammo, ent->client->ps.powerups ) ) { BG_FindAmmoForWeapon( i, &maxAmmo, &maxClips ); if( buyingEnergyAmmo ) { G_AddEvent( ent, EV_RPTUSE_SOUND, 0 ); if( BG_InventoryContainsUpgrade( UP_BATTPACK, ent->client->ps.stats ) ) maxAmmo = (int)( (float)maxAmmo * BATTPACK_MODIFIER ); } else if ( BG_InventoryContainsUpgrade( UP_BATTPACK, ent->client->ps.stats ) ) maxClips = (int)( (float)maxAmmo * BATTPACK_MODIFIER ); BG_PackAmmoArray( i, ent->client->ps.ammo, ent->client->ps.powerups, maxAmmo, maxClips ); restoredAmmo = qtrue; } } if( restoredAmmo ) G_ForceWeaponChange( ent, ent->client->ps.weapon ); }
/* ================= G_GiveClientMaxAmmo ================= */ void G_GiveClientMaxAmmo( gentity_t *ent, qboolean buyingEnergyAmmo ) { int i, maxAmmo, maxClips; qboolean restoredAmmo = qfalse, restoredEnergy = qfalse; //TODO find a solution to move dependency of ent->s.number and &ent->eventTime outside this function playerState_t *ps=&ent->client->ps; for ( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ ) { qboolean energyWeapon; energyWeapon = BG_Weapon( i )->usesEnergy; if ( !BG_InventoryContainsWeapon( i, ps->stats ) || BG_Weapon( i )->infiniteAmmo || BG_WeaponIsFull( i, ps->stats, ps->ammo, ps->clips ) || ( buyingEnergyAmmo && !energyWeapon ) ) { continue; } maxAmmo = BG_Weapon( i )->maxAmmo; maxClips = BG_Weapon( i )->maxClips; // Apply battery pack modifier if ( energyWeapon && BG_InventoryContainsUpgrade( UP_BATTPACK, ps->stats ) ) { maxAmmo *= BATTPACK_MODIFIER; restoredEnergy = qtrue; } ps->ammo = maxAmmo; ps->clips = maxClips; restoredAmmo = qtrue; } if ( restoredAmmo ) { G_ForceWeaponChange( ps, ps->weapon ); } if ( restoredEnergy ) { G_AddPlayerEvent( ps, EV_RPTUSE_SOUND, 0, ent->s.number, &ent->eventTime ); } }
/* =============== FireWeapon3 =============== */ void FireWeapon3( gentity_t *ent ) { if( ent->client ) { // set aiming directions AngleVectors( ent->client->ps.viewangles, forward, right, up ); CalcMuzzlePoint( ent, forward, right, up, muzzle ); } else { AngleVectors( ent->s.angles2, forward, right, up ); VectorCopy( ent->s.pos.trBase, muzzle ); } // fire the specific weapon switch( ent->s.weapon ) { case WP_ALEVEL3_UPG: bounceBallFire( ent ); break; case WP_ABUILD: slowBlobFire( ent ); break; case WP_ALEVEL2_UPG: bounceBallFire_level2( ent ); break; case WP_ALEVEL5: Prickles( ent ); break; case WP_ALEVEL4: // FireBreath_tyrant( ent ); break; case WP_MASS_DRIVER: if(g_humanStage.integer == S5 && BG_InventoryContainsUpgrade( UP_BATTPACK, ent->client->ps.stats )) { massDriverFire2( ent ); } break; default: break; } }
/* ================= G_GiveClientMaxAmmo ================= */ void G_GiveClientMaxAmmo( gentity_t *ent, qboolean buyingEnergyAmmo ) { int i, maxAmmo, maxClips; qboolean restoredAmmo = qfalse, restoredEnergy = qfalse; for ( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ ) { qboolean energyWeapon; energyWeapon = BG_Weapon( i )->usesEnergy; if ( !BG_InventoryContainsWeapon( i, ent->client->ps.stats ) || BG_Weapon( i )->infiniteAmmo || BG_WeaponIsFull( i, ent->client->ps.stats, ent->client->ps.Ammo, ent->client->ps.clips ) || ( buyingEnergyAmmo && !energyWeapon ) ) { continue; } maxAmmo = BG_Weapon( i )->maxAmmo; maxClips = BG_Weapon( i )->maxClips; // Apply battery pack modifier if ( energyWeapon && BG_InventoryContainsUpgrade( UP_BATTPACK, ent->client->ps.stats ) ) { maxAmmo *= BATTPACK_MODIFIER; restoredEnergy = qtrue; } ent->client->ps.Ammo = maxAmmo; ent->client->ps.clips = maxClips; restoredAmmo = qtrue; } if ( restoredAmmo ) { G_ForceWeaponChange( ent, ent->client->ps.weapon ); } if ( restoredEnergy ) { G_AddEvent( ent, EV_RPTUSE_SOUND, 0 ); } }
/* =================== CG_DrawItemSelectText =================== */ void CG_DrawItemSelectText( rectDef_t *rect, float scale, int textStyle ) { int x, w; char *name; float *color; color = CG_FadeColor( cg.weaponSelectTime, WEAPON_SELECT_TIME ); if( !color ) return; trap_R_SetColor( color ); // draw the selected name if( cg.weaponSelect <= 32 ) { if( cg_weapons[ cg.weaponSelect ].registered && BG_InventoryContainsWeapon( cg.weaponSelect, cg.snap->ps.stats ) ) { if( ( name = cg_weapons[ cg.weaponSelect ].humanName ) ) { w = CG_Text_Width( name, scale, 0 ); x = rect->x + rect->w / 2; CG_Text_Paint( x - w / 2, rect->y + rect->h, scale, color, name, 0, 0, textStyle ); } } } else if( cg.weaponSelect > 32 ) { if( cg_upgrades[ cg.weaponSelect - 32 ].registered && BG_InventoryContainsUpgrade( cg.weaponSelect - 32, cg.snap->ps.stats ) ) { if( ( name = cg_upgrades[ cg.weaponSelect - 32 ].humanName ) ) { w = CG_Text_Width( name, scale, 0 ); x = rect->x + rect->w / 2; CG_Text_Paint( x - w / 2, rect->y + rect->h, scale, color, name, 0, 0, textStyle ); } } } trap_R_SetColor( NULL ); }
static void CG_Rocket_DFCMArmouryBuyUpgrade( int handle, const char *data ) { upgrade_t upgrade = (upgrade_t) atoi( Info_ValueForKey( data, "1" ) ); const char *Class = ""; const char *Icon = ""; const char *action = ""; playerState_t *ps = &cg.snap->ps; int credits = ps->persistant[ PERS_CREDIT ]; if( BG_InventoryContainsUpgrade( upgrade, cg.predictedPlayerState.stats ) ){ Class = "active"; action = va( "onClick='Cmd.exec(\"sell %s\")'", BG_Upgrade( upgrade )->name ); //Check mark icon. UTF-8 encoding of \uf00c Icon = "<icon class=\"current\">\xEF\x80\x8C</icon>"; } else if ( !BG_UpgradeUnlocked( upgrade ) || BG_UpgradeDisabled( upgrade ) ) { Class = "locked"; //Padlock icon. UTF-8 encoding of \uf023 Icon = "<icon>\xEF\x80\xA3</icon>"; } else if ( !trap_Cvar_VariableIntegerValue( "x_freeUpgrades" ) && BG_Upgrade( upgrade )->price > credits ) { Class = "expensive"; //$1 bill icon. UTF-8 encoding of \uf0d6 Icon = "<icon>\xEF\x83\x96</icon>"; } else { Class = "available"; action = va( "onClick='Cmd.exec(\"buy +%s\")'", BG_Upgrade( upgrade )->name ); } Rocket_DataFormatterFormattedData( handle, va( "<button class='armourybuy %s' onMouseover='Events.pushevent(\"setDS armouryBuyList upgrades %s\", event)' %s>%s<img src='/%s'/></button>", Class, Info_ValueForKey( data, "2" ), action, Icon, CG_GetShaderNameFromHandle( cg_upgrades[ upgrade ].upgradeIcon)), false ); }
/* =============== sensor_equipment_match =============== */ qboolean sensor_equipment_match( gentity_t *self, gentity_t *activator ) { int i = 0; if ( !activator ) { return qfalse; } if ( self->conditions.weapons[ i ] == WP_NONE && self->conditions.upgrades[ i ] == UP_NONE ) { //if there is no equipment list all equipment triggers for the old behavior of target_equipment, but not the new or different one return qtrue; } else { //otherwise check against the lists for ( i = 0; self->conditions.weapons[ i ] != WP_NONE; i++ ) { if ( BG_InventoryContainsWeapon( self->conditions.weapons[ i ], activator->client->ps.stats ) ) { return qtrue; } } for ( i = 0; self->conditions.upgrades[ i ] != UP_NONE; i++ ) { if ( BG_InventoryContainsUpgrade( self->conditions.upgrades[ i ], activator->client->ps.stats ) ) { return qtrue; } } } return qfalse; }
void G_BotThink( gentity_t *self ) { char buf[MAX_STRING_CHARS]; usercmd_t *botCmdBuffer; vec3_t nudge; botRouteTarget_t routeTarget; self->botMind->cmdBuffer = self->client->pers.cmd; botCmdBuffer = &self->botMind->cmdBuffer; //reset command buffer usercmdClearButtons( botCmdBuffer->buttons ); // for nudges, e.g. spawn blocking nudge[0] = botCmdBuffer->doubleTap != dtType_t::DT_NONE ? botCmdBuffer->forwardmove : 0; nudge[1] = botCmdBuffer->doubleTap != dtType_t::DT_NONE ? botCmdBuffer->rightmove : 0; nudge[2] = botCmdBuffer->doubleTap != dtType_t::DT_NONE ? botCmdBuffer->upmove : 0; botCmdBuffer->forwardmove = 0; botCmdBuffer->rightmove = 0; botCmdBuffer->upmove = 0; botCmdBuffer->doubleTap = dtType_t::DT_NONE; //acknowledge recieved server commands //MUST be done while ( trap_BotGetServerCommand( self->client->ps.clientNum, buf, sizeof( buf ) ) ); BotSearchForEnemy( self ); BotFindClosestBuildings( self ); BotFindDamagedFriendlyStructure( self ); BotCalculateStuckTime( self ); //use medkit when hp is low if ( self->entity->Get<HealthComponent>()->Health() < BOT_USEMEDKIT_HP && BG_InventoryContainsUpgrade( UP_MEDKIT, self->client->ps.stats ) ) { BG_ActivateUpgrade( UP_MEDKIT, self->client->ps.stats ); } //infinite funds cvar if ( g_bot_infinite_funds.integer ) { G_AddCreditToClient( self->client, HUMAN_MAX_CREDITS, true ); } //hacky ping fix self->client->ps.ping = rand() % 50 + 50; if ( !self->botMind->behaviorTree ) { Log::Warn( "NULL behavior tree" ); return; } // always update the path corridor if ( self->botMind->goal.inuse ) { BotTargetToRouteTarget( self, self->botMind->goal, &routeTarget ); trap_BotUpdatePath( self->s.number, &routeTarget, &self->botMind->nav ); //BotClampPos( self ); } self->botMind->behaviorTree->run( self, ( AIGenericNode_t * ) self->botMind->behaviorTree ); // if we were nudged... VectorAdd( self->client->ps.velocity, nudge, self->client->ps.velocity ); self->client->pers.cmd = self->botMind->cmdBuffer; }
/* ================== TeamplayInfoMessage Format: clientNum location health weapon upgrade ================== */ void TeamplayInfoMessage( gentity_t *ent ) { char entry[ 19 ], string[ 1143 ]; int i, j; int team, stringlength; int sent = 0; gentity_t *player; gclient_t *cl; upgrade_t upgrade = UP_NONE; int curWeaponClass = WP_NONE; // sends weapon for humans, class for aliens char *tmp; if ( !g_allowTeamOverlay.integer ) { return; } if ( !ent->client->pers.teamInfo ) { return; } if ( ent->client->pers.teamSelection == TEAM_NONE ) { if ( ent->client->sess.spectatorState == SPECTATOR_FREE || ent->client->sess.spectatorClient < 0 ) { return; } team = g_entities[ ent->client->sess.spectatorClient ].client-> pers.teamSelection; } else { team = ent->client->pers.teamSelection; } string[ 0 ] = '\0'; stringlength = 0; for ( i = 0; i < MAX_CLIENTS; i++ ) { player = g_entities + i; cl = player->client; if ( ent == player || !cl || team != cl->pers.teamSelection || !player->inuse ) { continue; } if ( cl->sess.spectatorState != SPECTATOR_NOT ) { curWeaponClass = WP_NONE; upgrade = UP_NONE; } else if ( cl->pers.teamSelection == TEAM_HUMANS ) { curWeaponClass = cl->ps.weapon; if ( BG_InventoryContainsUpgrade( UP_BATTLESUIT, cl->ps.stats ) ) { upgrade = UP_BATTLESUIT; } else if ( BG_InventoryContainsUpgrade( UP_JETPACK, cl->ps.stats ) ) { upgrade = UP_JETPACK; } else if ( BG_InventoryContainsUpgrade( UP_BATTPACK, cl->ps.stats ) ) { upgrade = UP_BATTPACK; } else if ( BG_InventoryContainsUpgrade( UP_HELMET, cl->ps.stats ) ) { upgrade = UP_HELMET; } else if ( BG_InventoryContainsUpgrade( UP_LIGHTARMOUR, cl->ps.stats ) ) { upgrade = UP_LIGHTARMOUR; } else { upgrade = UP_NONE; } } else if ( cl->pers.teamSelection == TEAM_ALIENS ) { curWeaponClass = cl->ps.stats[ STAT_CLASS ]; upgrade = UP_NONE; } tmp = va( "%i %i %i %i", player->client->pers.location, player->client->ps.stats[ STAT_HEALTH ] < 1 ? 0 : player->client->ps.stats[ STAT_HEALTH ], curWeaponClass, upgrade ); if ( !strcmp( ent->client->pers.cinfo[ i ], tmp ) ) { continue; } Q_strncpyz( ent->client->pers.cinfo[ i ], tmp, sizeof( ent->client->pers.cinfo[ i ] ) ); Com_sprintf( entry, sizeof( entry ), " %i %s", i, tmp ); j = strlen( entry ); if ( stringlength + j >= sizeof( string ) ) { break; } strcpy( string + stringlength, entry ); stringlength += j; sent++; } if ( !sent ) { return; } trap_SendServerCommand( ent - g_entities, va( "tinfo%s", string ) ); }
/* ================== TeamplayInfoMessage Format: clientNum location health weapon upgrade ================== */ void TeamplayInfoMessage( gentity_t *ent ) { char entry[ 24 ]; char string[ ( MAX_CLIENTS - 1 ) * ( sizeof( entry ) - 1 ) + 1 ]; int i, j; int team, stringlength; gentity_t *player; gclient_t *cl; upgrade_t upgrade = UP_NONE; int curWeaponClass = WP_NONE; // sends weapon for humans, class for aliens int health = 0; if ( !g_allowTeamOverlay.integer ) { return; } if ( !ent->client->pers.teamInfo ) { return; } if ( ent->client->pers.team == TEAM_NONE ) { if ( ent->client->sess.spectatorState == SPECTATOR_FREE || ent->client->sess.spectatorClient < 0 ) { return; } team = g_entities[ ent->client->sess.spectatorClient ].client-> pers.team; } else { team = ent->client->pers.team; } string[ 0 ] = '\0'; stringlength = 0; for ( i = 0; i < level.maxclients; i++ ) { player = g_entities + i; cl = player->client; if ( ent == player || !cl || team != cl->pers.team || !player->inuse ) { continue; } // only update if changed since last time if ( cl->pers.infoChangeTime <= ent->client->pers.teamInfo ) { continue; } if ( cl->sess.spectatorState != SPECTATOR_NOT ) { curWeaponClass = WP_NONE; upgrade = UP_NONE; } else if ( cl->pers.team == TEAM_HUMANS ) { curWeaponClass = cl->ps.weapon; if ( BG_InventoryContainsUpgrade( UP_BATTLESUIT, cl->ps.stats ) ) { upgrade = UP_BATTLESUIT; } else if ( BG_InventoryContainsUpgrade( UP_JETPACK, cl->ps.stats ) ) { upgrade = UP_JETPACK; } else if ( BG_InventoryContainsUpgrade( UP_RADAR, cl->ps.stats ) ) { upgrade = UP_RADAR; } else if ( BG_InventoryContainsUpgrade( UP_LIGHTARMOUR, cl->ps.stats ) ) { upgrade = UP_LIGHTARMOUR; } else { upgrade = UP_NONE; } health = static_cast<int>( std::ceil( Entities::HealthOf(player) ) ); } else if ( cl->pers.team == TEAM_ALIENS ) { curWeaponClass = cl->ps.stats[ STAT_CLASS ]; upgrade = UP_NONE; health = static_cast<int>( std::ceil( Entities::HealthOf(player) ) ); } if( team == TEAM_ALIENS ) // aliens don't have upgrades { Com_sprintf( entry, sizeof( entry ), " %i %i %i %i %i", i, cl->pers.location, health, curWeaponClass, cl->pers.credit ); } else { Com_sprintf( entry, sizeof( entry ), " %i %i %i %i %i %i", i, cl->pers.location, health, curWeaponClass, cl->pers.credit, upgrade ); } j = strlen( entry ); // this should not happen if entry and string sizes are correct if ( stringlength + j >= (int) sizeof( string ) ) { break; } strcpy( string + stringlength, entry ); stringlength += j; } if( string[ 0 ] ) { trap_SendServerCommand( ent - g_entities, va( "tinfo%s", string ) ); ent->client->pers.teamInfo = level.time; } }
/* =============== CG_HumanText =============== */ static void CG_HumanText( char *text, playerState_t *ps ) { const char *name; upgrade_t upgrade = UP_NONE; if ( cg.weaponSelect < 32 ) { name = cg_weapons[ cg.weaponSelect ].humanName; } else { name = cg_upgrades[ cg.weaponSelect - 32 ].humanName; upgrade = (upgrade_t) ( cg.weaponSelect - 32 ); } if ( !ps->ammo && !ps->clips && !BG_Weapon( ps->weapon )->infiniteAmmo ) { //no ammo switch ( ps->weapon ) { case WP_MACHINEGUN: case WP_CHAINGUN: case WP_SHOTGUN: case WP_FLAMER: Q_strcat( text, MAX_TUTORIAL_TEXT, _( "Find an Armoury for more ammo\n" ) ); break; case WP_LAS_GUN: case WP_PULSE_RIFLE: case WP_MASS_DRIVER: case WP_LUCIFER_CANNON: Q_strcat( text, MAX_TUTORIAL_TEXT, _( "Find an Armoury, Reactor, or Repeater for more ammo\n" ) ); break; default: break; } } else { switch ( ps->weapon ) { case WP_BLASTER: case WP_MACHINEGUN: case WP_SHOTGUN: case WP_LAS_GUN: case WP_CHAINGUN: case WP_PULSE_RIFLE: case WP_FLAMER: Q_strcat( text, MAX_TUTORIAL_TEXT, va( _( "Press %s to fire the %s\n" ), CG_KeyNameForCommand( "+attack" ), _( BG_Weapon( ps->weapon )->humanName ) ) ); break; case WP_MASS_DRIVER: Q_strcat( text, MAX_TUTORIAL_TEXT, va( _( "Press %s to fire the %s\n" ), CG_KeyNameForCommand( "+attack" ), _( BG_Weapon( ps->weapon )->humanName ) ) ); Q_strcat( text, MAX_TUTORIAL_TEXT, va( _( "Hold %s to zoom\n" ), CG_KeyNameForCommand( "+attack2" ) ) ); break; case WP_PAIN_SAW: Q_strcat( text, MAX_TUTORIAL_TEXT, va( _( "Hold %s to activate the %s\n" ), CG_KeyNameForCommand( "+attack" ), _( BG_Weapon( ps->weapon )->humanName ) ) ); break; case WP_LUCIFER_CANNON: Q_strcat( text, MAX_TUTORIAL_TEXT, va( _( "Hold and release %s to fire a charged shot\n" ), CG_KeyNameForCommand( "+attack" ) ) ); Q_strcat( text, MAX_TUTORIAL_TEXT, va( _( "Press %s to fire the %s\n" ), CG_KeyNameForCommand( "+attack2" ), _( BG_Weapon( ps->weapon )->humanName ) ) ); break; case WP_HBUILD: CG_HumanCkitText( text, ps ); break; default: break; } } if ( upgrade == UP_NONE || ( upgrade > UP_NONE && BG_Upgrade( upgrade )->usable ) ) { Q_strcat( text, MAX_TUTORIAL_TEXT, va( _( "Press %s to use the %s\n" ), CG_KeyNameForCommand( "+useitem" ), name ) ); } if ( ps->stats[ STAT_HEALTH ] <= 35 && BG_InventoryContainsUpgrade( UP_MEDKIT, ps->stats ) ) { Q_strcat( text, MAX_TUTORIAL_TEXT, va( _( "Press %s to use your %s\n" ), CG_KeyNameForCommand( "itemact medkit" ), _( BG_Upgrade( UP_MEDKIT )->humanName ) ) ); } switch ( cg.nearUsableBuildable ) { case BA_H_ARMOURY: Q_strcat( text, MAX_TUTORIAL_TEXT, va( _( "Press %s to buy equipment upgrades at the %s\n" ), CG_KeyNameForCommand( "+activate" ), _( BG_Buildable( cg.nearUsableBuildable )->humanName ) ) ); break; case BA_NONE: break; default: Q_strcat( text, MAX_TUTORIAL_TEXT, va( _( "Press %s to use the %s\n" ), CG_KeyNameForCommand( "+activate" ), _( BG_Buildable( cg.nearUsableBuildable )->humanName ) ) ); break; } Q_strcat( text, MAX_TUTORIAL_TEXT, va( _( "Press %s and any direction to sprint\n" ), CG_KeyNameForCommand( "+sprint" ) ) ); if ( BG_InventoryContainsUpgrade( UP_FIREBOMB, ps->stats ) || BG_InventoryContainsUpgrade( UP_GRENADE, ps->stats ) ) { Q_strcat( text, MAX_TUTORIAL_TEXT, va( _( "Press %s to throw a grenade\n" ), CG_KeyNameForCommand( "itemact grenade" ) )); } }
/* =========== ClientUserInfoChanged Called from ClientConnect when the player first connects and directly by the server system when the player updates a userinfo variable. The game can override any of the settings and call trap_SetUserinfo if desired. ============ */ void ClientUserinfoChanged( int clientNum ) { gentity_t *ent; int teamTask, teamLeader, health; char *s; char model[ MAX_QPATH ]; char buffer[ MAX_QPATH ]; char filename[ MAX_QPATH ]; char oldname[ MAX_NAME_LENGTH ]; char newname[ MAX_NAME_LENGTH ]; char err[ MAX_STRING_CHARS ]; qboolean revertName = qfalse; qboolean showRenameMsg = qtrue; gclient_t *client; char c1[ MAX_INFO_STRING ]; char c2[ MAX_INFO_STRING ]; char userinfo[ MAX_INFO_STRING ]; team_t team; ent = g_entities + clientNum; client = ent->client; trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); // check for malformed or illegal info strings if( !Info_Validate(userinfo) ) strcpy( userinfo, "\\name\\badinfo" ); // check for local client s = Info_ValueForKey( userinfo, "ip" ); if( !strcmp( s, "localhost" ) ) client->pers.localClient = qtrue; // check the item prediction s = Info_ValueForKey( userinfo, "cg_predictItems" ); if( !atoi( s ) ) client->pers.predictItemPickup = qfalse; else client->pers.predictItemPickup = qtrue; // set name Q_strncpyz( oldname, client->pers.netname, sizeof( oldname ) ); s = Info_ValueForKey( userinfo, "name" ); ClientCleanName( s, newname, sizeof( newname ) ); if( strcmp( oldname, newname ) ) { if( !strlen( oldname ) && client->pers.connected != CON_CONNECTED ) showRenameMsg = qfalse; // in case we need to revert and there's no oldname ClientCleanName( va( "%s", client->pers.netname ), oldname, sizeof( oldname ) ); if( g_newbieNumbering.integer ) { if( !strcmp( newname, "UnnamedPlayer" ) ) Q_strncpyz( newname, G_NextNewbieName( ent ), sizeof( newname ) ); if( !strcmp( oldname, "UnnamedPlayer" ) ) Q_strncpyz( oldname, G_NextNewbieName( ent ), sizeof( oldname ) ); } if( client->pers.muted ) { trap_SendServerCommand( ent - g_entities, "print \"You cannot change your name while you are muted\n\"" ); revertName = qtrue; } else if( client->pers.nameChangeTime && ( level.time - client->pers.nameChangeTime ) <= ( g_minNameChangePeriod.value * 1000 ) ) { trap_SendServerCommand( ent - g_entities, va( "print \"Name change spam protection (g_minNameChangePeriod = %d)\n\"", g_minNameChangePeriod.integer ) ); revertName = qtrue; } else if( g_maxNameChanges.integer > 0 && client->pers.nameChanges >= g_maxNameChanges.integer ) { trap_SendServerCommand( ent - g_entities, va( "print \"Maximum name changes reached (g_maxNameChanges = %d)\n\"", g_maxNameChanges.integer ) ); revertName = qtrue; } else if( !G_admin_name_check( ent, newname, err, sizeof( err ) ) ) { trap_SendServerCommand( ent - g_entities, va( "print \"%s\n\"", err ) ); revertName = qtrue; } else if( client->pers.nlocked ) { trap_SendServerCommand( ent - g_entities, "print \"Your name is locked, you can no longer rename.\n\"" ); revertName = qtrue; } if( revertName ) { Q_strncpyz( client->pers.netname, oldname, sizeof( client->pers.netname ) ); Info_SetValueForKey( userinfo, "name", oldname ); trap_SetUserinfo( clientNum, userinfo ); } else { Q_strncpyz( client->pers.netname, newname, sizeof( client->pers.netname ) ); Info_SetValueForKey( userinfo, "name", newname ); trap_SetUserinfo( clientNum, userinfo ); if( client->pers.connected == CON_CONNECTED ) { client->pers.nameChangeTime = level.time; client->pers.nameChanges++; } } } if( client->sess.sessionTeam == TEAM_SPECTATOR ) { if( client->sess.spectatorState == SPECTATOR_SCOREBOARD ) Q_strncpyz( client->pers.netname, "scoreboard", sizeof( client->pers.netname ) ); } if( client->pers.connected >= CON_CONNECTING && showRenameMsg ) { if( strcmp( oldname, client->pers.netname ) ) { trap_SendServerCommand( -1, va( "print \"%s" S_COLOR_WHITE " renamed to %s^7\n\"", oldname, client->pers.netname ) ); if( g_decolourLogfiles.integer) { char decoloured[ MAX_STRING_CHARS ] = ""; if( g_decolourLogfiles.integer == 1 ) { Com_sprintf( decoloured, sizeof(decoloured), " (\"%s^7\" -> \"%s^7\")", oldname, client->pers.netname ); G_DecolorString( decoloured, decoloured ); G_LogPrintfColoured( "ClientRename: %i [%s] (%s) \"%s^7\" -> \"%s^7\"%s\n", clientNum, client->pers.ip, client->pers.guid, oldname, client->pers.netname, decoloured ); } else { G_LogPrintf( "ClientRename: %i [%s] (%s) \"%s^7\" -> \"%s^7\"%s\n", clientNum, client->pers.ip, client->pers.guid, oldname, client->pers.netname, decoloured ); } } else { G_LogPrintf( "ClientRename: %i [%s] (%s) \"%s^7\" -> \"%s^7\"\n", clientNum, client->pers.ip, client->pers.guid, oldname, client->pers.netname ); } G_admin_namelog_update( client, qfalse ); } } // set max health health = atoi( Info_ValueForKey( userinfo, "handicap" ) ); client->pers.maxHealth = health; if( client->pers.maxHealth < 1 || client->pers.maxHealth > 100 ) client->pers.maxHealth = 100; //hack to force a client update if the config string does not change between spawning if( client->pers.classSelection == PCL_NONE ) client->pers.maxHealth = 0; // set model if( client->ps.stats[ STAT_PCLASS ] == PCL_HUMAN && BG_InventoryContainsUpgrade( UP_BATTLESUIT, client->ps.stats ) ) { Com_sprintf( buffer, MAX_QPATH, "%s/%s", BG_FindModelNameForClass( PCL_HUMAN_BSUIT ), BG_FindSkinNameForClass( PCL_HUMAN_BSUIT ) ); } else if( client->pers.classSelection == PCL_NONE ) { //This looks hacky and frankly it is. The clientInfo string needs to hold different //model details to that of the spawning class or the info change will not be //registered and an axis appears instead of the player model. There is zero chance //the player can spawn with the battlesuit, hence this choice. Com_sprintf( buffer, MAX_QPATH, "%s/%s", BG_FindModelNameForClass( PCL_HUMAN_BSUIT ), BG_FindSkinNameForClass( PCL_HUMAN_BSUIT ) ); } else { Com_sprintf( buffer, MAX_QPATH, "%s/%s", BG_FindModelNameForClass( client->pers.classSelection ), BG_FindSkinNameForClass( client->pers.classSelection ) ); } Q_strncpyz( model, buffer, sizeof( model ) ); //don't bother setting model type if spectating if( client->pers.classSelection != PCL_NONE ) { //model segmentation Com_sprintf( filename, sizeof( filename ), "models/players/%s/animation.cfg", BG_FindModelNameForClass( client->pers.classSelection ) ); if( G_NonSegModel( filename ) ) client->ps.persistant[ PERS_STATE ] |= PS_NONSEGMODEL; else client->ps.persistant[ PERS_STATE ] &= ~PS_NONSEGMODEL; } // wallwalk follow s = Info_ValueForKey( userinfo, "cg_wwFollow" ); if( atoi( s ) ) client->ps.persistant[ PERS_STATE ] |= PS_WALLCLIMBINGFOLLOW; else client->ps.persistant[ PERS_STATE ] &= ~PS_WALLCLIMBINGFOLLOW; // wallwalk toggle s = Info_ValueForKey( userinfo, "cg_wwToggle" ); if( atoi( s ) ) client->ps.persistant[ PERS_STATE ] |= PS_WALLCLIMBINGTOGGLE; else client->ps.persistant[ PERS_STATE ] &= ~PS_WALLCLIMBINGTOGGLE; // teamInfo s = Info_ValueForKey( userinfo, "teamoverlay" ); if( ! *s || atoi( s ) != 0 ) client->pers.teamInfo = qtrue; else client->pers.teamInfo = qfalse; s = Info_ValueForKey( userinfo, "cg_unlagged" ); if( !s[0] || atoi( s ) != 0 ) client->pers.useUnlagged = qtrue; else client->pers.useUnlagged = qfalse; // team task (0 = none, 1 = offence, 2 = defence) teamTask = atoi( Info_ValueForKey( userinfo, "teamtask" ) ); // team Leader (1 = leader, 0 is normal player) teamLeader = client->sess.teamLeader; // colors strcpy( c1, Info_ValueForKey( userinfo, "color1" ) ); strcpy( c2, Info_ValueForKey( userinfo, "color2" ) ); team = client->pers.teamSelection; // send over a subset of the userinfo keys so other clients can // print scoreboards, display models, and play custom sounds Com_sprintf( userinfo, sizeof( userinfo ), "n\\%s\\t\\%i\\model\\%s\\hmodel\\%s\\c1\\%s\\c2\\%s\\" "hc\\%i\\w\\%i\\l\\%i\\tt\\%d\\" "tl\\%d\\ig\\%16s", client->pers.netname, team, model, model, c1, c2, client->pers.maxHealth, client->sess.wins, client->sess.losses, teamTask, teamLeader, BG_ClientListString( &client->sess.ignoreList ) ); trap_SetConfigstring( CS_PLAYERS + clientNum, userinfo ); /*G_LogPrintf( "ClientUserinfoChanged: %i %s\n", clientNum, userinfo );*/ }
/* =============== CG_HumanText =============== */ static void CG_HumanText( char *text, playerState_t *ps ) { char *name; int ammo, clips; upgrade_t upgrade = UP_NONE; if( cg.weaponSelect <= 32 ) name = cg_weapons[ cg.weaponSelect ].humanName; else if( cg.weaponSelect > 32 ) { name = cg_upgrades[ cg.weaponSelect - 32 ].humanName; upgrade = cg.weaponSelect - 32; } BG_UnpackAmmoArray( ps->weapon, &ps->ammo, ps->powerups, &ammo, &clips ); if( !ammo && !clips && !BG_FindInfinteAmmoForWeapon( ps->weapon ) ) { //no ammo switch( ps->weapon ) { case WP_MACHINEGUN: case WP_CHAINGUN: case WP_SHOTGUN: case WP_LAUNCHER: case WP_ROCKET_LAUNCHER: Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Find an Armoury and press %s for more ammo\n", CG_KeyNameForCommand( "buy ammo" ) ) ); break; case WP_LAS_GUN: case WP_MASS_DRIVER: Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Find a Reactor or Repeater and press %s for more ammo\n", CG_KeyNameForCommand( "buy ammo" ) ) ); break; default: break; } } else { switch( ps->weapon ) { case WP_LAUNCHER: Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Press %s to fire the %s\n", CG_KeyNameForCommand( "+attack" ), BG_FindHumanNameForWeapon( ps->weapon ) ) ); Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Press %s to fire incendiary grenade\n", CG_KeyNameForCommand( "+button5" ) ) ); break; case WP_PISTOL: case WP_MACHINEGUN: case WP_SHOTGUN: case WP_LAS_GUN: case WP_CHAINGUN: case WP_MASS_DRIVER: case WP_ROCKET_LAUNCHER: Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Press %s to fire the %s\n", CG_KeyNameForCommand( "+attack" ), BG_FindHumanNameForWeapon( ps->weapon ) ) ); Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Hold %s to zoom\n", CG_KeyNameForCommand( "+button5" ) ) ); break; case WP_HBUILD: case WP_HBUILD2: CG_HumanCkitText( text, ps ); break; default: break; } } Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Press %s and ", CG_KeyNameForCommand( "weapprev" ) ) ); Q_strcat( text, MAX_TUTORIAL_TEXT, va( "%s to select an upgrade\n", CG_KeyNameForCommand( "weapnext" ) ) ); if( upgrade == UP_NONE || ( upgrade > UP_NONE && BG_FindUsableForUpgrade( upgrade ) ) ) { Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Press %s to use the %s\n", CG_KeyNameForCommand( "+button2" ), name ) ); } if( ps->stats[ STAT_HEALTH ] <= 35 && BG_InventoryContainsUpgrade( UP_MEDKIT, ps->stats ) ) { Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Press %s to use your %s\n", CG_KeyNameForCommand( "itemact medkit" ), BG_FindHumanNameForUpgrade( UP_MEDKIT ) ) ); } Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Press %s to use a structure\n", CG_KeyNameForCommand( "+button7" ) ) ); }
static AIValue_t haveUpgrade( gentity_t *self, const AIValue_t *params ) { int upgrade = AIUnBoxInt( params[ 0 ] ); return AIBoxInt( !BG_UpgradeIsActive( upgrade, self->client->ps.stats ) && BG_InventoryContainsUpgrade( upgrade, self->client->ps.stats ) ); }
/* ================== ClientTimerActions Actions that happen once a second ================== */ void ClientTimerActions( gentity_t *ent, int msec ) { gclient_t *client; usercmd_t *ucmd; int aForward, aRight; ucmd = &ent->client->pers.cmd; aForward = abs( ucmd->forwardmove ); aRight = abs( ucmd->rightmove ); client = ent->client; client->time100 += msec; client->time1000 += msec; client->time10000 += msec; while ( client->time100 >= 100 ) { client->time100 -= 100; //if not trying to run then not trying to sprint if( aForward <= 64 ) client->ps.stats[ STAT_STATE ] &= ~SS_SPEEDBOOST; if( BG_InventoryContainsUpgrade( UP_JETPACK, client->ps.stats ) && BG_UpgradeIsActive( UP_JETPACK, client->ps.stats ) ) client->ps.stats[ STAT_STATE ] &= ~SS_SPEEDBOOST; if( ( client->ps.stats[ STAT_STATE ] & SS_SPEEDBOOST ) && ucmd->upmove >= 0 ) { //subtract stamina if( BG_InventoryContainsUpgrade( UP_LIGHTARMOUR, client->ps.stats ) ) client->ps.stats[ STAT_STAMINA ] -= STAMINA_LARMOUR_TAKE; else client->ps.stats[ STAT_STAMINA ] -= STAMINA_SPRINT_TAKE; if( client->ps.stats[ STAT_STAMINA ] < -MAX_STAMINA ) client->ps.stats[ STAT_STAMINA ] = -MAX_STAMINA; } if( ( aForward <= 64 && aForward > 5 ) || ( aRight <= 64 && aRight > 5 ) ) { //restore stamina client->ps.stats[ STAT_STAMINA ] += STAMINA_WALK_RESTORE; if( client->ps.stats[ STAT_STAMINA ] > MAX_STAMINA ) client->ps.stats[ STAT_STAMINA ] = MAX_STAMINA; } else if( aForward <= 5 && aRight <= 5 ) { //restore stamina faster client->ps.stats[ STAT_STAMINA ] += STAMINA_STOP_RESTORE; if( client->ps.stats[ STAT_STAMINA ] > MAX_STAMINA ) client->ps.stats[ STAT_STAMINA ] = MAX_STAMINA; } //client is charging up for a pounce if( client->ps.weapon == WP_ALEVEL3 || client->ps.weapon == WP_ALEVEL3_UPG ) { int pounceSpeed = 0; if( client->ps.weapon == WP_ALEVEL3 ) pounceSpeed = LEVEL3_POUNCE_SPEED; else if( client->ps.weapon == WP_ALEVEL3_UPG ) pounceSpeed = LEVEL3_POUNCE_UPG_SPEED; if( client->ps.stats[ STAT_MISC ] < pounceSpeed && ucmd->buttons & BUTTON_ATTACK2 ) client->ps.stats[ STAT_MISC ] += ( 100.0f / (float)LEVEL3_POUNCE_CHARGE_TIME ) * pounceSpeed; if( !( ucmd->buttons & BUTTON_ATTACK2 ) ) { if( client->ps.stats[ STAT_MISC ] > 0 ) { client->allowedToPounce = qtrue; client->pouncePayload = client->ps.stats[ STAT_MISC ]; } client->ps.stats[ STAT_MISC ] = 0; } if( client->ps.stats[ STAT_MISC ] > pounceSpeed ) client->ps.stats[ STAT_MISC ] = pounceSpeed; } //client is charging up for a... charge if( client->ps.weapon == WP_ALEVEL4 ) { if( client->ps.stats[ STAT_MISC ] < LEVEL4_CHARGE_TIME && ucmd->buttons & BUTTON_ATTACK2 && !client->charging ) { client->charging = qfalse; //should already be off, just making sure client->ps.stats[ STAT_STATE ] &= ~SS_CHARGING; if( ucmd->forwardmove > 0 ) { //trigger charge sound...is quite annoying //if( client->ps.stats[ STAT_MISC ] <= 0 ) // G_AddEvent( ent, EV_LEV4_CHARGE_PREPARE, 0 ); client->ps.stats[ STAT_MISC ] += (int)( 100 * (float)LEVEL4_CHARGE_CHARGE_RATIO ); if( client->ps.stats[ STAT_MISC ] > LEVEL4_CHARGE_TIME ) client->ps.stats[ STAT_MISC ] = LEVEL4_CHARGE_TIME; } else client->ps.stats[ STAT_MISC ] = 0; } if( !( ucmd->buttons & BUTTON_ATTACK2 ) || client->charging || client->ps.stats[ STAT_MISC ] == LEVEL4_CHARGE_TIME ) { if( client->ps.stats[ STAT_MISC ] > LEVEL4_MIN_CHARGE_TIME ) { client->ps.stats[ STAT_MISC ] -= 100; if( client->charging == qfalse ) G_AddEvent( ent, EV_LEV4_CHARGE_START, 0 ); client->charging = qtrue; client->ps.stats[ STAT_STATE ] |= SS_CHARGING; //if the charger has stopped moving take a chunk of charge away if( VectorLength( client->ps.velocity ) < 64.0f || aRight ) client->ps.stats[ STAT_MISC ] = client->ps.stats[ STAT_MISC ] / 2; //can't charge backwards if( ucmd->forwardmove < 0 ) client->ps.stats[ STAT_MISC ] = 0; } else client->ps.stats[ STAT_MISC ] = 0; if( client->ps.stats[ STAT_MISC ] <= 0 ) { client->ps.stats[ STAT_MISC ] = 0; client->charging = qfalse; client->ps.stats[ STAT_STATE ] &= ~SS_CHARGING; } } } //client is charging up an lcannon if( client->ps.weapon == WP_LUCIFER_CANNON ) { int ammo; BG_UnpackAmmoArray( WP_LUCIFER_CANNON, client->ps.ammo, client->ps.powerups, &ammo, NULL ); if( client->ps.stats[ STAT_MISC ] < LCANNON_TOTAL_CHARGE && ucmd->buttons & BUTTON_ATTACK ) client->ps.stats[ STAT_MISC ] += ( 100.0f / LCANNON_CHARGE_TIME ) * LCANNON_TOTAL_CHARGE; if( client->ps.stats[ STAT_MISC ] > LCANNON_TOTAL_CHARGE ) client->ps.stats[ STAT_MISC ] = LCANNON_TOTAL_CHARGE; if( client->ps.stats[ STAT_MISC ] > ( ammo * LCANNON_TOTAL_CHARGE ) / 10 ) client->ps.stats[ STAT_MISC ] = ammo * LCANNON_TOTAL_CHARGE / 10; } switch( client->ps.weapon ) { case WP_ABUILD: case WP_ABUILD2: case WP_HBUILD: case WP_HBUILD2: //set validity bit on buildable if( ( client->ps.stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT ) > BA_NONE ) { int dist = BG_FindBuildDistForClass( ent->client->ps.stats[ STAT_PCLASS ] ); vec3_t dummy; if( G_itemFits( ent, (buildable_t)(client->ps.stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT), dist, dummy ) == IBE_NONE ) client->ps.stats[ STAT_BUILDABLE ] |= SB_VALID_TOGGLEBIT; else client->ps.stats[ STAT_BUILDABLE ] &= ~SB_VALID_TOGGLEBIT; } //update build timer if( client->ps.stats[ STAT_MISC ] > 0 ) client->ps.stats[ STAT_MISC ] -= 100; if( client->ps.stats[ STAT_MISC ] < 0 ) client->ps.stats[ STAT_MISC ] = 0; break; default: break; } if( client->ps.stats[ STAT_STATE ] & SS_MEDKIT_ACTIVE ) { int remainingStartupTime = MEDKIT_STARTUP_TIME - ( level.time - client->lastMedKitTime ); if( remainingStartupTime < 0 ) { if( ent->health < ent->client->ps.stats[ STAT_MAX_HEALTH ] && ent->client->medKitHealthToRestore && ent->client->ps.pm_type != PM_DEAD ) { ent->client->medKitHealthToRestore--; ent->health++; } else ent->client->ps.stats[ STAT_STATE ] &= ~SS_MEDKIT_ACTIVE; } else { if( ent->health < ent->client->ps.stats[ STAT_MAX_HEALTH ] && ent->client->medKitHealthToRestore && ent->client->ps.pm_type != PM_DEAD ) { //partial increase if( level.time > client->medKitIncrementTime ) { ent->client->medKitHealthToRestore--; ent->health++; client->medKitIncrementTime = level.time + ( remainingStartupTime / MEDKIT_STARTUP_SPEED ); } } else ent->client->ps.stats[ STAT_STATE ] &= ~SS_MEDKIT_ACTIVE; } } } while( client->time1000 >= 1000 ) { client->time1000 -= 1000; //client is poison clouded if( client->ps.stats[ STAT_STATE ] & SS_POISONCLOUDED ) G_Damage( ent, client->lastPoisonCloudedClient, client->lastPoisonCloudedClient, NULL, NULL, LEVEL1_PCLOUD_DMG, 0, MOD_LEVEL1_PCLOUD ); //client is poisoned if( client->ps.stats[ STAT_STATE ] & SS_POISONED ) { int i; int seconds = ( ( level.time - client->lastPoisonTime ) / 1000 ) + 1; int damage = ALIEN_POISON_DMG, damage2 = 0; for( i = 0; i < seconds; i++ ) { if( i == seconds - 1 ) damage2 = damage; damage *= ALIEN_POISON_DIVIDER; } damage = damage2 - damage; G_Damage( ent, client->lastPoisonClient, client->lastPoisonClient, NULL, NULL, damage, 0, MOD_POISON ); } //replenish alien health if( client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) { int entityList[ MAX_GENTITIES ]; vec3_t range = { LEVEL4_REGEN_RANGE, LEVEL4_REGEN_RANGE, LEVEL4_REGEN_RANGE }; vec3_t mins, maxs; int i, num; gentity_t *boostEntity; float modifier = 1.0f; VectorAdd( client->ps.origin, range, maxs ); VectorSubtract( client->ps.origin, range, mins ); num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); for( i = 0; i < num; i++ ) { boostEntity = &g_entities[ entityList[ i ] ]; if( boostEntity->client && boostEntity->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS && boostEntity->client->ps.stats[ STAT_PCLASS ] == PCL_ALIEN_LEVEL4 ) { modifier = LEVEL4_REGEN_MOD; break; } else if( boostEntity->s.eType == ET_BUILDABLE && boostEntity->s.modelindex == BA_A_BOOSTER && boostEntity->spawned ) { modifier = BOOSTER_REGEN_MOD; break; } } if( ent->health > 0 && ent->health < client->ps.stats[ STAT_MAX_HEALTH ] && ( ent->lastDamageTime + ALIEN_REGEN_DAMAGE_TIME ) < level.time ) ent->health += BG_FindRegenRateForClass( client->ps.stats[ STAT_PCLASS ] ) * modifier; if( ent->health > client->ps.stats[ STAT_MAX_HEALTH ] ) ent->health = client->ps.stats[ STAT_MAX_HEALTH ]; } } while( client->time10000 >= 10000 ) { client->time10000 -= 10000; if( client->ps.weapon == WP_ALEVEL3_UPG ) { int ammo, maxAmmo; BG_FindAmmoForWeapon( WP_ALEVEL3_UPG, &maxAmmo, NULL ); BG_UnpackAmmoArray( WP_ALEVEL3_UPG, client->ps.ammo, client->ps.powerups, &ammo, NULL ); if( ammo < maxAmmo ) { ammo++; BG_PackAmmoArray( WP_ALEVEL3_UPG, client->ps.ammo, client->ps.powerups, ammo, 0 ); } } } }
/* =================== CG_DrawItemSelect =================== */ void CG_DrawItemSelect( rectDef_t *rect, vec4_t color ) { int i; float x = rect->x; float y = rect->y; float width = rect->w; float height = rect->h; float iconWidth; float iconHeight; int items[ 64 ]; int colinfo[ 64 ]; int numItems = 0, selectedItem = 0; int length; qboolean vertical; centity_t *cent; playerState_t *ps; cent = &cg_entities[ cg.snap->ps.clientNum ]; ps = &cg.snap->ps; // don't display if dead if( cg.predictedPlayerState.stats[ STAT_HEALTH ] <= 0 ) return; if( !( cg.snap->ps.pm_flags & PMF_FOLLOW ) ) { // first make sure that whatever it selected is actually selectable if( cg.weaponSelect < 32 ) { if( !CG_WeaponSelectable( cg.weaponSelect ) ) CG_NextWeapon_f( ); } else { if( !CG_UpgradeSelectable( cg.weaponSelect - 32 ) ) CG_NextWeapon_f( ); } } // showing weapon select clears pickup item display, but not the blend blob cg.itemPickupTime = 0; // put all weapons in the items list for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ ) { if( !BG_InventoryContainsWeapon( i, cg.snap->ps.stats ) ) continue; if( !ps->ammo && !ps->clips && !BG_Weapon( i )->infiniteAmmo ) colinfo[ numItems ] = 1; else colinfo[ numItems ] = 0; if( i == cg.weaponSelect ) selectedItem = numItems; if( !cg_weapons[ i ].registered ) { Com_Printf( S_COLOR_YELLOW "WARNING: CG_DrawItemSelect: weapon %d (%s) " "is not registered\n", i, BG_Weapon( i )->name ); continue; } items[ numItems ] = i; numItems++; } // put all upgrades in the weapons list for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) { if( !BG_InventoryContainsUpgrade( i, cg.snap->ps.stats ) ) continue; colinfo[ numItems ] = 0; if( !BG_Upgrade( i )->usable ) colinfo[ numItems ] = 2; if( i == cg.weaponSelect - 32 ) selectedItem = numItems; if( !cg_upgrades[ i ].registered ) { Com_Printf( S_COLOR_YELLOW "WARNING: CG_DrawItemSelect: upgrade %d (%s) " "is not registered\n", i, BG_Upgrade( i )->name ); continue; } items[ numItems ] = i + 32; numItems++; } // compute the length of the display window and determine orientation vertical = height > width; if( vertical ) { iconWidth = width * cgDC.aspectScale; iconHeight = width; length = height / ( width * cgDC.aspectScale ); } else { iconWidth = height * cgDC.aspectScale; iconHeight = height; length = width / ( height * cgDC.aspectScale ); } // render icon ring for( i = 0; i < length; i++ ) { int item = i - length / 2 + selectedItem; if( item < 0 ) item += length; else if( item >= length ) item -= length; if( item >= 0 && item < numItems ) { switch( colinfo[ item ] ) { case 0: color = colorCyan; break; case 1: color = colorRed; break; case 2: color = colorMdGrey; break; } color[3] = 0.5; trap_R_SetColor( color ); if( items[ item ] < 32 ) CG_DrawPic( x, y, iconWidth, iconHeight, cg_weapons[ items[ item ] ].weaponIcon ); else CG_DrawPic( x, y, iconWidth, iconHeight, cg_upgrades[ items[ item ] - 32 ].upgradeIcon ); } if( vertical ) y += iconHeight; else x += iconWidth; } trap_R_SetColor( NULL ); }
/* =============== CG_HumanText =============== */ static void CG_HumanText( char *text, playerState_t *ps ) { const char *name; upgrade_t upgrade = UP_NONE; if ( cg.weaponSelect < 32 ) { name = cg_weapons[ cg.weaponSelect ].humanName; } else { name = cg_upgrades[ cg.weaponSelect - 32 ].humanName; upgrade = cg.weaponSelect - 32; } if ( !ps->ammo && !ps->clips && !BG_Weapon( ps->weapon )->infiniteAmmo ) { //no ammo switch ( ps->weapon ) { case WP_MACHINEGUN: case WP_CHAINGUN: case WP_SHOTGUN: case WP_FLAMER: Q_strcat( text, MAX_TUTORIAL_TEXT, va( _( "Find an Armoury and press %s for more ammo\n" ), CG_KeyNameForCommand( "buy ammo" ) ) ); break; case WP_LAS_GUN: case WP_PULSE_RIFLE: case WP_MASS_DRIVER: case WP_LUCIFER_CANNON: Q_strcat( text, MAX_TUTORIAL_TEXT, va( _( "Find an Armoury, Reactor, or Repeater and press %s for more ammo\n" ), CG_KeyNameForCommand( "buy ammo" ) ) ); break; default: break; } } else { switch ( ps->weapon ) { case WP_BLASTER: case WP_MACHINEGUN: case WP_SHOTGUN: case WP_LAS_GUN: case WP_CHAINGUN: case WP_PULSE_RIFLE: case WP_FLAMER: Q_strcat( text, MAX_TUTORIAL_TEXT, va( _( "Press %s to fire the %s\n" ), CG_KeyNameForCommand( "+attack" ), _( BG_Weapon( ps->weapon )->humanName ) ) ); break; case WP_MASS_DRIVER: Q_strcat( text, MAX_TUTORIAL_TEXT, va( _( "Press %s to fire the %s\n" ), CG_KeyNameForCommand( "+attack" ), _( BG_Weapon( ps->weapon )->humanName ) ) ); Q_strcat( text, MAX_TUTORIAL_TEXT, va( _( "Hold %s to zoom\n" ), CG_KeyNameForCommand( "+attack2" ) ) ); break; case WP_PAIN_SAW: Q_strcat( text, MAX_TUTORIAL_TEXT, va( _( "Hold %s to activate the %s\n" ), CG_KeyNameForCommand( "+attack" ), _( BG_Weapon( ps->weapon )->humanName ) ) ); break; case WP_LUCIFER_CANNON: Q_strcat( text, MAX_TUTORIAL_TEXT, va( _( "Hold and release %s to fire a charged shot\n" ), CG_KeyNameForCommand( "+attack" ) ) ); Q_strcat( text, MAX_TUTORIAL_TEXT, va( _( "Press %s to fire the %s\n" ), CG_KeyNameForCommand( "+attack2" ), _( BG_Weapon( ps->weapon )->humanName ) ) ); break; case WP_HBUILD: CG_HumanCkitText( text, ps ); break; default: break; } } Q_strcat( text, MAX_TUTORIAL_TEXT, va( _( "Use %s and %s to select an upgrade\n" ), CG_KeyNameForCommand( "weapprev" ), CG_KeyNameForCommand( "weapnext" ) ) ); if ( upgrade == UP_NONE || ( upgrade > UP_NONE && BG_Upgrade( upgrade )->usable ) ) { Q_strcat( text, MAX_TUTORIAL_TEXT, va( _( "Press %s to use the %s\n" ), CG_KeyNameForCommand( "+useitem" ), name ) ); } if ( ps->stats[ STAT_HEALTH ] <= 35 && BG_InventoryContainsUpgrade( UP_MEDKIT, ps->stats ) ) { Q_strcat( text, MAX_TUTORIAL_TEXT, va( _( "Press %s to use your %s\n" ), CG_KeyNameForCommand( "itemact medkit" ), _( BG_Upgrade( UP_MEDKIT )->humanName ) ) ); } if ( ps->stats[ STAT_STAMINA ] <= STAMINA_BLACKOUT_LEVEL ) { Q_strcat( text, MAX_TUTORIAL_TEXT, _( "You are blacking out. Stop sprinting to recover stamina\n" ) ); } else if ( ps->stats[ STAT_STAMINA ] <= STAMINA_SLOW_LEVEL ) { Q_strcat( text, MAX_TUTORIAL_TEXT, _( "Your stamina is low. Stop sprinting to recover\n" ) ); } switch ( cg.nearUsableBuildable ) { case BA_H_ARMOURY: Q_strcat( text, MAX_TUTORIAL_TEXT, va( _( "Press %s to buy equipment upgrades at the %s. Sell your old weapon first!\n" ), CG_KeyNameForCommand( "+activate" ), _( BG_Buildable( cg.nearUsableBuildable )->humanName ) ) ); break; case BA_H_REPEATER: case BA_H_REACTOR: Q_strcat( text, MAX_TUTORIAL_TEXT, va( _( "Press %s to refill your energy weapon's ammo at the %s\n" ), CG_KeyNameForCommand( "+activate" ), _( BG_Buildable( cg.nearUsableBuildable )->humanName ) ) ); break; case BA_NONE: break; default: Q_strcat( text, MAX_TUTORIAL_TEXT, va( _( "Press %s to use the %s\n" ), CG_KeyNameForCommand( "+activate" ), _( BG_Buildable( cg.nearUsableBuildable )->humanName ) ) ); break; } Q_strcat( text, MAX_TUTORIAL_TEXT, va( _( "Press %s and any direction to sprint\n" ), CG_KeyNameForCommand( "+sprint" ) ) ); }
/* =========== ClientSpawn Called every time a client is placed fresh in the world: after the first ClientBegin, and after each respawn Initializes all non-persistant parts of playerState ============ */ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles ) { int index; vec3_t spawn_origin, spawn_angles; gclient_t *client; int i; clientPersistant_t saved; clientSession_t savedSess; int persistant[ MAX_PERSISTANT ]; gentity_t *spawnPoint = NULL, *event; int flags; int savedPing; int teamLocal; int eventSequence; char userinfo[ MAX_INFO_STRING ]; vec3_t up = { 0.0f, 0.0f, 1.0f }, implant_dir, implant_angles, spawnPoint_velocity; int maxAmmo, maxClips; weapon_t weapon; qboolean fromImplant = qfalse, hatchingFailed = qfalse; index = ent - g_entities; client = ent->client; teamLocal = client->pers.teamSelection; //if client is dead and following teammate, stop following before spawning if( client->sess.spectatorClient != -1 ) { client->sess.spectatorClient = -1; client->sess.spectatorState = SPECTATOR_FREE; } // only start client if chosen a class and joined a team if( client->pers.classSelection == PCL_NONE && teamLocal == TEAM_NONE ) client->sess.spectatorState = SPECTATOR_FREE; else if( client->pers.classSelection == PCL_NONE ) client->sess.spectatorState = SPECTATOR_LOCKED; // if client is dead and following teammate, stop following before spawning if( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) G_StopFollowing( ent ); if( origin != NULL ) VectorCopy( origin, spawn_origin ); if( angles != NULL ) VectorCopy( angles, spawn_angles ); // find a spawn point // do it before setting health back up, so farthest // ranging doesn't count this client if( client->sess.spectatorState != SPECTATOR_NOT ) { if( teamLocal == TEAM_NONE ) spawnPoint = G_SelectSpectatorSpawnPoint( spawn_origin, spawn_angles ); else if( teamLocal == TEAM_ALIENS ) spawnPoint = G_SelectAlienLockSpawnPoint( spawn_origin, spawn_angles ); else if( teamLocal == TEAM_HUMANS ) spawnPoint = G_SelectHumanLockSpawnPoint( spawn_origin, spawn_angles ); } else { if( spawn == NULL ) { G_Error( "ClientSpawn: spawn is NULL\n" ); return; } spawnPoint = spawn; if( ent != spawn ) { if( !spawnPoint->client ) //might be a human { //start spawn animation on spawnPoint G_SetBuildableAnim( spawnPoint, BANIM_SPAWN1, qtrue ); if( spawnPoint->buildableTeam == TEAM_ALIENS ) spawnPoint->clientSpawnTime = ALIEN_SPAWN_REPEAT_TIME; else if( spawnPoint->buildableTeam == TEAM_HUMANS ) spawnPoint->clientSpawnTime = HUMAN_SPAWN_REPEAT_TIME; } else { qboolean crouch; int i; float zoffs = 0.0f; trace_t tr; vec3_t neworigin, mins, maxs; fromImplant = qtrue; //spawning from a human // move the origin a bit on the Z axis so the aliens jumps out of the chest, not knees // also prevents grangers from getting stuck in ceilings and floors crouch = spawnPoint->client->ps.pm_flags & PMF_DUCKED; switch( client->pers.classSelection ) { case PCL_ALIEN_BUILDER0: if( !crouch ) zoffs = 19.0f; else zoffs = -4.0f; break; case PCL_ALIEN_BUILDER0_UPG: if( !crouch ) zoffs = 16.5f; else zoffs = -4.0f; break; case PCL_ALIEN_LEVEL0: if( !crouch ) zoffs = 15.0f; else zoffs = -9.1f; break; } spawn_origin[ 2 ] += zoffs; // check if the spot would block BG_ClassBoundingBox( client->pers.classSelection, mins, maxs, NULL, NULL, NULL ); trap_Trace( &tr, spawn_origin, mins, maxs, spawn_origin, spawnPoint->s.number, MASK_PLAYERSOLID ); // try to unblock the player if( tr.startsolid ) { Com_Printf("DEBUG: player is stuck!\n"); for( i = 0; i < 16*2; i++ ) { float a, r; VectorCopy( spawn_origin, neworigin ); a = (float)i / 16.0f * 2.0f * M_PI; #define fmod(a,n) ((a)-(n)*floor((a)/(n))) r = ( i < 16 ? 5.5f : 11.0f ) * 1.0f / cos( fmod( a+0.25f*M_PI, 0.5f*M_PI ) - 0.25f*M_PI ); neworigin[ 0 ] += cos( a ) * r; neworigin[ 1 ] += sin( a ) * r; trap_Trace( &tr, neworigin, mins, maxs, neworigin, spawnPoint->s.number, MASK_PLAYERSOLID ); if( !tr.startsolid ) { Com_Printf("DEBUG: player position fixed at iteration %i\n",i); VectorCopy( neworigin, spawn_origin ); break; } } } //reward the player that implanted this one if( spawnPoint->client->impregnatedBy >= 0 ) { gentity_t *granger; granger = &g_entities[ spawnPoint->client->impregnatedBy ]; G_AddCreditToClient( granger->client, ALIEN_IMPREGNATION_REWARD, qtrue ); AddScore( granger, ALIEN_IMPREGNATION_REWARD_SCORE ); } // kill the human, set up angles and velocity for the new alien if( !BG_InventoryContainsUpgrade( UP_BATTLESUIT, spawnPoint->client->ps.stats ) //humans without battlesuits always die || spawnPoint->client->ps.stats[ STAT_HEALTH ] < ALIEN_HATCHING_MAX_BATTLESUIT_HEALTH ) //battlesuits survive if high hp { //save viewangles and spawn velocity for velocity calculation VectorCopy( spawnPoint->client->ps.viewangles, implant_angles ); AngleVectors( implant_angles, implant_dir, NULL, NULL ); VectorCopy( spawnPoint->client->ps.velocity, spawnPoint_velocity ); //fire a nice chest exploding effect event = G_TempEntity( spawnPoint->s.pos.trBase, EV_ALIEN_HATCH ); VectorCopy( implant_dir, event->s.angles ); //kill the player G_Damage( spawnPoint, NULL, ent, NULL, NULL, spawnPoint->client->ps.stats[ STAT_HEALTH ], DAMAGE_NO_ARMOR, MOD_ALIEN_HATCH ); } else //human survives { //clear impregnation so the human won't explode again spawnPoint->client->isImpregnated = qfalse; spawnPoint->client->isImplantMature = qfalse; //make a sound event = G_TempEntity( spawnPoint->s.pos.trBase, EV_ALIEN_HATCH_FAILURE ); //damage the human G_Damage( spawnPoint, NULL, ent, NULL, NULL, ALIEN_FAILED_HATCH_DAMAGE, DAMAGE_NO_ARMOR, MOD_ALIEN_HATCH ); //kill the newly spawned alien VectorCopy( spawnPoint->client->ps.viewangles, implant_angles ); implant_dir[0] = 0.0f; implant_dir[1] = 0.0f; implant_dir[2] = 0.0f; hatchingFailed = qtrue; } } } } // toggle the teleport bit so the client knows to not lerp flags = ( ent->client->ps.eFlags & EF_TELEPORT_BIT ) ^ EF_TELEPORT_BIT; G_UnlaggedClear( ent ); // clear everything but the persistant data saved = client->pers; savedSess = client->sess; savedPing = client->ps.ping; for( i = 0; i < MAX_PERSISTANT; i++ ) persistant[ i ] = client->ps.persistant[ i ]; eventSequence = client->ps.eventSequence; memset( client, 0, sizeof( *client ) ); client->pers = saved; client->sess = savedSess; client->ps.ping = savedPing; client->lastkilled_client = -1; for( i = 0; i < MAX_PERSISTANT; i++ ) client->ps.persistant[ i ] = persistant[ i ]; client->ps.eventSequence = eventSequence; // increment the spawncount so the client will detect the respawn client->ps.persistant[ PERS_SPAWN_COUNT ]++; client->ps.persistant[ PERS_SPECSTATE ] = client->sess.spectatorState; client->airOutTime = level.time + 12000; trap_GetUserinfo( index, userinfo, sizeof( userinfo ) ); client->ps.eFlags = flags; //Com_Printf( "ent->client->pers->pclass = %i\n", ent->client->pers.classSelection ); ent->s.groundEntityNum = ENTITYNUM_NONE; ent->client = &level.clients[ index ]; ent->takedamage = qtrue; ent->inuse = qtrue; ent->classname = "player"; ent->r.contents = CONTENTS_BODY; ent->clipmask = MASK_PLAYERSOLID; ent->die = player_die; ent->waterlevel = 0; ent->watertype = 0; ent->flags = 0; // calculate each client's acceleration ent->evaluateAcceleration = qtrue; client->ps.stats[ STAT_MISC ] = 0; client->buildTimer = 0; client->ps.eFlags = flags; client->ps.clientNum = index; BG_ClassBoundingBox( ent->client->pers.classSelection, ent->r.mins, ent->r.maxs, NULL, NULL, NULL ); if( client->sess.spectatorState == SPECTATOR_NOT ) client->ps.stats[ STAT_MAX_HEALTH ] = BG_Class( ent->client->pers.classSelection )->health; else client->ps.stats[ STAT_MAX_HEALTH ] = 100; // clear entity values if( ent->client->pers.classSelection == PCL_HUMAN ) { BG_AddUpgradeToInventory( UP_MEDKIT, client->ps.stats ); weapon = client->pers.humanItemSelection; } else if( client->sess.spectatorState == SPECTATOR_NOT ) weapon = BG_Class( ent->client->pers.classSelection )->startWeapon; else weapon = WP_NONE; maxAmmo = BG_Weapon( weapon )->maxAmmo; maxClips = BG_Weapon( weapon )->maxClips; client->ps.stats[ STAT_WEAPON ] = weapon; client->ps.ammo = maxAmmo; client->ps.clips = maxClips; // We just spawned, not changing weapons client->ps.persistant[ PERS_NEWWEAPON ] = 0; ent->client->ps.stats[ STAT_CLASS ] = ent->client->pers.classSelection; ent->client->ps.stats[ STAT_TEAM ] = ent->client->pers.teamSelection; ent->client->ps.stats[ STAT_BUILDABLE ] = BA_NONE; ent->client->ps.stats[ STAT_STATE ] = 0; VectorSet( ent->client->ps.grapplePoint, 0.0f, 0.0f, 1.0f ); // health will count down towards max_health ent->health = client->ps.stats[ STAT_HEALTH ] = client->ps.stats[ STAT_MAX_HEALTH ]; //* 1.25; //if evolving scale health if( ent == spawn ) { ent->health *= ent->client->pers.evolveHealthFraction; client->ps.stats[ STAT_HEALTH ] *= ent->client->pers.evolveHealthFraction; } //clear the credits array for( i = 0; i < MAX_CLIENTS; i++ ) ent->credits[ i ] = 0; client->ps.stats[ STAT_STAMINA ] = STAMINA_MAX; //never impregnated after respawning client->isImpregnated = qfalse; client->isImplantMature = qfalse; G_SetOrigin( ent, spawn_origin ); VectorCopy( spawn_origin, client->ps.origin ); #define UP_VEL 150.0f #define F_VEL 50.0f //give aliens some spawn velocity if( client->sess.spectatorState == SPECTATOR_NOT && client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) { if( ent == spawn ) { //evolution particle system G_AddPredictableEvent( ent, EV_ALIEN_EVOLVE, DirToByte( up ) ); } else if( !fromImplant ) //regular egg { spawn_angles[ YAW ] += 180.0f; AngleNormalize360( spawn_angles[ YAW ] ); if( spawnPoint->s.origin2[ 2 ] > 0.0f ) { vec3_t forward, dir; AngleVectors( spawn_angles, forward, NULL, NULL ); VectorScale( forward, F_VEL, forward ); VectorAdd( spawnPoint->s.origin2, forward, dir ); VectorNormalize( dir ); VectorScale( dir, UP_VEL, client->ps.velocity ); } G_AddPredictableEvent( ent, EV_PLAYER_RESPAWN, 0 ); } else //implanted egg { VectorCopy( implant_angles, spawn_angles ); VectorScale( implant_dir, ALIEN_HATCHING_VELOCITY, client->ps.velocity ); VectorAdd( client->ps.velocity, spawnPoint_velocity, client->ps.velocity ); } } else if( client->sess.spectatorState == SPECTATOR_NOT && client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) { spawn_angles[ YAW ] += 180.0f; AngleNormalize360( spawn_angles[ YAW ] ); } // the respawned flag will be cleared after the attack and jump keys come up client->ps.pm_flags |= PMF_RESPAWNED; trap_GetUsercmd( client - level.clients, &ent->client->pers.cmd ); G_SetClientViewAngle( ent, spawn_angles ); if( client->sess.spectatorState == SPECTATOR_NOT ) { trap_LinkEntity( ent ); // force the base weapon up if( client->pers.teamSelection == TEAM_HUMANS ) G_ForceWeaponChange( ent, weapon ); client->ps.weaponstate = WEAPON_READY; } // don't allow full run speed for a bit client->ps.pm_flags |= PMF_TIME_KNOCKBACK; client->ps.pm_time = 100; client->respawnTime = level.time; ent->nextRegenTime = level.time; client->inactivityTime = level.time + g_inactivity.integer * 1000; client->latched_buttons = 0; // set default animations client->ps.torsoAnim = TORSO_STAND; client->ps.legsAnim = LEGS_IDLE; if( level.intermissiontime ) MoveClientToIntermission( ent ); else { // fire the targets of the spawn point if( !spawn ) G_UseTargets( spawnPoint, ent ); // select the highest weapon number available, after any // spawn given items have fired client->ps.weapon = 1; for( i = WP_NUM_WEAPONS - 1; i > 0 ; i-- ) { if( BG_InventoryContainsWeapon( i, client->ps.stats ) ) { client->ps.weapon = i; break; } } } client->lastRantBombTime = level.time; // run a client frame to drop exactly to the floor, // initialize animations and other things client->ps.commandTime = level.time - 100; ent->client->pers.cmd.serverTime = level.time; ClientThink( ent-g_entities ); // positively link the client, even if the command times are weird if( client->sess.spectatorState == SPECTATOR_NOT ) { BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); trap_LinkEntity( ent ); } // must do this here so the number of active clients is calculated CalculateRanks( ); // run the presend to set anything else ClientEndFrame( ent ); // clear entity state values BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); // kill him instantly after respawning if hatching failed if( fromImplant && hatchingFailed ) { VectorCopy( spawnPoint->client->ps.velocity, client->ps.velocity ); client->ps.stats[ STAT_HEALTH ] = ent->health = 0; player_die( ent, NULL, spawnPoint, 0, MOD_ALIEN_HATCH_FAILED ); } }
/* ================== TeamplayInfoMessage Format: clientNum location health weapon upgrade ================== */ void TeamplayInfoMessage(gentity_t *ent) { char entry[17], string[(MAX_CLIENTS - 1) * (sizeof(entry) - 1) + 1]; int i, j; int team, stringlength; gentity_t *player; gclient_t *cl; upgrade_t upgrade = UP_NONE; int curWeaponClass = WP_NONE; // sends weapon for humans, class for aliens const char *format; if (!g_allowTeamOverlay.integer) return; if (!ent->client->pers.teamInfo) return; if (ent->client->pers.teamSelection == TEAM_NONE) { if (ent->client->sess.spectatorState == SPECTATOR_FREE || ent->client->sess.spectatorClient < 0) return; team = g_entities[ent->client->sess.spectatorClient].client->pers.teamSelection; } else team = ent->client->pers.teamSelection; if (team == TEAM_ALIENS) format = " %i %i %i %i"; // aliens don't have upgrades else format = " %i %i %i %i %i"; string[0] = '\0'; stringlength = 0; for (i = 0; i < level.maxclients; i++) { player = g_entities + i; cl = player->client; if (ent == player || !cl || team != cl->pers.teamSelection || !player->inuse) continue; // only update if changed since last time if (cl->pers.infoChangeTime <= ent->client->pers.teamInfo) continue; if (cl->sess.spectatorState != SPECTATOR_NOT) { curWeaponClass = WP_NONE; upgrade = UP_NONE; } else if (cl->pers.teamSelection == TEAM_HUMANS) { curWeaponClass = cl->ps.weapon; if (BG_InventoryContainsUpgrade(UP_BATTLESUIT, cl->ps.stats)) upgrade = UP_BATTLESUIT; else if (BG_InventoryContainsUpgrade(UP_JETPACK, cl->ps.stats)) upgrade = UP_JETPACK; else if (BG_InventoryContainsUpgrade(UP_BATTPACK, cl->ps.stats)) upgrade = UP_BATTPACK; else if (BG_InventoryContainsUpgrade(UP_HELMET, cl->ps.stats)) upgrade = UP_HELMET; else if (BG_InventoryContainsUpgrade(UP_LIGHTARMOUR, cl->ps.stats)) upgrade = UP_LIGHTARMOUR; else upgrade = UP_NONE; } else if (cl->pers.teamSelection == TEAM_ALIENS) { curWeaponClass = cl->ps.stats[STAT_CLASS]; upgrade = UP_NONE; } Com_sprintf(entry, sizeof(entry), format, i, cl->pers.location, cl->ps.stats[STAT_HEALTH] < 1 ? 0 : cl->ps.stats[STAT_HEALTH], curWeaponClass, upgrade); j = strlen(entry); // this should not happen if entry and string sizes are correct if (stringlength + j >= sizeof(string)) break; strcpy(string + stringlength, entry); stringlength += j; } if (string[0]) { SV_GameSendServerCommand(ent - g_entities, va("tinfo%s", string)); ent->client->pers.teamInfo = level.time; } }
/* ============== 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; 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; if( pmove_msec.integer < 8 ) trap_Cvar_Set( "pmove_msec", "8" ); else if( pmove_msec.integer > 33 ) trap_Cvar_Set( "pmove_msec", "33" ); if( pmove_fixed.integer || client->pers.pmoveFixed ) { ucmd->serverTime = ( ( ucmd->serverTime + pmove_msec.integer - 1 ) / pmove_msec.integer ) * pmove_msec.integer; //if (ucmd->serverTime - client->ps.commandTime <= 0) // return; } // // check for exiting intermission // if( level.intermissiontime ) { ClientIntermissionThink( client ); return; } // spectators don't do much if( client->sess.sessionTeam == TEAM_SPECTATOR ) { if( client->sess.spectatorState == SPECTATOR_SCOREBOARD ) return; SpectatorThink( ent, ucmd ); return; } G_UpdatePTRConnection( client ); // check for inactivity timer, but never drop the local client of a non-dedicated server if( !ClientInactivityTimer( client ) ) return; if( client->noclip ) client->ps.pm_type = PM_NOCLIP; else if( client->ps.stats[ STAT_HEALTH ] <= 0 ) client->ps.pm_type = PM_DEAD; else if( client->ps.stats[ STAT_STATE ] & SS_INFESTING || client->ps.stats[ STAT_STATE ] & SS_HOVELING ) client->ps.pm_type = PM_FREEZE; else if( client->ps.stats[ STAT_STATE ] & SS_BLOBLOCKED || client->ps.stats[ STAT_STATE ] & SS_GRABBED ) client->ps.pm_type = PM_GRABBED; else if( BG_InventoryContainsUpgrade( UP_JETPACK, client->ps.stats ) && BG_UpgradeIsActive( UP_JETPACK, client->ps.stats ) ) client->ps.pm_type = PM_JETPACK; else client->ps.pm_type = PM_NORMAL; if( client->ps.stats[ STAT_STATE ] & SS_GRABBED && client->grabExpiryTime < level.time ) client->ps.stats[ STAT_STATE ] &= ~SS_GRABBED; if( client->ps.stats[ STAT_STATE ] & SS_BLOBLOCKED && client->lastLockTime + 5000 < level.time ) client->ps.stats[ STAT_STATE ] &= ~SS_BLOBLOCKED; if( client->ps.stats[ STAT_STATE ] & SS_SLOWLOCKED && client->lastLockTime + ABUILDER_BLOB_TIME < level.time ) client->ps.stats[ STAT_STATE ] &= ~SS_SLOWLOCKED; client->ps.stats[ STAT_BOOSTTIME ] = level.time - client->lastBoostedTime; if( client->ps.stats[ STAT_STATE ] & SS_BOOSTED && client->lastBoostedTime + BOOST_TIME < level.time ) client->ps.stats[ STAT_STATE ] &= ~SS_BOOSTED; if( client->ps.stats[ STAT_STATE ] & SS_POISONCLOUDED && client->lastPoisonCloudedTime + LEVEL1_PCLOUD_TIME < level.time ) client->ps.stats[ STAT_STATE ] &= ~SS_POISONCLOUDED; if( client->ps.stats[ STAT_STATE ] & SS_POISONED && client->lastPoisonTime + ALIEN_POISON_TIME < level.time ) client->ps.stats[ STAT_STATE ] &= ~SS_POISONED; client->ps.gravity = g_gravity.value; if( BG_InventoryContainsUpgrade( UP_MEDKIT, client->ps.stats ) && BG_UpgradeIsActive( UP_MEDKIT, client->ps.stats ) ) { //if currently using a medkit or have no need for a medkit now if( client->ps.stats[ STAT_STATE ] & SS_MEDKIT_ACTIVE || ( client->ps.stats[ STAT_HEALTH ] == client->ps.stats[ STAT_MAX_HEALTH ] && !( client->ps.stats[ STAT_STATE ] & SS_POISONED ) ) ) { BG_DeactivateUpgrade( UP_MEDKIT, client->ps.stats ); } else if( client->ps.stats[ STAT_HEALTH ] > 0 ) { //remove anti toxin BG_DeactivateUpgrade( UP_MEDKIT, client->ps.stats ); BG_RemoveUpgradeFromInventory( UP_MEDKIT, client->ps.stats ); client->ps.stats[ STAT_STATE ] &= ~SS_POISONED; client->poisonImmunityTime = level.time + MEDKIT_POISON_IMMUNITY_TIME; client->ps.stats[ STAT_STATE ] |= SS_MEDKIT_ACTIVE; client->lastMedKitTime = level.time; client->medKitHealthToRestore = client->ps.stats[ STAT_MAX_HEALTH ] - client->ps.stats[ STAT_HEALTH ]; client->medKitIncrementTime = level.time + ( MEDKIT_STARTUP_TIME / MEDKIT_STARTUP_SPEED ); G_AddEvent( ent, EV_MEDKIT_USED, 0 ); } } if( BG_InventoryContainsUpgrade( UP_GRENADE, client->ps.stats ) && BG_UpgradeIsActive( UP_GRENADE, client->ps.stats ) ) { int lastWeapon = ent->s.weapon; //remove grenade BG_DeactivateUpgrade( UP_GRENADE, client->ps.stats ); BG_RemoveUpgradeFromInventory( UP_GRENADE, client->ps.stats ); //M-M-M-M-MONSTER HACK ent->s.weapon = WP_GRENADE; FireWeapon( ent ); ent->s.weapon = lastWeapon; } // set speed client->ps.speed = g_speed.value * BG_FindSpeedForClass( client->ps.stats[ STAT_PCLASS ] ); if( client->lastCreepSlowTime + CREEP_TIMEOUT < level.time ) client->ps.stats[ STAT_STATE ] &= ~SS_CREEPSLOWED; //randomly disable the jet pack if damaged if( BG_InventoryContainsUpgrade( UP_JETPACK, client->ps.stats ) && BG_UpgradeIsActive( UP_JETPACK, client->ps.stats ) ) { if( ent->lastDamageTime + JETPACK_DISABLE_TIME > level.time ) { if( random( ) > JETPACK_DISABLE_CHANCE ) client->ps.pm_type = PM_NORMAL; } //switch jetpack off if no reactor if( !level.reactorPresent ) BG_DeactivateUpgrade( UP_JETPACK, client->ps.stats ); } // set up for pmove oldEventSequence = client->ps.eventSequence; memset( &pm, 0, sizeof( pm ) ); if( !( ucmd->buttons & BUTTON_TALK ) ) //&& client->ps.weaponTime <= 0 ) //TA: erk more server load { switch( client->ps.weapon ) { case WP_ALEVEL0: if( client->ps.weaponTime <= 0 ) pm.autoWeaponHit[ client->ps.weapon ] = CheckVenomAttack( ent ); break; case WP_ALEVEL1: case WP_ALEVEL1_UPG: CheckGrabAttack( ent ); break; case WP_ALEVEL3: case WP_ALEVEL3_UPG: if( client->ps.weaponTime <= 0 ) pm.autoWeaponHit[ client->ps.weapon ] = CheckPounceAttack( ent ); break; default: break; } } if( ent->flags & FL_FORCE_GESTURE ) { ent->flags &= ~FL_FORCE_GESTURE; ent->client->pers.cmd.buttons |= BUTTON_GESTURE; } pm.ps = &client->ps; pm.cmd = *ucmd; if( pm.ps->pm_type == PM_DEAD ) pm.tracemask = MASK_PLAYERSOLID; // & ~CONTENTS_BODY; if( pm.ps->stats[ STAT_STATE ] & SS_INFESTING || pm.ps->stats[ STAT_STATE ] & SS_HOVELING ) 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 = (qboolean)0; pm.pmove_fixed = pmove_fixed.integer | client->pers.pmoveFixed; pm.pmove_msec = pmove_msec.integer; VectorCopy( client->ps.origin, client->oldOrigin ); // moved from after Pmove -- potentially the cause of // future triggering bugs if( !ent->client->noclip ) G_TouchTriggers( ent ); Pmove( &pm ); // save results of pmove if( ent->client->ps.eventSequence != oldEventSequence ) ent->eventTime = level.time; if( g_smoothClients.integer ) BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, qtrue ); else BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue ); SendPendingPredictableEvents( &ent->client->ps ); if( !( ent->client->ps.eFlags & EF_FIRING ) ) client->fireHeld = qfalse; // for grapple if( !( ent->client->ps.eFlags & EF_FIRING2 ) ) client->fire2Held = qfalse; // 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 ); // link entity now, after any personal teleporters have been used trap_LinkEntity( ent ); // NOTE: now copy the exact origin over otherwise clients can be snapped into solid VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); VectorCopy( ent->client->ps.origin, ent->s.origin ); // 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; if( ( client->buttons & BUTTON_GETFLAG ) && !( client->oldbuttons & BUTTON_GETFLAG ) && client->ps.stats[ STAT_HEALTH ] > 0 ) { trace_t trace; vec3_t view, point; gentity_t *traceEnt; if( client->ps.stats[ STAT_STATE ] & SS_HOVELING ) { gentity_t *hovel = client->hovel; //only let the player out if there is room if( !AHovel_Blocked( hovel, ent, qtrue ) ) { //prevent lerping client->ps.eFlags ^= EF_TELEPORT_BIT; client->ps.eFlags &= ~EF_NODRAW; //client leaves hovel client->ps.stats[ STAT_STATE ] &= ~SS_HOVELING; //hovel is empty G_setBuildableAnim( hovel, BANIM_ATTACK2, qfalse ); hovel->active = qfalse; } else { //exit is blocked G_TriggerMenu( ent->client->ps.clientNum, MN_A_HOVEL_BLOCKED ); } } else { #define USE_OBJECT_RANGE 64 int entityList[ MAX_GENTITIES ]; vec3_t range = { USE_OBJECT_RANGE, USE_OBJECT_RANGE, USE_OBJECT_RANGE }; vec3_t mins, maxs; int i, num; //TA: look for object infront of player AngleVectors( client->ps.viewangles, view, NULL, NULL ); VectorMA( client->ps.origin, USE_OBJECT_RANGE, view, point ); trap_Trace( &trace, client->ps.origin, NULL, NULL, point, ent->s.number, MASK_SHOT ); traceEnt = &g_entities[ trace.entityNum ]; if( traceEnt && traceEnt->biteam == client->ps.stats[ STAT_PTEAM ] && traceEnt->use ) traceEnt->use( traceEnt, ent, ent ); //other and activator are the same in this context else { //no entity in front of player - do a small area search VectorAdd( client->ps.origin, range, maxs ); VectorSubtract( client->ps.origin, range, mins ); num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); for( i = 0; i < num; i++ ) { traceEnt = &g_entities[ entityList[ i ] ]; if( traceEnt && traceEnt->biteam == client->ps.stats[ STAT_PTEAM ] && traceEnt->use ) { traceEnt->use( traceEnt, ent, ent ); //other and activator are the same in this context break; } } if( i == num && client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) { if( BG_UpgradeClassAvailable( &client->ps ) ) { //no nearby objects and alien - show class menu G_TriggerMenu( ent->client->ps.clientNum, MN_A_INFEST ); } else { //flash frags G_AddEvent( ent, EV_ALIEN_EVOLVE_FAILED, 0 ); } } } } } // 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 if( g_forcerespawn.integer > 0 && ( level.time - client->respawnTime ) > 0 ) { respawn( ent ); return; } // pressing attack or use is the normal respawn method if( ucmd->buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) ) { respawn( ent ); } } return; } if( level.framenum > client->retriggerArmouryMenu && client->retriggerArmouryMenu ) { G_TriggerMenu( client->ps.clientNum, MN_H_ARMOURY ); client->retriggerArmouryMenu = 0; } // Give clients some credit periodically if( ent->client->lastKillTime + FREEKILL_PERIOD < level.time ) { if( g_suddenDeathTime.integer && ( level.time - level.startTime >= g_suddenDeathTime.integer * 60000 ) ) { //gotta love logic like this eh? } else if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) G_AddCreditToClient( ent->client, FREEKILL_ALIEN, qtrue ); else if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) G_AddCreditToClient( ent->client, FREEKILL_HUMAN, qtrue ); ent->client->lastKillTime = level.time; } // perform once-a-second actions ClientTimerActions( ent, msec ); if( ent->suicideTime > 0 && ent->suicideTime < level.time ) { ent->flags &= ~FL_GODMODE; ent->client->ps.stats[ STAT_HEALTH ] = ent->health = 0; player_die( ent, ent, ent, 100000, MOD_SUICIDE ); ent->suicideTime = 0; } }
/* =============== CG_HumanText =============== */ static void CG_HumanText( char *text, playerState_t *ps ) { char *name; upgrade_t upgrade = UP_NONE; if( cg.weaponSelect < 32 ) name = cg_weapons[ cg.weaponSelect ].humanName; else { name = cg_upgrades[ cg.weaponSelect - 32 ].humanName; upgrade = cg.weaponSelect - 32; } if( !ps->ammo && !ps->clips && !BG_Weapon( ps->weapon )->infiniteAmmo ) { //no ammo switch( ps->weapon ) { case WP_MACHINEGUN: case WP_CHAINGUN: case WP_SHOTGUN: case WP_FLAMER: Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Find an Armoury and press %s for more ammo\n", CG_KeyNameForCommand( "buy ammo" ) ) ); break; case WP_LAS_GUN: case WP_PULSE_RIFLE: case WP_MASS_DRIVER: case WP_LUCIFER_CANNON: Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Find a Reactor or Repeater and press %s for more ammo\n", CG_KeyNameForCommand( "buy ammo" ) ) ); break; default: break; } } else { switch( ps->weapon ) { case WP_BLASTER: case WP_MACHINEGUN: case WP_SHOTGUN: case WP_LAS_GUN: case WP_CHAINGUN: case WP_PULSE_RIFLE: case WP_FLAMER: Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Press %s to fire the %s\n", CG_KeyNameForCommand( "+attack" ), BG_Weapon( ps->weapon )->humanName ) ); break; case WP_MASS_DRIVER: Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Press %s to fire the %s\n", CG_KeyNameForCommand( "+attack" ), BG_Weapon( ps->weapon )->humanName ) ); Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Hold %s to zoom\n", CG_KeyNameForCommand( "+button5" ) ) ); break; case WP_PAIN_SAW: Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Hold %s to activate the %s\n", CG_KeyNameForCommand( "+attack" ), BG_Weapon( ps->weapon )->humanName ) ); break; case WP_LUCIFER_CANNON: Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Hold and release %s to fire a charged shot\n", CG_KeyNameForCommand( "+attack" ) ) ); Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Press %s to fire the %s\n", CG_KeyNameForCommand( "+button5" ), BG_Weapon( ps->weapon )->humanName ) ); break; case WP_HBUILD: CG_HumanCkitText( text, ps ); break; default: break; } } Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Press %s and ", CG_KeyNameForCommand( "weapprev" ) ) ); Q_strcat( text, MAX_TUTORIAL_TEXT, va( "%s to select an upgrade\n", CG_KeyNameForCommand( "weapnext" ) ) ); if( upgrade == UP_NONE || ( upgrade > UP_NONE && BG_Upgrade( upgrade )->usable ) ) { Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Press %s to use the %s\n", CG_KeyNameForCommand( "+button2" ), name ) ); } if( ps->stats[ STAT_HEALTH ] <= 35 && BG_InventoryContainsUpgrade( UP_MEDKIT, ps->stats ) ) { Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Press %s to use your %s\n", CG_KeyNameForCommand( "itemact medkit" ), BG_Upgrade( UP_MEDKIT )->humanName ) ); } if( ps->stats[ STAT_STAMINA ] <= STAMINA_BLACKOUT_LEVEL ) { Q_strcat( text, MAX_TUTORIAL_TEXT, "You are blacking out. Stop sprinting to recover stamina.\n" ); } else if( ps->stats[ STAT_STAMINA ] <= STAMINA_SLOW_LEVEL ) { Q_strcat( text, MAX_TUTORIAL_TEXT, "Your stamina is low. Stop sprinting to recover.\n" ); } if( cg.nearUsableBuildable ) { Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Press %s to use this structure\n", CG_KeyNameForCommand( "+button7" ) ) ); } Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Press %s and any direction to sprint\n", CG_KeyNameForCommand( "+button8" ) ) ); Q_strcat( text, MAX_TUTORIAL_TEXT, va( "Press %s and back or strafe to dodge\n", CG_KeyNameForCommand( "+button6" ) ) ); }
/* =================== CG_DrawItemSelect =================== */ void CG_DrawItemSelect( rectDef_t *rect, vec4_t color ) { int i; int x = rect->x; int y = rect->y; int width = rect->w; int height = rect->h; int iconsize; int items[ 64 ]; int numItems = 0, selectedItem = 0; int length; int selectWindow; qboolean vertical; centity_t *cent; playerState_t *ps; cent = &cg_entities[ cg.snap->ps.clientNum ]; ps = &cg.snap->ps; // don't display if dead if( cg.predictedPlayerState.stats[ STAT_HEALTH ] <= 0 ) return; if( !( cg.snap->ps.pm_flags & PMF_FOLLOW ) ) { // first make sure that whatever it selected is actually selectable if( cg.weaponSelect <= 32 && !CG_WeaponSelectable( cg.weaponSelect ) ) CG_NextWeapon_f( ); else if( cg.weaponSelect > 32 && !CG_UpgradeSelectable( cg.weaponSelect - 32 ) ) CG_NextWeapon_f( ); } // showing weapon select clears pickup item display, but not the blend blob cg.itemPickupTime = 0; if( height > width ) { vertical = qtrue; iconsize = width; length = height / width; } else if( height <= width ) { vertical = qfalse; iconsize = height; length = width / height; } selectWindow = length / 2; for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ ) { if( !BG_InventoryContainsWeapon( i, cg.snap->ps.stats ) ) continue; if( i == cg.weaponSelect ) selectedItem = numItems; CG_RegisterWeapon( i ); items[ numItems ] = i; numItems++; } for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) { if( !BG_InventoryContainsUpgrade( i, cg.snap->ps.stats ) ) continue; if( i == cg.weaponSelect - 32 ) selectedItem = numItems; CG_RegisterUpgrade( i ); items[ numItems ] = i + 32; numItems++; } for( i = 0; i < length; i++ ) { int displacement = i - selectWindow; int item = displacement + selectedItem; if( ( item >= 0 ) && ( item < numItems ) ) { trap_R_SetColor( color ); if( items[ item ] <= 32 ) CG_DrawPic( x, y, iconsize, iconsize, cg_weapons[ items[ item ] ].weaponIcon ); else if( items[ item ] > 32 ) CG_DrawPic( x, y, iconsize, iconsize, cg_upgrades[ items[ item ] - 32 ].upgradeIcon ); trap_R_SetColor( NULL ); } if( vertical ) y += iconsize; else x += iconsize; } }