mirror of
https://github.com/OpenMW/openmw.git
synced 2025-01-16 15:29:55 +00:00
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.
This commit is contained in:
parent
4dfe6078c8
commit
bdc6119b31
10 changed files with 66 additions and 27 deletions
|
@ -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
|
||||
|
|
|
@ -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<MWWorld::Ptr>* targetAllies)
|
||||
= 0;
|
||||
|
||||
/// Removes an actor and its allies from combat with the actor's targets.
|
||||
virtual void stopCombat(const MWWorld::Ptr& ptr) = 0;
|
||||
|
|
|
@ -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<ESM::GameSetting>().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
|
||||
|
|
|
@ -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<ESM::Spell>().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<ESM::GameSetting>().find("fCombatDelayNPC")->mValue.getFloat();
|
||||
}
|
||||
|
||||
// Say a provoking combat phrase
|
||||
const int iVoiceAttackOdds
|
||||
= store.get<ESM::GameSetting>().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<ESM::GameSetting>().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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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<MWWorld::Ptr> 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<MWWorld::Ptr>* 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<MWWorld::Ptr> 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"));
|
||||
}
|
||||
|
||||
|
|
|
@ -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<MWWorld::Ptr>* targetAllies) override;
|
||||
|
||||
void stopCombat(const MWWorld::Ptr& ptr) override;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in a new issue