/* * G_Chase_IsValidTarget */ static bool G_Chase_IsValidTarget( edict_t *ent, edict_t *target, bool teamonly ) { if( !ent || !target ) { return false; } if( !target->r.inuse || !target->r.client || trap_GetClientState( PLAYERNUM( target ) ) < CS_SPAWNED ) { return false; } if( target->s.team < TEAM_PLAYERS || target->s.team >= GS_MAX_TEAMS || target == ent ) { return false; } if( teamonly && !ent->r.client->teamstate.is_coach && G_ISGHOSTING( target ) ) { return false; } if( teamonly && target->s.team != ent->s.team ) { return false; } if( G_ISGHOSTING( target ) && !target->deadflag && target->s.team != TEAM_SPECTATOR ) { return false; // ghosts that are neither dead, nor speccing (probably originating from gt-specific rules) } return true; }
float BOT_DMclass_PlayerWeight( edict_t *self, edict_t *enemy ) { bool rage_mode = false; if( !enemy || enemy == self ) return 0; if( G_ISGHOSTING( enemy ) || enemy->flags & (FL_NOTARGET|FL_BUSY) ) return 0; if( self->r.client->ps.inventory[POWERUP_QUAD] || self->r.client->ps.inventory[POWERUP_SHELL] ) rage_mode = true; // don't fight against powerups. if( enemy->r.client && ( enemy->r.client->ps.inventory[POWERUP_QUAD] || enemy->r.client->ps.inventory[POWERUP_SHELL] ) ) return 0.2; //if not team based give some weight to every one if( GS_TeamBasedGametype() && ( enemy->s.team == self->s.team ) ) return 0; // if having EF_CARRIER we can assume it's someone important if( enemy->s.effects & EF_CARRIER ) return 2.0f; if( enemy == self->ai->last_attacker ) return rage_mode ? 4.0f : 1.0f; return rage_mode ? 4.0f : 0.3f; }
/* * G_ClientEndSnapFrame * * Called for each player at the end of the server frame * and right after spawning */ void G_ClientEndSnapFrame( edict_t *ent ) { gclient_t *client; int i; if( trap_GetClientState( PLAYERNUM( ent ) ) < CS_SPAWNED ) return; client = ent->r.client; // set fov if( !client->ps.pmove.stats[PM_STAT_ZOOMTIME] ) client->ps.fov = ent->r.client->fov; else { float frac = (float)client->ps.pmove.stats[PM_STAT_ZOOMTIME] / (float)ZOOMTIME; client->ps.fov = client->fov - ( (float)( client->fov - client->zoomfov ) * frac ); } // If the end of unit layout is displayed, don't give // the player any normal movement attributes if( GS_MatchState() >= MATCH_STATE_POSTMATCH ) { G_SetClientStats( ent ); } else { if( G_IsDead( ent ) && !level.gametype.customDeadBodyCam ) { G_Client_DeadView( ent ); } G_PlayerWorldEffects( ent ); // burn from lava, etc G_ClientDamageFeedback( ent ); // show damage taken along the snap G_SetClientStats( ent ); G_SetClientEffects( ent ); G_SetClientSound( ent ); G_SetClientFrame( ent ); client->ps.plrkeys = client->resp.snap.plrkeys; } G_ReleaseClientPSEvent( client ); // set the delta angle for( i = 0; i < 3; i++ ) client->ps.pmove.delta_angles[i] = ANGLE2SHORT( client->ps.viewangles[i] ) - client->ucmd.angles[i]; // this is pretty hackish. We exploit the fact that servers *do not* transmit // origin2/old_origin for ET_PLAYER/ET_CORPSE entities, and we use it for sending the player velocity if( !G_ISGHOSTING( ent ) ) { ent->r.svflags |= SVF_TRANSMITORIGIN2; VectorCopy( ent->velocity, ent->s.origin2 ); } else ent->r.svflags &= ~SVF_TRANSMITORIGIN2; }
void G_Gametype_GENERIC_ClientRespawn( edict_t *self, int old_team, int new_team ) { int i; gclient_t *client = self->r.client; gs_weapon_definition_t *weapondef; if( G_ISGHOSTING( self ) ) return; //give default items if( self->s.team != TEAM_SPECTATOR ) { if( GS_Instagib() ) { client->ps.inventory[WEAP_INSTAGUN] = 1; client->ps.inventory[AMMO_INSTAS] = 1; client->ps.inventory[AMMO_WEAK_INSTAS] = 1; } else { if( GS_MatchState() <= MATCH_STATE_WARMUP ) { for( i = WEAP_GUNBLADE; i < WEAP_TOTAL; i++ ) { if( i == WEAP_INSTAGUN ) // dont add instagun... continue; weapondef = GS_GetWeaponDef( i ); client->ps.inventory[i] = 1; if( weapondef->firedef_weak.ammo_id ) client->ps.inventory[weapondef->firedef_weak.ammo_id] = weapondef->firedef_weak.ammo_max; if( weapondef->firedef.ammo_id ) client->ps.inventory[weapondef->firedef.ammo_id] = weapondef->firedef.ammo_max; } client->resp.armor = GS_Armor_MaxCountForTag( ARMOR_YA ); } else { weapondef = GS_GetWeaponDef( WEAP_GUNBLADE ); client->ps.inventory[WEAP_GUNBLADE] = 1; client->ps.inventory[AMMO_GUNBLADE] = weapondef->firedef.ammo_max;; client->ps.inventory[AMMO_WEAK_GUNBLADE] = 0; } } } // select rocket launcher if available if( GS_CheckAmmoInWeapon( &client->ps, WEAP_ROCKETLAUNCHER ) ) client->ps.stats[STAT_PENDING_WEAPON] = WEAP_ROCKETLAUNCHER; else client->ps.stats[STAT_PENDING_WEAPON] = GS_SelectBestWeapon( &client->ps ); // add a teleportation effect if( self->r.solid != SOLID_NOT ) G_RespawnEffect( self ); }
/* * Touch_Item */ void Touch_Item( edict_t *ent, edict_t *other, cplane_t *plane, int surfFlags ) { bool taken; const gsitem_t *item = ent->item; if( !other->r.client || G_ISGHOSTING( other ) ) return; if( !( other->r.client->ps.pmove.stats[PM_STAT_FEATURES] & PMFEAT_ITEMPICK ) ) return; if( !item || !( item->flags & ITFLAG_PICKABLE ) ) return; // not a grabbable item if( !G_Gametype_CanPickUpItem( item ) ) return; taken = G_PickupItem( other, item, ent->spawnflags, ent->count, ent->invpak ); if( !( ent->spawnflags & ITEM_TARGETS_USED ) ) { G_UseTargets( ent, other ); ent->spawnflags |= ITEM_TARGETS_USED; } if( !taken ) return; if( ent->spawnflags & ITEM_TIMED ) ent->r.owner = other; // flash the screen G_AddPlayerStateEvent( other->r.client, PSEV_PICKUP, ( item->flags & IT_WEAPON ? item->tag : 0 ) ); G_AwardPlayerPickup( other, ent ); // for messages other->r.client->teamstate.last_pickup = ent; // show icon and name on status bar other->r.client->ps.stats[STAT_PICKUP_ITEM] = item->tag; other->r.client->resp.pickup_msg_time = level.time + 3000; if( ent->attenuation ) Touch_ItemSound( other, item ); if( !( ent->spawnflags & DROPPED_ITEM ) && G_Gametype_CanRespawnItem( item ) ) { if( (item->type & IT_WEAPON ) && GS_RaceGametype() ) return; // weapons stay in race SetRespawn( ent, G_Gametype_RespawnTimeForItem( item ) ); return; } G_FreeEdict( ent ); }
void Ai::Frame() { // Call super method first AiFrameAwareUpdatable::Frame(); if( !G_ISGHOSTING( self ) ) { entityPhysicsState->UpdateFromEntity( self ); } // Call brain Update() (Frame() and, maybe Think()) aiBaseBrain->Update(); if( level.spawnedTimeStamp + 5000 > game.realtime || !level.canSpawnEntities ) { self->nextThink = level.time + game.snapFrameTime; return; } }
/* * Touch_Item */ void Touch_Item( edict_t *ent, edict_t *other, cplane_t *plane, int surfFlags ) { qboolean taken; if( !other->r.client || G_ISGHOSTING( other ) ) return; if( !( other->r.client->ps.pmove.stats[PM_STAT_FEATURES] & PMFEAT_ITEMPICK ) ) return; if( !ent->item || !( ent->item->flags & ITFLAG_PICKABLE ) ) return; // not a grabbable item if( !G_Gametype_CanPickUpItem( ent->item ) ) return; taken = G_PickupItem( ent, other ); if( !( ent->spawnflags & ITEM_TARGETS_USED ) ) { G_UseTargets( ent, other ); ent->spawnflags |= ITEM_TARGETS_USED; } if( !taken ) return; // flash the screen G_AddPlayerStateEvent( other->r.client, PSEV_PICKUP, ( ent->item->flags & IT_WEAPON ? ent->item->tag : 0 ) ); G_AwardPlayerPickup( other, ent ); // for messages other->r.client->teamstate.last_pickup = ent; // show icon and name on status bar other->r.client->ps.stats[STAT_PICKUP_ITEM] = ent->item->tag; other->r.client->resp.pickup_msg_time = level.time + 3000; if( ent->attenuation ) Touch_ItemSound( other, ent->item ); if( !( ent->spawnflags & ( DROPPED_ITEM | DROPPED_PLAYER_ITEM ) ) && G_Gametype_CanRespawnItem( ent->item ) ) SetRespawn( ent, G_Gametype_RespawnTimeForItem( ent->item ) ); else G_FreeEdict( ent ); }
void AI_UpdateStatus( edict_t *self ) { if( !G_ISGHOSTING( self ) ) { AI_ResetWeights( self->ai ); self->ai->status.moveTypesMask = self->ai->pers.moveTypesMask; if( !GT_asCallBotStatus( self ) ) self->ai->pers.UpdateStatus( self ); self->ai->statusUpdateTimeout = level.time + AI_STATUS_TIMEOUT; // no cheating with moveTypesMask self->ai->status.moveTypesMask &= self->ai->pers.moveTypesMask; } }
/* * G_Chase_IsValidTarget */ static bool G_Chase_IsValidTarget( edict_t *ent, edict_t *target, bool teamonly ) { if( !ent || !target ) return false; if( !target->r.inuse || !target->r.client || trap_GetClientState( PLAYERNUM( target ) ) < CS_SPAWNED ) return false; if( target->s.team < TEAM_PLAYERS || target->s.team >= GS_MAX_TEAMS || target == ent ) return false; if( teamonly && !ent->r.client->teamstate.is_coach && G_ISGHOSTING( target ) ) return false; if( teamonly && target->s.team != ent->s.team ) return false; return true; }
/* * G_PickupItem */ qboolean G_PickupItem( edict_t *ent, edict_t *other ) { gsitem_t *it; qboolean taken = qfalse; if( !ent || !other ) return qfalse; if( other->r.client && G_ISGHOSTING( other ) ) return qfalse; if( !ent->item || !( ent->item->flags & ITFLAG_PICKABLE ) ) return qfalse; it = ent->item; if( it->type & IT_WEAPON ) { taken = Pickup_Weapon( ent, other ); } else if( it->type & IT_AMMO ) { taken = Pickup_Ammo( ent, other ); } else if( it->type & IT_ARMOR ) { taken = Pickup_Armor( ent, other ); } else if( it->type & IT_HEALTH ) { taken = Pickup_Health( ent, other ); } else if( it->type & IT_POWERUP ) { taken = Pickup_Powerup( ent, other ); } if( taken && other->r.client ) G_Gametype_ScoreEvent( other->r.client, "pickup", it->classname ); return taken; }
/* * G_Laser_Think */ static void G_Laser_Think( edict_t *ent ) { edict_t *owner; if( ent->s.ownerNum < 1 || ent->s.ownerNum > gs.maxclients ) { G_FreeEdict( ent ); return; } owner = &game.edicts[ent->s.ownerNum]; if( G_ISGHOSTING( owner ) || owner->s.weapon != WEAP_LASERGUN || trap_GetClientState( PLAYERNUM( owner ) ) < CS_SPAWNED || ( owner->r.client->ps.weaponState != WEAPON_STATE_REFIRESTRONG && owner->r.client->ps.weaponState != WEAPON_STATE_REFIRE ) ) { G_HideLaser( ent ); return; } ent->nextThink = level.time + 1; }
void Ai::Think() { if( !G_ISGHOSTING( self ) ) { // TODO: Check whether we are camping/holding a spot bool checkBlocked = true; if( !self->groundentity ) { checkBlocked = false; } else if( self->groundentity->use == Use_Plat && VectorLengthSquared( self->groundentity->velocity ) > 10 * 10 ) { checkBlocked = false; } if( checkBlocked && VectorLengthSquared( self->velocity ) > 30 * 30 ) { blockedTimeoutAt = level.time + BLOCKED_TIMEOUT; } // if completely stuck somewhere if( blockedTimeoutAt < level.time ) { OnBlockedTimeout(); return; } } }
/* * G_PickupItem */ bool G_PickupItem( edict_t *other, const gsitem_t *it, int flags, int count, const int *invpack ) { bool taken = false; if( other->r.client && G_ISGHOSTING( other ) ) return false; if( !it || !( it->flags & ITFLAG_PICKABLE ) ) return false; if( it->type & IT_WEAPON ) { taken = Pickup_Weapon( other, it, flags, count ); } else if( it->type & IT_AMMO ) { taken = Pickup_Ammo( other, it, count, invpack ); } else if( it->type & IT_ARMOR ) { taken = Pickup_Armor( other, it ); } else if( it->type & IT_HEALTH ) { taken = Pickup_Health( other, it, flags ); } else if( it->type & IT_POWERUP ) { taken = Pickup_Powerup( other, it, flags, count ); } if( taken && other->r.client ) G_Gametype_ScoreEvent( other->r.client, "pickup", it->classname ); return taken; }
//========================================== // AI_Think // think funtion for AIs //========================================== void AI_Think( edict_t *self ) { if( !self->ai || self->ai->type == AI_INACTIVE ) return; if( level.spawnedTimeStamp + 5000 > game.realtime || !level.canSpawnEntities ) { self->nextThink = level.time + game.snapFrameTime; return; } // check for being blocked if( !G_ISGHOSTING( self ) ) { AI_CategorizePosition( self ); if( VectorLengthFast( self->velocity ) > 37 ) self->ai->blocked_timeout = level.time + 10000; // if completely stuck somewhere if( self->ai->blocked_timeout < level.time ) { self->ai->pers.blockedTimeout( self ); return; } } //update status information to feed up ai if( self->ai->statusUpdateTimeout <= level.time ) AI_UpdateStatus( self ); if( AI_NodeHasTimedOut( self ) ) AI_ClearGoal( self ); if( self->ai->goal_node == NODE_INVALID ) AI_PickLongRangeGoal( self ); //if( self == level.think_client_entity ) AI_PickShortRangeGoal( self ); self->ai->pers.RunFrame( self ); // Show the path if( nav.debugMode && bot_showpath->integer && self->ai->goal_node != NODE_INVALID ) { // only draw the path of those bots which are being chased edict_t *chaser; bool chaserFound = false; for( chaser = game.edicts + 1; ENTNUM( chaser ) < gs.maxclients; chaser++ ) { if( chaser->r.client->resp.chase.active && chaser->r.client->resp.chase.target == ENTNUM( self ) ) { AITools_DrawPath( self, self->ai->goal_node ); chaserFound = true; } } if( !chaserFound && game.numBots == 1 ) AITools_DrawPath( self, self->ai->goal_node ); } }
void Bot::RegisterVisibleEnemies() { if(G_ISGHOSTING(self) || GS_MatchState() == MATCH_STATE_COUNTDOWN || GS_ShootingDisabled()) return; CheckIsInThinkFrame(__FUNCTION__); // Compute look dir before loop vec3_t lookDir; AngleVectors(self->s.angles, lookDir, nullptr, nullptr); float fov = 110.0f + 69.0f * Skill(); float dotFactor = cosf((float)DEG2RAD(fov / 2)); struct EntAndDistance { int entNum; float distance; EntAndDistance(int entNum_, float distance_): entNum(entNum_), distance(distance_) {} bool operator<(const EntAndDistance &that) const { return distance < that.distance; } }; // Do not call inPVS() and G_Visible() for potential targets inside a loop for all clients. // In worst case when all bots may see each other we get N^2 traces and PVS tests // First, select all candidate targets along with distance to a bot. // Then choose not more than BotBrain::maxTrackedEnemies nearest enemies for calling OnEnemyViewed() // It may cause data loss (far enemies may have higher logical priority), // but in a common good case (when there are few visible enemies) it preserves data, // and in the worst case mentioned above it does not act weird from player POV and prevents server hang up. // Note: non-client entities also may be candidate targets. StaticVector<EntAndDistance, MAX_EDICTS> candidateTargets; for (int i = 1; i < game.numentities; ++i) { edict_t *ent = game.edicts + i; if (botBrain.MayNotBeFeasibleEnemy(ent)) continue; // Reject targets quickly by fov Vec3 toTarget(ent->s.origin); toTarget -= self->s.origin; float squareDistance = toTarget.SquaredLength(); if (squareDistance < 1) continue; float invDistance = Q_RSqrt(squareDistance); toTarget *= invDistance; if (toTarget.Dot(lookDir) < dotFactor) continue; // It seams to be more instruction cache-friendly to just add an entity to a plain array // and sort it once after the loop instead of pushing an entity in a heap on each iteration candidateTargets.emplace_back(EntAndDistance(ENTNUM(ent), 1.0f / invDistance)); } std::sort(candidateTargets.begin(), candidateTargets.end()); // Select inPVS/visible targets first to aid instruction cache, do not call callbacks in loop StaticVector<edict_t *, MAX_CLIENTS> targetsInPVS; StaticVector<edict_t *, MAX_CLIENTS> visibleTargets; static_assert(AiBaseEnemyPool::MAX_TRACKED_ENEMIES <= MAX_CLIENTS, "targetsInPVS capacity may be exceeded"); for (int i = 0, end = std::min(candidateTargets.size(), botBrain.MaxTrackedEnemies()); i < end; ++i) { edict_t *ent = game.edicts + candidateTargets[i].entNum; if (trap_inPVS(self->s.origin, ent->s.origin)) targetsInPVS.push_back(ent); } for (auto ent: targetsInPVS) if (G_Visible(self, ent)) visibleTargets.push_back(ent); // Call bot brain callbacks on visible targets for (auto ent: visibleTargets) botBrain.OnEnemyViewed(ent); botBrain.AfterAllEnemiesViewed(); CheckAlertSpots(visibleTargets); }
//========================================== // AI_PickShortRangeGoal // Pick best goal based on importance and range. This function // overrides the long range goal selection for items that // are very close to the bot and are reachable. //========================================== static void AI_PickShortRangeGoal( edict_t *self ) { edict_t *bestGoal = NULL; float bestWeight = 0; nav_ents_t *goalEnt; const gsitem_t *item; bool canPickupItems; int i; if( !self->r.client || G_ISGHOSTING( self ) ) return; if( self->ai->state_combat_timeout > level.time ) { self->ai->shortRangeGoalTimeout = self->ai->state_combat_timeout; return; } if( self->ai->shortRangeGoalTimeout > level.time ) return; canPickupItems = (self->r.client->ps.pmove.stats[PM_STAT_FEATURES] & PMFEAT_ITEMPICK) != 0 ? true : false; self->ai->shortRangeGoalTimeout = level.time + AI_SHORT_RANGE_GOAL_DELAY; self->movetarget = NULL; FOREACH_GOALENT( goalEnt ) { float dist; i = goalEnt->id; if( !goalEnt->ent->r.inuse || goalEnt->ent->r.solid == SOLID_NOT ) continue; if( goalEnt->ent->r.client ) continue; if( self->ai->status.entityWeights[i] <= 0.0f ) continue; item = goalEnt->ent->item; if( canPickupItems && item ) { if( !G_Gametype_CanPickUpItem( item ) || !( item->flags & ITFLAG_PICKABLE ) ) { continue; } } dist = DistanceFast( self->s.origin, goalEnt->ent->s.origin ); if( goalEnt == self->ai->goalEnt ) { if( dist > AI_GOAL_SR_LR_RADIUS ) continue; } else { if( dist > AI_GOAL_SR_RADIUS ) continue; } clamp_low( dist, 0.01f ); if( AI_ShortRangeReachable( self, goalEnt->ent->s.origin ) ) { float weight; bool in_front = G_InFront( self, goalEnt->ent ); // Long range goal gets top priority if( in_front && goalEnt == self->ai->goalEnt ) { bestGoal = goalEnt->ent; break; } // get the one with the best weight weight = self->ai->status.entityWeights[i] / dist * (in_front ? 1.0f : 0.5f); if( weight > bestWeight ) { bestWeight = weight; bestGoal = goalEnt->ent; } } } if( bestGoal ) { self->movetarget = bestGoal; if( nav.debugMode && bot_showsrgoal->integer ) G_PrintChasersf( self, "%i %s: selected a %s for SR goal.\n", level.framenum, self->ai->pers.netname, self->movetarget->classname ); } else { // got nothing else to do so keep scanning self->ai->shortRangeGoalTimeout = level.time + AI_SHORT_RANGE_GOAL_DELAY_IDLE; } }
//========================================== // AI_PickLongRangeGoal // // Evaluate the best long range goal and send the bot on // its way. This is a good time waster, so use it sparingly. // Do not call it for every think cycle. // // jal: I don't think there is any problem by calling it, // now that we have stored the costs at the nav.costs table (I don't do it anyway) //========================================== void AI_PickLongRangeGoal( edict_t *self ) { #define WEIGHT_MAXDISTANCE_FACTOR 20000.0f #define COST_INFLUENCE 0.5f int i; float weight, bestWeight = 0.0; int current_node; float cost; float dist; nav_ents_t *goalEnt, *bestGoalEnt = NULL; AI_ClearGoal( self ); if( G_ISGHOSTING( self ) ) return; if( self->ai->longRangeGoalTimeout > level.time ) return; if( !self->r.client->ps.pmove.stats[PM_STAT_MAXSPEED] ) { return; } self->ai->longRangeGoalTimeout = level.time + AI_LONG_RANGE_GOAL_DELAY + brandom( 0, 1000 ); // look for a target current_node = AI_FindClosestReachableNode( self->s.origin, self, ( ( 1 + self->ai->nearest_node_tries ) * NODE_DENSITY ), NODE_ALL ); self->ai->current_node = current_node; if( current_node == NODE_INVALID ) { if( nav.debugMode && bot_showlrgoal->integer ) G_PrintChasersf( self, "%s: LRGOAL: Closest node not found. Tries:%i\n", self->ai->pers.netname, self->ai->nearest_node_tries ); self->ai->nearest_node_tries++; // extend search radius with each try return; } self->ai->nearest_node_tries = 0; // Run the list of potential goal entities FOREACH_GOALENT( goalEnt ) { i = goalEnt->id; if( !goalEnt->ent ) continue; if( !goalEnt->ent->r.inuse ) { goalEnt->node = NODE_INVALID; continue; } if( goalEnt->ent->r.client ) { if( G_ISGHOSTING( goalEnt->ent ) || goalEnt->ent->flags & (FL_NOTARGET|FL_BUSY) ) goalEnt->node = NODE_INVALID; else goalEnt->node = AI_FindClosestReachableNode( goalEnt->ent->s.origin, goalEnt->ent, NODE_DENSITY, NODE_ALL ); } if( goalEnt->ent->item ) { if( !G_Gametype_CanPickUpItem( goalEnt->ent->item ) ) continue; } if( goalEnt->node == NODE_INVALID ) continue; weight = self->ai->status.entityWeights[i]; if( weight <= 0.0f ) continue; // don't try to find cost for too far away objects dist = DistanceFast( self->s.origin, goalEnt->ent->s.origin ); if( dist > WEIGHT_MAXDISTANCE_FACTOR * weight/* || dist < AI_GOAL_SR_RADIUS*/ ) continue; cost = AI_FindCost( current_node, goalEnt->node, self->ai->status.moveTypesMask ); if( cost == NODE_INVALID ) continue; cost -= brandom( 0, 2000 ); // allow random variations clamp_low( cost, 1 ); weight = ( 1000 * weight ) / ( cost * COST_INFLUENCE ); // Check against cost of getting there if( weight > bestWeight ) { bestWeight = weight; bestGoalEnt = goalEnt; } } if( bestGoalEnt ) { self->ai->goalEnt = bestGoalEnt; AI_SetGoal( self, bestGoalEnt->node ); if( self->ai->goalEnt != NULL && nav.debugMode && bot_showlrgoal->integer ) G_PrintChasersf( self, "%s: selected a %s at node %d for LR goal. (weight %f)\n", self->ai->pers.netname, self->ai->goalEnt->ent->classname, self->ai->goalEnt->node, bestWeight ); return; } if( nav.debugMode && bot_showlrgoal->integer ) G_PrintChasersf( self, "%s: did not find a LR goal.\n", self->ai->pers.netname ); #undef WEIGHT_MAXDISTANCE_FACTOR #undef COST_INFLUENCE }
//========================================== // BOT_DMclass_FindEnemy // Scan for enemy (simplifed for now to just pick any visible enemy) //========================================== void BOT_DMclass_FindEnemy( edict_t *self ) { #define WEIGHT_MAXDISTANCE_FACTOR 15000 nav_ents_t *goalEnt; edict_t *bestTarget = NULL; float dist, weight, bestWeight = 9999999; vec3_t forward, vec; int i; if( G_ISGHOSTING( self ) || GS_MatchState() == MATCH_STATE_COUNTDOWN || GS_ShootingDisabled() ) { self->ai->enemyReactionDelay = 0; self->enemy = self->ai->latched_enemy = NULL; return; } // we also latch NULL enemies, so the bot can loose them if( self->ai->enemyReactionDelay > 0 ) { self->ai->enemyReactionDelay -= game.frametime; return; } self->enemy = self->ai->latched_enemy; FOREACH_GOALENT( goalEnt ) { i = goalEnt->id; if( !goalEnt->ent || !goalEnt->ent->r.inuse ) continue; if( !goalEnt->ent->r.client ) // this may be changed, there could be enemies which aren't clients continue; if( G_ISGHOSTING( goalEnt->ent ) ) continue; if( self->ai->status.entityWeights[i] <= 0 || goalEnt->ent->flags & (FL_NOTARGET|FL_BUSY) ) continue; if( GS_TeamBasedGametype() && goalEnt->ent->s.team == self->s.team ) continue; dist = DistanceFast( self->s.origin, goalEnt->ent->s.origin ); // ignore very soft weighted enemies unless they are in your face if( dist > 500 && self->ai->status.entityWeights[i] <= 0.1f ) continue; //if( dist > 700 && dist > WEIGHT_MAXDISTANCE_FACTOR * self->ai->status.entityWeights[i] ) // continue; weight = dist / self->ai->status.entityWeights[i]; if( weight < bestWeight ) { if( trap_inPVS( self->s.origin, goalEnt->ent->s.origin ) && G_Visible( self, goalEnt->ent ) ) { bool close = dist < 2000 || goalEnt->ent == self->ai->last_attacker; if( !close ) { VectorSubtract( goalEnt->ent->s.origin, self->s.origin, vec ); VectorNormalize( vec ); close = DotProduct( vec, forward ) > 0.3; } if( close ) { bestWeight = weight; bestTarget = goalEnt->ent; } } } } AI_NewEnemyInView( self, bestTarget ); #undef WEIGHT_MAXDISTANCE_FACTOR }
//========================================== // BOT_DMclass_RunFrame // States Machine & call client movement //========================================== static void BOT_DMclass_RunFrame( edict_t *self ) { usercmd_t ucmd; float weapon_quality; bool inhibitCombat = false; int i; if( G_ISGHOSTING( self ) ) { BOT_DMclass_GhostingFrame( self ); return; } memset( &ucmd, 0, sizeof( ucmd ) ); //get ready if in the game if( GS_MatchState() <= MATCH_STATE_WARMUP && !level.ready[PLAYERNUM(self)] && self->r.client->teamstate.timeStamp + 4000 < level.time ) G_Match_Ready( self ); if( level.gametype.dummyBots || bot_dummy->integer ) { self->r.client->level.last_activity = level.time; } else { BOT_DMclass_FindEnemy( self ); weapon_quality = BOT_DMclass_ChooseWeapon( self ); inhibitCombat = ( AI_CurrentLinkType( self ) & (LINK_JUMPPAD|LINK_JUMP|LINK_ROCKETJUMP) ) != 0 ? true : false; if( self->enemy && weapon_quality >= 0.3 && !inhibitCombat ) // don't fight with bad weapons { if( BOT_DMclass_FireWeapon( self, &ucmd ) ) self->ai->state_combat_timeout = level.time + AI_COMBATMOVE_TIMEOUT; } if( inhibitCombat ) self->ai->state_combat_timeout = 0; if( self->ai->state_combat_timeout > level.time ) { BOT_DMclass_CombatMovement( self, &ucmd ); } else { BOT_DMclass_Move( self, &ucmd ); } //set up for pmove for( i = 0; i < 3; i++ ) ucmd.angles[i] = ANGLE2SHORT( self->s.angles[i] ) - self->r.client->ps.pmove.delta_angles[i]; VectorSet( self->r.client->ps.pmove.delta_angles, 0, 0, 0 ); } // set approximate ping and show values ucmd.msec = game.frametime; ucmd.serverTimeStamp = game.serverTime; ClientThink( self, &ucmd, 0 ); self->nextThink = level.time + 1; BOT_DMclass_VSAYmessages( self ); }
/* * G_EdictsAddSnapEffects * add effects based on accumulated info along the server frame */ static void G_SnapEntities( void ) { edict_t *ent; int i; vec3_t dir, origin; for( i = 0, ent = &game.edicts[0]; i < game.numentities; i++, ent++ ) { if( !ent->r.inuse || ( ent->r.svflags & SVF_NOCLIENT ) ) continue; if( ent->s.type == ET_PARTICLES ) // particles use a special configuration { ent->s.frame = ent->particlesInfo.speed; ent->s.modelindex = ent->particlesInfo.shaderIndex; ent->s.modelindex2 = ent->particlesInfo.spread; ent->s.counterNum = ent->particlesInfo.time; ent->s.weapon = ent->particlesInfo.frequency; ent->s.effects = ent->particlesInfo.size & 0xFF; if( ent->particlesInfo.spherical ) ent->s.effects |= ( 1<<8 ); if( ent->particlesInfo.bounce ) ent->s.effects |= ( 1<<9 ); if( ent->particlesInfo.gravity ) ent->s.effects |= ( 1<<10 ); if( ent->particlesInfo.expandEffect ) ent->s.effects |= ( 1<<11 ); if( ent->particlesInfo.shrinkEffect ) ent->s.effects |= ( 1<<11 ); GClip_LinkEntity( ent ); continue; } if( ent->s.type == ET_PLAYER || ent->s.type == ET_CORPSE ) { // this is pretty hackish. We exploit the fact that 0.5 servers *do not* transmit // origin2/old_origin for ET_PLAYER/ET_CORPSE entities, and we use it for sending the player velocity if( !G_ISGHOSTING( ent ) ) { ent->r.svflags |= SVF_TRANSMITORIGIN2; VectorCopy( ent->velocity, ent->s.origin2 ); } else ent->r.svflags &= ~SVF_TRANSMITORIGIN2; } if( ISEVENTENTITY( ent ) || G_ISGHOSTING( ent ) || !ent->takedamage ) continue; // types which can have accumulated damage effects if( ( ent->s.type == ET_GENERIC || ent->s.type == ET_PLAYER || ent->s.type == ET_CORPSE ) ) // doors don't bleed { // Until we get a proper damage saved effect, we accumulate both into the blood fx // so, at least, we don't send 2 entities where we can send one ent->snap.damage_taken += ent->snap.damage_saved; //ent->snap.damage_saved = 0; //spawn accumulated damage if( ent->snap.damage_taken && !( ent->flags & FL_GODMODE ) && HEALTH_TO_INT( ent->health ) > 0 ) { edict_t *event; float damage = ent->snap.damage_taken; if( damage > 120 ) damage = 120; VectorCopy( ent->snap.damage_dir, dir ); VectorNormalize( dir ); VectorAdd( ent->s.origin, ent->snap.damage_at, origin ); if( ent->s.type == ET_PLAYER || ent->s.type == ET_CORPSE ) { event = G_SpawnEvent( EV_BLOOD, DirToByte( dir ), origin ); event->s.damage = HEALTH_TO_INT( damage ); event->s.ownerNum = i; // set owner // ET_PLAYERS can also spawn sound events if( ent->s.type == ET_PLAYER ) { // play an apropriate pain sound if( level.time >= ent->pain_debounce_time ) { // see if it should pain for a FALL or for damage received if( ent->snap.damage_fall ) { ent->pain_debounce_time = level.time + 200; } else if( !G_IsDead( ent ) ) { if( ent->r.client->ps.inventory[POWERUP_SHELL] > 0 ) G_AddEvent( ent, EV_PAIN, PAIN_WARSHELL, true ); else if( ent->health <= 20 ) G_AddEvent( ent, EV_PAIN, PAIN_20, true ); else if( ent->health <= 35 ) G_AddEvent( ent, EV_PAIN, PAIN_30, true ); else if( ent->health <= 60 ) G_AddEvent( ent, EV_PAIN, PAIN_60, true ); else G_AddEvent( ent, EV_PAIN, PAIN_100, true ); ent->pain_debounce_time = level.time + 400; } } } } else { event = G_SpawnEvent( EV_SPARKS, DirToByte( dir ), origin ); event->s.damage = HEALTH_TO_INT( damage ); } } } } }
static void UpdatePoint( edict_t *who ) { vec3_t angles, forward, diff; trace_t trace; edict_t *ent, *ent_best = NULL; int i, j; float value, value_best = 0.35f; // if nothing better is found, print nothing gclient_t *client = who->r.client; vec3_t boxpoints[8], viewpoint; AngleVectors( client->ps.viewangles, forward, NULL, NULL ); VectorCopy( who->s.origin, viewpoint ); viewpoint[2] += who->viewheight; for( i = 0; i < game.numentities; i++ ) { ent = game.edicts + i; if( !ent->r.inuse || !ent->s.modelindex || ent == who ) continue; if( G_ISGHOSTING( ent ) ) continue; if( ent->s.type != ET_PLAYER && ent->s.type != ET_ITEM ) continue; VectorSubtract( ent->s.origin, viewpoint, angles ); VectorNormalize( angles ); VectorSubtract( forward, angles, diff ); for( j = 0; j < 3; j++ ) if( diff[j] < 0 ) diff[j] = -diff[j]; value = VectorLengthFast( diff ); if( value < value_best ) { BuildBoxPoints( boxpoints, ent->s.origin, ent->r.mins, ent->r.maxs ); for( j = 0; j < 8; j++ ) { G_Trace( &trace, viewpoint, vec3_origin, vec3_origin, boxpoints[j], who, MASK_OPAQUE ); if( trace.fraction == 1 ) { value_best = value; ent_best = ent; break; } } } } if( ent_best != NULL ) { point = ent_best; VectorCopy( ent_best->s.origin, point_location ); } else { vec3_t dest; VectorMA( viewpoint, 8192, forward, dest ); G_Trace( &trace, viewpoint, vec3_origin, vec3_origin, dest, who, MASK_OPAQUE ); point = NULL; VectorCopy( trace.endpos, point_location ); } }
/* * ClientUserinfoChanged * called whenever the player updates a userinfo variable. * * The game can override any of the settings in place * (forcing skins or names, etc) before copying it off. */ void ClientUserinfoChanged( edict_t *ent, char *userinfo ) { char *s; char oldname[MAX_INFO_VALUE]; gclient_t *cl; int rgbcolor, i; assert( ent && ent->r.client ); assert( userinfo && Info_Validate( userinfo ) ); // check for malformed or illegal info strings if( !Info_Validate( userinfo ) ) { trap_DropClient( ent, DROP_TYPE_GENERAL, "Error: Invalid userinfo" ); return; } cl = ent->r.client; // ip s = Info_ValueForKey( userinfo, "ip" ); if( !s ) { trap_DropClient( ent, DROP_TYPE_GENERAL, "Error: Server didn't provide client IP" ); return; } Q_strncpyz( cl->ip, s, sizeof( cl->ip ) ); // socket s = Info_ValueForKey( userinfo, "socket" ); if( !s ) { trap_DropClient( ent, DROP_TYPE_GENERAL, "Error: Server didn't provide client socket" ); return; } Q_strncpyz( cl->socket, s, sizeof( cl->socket ) ); // color s = Info_ValueForKey( userinfo, "color" ); if( s ) rgbcolor = COM_ReadColorRGBString( s ); else rgbcolor = -1; if( rgbcolor != -1 ) { rgbcolor = COM_ValidatePlayerColor( rgbcolor ); Vector4Set( cl->color, COLOR_R( rgbcolor ), COLOR_G( rgbcolor ), COLOR_B( rgbcolor ), 255 ); } else { Vector4Set( cl->color, 255, 255, 255, 255 ); } // set name, it's validated and possibly changed first Q_strncpyz( oldname, cl->netname, sizeof( oldname ) ); G_SetName( ent, Info_ValueForKey( userinfo, "name" ) ); if( oldname[0] && Q_stricmp( oldname, cl->netname ) && !cl->isTV && !CheckFlood( ent, false ) ) G_PrintMsg( NULL, "%s%s is now known as %s%s\n", oldname, S_COLOR_WHITE, cl->netname, S_COLOR_WHITE ); if( !Info_SetValueForKey( userinfo, "name", cl->netname ) ) { trap_DropClient( ent, DROP_TYPE_GENERAL, "Error: Couldn't set userinfo (name)" ); return; } // clan tag G_SetClan( ent, Info_ValueForKey( userinfo, "clan" ) ); // handedness s = Info_ValueForKey( userinfo, "hand" ); if( !s ) cl->hand = 2; else cl->hand = bound( atoi( s ), 0, 2 ); // handicap s = Info_ValueForKey( userinfo, "handicap" ); if( s ) { i = atoi( s ); if( i > 90 || i < 0 ) { G_PrintMsg( ent, "Handicap must be defined in the [0-90] range.\n" ); cl->handicap = 0; } else { cl->handicap = i; } } s = Info_ValueForKey( userinfo, "cg_movementStyle" ); if( s ) { i = bound( atoi( s ), 0, GS_MAXBUNNIES - 1 ); if( trap_GetClientState( PLAYERNUM(ent) ) < CS_SPAWNED ) { if( i != cl->movestyle ) cl->movestyle = cl->movestyle_latched = i; } else if( cl->movestyle_latched != cl->movestyle ) { G_PrintMsg( ent, "A movement style change is already in progress. Please wait.\n" ); } else if( i != cl->movestyle_latched ) { cl->movestyle_latched = i; if( cl->movestyle_latched != cl->movestyle ) { edict_t *switcher; switcher = G_Spawn(); switcher->think = think_MoveTypeSwitcher; switcher->nextThink = level.time + 10000; switcher->s.ownerNum = ENTNUM( ent ); G_PrintMsg( ent, "Movement style will change in 10 seconds.\n" ); } } } // update the movement features depending on the movestyle if( !G_ISGHOSTING( ent ) && g_allow_bunny->integer ) { if( cl->movestyle == GS_CLASSICBUNNY ) cl->ps.pmove.stats[PM_STAT_FEATURES] &= ~PMFEAT_FWDBUNNY; else cl->ps.pmove.stats[PM_STAT_FEATURES] |= PMFEAT_FWDBUNNY; } s = Info_ValueForKey( userinfo, "cg_noAutohop" ); if( s && s[0] ) { if( atoi( s ) != 0 ) cl->ps.pmove.stats[PM_STAT_FEATURES] &= ~PMFEAT_CONTINOUSJUMP; else cl->ps.pmove.stats[PM_STAT_FEATURES] |= PMFEAT_CONTINOUSJUMP; } #ifdef UCMDTIMENUDGE s = Info_ValueForKey( userinfo, "cl_ucmdTimeNudge" ); if( !s ) { cl->ucmdTimeNudge = 0; } else { cl->ucmdTimeNudge = atoi( s ); clamp( cl->ucmdTimeNudge, -MAX_UCMD_TIMENUDGE, MAX_UCMD_TIMENUDGE ); } #endif // mm session // TODO: remove the key after storing it to gclient_t ! s = Info_ValueForKey( userinfo, "cl_mm_session" ); cl->mm_session = ( s == NULL ) ? 0 : atoi( s ); s = Info_ValueForKey( userinfo, "mmflags" ); cl->mmflags = ( s == NULL ) ? 0 : strtoul( s, NULL, 10 ); // tv if( cl->isTV ) { s = Info_ValueForKey( userinfo, "tv_port" ); cl->tv.port = s ? atoi( s ) : 0; s = Info_ValueForKey( userinfo, "tv_port6" ); cl->tv.port6 = s ? atoi( s ) : 0; s = Info_ValueForKey( userinfo, "max_cl" ); cl->tv.maxclients = s ? atoi( s ) : 0; s = Info_ValueForKey( userinfo, "num_cl" ); cl->tv.numclients = s ? atoi( s ) : 0; s = Info_ValueForKey( userinfo, "chan" ); cl->tv.channel = s ? atoi( s ) : 0; } if( !G_ISGHOSTING( ent ) && trap_GetClientState( PLAYERNUM( ent ) ) >= CS_SPAWNED ) G_Client_AssignTeamSkin( ent, userinfo ); // save off the userinfo in case we want to check something later Q_strncpyz( cl->userinfo, userinfo, sizeof( cl->userinfo ) ); G_UpdatePlayerInfoString( PLAYERNUM( ent ) ); G_UpdateMMPlayerInfoString( PLAYERNUM( ent ) ); G_Gametype_ScoreEvent( cl, "userinfochanged", oldname ); }
//========================================== // BOT_DMclass_FindEnemy // Scan for enemy (simplifed for now to just pick any visible enemy) //========================================== void BOT_DMclass_FindEnemy( edict_t *self ) { #define WEIGHT_MAXDISTANCE_FACTOR 15000 nav_ents_t *goalEnt; edict_t *bestTarget = NULL; float dist, weight, bestWeight = 9999999; int i; if( G_ISGHOSTING( self ) || GS_MatchState() == MATCH_STATE_COUNTDOWN || GS_ShootingDisabled() ) { self->ai.enemyReactionDelay = 0; self->enemy = self->ai.latched_enemy = NULL; return; } // we also latch NULL enemies, so the bot can loose them if( self->ai.enemyReactionDelay > 0 ) { self->ai.enemyReactionDelay -= game.frametime; return; } self->enemy = self->ai.latched_enemy; for( i = 0; i < nav.num_goalEnts; i++ ) { goalEnt = &nav.goalEnts[i]; if( !goalEnt->ent || !goalEnt->ent->r.inuse ) continue; if( !goalEnt->ent->r.client ) // this may be changed, there could be enemies which aren't clients continue; if( G_ISGHOSTING( goalEnt->ent ) ) continue; if( self->ai.status.entityWeights[i] <= 0 || goalEnt->ent->ai.notarget ) continue; if( GS_TeamBasedGametype() && goalEnt->ent->s.team == self->s.team ) continue; dist = DistanceFast( self->s.origin, goalEnt->ent->s.origin ); // ignore very soft weighted enemies unless they are in your face if( dist > 500 && self->ai.status.entityWeights[i] <= 0.1f ) continue; if( dist > 700 && dist > WEIGHT_MAXDISTANCE_FACTOR * self->ai.status.entityWeights[i] ) continue; if( trap_inPVS( self->s.origin, goalEnt->ent->s.origin ) && G_Visible( self, goalEnt->ent ) ) { weight = dist / self->ai.status.entityWeights[i]; if( ( dist < 350 ) || G_InFront( self, goalEnt->ent ) ) { if( weight < bestWeight ) { bestWeight = weight; bestTarget = goalEnt->ent; } } } } AI_NewEnemyInView( self, bestTarget ); #undef WEIGHT_MAXDISTANCE_FACTOR }