Advertisement
Guest User

Untitled

a guest
Apr 10th, 2020
683
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 105.80 KB | None | 0 0
  1. #include "actors.hpp"
  2.  
  3. #include <components/esm/esmreader.hpp>
  4. #include <components/esm/esmwriter.hpp>
  5.  
  6. #include <components/sceneutil/positionattitudetransform.hpp>
  7. #include <components/sceneutil/vismask.hpp>
  8. #include <components/debug/debuglog.hpp>
  9. #include <components/misc/rng.hpp>
  10. #include <components/settings/settings.hpp>
  11.  
  12. #include "../mwworld/esmstore.hpp"
  13. #include "../mwworld/class.hpp"
  14. #include "../mwworld/inventorystore.hpp"
  15. #include "../mwworld/actionequip.hpp"
  16. #include "../mwworld/player.hpp"
  17.  
  18. #include "../mwbase/world.hpp"
  19. #include "../mwbase/environment.hpp"
  20. #include "../mwbase/windowmanager.hpp"
  21. #include "../mwbase/dialoguemanager.hpp"
  22. #include "../mwbase/soundmanager.hpp"
  23. #include "../mwbase/mechanicsmanager.hpp"
  24. #include "../mwbase/statemanager.hpp"
  25.  
  26. #include "../mwmechanics/aibreathe.hpp"
  27.  
  28. #include "spellcasting.hpp"
  29. #include "steering.hpp"
  30. #include "npcstats.hpp"
  31. #include "creaturestats.hpp"
  32. #include "movement.hpp"
  33. #include "character.hpp"
  34. #include "aicombat.hpp"
  35. #include "aicombataction.hpp"
  36. #include "aifollow.hpp"
  37. #include "aipursue.hpp"
  38. #include "actor.hpp"
  39. #include "summoning.hpp"
  40. #include "combat.hpp"
  41. #include "actorutil.hpp"
  42.  
  43. namespace
  44. {
  45.  
  46. bool isConscious(const MWWorld::Ptr& ptr)
  47. {
  48.     const MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
  49.     return !stats.isDead() && !stats.getKnockedDown();
  50. }
  51.  
  52. int getBoundItemSlot (const std::string& itemId)
  53. {
  54.     static std::map<std::string, int> boundItemsMap;
  55.     if (boundItemsMap.empty())
  56.     {
  57.         std::string boundId = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("sMagicBoundBootsID")->mValue.getString();
  58.         boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_Boots;
  59.  
  60.         boundId = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("sMagicBoundCuirassID")->mValue.getString();
  61.         boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_Cuirass;
  62.  
  63.         boundId = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("sMagicBoundLeftGauntletID")->mValue.getString();
  64.         boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_LeftGauntlet;
  65.  
  66.         boundId = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("sMagicBoundRightGauntletID")->mValue.getString();
  67.         boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_RightGauntlet;
  68.  
  69.         boundId = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("sMagicBoundHelmID")->mValue.getString();
  70.         boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_Helmet;
  71.  
  72.         boundId = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("sMagicBoundShieldID")->mValue.getString();
  73.         boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_CarriedLeft;
  74.     }
  75.  
  76.     int slot = MWWorld::InventoryStore::Slot_CarriedRight;
  77.     std::map<std::string, int>::iterator it = boundItemsMap.find(itemId);
  78.     if (it != boundItemsMap.end())
  79.         slot = it->second;
  80.  
  81.     return slot;
  82. }
  83.  
  84. class CheckActorCommanded : public MWMechanics::EffectSourceVisitor
  85. {
  86.     MWWorld::Ptr mActor;
  87. public:
  88.     bool mCommanded;
  89.     CheckActorCommanded(const MWWorld::Ptr& actor)
  90.         : mActor(actor)
  91.     , mCommanded(false){}
  92.  
  93.     virtual void visit (MWMechanics::EffectKey key,
  94.                              const std::string& sourceName, const std::string& sourceId, int casterActorId,
  95.                         float magnitude, float remainingTime = -1, float totalTime = -1)
  96.     {
  97.         if (((key.mId == ESM::MagicEffect::CommandHumanoid && mActor.getClass().isNpc())
  98.             || (key.mId == ESM::MagicEffect::CommandCreature && mActor.getTypeName() == typeid(ESM::Creature).name()))
  99.             && magnitude >= mActor.getClass().getCreatureStats(mActor).getLevel())
  100.                 mCommanded = true;
  101.     }
  102. };
  103.  
  104. // Check for command effects having ended and remove package if necessary
  105. void adjustCommandedActor (const MWWorld::Ptr& actor)
  106. {
  107.     CheckActorCommanded check(actor);
  108.     MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor);
  109.     stats.getActiveSpells().visitEffectSources(check);
  110.  
  111.     bool hasCommandPackage = false;
  112.  
  113.     std::list<MWMechanics::AiPackage*>::const_iterator it;
  114.     for (it = stats.getAiSequence().begin(); it != stats.getAiSequence().end(); ++it)
  115.     {
  116.         if ((*it)->getTypeId() == MWMechanics::AiPackage::TypeIdFollow &&
  117.                 static_cast<MWMechanics::AiFollow*>(*it)->isCommanded())
  118.         {
  119.             hasCommandPackage = true;
  120.             break;
  121.         }
  122.     }
  123.  
  124.     if (!check.mCommanded && hasCommandPackage)
  125.         stats.getAiSequence().erase(it);
  126. }
  127.  
  128. void getRestorationPerHourOfSleep (const MWWorld::Ptr& ptr, float& health, float& magicka)
  129. {
  130.     MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr);
  131.     const MWWorld::Store<ESM::GameSetting>& settings = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
  132.  
  133.     int endurance = stats.getAttribute (ESM::Attribute::Endurance).getModified ();
  134.     health = 0.1f * endurance;
  135.  
  136.     float fRestMagicMult = settings.find("fRestMagicMult")->mValue.getFloat ();
  137.     magicka = fRestMagicMult * stats.getAttribute(ESM::Attribute::Intelligence).getModified();
  138. }
  139.  
  140. }
  141.  
  142. namespace MWMechanics
  143. {
  144.     static const int GREETING_SHOULD_START = 4; // how many updates should pass before NPC can greet player
  145.     static const int GREETING_SHOULD_END = 20;  // how many updates should pass before NPC stops turning to player
  146.     static const int GREETING_COOLDOWN = 40;    // how many updates should pass before NPC can continue movement
  147.     static const float DECELERATE_DISTANCE = 512.f;
  148.  
  149.     class GetStuntedMagickaDuration : public MWMechanics::EffectSourceVisitor
  150.     {
  151.     public:
  152.         float mRemainingTime;
  153.  
  154.         GetStuntedMagickaDuration(const MWWorld::Ptr& actor)
  155.             : mRemainingTime(0.f){}
  156.  
  157.         virtual void visit (MWMechanics::EffectKey key,
  158.                                 const std::string& sourceName, const std::string& sourceId, int casterActorId,
  159.                             float magnitude, float remainingTime = -1, float totalTime = -1)
  160.         {
  161.             if (mRemainingTime == -1) return;
  162.  
  163.             if (key.mId == ESM::MagicEffect::StuntedMagicka)
  164.             {
  165.                 if (totalTime == -1)
  166.                 {
  167.                     mRemainingTime = -1;
  168.                     return;
  169.                 }
  170.  
  171.                 if (remainingTime > mRemainingTime)
  172.                     mRemainingTime = remainingTime;
  173.             }
  174.         }
  175.     };
  176.  
  177.     class SoulTrap : public MWMechanics::EffectSourceVisitor
  178.     {
  179.         MWWorld::Ptr mCreature;
  180.         MWWorld::Ptr mActor;
  181.         bool mTrapped;
  182.     public:
  183.         SoulTrap(const MWWorld::Ptr& trappedCreature)
  184.             : mCreature(trappedCreature)
  185.             , mTrapped(false)
  186.         {
  187.         }
  188.  
  189.         virtual void visit (MWMechanics::EffectKey key,
  190.                                  const std::string& sourceName, const std::string& sourceId, int casterActorId,
  191.                             float magnitude, float remainingTime = -1, float totalTime = -1)
  192.         {
  193.             if (mTrapped)
  194.                 return;
  195.             if (key.mId != ESM::MagicEffect::Soultrap)
  196.                 return;
  197.             if (magnitude <= 0)
  198.                 return;
  199.  
  200.             MWBase::World* world = MWBase::Environment::get().getWorld();
  201.  
  202.             MWWorld::Ptr caster = world->searchPtrViaActorId(casterActorId);
  203.             if (caster.isEmpty() || !caster.getClass().isActor())
  204.                 return;
  205.  
  206.             static const float fSoulgemMult = world->getStore().get<ESM::GameSetting>().find("fSoulgemMult")->mValue.getFloat();;
  207.  
  208.         int creatureSoulValue = 0;
  209.         if (mCreature.getTypeName() == typeid(ESM::Creature).name())
  210.         {    creatureSoulValue = mCreature.get<ESM::Creature>()->mBase->mData.mSoul;
  211.  
  212.         }
  213.         else
  214.         {creatureSoulValue = 400;}
  215.          
  216.             if (creatureSoulValue == 0)
  217.                 return;
  218.  
  219.             // Use the smallest soulgem that is large enough to hold the soul
  220.             MWWorld::ContainerStore& container = caster.getClass().getContainerStore(caster);
  221.             MWWorld::ContainerStoreIterator gem = container.end();
  222.             float gemCapacity = std::numeric_limits<float>::max();
  223.             std::string soulgemFilter = "misc_soulgem"; // no other way to check for soulgems? :/
  224.  
  225.             for (MWWorld::ContainerStoreIterator it = container.begin(MWWorld::ContainerStore::Type_Miscellaneous);
  226.                  it != container.end(); ++it)
  227.             {
  228.                 const std::string& id = it->getCellRef().getRefId();
  229.                     float thisGemCapacity = it->get<ESM::Miscellaneous>()->mBase->mData.mValue * fSoulgemMult;
  230.         if (mCreature.getTypeName() != typeid(ESM::Creature).name() )
  231.         {
  232.             if ( id == "misc_soulgem_black"&& it->getCellRef().getSoul().empty())
  233.             {
  234.                         gemCapacity = thisGemCapacity;
  235.                         gem = it;
  236.  
  237.             }
  238.  
  239.         }
  240.         else{
  241.                 if (id.size() >= soulgemFilter.size() && id.substr(0,soulgemFilter.size()) == soulgemFilter)
  242.                 {
  243.                     if (thisGemCapacity >= creatureSoulValue && thisGemCapacity < gemCapacity
  244.                             && it->getCellRef().getSoul().empty()&& id != "misc_soulgem_black" )
  245.                     {
  246.                         gem = it;
  247.                         gemCapacity = thisGemCapacity;
  248.                     }
  249.                 }}
  250.             }
  251.         if (mCreature.getTypeName() != typeid(ESM::Creature).name())
  252.         {
  253.          const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
  254.             store.get<ESM::Creature>().find("soul_" + mCreature.getCellRef().getRefId());
  255.                  // MWBase::Environment::get().getWindowManager()->messageBox ("soul_" + mCreature.getCellRef().getRefId());
  256.         }
  257.             if (gem == container.end())
  258.                 return;
  259.  
  260.             // Set the soul on just one of the gems, not the whole stack
  261.             gem->getContainerStore()->unstack(*gem, caster);
  262. if (mCreature.getTypeName() == typeid(ESM::Creature).name())
  263. {  
  264.             gem->getCellRef().setSoul(mCreature.getCellRef().getRefId());
  265.  
  266. }
  267. else
  268. {
  269.  
  270.             gem->getCellRef().setSoul("soul_" + mCreature.getCellRef().getRefId());
  271.                 MWBase::Environment::get().getWindowManager()->messageBox(gem->getCellRef().getSoul());
  272.                
  273. }
  274.  
  275.             // Restack the gem with other gems with the same soul
  276.             gem->getContainerStore()->restack(*gem);
  277.  
  278.             mTrapped = true;
  279.  
  280.             if (caster == getPlayer())
  281.                 MWBase::Environment::get().getWindowManager()->messageBox("#{sSoultrapSuccess}");
  282.  
  283.             const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>()
  284.                     .search("VFX_Soul_Trap");
  285.             if (fx)
  286.                 MWBase::Environment::get().getWorld()->spawnEffect("meshes\\" + fx->mModel,
  287.                     "", mCreature.getRefData().getPosition().asVec3());
  288.  
  289.             MWBase::Environment::get().getSoundManager()->playSound3D(
  290.                 mCreature.getRefData().getPosition().asVec3(), "conjuration hit", 1.f, 1.f
  291.             );
  292.         }
  293.     };
  294.  
  295.     void Actors::addBoundItem (const std::string& itemId, const MWWorld::Ptr& actor)
  296.     {
  297.         MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor);
  298.         int slot = getBoundItemSlot(itemId);
  299.  
  300.         if (actor.getClass().getContainerStore(actor).count(itemId) != 0)
  301.             return;
  302.  
  303.         MWWorld::ContainerStoreIterator prevItem = store.getSlot(slot);
  304.  
  305.         MWWorld::Ptr boundPtr = *store.MWWorld::ContainerStore::add(itemId, 1, actor);
  306.         MWWorld::ActionEquip action(boundPtr);
  307.         action.execute(actor);
  308.  
  309.         if (actor != MWMechanics::getPlayer())
  310.             return;
  311.  
  312.         MWWorld::Ptr newItem = *store.getSlot(slot);
  313.  
  314.         if (newItem.isEmpty() || boundPtr != newItem)
  315.             return;
  316.  
  317.         MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer();
  318.  
  319.         // change draw state only if the item is in player's right hand
  320.         if (slot == MWWorld::InventoryStore::Slot_CarriedRight)
  321.             player.setDrawState(MWMechanics::DrawState_Weapon);
  322.  
  323.         if (prevItem != store.end())
  324.             player.setPreviousItem(itemId, prevItem->getCellRef().getRefId());
  325.     }
  326.  
  327.     void Actors::removeBoundItem (const std::string& itemId, const MWWorld::Ptr& actor)
  328.     {
  329.         MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor);
  330.         int slot = getBoundItemSlot(itemId);
  331.  
  332.         MWWorld::ContainerStoreIterator currentItem = store.getSlot(slot);
  333.  
  334.         bool wasEquipped = currentItem != store.end() && Misc::StringUtils::ciEqual(currentItem->getCellRef().getRefId(), itemId);
  335.  
  336.         if (actor != MWMechanics::getPlayer())
  337.         {
  338.             store.remove(itemId, 1, actor);
  339.  
  340.             // Equip a replacement
  341.             if (!wasEquipped)
  342.                 return;
  343.  
  344.             std::string type = currentItem->getTypeName();
  345.             if (type != typeid(ESM::Weapon).name() && type != typeid(ESM::Armor).name() && type != typeid(ESM::Clothing).name())
  346.                 return;
  347.  
  348.             if (actor.getClass().getCreatureStats(actor).isDead())
  349.                 return;
  350.  
  351.             if (!actor.getClass().hasInventoryStore(actor))
  352.                 return;
  353.  
  354.             if (actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf())
  355.                 return;
  356.  
  357.             actor.getClass().getInventoryStore(actor).autoEquip(actor);
  358.  
  359.             return;
  360.         }
  361.  
  362.         MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer();
  363.         std::string prevItemId = player.getPreviousItem(itemId);
  364.         player.erasePreviousItem(itemId);
  365.  
  366.         if (!prevItemId.empty())
  367.         {
  368.             // Find previous item (or its replacement) by id.
  369.             // we should equip previous item only if expired bound item was equipped.
  370.             MWWorld::Ptr item = store.findReplacement(prevItemId);
  371.             if (!item.isEmpty() && wasEquipped)
  372.             {
  373.                 MWWorld::ActionEquip action(item);
  374.                 action.execute(actor);
  375.             }
  376.         }
  377.  
  378.         store.remove(itemId, 1, actor);
  379.     }
  380.  
  381.     void Actors::updateActor (const MWWorld::Ptr& ptr, float duration)
  382.     {
  383.         // magic effects
  384.         adjustMagicEffects (ptr);
  385.         if (ptr.getClass().getCreatureStats(ptr).needToRecalcDynamicStats())
  386.             calculateDynamicStats (ptr);
  387.  
  388.         calculateCreatureStatModifiers (ptr, duration);
  389.         // fatigue restoration
  390.         calculateRestoration(ptr, duration);
  391.     }
  392.  
  393.     void Actors::updateHeadTracking(const MWWorld::Ptr& actor, const MWWorld::Ptr& targetActor,
  394.                                     MWWorld::Ptr& headTrackTarget, float& sqrHeadTrackDistance)
  395.     {
  396.         if (!actor.getRefData().getBaseNode())
  397.             return;
  398.  
  399.         if (targetActor.getClass().getCreatureStats(targetActor).isDead())
  400.             return;
  401.  
  402.         static const float fMaxHeadTrackDistance = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>()
  403.                 .find("fMaxHeadTrackDistance")->mValue.getFloat();
  404.         static const float fInteriorHeadTrackMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>()
  405.                 .find("fInteriorHeadTrackMult")->mValue.getFloat();
  406.         float maxDistance = fMaxHeadTrackDistance;
  407.         const ESM::Cell* currentCell = actor.getCell()->getCell();
  408.         if (!currentCell->isExterior() && !(currentCell->mData.mFlags & ESM::Cell::QuasiEx))
  409.             maxDistance *= fInteriorHeadTrackMult;
  410.  
  411.         const osg::Vec3f actor1Pos(actor.getRefData().getPosition().asVec3());
  412.         const osg::Vec3f actor2Pos(targetActor.getRefData().getPosition().asVec3());
  413.         float sqrDist = (actor1Pos - actor2Pos).length2();
  414.  
  415.         if (sqrDist > maxDistance*maxDistance)
  416.             return;
  417.  
  418.         // stop tracking when target is behind the actor
  419.         osg::Vec3f actorDirection = actor.getRefData().getBaseNode()->getAttitude() * osg::Vec3f(0,1,0);
  420.         osg::Vec3f targetDirection(actor2Pos - actor1Pos);
  421.         actorDirection.z() = 0;
  422.         targetDirection.z() = 0;
  423.         actorDirection.normalize();
  424.         targetDirection.normalize();
  425.         if (std::acos(actorDirection * targetDirection) < osg::DegreesToRadians(90.f)
  426.             && sqrDist <= sqrHeadTrackDistance
  427.             && MWBase::Environment::get().getWorld()->getLOS(actor, targetActor) // check LOS and awareness last as it's the most expensive function
  428.             && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(targetActor, actor))
  429.         {
  430.             sqrHeadTrackDistance = sqrDist;
  431.             headTrackTarget = targetActor;
  432.         }
  433.     }
  434.  
  435.     void Actors::playIdleDialogue(const MWWorld::Ptr& actor)
  436.     {
  437.         if (!actor.getClass().isActor() || actor == getPlayer() || MWBase::Environment::get().getSoundManager()->sayActive(actor))
  438.             return;
  439.  
  440.         const CreatureStats &stats = actor.getClass().getCreatureStats(actor);
  441.         if (stats.getAiSetting(CreatureStats::AI_Hello).getModified() == 0)
  442.             return;
  443.  
  444.         const MWMechanics::AiSequence& seq = stats.getAiSequence();
  445.         if (seq.isInCombat() || seq.hasPackage(AiPackage::TypeIdFollow) || seq.hasPackage(AiPackage::TypeIdEscort))
  446.             return;
  447.  
  448.         const osg::Vec3f playerPos(getPlayer().getRefData().getPosition().asVec3());
  449.         const osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3());
  450.         MWBase::World* world = MWBase::Environment::get().getWorld();
  451.         if (world->isSwimming(actor) || (playerPos - actorPos).length2() >= 3000 * 3000)
  452.             return;
  453.  
  454.         // Our implementation is not FPS-dependent unlike Morrowind's so it needs to be recalibrated.
  455.         // We chose to use the chance MW would have when run at 60 FPS with the default value of the GMST.
  456.         const float delta = MWBase::Environment::get().getFrameDuration() * 6.f;
  457.         static const float fVoiceIdleOdds = world->getStore().get<ESM::GameSetting>().find("fVoiceIdleOdds")->mValue.getFloat();
  458.         if (Misc::Rng::rollProbability() * 10000.f < fVoiceIdleOdds * delta && world->getLOS(getPlayer(), actor))
  459.             MWBase::Environment::get().getDialogueManager()->say(actor, "idle");
  460.     }
  461.  
  462.     void Actors::updateMovementSpeed(const MWWorld::Ptr& actor)
  463.     {
  464.         float previousSpeedFactor = actor.getClass().getMovementSettings(actor).mSpeedFactor;
  465.         float newSpeedFactor = 1.f;
  466.  
  467.         CreatureStats &stats = actor.getClass().getCreatureStats(actor);
  468.         MWMechanics::AiSequence& seq = stats.getAiSequence();
  469.  
  470.         if (!seq.isEmpty() && seq.getActivePackage()->useVariableSpeed())
  471.         {
  472.             osg::Vec3f targetPos = seq.getActivePackage()->getDestination();
  473.             osg::Vec3f actorPos = actor.getRefData().getPosition().asVec3();
  474.             float distance = (targetPos - actorPos).length();
  475.             if (distance < DECELERATE_DISTANCE)
  476.                 newSpeedFactor = std::max(0.7f, 0.1f * previousSpeedFactor * (distance/64.f + 2.f));
  477.         }
  478.  
  479.         actor.getClass().getMovementSettings(actor).mSpeedFactor = newSpeedFactor;
  480.     }
  481.  
  482.     void Actors::updateGreetingState(const MWWorld::Ptr& actor, bool turnOnly)
  483.     {
  484.         if (!actor.getClass().isActor() || actor == getPlayer())
  485.             return;
  486.  
  487.         CreatureStats &stats = actor.getClass().getCreatureStats(actor);
  488.         const MWMechanics::AiSequence& seq = stats.getAiSequence();
  489.         int packageId = seq.getTypeId();
  490.  
  491.         if (seq.isInCombat() ||
  492.             MWBase::Environment::get().getWorld()->isSwimming(actor) ||
  493.             (packageId != AiPackage::TypeIdWander && packageId != AiPackage::TypeIdTravel && packageId != -1))
  494.         {
  495.             stats.setTurningToPlayer(false);
  496.             stats.setGreetingTimer(0);
  497.             stats.setGreetingState(Greet_None);
  498.             return;
  499.         }
  500.  
  501.         MWWorld::Ptr player = getPlayer();
  502.         osg::Vec3f playerPos(player.getRefData().getPosition().asVec3());
  503.         osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3());
  504.         osg::Vec3f dir = playerPos - actorPos;
  505.  
  506.         if (stats.isTurningToPlayer())
  507.         {
  508.             // Reduce the turning animation glitch by using a *HUGE* value of
  509.             // epsilon...  TODO: a proper fix might be in either the physics or the
  510.             // animation subsystem
  511.             if (zTurn(actor, stats.getAngleToPlayer(), osg::DegreesToRadians(5.f)))
  512.             {
  513.                 stats.setTurningToPlayer(false);
  514.                 // An original engine launches an endless idle2 when an actor greets player.
  515.                 playAnimationGroup (actor, "idle2", 0, std::numeric_limits<int>::max(), false);
  516.             }
  517.         }
  518.  
  519.         if (turnOnly)
  520.             return;
  521.  
  522.         // Play a random voice greeting if the player gets too close
  523.         static int iGreetDistanceMultiplier = MWBase::Environment::get().getWorld()->getStore()
  524.             .get<ESM::GameSetting>().find("iGreetDistanceMultiplier")->mValue.getInteger();
  525.  
  526.         float helloDistance = static_cast<float>(stats.getAiSetting(CreatureStats::AI_Hello).getModified() * iGreetDistanceMultiplier);
  527.  
  528.         int greetingTimer = stats.getGreetingTimer();
  529.         GreetingState greetingState = stats.getGreetingState();
  530.         if (greetingState == Greet_None)
  531.         {
  532.             if ((playerPos - actorPos).length2() <= helloDistance*helloDistance &&
  533.                 !player.getClass().getCreatureStats(player).isDead() && !actor.getClass().getCreatureStats(actor).isParalyzed()
  534.                 && MWBase::Environment::get().getWorld()->getLOS(player, actor)
  535.                 && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, actor))
  536.                 greetingTimer++;
  537.  
  538.             if (greetingTimer >= GREETING_SHOULD_START)
  539.             {
  540.                 greetingState = Greet_InProgress;
  541.                 MWBase::Environment::get().getDialogueManager()->say(actor, "hello");
  542.                 greetingTimer = 0;
  543.             }
  544.         }
  545.  
  546.         if (greetingState == Greet_InProgress)
  547.         {
  548.             greetingTimer++;
  549.  
  550.             if (greetingTimer <= GREETING_SHOULD_END || MWBase::Environment::get().getSoundManager()->sayActive(actor))
  551.                 turnActorToFacePlayer(actor, dir);
  552.  
  553.             if (greetingTimer >= GREETING_COOLDOWN)
  554.             {
  555.                 greetingState = Greet_Done;
  556.                 greetingTimer = 0;
  557.             }
  558.         }
  559.  
  560.         if (greetingState == Greet_Done)
  561.         {
  562.             float resetDist = 2 * helloDistance;
  563.             if ((playerPos - actorPos).length2() >= resetDist*resetDist)
  564.                 greetingState = Greet_None;
  565.         }
  566.  
  567.         stats.setGreetingTimer(greetingTimer);
  568.         stats.setGreetingState(greetingState);
  569.     }
  570.  
  571.     void Actors::turnActorToFacePlayer(const MWWorld::Ptr& actor, const osg::Vec3f& dir)
  572.     {
  573.         actor.getClass().getMovementSettings(actor).mPosition[1] = 0;
  574.         actor.getClass().getMovementSettings(actor).mPosition[0] = 0;
  575.  
  576.         CreatureStats &stats = actor.getClass().getCreatureStats(actor);
  577.         if (!stats.isTurningToPlayer())
  578.         {
  579.             stats.setAngleToPlayer(std::atan2(dir.x(), dir.y()));
  580.             stats.setTurningToPlayer(true);
  581.         }
  582.     }
  583.  
  584.     void Actors::engageCombat (const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2, std::map<const MWWorld::Ptr, const std::set<MWWorld::Ptr> >& cachedAllies, bool againstPlayer)
  585.     {
  586.         // No combat for totally static creatures
  587.         if (!actor1.getClass().isMobile(actor1))
  588.             return;
  589.  
  590.         CreatureStats& creatureStats1 = actor1.getClass().getCreatureStats(actor1);
  591.         if (creatureStats1.isDead() || creatureStats1.getAiSequence().isInCombat(actor2))
  592.             return;
  593.  
  594.         const CreatureStats& creatureStats2 = actor2.getClass().getCreatureStats(actor2);
  595.         if (creatureStats2.isDead())
  596.             return;
  597.  
  598.         const osg::Vec3f actor1Pos(actor1.getRefData().getPosition().asVec3());
  599.         const osg::Vec3f actor2Pos(actor2.getRefData().getPosition().asVec3());
  600.         float sqrDist = (actor1Pos - actor2Pos).length2();
  601.  
  602.         if (sqrDist > mActorsProcessingRange*mActorsProcessingRange)
  603.             return;
  604.  
  605.         // If this is set to true, actor1 will start combat with actor2 if the awareness check at the end of the method returns true
  606.         bool aggressive = false;
  607.  
  608.         // Get actors allied with actor1. Includes those following or escorting actor1, actors following or escorting those actors, (recursive)
  609.         // and any actor currently being followed or escorted by actor1
  610.         std::set<MWWorld::Ptr> allies1;
  611.  
  612.         getActorsSidingWith(actor1, allies1, cachedAllies);
  613.  
  614.         // If an ally of actor1 has been attacked by actor2 or has attacked actor2, start combat between actor1 and actor2
  615.         for (const MWWorld::Ptr &ally : allies1)
  616.         {
  617.             if (creatureStats1.getAiSequence().isInCombat(ally))
  618.                 continue;
  619.  
  620.             if (creatureStats2.matchesActorId(ally.getClass().getCreatureStats(ally).getHitAttemptActorId()))
  621.             {
  622.                 MWBase::Environment::get().getMechanicsManager()->startCombat(actor1, actor2);
  623.                 // Also set the same hit attempt actor. Otherwise, if fighting the player, they may stop combat
  624.                 // if the player gets out of reach, while the ally would continue combat with the player
  625.                 creatureStats1.setHitAttemptActorId(ally.getClass().getCreatureStats(ally).getHitAttemptActorId());
  626.                 return;
  627.             }
  628.  
  629.             // If there's been no attack attempt yet but an ally of actor1 is in combat with actor2, become aggressive to actor2
  630.             if (ally.getClass().getCreatureStats(ally).getAiSequence().isInCombat(actor2))
  631.                 aggressive = true;
  632.         }
  633.  
  634.         std::set<MWWorld::Ptr> playerAllies;
  635.         getActorsSidingWith(MWMechanics::getPlayer(), playerAllies, cachedAllies);
  636.  
  637.         bool isPlayerFollowerOrEscorter = playerAllies.find(actor1) != playerAllies.end();
  638.  
  639.         // If actor2 and at least one actor2 are in combat with actor1, actor1 and its allies start combat with them
  640.         // Doesn't apply for player followers/escorters        
  641.         if (!aggressive && !isPlayerFollowerOrEscorter)
  642.         {
  643.             // Check that actor2 is in combat with actor1
  644.             if (actor2.getClass().getCreatureStats(actor2).getAiSequence().isInCombat(actor1))
  645.             {
  646.                 std::set<MWWorld::Ptr> allies2;
  647.  
  648.                 getActorsSidingWith(actor2, allies2, cachedAllies);
  649.  
  650.                 // Check that an ally of actor2 is also in combat with actor1
  651.                 for (const MWWorld::Ptr &ally2 : allies2)
  652.                 {
  653.                     if (ally2.getClass().getCreatureStats(ally2).getAiSequence().isInCombat(actor1))
  654.                     {
  655.                         MWBase::Environment::get().getMechanicsManager()->startCombat(actor1, actor2);
  656.                         // Also have actor1's allies start combat
  657.                         for (const MWWorld::Ptr& ally1 : allies1)
  658.                             MWBase::Environment::get().getMechanicsManager()->startCombat(ally1, actor2);
  659.                         return;
  660.                     }
  661.                 }
  662.             }
  663.         }
  664.  
  665.         // Stop here if target is unreachable
  666.         if (!canFight(actor1, actor2))
  667.             return;
  668.  
  669.         // If set in the settings file, player followers and escorters will become aggressive toward enemies in combat with them or the player
  670.         static const bool followersAttackOnSight = Settings::Manager::getBool("followers attack on sight", "Game");
  671.         if (!aggressive && isPlayerFollowerOrEscorter && followersAttackOnSight)
  672.         {
  673.             if (actor2.getClass().getCreatureStats(actor2).getAiSequence().isInCombat(actor1))
  674.                 aggressive = true;
  675.             else
  676.             {
  677.                 for (const MWWorld::Ptr &ally : allies1)
  678.                 {
  679.                     if (actor2.getClass().getCreatureStats(actor2).getAiSequence().isInCombat(ally))
  680.                     {
  681.                         aggressive = true;
  682.                         break;
  683.                     }
  684.                 }
  685.             }
  686.         }
  687.  
  688.         // Do aggression check if actor2 is the player or a player follower or escorter
  689.         if (!aggressive)
  690.         {
  691.             if (againstPlayer || playerAllies.find(actor2) != playerAllies.end())
  692.             {
  693.                 // Player followers and escorters with high fight should not initiate combat with the player or with
  694.                 // other player followers or escorters
  695.                 if (!isPlayerFollowerOrEscorter)
  696.                     aggressive = MWBase::Environment::get().getMechanicsManager()->isAggressive(actor1, actor2);
  697.             }
  698.         }
  699.  
  700.         // Make guards go aggressive with creatures that are in combat, unless the creature is a follower or escorter
  701.         if (!aggressive && actor1.getClass().isClass(actor1, "Guard") && !actor2.getClass().isNpc() && creatureStats2.getAiSequence().isInCombat())
  702.         {
  703.             // Check if the creature is too far
  704.             static const float fAlarmRadius = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fAlarmRadius")->mValue.getFloat();
  705.             if (sqrDist > fAlarmRadius * fAlarmRadius)
  706.                 return;
  707.  
  708.             bool followerOrEscorter = false;
  709.             for (const AiPackage* package : creatureStats2.getAiSequence())
  710.             {
  711.                 // The follow package must be first or have nothing but combat before it
  712.                 if (package->sideWithTarget())
  713.                 {
  714.                     followerOrEscorter = true;
  715.                     break;
  716.                 }
  717.                 else if (package->getTypeId() != MWMechanics::AiPackage::TypeIdCombat)
  718.                     break;
  719.             }
  720.             if (!followerOrEscorter)
  721.                 aggressive = true;
  722.         }
  723.  
  724.         // If any of the above conditions turned actor1 aggressive towards actor2, do an awareness check. If it passes, start combat with actor2.
  725.         if (aggressive)
  726.         {
  727.             bool LOS = MWBase::Environment::get().getWorld()->getLOS(actor1, actor2)
  728.                     && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(actor2, actor1);
  729.  
  730.             if (LOS)
  731.                 MWBase::Environment::get().getMechanicsManager()->startCombat(actor1, actor2);
  732.         }
  733.     }
  734.  
  735.     void Actors::adjustMagicEffects (const MWWorld::Ptr& creature)
  736.     {
  737.         CreatureStats& creatureStats =  creature.getClass().getCreatureStats (creature);
  738.         if (creatureStats.isDeathAnimationFinished())
  739.             return;
  740.  
  741.         MagicEffects now = creatureStats.getSpells().getMagicEffects();
  742.  
  743.         if (creature.getClass().hasInventoryStore(creature))
  744.         {
  745.             MWWorld::InventoryStore& store = creature.getClass().getInventoryStore (creature);
  746.             now += store.getMagicEffects();
  747.         }
  748.  
  749.         now += creatureStats.getActiveSpells().getMagicEffects();
  750.  
  751.         creatureStats.modifyMagicEffects(now);
  752.     }
  753.  
  754.     void Actors::calculateDynamicStats (const MWWorld::Ptr& ptr)
  755.     {
  756.         CreatureStats& creatureStats = ptr.getClass().getCreatureStats (ptr);
  757.  
  758.         int intelligence = creatureStats.getAttribute(ESM::Attribute::Intelligence).getModified();
  759.  
  760.         float base = 1.f;
  761.         if (ptr == getPlayer())
  762.             base = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fPCbaseMagickaMult")->mValue.getFloat();
  763.         else
  764.             base = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fNPCbaseMagickaMult")->mValue.getFloat();
  765.  
  766.         double magickaFactor = base +
  767.             creatureStats.getMagicEffects().get (EffectKey (ESM::MagicEffect::FortifyMaximumMagicka)).getMagnitude() * 0.1;
  768.  
  769.         DynamicStat<float> magicka = creatureStats.getMagicka();
  770.         float diff = (static_cast<int>(magickaFactor*intelligence)) - magicka.getBase();
  771.         float currentToBaseRatio = (magicka.getCurrent() / magicka.getBase());
  772.         magicka.setModified(magicka.getModified() + diff, 0);
  773.         magicka.setCurrent(magicka.getBase() * currentToBaseRatio, false, true);
  774.         creatureStats.setMagicka(magicka);
  775.     }
  776.  
  777.     void Actors::restoreDynamicStats (const MWWorld::Ptr& ptr, double hours, bool sleep)
  778.     {
  779.         MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr);
  780.         if (stats.isDead())
  781.             return;
  782.  
  783.         const MWWorld::Store<ESM::GameSetting>& settings = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
  784.  
  785.         if (sleep)
  786.         {
  787.             float health, magicka;
  788.             getRestorationPerHourOfSleep(ptr, health, magicka);
  789.  
  790.             DynamicStat<float> stat = stats.getHealth();
  791.             stat.setCurrent(stat.getCurrent() + health * hours);
  792.             stats.setHealth(stat);
  793.  
  794.             double restoreHours = hours;
  795.             bool stunted = stats.getMagicEffects ().get(ESM::MagicEffect::StuntedMagicka).getMagnitude() > 0;
  796.             if (stunted)
  797.             {
  798.                 // Stunted Magicka effect should be taken into account.
  799.                 GetStuntedMagickaDuration visitor(ptr);
  800.                 stats.getActiveSpells().visitEffectSources(visitor);
  801.                 stats.getSpells().visitEffectSources(visitor);
  802.                 if (ptr.getClass().hasInventoryStore(ptr))
  803.                     ptr.getClass().getInventoryStore(ptr).visitEffectSources(visitor);
  804.  
  805.                 // Take a maximum remaining duration of Stunted Magicka effects (-1 is a constant one) in game hours.
  806.                 if (visitor.mRemainingTime > 0)
  807.                 {
  808.                     double timeScale = MWBase::Environment::get().getWorld()->getTimeScaleFactor();
  809.                     restoreHours = std::max(0.0, hours - visitor.mRemainingTime * timeScale / 3600.f);
  810.                 }
  811.                 else if (visitor.mRemainingTime == -1)
  812.                     restoreHours = 0;
  813.             }
  814.  
  815.             if (restoreHours > 0)
  816.             {
  817.                 stat = stats.getMagicka();
  818.                 stat.setCurrent(stat.getCurrent() + magicka * restoreHours);
  819.                 stats.setMagicka(stat);
  820.             }
  821.         }
  822.  
  823.         // Current fatigue can be above base value due to a fortify effect.
  824.         // In that case stop here and don't try to restore.
  825.         DynamicStat<float> fatigue = stats.getFatigue();
  826.         if (fatigue.getCurrent() >= fatigue.getBase())
  827.             return;
  828.  
  829.         // Restore fatigue
  830.         float fFatigueReturnBase = settings.find("fFatigueReturnBase")->mValue.getFloat ();
  831.         float fFatigueReturnMult = settings.find("fFatigueReturnMult")->mValue.getFloat ();
  832.         float fEndFatigueMult = settings.find("fEndFatigueMult")->mValue.getFloat ();
  833.  
  834.         int endurance = stats.getAttribute (ESM::Attribute::Endurance).getModified ();
  835.  
  836.         float normalizedEncumbrance = ptr.getClass().getNormalizedEncumbrance(ptr);
  837.         if (normalizedEncumbrance > 1)
  838.             normalizedEncumbrance = 1;
  839.  
  840.         float x = fFatigueReturnBase + fFatigueReturnMult * (1 - normalizedEncumbrance);
  841.         x *= fEndFatigueMult * endurance;
  842.  
  843.         fatigue.setCurrent (fatigue.getCurrent() + 3600 * x * hours);
  844.         stats.setFatigue (fatigue);
  845.     }
  846.  
  847.     void Actors::calculateRestoration (const MWWorld::Ptr& ptr, float duration)
  848.     {
  849.         if (ptr.getClass().getCreatureStats(ptr).isDead())
  850.             return;
  851.  
  852.         MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr);
  853.  
  854.         // Current fatigue can be above base value due to a fortify effect.
  855.         // In that case stop here and don't try to restore.
  856.         DynamicStat<float> fatigue = stats.getFatigue();
  857.         if (fatigue.getCurrent() >= fatigue.getBase())
  858.             return;
  859.  
  860.         // Restore fatigue
  861.         int endurance = stats.getAttribute(ESM::Attribute::Endurance).getModified();
  862.         const MWWorld::Store<ESM::GameSetting>& settings = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
  863.         static const float fFatigueReturnBase = settings.find("fFatigueReturnBase")->mValue.getFloat ();
  864.         static const float fFatigueReturnMult = settings.find("fFatigueReturnMult")->mValue.getFloat ();
  865.  
  866.         float x = fFatigueReturnBase + fFatigueReturnMult * endurance;
  867.  
  868.         fatigue.setCurrent (fatigue.getCurrent() + duration * x);
  869.         stats.setFatigue (fatigue);
  870.     }
  871.  
  872.     class ExpiryVisitor : public EffectSourceVisitor
  873.     {
  874.         private:
  875.             MWWorld::Ptr mActor;
  876.             float mDuration;
  877.  
  878.         public:
  879.             ExpiryVisitor(const MWWorld::Ptr& actor, float duration)
  880.                 : mActor(actor), mDuration(duration)
  881.             {
  882.             }
  883.  
  884.             virtual void visit (MWMechanics::EffectKey key,
  885.                                 const std::string& /*sourceName*/, const std::string& /*sourceId*/, int /*casterActorId*/,
  886.                                 float magnitude, float remainingTime = -1, float /*totalTime*/ = -1)
  887.             {
  888.                 if (magnitude > 0 && remainingTime > 0 && remainingTime < mDuration)
  889.                 {
  890.                     CreatureStats& creatureStats = mActor.getClass().getCreatureStats(mActor);
  891.                     if (effectTick(creatureStats, mActor, key, magnitude * remainingTime))
  892.                         creatureStats.getMagicEffects().add(key, -magnitude);
  893.                 }
  894.             }
  895.     };
  896.  
  897.     void Actors::calculateCreatureStatModifiers (const MWWorld::Ptr& ptr, float duration)
  898.     {
  899.         CreatureStats &creatureStats = ptr.getClass().getCreatureStats(ptr);
  900.         const MagicEffects &effects = creatureStats.getMagicEffects();
  901.  
  902.         bool wasDead = creatureStats.isDead();
  903.  
  904.         if (duration > 0)
  905.         {
  906.             // Apply correct magnitude for tickable effects that have just expired,
  907.             // in case duration > remaining time of effect.
  908.             // One case where this will happen is when the player uses the rest/wait command
  909.             // while there is a tickable effect active that should expire before the end of the rest/wait.
  910.             ExpiryVisitor visitor(ptr, duration);
  911.             creatureStats.getActiveSpells().visitEffectSources(visitor);
  912.  
  913.             for (MagicEffects::Collection::const_iterator it = effects.begin(); it != effects.end(); ++it)
  914.             {
  915.                 // tickable effects (i.e. effects having a lasting impact after expiry)
  916.                 effectTick(creatureStats, ptr, it->first, it->second.getMagnitude() * duration);
  917.  
  918.                 // instant effects are already applied on spell impact in spellcasting.cpp, but may also come from permanent abilities
  919.                 if (it->second.getMagnitude() > 0)
  920.                 {
  921.                     CastSpell cast(ptr, ptr);
  922.                     if (cast.applyInstantEffect(ptr, ptr, it->first, it->second.getMagnitude()))
  923.                     {
  924.                         creatureStats.getSpells().purgeEffect(it->first.mId);
  925.                         creatureStats.getActiveSpells().purgeEffect(it->first.mId);
  926.                         if (ptr.getClass().hasInventoryStore(ptr))
  927.                             ptr.getClass().getInventoryStore(ptr).purgeEffect(it->first.mId);
  928.                     }
  929.                 }
  930.             }
  931.         }
  932.  
  933.         // purge levitate effect if levitation is disabled
  934.         // check only modifier, because base value can be setted from SetFlying console command.
  935.         if (MWBase::Environment::get().getWorld()->isLevitationEnabled() == false && effects.get(ESM::MagicEffect::Levitate).getModifier() > 0)
  936.         {
  937.             creatureStats.getSpells().purgeEffect(ESM::MagicEffect::Levitate);
  938.             creatureStats.getActiveSpells().purgeEffect(ESM::MagicEffect::Levitate);
  939.             if (ptr.getClass().hasInventoryStore(ptr))
  940.                 ptr.getClass().getInventoryStore(ptr).purgeEffect(ESM::MagicEffect::Levitate);
  941.  
  942.             if (ptr == getPlayer())
  943.             {
  944.                 MWBase::Environment::get().getWindowManager()->messageBox ("#{sLevitateDisabled}");
  945.             }
  946.         }
  947.  
  948.         // dynamic stats
  949.         for (int i = 0; i < 3; ++i)
  950.         {
  951.             DynamicStat<float> stat = creatureStats.getDynamic(i);
  952.             stat.setCurrentModifier(effects.get(ESM::MagicEffect::FortifyHealth + i).getMagnitude() -
  953.                 effects.get(ESM::MagicEffect::DrainHealth + i).getMagnitude(),
  954.                 // Magicka can be decreased below zero due to a fortify effect wearing off
  955.                 // Fatigue can be decreased below zero meaning the actor will be knocked out
  956.                 i == 1 || i == 2);
  957.  
  958.             creatureStats.setDynamic(i, stat);
  959.         }
  960.  
  961.         // attributes
  962.         for(int i = 0;i < ESM::Attribute::Length;++i)
  963.         {
  964.             AttributeValue stat = creatureStats.getAttribute(i);
  965.             stat.setModifier(static_cast<int>(effects.get(EffectKey(ESM::MagicEffect::FortifyAttribute, i)).getMagnitude() -
  966.                              effects.get(EffectKey(ESM::MagicEffect::DrainAttribute, i)).getMagnitude() -
  967.                              effects.get(EffectKey(ESM::MagicEffect::AbsorbAttribute, i)).getMagnitude()));
  968.  
  969.             creatureStats.setAttribute(i, stat);
  970.         }
  971.  
  972.         if (creatureStats.needToRecalcDynamicStats())
  973.             calculateDynamicStats(ptr);
  974.  
  975.         {
  976.             Spells & spells = creatureStats.getSpells();
  977.             for (Spells::TIterator it = spells.begin(); it != spells.end(); ++it)
  978.             {
  979.                 if (spells.getCorprusSpells().find(it->first) != spells.getCorprusSpells().end())
  980.                 {
  981.                     if (MWBase::Environment::get().getWorld()->getTimeStamp() >= spells.getCorprusSpells().at(it->first).mNextWorsening)
  982.                     {
  983.                         spells.worsenCorprus(it->first);
  984.  
  985.                         if (ptr == getPlayer())
  986.                             MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicCorprusWorsens}");
  987.                     }
  988.                 }
  989.             }
  990.         }
  991.  
  992.         // AI setting modifiers
  993.         int creature = !ptr.getClass().isNpc();
  994.         if (creature && ptr.get<ESM::Creature>()->mBase->mData.mType == ESM::Creature::Humanoid)
  995.             creature = false;
  996.         // Note: the Creature variants only work on normal creatures, not on daedra or undead creatures.
  997.         if (!creature || ptr.get<ESM::Creature>()->mBase->mData.mType == ESM::Creature::Creatures)
  998.         {
  999.             Stat<int> stat = creatureStats.getAiSetting(CreatureStats::AI_Fight);
  1000.             stat.setModifier(static_cast<int>(effects.get(ESM::MagicEffect::FrenzyHumanoid + creature).getMagnitude()
  1001.                 - effects.get(ESM::MagicEffect::CalmHumanoid+creature).getMagnitude()));
  1002.             creatureStats.setAiSetting(CreatureStats::AI_Fight, stat);
  1003.  
  1004.             stat = creatureStats.getAiSetting(CreatureStats::AI_Flee);
  1005.             stat.setModifier(static_cast<int>(effects.get(ESM::MagicEffect::DemoralizeHumanoid + creature).getMagnitude()
  1006.                 - effects.get(ESM::MagicEffect::RallyHumanoid+creature).getMagnitude()));
  1007.             creatureStats.setAiSetting(CreatureStats::AI_Flee, stat);
  1008.         }
  1009.         if (creature && ptr.get<ESM::Creature>()->mBase->mData.mType == ESM::Creature::Undead)
  1010.         {
  1011.             Stat<int> stat = creatureStats.getAiSetting(CreatureStats::AI_Flee);
  1012.             stat.setModifier(static_cast<int>(effects.get(ESM::MagicEffect::TurnUndead).getMagnitude()));
  1013.             creatureStats.setAiSetting(CreatureStats::AI_Flee, stat);
  1014.         }
  1015.  
  1016.         if (!wasDead && creatureStats.isDead())
  1017.         {
  1018.             // The actor was killed by a magic effect. Figure out if the player was responsible for it.
  1019.             const ActiveSpells& spells = creatureStats.getActiveSpells();
  1020.             MWWorld::Ptr player = getPlayer();
  1021.             std::set<MWWorld::Ptr> playerFollowers;
  1022.             getActorsSidingWith(player, playerFollowers);
  1023.  
  1024.             for (ActiveSpells::TIterator it = spells.begin(); it != spells.end(); ++it)
  1025.             {
  1026.                 bool actorKilled = false;
  1027.  
  1028.                 const ActiveSpells::ActiveSpellParams& spell = it->second;
  1029.                 MWWorld::Ptr caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(spell.mCasterActorId);
  1030.                 for (std::vector<ActiveSpells::ActiveEffect>::const_iterator effectIt = spell.mEffects.begin();
  1031.                      effectIt != spell.mEffects.end(); ++effectIt)
  1032.                 {
  1033.                     int effectId = effectIt->mEffectId;
  1034.                     bool isDamageEffect = false;
  1035.  
  1036.                     int damageEffects[] = {
  1037.                         ESM::MagicEffect::FireDamage, ESM::MagicEffect::ShockDamage, ESM::MagicEffect::FrostDamage, ESM::MagicEffect::Poison,
  1038.                         ESM::MagicEffect::SunDamage, ESM::MagicEffect::DamageHealth, ESM::MagicEffect::AbsorbHealth
  1039.                     };
  1040.  
  1041.                     for (unsigned int i=0; i<sizeof(damageEffects)/sizeof(int); ++i)
  1042.                     {
  1043.                         if (damageEffects[i] == effectId)
  1044.                             isDamageEffect = true;
  1045.                     }
  1046.  
  1047.                     if (isDamageEffect)
  1048.                     {
  1049.                         if (caster == player || playerFollowers.find(caster) != playerFollowers.end())
  1050.                         {
  1051.                             if (caster.getClass().isNpc() && caster.getClass().getNpcStats(caster).isWerewolf())
  1052.                                 caster.getClass().getNpcStats(caster).addWerewolfKill();
  1053.  
  1054.                             MWBase::Environment::get().getMechanicsManager()->actorKilled(ptr, player);
  1055.                             actorKilled = true;
  1056.                             break;
  1057.                         }
  1058.                     }
  1059.                 }
  1060.  
  1061.                 if (actorKilled)
  1062.                     break;
  1063.             }
  1064.         }
  1065.  
  1066.         // TODO: dirty flag for magic effects to avoid some unnecessary work below?
  1067.  
  1068.         // any value of calm > 0 will stop the actor from fighting
  1069.         if ((effects.get(ESM::MagicEffect::CalmHumanoid).getMagnitude() > 0 && ptr.getClass().isNpc())
  1070.             || (effects.get(ESM::MagicEffect::CalmCreature).getMagnitude() > 0 && !ptr.getClass().isNpc()))
  1071.             creatureStats.getAiSequence().stopCombat();
  1072.  
  1073.         // Update bound effects
  1074.         // Note: in vanilla MW multiple bound items of the same type can be created by different spells.
  1075.         // As these extra copies are kinda useless this may or may not be important.
  1076.         static std::map<int, std::string> boundItemsMap;
  1077.         if (boundItemsMap.empty())
  1078.         {
  1079.             boundItemsMap[ESM::MagicEffect::BoundBattleAxe] = "sMagicBoundBattleAxeID";
  1080.             boundItemsMap[ESM::MagicEffect::BoundBoots] = "sMagicBoundBootsID";
  1081.             boundItemsMap[ESM::MagicEffect::BoundCuirass] = "sMagicBoundCuirassID";
  1082.             boundItemsMap[ESM::MagicEffect::BoundDagger] = "sMagicBoundDaggerID";
  1083.             boundItemsMap[ESM::MagicEffect::BoundGloves] = "sMagicBoundLeftGauntletID"; // Note: needs RightGauntlet variant too (see below)
  1084.             boundItemsMap[ESM::MagicEffect::BoundHelm] = "sMagicBoundHelmID";
  1085.             boundItemsMap[ESM::MagicEffect::BoundLongbow] = "sMagicBoundLongbowID";
  1086.             boundItemsMap[ESM::MagicEffect::BoundLongsword] = "sMagicBoundLongswordID";
  1087.             boundItemsMap[ESM::MagicEffect::BoundMace] = "sMagicBoundMaceID";
  1088.             boundItemsMap[ESM::MagicEffect::BoundShield] = "sMagicBoundShieldID";
  1089.             boundItemsMap[ESM::MagicEffect::BoundSpear] = "sMagicBoundSpearID";
  1090.         }
  1091.  
  1092.         for (std::map<int, std::string>::iterator it = boundItemsMap.begin(); it != boundItemsMap.end(); ++it)
  1093.         {
  1094.             bool found = creatureStats.mBoundItems.find(it->first) != creatureStats.mBoundItems.end();
  1095.             float magnitude = effects.get(it->first).getMagnitude();
  1096.             if (found != (magnitude > 0))
  1097.             {
  1098.                 if (magnitude > 0)
  1099.                     creatureStats.mBoundItems.insert(it->first);
  1100.                 else
  1101.                     creatureStats.mBoundItems.erase(it->first);
  1102.  
  1103.                 std::string itemGmst = it->second;
  1104.                 std::string item = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
  1105.                             itemGmst)->mValue.getString();
  1106.  
  1107.                 magnitude > 0 ? addBoundItem(item, ptr) : removeBoundItem(item, ptr);
  1108.  
  1109.                 if (it->first == ESM::MagicEffect::BoundGloves)
  1110.                 {
  1111.                     item = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
  1112.                                 "sMagicBoundRightGauntletID")->mValue.getString();
  1113.                     magnitude > 0 ? addBoundItem(item, ptr) : removeBoundItem(item, ptr);
  1114.                 }
  1115.             }
  1116.         }
  1117.  
  1118.         bool hasSummonEffect = false;
  1119.         for (MagicEffects::Collection::const_iterator it = effects.begin(); it != effects.end(); ++it)
  1120.         {
  1121.             if (isSummoningEffect(it->first.mId))
  1122.             {
  1123.                 hasSummonEffect = true;
  1124.                 break;
  1125.             }
  1126.         }
  1127.  
  1128.         if (!creatureStats.getSummonedCreatureMap().empty() || !creatureStats.getSummonedCreatureGraveyard().empty() || hasSummonEffect)
  1129.         {
  1130.             UpdateSummonedCreatures updateSummonedCreatures(ptr);
  1131.             creatureStats.getActiveSpells().visitEffectSources(updateSummonedCreatures);
  1132.             if (ptr.getClass().hasInventoryStore(ptr))
  1133.                 ptr.getClass().getInventoryStore(ptr).visitEffectSources(updateSummonedCreatures);
  1134.             updateSummonedCreatures.process(mTimerDisposeSummonsCorpses == 0.f);
  1135.         }
  1136.     }
  1137.  
  1138.     void Actors::calculateNpcStatModifiers (const MWWorld::Ptr& ptr, float duration)
  1139.     {
  1140.         NpcStats &npcStats = ptr.getClass().getNpcStats(ptr);
  1141.         const MagicEffects &effects = npcStats.getMagicEffects();
  1142.  
  1143.         // skills
  1144.         for(int i = 0;i < ESM::Skill::Length;++i)
  1145.         {
  1146.             SkillValue& skill = npcStats.getSkill(i);
  1147.             skill.setModifier(static_cast<int>(effects.get(EffectKey(ESM::MagicEffect::FortifySkill, i)).getMagnitude() -
  1148.                              effects.get(EffectKey(ESM::MagicEffect::DrainSkill, i)).getMagnitude() -
  1149.                              effects.get(EffectKey(ESM::MagicEffect::AbsorbSkill, i)).getMagnitude()));
  1150.         }
  1151.     }
  1152.  
  1153.     bool Actors::isAttackPreparing(const MWWorld::Ptr& ptr)
  1154.     {
  1155.         PtrActorMap::iterator it = mActors.find(ptr);
  1156.         if (it == mActors.end())
  1157.             return false;
  1158.         CharacterController* ctrl = it->second->getCharacterController();
  1159.  
  1160.         return ctrl->isAttackPreparing();
  1161.     }
  1162.  
  1163.     bool Actors::isRunning(const MWWorld::Ptr& ptr)
  1164.     {
  1165.         PtrActorMap::iterator it = mActors.find(ptr);
  1166.         if (it == mActors.end())
  1167.             return false;
  1168.         CharacterController* ctrl = it->second->getCharacterController();
  1169.  
  1170.         return ctrl->isRunning();
  1171.     }
  1172.  
  1173.     bool Actors::isSneaking(const MWWorld::Ptr& ptr)
  1174.     {
  1175.         PtrActorMap::iterator it = mActors.find(ptr);
  1176.         if (it == mActors.end())
  1177.             return false;
  1178.         CharacterController* ctrl = it->second->getCharacterController();
  1179.  
  1180.         return ctrl->isSneaking();
  1181.     }
  1182.  
  1183.     void Actors::updateDrowning(const MWWorld::Ptr& ptr, float duration, bool isKnockedOut, bool isPlayer)
  1184.     {
  1185.         NpcStats &stats = ptr.getClass().getNpcStats(ptr);
  1186.  
  1187.         // When npc stats are just initialized, mTimeToStartDrowning == -1 and we should get value from GMST
  1188.         static const float fHoldBreathTime = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fHoldBreathTime")->mValue.getFloat();
  1189.         if (stats.getTimeToStartDrowning() == -1.f)
  1190.             stats.setTimeToStartDrowning(fHoldBreathTime);
  1191.  
  1192.         if (!isPlayer && stats.getTimeToStartDrowning() < fHoldBreathTime / 2)
  1193.         {
  1194.             AiSequence& seq = ptr.getClass().getCreatureStats(ptr).getAiSequence();
  1195.             if (seq.getTypeId() != AiPackage::TypeIdBreathe) //Only add it once
  1196.                 seq.stack(AiBreathe(), ptr);
  1197.         }
  1198.  
  1199.         MWBase::World *world = MWBase::Environment::get().getWorld();
  1200.         bool knockedOutUnderwater = (isKnockedOut && world->isUnderwater(ptr.getCell(), osg::Vec3f(ptr.getRefData().getPosition().asVec3())));
  1201.         if((world->isSubmerged(ptr) || knockedOutUnderwater)
  1202.            && stats.getMagicEffects().get(ESM::MagicEffect::WaterBreathing).getMagnitude() == 0)
  1203.         {
  1204.             float timeLeft = 0.0f;
  1205.             if(knockedOutUnderwater)
  1206.                 stats.setTimeToStartDrowning(0);
  1207.             else
  1208.             {
  1209.                 timeLeft = stats.getTimeToStartDrowning() - duration;
  1210.                 if(timeLeft < 0.0f)
  1211.                     timeLeft = 0.0f;
  1212.                 stats.setTimeToStartDrowning(timeLeft);
  1213.             }
  1214.  
  1215.             bool godmode = isPlayer && MWBase::Environment::get().getWorld()->getGodModeState();
  1216.  
  1217.             if(timeLeft == 0.0f && !godmode)
  1218.             {
  1219.                 // If drowning, apply 3 points of damage per second
  1220.                 static const float fSuffocationDamage = world->getStore().get<ESM::GameSetting>().find("fSuffocationDamage")->mValue.getFloat();
  1221.                 DynamicStat<float> health = stats.getHealth();
  1222.                 health.setCurrent(health.getCurrent() - fSuffocationDamage*duration);
  1223.                 stats.setHealth(health);
  1224.  
  1225.                 // Play a drowning sound
  1226.                 MWBase::SoundManager *sndmgr = MWBase::Environment::get().getSoundManager();
  1227.                 if(!sndmgr->getSoundPlaying(ptr, "drown"))
  1228.                     sndmgr->playSound3D(ptr, "drown", 1.0f, 1.0f);
  1229.  
  1230.                 if(isPlayer)
  1231.                     MWBase::Environment::get().getWindowManager()->activateHitOverlay(false);
  1232.             }
  1233.         }
  1234.         else
  1235.             stats.setTimeToStartDrowning(fHoldBreathTime);
  1236.     }
  1237.  
  1238.     void Actors::updateEquippedLight (const MWWorld::Ptr& ptr, float duration, bool mayEquip)
  1239.     {
  1240.         bool isPlayer = (ptr == getPlayer());
  1241.  
  1242.         MWWorld::InventoryStore &inventoryStore = ptr.getClass().getInventoryStore(ptr);
  1243.         MWWorld::ContainerStoreIterator heldIter =
  1244.                 inventoryStore.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
  1245.         /**
  1246.          * Automatically equip NPCs torches at night and unequip them at day
  1247.          */
  1248.         if (!isPlayer)
  1249.         {
  1250.             MWWorld::ContainerStoreIterator torch = inventoryStore.end();
  1251.             for (MWWorld::ContainerStoreIterator it = inventoryStore.begin(); it != inventoryStore.end(); ++it)
  1252.             {
  1253.                 if (it->getTypeName() == typeid(ESM::Light).name() &&
  1254.                     it->getClass().canBeEquipped(*it, ptr).first)
  1255.                 {
  1256.                     torch = it;
  1257.                     break;
  1258.                 }
  1259.             }
  1260.  
  1261.             if (mayEquip)
  1262.             {
  1263.                 if (torch != inventoryStore.end())
  1264.                 {
  1265.                     if (!ptr.getClass().getCreatureStats (ptr).getAiSequence().isInCombat())
  1266.                     {
  1267.                         // For non-hostile NPCs, unequip whatever is in the left slot in favor of a light.
  1268.                         if (heldIter != inventoryStore.end() && heldIter->getTypeName() != typeid(ESM::Light).name())
  1269.                             inventoryStore.unequipItem(*heldIter, ptr);
  1270.                     }
  1271.                     else if (heldIter == inventoryStore.end() || heldIter->getTypeName() == typeid(ESM::Light).name())
  1272.                     {
  1273.                         // For hostile NPCs, see if they have anything better to equip first
  1274.                         inventoryStore.autoEquip(ptr);
  1275.                     }
  1276.  
  1277.                     heldIter = inventoryStore.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
  1278.  
  1279.                     // If we have a torch and can equip it, then equip it now.
  1280.                     if (heldIter == inventoryStore.end())
  1281.                     {
  1282.                         inventoryStore.equip(MWWorld::InventoryStore::Slot_CarriedLeft, torch, ptr);
  1283.                     }
  1284.                 }
  1285.             }
  1286.             else
  1287.             {
  1288.                 if (heldIter != inventoryStore.end() && heldIter->getTypeName() == typeid(ESM::Light).name())
  1289.                 {
  1290.                     // At day, unequip lights and auto equip shields or other suitable items
  1291.                     // (Note: autoEquip will ignore lights)
  1292.                     inventoryStore.autoEquip(ptr);
  1293.                 }
  1294.             }
  1295.         }
  1296.  
  1297.         heldIter = inventoryStore.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
  1298.  
  1299.         //If holding a light...
  1300.         if(heldIter.getType() == MWWorld::ContainerStore::Type_Light)
  1301.         {
  1302.             // Use time from the player's light
  1303.             if(isPlayer)
  1304.             {
  1305.                 float timeRemaining = heldIter->getClass().getRemainingUsageTime(*heldIter);
  1306.  
  1307.                 // -1 is infinite light source. Other negative values are treated as 0.
  1308.                 if(timeRemaining != -1.0f)
  1309.                 {
  1310.                     timeRemaining -= duration;
  1311.  
  1312.                     if(timeRemaining > 0.0f)
  1313.                         heldIter->getClass().setRemainingUsageTime(*heldIter, timeRemaining);
  1314.                     else
  1315.                     {
  1316.                         inventoryStore.remove(*heldIter, 1, ptr); // remove it
  1317.                         return;
  1318.                     }
  1319.                 }
  1320.             }
  1321.  
  1322.             // Both NPC and player lights extinguish in water.
  1323.             if(MWBase::Environment::get().getWorld()->isSwimming(ptr))
  1324.             {
  1325.                 inventoryStore.remove(*heldIter, 1, ptr); // remove it
  1326.  
  1327.                 // ...But, only the player makes a sound.
  1328.                 if(isPlayer)
  1329.                     MWBase::Environment::get().getSoundManager()->playSound("torch out",
  1330.                             1.0, 1.0, MWSound::Type::Sfx, MWSound::PlayMode::NoEnv);
  1331.             }
  1332.         }
  1333.     }
  1334.  
  1335.     void Actors::updateCrimePursuit(const MWWorld::Ptr& ptr, float duration)
  1336.     {
  1337.         MWWorld::Ptr player = getPlayer();
  1338.         if (ptr != player && ptr.getClass().isNpc())
  1339.         {
  1340.             // get stats of witness
  1341.             CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr);
  1342.             NpcStats& npcStats = ptr.getClass().getNpcStats(ptr);
  1343.  
  1344.             if (player.getClass().getNpcStats(player).isWerewolf())
  1345.                 return;
  1346.  
  1347.             if (ptr.getClass().isClass(ptr, "Guard") && creatureStats.getAiSequence().getTypeId() != AiPackage::TypeIdPursue && !creatureStats.getAiSequence().isInCombat()
  1348.                 && creatureStats.getMagicEffects().get(ESM::MagicEffect::CalmHumanoid).getMagnitude() == 0)
  1349.             {
  1350.                 const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore();
  1351.                 static const int cutoff = esmStore.get<ESM::GameSetting>().find("iCrimeThreshold")->mValue.getInteger();
  1352.                 // Force dialogue on sight if bounty is greater than the cutoff
  1353.                 // In vanilla morrowind, the greeting dialogue is scripted to either arrest the player (< 5000 bounty) or attack (>= 5000 bounty)
  1354.                 if (   player.getClass().getNpcStats(player).getBounty() >= cutoff
  1355.                        // TODO: do not run these two every frame. keep an Aware state for each actor and update it every 0.2 s or so?
  1356.                     && MWBase::Environment::get().getWorld()->getLOS(ptr, player)
  1357.                     && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, ptr))
  1358.                 {
  1359.                     static const int iCrimeThresholdMultiplier = esmStore.get<ESM::GameSetting>().find("iCrimeThresholdMultiplier")->mValue.getInteger();
  1360.                     if (player.getClass().getNpcStats(player).getBounty() >= cutoff * iCrimeThresholdMultiplier)
  1361.                     {
  1362.                         MWBase::Environment::get().getMechanicsManager()->startCombat(ptr, player);
  1363.                         creatureStats.setHitAttemptActorId(player.getClass().getCreatureStats(player).getActorId()); // Stops the guard from quitting combat if player is unreachable
  1364.                     }
  1365.                     else
  1366.                         creatureStats.getAiSequence().stack(AiPursue(player), ptr);
  1367.                     creatureStats.setAlarmed(true);
  1368.                     npcStats.setCrimeId(MWBase::Environment::get().getWorld()->getPlayer().getNewCrimeId());
  1369.                 }
  1370.             }
  1371.  
  1372.             // if I was a witness to a crime
  1373.             if (npcStats.getCrimeId() != -1)
  1374.             {
  1375.                 // if you've paid for your crimes and I havent noticed
  1376.                 if( npcStats.getCrimeId() <= MWBase::Environment::get().getWorld()->getPlayer().getCrimeId() )
  1377.                 {
  1378.                     // Calm witness down
  1379.                     if (ptr.getClass().isClass(ptr, "Guard"))
  1380.                         creatureStats.getAiSequence().stopPursuit();
  1381.                     creatureStats.getAiSequence().stopCombat();
  1382.  
  1383.                     // Reset factors to attack
  1384.                     creatureStats.setAttacked(false);
  1385.                     creatureStats.setAlarmed(false);
  1386.                     creatureStats.setAiSetting(CreatureStats::AI_Fight, ptr.getClass().getBaseFightRating(ptr));
  1387.  
  1388.                     // Update witness crime id
  1389.                     npcStats.setCrimeId(-1);
  1390.                 }
  1391.             }
  1392.         }
  1393.     }
  1394.  
  1395.     Actors::Actors()
  1396.     {
  1397.         mTimerDisposeSummonsCorpses = 0.2f; // We should add a delay between summoned creature death and its corpse despawning
  1398.  
  1399.         updateProcessingRange();
  1400.     }
  1401.  
  1402.     Actors::~Actors()
  1403.     {
  1404.         clear();
  1405.     }
  1406.  
  1407.     float Actors::getProcessingRange() const
  1408.     {
  1409.         return mActorsProcessingRange;
  1410.     }
  1411.  
  1412.     void Actors::updateProcessingRange()
  1413.     {
  1414.         // We have to cap it since using high values (larger than 7168) will make some quests harder or impossible to complete (bug #1876)
  1415.         static const float maxProcessingRange = 7168.f;
  1416.         static const float minProcessingRange = maxProcessingRange / 2.f;
  1417.  
  1418.         float actorsProcessingRange = Settings::Manager::getFloat("actors processing range", "Game");
  1419.         actorsProcessingRange = std::min(actorsProcessingRange, maxProcessingRange);
  1420.         actorsProcessingRange = std::max(actorsProcessingRange, minProcessingRange);
  1421.         mActorsProcessingRange = actorsProcessingRange;
  1422.     }
  1423.  
  1424.     void Actors::addActor (const MWWorld::Ptr& ptr, bool updateImmediately)
  1425.     {
  1426.         removeActor(ptr);
  1427.  
  1428.         MWRender::Animation *anim = MWBase::Environment::get().getWorld()->getAnimation(ptr);
  1429.         if (!anim)
  1430.             return;
  1431.         mActors.insert(std::make_pair(ptr, new Actor(ptr, anim)));
  1432.  
  1433.         CharacterController* ctrl = mActors[ptr]->getCharacterController();
  1434.         if (updateImmediately)
  1435.             ctrl->update(0);
  1436.  
  1437.         // We should initially hide actors outside of processing range.
  1438.         // Note: since we update player after other actors, distance will be incorrect during teleportation.
  1439.         // Do not update visibility if player was teleported, so actors will be visible during teleportation frame.
  1440.         if (MWBase::Environment::get().getWorld()->getPlayer().wasTeleported())
  1441.             return;
  1442.  
  1443.         updateVisibility(ptr, ctrl);
  1444.     }
  1445.  
  1446.     void Actors::updateVisibility (const MWWorld::Ptr& ptr, CharacterController* ctrl)
  1447.     {
  1448.         MWWorld::Ptr player = MWMechanics::getPlayer();
  1449.         if (ptr == player)
  1450.             return;
  1451.  
  1452.         const float dist = (player.getRefData().getPosition().asVec3() - ptr.getRefData().getPosition().asVec3()).length();
  1453.         if (dist > mActorsProcessingRange)
  1454.         {
  1455.             ptr.getRefData().getBaseNode()->setNodeMask(SceneUtil::Mask_Disabled);
  1456.             return;
  1457.         }
  1458.         else
  1459.             ptr.getRefData().getBaseNode()->setNodeMask(SceneUtil::Mask_Actor);
  1460.  
  1461.         // Fade away actors on large distance (>90% of actor's processing distance)
  1462.         float visibilityRatio = 1.0;
  1463.         float fadeStartDistance = mActorsProcessingRange*0.9f;
  1464.         float fadeEndDistance = mActorsProcessingRange;
  1465.         float fadeRatio = (dist - fadeStartDistance)/(fadeEndDistance - fadeStartDistance);
  1466.         if (fadeRatio > 0)
  1467.             visibilityRatio -= std::max(0.f, fadeRatio);
  1468.  
  1469.         visibilityRatio = std::min(1.f, visibilityRatio);
  1470.  
  1471.         ctrl->setVisibility(visibilityRatio);
  1472.     }
  1473.  
  1474.     void Actors::removeActor (const MWWorld::Ptr& ptr)
  1475.     {
  1476.         PtrActorMap::iterator iter = mActors.find(ptr);
  1477.         if(iter != mActors.end())
  1478.         {
  1479.             delete iter->second;
  1480.             mActors.erase(iter);
  1481.         }
  1482.     }
  1483.  
  1484.     void Actors::castSpell(const MWWorld::Ptr& ptr, const std::string spellId, bool manualSpell)
  1485.     {
  1486.         PtrActorMap::iterator iter = mActors.find(ptr);
  1487.         if(iter != mActors.end())
  1488.             iter->second->getCharacterController()->castSpell(spellId, manualSpell);
  1489.     }
  1490.  
  1491.     bool Actors::isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer)
  1492.     {
  1493.         if (!actor.getClass().isActor())
  1494.             return false;
  1495.  
  1496.         // If an observer is NPC, check if he detected an actor
  1497.         if (!observer.isEmpty() && observer.getClass().isNpc())
  1498.         {
  1499.             return
  1500.                 MWBase::Environment::get().getWorld()->getLOS(observer, actor) &&
  1501.                 MWBase::Environment::get().getMechanicsManager()->awarenessCheck(actor, observer);
  1502.         }
  1503.  
  1504.         // Otherwise check if any actor in AI processing range sees the target actor
  1505.         std::vector<MWWorld::Ptr> neighbors;
  1506.         osg::Vec3f position (actor.getRefData().getPosition().asVec3());
  1507.         getObjectsInRange(position, mActorsProcessingRange, neighbors);
  1508.         for (const MWWorld::Ptr &neighbor : neighbors)
  1509.         {
  1510.             if (neighbor == actor)
  1511.                 continue;
  1512.  
  1513.             bool result = MWBase::Environment::get().getWorld()->getLOS(neighbor, actor)
  1514.                        && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(actor, neighbor);
  1515.  
  1516.             if (result)
  1517.                 return true;
  1518.         }
  1519.  
  1520.         return false;
  1521.     }
  1522.  
  1523.     void Actors::updateActor(const MWWorld::Ptr &old, const MWWorld::Ptr &ptr)
  1524.     {
  1525.         PtrActorMap::iterator iter = mActors.find(old);
  1526.         if(iter != mActors.end())
  1527.         {
  1528.             Actor *actor = iter->second;
  1529.             mActors.erase(iter);
  1530.  
  1531.             actor->updatePtr(ptr);
  1532.             mActors.insert(std::make_pair(ptr, actor));
  1533.         }
  1534.     }
  1535.  
  1536.     void Actors::dropActors (const MWWorld::CellStore *cellStore, const MWWorld::Ptr& ignore)
  1537.     {
  1538.         PtrActorMap::iterator iter = mActors.begin();
  1539.         while(iter != mActors.end())
  1540.         {
  1541.             if((iter->first.isInCell() && iter->first.getCell()==cellStore) && iter->first != ignore)
  1542.             {
  1543.                 delete iter->second;
  1544.                 mActors.erase(iter++);
  1545.             }
  1546.             else
  1547.                 ++iter;
  1548.         }
  1549.     }
  1550.  
  1551.     void Actors::updateCombatMusic ()
  1552.     {
  1553.         MWWorld::Ptr player = getPlayer();
  1554.         const osg::Vec3f playerPos = player.getRefData().getPosition().asVec3();
  1555.         bool hasHostiles = false; // need to know this to play Battle music
  1556.         bool aiActive = MWBase::Environment::get().getMechanicsManager()->isAIActive();
  1557.  
  1558.         if (aiActive)
  1559.         {
  1560.             for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter)
  1561.             {
  1562.                 if (iter->first == player) continue;
  1563.  
  1564.                 bool inProcessingRange = (playerPos - iter->first.getRefData().getPosition().asVec3()).length2() <= mActorsProcessingRange*mActorsProcessingRange;
  1565.                 if (inProcessingRange)
  1566.                 {
  1567.                     MWMechanics::CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first);
  1568.                     if (!stats.isDead() && stats.getAiSequence().isInCombat())
  1569.                     {
  1570.                         hasHostiles = true;
  1571.                         break;
  1572.                     }
  1573.                 }
  1574.             }
  1575.         }
  1576.  
  1577.         // check if we still have any player enemies to switch music
  1578.         static int currentMusic = 0;
  1579.  
  1580.         if (currentMusic != 1 && !hasHostiles && !(player.getClass().getCreatureStats(player).isDead() &&
  1581.         MWBase::Environment::get().getSoundManager()->isMusicPlaying()))
  1582.         {
  1583.             MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Explore"));
  1584.             currentMusic = 1;
  1585.         }
  1586.         else if (currentMusic != 2 && hasHostiles)
  1587.         {
  1588.             MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Battle"));
  1589.             currentMusic = 2;
  1590.         }
  1591.  
  1592.     }
  1593.  
  1594.     void Actors::update (float duration, bool paused)
  1595.     {
  1596.         if(!paused)
  1597.         {
  1598.             static float timerUpdateAITargets = 0;
  1599.             static float timerUpdateHeadTrack = 0;
  1600.             static float timerUpdateEquippedLight = 0;
  1601.             static float timerUpdateHello = 0;
  1602.             const float updateEquippedLightInterval = 1.0f;
  1603.  
  1604.             // target lists get updated once every 1.0 sec
  1605.             if (timerUpdateAITargets >= 1.0f) timerUpdateAITargets = 0;
  1606.             if (timerUpdateHeadTrack >= 0.3f) timerUpdateHeadTrack = 0;
  1607.             if (timerUpdateHello >= 0.25f) timerUpdateHello = 0;
  1608.             if (mTimerDisposeSummonsCorpses >= 0.2f) mTimerDisposeSummonsCorpses = 0;
  1609.             if (timerUpdateEquippedLight >= updateEquippedLightInterval) timerUpdateEquippedLight = 0;
  1610.  
  1611.             // show torches only when there are darkness and no precipitations
  1612.             MWBase::World* world = MWBase::Environment::get().getWorld();
  1613.             bool showTorches = world->useTorches();
  1614.  
  1615.             MWWorld::Ptr player = getPlayer();
  1616.             const osg::Vec3f playerPos = player.getRefData().getPosition().asVec3();
  1617.  
  1618.             /// \todo move update logic to Actor class where appropriate
  1619.  
  1620.             std::map<const MWWorld::Ptr, const std::set<MWWorld::Ptr> > cachedAllies; // will be filled as engageCombat iterates
  1621.  
  1622.             bool aiActive = MWBase::Environment::get().getMechanicsManager()->isAIActive();
  1623.             int attackedByPlayerId = player.getClass().getCreatureStats(player).getHitAttemptActorId();
  1624.             if (attackedByPlayerId != -1)
  1625.             {
  1626.                 const MWWorld::Ptr playerHitAttemptActor = world->searchPtrViaActorId(attackedByPlayerId);
  1627.  
  1628.                 if (!playerHitAttemptActor.isInCell())
  1629.                     player.getClass().getCreatureStats(player).setHitAttemptActorId(-1);
  1630.             }
  1631.  
  1632.              // AI and magic effects update
  1633.             for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter)
  1634.             {
  1635.                 bool isPlayer = iter->first == player;
  1636.                 CharacterController* ctrl = iter->second->getCharacterController();
  1637.  
  1638.                 float distSqr = (playerPos - iter->first.getRefData().getPosition().asVec3()).length2();
  1639.                 // AI processing is only done within given distance to the player.
  1640.                 bool inProcessingRange = distSqr <= mActorsProcessingRange*mActorsProcessingRange;
  1641.  
  1642.                 if (isPlayer)
  1643.                     ctrl->setAttackingOrSpell(world->getPlayer().getAttackingOrSpell());
  1644.  
  1645.                 // If dead or no longer in combat, no longer store any actors who attempted to hit us. Also remove for the player.
  1646.                 if (iter->first != player && (iter->first.getClass().getCreatureStats(iter->first).isDead()
  1647.                     || !iter->first.getClass().getCreatureStats(iter->first).getAiSequence().isInCombat()
  1648.                     || !inProcessingRange))
  1649.                 {
  1650.                     iter->first.getClass().getCreatureStats(iter->first).setHitAttemptActorId(-1);
  1651.                     if (player.getClass().getCreatureStats(player).getHitAttemptActorId() == iter->first.getClass().getCreatureStats(iter->first).getActorId())
  1652.                         player.getClass().getCreatureStats(player).setHitAttemptActorId(-1);
  1653.                 }
  1654.  
  1655.                 // For dead actors we need to remove looping spell particles
  1656.                 if (iter->first.getClass().getCreatureStats(iter->first).isDead())
  1657.                     ctrl->updateContinuousVfx();
  1658.                 else
  1659.                 {
  1660.                     bool cellChanged = world->hasCellChanged();
  1661.                     MWWorld::Ptr actor = iter->first; // make a copy of the map key to avoid it being invalidated when the player teleports
  1662.                     updateActor(actor, duration);
  1663.  
  1664.                     // Looping magic VFX update
  1665.                     // Note: we need to do this before any of the animations are updated.
  1666.                     // Reaching the text keys may trigger Hit / Spellcast (and as such, particles),
  1667.                     // so updating VFX immediately after that would just remove the particle effects instantly.
  1668.                     // There needs to be a magic effect update in between.
  1669.                     ctrl->updateContinuousVfx();
  1670.  
  1671.                     if (!cellChanged && world->hasCellChanged())
  1672.                     {
  1673.                         return; // for now abort update of the old cell when cell changes by teleportation magic effect
  1674.                                 // a better solution might be to apply cell changes at the end of the frame
  1675.                     }
  1676.                     if (aiActive && inProcessingRange)
  1677.                     {
  1678.                         if (timerUpdateAITargets == 0)
  1679.                         {
  1680.                             if (!isPlayer)
  1681.                                 adjustCommandedActor(iter->first);
  1682.  
  1683.                             for(PtrActorMap::iterator it(mActors.begin()); it != mActors.end(); ++it)
  1684.                             {
  1685.                                 if (it->first == iter->first || isPlayer) // player is not AI-controlled
  1686.                                     continue;
  1687.                                 engageCombat(iter->first, it->first, cachedAllies, it->first == player);
  1688.                             }
  1689.                         }
  1690.                         if (timerUpdateHeadTrack == 0)
  1691.                         {
  1692.                             float sqrHeadTrackDistance = std::numeric_limits<float>::max();
  1693.                             MWWorld::Ptr headTrackTarget;
  1694.  
  1695.                             MWMechanics::CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first);
  1696.                             bool firstPersonPlayer = isPlayer && world->isFirstPerson();
  1697.  
  1698.                             // 1. Unconsious actor can not track target
  1699.                             // 2. Actors in combat and pursue mode do not bother to headtrack
  1700.                             // 3. Player character does not use headtracking in the 1st-person view
  1701.                             if (!stats.getKnockedDown() &&
  1702.                                 !stats.getAiSequence().isInCombat() &&
  1703.                                 !stats.getAiSequence().hasPackage(AiPackage::TypeIdPursue) &&
  1704.                                 !firstPersonPlayer)
  1705.                             {
  1706.                                 for(PtrActorMap::iterator it(mActors.begin()); it != mActors.end(); ++it)
  1707.                                 {
  1708.                                     if (it->first == iter->first)
  1709.                                         continue;
  1710.                                     updateHeadTracking(iter->first, it->first, headTrackTarget, sqrHeadTrackDistance);
  1711.                                 }
  1712.                             }
  1713.  
  1714.                             ctrl->setHeadTrackTarget(headTrackTarget);
  1715.                         }
  1716.  
  1717.                         if (iter->first.getClass().isNpc() && iter->first != player)
  1718.                             updateCrimePursuit(iter->first, duration);
  1719.  
  1720.                         if (iter->first != player)
  1721.                         {
  1722.                             CreatureStats &stats = iter->first.getClass().getCreatureStats(iter->first);
  1723.                             if (isConscious(iter->first))
  1724.                             {
  1725.                                 stats.getAiSequence().execute(iter->first, *ctrl, duration);
  1726.                                 updateGreetingState(iter->first, timerUpdateHello > 0);
  1727.                                 playIdleDialogue(iter->first);
  1728.                                 updateMovementSpeed(iter->first);
  1729.                             }
  1730.                         }
  1731.                     }
  1732.                     else if (aiActive && iter->first != player && isConscious(iter->first))
  1733.                     {
  1734.                         CreatureStats &stats = iter->first.getClass().getCreatureStats(iter->first);
  1735.                         stats.getAiSequence().execute(iter->first, *ctrl, duration, /*outOfRange*/true);
  1736.                     }
  1737.  
  1738.                     if(iter->first.getClass().isNpc())
  1739.                     {
  1740.                         // We can not update drowning state for actors outside of AI distance - they can not resurface to breathe
  1741.                         if (inProcessingRange)
  1742.                             updateDrowning(iter->first, duration, ctrl->isKnockedOut(), isPlayer);
  1743.  
  1744.                         calculateNpcStatModifiers(iter->first, duration);
  1745.  
  1746.                         if (timerUpdateEquippedLight == 0)
  1747.                             updateEquippedLight(iter->first, updateEquippedLightInterval, showTorches);
  1748.                     }
  1749.                 }
  1750.             }
  1751.  
  1752.             timerUpdateAITargets += duration;
  1753.             timerUpdateHeadTrack += duration;
  1754.             timerUpdateEquippedLight += duration;
  1755.             timerUpdateHello += duration;
  1756.             mTimerDisposeSummonsCorpses += duration;
  1757.  
  1758.             // Animation/movement update
  1759.             CharacterController* playerCharacter = nullptr;
  1760.             for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter)
  1761.             {
  1762.                 const float dist = (playerPos - iter->first.getRefData().getPosition().asVec3()).length();
  1763.                 bool isPlayer = iter->first == player;
  1764.                 CreatureStats &stats = iter->first.getClass().getCreatureStats(iter->first);
  1765.                 // Actors with active AI should be able to move.
  1766.                 bool alwaysActive = false;
  1767.                 if (!isPlayer && isConscious(iter->first) && !stats.isParalyzed())
  1768.                 {
  1769.                     MWMechanics::AiSequence& seq = stats.getAiSequence();
  1770.                     alwaysActive = !seq.isEmpty() && seq.getActivePackage()->alwaysActive();
  1771.                 }
  1772.                 bool inRange = isPlayer || dist <= mActorsProcessingRange || alwaysActive;
  1773.                 int activeFlag = 1; // Can be changed back to '2' to keep updating bounding boxes off screen (more accurate, but slower)
  1774.                 if (isPlayer)
  1775.                     activeFlag = 2;
  1776.                 int active = inRange ? activeFlag : 0;
  1777.  
  1778.                 CharacterController* ctrl = iter->second->getCharacterController();
  1779.                 ctrl->setActive(active);
  1780.  
  1781.                 if (!inRange)
  1782.                 {
  1783.                     iter->first.getRefData().getBaseNode()->setNodeMask(SceneUtil::Mask_Disabled);
  1784.                     world->setActorCollisionMode(iter->first, false, false);
  1785.                     continue;
  1786.                 }
  1787.                 else if (!isPlayer)
  1788.                     iter->first.getRefData().getBaseNode()->setNodeMask(SceneUtil::Mask_Actor);
  1789.  
  1790.                 const bool isDead = iter->first.getClass().getCreatureStats(iter->first).isDead();
  1791.                 if (!isDead && iter->first.getClass().getCreatureStats(iter->first).isParalyzed())
  1792.                     ctrl->skipAnim();
  1793.  
  1794.                 // Handle player last, in case a cell transition occurs by casting a teleportation spell
  1795.                 // (would invalidate the iterator)
  1796.                 if (iter->first == getPlayer())
  1797.                 {
  1798.                     playerCharacter = ctrl;
  1799.                     continue;
  1800.                 }
  1801.  
  1802.                 world->setActorCollisionMode(iter->first, true, !iter->first.getClass().getCreatureStats(iter->first).isDeathAnimationFinished());
  1803.                 ctrl->update(duration);
  1804.  
  1805.                 updateVisibility(iter->first, ctrl);
  1806.             }
  1807.  
  1808.             if (playerCharacter)
  1809.             {
  1810.                 playerCharacter->update(duration);
  1811.                 playerCharacter->setVisibility(1.f);
  1812.             }
  1813.  
  1814.             for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter)
  1815.             {
  1816.                 const MWWorld::Class &cls = iter->first.getClass();
  1817.                 CreatureStats &stats = cls.getCreatureStats(iter->first);
  1818.  
  1819.                 //KnockedOutOneFrameLogic
  1820.                 //Used for "OnKnockedOut" command
  1821.                 //Put here to ensure that it's run for PRECISELY one frame.
  1822.                 if (stats.getKnockedDown() && !stats.getKnockedDownOneFrame() && !stats.getKnockedDownOverOneFrame())
  1823.                 { //Start it for one frame if nessesary
  1824.                     stats.setKnockedDownOneFrame(true);
  1825.                 }
  1826.                 else if (stats.getKnockedDownOneFrame() && !stats.getKnockedDownOverOneFrame())
  1827.                 { //Turn off KnockedOutOneframe
  1828.                     stats.setKnockedDownOneFrame(false);
  1829.                     stats.setKnockedDownOverOneFrame(true);
  1830.                 }
  1831.             }
  1832.  
  1833.             killDeadActors();
  1834.             updateSneaking(playerCharacter, duration);
  1835.         }
  1836.  
  1837.         updateCombatMusic();
  1838.     }
  1839.  
  1840.     void Actors::notifyDied(const MWWorld::Ptr &actor)
  1841.     {
  1842.         actor.getClass().getCreatureStats(actor).notifyDied();
  1843.  
  1844.         ++mDeathCount[Misc::StringUtils::lowerCase(actor.getCellRef().getRefId())];
  1845.     }
  1846.  
  1847.     void Actors::resurrect(const MWWorld::Ptr &ptr)
  1848.     {
  1849.         PtrActorMap::iterator iter = mActors.find(ptr);
  1850.         if(iter != mActors.end())
  1851.         {
  1852.             if(iter->second->getCharacterController()->isDead())
  1853.             {
  1854.                 // Actor has been resurrected. Notify the CharacterController and re-enable collision.
  1855.                 MWBase::Environment::get().getWorld()->enableActorCollision(iter->first, true);
  1856.                 iter->second->getCharacterController()->resurrect();
  1857.             }
  1858.         }
  1859.     }
  1860.  
  1861.     void Actors::killDeadActors()
  1862.     {
  1863.         for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter)
  1864.         {
  1865.             const MWWorld::Class &cls = iter->first.getClass();
  1866.             CreatureStats &stats = cls.getCreatureStats(iter->first);
  1867.  
  1868.             if(!stats.isDead())
  1869.                 continue;
  1870.  
  1871.             MWBase::Environment::get().getWorld()->removeActorPath(iter->first);
  1872.             CharacterController::KillResult killResult = iter->second->getCharacterController()->kill();
  1873.             if (killResult == CharacterController::Result_DeathAnimStarted)
  1874.             {
  1875.                 // Play dying words
  1876.                 // Note: It's not known whether the soundgen tags scream, roar, and moan are reliable
  1877.                 // for NPCs since some of the npc death animation files are missing them.
  1878.                 MWBase::Environment::get().getDialogueManager()->say(iter->first, "hit");
  1879.  
  1880.                 // Apply soultrap
  1881.                 //if (iter->first.getTypeName() == typeid(ESM::Creature).name())
  1882.                 {
  1883.                     SoulTrap soulTrap (iter->first);
  1884.                     stats.getActiveSpells().visitEffectSources(soulTrap);
  1885.                 }
  1886.         //else
  1887.         {
  1888.        
  1889. }
  1890.  
  1891.                 // Magic effects will be reset later, and the magic effect that could kill the actor
  1892.                 // needs to be determined now
  1893.                 calculateCreatureStatModifiers(iter->first, 0);
  1894.  
  1895.                 if (cls.isEssential(iter->first))
  1896.                     MWBase::Environment::get().getWindowManager()->messageBox("#{sKilledEssential}");
  1897.             }
  1898.             else if (killResult == CharacterController::Result_DeathAnimJustFinished)
  1899.             {
  1900.                 notifyDied(iter->first);
  1901.  
  1902.                 // Reset magic effects and recalculate derived effects
  1903.                 // One case where we need this is to make sure bound items are removed upon death
  1904.                 stats.modifyMagicEffects(MWMechanics::MagicEffects());
  1905.                 stats.getActiveSpells().clear();
  1906.                 stats.getSpells().clear();
  1907.                 // Make sure spell effects are removed
  1908.                 purgeSpellEffects(stats.getActorId());
  1909.  
  1910.                 // Reset dynamic stats, attributes and skills
  1911.                 calculateCreatureStatModifiers(iter->first, 0);
  1912.                 if (iter->first.getClass().isNpc())
  1913.                     calculateNpcStatModifiers(iter->first, 0);
  1914.  
  1915.                 if( iter->first == getPlayer())
  1916.                 {
  1917.                     //player's death animation is over
  1918.                     MWBase::Environment::get().getStateManager()->askLoadRecent();
  1919.                 }
  1920.                 else
  1921.                 {
  1922.                     // NPC death animation is over, disable actor collision
  1923.                     MWBase::Environment::get().getWorld()->enableActorCollision(iter->first, false);
  1924.                 }
  1925.  
  1926.                 // Play Death Music if it was the player dying
  1927.                 if(iter->first == getPlayer())
  1928.                     MWBase::Environment::get().getSoundManager()->streamMusic("Special/MW_Death.mp3");
  1929.             }
  1930.         }
  1931.     }
  1932.  
  1933.     void Actors::cleanupSummonedCreature (MWMechanics::CreatureStats& casterStats, int creatureActorId)
  1934.     {
  1935.         MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(creatureActorId);
  1936.         if (!ptr.isEmpty())
  1937.         {
  1938.             MWBase::Environment::get().getWorld()->deleteObject(ptr);
  1939.  
  1940.             const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>()
  1941.                     .search("VFX_Summon_End");
  1942.             if (fx)
  1943.                 MWBase::Environment::get().getWorld()->spawnEffect("meshes\\" + fx->mModel,
  1944.                     "", ptr.getRefData().getPosition().asVec3());
  1945.  
  1946.             // Remove the summoned creature's summoned creatures as well
  1947.             MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
  1948.             std::map<CreatureStats::SummonKey, int>& creatureMap = stats.getSummonedCreatureMap();
  1949.             for (const auto& creature : creatureMap)
  1950.                 cleanupSummonedCreature(stats, creature.second);
  1951.             creatureMap.clear();
  1952.         }
  1953.         else if (creatureActorId != -1)
  1954.         {
  1955.             // We didn't find the creature. It's probably in an inactive cell.
  1956.             // Add to graveyard so we can delete it when the cell becomes active.
  1957.             std::vector<int>& graveyard = casterStats.getSummonedCreatureGraveyard();
  1958.             graveyard.push_back(creatureActorId);
  1959.         }
  1960.  
  1961.         purgeSpellEffects(creatureActorId);
  1962.     }
  1963.  
  1964.     void Actors::purgeSpellEffects(int casterActorId)
  1965.     {
  1966.         for (PtrActorMap::iterator iter(mActors.begin());iter != mActors.end();++iter)
  1967.         {
  1968.             MWMechanics::ActiveSpells& spells = iter->first.getClass().getCreatureStats(iter->first).getActiveSpells();
  1969.             spells.purge(casterActorId);
  1970.         }
  1971.     }
  1972.  
  1973.     void Actors::rest(double hours, bool sleep)
  1974.     {
  1975.         float duration = hours * 3600.f / MWBase::Environment::get().getWorld()->getTimeScaleFactor();
  1976.         const MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
  1977.         const osg::Vec3f playerPos = player.getRefData().getPosition().asVec3();
  1978.  
  1979.         for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter)
  1980.         {
  1981.             if (iter->first.getClass().getCreatureStats(iter->first).isDead())
  1982.                 continue;
  1983.  
  1984.             if (!sleep || iter->first == player)
  1985.                 restoreDynamicStats(iter->first, hours, sleep);
  1986.  
  1987.             if ((!iter->first.getRefData().getBaseNode()) ||
  1988.                     (playerPos - iter->first.getRefData().getPosition().asVec3()).length2() > mActorsProcessingRange*mActorsProcessingRange)
  1989.                 continue;
  1990.  
  1991.             adjustMagicEffects (iter->first);
  1992.             if (iter->first.getClass().getCreatureStats(iter->first).needToRecalcDynamicStats())
  1993.                 calculateDynamicStats (iter->first);
  1994.  
  1995.             calculateCreatureStatModifiers (iter->first, duration);
  1996.             if (iter->first.getClass().isNpc())
  1997.                 calculateNpcStatModifiers(iter->first, duration);
  1998.  
  1999.             MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(iter->first);
  2000.             if (animation)
  2001.             {
  2002.                 animation->removeEffects();
  2003.                 MWBase::Environment::get().getWorld()->applyLoopingParticles(iter->first);
  2004.             }
  2005.  
  2006.         }
  2007.  
  2008.         fastForwardAi();
  2009.     }
  2010.  
  2011.     void Actors::updateSneaking(CharacterController* ctrl, float duration)
  2012.     {
  2013.         static float sneakTimer = 0.f; // Times update of sneak icon
  2014.  
  2015.         if (!ctrl)
  2016.         {
  2017.             MWBase::Environment::get().getWindowManager()->setSneakVisibility(false);
  2018.             return;
  2019.         }
  2020.  
  2021.         MWWorld::Ptr player = getPlayer();
  2022.  
  2023.         if (!MWBase::Environment::get().getMechanicsManager()->isSneaking(player))
  2024.         {
  2025.             MWBase::Environment::get().getWindowManager()->setSneakVisibility(false);
  2026.             return;
  2027.         }
  2028.  
  2029.         static float sneakSkillTimer = 0.f; // Times sneak skill progress from "avoid notice"
  2030.  
  2031.         MWBase::World* world = MWBase::Environment::get().getWorld();
  2032.         const MWWorld::Store<ESM::GameSetting>& gmst = world->getStore().get<ESM::GameSetting>();
  2033.         static const float fSneakUseDist = gmst.find("fSneakUseDist")->mValue.getFloat();
  2034.         static const float fSneakUseDelay = gmst.find("fSneakUseDelay")->mValue.getFloat();
  2035.  
  2036.         if (sneakTimer >= fSneakUseDelay)
  2037.             sneakTimer = 0.f;
  2038.  
  2039.         if (sneakTimer == 0.f)
  2040.         {
  2041.             // Set when an NPC is within line of sight and distance, but is still unaware. Used for skill progress.
  2042.             bool avoidedNotice = false;
  2043.             bool detected = false;
  2044.  
  2045.             std::vector<MWWorld::Ptr> observers;
  2046.             osg::Vec3f position(player.getRefData().getPosition().asVec3());
  2047.             float radius = std::min(fSneakUseDist, mActorsProcessingRange);
  2048.             getObjectsInRange(position, radius, observers);
  2049.  
  2050.             for (const MWWorld::Ptr &observer : observers)
  2051.             {
  2052.                 if (observer == player || observer.getClass().getCreatureStats(observer).isDead())
  2053.                     continue;
  2054.  
  2055.                 if (world->getLOS(player, observer))
  2056.                 {
  2057.                     if (MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, observer))
  2058.                     {
  2059.                         detected = true;
  2060.                         avoidedNotice = false;
  2061.                         MWBase::Environment::get().getWindowManager()->setSneakVisibility(false);
  2062.                         break;
  2063.                     }
  2064.                     else
  2065.                     {
  2066.                         avoidedNotice = true;
  2067.                     }
  2068.                 }
  2069.             }
  2070.  
  2071.             if (sneakSkillTimer >= fSneakUseDelay)
  2072.                 sneakSkillTimer = 0.f;
  2073.  
  2074.             if (avoidedNotice && sneakSkillTimer == 0.f)
  2075.                 player.getClass().skillUsageSucceeded(player, ESM::Skill::Sneak, 0);
  2076.  
  2077.             if (!detected)
  2078.                 MWBase::Environment::get().getWindowManager()->setSneakVisibility(true);
  2079.         }
  2080.  
  2081.         sneakTimer += duration;
  2082.         sneakSkillTimer += duration;
  2083.     }
  2084.  
  2085.     int Actors::getHoursToRest(const MWWorld::Ptr &ptr) const
  2086.     {
  2087.         float healthPerHour, magickaPerHour;
  2088.         getRestorationPerHourOfSleep(ptr, healthPerHour, magickaPerHour);
  2089.  
  2090.         CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
  2091.         bool stunted = stats.getMagicEffects ().get(ESM::MagicEffect::StuntedMagicka).getMagnitude() > 0;
  2092.  
  2093.         float healthHours  = healthPerHour > 0
  2094.                              ? (stats.getHealth().getModified() - stats.getHealth().getCurrent()) / healthPerHour
  2095.                              : 1.0f;
  2096.         float magickaHours = magickaPerHour > 0 && !stunted
  2097.                               ? (stats.getMagicka().getModified() - stats.getMagicka().getCurrent()) / magickaPerHour
  2098.                               : 1.0f;
  2099.  
  2100.         int autoHours = static_cast<int>(std::ceil(std::max(1.f, std::max(healthHours, magickaHours))));
  2101.         return autoHours;
  2102.     }
  2103.  
  2104.     int Actors::countDeaths (const std::string& id) const
  2105.     {
  2106.         std::map<std::string, int>::const_iterator iter = mDeathCount.find(id);
  2107.         if(iter != mDeathCount.end())
  2108.             return iter->second;
  2109.         return 0;
  2110.     }
  2111.  
  2112.     void Actors::forceStateUpdate(const MWWorld::Ptr & ptr)
  2113.     {
  2114.         PtrActorMap::iterator iter = mActors.find(ptr);
  2115.         if(iter != mActors.end())
  2116.             iter->second->getCharacterController()->forceStateUpdate();
  2117.     }
  2118.  
  2119.     bool Actors::playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number, bool persist)
  2120.     {
  2121.         PtrActorMap::iterator iter = mActors.find(ptr);
  2122.         if(iter != mActors.end())
  2123.         {
  2124.             return iter->second->getCharacterController()->playGroup(groupName, mode, number, persist);
  2125.         }
  2126.         else
  2127.         {
  2128.             Log(Debug::Warning) << "Warning: Actors::playAnimationGroup: Unable to find " << ptr.getCellRef().getRefId();
  2129.             return false;
  2130.         }
  2131.     }
  2132.     void Actors::skipAnimation(const MWWorld::Ptr& ptr)
  2133.     {
  2134.         PtrActorMap::iterator iter = mActors.find(ptr);
  2135.         if(iter != mActors.end())
  2136.             iter->second->getCharacterController()->skipAnim();
  2137.     }
  2138.  
  2139.     bool Actors::checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName)
  2140.     {
  2141.         PtrActorMap::iterator iter = mActors.find(ptr);
  2142.         if(iter != mActors.end())
  2143.             return iter->second->getCharacterController()->isAnimPlaying(groupName);
  2144.         return false;
  2145.     }
  2146.  
  2147.     void Actors::persistAnimationStates()
  2148.     {
  2149.         for (PtrActorMap::iterator iter = mActors.begin(); iter != mActors.end(); ++iter)
  2150.             iter->second->getCharacterController()->persistAnimationState();
  2151.     }
  2152.  
  2153.     void Actors::getObjectsInRange(const osg::Vec3f& position, float radius, std::vector<MWWorld::Ptr>& out)
  2154.     {
  2155.         for (PtrActorMap::iterator iter = mActors.begin(); iter != mActors.end(); ++iter)
  2156.         {
  2157.             if ((iter->first.getRefData().getPosition().asVec3() - position).length2() <= radius*radius)
  2158.                 out.push_back(iter->first);
  2159.         }
  2160.     }
  2161.  
  2162.     bool Actors::isAnyObjectInRange(const osg::Vec3f& position, float radius)
  2163.     {
  2164.         for (PtrActorMap::iterator iter = mActors.begin(); iter != mActors.end(); ++iter)
  2165.         {
  2166.             if ((iter->first.getRefData().getPosition().asVec3() - position).length2() <= radius*radius)
  2167.                 return true;
  2168.         }
  2169.  
  2170.         return false;
  2171.     }
  2172.  
  2173.     std::list<MWWorld::Ptr> Actors::getActorsSidingWith(const MWWorld::Ptr& actor)
  2174.     {
  2175.         std::list<MWWorld::Ptr> list;
  2176.         for(PtrActorMap::iterator iter = mActors.begin(); iter != mActors.end(); ++iter)
  2177.         {
  2178.             const MWWorld::Ptr &iteratedActor = iter->first;
  2179.             if (iteratedActor == getPlayer())
  2180.                 continue;
  2181.  
  2182.             const bool sameActor = (iteratedActor == actor);
  2183.  
  2184.             const CreatureStats &stats = iteratedActor.getClass().getCreatureStats(iteratedActor);
  2185.             if (stats.isDead())
  2186.                 continue;
  2187.  
  2188.             // An actor counts as siding with this actor if Follow or Escort is the current AI package, or there are only Combat and Wander packages before the Follow/Escort package
  2189.             // Actors that are targeted by this actor's Follow or Escort packages also side with them
  2190.             for (const AiPackage* package : stats.getAiSequence())
  2191.             {
  2192.                 if (package->sideWithTarget() && !package->getTarget().isEmpty())
  2193.                 {
  2194.                     if (sameActor)
  2195.                     {
  2196.                         list.push_back(package->getTarget());
  2197.                     }
  2198.                     else if (package->getTarget() == actor)
  2199.                     {
  2200.                         list.push_back(iteratedActor);
  2201.                     }
  2202.                     break;
  2203.                 }
  2204.                 else if (package->getTypeId() != AiPackage::TypeIdCombat && package->getTypeId() != AiPackage::TypeIdWander)
  2205.                     break;
  2206.             }
  2207.         }
  2208.         return list;
  2209.     }
  2210.  
  2211.     std::list<MWWorld::Ptr> Actors::getActorsFollowing(const MWWorld::Ptr& actor)
  2212.     {
  2213.         std::list<MWWorld::Ptr> list;
  2214.         for(PtrActorMap::iterator iter(mActors.begin());iter != mActors.end();++iter)
  2215.         {
  2216.             const MWWorld::Ptr &iteratedActor = iter->first;
  2217.             if (iteratedActor == getPlayer() || iteratedActor == actor)
  2218.                 continue;
  2219.  
  2220.             const CreatureStats &stats = iteratedActor.getClass().getCreatureStats(iteratedActor);
  2221.             if (stats.isDead())
  2222.                 continue;
  2223.  
  2224.             // An actor counts as following if AiFollow is the current AiPackage,
  2225.             // or there are only Combat and Wander packages before the AiFollow package
  2226.             for (const AiPackage* package : stats.getAiSequence())
  2227.             {
  2228.                 if (package->followTargetThroughDoors() && package->getTarget() == actor)
  2229.                     list.push_back(iteratedActor);
  2230.                 else if (package->getTypeId() != AiPackage::TypeIdCombat && package->getTypeId() != AiPackage::TypeIdWander)
  2231.                     break;
  2232.             }
  2233.         }
  2234.         return list;
  2235.     }
  2236.  
  2237.     void Actors::getActorsFollowing(const MWWorld::Ptr &actor, std::set<MWWorld::Ptr>& out) {
  2238.         std::list<MWWorld::Ptr> followers = getActorsFollowing(actor);
  2239.         for(const MWWorld::Ptr &follower : followers)
  2240.             if (out.insert(follower).second)
  2241.                 getActorsFollowing(follower, out);
  2242.     }
  2243.  
  2244.     void Actors::getActorsSidingWith(const MWWorld::Ptr &actor, std::set<MWWorld::Ptr>& out) {
  2245.         std::list<MWWorld::Ptr> followers = getActorsSidingWith(actor);
  2246.         for(const MWWorld::Ptr &follower : followers)
  2247.             if (out.insert(follower).second)
  2248.                 getActorsSidingWith(follower, out);
  2249.     }
  2250.  
  2251.     void Actors::getActorsSidingWith(const MWWorld::Ptr &actor, std::set<MWWorld::Ptr>& out, std::map<const MWWorld::Ptr, const std::set<MWWorld::Ptr> >& cachedAllies) {
  2252.         // If we have already found actor's allies, use the cache
  2253.         std::map<const MWWorld::Ptr, const std::set<MWWorld::Ptr> >::const_iterator search = cachedAllies.find(actor);
  2254.         if (search != cachedAllies.end())
  2255.             out.insert(search->second.begin(), search->second.end());
  2256.         else
  2257.         {
  2258.             std::list<MWWorld::Ptr> followers = getActorsSidingWith(actor);
  2259.             for (const MWWorld::Ptr &follower : followers)
  2260.                 if (out.insert(follower).second)
  2261.                     getActorsSidingWith(follower, out, cachedAllies);
  2262.  
  2263.             // Cache ptrs and their sets of allies
  2264.             cachedAllies.insert(std::make_pair(actor, out));
  2265.             for (const MWWorld::Ptr &iter : out)
  2266.             {
  2267.                 search = cachedAllies.find(iter);
  2268.                 if (search == cachedAllies.end())
  2269.                     cachedAllies.insert(std::make_pair(iter, out));
  2270.             }
  2271.         }
  2272.     }
  2273.  
  2274.     std::list<int> Actors::getActorsFollowingIndices(const MWWorld::Ptr &actor)
  2275.     {
  2276.         std::list<int> list;
  2277.         for(PtrActorMap::iterator iter(mActors.begin());iter != mActors.end();++iter)
  2278.         {
  2279.             const MWWorld::Ptr &iteratedActor = iter->first;
  2280.             if (iteratedActor == getPlayer() || iteratedActor == actor)
  2281.                 continue;
  2282.  
  2283.             const CreatureStats &stats = iteratedActor.getClass().getCreatureStats(iteratedActor);
  2284.             if (stats.isDead())
  2285.                 continue;
  2286.  
  2287.             // An actor counts as following if AiFollow is the current AiPackage,
  2288.             // or there are only Combat and Wander packages before the AiFollow package
  2289.             for (AiPackage* package : stats.getAiSequence())
  2290.             {
  2291.                 if (package->followTargetThroughDoors() && package->getTarget() == actor)
  2292.                 {
  2293.                     list.push_back(static_cast<AiFollow*>(package)->getFollowIndex());
  2294.                     break;
  2295.                 }
  2296.                 else if (package->getTypeId() != AiPackage::TypeIdCombat && package->getTypeId() != AiPackage::TypeIdWander)
  2297.                     break;
  2298.             }
  2299.         }
  2300.         return list;
  2301.     }
  2302.  
  2303.     std::list<MWWorld::Ptr> Actors::getActorsFighting(const MWWorld::Ptr& actor) {
  2304.         std::list<MWWorld::Ptr> list;
  2305.         std::vector<MWWorld::Ptr> neighbors;
  2306.         osg::Vec3f position (actor.getRefData().getPosition().asVec3());
  2307.         getObjectsInRange(position, mActorsProcessingRange, neighbors);
  2308.         for(const MWWorld::Ptr& neighbor : neighbors)
  2309.         {
  2310.             if (neighbor == actor)
  2311.                 continue;
  2312.  
  2313.             const CreatureStats &stats = neighbor.getClass().getCreatureStats(neighbor);
  2314.             if (stats.isDead())
  2315.                 continue;
  2316.  
  2317.             if (stats.getAiSequence().isInCombat(actor))
  2318.                 list.push_front(neighbor);
  2319.         }
  2320.         return list;
  2321.     }
  2322.  
  2323.     std::list<MWWorld::Ptr> Actors::getEnemiesNearby(const MWWorld::Ptr& actor)
  2324.     {
  2325.         std::list<MWWorld::Ptr> list;
  2326.         std::vector<MWWorld::Ptr> neighbors;
  2327.         osg::Vec3f position (actor.getRefData().getPosition().asVec3());
  2328.         getObjectsInRange(position, mActorsProcessingRange, neighbors);
  2329.  
  2330.         std::set<MWWorld::Ptr> followers;
  2331.         getActorsFollowing(actor, followers);
  2332.         for (auto neighbor = neighbors.begin(); neighbor != neighbors.end(); ++neighbor)
  2333.         {
  2334.             const CreatureStats &stats = neighbor->getClass().getCreatureStats(*neighbor);
  2335.             if (stats.isDead() || *neighbor == actor || neighbor->getClass().isPureWaterCreature(*neighbor))
  2336.                 continue;
  2337.  
  2338.             const bool isFollower = followers.find(*neighbor) != followers.end();
  2339.  
  2340.             if (stats.getAiSequence().isInCombat(actor) || (MWBase::Environment::get().getMechanicsManager()->isAggressive(*neighbor, actor) && !isFollower))
  2341.                 list.push_back(*neighbor);
  2342.         }
  2343.         return list;
  2344.     }
  2345.  
  2346.  
  2347.     void Actors::write (ESM::ESMWriter& writer, Loading::Listener& listener) const
  2348.     {
  2349.         writer.startRecord(ESM::REC_DCOU);
  2350.         for (std::map<std::string, int>::const_iterator it = mDeathCount.begin(); it != mDeathCount.end(); ++it)
  2351.         {
  2352.             writer.writeHNString("ID__", it->first);
  2353.             writer.writeHNT ("COUN", it->second);
  2354.         }
  2355.         writer.endRecord(ESM::REC_DCOU);
  2356.     }
  2357.  
  2358.     void Actors::readRecord (ESM::ESMReader& reader, uint32_t type)
  2359.     {
  2360.         if (type == ESM::REC_DCOU)
  2361.         {
  2362.             while (reader.isNextSub("ID__"))
  2363.             {
  2364.                 std::string id = reader.getHString();
  2365.                 int count;
  2366.                 reader.getHNT(count, "COUN");
  2367.                 if (MWBase::Environment::get().getWorld()->getStore().find(id))
  2368.                     mDeathCount[id] = count;
  2369.             }
  2370.         }
  2371.     }
  2372.  
  2373.     void Actors::clear()
  2374.     {
  2375.         PtrActorMap::iterator it(mActors.begin());
  2376.         for (; it != mActors.end(); ++it)
  2377.         {
  2378.             delete it->second;
  2379.             it->second = nullptr;
  2380.         }
  2381.         mActors.clear();
  2382.         mDeathCount.clear();
  2383.     }
  2384.  
  2385.     void Actors::updateMagicEffects(const MWWorld::Ptr &ptr)
  2386.     {
  2387.         adjustMagicEffects(ptr);
  2388.         calculateCreatureStatModifiers(ptr, 0.f);
  2389.         if (ptr.getClass().isNpc())
  2390.             calculateNpcStatModifiers(ptr, 0.f);
  2391.     }
  2392.  
  2393.     bool Actors::isReadyToBlock(const MWWorld::Ptr &ptr) const
  2394.     {
  2395.         PtrActorMap::const_iterator it = mActors.find(ptr);
  2396.         if (it == mActors.end())
  2397.             return false;
  2398.  
  2399.         return it->second->getCharacterController()->isReadyToBlock();
  2400.     }
  2401.  
  2402.     bool Actors::isCastingSpell(const MWWorld::Ptr &ptr) const
  2403.     {
  2404.         PtrActorMap::const_iterator it = mActors.find(ptr);
  2405.         if (it == mActors.end())
  2406.             return false;
  2407.  
  2408.         return it->second->getCharacterController()->isCastingSpell();
  2409.     }
  2410.  
  2411.     bool Actors::isAttackingOrSpell(const MWWorld::Ptr& ptr) const
  2412.     {
  2413.         PtrActorMap::const_iterator it = mActors.find(ptr);
  2414.         if (it == mActors.end())
  2415.             return false;
  2416.         CharacterController* ctrl = it->second->getCharacterController();
  2417.  
  2418.         return ctrl->isAttackingOrSpell();
  2419.     }
  2420.  
  2421.     void Actors::fastForwardAi()
  2422.     {
  2423.         if (!MWBase::Environment::get().getMechanicsManager()->isAIActive())
  2424.             return;
  2425.  
  2426.         // making a copy since fast-forward could move actor to a different cell and invalidate the mActors iterator
  2427.         PtrActorMap map = mActors;
  2428.         for (PtrActorMap::iterator it = map.begin(); it != map.end(); ++it)
  2429.         {
  2430.             MWWorld::Ptr ptr = it->first;
  2431.             if (ptr == getPlayer()
  2432.                     || !isConscious(ptr)
  2433.                     || ptr.getClass().getCreatureStats(ptr).isParalyzed())
  2434.                 continue;
  2435.             MWMechanics::AiSequence& seq = ptr.getClass().getCreatureStats(ptr).getAiSequence();
  2436.             seq.fastForward(ptr);
  2437.         }
  2438.     }
  2439. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement