/* ================= 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 ); } }
/** * @brief Checks the condition for G_RefillAmmo. */ static qboolean CanUseAmmoRefill( gentity_t *self ) { const weaponAttributes_t *wa; playerState_t *ps; if ( !self || !self->client ) { return qfalse; } ps = &self->client->ps; wa = BG_Weapon( ps->stats[ STAT_WEAPON ] ); if ( wa->infiniteAmmo ) { return qfalse; } if ( wa->maxClips == 0 ) { // clipless weapons can be refilled whenever they lack ammo return ( ps->ammo != wa->maxAmmo ); } else if ( ps->clips != wa->maxClips ) { // clip weapons have to miss a clip to be refillable return qtrue; } else { return qfalse; } }
static void CG_CompleteItem( void ) { int i = 0; if( cgs.clientinfo[ cg.clientNum ].team == TEAM_ALIENS ) { return; } trap_CompleteCallback( "weapon" ); for( i = 0; i < UP_NUM_UPGRADES; i++ ) { const upgradeAttributes_t *item = BG_Upgrade( i ); if ( item->usable ) { trap_CompleteCallback( item->name ); } } for( i = 0; i < WP_NUM_WEAPONS; i++ ) { const weaponAttributes_t *item = BG_Weapon( i ); if( item->team == TEAM_HUMANS ) { trap_CompleteCallback( item->name ); } } }
/** * @brief Refills clips on clip based weapons, refills charge on other weapons. * @param self * @param triggerEvent Trigger an event when relvant resource was modified. * @return Whether relevant resource was modified. */ qboolean G_RefillAmmo( gentity_t *self, qboolean triggerEvent ) { if ( !CanUseAmmoRefill( self ) ) { return qfalse; } self->client->lastAmmoRefillTime = level.time; if ( BG_Weapon( self->client->ps.stats[ STAT_WEAPON ] )->maxClips > 0 ) { GiveMaxClips( self ); if ( triggerEvent ) { G_AddEvent( self, EV_CLIPS_REFILL, 0 ); } } else { GiveFullClip( self ); if ( triggerEvent ) { G_AddEvent( self, EV_AMMO_REFILL, 0 ); } } return qtrue; }
static void CG_CompleteBuy( void ) { int i; if( cgs.clientinfo[ cg.clientNum ].team != TEAM_HUMANS ) { return; } for( i = 0; i < UP_NUM_UPGRADES; i++ ) { const upgradeAttributes_t *item = BG_Upgrade( i ); if ( item->purchasable && item->team == TEAM_HUMANS ) { trap_CompleteCallback( item->name ); } } trap_CompleteCallback( "grenade" ); // called "gren" elsewhere, so special-case it for( i = 0; i < WP_NUM_WEAPONS; i++ ) { const weaponAttributes_t *item = BG_Weapon( i ); if ( item->purchasable && item->team == TEAM_HUMANS ) { trap_CompleteCallback( item->name ); } } }
/* ================= 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 ); } }
static void CG_CompleteClass( void ) { int i = 0; if ( cgs.clientinfo[ cg.clientNum ].team == TEAM_ALIENS ) { for ( i = PCL_ALIEN_BUILDER0; i < PCL_HUMAN; i++ ) { trap_CompleteCallback( BG_Class( i )->name ); } } else if ( cgs.clientinfo[ cg.clientNum ].team == TEAM_HUMANS ) { trap_CompleteCallback( BG_Weapon( WP_HBUILD )->name ); trap_CompleteCallback( BG_Weapon( WP_MACHINEGUN )->name ); } }
/* =============== trigger_ammo_touch =============== */ void trigger_ammo_touch( gentity_t *self, gentity_t *other, trace_t *trace ) { int maxClips, maxAmmo; weapon_t weapon; if( !other->client ) return; if( other->client->ps.stats[ STAT_TEAM ] != TEAM_HUMANS ) return; if( self->timestamp > level.time ) return; if( other->client->ps.weaponstate != WEAPON_READY ) return; weapon = BG_PrimaryWeapon( other->client->ps.stats ); if( BG_Weapon( weapon )->usesEnergy && self->spawnflags & 2 ) return; if( !BG_Weapon( weapon )->usesEnergy && self->spawnflags & 4 ) return; if( self->spawnflags & 1 ) self->timestamp = level.time + 1000; else self->timestamp = level.time + FRAMETIME; maxAmmo = BG_Weapon( weapon )->maxAmmo; maxClips = BG_Weapon( weapon )->maxClips; if( ( other->client->ps.ammo + self->damage ) > maxAmmo ) { if( other->client->ps.clips < maxClips ) { other->client->ps.clips++; other->client->ps.ammo = 1; } else other->client->ps.ammo = maxAmmo; } else other->client->ps.ammo += self->damage; }
static void CG_CompleteClass() { int i = 0; if ( cgs.clientinfo[ cg.clientNum ].team == TEAM_ALIENS ) { // TODO: Add iterator for alien/human classes for ( i = PCL_ALIEN_BUILDER0; i < PCL_HUMAN_NAKED; i++ ) { trap_CompleteCallback( BG_Class( i )->name ); } } else if ( cgs.clientinfo[ cg.clientNum ].team == TEAM_HUMANS ) { trap_CompleteCallback( BG_Weapon( WP_HBUILD )->name ); trap_CompleteCallback( BG_Weapon( WP_MACHINEGUN )->name ); } }
static void CG_CompleteBuy_internal( bool negatives ) { int i; for( i = 0; i < UP_NUM_UPGRADES; i++ ) { const upgradeAttributes_t *item = BG_Upgrade( i ); if ( item->purchasable && item->team == TEAM_HUMANS ) { trap_CompleteCallback( item->name ); if ( negatives ) { trap_CompleteCallback( va( "-%s", item->name ) ); } } } trap_CompleteCallback( "grenade" ); // called "gren" elsewhere, so special-case it if ( negatives ) { trap_CompleteCallback( "-grenade" ); i = BG_GetPlayerWeapon( &cg.snap->ps ); } for( i = 0; i < WP_NUM_WEAPONS; i++ ) { const weaponAttributes_t *item = BG_Weapon( i ); if ( item->purchasable && item->team == TEAM_HUMANS ) { trap_CompleteCallback( item->name ); if ( negatives ) { trap_CompleteCallback( va( "-%s", BG_Weapon( i )->name ) ); } } } }
static const char *UnlockableHumanName( unlockable_t *unlockable ) { switch ( unlockable->type ) { case UNLT_WEAPON: return BG_Weapon( unlockable->num )->humanName; case UNLT_UPGRADE: return BG_Upgrade( unlockable->num )->humanName; case UNLT_BUILDABLE: return BG_Buildable( unlockable->num )->humanName; case UNLT_CLASS: return BG_ClassModelConfig( unlockable->num )->humanName; } Com_Error( ERR_FATAL, "UnlockableHumanName: Unlockable has unknown type" ); return nullptr; }
/* ================= CG_RegisterWeapon ================= */ void CG_RegisterWeapon( int weaponNum ) { weaponInfo_t *weaponInfo; char path[ MAX_QPATH ]; vec3_t mins, maxs; int i; if( weaponNum <= WP_NONE || weaponNum >= WP_NUM_WEAPONS ) { CG_Error( "CG_RegisterWeapon: out of range: %d", weaponNum ); return; } weaponInfo = &cg_weapons[ weaponNum ]; if( weaponInfo->registered ) { CG_Printf( "CG_RegisterWeapon: already registered: (%d) %s\n", weaponNum, BG_Weapon( weaponNum )->name ); return; } weaponInfo->registered = qtrue; if( !BG_Weapon( weaponNum )->name[ 0 ] ) CG_Error( "Couldn't find weapon %i", weaponNum ); Com_sprintf( path, MAX_QPATH, "models/weapons/%s/weapon.cfg", BG_Weapon( weaponNum )->name ); weaponInfo->humanName = BG_Weapon( weaponNum )->humanName; if( !CG_ParseWeaponFile( path, weaponInfo ) ) Com_Printf( S_COLOR_RED "ERROR: failed to parse %s\n", path ); // calc midpoint for rotation trap_R_ModelBounds( weaponInfo->weaponModel, mins, maxs ); for( i = 0 ; i < 3 ; i++ ) weaponInfo->weaponMidpoint[ i ] = mins[ i ] + 0.5 * ( maxs[ i ] - mins[ i ] ); }
/* ================== CG_PlayerIsBuilder ================== */ static qboolean CG_PlayerIsBuilder( buildable_t buildable ) { switch( cg.predictedPlayerState.weapon ) { case WP_ABUILD: case WP_HBUILD: return BG_Buildable( buildable )->team == BG_Weapon( cg.predictedPlayerState.weapon )->team; default: return qfalse; } }
static void CG_Rocket_DFCMArmouryBuyWeapon( int handle, const char *data ) { weapon_t weapon = (weapon_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 ]; weapon_t currentweapon = BG_PrimaryWeapon( ps->stats ); credits += BG_Weapon( currentweapon )->price; if( BG_InventoryContainsWeapon( weapon, cg.predictedPlayerState.stats ) ){ Class = "active"; action = va( "onClick='Cmd.exec(\"sell %s\")'", BG_Weapon( weapon )->name ); //Check mark icon. UTF-8 encoding of \uf00c Icon = "<icon class=\"current\">\xEF\x80\x8C</icon>"; } else if ( !BG_WeaponUnlocked( weapon ) || BG_WeaponDisabled( weapon ) ) { Class = "locked"; //Padlock icon. UTF-8 encoding of \uf023 Icon = "<icon>\xEF\x80\xA3</icon>"; } else if(BG_Weapon( weapon )->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_Weapon( weapon )->name ); } Rocket_DataFormatterFormattedData( handle, va( "<button class='armourybuy %s' onMouseover='Events.pushevent(\"setDS armouryBuyList weapons %s\", event)' %s>%s<img src='/%s'/></button>", Class, Info_ValueForKey( data, "2" ), action, Icon, CG_GetShaderNameFromHandle( cg_weapons[ weapon ].ammoIcon )), false ); }
/** * @brief Refills current ammo clip/charge. */ static void GiveFullClip( gentity_t *self ) { playerState_t *ps; const weaponAttributes_t *wa; if ( !self || !self->client ) { return; } ps = &self->client->ps; wa = BG_Weapon( ps->stats[ STAT_WEAPON ] ); ps->ammo = wa->maxAmmo; }
/** * @brief Attempts to refill ammo from a close source. * @return Whether ammo was refilled. */ qboolean G_FindAmmo( gentity_t *self ) { gentity_t *neighbor = NULL; qboolean foundSource = qfalse; // don't search for a source if refilling isn't possible if ( !CanUseAmmoRefill( self ) ) { return qfalse; } // search for ammo source while ( ( neighbor = G_IterateEntitiesWithinRadius( neighbor, self->s.origin, ENTITY_BUY_RANGE ) ) ) { // only friendly, living and powered buildables provide ammo if ( neighbor->s.eType != ET_BUILDABLE || !G_OnSameTeam( self, neighbor ) || !neighbor->spawned || !neighbor->powered || neighbor->health <= 0 ) { continue; } switch ( neighbor->s.modelindex ) { case BA_H_ARMOURY: foundSource = qtrue; break; case BA_H_REACTOR: case BA_H_REPEATER: if ( BG_Weapon( self->client->ps.stats[ STAT_WEAPON ] )->usesEnergy ) { foundSource = qtrue; } break; } } if ( foundSource ) { return G_RefillAmmo( self, qtrue ); } return qfalse; }
/* ====================================================================== BUILD GUN ====================================================================== */ void CheckCkitRepair( gentity_t *ent ) { vec3_t viewOrigin, forward, end; trace_t tr; gentity_t *traceEnt; int bHealth; //TODO find a solution to move dependency of ent->s.number and &ent->eventTime outside this function playerState_t *ps=&ent->client->ps; if ( ps->weaponTime > 0 || ps->stats[ STAT_MISC ] > 0 ) { return; } BG_GetClientViewOrigin( ps, viewOrigin ); AngleVectors( ps->viewangles, forward, NULL, NULL ); VectorMA( viewOrigin, 100, forward, end ); trap_Trace( &tr, viewOrigin, NULL, NULL, end, ent->s.number, MASK_PLAYERSOLID ); traceEnt = &g_entities[ tr.entityNum ]; if ( tr.fraction < 1.0f && traceEnt->spawned && traceEnt->health > 0 && traceEnt->s.eType == ET_BUILDABLE && traceEnt->buildableTeam == TEAM_HUMANS ) { bHealth = BG_Buildable( traceEnt->s.modelindex )->health; if ( traceEnt->health < bHealth ) { traceEnt->health += HBUILD_HEALRATE; if ( traceEnt->health >= bHealth ) { traceEnt->health = bHealth; G_AddPlayerEvent( ps, EV_BUILD_REPAIRED, 0, ent->s.number, &ent->eventTime ); } else { G_AddPlayerEvent( ps, EV_BUILD_REPAIR, 0, ent->s.number, &ent->eventTime ); } ps->weaponTime += BG_Weapon( ps->weapon )->repeatRate1; } } }
/* ====================================================================== BUILD GUN ====================================================================== */ void CheckCkitRepair( gentity_t *ent ) { vec3_t viewOrigin, forward, end; trace_t tr; gentity_t *traceEnt; int bHealth; if ( ent->client->ps.weaponTime > 0 || ent->client->ps.stats[ STAT_MISC ] > 0 ) { return; } BG_GetClientViewOrigin( &ent->client->ps, viewOrigin ); AngleVectors( ent->client->ps.viewangles, forward, NULL, NULL ); VectorMA( viewOrigin, 100, forward, end ); trap_Trace( &tr, viewOrigin, NULL, NULL, end, ent->s.number, MASK_PLAYERSOLID ); traceEnt = &g_entities[ tr.entityNum ]; if ( tr.fraction < 1.0f && traceEnt->spawned && traceEnt->health > 0 && traceEnt->s.eType == ET_BUILDABLE && traceEnt->buildableTeam == TEAM_HUMANS ) { bHealth = BG_Buildable( traceEnt->s.modelindex )->health; if ( traceEnt->health < bHealth ) { traceEnt->health += HBUILD_HEALRATE; if ( traceEnt->health >= bHealth ) { traceEnt->health = bHealth; G_AddEvent( ent, EV_BUILD_REPAIRED, 0 ); } else { G_AddEvent( ent, EV_BUILD_REPAIR, 0 ); } ent->client->ps.weaponTime += BG_Weapon( ent->client->ps.weapon )->repeatRate1; } } }
void G_CheckCkitRepair( gentity_t *self ) { vec3_t viewOrigin, forward, end; trace_t tr; gentity_t *traceEnt; if ( self->client->ps.weaponTime > 0 || self->client->ps.stats[ STAT_MISC ] > 0 ) { return; } BG_GetClientViewOrigin( &self->client->ps, viewOrigin ); AngleVectors( self->client->ps.viewangles, forward, NULL, NULL ); VectorMA( viewOrigin, 100, forward, end ); trap_Trace( &tr, viewOrigin, NULL, NULL, end, self->s.number, MASK_PLAYERSOLID ); traceEnt = &g_entities[ tr.entityNum ]; if ( tr.fraction < 1.0f && traceEnt->spawned && traceEnt->health > 0 && traceEnt->s.eType == ET_BUILDABLE && traceEnt->buildableTeam == TEAM_HUMANS ) { const buildableAttributes_t *buildable; buildable = BG_Buildable( traceEnt->s.modelindex ); if ( traceEnt->health < buildable->health ) { if ( G_Heal( traceEnt, HBUILD_HEALRATE ) ) { G_AddEvent( self, EV_BUILD_REPAIR, 0 ); } else { G_AddEvent( self, EV_BUILD_REPAIRED, 0 ); } self->client->ps.weaponTime += BG_Weapon( self->client->ps.weapon )->repeatRate1; } } }
void G_CheckCkitRepair( gentity_t *self ) { vec3_t viewOrigin, forward, end; trace_t tr; gentity_t *traceEnt; if ( self->client->ps.weaponTime > 0 || self->client->ps.stats[ STAT_MISC ] > 0 ) { return; } BG_GetClientViewOrigin( &self->client->ps, viewOrigin ); AngleVectors( self->client->ps.viewangles, forward, nullptr, nullptr ); VectorMA( viewOrigin, 100, forward, end ); trap_Trace( &tr, viewOrigin, nullptr, nullptr, end, self->s.number, MASK_PLAYERSOLID, 0 ); traceEnt = &g_entities[ tr.entityNum ]; if ( tr.fraction < 1.0f && traceEnt->spawned && traceEnt->s.eType == ET_BUILDABLE && traceEnt->buildableTeam == TEAM_HUMANS ) { HealthComponent *healthComponent = traceEnt->entity->Get<HealthComponent>(); if (healthComponent && healthComponent->Alive() && !healthComponent->FullHealth()) { traceEnt->entity->Heal(HBUILD_HEALRATE, nullptr); if (healthComponent->FullHealth()) { G_AddEvent(self, EV_BUILD_REPAIRED, 0); } else { G_AddEvent(self, EV_BUILD_REPAIR, 0); } self->client->ps.weaponTime += BG_Weapon( self->client->ps.weapon )->repeatRate1; } } }
bool AnimDelta::LoadData(clientInfo_t* ci) { char newModelName[ MAX_QPATH ]; // special handling for human_(naked|light|medium) if ( !Q_stricmp( ci->modelName, "human_naked" ) || !Q_stricmp( ci->modelName, "human_light" ) || !Q_stricmp( ci->modelName, "human_medium" ) ) { Q_strncpyz( newModelName, "human_nobsuit_common", sizeof( newModelName ) ); } else { Q_strncpyz( newModelName, ci->modelName, sizeof( newModelName ) ); } refSkeleton_t base; refSkeleton_t delta; for ( int i = WP_NONE + 1; i < WP_NUM_WEAPONS; ++i ) { int handle = LoadDeltaAnimation( static_cast<weapon_t>( i ), newModelName, ci->iqm ); if ( !handle ) continue; Log::Debug("Loaded delta for %s %s", newModelName, BG_Weapon( i )->humanName); trap_R_BuildSkeleton( &delta, handle, 1, 1, 0, false ); // Derive the delta from the base stand animation. trap_R_BuildSkeleton( &base, ci->animations[ TORSO_STAND ].handle, 1, 1, 0, false ); auto ret = deltas_.insert( std::make_pair( i, std::vector<delta_t>( boneIndicies_.size() ) ) ); auto& weaponDeltas = ret.first->second; for ( size_t j = 0; j < boneIndicies_.size(); ++j ) { VectorSubtract( delta.bones[ boneIndicies_[ j ] ].t.trans, base.bones[ boneIndicies_[ j ] ].t.trans, weaponDeltas[ j ].delta ); QuatInverse( base.bones[ boneIndicies_[ j ] ].t.rot ); QuatMultiply( base.bones[ boneIndicies_[ j ] ].t.rot, delta.bones[ boneIndicies_[ j ] ].t.rot, weaponDeltas[ j ].rot ); } } return true; }
static int CG_CalcFov( void ) { float y; float phase; float v; int contents; float fov_x, fov_y; float zoomFov; float f; int inwater; int attribFov; usercmd_t cmd; usercmd_t oldcmd; int cmdNum; cmdNum = trap_GetCurrentCmdNumber( ); trap_GetUserCmd( cmdNum, &cmd ); trap_GetUserCmd( cmdNum - 1, &oldcmd ); // switch follow modes if necessary: cycle between free -> follow -> third-person follow if( cmd.buttons & BUTTON_USE_HOLDABLE && !( oldcmd.buttons & BUTTON_USE_HOLDABLE ) ) { if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) { if( !cg.chaseFollow ) cg.chaseFollow = qtrue; else { cg.chaseFollow = qfalse; trap_SendClientCommand( "follow\n" ); } } else if ( cg.snap->ps.persistant[ PERS_SPECSTATE ] != SPECTATOR_NOT ) trap_SendClientCommand( "follow\n" ); } if( cg.predictedPlayerState.pm_type == PM_INTERMISSION || ( cg.snap->ps.persistant[ PERS_SPECSTATE ] != SPECTATOR_NOT ) || ( cg.renderingThirdPerson ) ) { // if in intermission or third person, use a fixed value fov_y = BASE_FOV_Y; } else { // don't lock the fov globally - we need to be able to change it attribFov = BG_Class( cg.predictedPlayerState.stats[ STAT_CLASS ] )->fov * 0.75f; fov_y = attribFov; if ( fov_y < 1.0f ) fov_y = 1.0f; else if ( fov_y > MAX_FOV_Y ) fov_y = MAX_FOV_Y; if( cg.spawnTime > ( cg.time - FOVWARPTIME ) && BG_ClassHasAbility( cg.predictedPlayerState.stats[ STAT_CLASS ], SCA_FOVWARPS ) ) { float fraction = (float)( cg.time - cg.spawnTime ) / FOVWARPTIME; fov_y = MAX_FOV_WARP_Y - ( ( MAX_FOV_WARP_Y - fov_y ) * fraction ); } // account for zooms zoomFov = BG_Weapon( cg.predictedPlayerState.weapon )->zoomFov * 0.75f; if ( zoomFov < 1.0f ) zoomFov = 1.0f; else if ( zoomFov > attribFov ) zoomFov = attribFov; // only do all the zoom stuff if the client CAN zoom // FIXME: zoom control is currently hard coded to BUTTON_ATTACK2 if( BG_Weapon( cg.predictedPlayerState.weapon )->canZoom ) { if ( cg.zoomed ) { f = ( cg.time - cg.zoomTime ) / (float)ZOOM_TIME; if ( f > 1.0f ) fov_y = zoomFov; else fov_y = fov_y + f * ( zoomFov - fov_y ); // BUTTON_ATTACK2 isn't held so unzoom next time if( !( cmd.buttons & BUTTON_ATTACK2 ) ) { cg.zoomed = qfalse; cg.zoomTime = MIN( cg.time, cg.time + cg.time - cg.zoomTime - ZOOM_TIME ); } } else { f = ( cg.time - cg.zoomTime ) / (float)ZOOM_TIME; if ( f <= 1.0f ) fov_y = zoomFov + f * ( fov_y - zoomFov ); // BUTTON_ATTACK2 is held so zoom next time if( cmd.buttons & BUTTON_ATTACK2 ) { cg.zoomed = qtrue; cg.zoomTime = MIN( cg.time, cg.time + cg.time - cg.zoomTime - ZOOM_TIME ); } } } } y = cg.refdef.height / tan( 0.5f * DEG2RAD( fov_y ) ); fov_x = atan2( cg.refdef.width, y ); fov_x = 2.0f * RAD2DEG( fov_x ); // warp if underwater contents = CG_PointContents( cg.refdef.vieworg, -1 ); if( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) { phase = cg.time / 1000.0f * WAVE_FREQUENCY * M_PI * 2.0f; v = WAVE_AMPLITUDE * sin( phase ); fov_x += v; fov_y -= v; inwater = qtrue; } else inwater = qfalse; if( ( cg.predictedPlayerEntity.currentState.eFlags & EF_POISONCLOUDED ) && ( cg.time - cg.poisonedTime < PCLOUD_DISORIENT_DURATION) && cg.predictedPlayerState.stats[ STAT_HEALTH ] > 0 && !( cg.snap->ps.pm_flags & PMF_FOLLOW ) ) { float scale = 1.0f - (float)( cg.time - cg.poisonedTime ) / BG_PlayerPoisonCloudTime( &cg.predictedPlayerState ); phase = ( cg.time - cg.poisonedTime ) / 1000.0f * PCLOUD_ZOOM_FREQUENCY * M_PI * 2.0f; v = PCLOUD_ZOOM_AMPLITUDE * sin( phase ) * scale; fov_x += v; fov_y += v; } // set it cg.refdef.fov_x = fov_x; cg.refdef.fov_y = fov_y; if( !cg.zoomed ) cg.zoomSensitivity = 1.0f; else cg.zoomSensitivity = cg.refdef.fov_y / 75.0f; return inwater; }
void BG_ImportUnlockablesFromMask( int team, int mask ) { int unlockableNum, teamUnlockableNum = 0, itemNum = 0, unlockThreshold; unlockable_t *unlockable; int unlockableType = 0; team_t currentTeam; bool newStatus; int statusChanges[ NUM_UNLOCKABLES ]; #ifdef BUILD_CGAME int statusChangeCount = 0; #endif // maintain a cache to prevent redundant imports static int lastMask = 0; static team_t lastTeam = TEAM_NONE; // just import if data is unavailable, cached mask is outdated or team has changed if ( unlockablesDataAvailable && team == lastTeam && mask == lastMask ) { return; } // cache input lastMask = mask; lastTeam = (team_t) team; // no status change yet memset( statusChanges, 0, sizeof( statusChanges ) ); for ( unlockableNum = 0; unlockableNum < NUM_UNLOCKABLES; unlockableNum++ ) { unlockable = &unlockables[ unlockableNum ]; // also iterate over item types, itemNum is a per-type counter if ( unlockableType < UNLT_NUM_UNLOCKABLETYPES - 1 && unlockableNum == unlockablesTypeOffset[ unlockableType + 1 ] ) { unlockableType++; itemNum = 0; } switch ( unlockableType ) { case UNLT_WEAPON: currentTeam = BG_Weapon( itemNum )->team; unlockThreshold = BG_Weapon( itemNum )->unlockThreshold; break; case UNLT_UPGRADE: currentTeam = TEAM_HUMANS; unlockThreshold = BG_Upgrade( itemNum )->unlockThreshold; break; case UNLT_BUILDABLE: currentTeam = BG_Buildable( itemNum )->team; unlockThreshold = BG_Buildable( itemNum )->unlockThreshold; break; case UNLT_CLASS: currentTeam = TEAM_ALIENS; unlockThreshold = BG_Class( itemNum )->unlockThreshold; break; default: Com_Error( ERR_FATAL, "BG_ImportUnlockablesFromMask: Unknown unlockable type" ); } unlockThreshold = MAX( unlockThreshold, 0 ); unlockable->type = unlockableType; unlockable->num = itemNum; unlockable->team = currentTeam; unlockable->unlockThreshold = unlockThreshold; unlockable->lockThreshold = UnlockToLockThreshold( unlockThreshold ); // retrieve the item's locking state if ( !unlockThreshold ) { unlockable->statusKnown = true; unlockable->unlocked = true; } else if ( currentTeam == team ) { newStatus = mask & ( 1 << teamUnlockableNum ); #ifdef BUILD_CGAME // notify client about single status change if ( unlockablesTeamKnowledge == team && unlockable->statusKnown && unlockable->unlocked != newStatus ) { InformUnlockableStatusChange( unlockable, newStatus ); statusChanges[ unlockableNum ] = newStatus ? 1 : -1; statusChangeCount++; } #endif unlockable->statusKnown = true; unlockable->unlocked = newStatus; teamUnlockableNum++; } else { unlockable->statusKnown = false; unlockable->unlocked = false; } itemNum++; } #ifdef BUILD_CGAME // notify client about all status changes if ( statusChangeCount ) { InformUnlockableStatusChanges( statusChanges, statusChangeCount ); } // export team and mask into cvar for UI trap_Cvar_Set( "ui_unlockables", va( "%d %d", team, mask ) ); #endif // we only know the state for one team unlockablesDataAvailable = true; unlockablesTeamKnowledge = (team_t) team; // save mask for later use unlockablesMask[ team ] = mask; }
/* =========== 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; int flags; int savedPing; int teamLocal; int eventSequence; char userinfo[ MAX_INFO_STRING ]; vec3_t up = { 0.0f, 0.0f, 1.0f }; int maxAmmo, maxClips; weapon_t weapon; 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 ) { //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; } } // 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->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; 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 { 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 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 ); client->ps.weapon = client->ps.stats[ STAT_WEAPON ]; } // 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 ); client->pers.infoChangeTime = level.time; }
void G_UpdateUnlockables() { int itemNum = 0, unlockableNum, unlockThreshold; float momentum; unlockable_t *unlockable; int unlockableType = 0; team_t team; for ( unlockableNum = 0; unlockableNum < NUM_UNLOCKABLES; unlockableNum++ ) { unlockable = &unlockables[ unlockableNum ]; // also iterate over item types, itemNum is a per-type counter while ( unlockableType < UNLT_NUM_UNLOCKABLETYPES - 1 && unlockableNum == unlockablesTypeOffset[ unlockableType + 1 ] ) { unlockableType++; itemNum = 0; } switch ( unlockableType ) { case UNLT_WEAPON: team = BG_Weapon( itemNum )->team; unlockThreshold = BG_Weapon( itemNum )->unlockThreshold; break; case UNLT_UPGRADE: team = TEAM_HUMANS; unlockThreshold = BG_Upgrade( itemNum )->unlockThreshold; break; case UNLT_BUILDABLE: team = BG_Buildable( itemNum )->team; unlockThreshold = BG_Buildable( itemNum )->unlockThreshold; break; case UNLT_CLASS: team = TEAM_ALIENS; unlockThreshold = BG_Class( itemNum )->unlockThreshold; break; default: Com_Error( ERR_FATAL, "G_UpdateUnlockables: Unknown unlockable type" ); } unlockThreshold = MAX( unlockThreshold, 0 ); momentum = level.team[ team ].momentum; unlockable->type = unlockableType; unlockable->num = itemNum; unlockable->team = team; unlockable->statusKnown = true; unlockable->unlockThreshold = unlockThreshold; unlockable->lockThreshold = UnlockToLockThreshold( unlockThreshold ); // calculate the item's locking state unlockable->unlocked = ( !unlockThreshold || momentum >= unlockThreshold || ( unlockable->unlocked && momentum >= unlockable->lockThreshold ) ); itemNum++; /*Com_Printf( "G_UpdateUnlockables: Team %s, Type %s, Item %s, Momentum %d, Threshold %d, " "Unlocked %d, Synchronize %d\n", BG_TeamName( team ), UnlockableTypeName( unlockable ), UnlockableName( unlockable ), momentum, unlockThreshold, unlockable->unlocked, unlockable->synchronize );*/ } // GAME knows about all teams unlockablesDataAvailable = true; unlockablesTeamKnowledge = TEAM_ALL; // generate masks for network transmission UpdateUnlockablesMask(); }
// TODO: Clean this mess further (split into helper functions) void G_Damage( gentity_t *target, gentity_t *inflictor, gentity_t *attacker, vec3_t dir, vec3_t point, int damage, int damageFlags, int mod ) { gclient_t *client; int take, loss; int knockback; float modifier; if ( !target || !target->takedamage || target->health <= 0 || level.intermissionQueued ) { return; } client = target->client; // don't handle noclip clients if ( client && client->noclip ) { return; } // set inflictor to world if missing if ( !inflictor ) { inflictor = &g_entities[ ENTITYNUM_WORLD ]; } // set attacker to world if missing if ( !attacker ) { attacker = &g_entities[ ENTITYNUM_WORLD ]; } // don't handle ET_MOVER w/o die or pain function if ( target->s.eType == ET_MOVER && !( target->die || target->pain ) ) { // special case for ET_MOVER with act function in initial position if ( ( target->moverState == MOVER_POS1 || target->moverState == ROTATOR_POS1 ) && target->act ) { target->act( target, inflictor, attacker ); } return; } // do knockback against clients if ( client && !( damageFlags & DAMAGE_NO_KNOCKBACK ) && dir ) { // scale knockback by weapon if ( inflictor->s.weapon != WP_NONE ) { knockback = ( int )( ( float )damage * BG_Weapon( inflictor->s.weapon )->knockbackScale ); } else { knockback = damage; } // apply generic damage to knockback modifier knockback *= DAMAGE_TO_KNOCKBACK; // HACK: Too much knockback from falling makes you bounce and looks silly if ( mod == MOD_FALLING ) { knockback = MIN( knockback, MAX_FALLDMG_KNOCKBACK ); } G_KnockbackByDir( target, dir, knockback, qfalse ); } else { // damage knockback gets saved, so initialize it here knockback = 0; } // godmode prevents damage if ( target->flags & FL_GODMODE ) { return; } // check for protection if ( !( damageFlags & DAMAGE_NO_PROTECTION ) ) { // check for protection from friendly damage if ( target != attacker && G_OnSameTeam( target, attacker ) ) { // check if friendly fire has been disabled if ( !g_friendlyFire.integer ) { return; } // don't do friendly damage on movement attacks switch ( mod ) { case MOD_LEVEL3_POUNCE: case MOD_LEVEL4_TRAMPLE: return; default: break; } // if dretchpunt is enabled and this is a dretch, do dretchpunt instead of damage if ( g_dretchPunt.integer && target->client && ( target->client->ps.stats[ STAT_CLASS ] == PCL_ALIEN_LEVEL0 || target->client->ps.stats[ STAT_CLASS ] == PCL_ALIEN_LEVEL0_UPG ) ) { vec3_t dir, push; VectorSubtract( target->r.currentOrigin, attacker->r.currentOrigin, dir ); VectorNormalizeFast( dir ); VectorScale( dir, ( damage * 10.0f ), push ); push[ 2 ] = 64.0f; VectorAdd( target->client->ps.velocity, push, target->client->ps.velocity ); return; } } // for buildables, never protect from damage dealt by building actions if ( target->s.eType == ET_BUILDABLE && attacker->client && mod != MOD_DECONSTRUCT && mod != MOD_SUICIDE && mod != MOD_REPLACE && mod != MOD_NOCREEP ) { // check for protection from friendly buildable damage if ( G_OnSameTeam( target, attacker ) && !g_friendlyBuildableFire.integer ) { return; } } } // update combat timers if ( target->client && attacker->client && target != attacker ) { target->client->lastCombatTime = level.time; attacker->client->lastCombatTime = level.time; } if ( client ) { // save damage (w/o armor modifier), knockback client->damage_received += damage; client->damage_knockback += knockback; // save damage direction if ( dir ) { VectorCopy( dir, client->damage_from ); client->damage_fromWorld = qfalse; } else { VectorCopy( target->r.currentOrigin, client->damage_from ); client->damage_fromWorld = qtrue; } // drain jetpack fuel client->ps.stats[ STAT_FUEL ] -= damage * JETPACK_FUEL_PER_DMG; if ( client->ps.stats[ STAT_FUEL ] < 0 ) { client->ps.stats[ STAT_FUEL ] = 0; } // apply damage modifier modifier = CalcDamageModifier( point, target, (class_t) client->ps.stats[ STAT_CLASS ], damageFlags ); take = ( int )( ( float )damage * modifier + 0.5f ); // if boosted poison every attack if ( attacker->client && ( attacker->client->ps.stats[ STAT_STATE ] & SS_BOOSTED ) && target->client->pers.team == TEAM_HUMANS && target->client->poisonImmunityTime < level.time ) { switch ( mod ) { case MOD_POISON: case MOD_LEVEL1_PCLOUD: case MOD_LEVEL2_ZAP: break; default: target->client->ps.stats[ STAT_STATE ] |= SS_POISONED; target->client->lastPoisonTime = level.time; target->client->lastPoisonClient = attacker; } } } else { take = damage; } // make sure damage is done if ( take < 1 ) { take = 1; } if ( g_debugDamage.integer > 0 ) { G_Printf( "G_Damage: %3i (%3i → %3i)\n", take, target->health, target->health - take ); } // do the damage target->health = target->health - take; if ( target->client ) { target->client->ps.stats[ STAT_HEALTH ] = target->health; target->client->pers.infoChangeTime = level.time; // ? } target->lastDamageTime = level.time; // TODO: gentity_t->nextRegenTime only affects alien clients, remove it and use lastDamageTime // Optionally (if needed for some reason), move into client struct and add "Alien" to name target->nextRegenTime = level.time + ALIEN_CLIENT_REGEN_WAIT; // handle non-self damage if ( attacker != target ) { if ( target->health < 0 ) { loss = ( take + target->health ); } else { loss = take; } if ( attacker->client ) { // add to the attacker's account on the target target->credits[ attacker->client->ps.clientNum ] += ( float )loss; // notify the attacker of a hit NotifyClientOfHit( attacker ); } // update buildable stats if ( attacker->s.eType == ET_BUILDABLE && attacker->health > 0 ) { attacker->buildableStatsTotal += loss; } } // handle dying target if ( target->health <= 0 ) { // set no knockback flag for clients if ( client ) { target->flags |= FL_NO_KNOCKBACK; } // cap negative health if ( target->health < -999 ) { target->health = -999; } // call die function if ( target->die ) { target->die( target, inflictor, attacker, mod ); } // update buildable stats if ( attacker->s.eType == ET_BUILDABLE && attacker->health > 0 ) { attacker->buildableStatsCount++; } // for non-client victims, fire ON_DIE event if( !target->client ) { G_EventFireEntity( target, attacker, ON_DIE ); } return; } else if ( target->pain ) { target->pain( target, attacker, take ); } }
static int CG_CalcFov( void ) { float y; float phase; float v; int contents; float fov_x, fov_y; float zoomFov; float f; int inwater; int attribFov; usercmd_t cmd; usercmd_t oldcmd; int cmdNum; cmdNum = trap_GetCurrentCmdNumber(); trap_GetUserCmd( cmdNum, &cmd ); trap_GetUserCmd( cmdNum - 1, &oldcmd ); // switch follow modes if necessary: cycle between free -> follow -> third-person follow if ( usercmdButtonPressed( cmd.buttons, BUTTON_USE_HOLDABLE ) && !usercmdButtonPressed( oldcmd.buttons, BUTTON_USE_HOLDABLE ) ) { if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) { if ( !cg.chaseFollow ) { cg.chaseFollow = qtrue; } else { cg.chaseFollow = qfalse; trap_SendClientCommand( "follow\n" ); } } else if ( cg.snap->ps.persistant[ PERS_SPECSTATE ] != SPECTATOR_NOT ) { trap_SendClientCommand( "follow\n" ); } } if ( cg.predictedPlayerState.pm_type == PM_INTERMISSION || ( cg.snap->ps.persistant[ PERS_SPECSTATE ] != SPECTATOR_NOT ) || ( cg.renderingThirdPerson ) ) { // if in intermission or third person, use a fixed value fov_y = BASE_FOV_Y; } else { // don't lock the fov globally - we need to be able to change it if ( ( attribFov = trap_Cvar_VariableIntegerValue( BG_Class( cg.predictedPlayerState.stats[ STAT_CLASS ] )->fovCvar ) ) ) { if ( attribFov < 80 ) { attribFov = 80; } else if ( attribFov >= 140 ) { attribFov = 140; } } else { attribFov = BG_Class( cg.predictedPlayerState.stats[ STAT_CLASS ] )->fov; } attribFov *= 0.75; fov_y = attribFov; if ( fov_y < 1.0f ) { fov_y = 1.0f; } else if ( fov_y > MAX_FOV_Y ) { fov_y = MAX_FOV_Y; } if ( cg.spawnTime > ( cg.time - FOVWARPTIME ) && BG_ClassHasAbility( cg.predictedPlayerState.stats[ STAT_CLASS ], SCA_FOVWARPS ) ) { float fraction = ( float )( cg.time - cg.spawnTime ) / FOVWARPTIME; fov_y = MAX_FOV_WARP_Y - ( ( MAX_FOV_WARP_Y - fov_y ) * fraction ); } // account for zooms zoomFov = BG_Weapon( cg.predictedPlayerState.weapon )->zoomFov * 0.75f; if ( zoomFov < 1.0f ) { zoomFov = 1.0f; } else if ( zoomFov > attribFov ) { zoomFov = attribFov; } // only do all the zoom stuff if the client CAN zoom // FIXME: zoom control is currently hard coded to WBUTTON_ATTACK2 if ( BG_Weapon( cg.predictedPlayerState.weapon )->canZoom ) { if ( cg.zoomed ) { f = ( cg.time - cg.zoomTime ) / ( float ) ZOOM_TIME; if ( f > 1.0f ) { fov_y = zoomFov; } else { fov_y = fov_y + f * ( zoomFov - fov_y ); } // WBUTTON_ATTACK2 isn't held so unzoom next time if ( !usercmdButtonPressed( cmd.buttons, BUTTON_ATTACK2 ) || cg.snap->ps.weaponstate == WEAPON_RELOADING ) { cg.zoomed = qfalse; cg.zoomTime = MIN( cg.time, cg.time + cg.time - cg.zoomTime - ZOOM_TIME ); } } else { f = ( cg.time - cg.zoomTime ) / ( float ) ZOOM_TIME; if ( f < 1.0f ) { fov_y = zoomFov + f * ( fov_y - zoomFov ); } // WBUTTON_ATTACK2 is held so zoom next time if ( usercmdButtonPressed( cmd.buttons, BUTTON_ATTACK2 ) && cg.snap->ps.weaponstate != WEAPON_RELOADING ) { cg.zoomed = qtrue; cg.zoomTime = MIN( cg.time, cg.time + cg.time - cg.zoomTime - ZOOM_TIME ); } } } else if ( cg.zoomed ) { cg.zoomed = qfalse; } } y = cg.refdef.height / tan( 0.5f * DEG2RAD( fov_y ) ); fov_x = atan2( cg.refdef.width, y ); fov_x = 2.0f * RAD2DEG( fov_x ); // warp if underwater contents = CG_PointContents( cg.refdef.vieworg, -1 ); if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) { phase = cg.time / 1000.0f * WAVE_FREQUENCY * M_PI * 2.0f; v = WAVE_AMPLITUDE * sin( phase ); fov_x += v; fov_y -= v; inwater = qtrue; } else { inwater = qfalse; } // set it cg.refdef.fov_x = fov_x; cg.refdef.fov_y = fov_y; if ( !cg.zoomed ) { cg.zoomSensitivity = 1.0f; } else { cg.zoomSensitivity = cg.refdef.fov_y / 75.0f; } return inwater; }
/* ================================================================================= trigger_ammo ================================================================================= */ void env_afx_ammo_touch( gentity_t *self, gentity_t *other, trace_t* ) { int maxClips, maxAmmo; weapon_t weapon; if ( !other->client ) { return; } if ( other->client->pers.team != TEAM_HUMANS ) { return; } if ( self->timestamp > level.time ) { return; } if ( other->client->ps.weaponstate != WEAPON_READY ) { return; } weapon = BG_PrimaryWeapon( other->client->ps.stats ); if ( BG_Weapon( weapon )->usesEnergy && ( self->spawnflags & 2 ) ) { return; } if ( !BG_Weapon( weapon )->usesEnergy && ( self->spawnflags & 4 ) ) { return; } if ( self->spawnflags & 1 ) { self->timestamp = level.time + 1000; } else { self->timestamp = level.time + FRAMETIME; } maxAmmo = BG_Weapon( weapon )->maxAmmo; maxClips = BG_Weapon( weapon )->maxClips; if ( ( other->client->ps.ammo + self->config.amount ) > maxAmmo ) { if ( other->client->ps.clips < maxClips ) { other->client->ps.clips++; other->client->ps.ammo = 1; } else { other->client->ps.ammo = maxAmmo; } } else { other->client->ps.ammo += self->config.amount; } }
/* =========== ClientSpawn Called every time a client is placed fresh in the world: after the first ClientBegin, and after each respawn and evolve Initializes all non-persistent parts of playerState ============ */ void ClientSpawn( gentity_t *ent, gentity_t *spawn, const vec3_t origin, const vec3_t angles ) { int index; vec3_t spawn_origin, spawn_angles; gclient_t *client; int i; clientPersistant_t saved; clientSession_t savedSess; bool savedNoclip, savedCliprcontents; int persistant[ MAX_PERSISTANT ]; gentity_t *spawnPoint = nullptr; int flags; int savedPing; int teamLocal; int eventSequence; char userinfo[ MAX_INFO_STRING ]; vec3_t up = { 0.0f, 0.0f, 1.0f }; int maxAmmo, maxClips; weapon_t weapon; ClientSpawnCBSE(ent, ent == spawn); index = ent - g_entities; client = ent->client; teamLocal = client->pers.team; //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 != nullptr ) { VectorCopy( origin, spawn_origin ); } if ( angles != nullptr ) { 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 == nullptr ) { Com_Error(errorParm_t::ERR_DROP, "ClientSpawn: spawn is NULL" ); } spawnPoint = spawn; if ( spawnPoint->s.eType == entityType_t::ET_BUILDABLE ) { G_SetBuildableAnim( spawnPoint, BANIM_SPAWN1, true ); if ( spawnPoint->buildableTeam == TEAM_ALIENS ) { spawnPoint->clientSpawnTime = ALIEN_SPAWN_REPEAT_TIME; } else if ( spawnPoint->buildableTeam == TEAM_HUMANS ) { spawnPoint->clientSpawnTime = HUMAN_SPAWN_REPEAT_TIME; } } } // 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 persistent data saved = client->pers; savedSess = client->sess; savedPing = client->ps.ping; savedNoclip = client->noclip; savedCliprcontents = client->cliprcontents; 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->noclip = savedNoclip; client->cliprcontents = savedCliprcontents; 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; //Log::Notice( "ent->client->pers->pclass = %i\n", ent->client->pers.classSelection ); ent->s.groundEntityNum = ENTITYNUM_NONE; ent->client = &level.clients[ index ]; ent->classname = S_PLAYER_CLASSNAME; if ( client->noclip ) { client->cliprcontents = CONTENTS_BODY; } else { ent->r.contents = CONTENTS_BODY; } ent->clipmask = MASK_PLAYERSOLID; ent->die = G_PlayerDie; ent->waterlevel = 0; ent->watertype = 0; ent->flags &= FL_GODMODE | FL_NOTARGET; // calculate each client's acceleration ent->evaluateAcceleration = true; client->ps.stats[ STAT_MISC ] = 0; client->ps.eFlags = flags; client->ps.clientNum = index; BG_ClassBoundingBox( ent->client->pers.classSelection, ent->r.mins, ent->r.maxs, nullptr, nullptr, nullptr ); // clear entity values if ( ent->client->pers.classSelection == PCL_HUMAN_NAKED ) { 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; client->ps.persistant[ PERS_TEAM ] = client->pers.team; // TODO: Check whether stats can be cleared at once instead of per field client->ps.stats[ STAT_STAMINA ] = STAMINA_MAX; client->ps.stats[ STAT_FUEL ] = JETPACK_FUEL_MAX; client->ps.stats[ STAT_CLASS ] = ent->client->pers.classSelection; client->ps.stats[ STAT_BUILDABLE ] = BA_NONE; client->ps.stats[ STAT_PREDICTION ] = 0; client->ps.stats[ STAT_STATE ] = 0; VectorSet( client->ps.grapplePoint, 0.0f, 0.0f, 1.0f ); //clear the credits array // TODO: Handle in HealthComponent or ClientComponent. for ( i = 0; i < MAX_CLIENTS; i++ ) { ent->credits[ i ].value = 0.0f; ent->credits[ i ].time = 0; ent->credits[ i ].team = TEAM_NONE; } G_SetOrigin( ent, spawn_origin ); VectorCopy( spawn_origin, client->ps.origin ); //give aliens some spawn velocity if ( client->sess.spectatorState == SPECTATOR_NOT && client->pers.team == TEAM_ALIENS ) { if ( ent == spawn ) { //evolution particle system G_AddPredictableEvent( ent, EV_ALIEN_EVOLVE, DirToByte( up ) ); } else { spawn_angles[ YAW ] += 180.0f; AngleNormalize360( spawn_angles[ YAW ] ); if ( spawnPoint->s.origin2[ 2 ] > 0.0f ) { vec3_t forward, dir; AngleVectors( spawn_angles, forward, nullptr, nullptr ); VectorAdd( spawnPoint->s.origin2, forward, dir ); VectorNormalize( dir ); VectorScale( dir, BG_Class( ent->client->pers.classSelection )->jumpMagnitude, client->ps.velocity ); } G_AddPredictableEvent( ent, EV_PLAYER_RESPAWN, 0 ); } } else if ( client->sess.spectatorState == SPECTATOR_NOT && client->pers.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.team == 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; usercmdClearButtons( client->latched_buttons ); // 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 && spawnPoint ) { G_EventFireEntity( spawnPoint, ent, ON_SPAWN ); } // 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; } } } // 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, true ); 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, true ); client->pers.infoChangeTime = level.time; // (re)tag the client for its team Beacon::DeleteTags( ent ); Beacon::Tag( ent, (team_t)ent->client->ps.persistant[ PERS_TEAM ], true ); }
/* =============== 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" ) )); } }