void PainState::Init( idAI *owner ) { // Init base class first State::Init( owner ); DM_LOG( LC_AI, LT_INFO )LOGSTRING( "PainState initialised.\r" ); assert( owner ); // grayman #3424 - if already playing a pain anim, skip this one if( idStr( owner->WaitState( ANIMCHANNEL_TORSO ) ) == "pain" ) { return; } Memory &memory = owner->GetMemory(); // Play the animation owner->SetAnimState( ANIMCHANNEL_TORSO, "Torso_Pain", 4 ); owner->SetAnimState( ANIMCHANNEL_LEGS, "Legs_Pain", 4 ); owner->SetWaitState( ANIMCHANNEL_TORSO, "pain" ); owner->SetWaitState( ANIMCHANNEL_LEGS, "pain" ); // Set end time _stateEndTime = gameLocal.time + 5000; // grayman #3140 - if drowning, skip issuing a message. The drowning // sound effect is handled in idActor::Damage(). if( memory.causeOfPain != EPC_Drown ) { memory.alertPos = owner->GetPhysics()->GetOrigin(); // Do a single bark and assemble an AI message CommMessagePtr message = CommMessagePtr( new CommMessage( CommMessage::DetectedEnemy_CommType, owner, NULL, // from this AI to anyone NULL, memory.alertPos, 0 ) ); owner->commSubsystem->AddCommTask( CommunicationTaskPtr( new SingleBarkTask( "snd_pain_large", message ) ) ); if( cv_ai_debug_transition_barks.GetBool() ) { gameLocal.Printf( "%d: %s is hurt, barks 'snd_pain_large'\n", gameLocal.time, owner->GetName() ); } } }
// Gets called each time the mind is thinking void PainState::Think( idAI *owner ) { if( ( gameLocal.time >= _stateEndTime ) || ( idStr( owner->WaitState( ANIMCHANNEL_TORSO ) ) != "pain" ) ) { bool willBark = ( owner->AI_AlertLevel < owner->thresh_5 ); // don't bark a response if in combat bool willFlee = ( ( ( owner->GetNumMeleeWeapons() == 0 ) && ( owner->GetNumRangedWeapons() == 0 ) ) || owner->spawnArgs.GetBool( "is_civilian", "0" ) ); // grayman #3140 - what caused this pain? Memory &memory = owner->GetMemory(); if( memory.causeOfPain == EPC_Drown ) { // no bark and no fleeing if drowning willBark = false; willFlee = false; } else if( memory.causeOfPain == EPC_Projectile ) { // If fleeing, snd_taking_fire will be played at the start of the flee state, // so there's no reason to play it here. // If not fleeing, play snd_taking_fire here. willBark = !willFlee; memory.posEnemySeen = owner->GetPhysics()->GetOrigin(); } if( willBark ) { // grayman #3140 - Emit the snd_taking_fire bark // This will hold the message to be delivered with the bark CommMessagePtr message; message = CommMessagePtr( new CommMessage( CommMessage::RequestForHelp_CommType, owner, NULL, // from this AI to anyone NULL, owner->GetPhysics()->GetOrigin(), 0 ) ); owner->commSubsystem->AddCommTask( CommunicationTaskPtr( new SingleBarkTask( "snd_taking_fire", message ) ) ); if( cv_ai_debug_transition_barks.GetBool() ) { gameLocal.Printf( "%d: %s is hurt, barks 'snd_taking_fire'\n", gameLocal.time, owner->GetName() ); } } if( willFlee && memory.fleeingDone ) { // grayman #3331 - civilians and unarmed AI should flee // grayman #3847 - already fleeing? if( owner->AI_AlertLevel >= owner->thresh_5 ) { // grayman #3847 owner->fleeingEvent = false; // I'm fleeing an enemy owner->fleeingFromPerson = owner->GetEnemy(); // grayman #3847 } else { owner->fleeingEvent = true; // I'm fleeing the scene, not fleeing an enemy owner->fleeingFromPerson = NULL; // grayman #3847 } owner->fleeingFrom = owner->GetPhysics()->GetOrigin(); // grayman #3848 owner->emitFleeBarks = true; // grayman #3474 if( memory.fleeingDone ) { // grayman #3847 - only flee if not already fleeing owner->GetMind()->SwitchState( STATE_FLEE ); } return; } // End this state owner->GetMind()->EndState(); } }
void IdleSleepState::Init(idAI* owner) { // Init base class first State::Init(owner); DM_LOG(LC_AI, LT_INFO)LOGSTRING("IdleSleepState initialised.\r"); assert(owner); // Memory shortcut Memory& memory = owner->GetMemory(); memory.alertClass = EAlertNone; memory.alertType = EAlertTypeNone; if (owner->HasSeenEvidence() && !owner->spawnArgs.GetBool("disable_alert_idle", "0")) { owner->GetMind()->SwitchState(STATE_ALERT_IDLE); return; } if (owner->GetMoveType() != MOVETYPE_SLEEP && (!owner->ReachedPos(memory.idlePosition, MOVE_TO_POSITION) || owner->GetCurrentYaw() != memory.idleYaw)) { // we need to get to the bed first before starting to sleep, back to idle state owner->GetMind()->SwitchState(owner->backboneStates[ERelaxed]); return; } // Ensure we are in the correct alert level if (!CheckAlertLevel(owner)) return; _startSleeping = owner->spawnArgs.GetBool("sleeping", "0"); _startSitting = owner->spawnArgs.GetBool("sitting", "0"); _alertLevelDecreaseRate = 0.01f; // owner->SheathWeapon(); owner->actionSubsystem->ClearTasks(); owner->senseSubsystem->ClearTasks(); owner->commSubsystem->ClearTasks(); InitialiseMovement(owner); int idleBarkIntervalMin = SEC2MS(owner->spawnArgs.GetInt("sleep_bark_interval_min", "10")); int idleBarkIntervalMax = SEC2MS(owner->spawnArgs.GetInt("sleep_bark_interval_max", "30")); owner->commSubsystem->AddCommTask( CommunicationTaskPtr(new RepeatedBarkTask("snd_sleeping", idleBarkIntervalMin, idleBarkIntervalMax)) ); // Let the AI update their weapons (make them nonsolid) owner->UpdateAttachmentContents(false); }
void AgitatedSearchingStateLanternBot::Init(idAI* owner) { // Init base class first State::Init(owner); DM_LOG(LC_AI, LT_INFO)LOGSTRING("AgitatedSearchingStateLanternBot initialised.\r"); assert(owner); // Ensure we are in the correct alert level if (!CheckAlertLevel(owner)) { return; } owner->movementSubsystem->ClearTasks(); owner->senseSubsystem->ClearTasks(); owner->actionSubsystem->ClearTasks(); owner->commSubsystem->ClearTasks(); // grayman #3182 owner->searchSubsystem->ClearTasks(); // grayman #3857 owner->StopMove(MOVE_STATUS_DONE); // Start with an invalid position _curAlertPos = idVec3(idMath::INFINITY, idMath::INFINITY, idMath::INFINITY); // Move to a position where we can light up the alert position from MoveTowardAlertPos(owner); owner->GetMemory().currentSearchEventID = owner->LogSuspiciousEvent( E_EventTypeMisc, owner->GetPhysics()->GetOrigin(), NULL, true ); // grayman #3857 // This will hold the message to be delivered with the inaudible bark CommMessagePtr message(new CommMessage( CommMessage::RequestForHelp_CommType, // grayman #3857 - asking for a response //CommMessage::DetectedEnemy_CommType, // grayman #3857 - this does nothing when no entity (parameter 4) is provided owner, NULL,// from this AI to anyone NULL, _curAlertPos, owner->GetMemory().currentSearchEventID // grayman #3857 (was '0') )); // The communication system plays starting bark owner->commSubsystem->AddCommTask( CommunicationTaskPtr(new RepeatedBarkTask("snd_spotted_noise", 3000, 4000, message)) ); // Add the script task blowing the alarm whistle owner->actionSubsystem->PushTask(TaskPtr(new ScriptTask("startAlarmWhistle"))); }
void SearchingState::Init( idAI *owner ) { // Init base class first State::Init( owner ); DM_LOG( LC_AI, LT_INFO )LOGSTRING( "SearchingState initialised.\r" ); assert( owner ); // Ensure we are in the correct alert level if( !CheckAlertLevel( owner ) ) { return; } if( owner->GetMoveType() == MOVETYPE_SIT || owner->GetMoveType() == MOVETYPE_SLEEP ) { owner->GetUp(); } // Shortcut reference Memory &memory = owner->GetMemory(); float alertTime = owner->atime3 + owner->atime3_fuzzyness * ( gameLocal.random.RandomFloat() - 0.5 ); _alertLevelDecreaseRate = ( owner->thresh_4 - owner->thresh_3 ) / alertTime; if( owner->AlertIndexIncreased() || memory.mandatory ) { // grayman #3331 // Setup a new hiding spot search StartNewHidingSpotSearch( owner ); } if( owner->AlertIndexIncreased() ) { // grayman #3423 - when the alert level is ascending, kill the repeated bark task owner->commSubsystem->ClearTasks(); // Play bark if alert level is ascending // grayman #3496 - enough time passed since last alert bark? if( gameLocal.time >= memory.lastTimeAlertBark + MIN_TIME_BETWEEN_ALERT_BARKS ) { idStr bark; if( ( memory.alertedDueToCommunication == false ) && ( ( memory.alertType == EAlertTypeSuspicious ) || ( memory.alertType == EAlertTypeEnemy ) ) ) { bool friendsNear = ( ( MS2SEC( gameLocal.time - memory.lastTimeFriendlyAISeen ) ) <= MAX_FRIEND_SIGHTING_SECONDS_FOR_ACCOMPANIED_ALERT_BARK ); if( ( memory.alertClass == EAlertVisual_1 ) || ( memory.alertClass == EAlertVisual_2 ) || // grayman #2603, #3424 // (memory.alertClass == EAlertVisual_3) ) || // grayman #3472 - no longer needed ( memory.alertClass == EAlertVisual_4 ) ) { // grayman #3498 if( friendsNear ) { bark = "snd_alert3sc"; } else { bark = "snd_alert3s"; } } else if( memory.alertClass == EAlertAudio ) { if( friendsNear ) { bark = "snd_alert3hc"; } else { bark = "snd_alert3h"; } } else if( friendsNear ) { bark = "snd_alert3c"; } else { bark = "snd_alert3"; } // Allocate a SingleBarkTask, set the sound and enqueue it owner->commSubsystem->AddCommTask( CommunicationTaskPtr( new SingleBarkTask( bark ) ) ); memory.lastTimeAlertBark = gameLocal.time; // grayman #3496 if( cv_ai_debug_transition_barks.GetBool() ) { gameLocal.Printf( "%d: %s rises to Searching state, barks '%s'\n", gameLocal.time, owner->GetName(), bark.c_str() ); } } } else { if( cv_ai_debug_transition_barks.GetBool() ) { gameLocal.Printf( "%d: %s rises to Searching state, can't bark 'snd_alert3{s/sc/h/hc/c}' yet\n", gameLocal.time, owner->GetName() ); } } } else if( memory.alertType == EAlertTypeEnemy ) { // reduce the alert type, so we can react to other alert types (such as a dead person) memory.alertType = EAlertTypeSuspicious; } // grayman #3472 - When ascending, set up a repeated bark if( owner->AlertIndexIncreased() ) { owner->commSubsystem->AddSilence( 5000 + gameLocal.random.RandomInt( 3000 ) ); // grayman #3424 // This will hold the message to be delivered with the bark CommMessagePtr message( new CommMessage( CommMessage::DetectedEnemy_CommType, owner, NULL,// from this AI to anyone NULL, idVec3( idMath::INFINITY, idMath::INFINITY, idMath::INFINITY ), 0 ) ); int minTime = SEC2MS( owner->spawnArgs.GetFloat( "searchbark_delay_min", "10" ) ); int maxTime = SEC2MS( owner->spawnArgs.GetFloat( "searchbark_delay_max", "15" ) ); owner->commSubsystem->AddCommTask( CommunicationTaskPtr( new RepeatedBarkTask( "snd_state3", minTime, maxTime, message ) ) ); } else { // descending // Allow repeated barks from Agitated Searching to continue. } if( !owner->HasSeenEvidence() ) { owner->SheathWeapon(); owner->UpdateAttachmentContents( false ); } else { // Let the AI update their weapons (make them solid) owner->UpdateAttachmentContents( true ); } }
// Gets called each time the mind is thinking void PocketPickedState::Think(idAI* owner) { owner->PerformVisualScan(); // Let the AI check its senses // Check conditions for continuing. moveType_t moveType = owner->GetMoveType(); Memory& memory = owner->GetMemory(); if ( owner->AI_DEAD || // stop reacting if dead owner->AI_KNOCKEDOUT || // or unconscious (owner->AI_AlertIndex >= ESearching) || // stop if alert level is too high memory.stopReactingToPickedPocket || // check if something happened to abort the state (moveType == MOVETYPE_SLEEP) || // or asleep (moveType == MOVETYPE_SIT_DOWN) || // or in the act of sitting down (moveType == MOVETYPE_LAY_DOWN) || // or in the act of lying down to sleep (moveType == MOVETYPE_GET_UP) || // or getting up from sitting (moveType == MOVETYPE_GET_UP_FROM_LYING) ) // or getting up from lying down { Wrapup(owner); return; } switch (_pocketPickedState) { case EStateReact: { owner->actionSubsystem->ClearTasks(); owner->movementSubsystem->ClearTasks(); owner->m_ReactingToPickedPocket = true; // Emit the picked pocket bark if alert level is low, and we're not in Alert Idle if (owner->AI_AlertIndex < EObservant) { if (!(owner->HasSeenEvidence() && ( owner->spawnArgs.GetBool("disable_alert_idle", "0") == false) ) ) { CommMessagePtr message; owner->commSubsystem->AddCommTask(CommunicationTaskPtr(new SingleBarkTask("snd_notice_pickpocket", message))); } } // If this occurred w/in the original alert window that surrounds the // pickpocket event, it's one more piece of evidence of something out of place. if (memory.insideAlertWindow) { memory.insideAlertWindow = false; memory.countEvidenceOfIntruders += EVIDENCE_COUNT_INCREASE_SUSPICIOUS; memory.posEvidenceIntruders = owner->GetPhysics()->GetOrigin(); memory.timeEvidenceIntruders = gameLocal.time; } // Increase the alert level? float alertInc = owner->spawnArgs.GetFloat("pickpocket_alert","0"); if (alertInc > 0.0f) { // Set the new alert level, but cap it just under Combat. // If the new alert level pushes the AI up into // Searching or Agitated Searching, the Picked Pocket State will end. float newAlertLevel = owner->AI_AlertLevel + alertInc; if ( newAlertLevel >= owner->thresh_5 ) { newAlertLevel = owner->thresh_5 - 0.1; } owner->SetAlertLevel(newAlertLevel); // If the alert level is now in Searching or Agitated Searching, // create a search area. if ( owner->AI_AlertIndex >= ESearching ) { memory.alertPos = owner->GetPhysics()->GetOrigin(); memory.alertClass = EAlertTactile; memory.alertType = EAlertTypeSuspiciousItem; memory.alertRadius = AUDIO_ALERT_RADIUS; memory.alertSearchVolume = AUDIO_SEARCH_VOLUME; memory.alertSearchExclusionVolume.Zero(); owner->AI_VISALERT = false; memory.visualAlert = false; memory.mandatory = false; memory.stimulusLocationItselfShouldBeSearched = true; memory.investigateStimulusLocationClosely = false; memory.alertedDueToCommunication = false; } } _pocketPickedState = EStateStopping; break; } case EStateStopping: // If AI is sitting, he won't stand or stop moving if ( owner->GetMoveType() == MOVETYPE_SIT ) { _waitEndTime = gameLocal.time; _pocketPickedState = EStateTurnToward; } else { // Stop moving owner->StopMove(MOVE_STATUS_DONE); _pocketPickedState = EStateStartAnim; } break; case EStateStartAnim: // Don't play the animation if you're holding a torch or lantern. // If you're holding a weapon, you probably shouldn't be here, but // the weapon defs include replacement anims. if ( ( owner->GetTorch() == NULL ) && ( owner->GetLantern() == NULL )) { // Start the animation owner->SetAnimState(ANIMCHANNEL_TORSO, "Torso_PocketPicked", 4); owner->SetAnimState(ANIMCHANNEL_LEGS, "Legs_PocketPicked", 4); owner->SetWaitState("pocketPicked"); owner->SetWaitState(ANIMCHANNEL_TORSO, "pocketPicked"); owner->SetWaitState(ANIMCHANNEL_LEGS, "pocketPicked"); } _pocketPickedState = EStatePlayAnim; break; case EStatePlayAnim: if (idStr(owner->WaitState()) != "pocketPicked") { // Turn to look behind you. owner->TurnToward(owner->GetCurrentYaw() + 180); _waitEndTime = gameLocal.time + 1000; _pocketPickedState = EStateTurnToward; } break; case EStateTurnToward: if (gameLocal.time >= _waitEndTime) { // Done turning. idVec3 vec; float duration = LOOK_TIME_MIN + gameLocal.random.RandomFloat()*(LOOK_TIME_MAX - LOOK_TIME_MIN); if ( owner->GetMoveType() == MOVETYPE_SIT ) { // Stare at the ground to your right or left, as if looking for something idAngles angles = owner->viewAxis.ToAngles(); float forward = angles.yaw; float lookAngle = forward + (gameLocal.random.RandomFloat() < 0.5 ? 70 : -70); idAngles lookAngles(0.0f, lookAngle, 0.0f); vec = lookAngles.ToForward()*100; // ToForward() returns unit vector } else { // Stare at the ground in front of you, as if looking for something vec = owner->viewAxis.ToAngles().ToForward()*100; // ToForward() returns unit vector } vec.z = 0; idVec3 lookAtMe = owner->GetEyePosition() + vec; lookAtMe.z = owner->GetPhysics()->GetOrigin().z; owner->Event_LookAtPosition(lookAtMe, MS2SEC(duration)); _pocketPickedState = EStateLookAt; _waitEndTime = gameLocal.time + duration; } break; case EStateLookAt: // look for a while if (gameLocal.time >= _waitEndTime) { // Done looking, end this state Wrapup(owner); return; } break; default: break; } }
void LostTrackOfEnemyState::Init(idAI* owner) { // Init base class first State::Init(owner); DM_LOG(LC_AI, LT_INFO)LOGSTRING("LostTrackOfEnemyState initialised.\r"); assert(owner); // Shortcut reference Memory& memory = owner->GetMemory(); owner->SetAlertLevel((owner->thresh_5 + owner->thresh_4) * 0.5); // Draw weapon, if we haven't already if (!owner->GetAttackFlag(COMBAT_MELEE) && !owner->GetAttackFlag(COMBAT_RANGED)) { // grayman #3331 - draw your ranged weapon if you have one, otherwise draw your melee weapon. // Note that either weapon could be drawn, but if we default to melee, AI with ranged and // melee weapons will draw their melee weapon, and we'll never see ranged weapons get drawn. // Odds are that the enemy is nowhere nearby anyway, since we're just searching. if ( ( owner->GetNumRangedWeapons() > 0 ) && !owner->spawnArgs.GetBool("unarmed_ranged","0") ) { owner->DrawWeapon(COMBAT_RANGED); } else if ( ( owner->GetNumMeleeWeapons() > 0 ) && !owner->spawnArgs.GetBool("unarmed_melee","0") ) { owner->DrawWeapon(COMBAT_MELEE); } } // Setup the search parameters memory.alertPos = owner->lastVisibleReachableEnemyPos; memory.alertRadius = LOST_ENEMY_ALERT_RADIUS; memory.alertSearchVolume = LOST_ENEMY_SEARCH_VOLUME; memory.alertSearchExclusionVolume.Zero(); memory.alertedDueToCommunication = false; memory.stimulusLocationItselfShouldBeSearched = true; // Forget about the enemy, prevent UpdateEnemyPosition from "cheating". owner->ClearEnemy(); memory.visualAlert = false; // grayman #2422 memory.mandatory = true; // grayman #3331 // Enqueue a lost track of enemy bark owner->commSubsystem->AddCommTask( CommunicationTaskPtr(new SingleBarkTask("snd_lostTrackOfEnemy")) ); if (cv_ai_debug_transition_barks.GetBool()) { gameLocal.Printf("%d: %s loses track of the enemy, barks 'snd_lostTrackOfEnemy'\n",gameLocal.time,owner->GetName()); } // For now, clear the action tasks and movement tasks owner->actionSubsystem->ClearTasks(); owner->movementSubsystem->ClearTasks(); owner->GetMind()->EndState(); }
// Gets called each time the mind is thinking void CombatState::Think(idAI* owner) { // Do we have an expiry date? if (_endTime > 0) { if (gameLocal.time >= _endTime) { owner->GetMind()->EndState(); } return; } // Ensure we are in the correct alert level if (!CheckAlertLevel(owner)) { // owner->GetMind()->EndState(); // grayman #3182 - already done in CheckAlertLevel() return; } // grayman #3331 - make sure you're still fighting the same enemy. // grayman #3355 - fight the closest enemy idActor* enemy = _enemy.GetEntity(); idActor* newEnemy = owner->GetEnemy(); if ( enemy ) { if ( newEnemy && ( newEnemy != enemy ) ) { idVec3 ownerOrigin = owner->GetPhysics()->GetOrigin(); float dist2EnemySqr = ( enemy->GetPhysics()->GetOrigin() - ownerOrigin ).LengthSqr(); float dist2NewEnemySqr = ( newEnemy->GetPhysics()->GetOrigin() - ownerOrigin ).LengthSqr(); if ( dist2NewEnemySqr < dist2EnemySqr ) { owner->GetMind()->EndState(); return; // state has ended } } } else { enemy = newEnemy; } if (!CheckEnemyStatus(enemy, owner)) { owner->GetMind()->EndState(); return; // state has ended } // grayman #3520 - don't look toward new alerts if ( owner->m_lookAtAlertSpot ) { owner->m_lookAtAlertSpot = false; owner->m_lookAtPos = idVec3(idMath::INFINITY,idMath::INFINITY,idMath::INFINITY); } // angua: look at enemy owner->Event_LookAtPosition(enemy->GetEyePosition(), gameLocal.msec); Memory& memory = owner->GetMemory(); idVec3 vec2Enemy = enemy->GetPhysics()->GetOrigin() - owner->GetPhysics()->GetOrigin(); float dist2Enemy = vec2Enemy.LengthFast(); // grayman #3331 - need to take vertical separation into account. It's possible to have the origins // close enough to be w/in the melee zone, but still be unable to hit the enemy. bool inMeleeRange = ( dist2Enemy <= ( 3 * owner->GetMeleeRange() ) ); ECombatType newCombatType; if ( inMeleeRange && !_meleePossible ) // grayman #3355 - can't fight up close { owner->fleeingEvent = false; // grayman #3356 owner->emitFleeBarks = false; // grayman #3474 owner->GetMind()->SwitchState(STATE_FLEE); return; } if ( !inMeleeRange && _rangedPossible ) { newCombatType = COMBAT_RANGED; } else { newCombatType = COMBAT_MELEE; } // Check for situation where you're in the melee zone, yet you're unable to hit // the enemy. This can happen if the enemy is above or below you and you can't // reach them. switch(_combatSubState) { case EStateReaction: { if ( gameLocal.time < _reactionEndTime ) { return; // stay in this state until the reaction time expires } // Check to see if the enemy is still visible. // grayman #2816 - Visibility doesn't matter if you're in combat because // you bumped into your enemy. idEntity* tactEnt = owner->GetTactEnt(); if ( ( tactEnt == NULL ) || !tactEnt->IsType(idActor::Type) || ( tactEnt != enemy ) || !owner->AI_TACTALERT ) { if ( !owner->CanSee(enemy, true) ) { owner->ClearEnemy(); owner->SetAlertLevel(owner->thresh_5 - 0.1); // reset alert level just under Combat owner->GetMind()->EndState(); return; } } owner->m_ignorePlayer = false; // grayman #3063 - clear flag that prevents mission statistics on player sightings // The AI has processed his reaction, and needs to move into combat, or flee. _criticalHealth = owner->spawnArgs.GetInt("health_critical", "0"); // greebo: Check for weapons and flee if ... if ( ( !_meleePossible && !_rangedPossible ) || // ... I'm unarmed ( owner->spawnArgs.GetBool("is_civilian", "0")) || // ... I'm a civilian, and don't fight ( owner->health < _criticalHealth ) ) // grayman #3140 ... I'm very damaged and can't afford to engage in combat { owner->fleeingEvent = false; // grayman #3356 owner->emitFleeBarks = true; // grayman #3474 owner->GetMind()->SwitchState(STATE_FLEE); return; } memory.stopRelight = true; // grayman #2603 - abort a relight in progress memory.stopExaminingRope = true; // grayman #2872 - stop examining a rope memory.stopReactingToHit = true; // grayman #2816 _combatSubState = EStateDoOnce; break; } case EStateDoOnce: { // Check for sitting or sleeping moveType_t moveType = owner->GetMoveType(); if ( moveType == MOVETYPE_SIT || moveType == MOVETYPE_SLEEP || moveType == MOVETYPE_SIT_DOWN || moveType == MOVETYPE_LAY_DOWN ) { owner->GetUp(); // okay if called multiple times return; } // grayman #3009 - check for getting up from sitting or sleeping if ( moveType == MOVETYPE_GET_UP || moveType == MOVETYPE_GET_UP_FROM_LYING ) { return; } // not sitting or sleeping at this point // Stop what you're doing owner->StopMove(MOVE_STATUS_DONE); owner->movementSubsystem->ClearTasks(); owner->senseSubsystem->ClearTasks(); owner->actionSubsystem->ClearTasks(); // Bark // This will hold the message to be delivered with the bark, if appropriate CommMessagePtr message; // Only alert the bystanders if we didn't receive the alert by message ourselves if (!memory.alertedDueToCommunication) { message = CommMessagePtr(new CommMessage( CommMessage::DetectedEnemy_CommType, owner, NULL, // from this AI to anyone enemy, memory.lastEnemyPos, 0 )); } // grayman #3496 - This is an alert bark, but it's not subject to the delay // between alert barks because the previous bark will end before we get here. // It's also the culmination of a journey up from Idle State, and if the AI is // going to go after an enemy, this bark needs to be heard. // We'll still reset the last alert bark time, though. // The communication system plays starting bark // grayman #3343 - accommodate different barks for human and non-human enemies idPlayer* player(NULL); if (enemy->IsType(idPlayer::Type)) { player = static_cast<idPlayer*>(enemy); } idStr bark = ""; if (player && player->m_bShoulderingBody) { bark = "snd_spotted_player_with_body"; } else if ((MS2SEC(gameLocal.time - memory.lastTimeFriendlyAISeen)) <= MAX_FRIEND_SIGHTING_SECONDS_FOR_ACCOMPANIED_ALERT_BARK) { idStr enemyAiUse = enemy->spawnArgs.GetString("AIUse"); if ( ( enemyAiUse == AIUSE_MONSTER ) || ( enemyAiUse == AIUSE_UNDEAD ) ) { bark = "snd_to_combat_company_monster"; } else { bark = "snd_to_combat_company"; } } else { idStr enemyAiUse = enemy->spawnArgs.GetString("AIUse"); if ( ( enemyAiUse == AIUSE_MONSTER ) || ( enemyAiUse == AIUSE_UNDEAD ) ) { bark = "snd_to_combat_monster"; } else { bark = "snd_to_combat"; } } owner->commSubsystem->AddCommTask(CommunicationTaskPtr(new SingleBarkTask(bark, message))); owner->GetMemory().lastTimeAlertBark = gameLocal.time; // grayman #3496 if (cv_ai_debug_transition_barks.GetBool()) { gameLocal.Printf("%d: %s starts combat, barks '%s'\n",gameLocal.time,owner->GetName(),bark.c_str()); } _justDrewWeapon = false; _combatSubState = EStateCheckWeaponState; break; } case EStateCheckWeaponState: // check which combat type we should use { // Can you continue with your current combat type, and not have to switch weapons? // Check for case where melee combat has stalled. You're in the melee zone, but you're // unable to hit the enemy. Perhaps he's higher or lower than you and you can't reach him. if ( !owner->AI_FORWARD && // not moving ( _combatType == COMBAT_MELEE ) && // in melee combat _rangedPossible && // ranged combat is possible !owner->TestMelee() ) // I can't hit the enemy { float orgZ = owner->GetPhysics()->GetOrigin().z; float height = owner->GetPhysics()->GetBounds().GetSize().z; float enemyOrgZ = enemy->GetPhysics()->GetOrigin().z; float enemyHeight = enemy->GetPhysics()->GetBounds().GetSize().z; if ( ( (orgZ + height + owner->melee_range_vert) < enemyOrgZ ) || // enemy too high ( (enemyOrgZ + enemyHeight) < orgZ ) ) // enemy too low { newCombatType = COMBAT_RANGED; } } if ( newCombatType == _combatType ) { // yes - no need to run weapon-switching animations _combatSubState = EStateCombatAndChecks; return; } // Do you need to switch a melee or ranged weapon? You might already have one // drawn, or you might have none drawn, or you might have to change weapons, // or you might be using unarmed attacks, and you don't need a drawn weapon. // Check for unarmed combat. if ( _unarmedMelee && ( newCombatType == COMBAT_MELEE ) ) { // unarmed combat doesn't need attached weapons _combatType = COMBAT_NONE; // clear ranged combat tasks and start melee combat tasks _combatSubState = EStateCombatAndChecks; return; } if ( _unarmedRanged && ( newCombatType == COMBAT_RANGED ) ) { // unarmed combat doesn't need attached weapons _combatType = COMBAT_NONE; // clear melee combat tasks and start ranged combat tasks _combatSubState = EStateCombatAndChecks; return; } // Do you have a drawn weapon? if ( owner->GetAttackFlag(COMBAT_MELEE) && ( newCombatType == COMBAT_MELEE ) ) { // melee weapon is already drawn _combatSubState = EStateCombatAndChecks; return; } if ( owner->GetAttackFlag(COMBAT_RANGED) && ( newCombatType == COMBAT_RANGED ) ) { // ranged weapon is already drawn _combatSubState = EStateCombatAndChecks; return; } // At this point, we know we need to draw a weapon that's not already drawn. // See if you need to sheathe a drawn weapon. if ( ( ( newCombatType == COMBAT_RANGED ) && owner->GetAttackFlag(COMBAT_MELEE) ) || ( ( newCombatType == COMBAT_MELEE ) && owner->GetAttackFlag(COMBAT_RANGED) ) ) { // switch from one type of weapon to another owner->movementSubsystem->ClearTasks(); owner->actionSubsystem->ClearTasks(); _combatType = COMBAT_NONE; // sheathe current weapon so you can draw the other weapon owner->SheathWeapon(); _waitEndTime = gameLocal.time + 2000; // safety net _combatSubState = EStateSheathingWeapon; return; } // No need to sheathe a weapon _combatSubState = EStateDrawWeapon; break; } case EStateSheathingWeapon: { // if you're sheathing a weapon, stay in this state until it's done, or until the timer expires // grayman #3355 - check wait state if ( ( gameLocal.time < _waitEndTime ) && ( idStr(owner->WaitState()) == "sheath") ) { return; } _combatSubState = EStateDrawWeapon; break; } case EStateDrawWeapon: { // grayman #3331 - if you don't already have the correct weapon drawn, // draw a ranged weapon if you're far from the enemy, and you have a // ranged weapon, otherwise draw your melee weapon bool drawingWeapon = false; if ( !inMeleeRange ) { // beyond melee range if ( !owner->GetAttackFlag(COMBAT_RANGED) && _rangedPossible ) { owner->DrawWeapon(COMBAT_RANGED); drawingWeapon = true; } else // no ranged weapon { owner->DrawWeapon(COMBAT_MELEE); drawingWeapon = true; } } else // in melee range { if ( _meleePossible && !owner->GetAttackFlag(COMBAT_MELEE) ) { owner->DrawWeapon(COMBAT_MELEE); drawingWeapon = true; } } // grayman #3331 - if this is the first weapon draw, to make sure the weapon is drawn // before starting combat, delay some before starting to chase the enemy. // The farther away the enemy is, the better the chance that you'll start chasing before your // weapon is drawn. If he's close, this gives you time to completely draw your weapon before // engaging him. The interesting distance is how far you have to travel to get w/in melee range. if ( _needInitialDrawDelay ) // True if this is the first time through, and you don't already have a raised weapon { int delay = 0; if ( drawingWeapon ) { delay = (int)(2064.0f - 20.0f*(dist2Enemy - owner->GetMeleeRange())); if ( delay < 0 ) { delay = gameLocal.random.RandomInt(2064); } _waitEndTime = gameLocal.time + delay; _combatSubState = EStateDrawingWeapon; // grayman #3563 - safety net when drawing a weapon _drawEndTime = gameLocal.time + MAX_DRAW_DURATION; } else { _combatSubState = EStateCombatAndChecks; } _needInitialDrawDelay = false; // No need to do this again } else { if ( drawingWeapon ) { _waitEndTime = gameLocal.time; _combatSubState = EStateDrawingWeapon; // grayman #3563 - safety net when drawing a weapon _drawEndTime = gameLocal.time + MAX_DRAW_DURATION; } else { _combatSubState = EStateCombatAndChecks; } } break; } case EStateDrawingWeapon: { // grayman #3355 - check wait state if ( idStr(owner->WaitState()) == "draw" ) { if ( gameLocal.time < _drawEndTime ) // grayman #3563 - check safety net { return; // wait until weapon is drawn } } if ( gameLocal.time < _waitEndTime ) { return; // wait until timer expires } // Weapon is now drawn _justDrewWeapon = true; _combatSubState = EStateCombatAndChecks; break; } case EStateCombatAndChecks: { // Need to check if a weapon that was just drawn is correct for the zone you're now in, in case // you started drawing the correct weapon for one zone, and while it was drawing, you switched // to the other zone. if ( _justDrewWeapon ) { if ( newCombatType == COMBAT_RANGED ) { // beyond melee range if ( !owner->GetAttackFlag(COMBAT_RANGED) && _rangedPossible ) { // wrong weapon raised - go back and get the correct one _justDrewWeapon = false; _combatSubState = EStateCheckWeaponState; return; } } else // in melee combat { if ( !owner->GetAttackFlag(COMBAT_MELEE) && _meleePossible ) { // wrong weapon raised - go back and get the correct one _justDrewWeapon = false; _combatSubState = EStateCheckWeaponState; return; } } } _justDrewWeapon = false; if ( _combatType == COMBAT_NONE ) // Either combat hasn't been initially set up, or you're switching weapons { if ( newCombatType == COMBAT_RANGED ) { // Set up ranged combat owner->actionSubsystem->PushTask(RangedCombatTask::CreateInstance()); owner->movementSubsystem->PushTask(ChaseEnemyRangedTask::CreateInstance()); _combatType = COMBAT_RANGED; } else { // Set up melee combat ChaseEnemyTaskPtr chaseEnemy = ChaseEnemyTask::CreateInstance(); chaseEnemy->SetEnemy(enemy); owner->movementSubsystem->PushTask(chaseEnemy); owner->actionSubsystem->PushTask(MeleeCombatTask::CreateInstance()); _combatType = COMBAT_MELEE; } // Let the AI update their weapons (make them nonsolid) owner->UpdateAttachmentContents(false); } // Check the distance to the enemy, the subsystem tasks need it. memory.canHitEnemy = owner->CanHitEntity(enemy, _combatType); // grayman #3331 - willBeAbleToHitEnemy is only relevant if canHitEnemy is FALSE if ( owner->m_bMeleePredictProximity && !memory.canHitEnemy ) { memory.willBeAbleToHitEnemy = owner->WillBeAbleToHitEntity(enemy, _combatType); } // Check whether the enemy can hit us in the near future memory.canBeHitByEnemy = owner->CanBeHitByEntity(enemy, _combatType); if ( !owner->AI_ENEMY_VISIBLE && ( ( ( _combatType == COMBAT_MELEE ) && !memory.canHitEnemy ) || ( _combatType == COMBAT_RANGED) ) ) { // The enemy is not visible, let's keep track of him for a small amount of time if (gameLocal.time - memory.lastTimeEnemySeen < MAX_BLIND_CHASE_TIME) { // Cheat a bit and take the last reachable position as "visible & reachable" owner->lastVisibleReachableEnemyPos = owner->lastReachableEnemyPos; } else if (owner->ReachedPos(owner->lastVisibleReachableEnemyPos, MOVE_TO_POSITION) || ( ( gameLocal.time - memory.lastTimeEnemySeen ) > 2 * MAX_BLIND_CHASE_TIME) ) { // BLIND_CHASE_TIME has expired, we have lost the enemy! owner->GetMind()->SwitchState(STATE_LOST_TRACK_OF_ENEMY); return; } } // Flee if you're damaged and the current melee action is finished if ( ( owner->health < _criticalHealth ) && ( owner->m_MeleeStatus.m_ActionState == MELEEACTION_READY ) ) { DM_LOG(LC_AI, LT_INFO)LOGSTRING("I'm badly hurt, I'm afraid, and am fleeing!\r"); owner->fleeingEvent = false; // grayman #3182 owner->emitFleeBarks = true; // grayman #3474 owner->GetMind()->SwitchState(STATE_FLEE); return; } _combatSubState = EStateCheckWeaponState; break; } default: break; } }
void CombatState::Init(idAI* owner) { // Init base class first State::Init(owner); // Set end time to something invalid _endTime = -1; DM_LOG(LC_AI, LT_INFO)LOGSTRING("CombatState initialised.\r"); assert(owner); // Ensure we are in the correct alert level if (!CheckAlertLevel(owner)) { return; } if (!owner->GetMind()->PerformCombatCheck()) { return; } if ( ( owner->GetMoveType() == MOVETYPE_SIT ) || ( owner->GetMoveType() == MOVETYPE_SLEEP) ) { owner->GetUp(); } // grayman #3075 - if we're kneeling, doing close inspection of // a spot, stop the animation. Otherwise, the kneeling animation gets // restarted a few moments later. idStr torsoString = "Torso_KneelDown"; idStr legsString = "Legs_KneelDown"; bool torsoKneelingAnim = (torsoString.Cmp(owner->GetAnimState(ANIMCHANNEL_TORSO)) == 0); bool legsKneelingAnim = (legsString.Cmp(owner->GetAnimState(ANIMCHANNEL_LEGS)) == 0); if ( torsoKneelingAnim || legsKneelingAnim ) { // Reset anims owner->StopAnim(ANIMCHANNEL_TORSO, 0); owner->StopAnim(ANIMCHANNEL_LEGS, 0); } // grayman #3472 - kill the repeated bark task owner->commSubsystem->ClearTasks(); // grayman #3182 // grayman #3496 // Enough time passed since last alert bark? if ( gameLocal.time >= owner->GetMemory().lastTimeAlertBark + MIN_TIME_BETWEEN_ALERT_BARKS ) { // grayman #3496 - But only bark if you haven't recently spent time in Agitated Search. if ( !owner->GetMemory().agitatedSearched ) { // The communication system plays reaction bark CommMessagePtr message; owner->commSubsystem->AddCommTask(CommunicationTaskPtr(new SingleBarkTask("snd_alert5", message))); owner->GetMemory().lastTimeAlertBark = gameLocal.time; // grayman #3496 if (cv_ai_debug_transition_barks.GetBool()) { gameLocal.Printf("%d: %s enters Combat State, barks surprised reaction 'snd_alert5'\n",gameLocal.time,owner->GetName()); } } else { if (cv_ai_debug_transition_barks.GetBool()) { gameLocal.Printf("%d: %s enters Combat State after spending time in Agitated Searching, so won't bark 'snd_alert5'\n",gameLocal.time,owner->GetName()); } } } else { if (cv_ai_debug_transition_barks.GetBool()) { gameLocal.Printf("%d: %s enters Combat State, can't bark 'snd_alert5' yet\n",gameLocal.time,owner->GetName()); } } // All remaining init code is moved into Think() and done in the EStateReaction substate, // because the things it does need to occur after the initial reaction delay. // grayman #3063 // Add a delay before you process the remainder of Init(). // The length of the delay depends on the distance to the enemy. // We have an enemy, store the enemy entity locally _enemy = owner->GetEnemy(); idActor* enemy = _enemy.GetEntity(); // grayman #3331 - clear combat state _combatType = COMBAT_NONE; // get melee possibilities _meleePossible = ( owner->GetNumMeleeWeapons() > 0 ); _rangedPossible = ( owner->GetNumRangedWeapons() > 0 ); /* grayman #3492 - should wait until after the reaction time before fleeing // grayman #3355 - flee if you have no weapons if (!_meleePossible && !_rangedPossible) { owner->fleeingEvent = false; // grayman #3356 owner->emitFleeBarks = true; // grayman #3474 owner->GetMind()->SwitchState(STATE_FLEE); return; } */ // grayman #3331 - save combat possibilities _unarmedMelee = owner->spawnArgs.GetBool("unarmed_melee","0"); _unarmedRanged = owner->spawnArgs.GetBool("unarmed_ranged","0"); _armedMelee = _meleePossible && !_unarmedMelee; _armedRanged = _rangedPossible && !_unarmedRanged; // grayman #3331 - do we need an initial delay at weapon drawing? _needInitialDrawDelay = !( owner->GetAttackFlag(COMBAT_MELEE) || owner->GetAttackFlag(COMBAT_RANGED) ); // not if we have a weapon raised idVec3 vec2Enemy = enemy->GetPhysics()->GetOrigin() - owner->GetPhysics()->GetOrigin(); //vec2Enemy.z = 0; // ignore vertical component float dist2Enemy = vec2Enemy.LengthFast(); int reactionTime = REACTION_TIME_MIN + (dist2Enemy*(REACTION_TIME_MAX - REACTION_TIME_MIN))/(cv_ai_sight_combat_cutoff.GetFloat()/s_DOOM_TO_METERS); if ( reactionTime > REACTION_TIME_MAX ) { reactionTime = REACTION_TIME_MAX; } // grayman #3331 - add a bit of variability so multiple AI spotting the enemy in the same frame aren't in sync reactionTime += gameLocal.random.RandomInt(REACTION_TIME_MAX/2); _combatSubState = EStateReaction; _reactionEndTime = gameLocal.time + reactionTime; }
void AgitatedSearchingState::Init(idAI* owner) { // Init base class first (note: we're not calling SearchingState::Init() on purpose here) State::Init(owner); DM_LOG(LC_AI, LT_INFO)LOGSTRING("AgitatedSearchingState initialised.\r"); assert(owner); // Shortcut reference Memory& memory = owner->GetMemory(); memory.leaveAlertState = false; // Ensure we are in the correct alert level if ( !CheckAlertLevel(owner) ) { return; } // grayman #3496 - note that we spent time in Agitated Search memory.agitatedSearched = true; CalculateAlertDecreaseRate(owner); if (owner->GetMoveType() == MOVETYPE_SIT || owner->GetMoveType() == MOVETYPE_SLEEP) { owner->GetUp(); } // Set up a new hiding spot search if not already assigned to one if (owner->m_searchID <= 0) { if (!StartNewHidingSpotSearch(owner)) // grayman #3857 - AI gets his assignment { // grayman - this section can't run because it causes // the stealth score to rise dramatically during player sightings //owner->SetAlertLevel(owner->thresh_3 - 0.1); // failed to create a search, so drop down to Suspicious mode //owner->GetMind()->EndState(); //return; } } // kill the repeated and single bark tasks owner->commSubsystem->ClearTasks(); // grayman #3182 memory.repeatedBarkState = ERBS_NULL; // grayman #3857 if (owner->AlertIndexIncreased()) { // grayman #3496 - enough time passed since last alert bark? // grayman #3857 - enough time passed since last visual stim bark? if ( ( gameLocal.time >= memory.lastTimeAlertBark + MIN_TIME_BETWEEN_ALERT_BARKS ) && ( gameLocal.time >= memory.lastTimeVisualStimBark + MIN_TIME_BETWEEN_ALERT_BARKS ) ) { idStr soundName = ""; if ( ( memory.alertedDueToCommunication == false ) && ( ( memory.alertType == EAlertTypeSuspicious ) || ( memory.alertType == EAlertTypeEnemy ) || ( memory.alertType == EAlertTypeFailedKO ) ) ) { if (owner->HasSeenEvidence()) { soundName = "snd_alert4"; } else { soundName = "snd_alert4NoEvidence"; } CommMessagePtr message = CommMessagePtr(new CommMessage( CommMessage::DetectedSomethingSuspicious_CommType, owner, NULL, // from this AI to anyone NULL, memory.alertPos, memory.currentSearchEventID // grayman #3438 )); owner->commSubsystem->AddCommTask(CommunicationTaskPtr(new SingleBarkTask(soundName,message))); memory.lastTimeAlertBark = gameLocal.time; // grayman #3496 if (cv_ai_debug_transition_barks.GetBool()) { gameLocal.Printf("%d: %s rises to Agitated Searching state, barks '%s'\n",gameLocal.time,owner->GetName(),soundName.c_str()); } } else if ( memory.respondingToSomethingSuspiciousMsg ) // grayman #3857 { soundName = "snd_helpSearch"; // Allocate a SingleBarkTask, set the sound and enqueue it owner->commSubsystem->AddCommTask(CommunicationTaskPtr(new SingleBarkTask(soundName))); memory.lastTimeAlertBark = gameLocal.time; // grayman #3496 if (cv_ai_debug_transition_barks.GetBool()) { gameLocal.Printf("%d: %s rises to Searching state, barks '%s'\n",gameLocal.time,owner->GetName(),soundName.c_str()); } } } else { if (cv_ai_debug_transition_barks.GetBool()) { gameLocal.Printf("%d: %s rises to Agitated Searching state, can't bark 'snd_alert4{NoEvidence}' yet\n",gameLocal.time,owner->GetName()); } } } owner->commSubsystem->AddSilence(5000 + gameLocal.random.RandomInt(3000)); // grayman #3424 SetRepeatedBark(owner); // grayman #3857 DrawWeapon(owner); // grayman #3507 // Let the AI update their weapons (make them solid) owner->UpdateAttachmentContents(true); // grayman #3857 - no idle search anims in this state owner->actionSubsystem->ClearTasks(); }
void AgitatedSearchingState::SetRepeatedBark(idAI* owner) { Memory& memory = owner->GetMemory(); ERepeatedBarkState newRepeatedBarkState = ERBS_NULL; Search *search = gameLocal.m_searchManager->GetSearch(owner->m_searchID); if (search) // should always be non-NULL { Assignment *assignment = gameLocal.m_searchManager->GetAssignment(search,owner); int searcherCount = search->_searcherCount; idStr soundName; if (owner->HasSeenEvidence()) { if (assignment && (assignment->_searcherRole == E_ROLE_SEARCHER)) { if (searcherCount > 1) { newRepeatedBarkState = ERBS_SEARCHER_MULTIPLE_EVIDENCE; } else { newRepeatedBarkState = ERBS_SEARCHER_SINGLE_EVIDENCE; } } else { newRepeatedBarkState = ERBS_GUARD_OBSERVER; } } else // has not seen evidence { if (assignment && (assignment->_searcherRole == E_ROLE_SEARCHER)) { if (searcherCount > 1) { newRepeatedBarkState = ERBS_SEARCHER_MULTIPLE_NO_EVIDENCE; } else { newRepeatedBarkState = ERBS_SEARCHER_SINGLE_NO_EVIDENCE; } } else { newRepeatedBarkState = ERBS_GUARD_OBSERVER; } } } if (memory.repeatedBarkState != newRepeatedBarkState) { memory.repeatedBarkState = newRepeatedBarkState; bool sendSuspiciousMessage = true; // whether to send a 'something is suspicious' message or not owner->commSubsystem->ClearTasks(); // kill any previous repeated bark task; we'll be starting a new one idStr soundName; switch (newRepeatedBarkState) { case ERBS_SEARCHER_SINGLE_NO_EVIDENCE: soundName = "snd_state4SeenNoEvidence"; // what to say when you're by yourself and you've seen no evidence break; case ERBS_SEARCHER_MULTIPLE_NO_EVIDENCE: soundName = "snd_state4SeenNoEvidence_c"; // what to say when friends are helping you search and you've seen no evidence break; case ERBS_SEARCHER_SINGLE_EVIDENCE: soundName = "snd_state4SeenEvidence"; // what to say when you're by yourself and you've seen evidence break; case ERBS_SEARCHER_MULTIPLE_EVIDENCE: soundName = "snd_state4SeenEvidence_c"; // what to say when friends are helping you search and you've seen evidence break; default: case ERBS_NULL: case ERBS_GUARD_OBSERVER: soundName = "snd_state3"; // guards and observers say this, regardless of whether they've seen evidence or not // grayman #3857 - "snd_state3" repeated barks are not intended to // alert nearby friends. Just send along a blank message. sendSuspiciousMessage = false; break; } CommMessagePtr message; if (sendSuspiciousMessage) { message = CommMessagePtr(new CommMessage( CommMessage::DetectedSomethingSuspicious_CommType, owner, NULL, // from this AI to anyone NULL, memory.alertPos, memory.currentSearchEventID // grayman #3438 )); } int minTime = SEC2MS(owner->spawnArgs.GetFloat("searchbark_delay_min", "10")); int maxTime = SEC2MS(owner->spawnArgs.GetFloat("searchbark_delay_max", "15")); owner->commSubsystem->AddCommTask(CommunicationTaskPtr(new RepeatedBarkTask(soundName, minTime, maxTime, message))); } }
void ObservantState::Init(idAI* owner) { // Init base class first State::Init(owner); DM_LOG(LC_AI, LT_INFO)LOGSTRING("ObservantState initialised.\r"); assert(owner); // Ensure we are in the correct alert level if (!CheckAlertLevel(owner)) { return; } // Shortcut reference Memory& memory = owner->GetMemory(); float alertTime = owner->atime1 + owner->atime1_fuzzyness * (gameLocal.random.RandomFloat() - 0.5); _alertLevelDecreaseRate = (owner->thresh_2 - owner->thresh_1) / alertTime; // grayman #2866 - zero alert decrease rate if handling a door or elevator memory.savedAlertLevelDecreaseRate = _alertLevelDecreaseRate; // save for restoration later if ((owner->m_HandlingDoor) || (owner->m_HandlingElevator)) { _alertLevelDecreaseRate = 0; } // Stop playing idle animation owner->actionSubsystem->ClearTasks(); // grayman #3438 - kill the repeated bark task owner->commSubsystem->ClearTasks(); owner->searchSubsystem->ClearTasks(); // grayman #3857 memory.currentSearchEventID = -1; // grayman #3472 - Play bark if alert level is ascending // grayman #3487 - But not if asleep if (owner->GetMoveType() != MOVETYPE_SLEEP) { if (owner->AlertIndexIncreased()) { // grayman #3496 - Enough time passed since last alert bark? // grayman #3857 - Enough time passed since last visual stim bark? if ( ( gameLocal.time >= memory.lastTimeAlertBark + MIN_TIME_BETWEEN_ALERT_BARKS ) && ( gameLocal.time >= memory.lastTimeVisualStimBark + MIN_TIME_BETWEEN_ALERT_BARKS ) ) { if ( !memory.alertedDueToCommunication && ( memory.alertClass != EAlertVisual_4 ) ) // grayman #2920, grayman #3498 { // barking idStr bark; if ((memory.alertClass == EAlertVisual_1) || (memory.alertClass == EAlertVisual_2) ) //(memory.alertClass == EAlertVisual_3) ) // grayman #2603, #3424, grayman #3472 - no longer needed { bark = "snd_alert1s"; } else if (memory.alertClass == EAlertAudio) { bark = "snd_alert1h"; } else { bark = "snd_alert1"; } owner->commSubsystem->AddCommTask(CommunicationTaskPtr(new SingleBarkTask(bark))); memory.lastTimeAlertBark = gameLocal.time; // grayman #3496 if (cv_ai_debug_transition_barks.GetBool()) { gameLocal.Printf("%d: %s rises to Observant state, barks '%s'\n",gameLocal.time,owner->GetName(),bark.c_str()); } } else { memory.alertedDueToCommunication = false; // reset } } else { if (cv_ai_debug_transition_barks.GetBool()) { gameLocal.Printf("%d: %s rises to Observant state, can't bark 'snd_alert1s' yet\n",gameLocal.time,owner->GetName()); } } } } // Let the AI update their weapons (make them nonsolid) owner->UpdateAttachmentContents(false); }
void UnreachableTargetState::Init(idAI* owner) { // Init base class first State::Init(owner); DM_LOG(LC_AI, LT_INFO)LOGSTRING("UnreachableTargetState initialised.\r"); assert(owner); // Shortcut reference Memory& memory = owner->GetMemory(); idActor* enemy = owner->GetEnemy(); if (!enemy) { owner->GetMind()->SwitchState(STATE_LOST_TRACK_OF_ENEMY); return; } _enemy = enemy; // This checks if taking cover is possible and enabled for this AI _takingCoverPossible = false; if (owner->spawnArgs.GetBool("taking_cover_enabled","0")) { aasGoal_t hideGoal; // grayman #3280 - enemies look with their eyes, not their feet _takingCoverPossible = owner->LookForCover(hideGoal, enemy, enemy->GetEyePosition()); if (_takingCoverPossible) { // We should not go into TakeCoverState if we are already at a suitable position if (hideGoal.origin == owner->GetPhysics()->GetOrigin() ) { _takingCoverPossible = false; } DM_LOG(LC_AI, LT_INFO)LOGSTRING("Taking Cover Possible: %d \r" , _takingCoverPossible); } } _takeCoverTime = -1; // Fill the subsystems with their tasks // Create the message CommMessagePtr message(new CommMessage( CommMessage::RequestForMissileHelp_CommType, owner, NULL, // from this AI to anyone enemy, memory.lastEnemyPos, 0 )); // grayman #3343 - accommodate different barks for human and non-human enemies idStr bark = ""; idStr enemyAiUse = enemy->spawnArgs.GetString("AIUse"); if ( ( enemyAiUse == AIUSE_MONSTER ) || ( enemyAiUse == AIUSE_UNDEAD ) ) { bark = "snd_cantReachTargetMonster"; } else { bark = "snd_cantReachTarget"; } owner->commSubsystem->AddCommTask(CommunicationTaskPtr(new SingleBarkTask(bark, message))); if (cv_ai_debug_transition_barks.GetBool()) { gameLocal.Printf("%d: %s can't reach the target, barks '%s'\n",gameLocal.time,owner->GetName(),bark.c_str()); } // The sensory system does nothing so far owner->senseSubsystem->ClearTasks(); owner->StopMove(MOVE_STATUS_DONE); owner->movementSubsystem->ClearTasks(); memory.stopRelight = true; // grayman #2603 - abort a relight in progress memory.stopExaminingRope = true; // grayman #2872 - stop examining a rope memory.stopReactingToHit = true; // grayman #2816 owner->actionSubsystem->ClearTasks(); _moveRequired = false; if (owner->spawnArgs.GetBool("outofreach_projectile_enabled", "0")) { // Check the distance between AI and the player, if it is too large try to move closer // Start throwing objects if we are close enough idVec3 enemyDirection = enemy->GetPhysics()->GetOrigin() - owner->GetPhysics()->GetOrigin(); float dist = (enemyDirection).LengthFast(); //TODO: make not hardcoded if (dist > 300) { _moveRequired = true; idVec3 throwPos = enemy->GetPhysics()->GetOrigin() - enemyDirection / dist * 300; // TODO: Trace to get floor position throwPos.z = owner->GetPhysics()->GetOrigin().z; owner->movementSubsystem->PushTask(TaskPtr(new MoveToPositionTask(throwPos))); owner->AI_MOVE_DONE = false; } else { // greebo: Sheathe weapon before starting to throw // FIXME: put weapon to left hand? owner->SheathWeapon(); owner->FaceEnemy(); owner->actionSubsystem->PushTask(ThrowObjectTask::CreateInstance()); // Wait at least 3 sec after starting to throw before taking cover // TODO: make not hardcoded, some randomness? _takeCoverTime = gameLocal.time + 3000; } DM_LOG(LC_AI, LT_INFO)LOGSTRING("move required: %d \r" , _moveRequired); } else { owner->movementSubsystem->PushTask( TaskPtr(new MoveToPositionTask(owner->lastVisibleReachableEnemyPos)) ); _takeCoverTime = gameLocal.time + 3000; } _reachEnemyCheck = 0; }
// Gets called each time the mind is thinking void HitByMoveableState::Think(idAI* owner) { // grayman #3424 - don't process if dead or unconscious if ( owner->AI_DEAD || owner->AI_KNOCKEDOUT ) { Wrapup(owner); return; } // check if something happened to abort the state if (owner->GetMemory().stopReactingToHit) { Wrapup(owner); return; } owner->PerformVisualScan(); // Let the AI check its senses if (owner->AI_AlertLevel >= owner->thresh_5) // finished if alert level is too high { Wrapup(owner); return; } idEntity* tactEnt = owner->GetMemory().hitByThisMoveable.GetEntity(); if ( tactEnt == NULL ) { Wrapup(owner); return; } switch (_hitByMoveableState) { case EStateSittingSleeping: if (gameLocal.time >= _waitEndTime) { if (owner->AI_MOVE_DONE && (owner->GetMoveType() != MOVETYPE_GET_UP)) // standing yet? { _hitByMoveableState = EStateTurnToward; } } break; case EStateStarting: if (gameLocal.time >= _waitEndTime) { _hitByMoveableState = EStateTurnToward; } break; case EStateTurnToward: // Turn toward the object that hit you. { // if _lookAtDuration is 0, skip turning toward and looking at the object if ( _lookAtDuration == 0 ) { _hitByMoveableState = EStateTurnBack; } else { owner->TurnToward(tactEnt->GetPhysics()->GetOrigin()); _hitByMoveableState = EStateLookAt; _waitEndTime = gameLocal.time + 1000; } } break; case EStateLookAt: // look at the entity if (gameLocal.time >= _waitEndTime) { float duration = _lookAtDuration + (_lookAtDuration/5)*(gameLocal.random.RandomFloat() - 0.5f ); owner->Event_LookAtEntity( tactEnt, duration ); _waitEndTime = gameLocal.time + duration*1000; _hitByMoveableState = EStateTurnBack; } break; case EStateTurnBack: // turn toward the direction the entity came from if (gameLocal.time >= _waitEndTime) { owner->TurnToward(_pos); _waitEndTime = gameLocal.time + 1000; _hitByMoveableState = EStateLookBack; } break; case EStateLookBack: // look in the direction the entity came from if (gameLocal.time >= _waitEndTime) { if ( _lookBackDuration > 0 ) { float duration = _lookBackDuration + (_lookBackDuration/5)*(gameLocal.random.RandomFloat() - 0.5f ); owner->Event_LookAtPosition( _pos, duration ); _waitEndTime = gameLocal.time + duration*1000; } _hitByMoveableState = EStateFinal; } break; case EStateFinal: if (gameLocal.time >= _waitEndTime) { // If we make it to here, we haven't spotted // an enemy, otherwise our alert level would // have been high enough to abort this state // at the beginning of Think(). // If we see a friend or a neutral, we assume // this was a harmless impact, and we can return // to what we were doing. idVec3 ownerOrg = owner->GetPhysics()->GetOrigin(); idBounds bounds( ownerOrg - idVec3( HIT_FIND_THROWER_HORZ, HIT_FIND_THROWER_HORZ, HIT_FIND_THROWER_VERT ), ownerOrg + idVec3( HIT_FIND_THROWER_HORZ, HIT_FIND_THROWER_HORZ, HIT_FIND_THROWER_VERT ) ); idEntity* ents[MAX_GENTITIES]; int num = gameLocal.clip.EntitiesTouchingBounds( bounds, CONTENTS_BODY, ents, MAX_GENTITIES ); for ( int i = 0 ; i < num ; i++ ) { idEntity *candidate = ents[i]; if ( candidate == NULL ) // just in case { continue; } if ( candidate == owner ) // skip myself { continue; } if ( !candidate->IsType(idActor::Type) ) // skip non-Actors { continue; } idActor *candidateActor = static_cast<idActor *>(candidate); if ( candidateActor->GetPhysics()->GetMass() <= 5.0 ) // skip actors with small mass (rats, spiders) { continue; } if ( candidateActor->IsKnockedOut() || ( candidateActor->health < 0 ) ) { continue; // skip if knocked out or dead } if ( candidateActor->IsType(idAI::Type) ) { if ( static_cast<idAI*>(candidateActor)->GetMoveType() == MOVETYPE_SLEEP ) { continue; // skip if asleep } } if ( candidateActor->fl.notarget ) { continue; // skip if NOTARGET is set } if ( owner->IsFriend(candidateActor) || owner->IsNeutral(candidateActor) ) { // Don't admonish the alleged thrower if tactEnt came in from above (dropped) if ( _pos.z <= owner->GetEyePosition().z ) { // make sure there's LOS (no walls in between) if ( owner->CanSeeExt( candidateActor, false, false ) ) // don't use FOV or lighting, to increase chance of success { // look at your friend/neutral if they're in your FOV int delay = 0; // bark delay if ( owner->CanSeeExt( candidateActor, true, false) ) // use FOV this time, but ignore lighting { owner->Event_LookAtPosition(candidateActor->GetEyePosition(),2.0f); delay = 1000; // give head time to move before barking } // bark an admonishment whether you're facing them or not CommMessagePtr message; // no message, but the argument is needed so the start delay can be included owner->commSubsystem->AddCommTask(CommunicationTaskPtr(new SingleBarkTask("snd_admonish_friend",message,delay))); if (cv_ai_debug_transition_barks.GetBool()) { gameLocal.Printf("%d: %s hit by moveable, barks 'snd_admonish_friend'\n",gameLocal.time,owner->GetName()); } Wrapup(owner); return; } } } } // No one about, so let's cheat. If a friend or neutral threw // the object, we return to what we were doing. idActor* responsible = _responsibleActor.GetEntity(); if ( ( responsible == NULL ) || owner->IsFriend(responsible) || owner->IsNeutral(responsible) ) { Wrapup(owner); return; } // No one about. If an enemy threw the object, start searching. if ( owner->IsEnemy(responsible) ) { Memory& memory = owner->GetMemory(); memory.alertType = EAlertTypeSuspicious; memory.alertClass = EAlertTactile; memory.alertPos = ownerOrg; memory.alertRadius = TACTILE_ALERT_RADIUS; memory.alertSearchVolume = TACTILE_SEARCH_VOLUME; memory.alertSearchExclusionVolume.Zero(); memory.visualAlert = false; // If we got this far, we give the alert // Set the alert amount to the according tactile alert value float amount = cv_ai_tactalert.GetFloat(); // NOTE: Latest tactile alert always overrides other alerts owner->m_TactAlertEnt = tactEnt; owner->m_AlertedByActor = responsible; amount *= owner->GetAcuity("tact"); // grayman #3009 - pass the alert position so the AI can look in the direction of who's responsible owner->PreAlertAI("tact", amount, responsible->GetEyePosition()); // grayman #3356 // Set last visual contact location to this location as that is used in case // the target gets away. owner->m_LastSight = ownerOrg; // If no enemy set so far, set the last visible enemy position. if ( owner->GetEnemy() == NULL ) { owner->lastVisibleEnemyPos = ownerOrg; } owner->AI_TACTALERT = true; memory.mandatory = true; // grayman #3331 } Wrapup(owner); return; } break; default: break; } }