// Decide if we should move to help the player, return true if we will bool CCSBot::RespondToHelpRequest(CBasePlayer *them, Place place, float maxRange) { if (IsRogue()) return false; // if we're busy, ignore if (IsBusy()) return false; // if we are too far away, ignore if (maxRange > 0.0f) { // compute actual travel distance PathCost pc(this); real_t travelDistance = NavAreaTravelDistance(m_lastKnownArea, TheNavAreaGrid.GetNearestNavArea(&them->pev->origin), pc); if (travelDistance < 0.0f) return false; if (travelDistance > maxRange) return false; } if (place == UNDEFINED_PLACE) { // if we have no "place" identifier, go directly to them // if we are already there, ignore float rangeSq = (them->pev->origin - pev->origin).LengthSquared(); const float close = 750.0f * 750.0f; if (rangeSq < close) return true; MoveTo(&them->pev->origin, FASTEST_ROUTE); } else { // if we are already there, ignore if (GetPlace() == place) return true; // go to where help is needed const Vector *pos = GetRandomSpotAtPlace(place); if (pos) { MoveTo(pos, FASTEST_ROUTE); } else { MoveTo(&them->pev->origin, FASTEST_ROUTE); } } // acknowledge GetChatter()->Say("OnMyWay"); return true; }
// Invoked when killed void CCSBot::Killed(entvars_t *pevAttacker, int iGib) { PrintIfWatched("Killed( attacker = %s )\n", STRING(pevAttacker->netname)); GetChatter()->OnDeath(); // increase the danger where we died const float deathDanger = 1.0f; const float deathDangerRadius = 500.0f; IncreaseDangerNearby(m_iTeam - 1, deathDanger, m_lastKnownArea, &pev->origin, deathDangerRadius); // end voice feedback EndVoiceFeedback(); // extend CBasePlayer::Killed(pevAttacker, iGib); }
BOOL ChattersModel::OnChatterJoining(UINT16 account_id, const ChatInfo& room, const OpStringC& chatter, BOOL is_operator, BOOL is_voiced, const OpStringC& prefix, BOOL initial) { if (!IsModelForRoom(account_id, room.ChatName())) { return TRUE; } if (GetChatter(chatter)) return FALSE; ChattersModelItem* item = OP_NEW(ChattersModelItem, (m_account_id, chatter, is_operator, is_voiced, prefix)); if (!item) return FALSE; m_chatters_hash_table.Add(item->GetName(), item); AddLast(item); return TRUE; }
// Blind the bot for the given duration void CCSBot::__MAKE_VHOOK(Blind)(float duration, float holdTime, float fadeTime, int alpha) { // extend CBasePlayer::Blind(duration, holdTime, fadeTime, alpha); PrintIfWatched("I'm blind!\n"); if (RANDOM_FLOAT(0.0f, 100.0f) < 33.3f) { GetChatter()->Say("Blinded", 1.0f); } // decide which way to move while blind m_blindMoveDir = static_cast<NavRelativeDirType>(RANDOM_LONG(1, NUM_RELATIVE_DIRECTIONS - 1)); // if blinded while in combat - then spray and pray! m_blindFire = (RANDOM_FLOAT(0.0f, 100.0f) < 10.0f) != 0; // no longer safe AdjustSafeTime(); }
void CCSBot::__MAKE_VHOOK(OnEvent)(GameEventType event, CBaseEntity *entity, CBaseEntity *other) { GetGameState()->OnEvent(event, entity, other); GetChatter()->OnEvent(event, entity, other); // Morale adjustments happen even for dead players switch (event) { case EVENT_TERRORISTS_WIN: if (m_iTeam == CT) { DecreaseMorale(); } else { IncreaseMorale(); } break; case EVENT_CTS_WIN: if (m_iTeam == CT) { IncreaseMorale(); } else { DecreaseMorale(); } break; } if (!IsAlive()) return; CBasePlayer *player = static_cast<CBasePlayer *>(entity); // If we just saw a nearby friend die, and we haven't yet acquired an enemy // automatically acquire our dead friend's killer if (!IsAttacking() && (GetDisposition() == ENGAGE_AND_INVESTIGATE || GetDisposition() == OPPORTUNITY_FIRE)) { if (event == EVENT_PLAYER_DIED) { if (BotRelationship(player) == BOT_TEAMMATE) { CBasePlayer *killer = static_cast<CBasePlayer *>(other); // check that attacker is an enemy (for friendly fire, etc) if (killer != NULL && killer->IsPlayer()) { // check if we saw our friend die - dont check FOV - assume we're aware of our surroundings in combat // snipers stay put if (!IsSniper() && IsVisible(&player->pev->origin)) { // people are dying - we should hurry Hurry(RANDOM_FLOAT(10.0f, 15.0f)); // if we're hiding with only our knife, be a little more cautious const float knifeAmbushChance = 50.0f; if (!IsHiding() || !IsUsingKnife() || RANDOM_FLOAT(0, 100) < knifeAmbushChance) { PrintIfWatched("Attacking our friend's killer!\n"); Attack(killer); return; } } } } } } switch (event) { case EVENT_PLAYER_DIED: { CBasePlayer *victim = player; CBasePlayer *killer = (other != NULL && other->IsPlayer()) ? static_cast<CBasePlayer *>(other) : NULL; // if the human player died in the single player game, tell the team if (CSGameRules()->IsCareer() && !victim->IsBot() && BotRelationship(victim) == BOT_TEAMMATE) { GetChatter()->Say("CommanderDown", 20.0f); } // keep track of the last player we killed if (killer == this) { m_lastVictimID = victim->entindex(); } // react to teammate death if (BotRelationship(victim) == BOT_TEAMMATE) { // chastise friendly fire from humans if (killer != NULL && !killer->IsBot() && BotRelationship(killer) == BOT_TEAMMATE && killer != this) { GetChatter()->KilledFriend(); } if (IsHunting()) { PrintIfWatched("Rethinking hunt due to teammate death\n"); Idle(); return; } if (IsAttacking()) { if (GetTimeSinceLastSawEnemy() > 0.4f) { PrintIfWatched("Rethinking my attack due to teammate death\n"); // allow us to sneak past windows, doors, etc IgnoreEnemies(1.0f); // move to last known position of enemy - this could cause us to flank if // the danger has changed due to our teammate's recent death SetTask(MOVE_TO_LAST_KNOWN_ENEMY_POSITION, GetEnemy()); MoveTo(&GetLastKnownEnemyPosition()); return; } } } // an enemy was killed else { if (killer != NULL && BotRelationship(killer) == BOT_TEAMMATE) { // only chatter about enemy kills if we see them occur, and they were the last one we see if (GetNearbyEnemyCount() <= 1) { // report if number of enemies left is few and we killed the last one we saw locally GetChatter()->EnemiesRemaining(); if (IsVisible(&victim->pev->origin, CHECK_FOV)) { // congratulate teammates on their kills if (killer != this) { float delay = RANDOM_FLOAT(2.0f, 3.0f); if (killer->IsBot()) { if (RANDOM_FLOAT(0.0f, 100.0f) < 40.0f) GetChatter()->Say("NiceShot", 3.0f, delay); } else { // humans get the honorific if (CSGameRules()->IsCareer()) GetChatter()->Say("NiceShotCommander", 3.0f, delay); else GetChatter()->Say("NiceShotSir", 3.0f, delay); } } } } } } return; } case EVENT_TERRORISTS_WIN: if (m_iTeam == TERRORIST) GetChatter()->CelebrateWin(); return; case EVENT_CTS_WIN: if (m_iTeam == CT) GetChatter()->CelebrateWin(); return; case EVENT_BOMB_DEFUSED: if (m_iTeam == CT && TheCSBots()->GetBombTimeLeft() < 2.0) GetChatter()->Say("BarelyDefused"); return; case EVENT_BOMB_PICKED_UP: { if (m_iTeam == CT && player != NULL) { // check if we're close enough to hear it const float bombPickupHearRangeSq = 1000.0f * 1000.0f; if ((pev->origin - player->pev->origin).LengthSquared() < bombPickupHearRangeSq) { GetChatter()->TheyPickedUpTheBomb(); } } return; } case EVENT_BOMB_BEEP: { // if we don't know where the bomb is, but heard it beep, we've discovered it if (GetGameState()->IsPlantedBombLocationKnown() == false) { // check if we're close enough to hear it const float bombBeepHearRangeSq = 1000.0f * 1000.0f; if ((pev->origin - entity->pev->origin).LengthSquared() < bombBeepHearRangeSq) { // radio the news to our team if (m_iTeam == CT && GetGameState()->GetPlantedBombsite() == CSGameState::UNKNOWN) { const CCSBotManager::Zone *zone = TheCSBots()->GetZone(&entity->pev->origin); if (zone != NULL) GetChatter()->FoundPlantedBomb(zone->m_index); } // remember where the bomb is GetGameState()->UpdatePlantedBomb(&entity->pev->origin); } } return; } case EVENT_BOMB_PLANTED: { // if we're a CT, forget what we're doing and go after the bomb if (m_iTeam == CT) { Idle(); } // if we are following someone, stop following if (IsFollowing()) { StopFollowing(); Idle(); } OnEvent(EVENT_BOMB_BEEP, other); return; } case EVENT_BOMB_DEFUSE_ABORTED: PrintIfWatched("BOMB DEFUSE ABORTED\n"); return; case EVENT_WEAPON_FIRED: case EVENT_WEAPON_FIRED_ON_EMPTY: case EVENT_WEAPON_RELOADED: { if (m_enemy == entity && IsUsingKnife()) ForceRun(5.0f); break; } default: break; } // Process radio events from our team if (player != NULL && BotRelationship(player) == BOT_TEAMMATE && event > EVENT_START_RADIO_1 && event < EVENT_END_RADIO) { // TODO: Distinguish between radio commands and responses if (event != EVENT_RADIO_AFFIRMATIVE && event != EVENT_RADIO_NEGATIVE && event != EVENT_RADIO_REPORTING_IN) { m_lastRadioCommand = event; m_lastRadioRecievedTimestamp = gpGlobals->time; m_radioSubject = player; m_radioPosition = player->pev->origin; } } // player_follows needs a player if (player == NULL) return; if (!IsRogue() && event == EVENT_HOSTAGE_CALLED_FOR_HELP && m_iTeam == CT && IsHunting()) { if ((entity->pev->origin - pev->origin).IsLengthGreaterThan(1000.0f)) return; if (IsVisible(&entity->Center())) { m_task = COLLECT_HOSTAGES; m_taskEntity = NULL; Run(); m_goalEntity = entity; MoveTo(&entity->pev->origin, m_hostageEscortCount == 0 ? SAFEST_ROUTE : FASTEST_ROUTE); PrintIfWatched("I'm fetching a hostage that called out to me\n"); return; } } // don't pay attention to noise that friends make if (!IsEnemy(player)) return; float range; PriorityType priority; bool isHostile; if (IsGameEventAudible(event, entity, other, &range, &priority, &isHostile) == false) return; if (event == EVENT_HOSTAGE_USED) { if (m_iTeam == CT) return; if ((entity->pev->origin - pev->origin).IsLengthGreaterThan(range)) return; GetChatter()->HostagesBeingTaken(); if (!GetGameState()->GetNearestVisibleFreeHostage() && m_task != GUARD_HOSTAGE_RESCUE_ZONE && GuardRandomZone()) { m_task = GUARD_HOSTAGE_RESCUE_ZONE; m_taskEntity = NULL; SetDisposition(OPPORTUNITY_FIRE); PrintIfWatched("Trying to beat them to an escape zone!\n"); } } // check if noise is close enough for us to hear const Vector *newNoisePosition = &player->pev->origin; float newNoiseDist = (pev->origin - *newNoisePosition).Length(); if (newNoiseDist < range) { // we heard the sound if ((IsLocalPlayerWatchingMe() && cv_bot_debug.value == 3.0f) || cv_bot_debug.value == 4.0f) { PrintIfWatched("Heard noise (%s from %s, pri %s, time %3.1f)\n", (event == EVENT_WEAPON_FIRED) ? "Weapon fire " : "", STRING(player->pev->netname), (priority == PRIORITY_HIGH) ? "HIGH" : ((priority == PRIORITY_MEDIUM) ? "MEDIUM" : "LOW"), gpGlobals->time); } if (event == EVENT_PLAYER_FOOTSTEP && IsUsingSniperRifle() && newNoiseDist < 300.0) EquipPistol(); // should we pay attention to it // if noise timestamp is zero, there is no prior noise if (m_noiseTimestamp > 0.0f) { // only overwrite recent sound if we are louder (closer), or more important - if old noise was long ago, its faded const float shortTermMemoryTime = 3.0f; if (gpGlobals->time - m_noiseTimestamp < shortTermMemoryTime) { // prior noise is more important - ignore new one if (priority < m_noisePriority) return; float oldNoiseDist = (pev->origin - m_noisePosition).Length(); if (newNoiseDist >= oldNoiseDist) return; } } // find the area in which the noise occured // TODO: Better handle when noise occurs off the nav mesh // TODO: Make sure noise area is not through a wall or ceiling from source of noise // TODO: Change GetNavTravelTime to better deal with NULL destination areas CNavArea *noiseArea = TheNavAreaGrid.GetNavArea(newNoisePosition); if (noiseArea == NULL) noiseArea = TheNavAreaGrid.GetNearestNavArea(newNoisePosition); if (noiseArea == NULL) { PrintIfWatched(" *** Noise occurred off the nav mesh - ignoring!\n"); return; } m_noiseArea = noiseArea; // remember noise priority m_noisePriority = priority; // randomize noise position in the area a bit - hearing isn't very accurate // the closer the noise is, the more accurate our placement // TODO: Make sure not to pick a position on the opposite side of ourselves. const float maxErrorRadius = 400.0f; const float maxHearingRange = 2000.0f; float errorRadius = maxErrorRadius * newNoiseDist / maxHearingRange; m_noisePosition.x = newNoisePosition->x + RANDOM_FLOAT(-errorRadius, errorRadius); m_noisePosition.y = newNoisePosition->y + RANDOM_FLOAT(-errorRadius, errorRadius); // make sure noise position remains in the same area m_noiseArea->GetClosestPointOnArea(&m_noisePosition, &m_noisePosition); m_isNoiseTravelRangeChecked = false; // note when we heard the noise m_noiseTimestamp = gpGlobals->time; } }
void ChattersModel::OnChatPropertyChanged(UINT16 account_id, const ChatInfo& room, const OpStringC& chatter, const OpStringC& changed_by, EngineTypes::ChatProperty property, const OpStringC& property_string, INT32 property_value) { if (!IsModelForRoom(account_id, room.ChatName())) { return; } if (property == EngineTypes::ROOM_MODERATED) { m_is_moderated = property_value; ChangeAll(); return; } ChattersModelItem* item = GetChatter(chatter); if (item) { switch (property) { case EngineTypes::CHATTER_NICK: { m_chatters_hash_table.Remove(item->GetName(), &item); item->SetName(property_string); m_chatters_hash_table.Add(item->GetName(), item); break; } case EngineTypes::CHATTER_OPERATOR: { item->SetOperator(property_value); break; } case EngineTypes::CHATTER_VOICED: { item->SetVoiced(property_value); break; } case EngineTypes::CHATTER_PRESENCE: { item->SetStatus((AccountTypes::ChatStatus) property_value); break; } case EngineTypes::CHATTER_UNKNOWN_MODE_OPERATOR: { item->SetOperator(property_value == 1); } case EngineTypes::CHATTER_UNKNOWN_MODE_VOICED: { item->SetVoiced(property_value == 1); } } item->Change(TRUE); } }
// Invoked when injured by something // NOTE: We dont want to directly call Attack() here, or the bots will have super-human reaction times when injured BOOL CCSBot::TakeDamage(entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType) { CBaseEntity *pAttacker = GetClassPtr<CCSEntity>((CBaseEntity *)pevInflictor); // if we were attacked by a teammate, rebuke if (pAttacker->IsPlayer()) { CBasePlayer *pPlayer = static_cast<CBasePlayer *>(pAttacker); if (BotRelationship(pPlayer) == BOT_TEAMMATE && !pPlayer->IsBot()) { GetChatter()->FriendlyFire(); } if (IsEnemy(pPlayer)) { // Track previous attacker so we don't try to panic multiple times for a shotgun blast CBasePlayer *lastAttacker = m_attacker; float lastAttackedTimestamp = m_attackedTimestamp; // keep track of our last attacker m_attacker = pPlayer; m_attackedTimestamp = gpGlobals->time; // no longer safe AdjustSafeTime(); if (!IsSurprised() && (m_attacker != lastAttacker || m_attackedTimestamp != lastAttackedTimestamp)) { // being hurt by an enemy we can't see causes panic if (!IsVisible(pPlayer, CHECK_FOV)) { bool bPanic = false; // if not attacking anything, look around to try to find attacker if (!IsAttacking()) { bPanic = true; } else { // we are attacking if (!IsEnemyVisible()) { // can't see our current enemy, panic to acquire new attacker bPanic = true; } } if (!bPanic) { float invSkill = 1.0f - GetProfile()->GetSkill(); float panicChance = invSkill * invSkill * 50.0f; if (panicChance > RANDOM_FLOAT(0, 100)) { bPanic = true; } } if (bPanic) { // can't see our current enemy, panic to acquire new attacker Panic(m_attacker); } } } } } // extend return CBasePlayer::TakeDamage(pevInflictor, pevAttacker, flDamage, bitsDamageType); }
// Respond to radio commands from HUMAN players void CCSBot::RespondToRadioCommands() { // bots use the chatter system to respond to each other if (m_radioSubject.IsValid() && m_radioSubject->IsPlayer()) { if (m_radioSubject->IsBot()) { m_lastRadioCommand = EVENT_INVALID; return; } } if (m_lastRadioCommand == EVENT_INVALID) return; // a human player has issued a radio command GetChatter()->ResetRadioSilenceDuration(); // if we are doing something important, ignore the radio // unless it is a "report in" request - we can do that while we continue to do other things // TODO: Create "uninterruptable" flag if (m_lastRadioCommand != EVENT_RADIO_REPORT_IN_TEAM) { if (IsBusy()) { // consume command m_lastRadioCommand = EVENT_INVALID; return; } } // wait for reaction time before responding // delay needs to be long enough for the radio message we're responding to to finish float respondTime = 1.0f + 2.0f * GetProfile()->GetReactionTime(); if (IsRogue()) respondTime += 2.0f; if (gpGlobals->time - m_lastRadioRecievedTimestamp < respondTime) return; // rogues won't follow commands, unless already following the player if (!IsFollowing() && IsRogue()) { if (IsRadioCommand(m_lastRadioCommand)) { GetChatter()->Negative(); } // consume command m_lastRadioCommand = EVENT_INVALID; return; } if (!m_radioSubject) return; // respond to command bool canDo = false; const float inhibitAutoFollowDuration = 60.0f; switch (m_lastRadioCommand) { case EVENT_RADIO_REPORT_IN_TEAM: { GetChatter()->ReportingIn(); break; } case EVENT_RADIO_FOLLOW_ME: case EVENT_RADIO_COVER_ME: case EVENT_RADIO_STICK_TOGETHER_TEAM: case EVENT_RADIO_REGROUP_TEAM: { if (!IsFollowing()) { Follow(m_radioSubject); m_radioSubject->AllowAutoFollow(); canDo = true; } break; } case EVENT_RADIO_ENEMY_SPOTTED: case EVENT_RADIO_NEED_BACKUP: case EVENT_RADIO_TAKING_FIRE: { if (!IsFollowing()) { Follow(m_radioSubject); GetChatter()->Say("OnMyWay"); m_radioSubject->AllowAutoFollow(); canDo = false; } break; } case EVENT_RADIO_TEAM_FALL_BACK: { if (TryToRetreat()) canDo = true; break; } case EVENT_RADIO_HOLD_THIS_POSITION: { // find the leader's area SetTask(HOLD_POSITION); StopFollowing(); m_radioSubject->InhibitAutoFollow(inhibitAutoFollowDuration); Hide(TheNavAreaGrid.GetNearestNavArea(&m_radioPosition)); canDo = true; break; } case EVENT_RADIO_GO_GO_GO: case EVENT_RADIO_STORM_THE_FRONT: { StopFollowing(); Hunt(); canDo = true; m_radioSubject->InhibitAutoFollow(inhibitAutoFollowDuration); break; } case EVENT_RADIO_GET_OUT_OF_THERE: { if (TheCSBots()->IsBombPlanted()) { EscapeFromBomb(); m_radioSubject->InhibitAutoFollow(inhibitAutoFollowDuration); canDo = true; } break; } case EVENT_RADIO_SECTOR_CLEAR: { // if this is a defusal scenario, and the bomb is planted, // and a human player cleared a bombsite, check it off our list too if (TheCSBots()->GetScenario() == CCSBotManager::SCENARIO_DEFUSE_BOMB) { if (m_iTeam == CT && TheCSBots()->IsBombPlanted()) { const CCSBotManager::Zone *zone = TheCSBots()->GetClosestZone(m_radioSubject); if (zone) { GetGameState()->ClearBombsite(zone->m_index); // if we are huting for the planted bomb, re-select bombsite if (GetTask() == FIND_TICKING_BOMB) Idle(); canDo = true; } } } break; } default: // ignore all other radio commands for now return; } if (canDo) { // affirmative GetChatter()->Affirmative(); // if we agreed to follow a new command, put away our grenade if (IsRadioCommand(m_lastRadioCommand) && IsUsingGrenade()) { EquipBestWeapon(); } } // consume command m_lastRadioCommand = EVENT_INVALID; }