From b386c68142ab18b4cdbe484623957ea8e2804ee0 Mon Sep 17 00:00:00 2001 From: grayman Date: Mon, 25 Mar 2013 17:09:21 +0000 Subject: [PATCH] Fix for issue #3140. git-svn-id: https://svn.thedarkmod.com/svn/darkmod_src/trunk@5722 49c82d7f-2e2a-0410-a16f-ae8f201b507f --- game/SndProp.cpp | 31 +++++++--- game/ai/AI.cpp | 35 ++++++++++-- game/ai/Memory.cpp | 4 ++ game/ai/Memory.h | 16 ++++++ game/ai/States/CombatState.cpp | 15 ++--- game/ai/States/FleeState.cpp | 11 +++- game/ai/States/PainState.cpp | 87 ++++++++++++++++++++++------- game/ai/States/State.cpp | 83 +++++++++++++++++++++------ game/ai/States/State.h | 2 +- game/ai/States/StayInCoverState.cpp | 2 + game/ai/States/StayInCoverState.h | 2 +- 11 files changed, 223 insertions(+), 65 deletions(-) diff --git a/game/SndProp.cpp b/game/SndProp.cpp index e5c6edc73..5aa1cf70d 100644 --- a/game/SndProp.cpp +++ b/game/SndProp.cpp @@ -544,8 +544,8 @@ void CsndProp::Propagate if ( cv_spr_debug.GetBool() ) { - gameLocal.Printf("Found %d ents with valid type for propagation\n", validTypeEnts.Num() ); - DM_LOG(LC_SOUND, LT_DEBUG)LOGSTRING("Found %d ents with valid type for propagation\r", validTypeEnts.Num() ); + gameLocal.Printf("Found %d valid AIs to propagate to\n", validTypeEnts.Num() ); + DM_LOG(LC_SOUND, LT_DEBUG)LOGSTRING("Found %d valid AIs to propagate to\r", validTypeEnts.Num() ); timer_Prop.Stop(); // grayman - only time things if the debug cvar is set DM_LOG(LC_SOUND, LT_INFO)LOGSTRING("Timer: Finished finding all AI entities, comptime = %lf [ms]\r", timer_Prop.Milliseconds() ); timer_Prop.Start(); @@ -587,8 +587,17 @@ void CsndProp::Propagate DM_LOG(LC_SOUND, LT_DEBUG)LOGSTRING("Sound was propagated from inanimate object: Alerts all teams\r" ); } } + else if ( testAI == maker ) // grayman #3140 - makers don't ping themselves + { + // do nothing, bValidTeam is false at this point + } else { + // grayman - tmask holds flags that describe which team + // relationships should receive the propagated sound. + // When one or more of the flags matches the relationship + // flags between the maker and the listener (testAI), then + // the listener should respond to the sound. compMask.m_bits.same = ( testAI->team == mteam ); compMask.m_bits.friendly = testAI->IsFriend(maker); compMask.m_bits.neutral = testAI->IsNeutral(maker); @@ -607,8 +616,7 @@ void CsndProp::Propagate // TODO : Add another else if for the case of Listeners - // don't alert the AI that caused the sound - if ( bValidTeam && ( testAI != maker ) ) + if ( bValidTeam /* && ( testAI != maker ) */ ) // grayman #3140 - maker test moved up { if ( cv_spr_debug.GetBool() ) { @@ -620,7 +628,7 @@ void CsndProp::Propagate if ( cv_spr_debug.GetBool() ) { - DM_LOG(LC_SOUND, LT_DEBUG)LOGSTRING("AI %s does not have a valid team for propagation\r", testAI->name.c_str() ); + DM_LOG(LC_SOUND, LT_DEBUG)LOGSTRING("AI %s either doesn't have a valid team for propagation, or is the maker\r", testAI->name.c_str() ); } } @@ -650,6 +658,13 @@ void CsndProp::Propagate // Don't bother propagation if no one is in range if ( validEnts.Num() == 0 ) { + // grayman #3140 - We're done propagating, clear the message list of the issuing AI, if appropriate + if ( maker->IsType(idAI::Type) ) + { + idAI* makerAI = static_cast(maker); + makerAI->ClearMessages(); + } + return; } @@ -750,7 +765,7 @@ void CsndProp::SetupParms( const idDict *parms, SSprParms *propParms, USprFlags DM_LOG(LC_SOUND,LT_DEBUG)LOGSTRING("Parsing team alert and propagation flags from propagated_sounds.def\r"); - // note: by default, if the key is not found, GetBool returns false + // note: by default, if the key is not found, GetBool returns 'false' for the first 3, and 'true' for prop_to_enemy tempflags.m_bits.same = parms->GetBool("prop_to_same"); tempflags.m_bits.friendly = parms->GetBool("prop_to_friend"); tempflags.m_bits.neutral = parms->GetBool("prop_to_neutral"); @@ -1207,10 +1222,10 @@ void CsndProp::ProcessAI(idAI* ai, idVec3 origin, SSprParms *propParms) // check AI hearing, get environmental noise, etc if ( cv_spr_debug.GetBool() ) { - gameLocal.Printf("Propagated sound %s to AI %s, from origin %s : Propagated volume %f, Apparent origin of sound: %s \r", + gameLocal.Printf("Propagated sound %s to AI %s, from origin %s : Propagated volume %f, Apparent origin of sound: %s\n", propParms->name.c_str(), ai->name.c_str(), origin.ToString(), propParms->propVol, propParms->direction.ToString() ); - DM_LOG(LC_SOUND, LT_DEBUG)LOGSTRING("Propagated sound %s to AI %s, from origin %s : Propagated volume %f, Apparent origin of sound: %s \r", + DM_LOG(LC_SOUND, LT_DEBUG)LOGSTRING("Propagated sound %s to AI %s, from origin %s : Propagated volume %f, Apparent origin of sound: %s\r", propParms->name.c_str(), ai->name.c_str(), origin.ToString(), propParms->propVol, propParms->direction.ToString() ); } diff --git a/game/ai/AI.cpp b/game/ai/AI.cpp index 4a140b32c..4a92ca576 100644 --- a/game/ai/AI.cpp +++ b/game/ai/AI.cpp @@ -6152,10 +6152,36 @@ bool idAI::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVe ChangeEntityRelation(attacker, -10); // Switch to pain state if idle - if ( ( AI_AlertIndex == ai::ERelaxed ) && + if ( /*( AI_AlertIndex == ai::ERelaxed ) && */ // grayman #3140 - go to PainState at any alert level ( damage > 0 ) && ( ( damageDef == NULL ) || !damageDef->GetBool("no_pain_anim", "0"))) { + // grayman #3140 - note what caused the damage, in case PainState needs to do something special. + // Start with the basic causes (arrow, melee, moveable) and expand to the others as needed. + ai::Memory& memory = GetMemory(); + memory.causeOfPain = ai::EPC_None; + if ( inflictor ) + { + if ( inflictor->IsType(idProjectile::Type) ) + { + memory.causeOfPain = ai::EPC_Projectile; + } + else if ( inflictor->IsType(CMeleeWeapon::Type) ) + { + memory.causeOfPain = ai::EPC_Melee; + } + else if ( inflictor->IsType(idMoveable::Type) ) + { + memory.causeOfPain = ai::EPC_Moveable; + } + } + else + { + if ( damageDef->GetBool( "no_air" ) ) + { + memory.causeOfPain = ai::EPC_Drown; + } + } GetMind()->PushState(ai::StatePtr(new ai::PainState)); } } @@ -6406,7 +6432,7 @@ void idAI::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, return; } - if (inflictor != NULL && inflictor->IsType(idProjectile::Type)) + if ( ( inflictor != NULL ) && inflictor->IsType(idProjectile::Type)) { int damageTaken = preHitHealth - health; @@ -8969,7 +8995,7 @@ void idAI::HearSound(SSprParms *propParms, float noise, const idVec3& origin) // Retrieve the messages from the other AI, if there are any if (propParms->makerAI != NULL) { - for (int i = 0; i < propParms->makerAI->m_Messages.Num(); i++) + for ( int i = 0 ; i < propParms->makerAI->m_Messages.Num() ; i++ ) { mind->GetState()->OnAICommMessage(*propParms->makerAI->m_Messages[i], psychLoud); } @@ -12136,7 +12162,8 @@ bool idAI::CanGreet() // grayman #3338 if ( ( greetingState == ECannotGreet ) || // can never greet ( greetingState == ECannotGreetYet ) || // not allowed to greet yet ( AI_AlertIndex >= ai::EObservant) || // too alert - ( GetAttackFlag(COMBAT_MELEE) && !spawnArgs.GetBool("unarmed_melee","0") ) || // visible melee weapon drawn + ( mind->GetState()->GetName() == "Flee" ) || // grayman #3140 - no greeting if fleeing + ( GetAttackFlag(COMBAT_MELEE) && !spawnArgs.GetBool("unarmed_melee","0") ) || // visible melee weapon drawn ( GetAttackFlag(COMBAT_RANGED) && !spawnArgs.GetBool("unarmed_ranged","0") ) ) // visible ranged weapon drawn { return false; diff --git a/game/ai/Memory.cpp b/game/ai/Memory.cpp index 0a684a3d9..05f7da85f 100644 --- a/game/ai/Memory.cpp +++ b/game/ai/Memory.cpp @@ -147,6 +147,7 @@ void Memory::Save(idSaveGame* savefile) const savefile->WriteBool(stopHandlingElevator); // grayman #2816 savefile->WriteInt(nextTime2GenRandomSpot); // grayman #2422 savefile->WriteInt(static_cast(alertClass)); + savefile->WriteInt(static_cast(causeOfPain)); // grayman #3140 savefile->WriteInt(static_cast(alertType)); savefile->WriteFloat(alertRadius); savefile->WriteBool(stimulusLocationItselfShouldBeSearched); @@ -264,6 +265,9 @@ void Memory::Restore(idRestoreGame* savefile) savefile->ReadInt(temp); alertClass = static_cast(temp); + savefile->ReadInt(temp); + causeOfPain = static_cast(temp); // grayman #3140 + savefile->ReadInt(temp); alertType = static_cast(temp); diff --git a/game/ai/Memory.h b/game/ai/Memory.h index 7d44af5f1..26e50b2b5 100644 --- a/game/ai/Memory.h +++ b/game/ai/Memory.h @@ -145,6 +145,19 @@ enum EAlertState EAlertStateNum }; +// grayman #3140 - what caused my pain? +enum EPainCause +{ + EPC_None = 0, + EPC_Projectile, + EPC_Melee, + EPC_Drown, + EPC_KO, + EPC_Fall, + EPC_Moveable, + EPC_Num +}; + const char* const AlertStateNames[EAlertStateNum] = { "Relaxed", @@ -264,6 +277,9 @@ class Memory idVec3 posMissingItem; idVec3 posEvidenceIntruders; + // grayman #3140 - cause of pain + EPainCause causeOfPain; + // grayman #3331 - force a hiding spot search (don't rely just on the alert index changing) bool mandatory; diff --git a/game/ai/States/CombatState.cpp b/game/ai/States/CombatState.cpp index d7980fe17..c8e2abd46 100644 --- a/game/ai/States/CombatState.cpp +++ b/game/ai/States/CombatState.cpp @@ -340,18 +340,11 @@ void CombatState::Think(idAI* owner) _criticalHealth = owner->spawnArgs.GetInt("health_critical", "0"); - // greebo: Check for weapons and flee if we are unarmed. - if (!_meleePossible && !_rangedPossible) + // 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 { - DM_LOG(LC_AI, LT_INFO)LOGSTRING("I'm unarmed, I'm afraid!\r"); - owner->GetMind()->SwitchState(STATE_FLEE); - return; - } - - // greebo: Check for civilian AI, which will always flee in face of a combat (this is a temporary query) - if (owner->spawnArgs.GetBool("is_civilian", "0")) - { - DM_LOG(LC_AI, LT_INFO)LOGSTRING("I'm a civilian. I'm afraid.\r"); owner->GetMind()->SwitchState(STATE_FLEE); return; } diff --git a/game/ai/States/FleeState.cpp b/game/ai/States/FleeState.cpp index 3e6380cc7..559f52589 100644 --- a/game/ai/States/FleeState.cpp +++ b/game/ai/States/FleeState.cpp @@ -93,9 +93,14 @@ void FleeState::Init(idAI* owner) singleBark = "snd_to_flee_event"; } - owner->commSubsystem->AddCommTask( - CommunicationTaskPtr(new SingleBarkTask(singleBark,message)) - ); + // grayman #3140 - if hit by an arrow, issue a different bark + if ( memory.causeOfPain == EPC_Projectile ) + { + singleBark = "snd_taking_fire"; + memory.causeOfPain = EPC_None; + } + + owner->commSubsystem->AddCommTask(CommunicationTaskPtr(new SingleBarkTask(singleBark,message))); owner->commSubsystem->AddSilence(3000); diff --git a/game/ai/States/PainState.cpp b/game/ai/States/PainState.cpp index 50435b952..569747b60 100644 --- a/game/ai/States/PainState.cpp +++ b/game/ai/States/PainState.cpp @@ -58,19 +58,24 @@ void PainState::Init(idAI* owner) // Set end time _stateEndTime = gameLocal.time + 5000; - 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 - )); - - owner->commSubsystem->AddCommTask( - CommunicationTaskPtr(new SingleBarkTask("snd_pain_large", message)) - ); + // 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 + )); + + owner->commSubsystem->AddCommTask( + CommunicationTaskPtr(new SingleBarkTask("snd_pain_large", message)) + ); + } } // Gets called each time the mind is thinking @@ -79,18 +84,58 @@ void PainState::Think(idAI* owner) if ( ( gameLocal.time >= _stateEndTime ) || ( idStr(owner->WaitState(ANIMCHANNEL_TORSO)) != "pain" ) ) { - // grayman #3331 - civilians and unarmed AI should flee - if ( ( ( owner->GetNumMeleeWeapons() == 0 ) && ( owner->GetNumRangedWeapons() == 0 ) ) || - owner->spawnArgs.GetBool("is_civilian", "0") ) + 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 ) { - owner->fleeingEvent = true; // I'm fleeing the scene, not fleeing an enemy - owner->GetMind()->SwitchState(STATE_FLEE); + // 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.timeEnemySeen = gameLocal.time; + 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() + )); + + owner->commSubsystem->AddCommTask(CommunicationTaskPtr(new SingleBarkTask("snd_taking_fire", message))); } - else + + if ( willFlee ) // grayman #3331 - civilians and unarmed AI should flee { - // End this state - owner->GetMind()->EndState(); + owner->fleeingEvent = true; // I'm fleeing the scene, not fleeing an enemy + owner->GetMind()->SwitchState(STATE_FLEE); + return; } + + // End this state + owner->GetMind()->EndState(); } } diff --git a/game/ai/States/State.cpp b/game/ai/States/State.cpp index 9c142e1b7..f6f63098a 100644 --- a/game/ai/States/State.cpp +++ b/game/ai/States/State.cpp @@ -247,7 +247,9 @@ void State::OnTactileAlert(idEntity* tactEnt) // If this is a projectile, fire the corresponding event if (tactEnt->IsType(idProjectile::Type)) { - OnProjectileHit(static_cast(tactEnt)); + // grayman #3140 - now handled by the path through + // idProjectile::Collide()->idAI::Damage() + //OnProjectileHit(static_cast(tactEnt)); } else { @@ -304,6 +306,7 @@ void State::OnTactileAlert(idEntity* tactEnt) } } +/* grayman #3140 - no longer used void State::OnProjectileHit(idProjectile* projectile) { idAI* owner = _owner.GetEntity(); @@ -337,6 +340,7 @@ void State::OnProjectileHit(idProjectile* projectile) memory.mandatory = true; // grayman #3331 } } +*/ void State::OnAudioAlert() { @@ -1574,7 +1578,13 @@ void State::OnActorEncounter(idEntity* stimSource, idAI* owner) dir.z = 0; float distSqr = dir.LengthSqr(); - if ( distSqr <= Square(REMARK_DISTANCE) ) + float remarkLimit = REMARK_DISTANCE; + if ( owner->GetMind()->GetState()->GetName() == "Flee" ) // grayman #3140 - increase remark limit if fleeing + { + remarkLimit *= 3; + } + + if ( distSqr <= Square(remarkLimit) ) { // Issue a communication stim to the friend we spotted. // We can issue warnings, greetings, etc... @@ -1589,7 +1599,6 @@ void State::OnActorEncounter(idEntity* stimSource, idAI* owner) { if ( InsideWarningVolume( memory.posEnemySeen, otherOrigin, WARN_DIST_ENEMY_SEEN ) ) // grayman #2903 { - gameLocal.Printf("%d: %s sees %s, and warns that enemies have been seen.\n",gameLocal.time,owner->name.c_str(),otherAI->name.c_str()); message = CommMessagePtr(new CommMessage( CommMessage::ConveyWarning_EnemiesHaveBeenSeen_CommType, owner, other, // from this AI to the other @@ -2506,16 +2515,32 @@ void State::OnProjectileHit(idProjectile* projectile, idEntity* attacker, int da return; } + bool isAfraid = ( ( ( owner->GetNumMeleeWeapons() == 0 ) && ( owner->GetNumRangedWeapons() == 0 ) ) || + owner->spawnArgs.GetBool("is_civilian", "0") ); + // grayman #3331 - If you're a civilian, or you're unarmed, flee! // But only if no damage was done. When damaged, the flee is handled // by PainState, because we have to wait for the pain animation // to finish. if ( damageTaken == 0 ) { - if ( ( ( owner->GetNumMeleeWeapons() == 0 ) && ( owner->GetNumRangedWeapons() == 0 ) ) || - owner->spawnArgs.GetBool("is_civilian", "0") ) + if ( isAfraid ) { - owner->fleeingEvent = true; // I'm fleeing the scene of the murder, not fleeing an enemy + // 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::SearchOrder_CommType, + owner, NULL, // from this AI to anyone + NULL, + owner->GetPhysics()->GetOrigin() + )); + + owner->commSubsystem->AddCommTask(CommunicationTaskPtr(new SingleBarkTask("snd_taking_fire", message))); + + owner->fleeingEvent = true; // I'm fleeing because I was hit, not fleeing an enemy owner->GetMind()->SwitchState(STATE_FLEE); return; } @@ -2532,15 +2557,6 @@ void State::OnProjectileHit(idProjectile* projectile, idEntity* attacker, int da // already in combat mode, you should react to this regardless of what // else you're doing. -/* if ( damageTaken > 0 ) - { - alertType = EAlertTypeDamage; - } - else - { - alertType = EAlertTypeWeapon; - } - */ alertType = EAlertTypeHitByProjectile; // grayman #3331 if ( !ShouldProcessAlert( alertType ) ) @@ -2551,8 +2567,43 @@ void State::OnProjectileHit(idProjectile* projectile, idEntity* attacker, int da DM_LOG(LC_AI, LT_INFO)LOGSTRING("Alerting AI %s due to projectile.\r", owner->name.c_str()); + // grayman #3140 - If a civilan or not armed, you only got here because + // damage was taken. PainState will set up fleeing, and we don't want + // you searching, so there's nothing remaining for you to do here. + + if ( isAfraid ) + { + owner->SetAlertLevel(owner->thresh_5 - 0.1f); + + // Treat getting hit by a projectile as proof that an enemy is present. + Memory& memory = owner->GetMemory(); + memory.timeEnemySeen = gameLocal.time; + memory.posEnemySeen = owner->GetPhysics()->GetOrigin(); + return; + } + + // At this point, if no damage was taken, emit a bark. + if ( damageTaken == 0 ) + { + // 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::SearchOrder_CommType, + owner, NULL, // from this AI to anyone + NULL, + owner->GetPhysics()->GetOrigin() + )); + + owner->commSubsystem->AddCommTask(CommunicationTaskPtr(new SingleBarkTask("snd_taking_fire", message))); + } + + // At this point, you're armed and not a civilian, and either damage was taken, or it wasn't. + // Set up a search. + if ( owner->AI_AlertLevel < owner->thresh_5 ) // grayman #3331 - ignore only if in combat -// if ( owner->AI_AlertLevel < ( owner->thresh_5 - 0.1f ) ) { Memory& memory = owner->GetMemory(); diff --git a/game/ai/States/State.h b/game/ai/States/State.h index 03ef0b7d7..fc3c6cac6 100644 --- a/game/ai/States/State.h +++ b/game/ai/States/State.h @@ -153,7 +153,7 @@ class State * an idProjectile. It does not alert the owning AI, as this is * handled in the calling OnTactileAlert method. */ - virtual void OnProjectileHit(idProjectile* projectile); +// virtual void OnProjectileHit(idProjectile* projectile); // grayman #3140 - no longer used /** * greebo: Method implemented by the States to check diff --git a/game/ai/States/StayInCoverState.cpp b/game/ai/States/StayInCoverState.cpp index 195218a94..fb8013df3 100644 --- a/game/ai/States/StayInCoverState.cpp +++ b/game/ai/States/StayInCoverState.cpp @@ -100,6 +100,7 @@ void StayInCoverState::Think(idAI* owner) } } +/* grayman #3140 - no longer used void StayInCoverState::OnProjectileHit(idProjectile* projectile) { idAI* owner = _owner.GetEntity(); @@ -120,6 +121,7 @@ void StayInCoverState::OnProjectileHit(idProjectile* projectile) owner->GetMind()->EndState(); } } +*/ void StayInCoverState::Save(idSaveGame* savefile) const { diff --git a/game/ai/States/StayInCoverState.h b/game/ai/States/StayInCoverState.h index 9a3f8ae8f..02fc99ed1 100644 --- a/game/ai/States/StayInCoverState.h +++ b/game/ai/States/StayInCoverState.h @@ -50,7 +50,7 @@ class StayInCoverState : protected: // Override the base class method to catch projectile hit events - virtual void OnProjectileHit(idProjectile* projectile); +// virtual void OnProjectileHit(idProjectile* projectile); // grayman #3140 - no longer used }; } // namespace ai