diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index 07d4eb2bc0..91d0ed9bf5 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -120,6 +120,7 @@ namespace MWBase /** * @brief Commit a crime. If any actors witness the crime and report it, * reportCrime will be called automatically. + * @note victim may be empty * @param arg Depends on \a type, e.g. for Theft, the value of the item that was stolen. * @return was the crime reported? */ @@ -192,7 +193,9 @@ namespace MWBase virtual void clear() = 0; - virtual bool isAggressive (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target) = 0; + /// @param bias Can be used to add an additional aggression bias towards the target, + /// making it more likely for the function to return true. + virtual bool isAggressive (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, int bias=0) = 0; }; } diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index ddb3087dd5..5e46a11a3b 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -789,7 +789,6 @@ namespace MWMechanics if (ptr.getClass().isClass(ptr, "Guard") && creatureStats.getAiSequence().getTypeId() != AiPackage::TypeIdPursue && !creatureStats.isHostile()) { - /// \todo Move me! I shouldn't be here... const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); float cutoff = float(esmStore.get().find("iCrimeThreshold")->getInt()); // Force dialogue on sight if bounty is greater than the cutoff @@ -817,7 +816,6 @@ namespace MWMechanics creatureStats.getAiSequence().stopCombat(); // Reset factors to attack - // TODO: Not a complete list, disposition changes? creatureStats.setHostile(false); creatureStats.setAttacked(false); creatureStats.setAlarmed(false); @@ -825,15 +823,6 @@ namespace MWMechanics // Update witness crime id npcStats.setCrimeId(-1); } - else if (!creatureStats.isHostile() && creatureStats.getAiSequence().getTypeId() != AiPackage::TypeIdPursue) - { - if (ptr.getClass().isClass(ptr, "Guard")) - creatureStats.getAiSequence().stack(AiPursue(player), ptr); - else - { - MWBase::Environment::get().getMechanicsManager()->startCombat(ptr, player); - } - } } } } diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 05723c5756..3aeeee65a4 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -221,6 +221,11 @@ void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor) { return; // target is already pursued } + if((*iter)->getTypeId() == AiPackage::TypeIdCombat && package.getTypeId() == AiPackage::TypeIdCombat + && static_cast(*iter)->getTarget() == static_cast(&package)->getTarget()) + { + return; // already in combat with this actor + } else if ((*iter)->getTypeId() == AiPackage::TypeIdWander) static_cast(*iter)->setReturnPosition(Ogre::Vector3(actor.getRefData().getPosition().pos)); } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 4d69301352..cb59d188f7 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -14,6 +14,7 @@ #include "../mwworld/player.hpp" #include "../mwmechanics/aicombat.hpp" +#include "../mwmechanics/aipursue.hpp" #include @@ -836,7 +837,7 @@ namespace MWMechanics const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); // What amount of alarm did this crime generate? - int alarm; + int alarm = 0; if (type == OT_Trespassing || type == OT_SleepingInOwnedBed) alarm = esmStore.get().find("iAlarmTresspass")->getInt(); else if (type == OT_Pickpocket) @@ -847,17 +848,14 @@ namespace MWMechanics alarm = esmStore.get().find("iAlarmKilling")->getInt(); else if (type == OT_Theft) alarm = esmStore.get().find("iAlarmStealing")->getInt(); - else - return false; - // Innocent until proven guilty bool reported = false; // Find all the actors within the alarm radius std::vector neighbors; Ogre::Vector3 from = Ogre::Vector3(player.getRefData().getPosition().pos); - int radius = esmStore.get().find("fAlarmRadius")->getInt(); + float radius = esmStore.get().find("fAlarmRadius")->getFloat(); mActors.getObjectsInRange(from, radius, neighbors); @@ -865,9 +863,9 @@ namespace MWMechanics if (!victim.isEmpty() && from.squaredDistance(Ogre::Vector3(victim.getRefData().getPosition().pos)) > radius*radius) neighbors.push_back(victim); - int id = MWBase::Environment::get().getWorld()->getPlayer().getNewCrimeId(); + bool victimAware = false; - // Find actors who witnessed the crime + // Find actors who directly witnessed the crime for (std::vector::iterator it = neighbors.begin(); it != neighbors.end(); ++it) { if (*it == player) continue; // not the player @@ -875,22 +873,13 @@ namespace MWMechanics // Was the crime seen? if (MWBase::Environment::get().getWorld()->getLOS(player, *it) && awarenessCheck(player, *it) ) { - // TODO: Add more messages + if (*it == victim) + victimAware = true; + + // TODO: are there other messages? if (type == OT_Theft) MWBase::Environment::get().getDialogueManager()->say(*it, "thief"); - if (*it == victim) - { - // Self-defense - // The victim is aware of the criminal/assailant. If being assaulted, fight back now - // (regardless of whether the assault is reported or not) - // This applies to both NPCs and creatures - - // ... except if this is a guard: then the player is given a chance to pay a fine / go to jail instead - if (type == OT_Assault && !it->getClass().isClass(*it, "guard")) - MWBase::Environment::get().getMechanicsManager()->startCombat(victim, player); - } - // Crime reporting only applies to NPCs if (!it->getClass().isNpc()) continue; @@ -899,32 +888,19 @@ namespace MWMechanics if (it->getClass().getCreatureStats(*it).getAiSetting(CreatureStats::AI_Alarm).getBase() >= alarm) { reported = true; - - // Tell everyone, including yourself - for (std::vector::iterator it1 = neighbors.begin(); it1 != neighbors.end(); ++it1) - { - if ( *it1 == player - || !it1->getClass().isNpc()) continue; // not the player and is an NPC - - // Will other witnesses paticipate in crime - if ( it1->getClass().getCreatureStats(*it1).getAiSetting(CreatureStats::AI_Alarm).getBase() >= alarm - || type == OT_Assault ) - { - it1->getClass().getNpcStats(*it1).setCrimeId(id); - } - - // Mark as Alarmed for dialogue - it1->getClass().getCreatureStats(*it1).setAlarmed(true); - } } } } + if (reported) reportCrime(player, victim, type, arg); + else if (victimAware && !victim.isEmpty() && type == OT_Assault) + startCombat(victim, player); + return reported; } - void MechanicsManager::reportCrime(const MWWorld::Ptr &ptr, const MWWorld::Ptr &victim, OffenseType type, int arg) + void MechanicsManager::reportCrime(const MWWorld::Ptr &player, const MWWorld::Ptr &victim, OffenseType type, int arg) { const MWWorld::Store& store = MWBase::Environment::get().getWorld()->getStore().get(); @@ -944,7 +920,7 @@ namespace MWMechanics } MWBase::Environment::get().getWindowManager()->messageBox("#{sCrimeMessage}"); - ptr.getClass().getNpcStats(ptr).setBounty(ptr.getClass().getNpcStats(ptr).getBounty() + player.getClass().getNpcStats(player).setBounty(player.getClass().getNpcStats(player).getBounty() + arg); // If committing a crime against a faction member, expell from the faction @@ -953,9 +929,81 @@ namespace MWMechanics std::string factionID; if(!victim.getClass().getNpcStats(victim).getFactionRanks().empty()) factionID = victim.getClass().getNpcStats(victim).getFactionRanks().begin()->first; - if (ptr.getClass().getNpcStats(ptr).isSameFaction(victim.getClass().getNpcStats(victim))) + if (player.getClass().getNpcStats(player).isSameFaction(victim.getClass().getNpcStats(victim))) { - ptr.getClass().getNpcStats(ptr).expell(factionID); + player.getClass().getNpcStats(player).expell(factionID); + } + } + + // Make surrounding actors within alarm distance respond to the crime + std::vector neighbors; + + const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); + + Ogre::Vector3 from = Ogre::Vector3(player.getRefData().getPosition().pos); + float radius = esmStore.get().find("fAlarmRadius")->getFloat(); + + mActors.getObjectsInRange(from, radius, neighbors); + + // victim should be considered even beyond alarm radius + if (!victim.isEmpty() && from.squaredDistance(Ogre::Vector3(victim.getRefData().getPosition().pos)) > radius*radius) + neighbors.push_back(victim); + + int id = MWBase::Environment::get().getWorld()->getPlayer().getNewCrimeId(); + + // What amount of provocation did this crime generate? + // Controls whether witnesses will engage combat with the criminal. + int fight = 0; + if (type == OT_Trespassing || type == OT_SleepingInOwnedBed) + fight = esmStore.get().find("iFightTrespass")->getInt(); + else if (type == OT_Pickpocket) + fight = esmStore.get().find("iFightPickpocket")->getInt(); + else if (type == OT_Assault) // Note: iFightAttack is for the victim, iFightAttacking for witnesses? + fight = esmStore.get().find("iFightAttack")->getInt(); + else if (type == OT_Murder) + fight = esmStore.get().find("iFightKilling")->getInt(); + else if (type == OT_Theft) + fight = esmStore.get().find("fFightStealing")->getFloat(); + + const int iFightAttacking = esmStore.get().find("iFightAttacking")->getInt(); + + // Tell everyone (including the original reporter) in alarm range + for (std::vector::iterator it = neighbors.begin(); it != neighbors.end(); ++it) + { + if ( *it == player + || !it->getClass().isNpc()) continue; + + int aggression = fight; + + // Note: iFightAttack is used for the victim, iFightAttacking for witnesses? + if (*it != victim && type == OT_Assault) + aggression = iFightAttacking; + + if (it->getClass().isClass(*it, "guard")) + { + // Mark as Alarmed for dialogue + it->getClass().getCreatureStats(*it).setAlarmed(true); + + // Set the crime ID, which we will use to calm down participants + // once the bounty has been paid. + it->getClass().getNpcStats(*it).setCrimeId(id); + + it->getClass().getCreatureStats(*it).getAiSequence().stack(AiPursue(player), *it); + } + else + { + bool aggressive = MWBase::Environment::get().getMechanicsManager()->isAggressive(*it, player, aggression); + if (aggressive) + { + startCombat(*it, player); + + // Set the crime ID, which we will use to calm down participants + // once the bounty has been paid. + it->getClass().getNpcStats(*it).setCrimeId(id); + + // Mark as Alarmed for dialogue + it->getClass().getCreatureStats(*it).setAlarmed(true); + } } } } @@ -1099,7 +1147,7 @@ namespace MWMechanics mActors.clear(); } - bool MechanicsManager::isAggressive(const MWWorld::Ptr &ptr, const MWWorld::Ptr &target) + bool MechanicsManager::isAggressive(const MWWorld::Ptr &ptr, const MWWorld::Ptr &target, int bias) { Ogre::Vector3 pos1 (ptr.getRefData().getPosition().pos); Ogre::Vector3 pos2 (target.getRefData().getPosition().pos); @@ -1117,9 +1165,10 @@ namespace MWMechanics if (ptr.getClass().isNpc()) disposition = getDerivedDisposition(ptr); - int fight = ptr.getClass().getCreatureStats(ptr).getAiSetting(CreatureStats::AI_Fight).getModified() + int fight = std::max(0.f, ptr.getClass().getCreatureStats(ptr).getAiSetting(CreatureStats::AI_Fight).getModified() + (iFightDistanceBase - fFightDistanceMultiplier * d) - + ((50 - disposition) * fFightDispMult); + + ((50 - disposition) * fFightDispMult)) + + bias; return (fight >= 100); } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 677d60ae58..0fee72b77e 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -157,7 +157,9 @@ namespace MWMechanics virtual void clear(); - virtual bool isAggressive (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target); + /// @param bias Can be used to add an additional aggression bias towards the target, + /// making it more likely for the function to return true. + virtual bool isAggressive (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, int bias=0); }; }