From bdc6119b31d4fdda2a178c3e79055eeb41eb69f5 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 25 Jan 2024 19:36:41 +0100 Subject: [PATCH] Bring attack voice lines in line with research Only play them when starting combat when not in combat or not in combat with one of the target's allies. Don't play them when casting spells whose first effect isn't ranged. --- CHANGELOG.md | 1 + apps/openmw/mwbase/mechanicsmanager.hpp | 4 +- apps/openmw/mwmechanics/actors.cpp | 10 ++--- apps/openmw/mwmechanics/aicombat.cpp | 25 +++++++---- apps/openmw/mwmechanics/aicombat.hpp | 2 +- apps/openmw/mwmechanics/aicombataction.hpp | 2 + apps/openmw/mwmechanics/aisequence.hpp | 3 -- .../mwmechanics/mechanicsmanagerimp.cpp | 41 +++++++++++++++---- .../mwmechanics/mechanicsmanagerimp.hpp | 3 +- apps/openmw/mwscript/aiextensions.cpp | 2 +- 10 files changed, 66 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95b04c2741..c467fd35a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ Bug #5129: Stuttering animation on Centurion Archer Bug #5280: Unskinned shapes in skinned equipment are rendered in the wrong place Bug #5371: Keyframe animation tracks are used for any file that begins with an X + Bug #5413: Enemies do a battlecry everytime the player summons a creature Bug #5714: Touch spells cast using ExplodeSpell don't always explode Bug #5849: Paralysis breaks landing Bug #5870: Disposing of actors who were selected in the console doesn't deselect them like vanilla diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index 9e99a37ec7..e1cce6acc4 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -109,7 +109,9 @@ namespace MWBase virtual bool awarenessCheck(const MWWorld::Ptr& ptr, const MWWorld::Ptr& observer) = 0; /// Makes \a ptr fight \a target. Also shouts a combat taunt. - virtual void startCombat(const MWWorld::Ptr& ptr, const MWWorld::Ptr& target) = 0; + virtual void startCombat( + const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, const std::set* targetAllies) + = 0; /// Removes an actor and its allies from combat with the actor's targets. virtual void stopCombat(const MWWorld::Ptr& ptr) = 0; diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index bc3cc3bb6a..d0ae5c6ea4 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -620,7 +620,7 @@ namespace MWMechanics if (creatureStats2.matchesActorId(ally.getClass().getCreatureStats(ally).getHitAttemptActorId())) { - mechanicsManager->startCombat(actor1, actor2); + mechanicsManager->startCombat(actor1, actor2, nullptr); // Also set the same hit attempt actor. Otherwise, if fighting the player, they may stop combat // if the player gets out of reach, while the ally would continue combat with the player creatureStats1.setHitAttemptActorId(ally.getClass().getCreatureStats(ally).getHitAttemptActorId()); @@ -655,11 +655,11 @@ namespace MWMechanics { if (ally2 != actor2 && ally2.getClass().getCreatureStats(ally2).getAiSequence().isInCombat(actor1)) { - mechanicsManager->startCombat(actor1, actor2); + mechanicsManager->startCombat(actor1, actor2, &allies2); // Also have actor1's allies start combat for (const MWWorld::Ptr& ally1 : allies1) if (ally1 != player) - mechanicsManager->startCombat(ally1, actor2); + mechanicsManager->startCombat(ally1, actor2, &allies2); return; } } @@ -747,7 +747,7 @@ namespace MWMechanics bool LOS = world->getLOS(actor1, actor2) && mechanicsManager->awarenessCheck(actor2, actor1); if (LOS) - mechanicsManager->startCombat(actor1, actor2); + mechanicsManager->startCombat(actor1, actor2, nullptr); } } @@ -1133,7 +1133,7 @@ namespace MWMechanics = esmStore.get().find("iCrimeThresholdMultiplier")->mValue.getInteger(); if (playerStats.getBounty() >= cutoff * iCrimeThresholdMultiplier) { - mechanicsManager->startCombat(ptr, player); + mechanicsManager->startCombat(ptr, player, nullptr); creatureStats.setHitAttemptActorId( playerClass.getCreatureStats(player) .getActorId()); // Stops the guard from quitting combat if player is unreachable diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 12072df2ac..0b3c2b8bd2 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -270,7 +270,15 @@ namespace MWMechanics { storage.startCombatMove(isRangedCombat, distToTarget, rangeAttack, actor, target); // start new attack - storage.startAttackIfReady(actor, characterController, weapon, isRangedCombat); + bool canShout = true; + ESM::RefId spellId = storage.mCurrentAction->getSpell(); + if (!spellId.empty()) + { + const ESM::Spell* spell = MWBase::Environment::get().getESMStore()->get().find(spellId); + if (spell->mEffects.mList.empty() || spell->mEffects.mList[0].mRange != ESM::RT_Target) + canShout = false; + } + storage.startAttackIfReady(actor, characterController, weapon, isRangedCombat, canShout); } // If actor uses custom destination it has to try to rebuild path because environment can change @@ -635,7 +643,7 @@ namespace MWMechanics } void AiCombatStorage::startAttackIfReady(const MWWorld::Ptr& actor, CharacterController& characterController, - const ESM::Weapon* weapon, bool distantCombat) + const ESM::Weapon* weapon, bool distantCombat, bool canShout) { if (mReadyToAttack && characterController.readyToStartAttack()) { @@ -658,12 +666,15 @@ namespace MWMechanics baseDelay = store.get().find("fCombatDelayNPC")->mValue.getFloat(); } - // Say a provoking combat phrase - const int iVoiceAttackOdds - = store.get().find("iVoiceAttackOdds")->mValue.getInteger(); - if (Misc::Rng::roll0to99(prng) < iVoiceAttackOdds) + if (canShout) { - MWBase::Environment::get().getDialogueManager()->say(actor, ESM::RefId::stringRefId("attack")); + // Say a provoking combat phrase + const int iVoiceAttackOdds + = store.get().find("iVoiceAttackOdds")->mValue.getInteger(); + if (Misc::Rng::roll0to99(prng) < iVoiceAttackOdds) + { + MWBase::Environment::get().getDialogueManager()->say(actor, ESM::RefId::stringRefId("attack")); + } } mAttackCooldown = std::min(baseDelay + 0.01 * Misc::Rng::roll0to99(prng), baseDelay + 0.9); } diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index 494f038d6e..d5a9c3464c 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -64,7 +64,7 @@ namespace MWMechanics void updateCombatMove(float duration); void stopCombatMove(); void startAttackIfReady(const MWWorld::Ptr& actor, CharacterController& characterController, - const ESM::Weapon* weapon, bool distantCombat); + const ESM::Weapon* weapon, bool distantCombat, bool canShout); void updateAttack(const MWWorld::Ptr& actor, CharacterController& characterController); void stopAttack(); diff --git a/apps/openmw/mwmechanics/aicombataction.hpp b/apps/openmw/mwmechanics/aicombataction.hpp index 05d2a18e25..9eb7df211a 100644 --- a/apps/openmw/mwmechanics/aicombataction.hpp +++ b/apps/openmw/mwmechanics/aicombataction.hpp @@ -16,6 +16,7 @@ namespace MWMechanics virtual float getCombatRange(bool& isRanged) const = 0; virtual float getActionCooldown() const { return 0.f; } virtual const ESM::Weapon* getWeapon() const { return nullptr; } + virtual ESM::RefId getSpell() const { return {}; } virtual bool isAttackingOrSpell() const { return true; } virtual bool isFleeing() const { return false; } }; @@ -43,6 +44,7 @@ namespace MWMechanics void prepare(const MWWorld::Ptr& actor) override; float getCombatRange(bool& isRanged) const override; + ESM::RefId getSpell() const override { return mSpellId; } }; class ActionEnchantedItem : public Action diff --git a/apps/openmw/mwmechanics/aisequence.hpp b/apps/openmw/mwmechanics/aisequence.hpp index 92c1724ea6..6b02da6633 100644 --- a/apps/openmw/mwmechanics/aisequence.hpp +++ b/apps/openmw/mwmechanics/aisequence.hpp @@ -132,9 +132,6 @@ namespace MWMechanics /// Are we in combat with this particular actor? bool isInCombat(const MWWorld::Ptr& actor) const; - bool canAddTarget(const ESM::Position& actorPos, float distToTarget) const; - ///< Function assumes that actor can have only 1 target apart player - /// Removes all combat packages until first non-combat or stack empty. void stopCombat(); diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index fd9d98f3b9..2c556185ce 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1169,9 +1169,9 @@ namespace MWMechanics && !victim.getClass().getCreatureStats(victim).getAiSequence().hasPackage(AiPackageTypeId::Pursue)) reported = reportCrime(player, victim, type, ESM::RefId(), arg); + // TODO: combat should be started with an "unaware" flag, which makes the victim flee? if (!reported) - startCombat(victim, - player); // TODO: combat should be started with an "unaware" flag, which makes the victim flee? + startCombat(victim, player, &playerFollowers); } return crimeSeen; } @@ -1412,7 +1412,7 @@ namespace MWMechanics observerStats.modCrimeDispositionModifier(dispositionModifier); } - startCombat(actor, player); + startCombat(actor, player, &playerFollowers); // Apply aggression value to the base Fight rating, so that the actor can continue fighting // after a Calm spell wears off @@ -1463,7 +1463,7 @@ namespace MWMechanics // Attacker is in combat with us, but we are not in combat with the attacker yet. Time to fight back. // Note: accidental or collateral damage attacks are ignored. if (!victim.getClass().getCreatureStats(victim).getAiSequence().isInPursuit()) - startCombat(victim, player); + startCombat(victim, player, &playerFollowers); // Set the crime ID, which we will use to calm down participants // once the bounty has been paid. @@ -1508,14 +1508,14 @@ namespace MWMechanics if (!peaceful) { - startCombat(target, attacker); + startCombat(target, attacker, nullptr); // Force friendly actors into combat to prevent infighting between followers std::set followersTarget; getActorsSidingWith(target, followersTarget); for (const auto& follower : followersTarget) { if (follower != attacker && follower != player) - startCombat(follower, attacker); + startCombat(follower, attacker, nullptr); } } } @@ -1664,7 +1664,8 @@ namespace MWMechanics } } - void MechanicsManager::startCombat(const MWWorld::Ptr& ptr, const MWWorld::Ptr& target) + void MechanicsManager::startCombat( + const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, const std::set* targetAllies) { CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); @@ -1682,6 +1683,30 @@ namespace MWMechanics } const bool inCombat = stats.getAiSequence().isInCombat(); + bool shout = !inCombat; + if (inCombat) + { + const auto isInCombatWithOneOf = [&](const auto& allies) { + for (const MWWorld::Ptr& ally : allies) + { + if (stats.getAiSequence().isInCombat(ally)) + return true; + } + return false; + }; + if (targetAllies) + shout = !isInCombatWithOneOf(*targetAllies); + else + { + shout = stats.getAiSequence().isInCombat(target); + if (!shout) + { + std::set sidingActors; + getActorsSidingWith(target, sidingActors); + shout = !isInCombatWithOneOf(sidingActors); + } + } + } stats.getAiSequence().stack(MWMechanics::AiCombat(target), ptr); if (target == getPlayer()) { @@ -1716,7 +1741,7 @@ namespace MWMechanics } // Must be done after the target is set up, so that CreatureTargetted dialogue filter works properly - if (!inCombat) + if (shout) MWBase::Environment::get().getDialogueManager()->say(ptr, ESM::RefId::stringRefId("attack")); } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index f2483396fe..027fa9bb62 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -107,7 +107,8 @@ namespace MWMechanics bool awarenessCheck(const MWWorld::Ptr& ptr, const MWWorld::Ptr& observer) override; /// Makes \a ptr fight \a target. Also shouts a combat taunt. - void startCombat(const MWWorld::Ptr& ptr, const MWWorld::Ptr& target) override; + void startCombat( + const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, const std::set* targetAllies) override; void stopCombat(const MWWorld::Ptr& ptr) override; diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp index a44b391e13..3493c98f41 100644 --- a/apps/openmw/mwscript/aiextensions.cpp +++ b/apps/openmw/mwscript/aiextensions.cpp @@ -508,7 +508,7 @@ namespace MWScript MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(targetID, true, false); if (!target.isEmpty() && !target.getClass().getCreatureStats(target).isDead()) - MWBase::Environment::get().getMechanicsManager()->startCombat(actor, target); + MWBase::Environment::get().getMechanicsManager()->startCombat(actor, target, nullptr); } };