1 #include "creature.hpp"
2 
3 #include <components/misc/rng.hpp>
4 #include <components/debug/debuglog.hpp>
5 #include <components/esm/loadcrea.hpp>
6 #include <components/esm/creaturestate.hpp>
7 #include <components/settings/settings.hpp>
8 
9 #include "../mwmechanics/creaturestats.hpp"
10 #include "../mwmechanics/magiceffects.hpp"
11 #include "../mwmechanics/movement.hpp"
12 #include "../mwmechanics/disease.hpp"
13 #include "../mwmechanics/spellcasting.hpp"
14 #include "../mwmechanics/difficultyscaling.hpp"
15 
16 #include "../mwbase/environment.hpp"
17 #include "../mwbase/mechanicsmanager.hpp"
18 #include "../mwbase/windowmanager.hpp"
19 #include "../mwbase/world.hpp"
20 #include "../mwbase/soundmanager.hpp"
21 
22 #include "../mwworld/ptr.hpp"
23 #include "../mwworld/actiontalk.hpp"
24 #include "../mwworld/actionopen.hpp"
25 #include "../mwworld/failedaction.hpp"
26 #include "../mwworld/customdata.hpp"
27 #include "../mwworld/containerstore.hpp"
28 #include "../mwworld/cellstore.hpp"
29 #include "../mwworld/localscripts.hpp"
30 
31 #include "../mwrender/renderinginterface.hpp"
32 #include "../mwrender/objects.hpp"
33 
34 #include "../mwgui/tooltips.hpp"
35 
36 #include "../mwworld/inventorystore.hpp"
37 
38 #include "../mwmechanics/npcstats.hpp"
39 #include "../mwmechanics/combat.hpp"
40 #include "../mwmechanics/actorutil.hpp"
41 
42 namespace
43 {
isFlagBitSet(const MWWorld::ConstPtr & ptr,ESM::Creature::Flags bitMask)44     bool isFlagBitSet(const MWWorld::ConstPtr &ptr, ESM::Creature::Flags bitMask)
45     {
46         return (ptr.get<ESM::Creature>()->mBase->mFlags & bitMask) != 0;
47     }
48 }
49 
50 namespace MWClass
51 {
52 
53     class CreatureCustomData : public MWWorld::TypedCustomData<CreatureCustomData>
54     {
55     public:
56         MWMechanics::CreatureStats mCreatureStats;
57         std::unique_ptr<MWWorld::ContainerStore> mContainerStore; // may be InventoryStore for some creatures
58         MWMechanics::Movement mMovement;
59 
60         CreatureCustomData() = default;
61         CreatureCustomData(const CreatureCustomData& other);
62         CreatureCustomData(CreatureCustomData&& other) = default;
63 
asCreatureCustomData()64         CreatureCustomData& asCreatureCustomData() override
65         {
66             return *this;
67         }
asCreatureCustomData() const68         const CreatureCustomData& asCreatureCustomData() const override
69         {
70             return *this;
71         }
72     };
73 
CreatureCustomData(const CreatureCustomData & other)74     CreatureCustomData::CreatureCustomData(const CreatureCustomData& other)
75         : mCreatureStats(other.mCreatureStats),
76           mContainerStore(other.mContainerStore->clone()),
77           mMovement(other.mMovement)
78     {
79     }
80 
getGmst()81     const Creature::GMST& Creature::getGmst()
82     {
83         static GMST gmst;
84         static bool inited = false;
85         if (!inited)
86         {
87             const MWBase::World *world = MWBase::Environment::get().getWorld();
88             const MWWorld::Store<ESM::GameSetting> &store = world->getStore().get<ESM::GameSetting>();
89             gmst.fMinWalkSpeedCreature = store.find("fMinWalkSpeedCreature");
90             gmst.fMaxWalkSpeedCreature = store.find("fMaxWalkSpeedCreature");
91             gmst.fEncumberedMoveEffect = store.find("fEncumberedMoveEffect");
92             gmst.fSneakSpeedMultiplier = store.find("fSneakSpeedMultiplier");
93             gmst.fAthleticsRunBonus = store.find("fAthleticsRunBonus");
94             gmst.fBaseRunMultiplier = store.find("fBaseRunMultiplier");
95             gmst.fMinFlySpeed = store.find("fMinFlySpeed");
96             gmst.fMaxFlySpeed = store.find("fMaxFlySpeed");
97             gmst.fSwimRunBase = store.find("fSwimRunBase");
98             gmst.fSwimRunAthleticsMult = store.find("fSwimRunAthleticsMult");
99             gmst.fKnockDownMult = store.find("fKnockDownMult");
100             gmst.iKnockDownOddsMult = store.find("iKnockDownOddsMult");
101             gmst.iKnockDownOddsBase = store.find("iKnockDownOddsBase");
102             inited = true;
103         }
104         return gmst;
105     }
106 
ensureCustomData(const MWWorld::Ptr & ptr) const107     void Creature::ensureCustomData (const MWWorld::Ptr& ptr) const
108     {
109         if (!ptr.getRefData().getCustomData())
110         {
111             std::unique_ptr<CreatureCustomData> data (new CreatureCustomData);
112 
113             MWWorld::LiveCellRef<ESM::Creature> *ref = ptr.get<ESM::Creature>();
114 
115             // creature stats
116             data->mCreatureStats.setAttribute(ESM::Attribute::Strength, ref->mBase->mData.mStrength);
117             data->mCreatureStats.setAttribute(ESM::Attribute::Intelligence, ref->mBase->mData.mIntelligence);
118             data->mCreatureStats.setAttribute(ESM::Attribute::Willpower, ref->mBase->mData.mWillpower);
119             data->mCreatureStats.setAttribute(ESM::Attribute::Agility, ref->mBase->mData.mAgility);
120             data->mCreatureStats.setAttribute(ESM::Attribute::Speed, ref->mBase->mData.mSpeed);
121             data->mCreatureStats.setAttribute(ESM::Attribute::Endurance, ref->mBase->mData.mEndurance);
122             data->mCreatureStats.setAttribute(ESM::Attribute::Personality, ref->mBase->mData.mPersonality);
123             data->mCreatureStats.setAttribute(ESM::Attribute::Luck, ref->mBase->mData.mLuck);
124             data->mCreatureStats.setHealth(static_cast<float>(ref->mBase->mData.mHealth));
125             data->mCreatureStats.setMagicka(static_cast<float>(ref->mBase->mData.mMana));
126             data->mCreatureStats.setFatigue(static_cast<float>(ref->mBase->mData.mFatigue));
127 
128             data->mCreatureStats.setLevel(ref->mBase->mData.mLevel);
129 
130             data->mCreatureStats.getAiSequence().fill(ref->mBase->mAiPackage);
131 
132             data->mCreatureStats.setAiSetting (MWMechanics::CreatureStats::AI_Hello, ref->mBase->mAiData.mHello);
133             data->mCreatureStats.setAiSetting (MWMechanics::CreatureStats::AI_Fight, ref->mBase->mAiData.mFight);
134             data->mCreatureStats.setAiSetting (MWMechanics::CreatureStats::AI_Flee, ref->mBase->mAiData.mFlee);
135             data->mCreatureStats.setAiSetting (MWMechanics::CreatureStats::AI_Alarm, ref->mBase->mAiData.mAlarm);
136 
137             // Persistent actors with 0 health do not play death animation
138             if (data->mCreatureStats.isDead())
139                 data->mCreatureStats.setDeathAnimationFinished(isPersistent(ptr));
140 
141             // spells
142             bool spellsInitialised = data->mCreatureStats.getSpells().setSpells(ref->mBase->mId);
143             if (!spellsInitialised)
144                 data->mCreatureStats.getSpells().addAllToInstance(ref->mBase->mSpells.mList);
145 
146             // inventory
147             bool hasInventory = hasInventoryStore(ptr);
148             if (hasInventory)
149                 data->mContainerStore = std::make_unique<MWWorld::InventoryStore>();
150             else
151                 data->mContainerStore = std::make_unique<MWWorld::ContainerStore>();
152 
153             data->mCreatureStats.setGoldPool(ref->mBase->mData.mGold);
154 
155             data->mCreatureStats.setNeedRecalcDynamicStats(false);
156 
157             // store
158             ptr.getRefData().setCustomData(std::move(data));
159 
160             getContainerStore(ptr).fill(ref->mBase->mInventory, ptr.getCellRef().getRefId());
161 
162             if (hasInventory)
163                 getInventoryStore(ptr).autoEquip(ptr);
164         }
165     }
166 
insertObjectRendering(const MWWorld::Ptr & ptr,const std::string & model,MWRender::RenderingInterface & renderingInterface) const167     void Creature::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const
168     {
169         MWRender::Objects& objects = renderingInterface.getObjects();
170         objects.insertCreature(ptr, model, hasInventoryStore(ptr));
171     }
172 
getModel(const MWWorld::ConstPtr & ptr) const173     std::string Creature::getModel(const MWWorld::ConstPtr &ptr) const
174     {
175         const MWWorld::LiveCellRef<ESM::Creature> *ref = ptr.get<ESM::Creature>();
176 
177         const std::string &model = ref->mBase->mModel;
178         if (!model.empty()) {
179             return "meshes\\" + model;
180         }
181         return "";
182     }
183 
getModelsToPreload(const MWWorld::Ptr & ptr,std::vector<std::string> & models) const184     void Creature::getModelsToPreload(const MWWorld::Ptr &ptr, std::vector<std::string> &models) const
185     {
186         std::string model = getModel(ptr);
187         if (!model.empty())
188             models.push_back(model);
189 
190         // FIXME: use const version of InventoryStore functions once they are available
191         if (hasInventoryStore(ptr))
192         {
193             const MWWorld::InventoryStore& invStore = getInventoryStore(ptr);
194             for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot)
195             {
196                 MWWorld::ConstContainerStoreIterator equipped = invStore.getSlot(slot);
197                 if (equipped != invStore.end())
198                 {
199                     model = equipped->getClass().getModel(*equipped);
200                     if (!model.empty())
201                         models.push_back(model);
202                 }
203             }
204         }
205     }
206 
getName(const MWWorld::ConstPtr & ptr) const207     std::string Creature::getName (const MWWorld::ConstPtr& ptr) const
208     {
209         const MWWorld::LiveCellRef<ESM::Creature> *ref = ptr.get<ESM::Creature>();
210         const std::string& name = ref->mBase->mName;
211 
212         return !name.empty() ? name : ref->mBase->mId;
213     }
214 
getCreatureStats(const MWWorld::Ptr & ptr) const215     MWMechanics::CreatureStats& Creature::getCreatureStats (const MWWorld::Ptr& ptr) const
216     {
217         ensureCustomData (ptr);
218 
219         return ptr.getRefData().getCustomData()->asCreatureCustomData().mCreatureStats;
220     }
221 
222 
hit(const MWWorld::Ptr & ptr,float attackStrength,int type) const223     void Creature::hit(const MWWorld::Ptr& ptr, float attackStrength, int type) const
224     {
225         MWWorld::LiveCellRef<ESM::Creature> *ref =
226             ptr.get<ESM::Creature>();
227         const MWWorld::Store<ESM::GameSetting> &gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
228         MWMechanics::CreatureStats &stats = getCreatureStats(ptr);
229 
230         if (stats.getDrawState() != MWMechanics::DrawState_Weapon)
231             return;
232 
233         // Get the weapon used (if hand-to-hand, weapon = inv.end())
234         MWWorld::Ptr weapon;
235         if (hasInventoryStore(ptr))
236         {
237             MWWorld::InventoryStore &inv = getInventoryStore(ptr);
238             MWWorld::ContainerStoreIterator weaponslot = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
239             if (weaponslot != inv.end() && weaponslot->getTypeName() == typeid(ESM::Weapon).name())
240                 weapon = *weaponslot;
241         }
242 
243         MWMechanics::applyFatigueLoss(ptr, weapon, attackStrength);
244 
245         float dist = gmst.find("fCombatDistance")->mValue.getFloat();
246         if (!weapon.isEmpty())
247             dist *= weapon.get<ESM::Weapon>()->mBase->mData.mReach;
248 
249         // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result.
250         std::vector<MWWorld::Ptr> targetActors;
251         stats.getAiSequence().getCombatTargets(targetActors);
252 
253         std::pair<MWWorld::Ptr, osg::Vec3f> result = MWBase::Environment::get().getWorld()->getHitContact(ptr, dist, targetActors);
254         if (result.first.isEmpty())
255             return; // Didn't hit anything
256 
257         MWWorld::Ptr victim = result.first;
258 
259         if (!victim.getClass().isActor())
260             return; // Can't hit non-actors
261 
262         osg::Vec3f hitPosition (result.second);
263 
264         float hitchance = MWMechanics::getHitChance(ptr, victim, ref->mBase->mData.mCombat);
265 
266         if(Misc::Rng::roll0to99() >= hitchance)
267         {
268             victim.getClass().onHit(victim, 0.0f, false, MWWorld::Ptr(), ptr, osg::Vec3f(), false);
269             MWMechanics::reduceWeaponCondition(0.f, false, weapon, ptr);
270             return;
271         }
272 
273         int min,max;
274         switch (type)
275         {
276         case 0:
277             min = ref->mBase->mData.mAttack[0];
278             max = ref->mBase->mData.mAttack[1];
279             break;
280         case 1:
281             min = ref->mBase->mData.mAttack[2];
282             max = ref->mBase->mData.mAttack[3];
283             break;
284         case 2:
285         default:
286             min = ref->mBase->mData.mAttack[4];
287             max = ref->mBase->mData.mAttack[5];
288             break;
289         }
290 
291         float damage = min + (max - min) * attackStrength;
292         bool healthdmg = true;
293         if (!weapon.isEmpty())
294         {
295             const unsigned char *attack = nullptr;
296             if(type == ESM::Weapon::AT_Chop)
297                 attack = weapon.get<ESM::Weapon>()->mBase->mData.mChop;
298             else if(type == ESM::Weapon::AT_Slash)
299                 attack = weapon.get<ESM::Weapon>()->mBase->mData.mSlash;
300             else if(type == ESM::Weapon::AT_Thrust)
301                 attack = weapon.get<ESM::Weapon>()->mBase->mData.mThrust;
302             if(attack)
303             {
304                 damage = attack[0] + ((attack[1]-attack[0])*attackStrength);
305                 MWMechanics::adjustWeaponDamage(damage, weapon, ptr);
306                 MWMechanics::resistNormalWeapon(victim, ptr, weapon, damage);
307                 MWMechanics::reduceWeaponCondition(damage, true, weapon, ptr);
308             }
309 
310             // Apply "On hit" enchanted weapons
311             MWMechanics::applyOnStrikeEnchantment(ptr, victim, weapon, hitPosition);
312         }
313         else if (isBipedal(ptr))
314         {
315             MWMechanics::getHandToHandDamage(ptr, victim, damage, healthdmg, attackStrength);
316         }
317 
318         MWMechanics::applyElementalShields(ptr, victim);
319 
320         if (MWMechanics::blockMeleeAttack(ptr, victim, weapon, damage, attackStrength))
321             damage = 0;
322 
323         MWMechanics::diseaseContact(victim, ptr);
324 
325         victim.getClass().onHit(victim, damage, healthdmg, weapon, ptr, hitPosition, true);
326     }
327 
onHit(const MWWorld::Ptr & ptr,float damage,bool ishealth,const MWWorld::Ptr & object,const MWWorld::Ptr & attacker,const osg::Vec3f & hitPosition,bool successful) const328     void Creature::onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful) const
329     {
330         MWMechanics::CreatureStats& stats = getCreatureStats(ptr);
331 
332         // NOTE: 'object' and/or 'attacker' may be empty.
333         if (!attacker.isEmpty() && attacker.getClass().isActor() && !stats.getAiSequence().isInCombat(attacker))
334             stats.setAttacked(true);
335 
336         // Self defense
337         bool setOnPcHitMe = true; // Note OnPcHitMe is not set for friendly hits.
338 
339         // No retaliation for totally static creatures (they have no movement or attacks anyway)
340         if (isMobile(ptr) && !attacker.isEmpty())
341             setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker);
342 
343         // Attacker and target store each other as hitattemptactor if they have no one stored yet
344         if (!attacker.isEmpty() && attacker.getClass().isActor())
345         {
346             MWMechanics::CreatureStats& statsAttacker = attacker.getClass().getCreatureStats(attacker);
347             // First handle the attacked actor
348             if ((stats.getHitAttemptActorId() == -1)
349                 && (statsAttacker.getAiSequence().isInCombat(ptr)
350                     || attacker == MWMechanics::getPlayer()))
351                 stats.setHitAttemptActorId(statsAttacker.getActorId());
352 
353             // Next handle the attacking actor
354             if ((statsAttacker.getHitAttemptActorId() == -1)
355                 && (statsAttacker.getAiSequence().isInCombat(ptr)
356                     || attacker == MWMechanics::getPlayer()))
357                 statsAttacker.setHitAttemptActorId(stats.getActorId());
358         }
359 
360         if (!object.isEmpty())
361             stats.setLastHitAttemptObject(object.getCellRef().getRefId());
362 
363         if (setOnPcHitMe && !attacker.isEmpty() && attacker == MWMechanics::getPlayer())
364         {
365             const std::string &script = ptr.get<ESM::Creature>()->mBase->mScript;
366             /* Set the OnPCHitMe script variable. The script is responsible for clearing it. */
367             if(!script.empty())
368                 ptr.getRefData().getLocals().setVarByInt(script, "onpchitme", 1);
369         }
370 
371         if (!successful)
372         {
373             // Missed
374             if (!attacker.isEmpty() && attacker == MWMechanics::getPlayer())
375                 MWBase::Environment::get().getSoundManager()->playSound3D(ptr, "miss", 1.0f, 1.0f);
376             return;
377         }
378 
379         if (!object.isEmpty())
380             stats.setLastHitObject(object.getCellRef().getRefId());
381 
382         if (damage < 0.001f)
383             damage = 0;
384 
385         if (damage > 0.f)
386         {
387             if (!attacker.isEmpty())
388             {
389                 // Check for knockdown
390                 float agilityTerm = stats.getAttribute(ESM::Attribute::Agility).getModified() * getGmst().fKnockDownMult->mValue.getFloat();
391                 float knockdownTerm = stats.getAttribute(ESM::Attribute::Agility).getModified()
392                         * getGmst().iKnockDownOddsMult->mValue.getInteger() * 0.01f + getGmst().iKnockDownOddsBase->mValue.getInteger();
393                 if (ishealth && agilityTerm <= damage && knockdownTerm <= Misc::Rng::roll0to99())
394                     stats.setKnockedDown(true);
395                 else
396                     stats.setHitRecovery(true); // Is this supposed to always occur?
397             }
398 
399             if(ishealth)
400             {
401                 damage *= damage / (damage + getArmorRating(ptr));
402                 damage = std::max(1.f, damage);
403                 if (!attacker.isEmpty())
404                 {
405                     damage = scaleDamage(damage, attacker, ptr);
406                     MWBase::Environment::get().getWorld()->spawnBloodEffect(ptr, hitPosition);
407                 }
408 
409                 MWBase::Environment::get().getSoundManager()->playSound3D(ptr, "Health Damage", 1.0f, 1.0f);
410 
411                 MWMechanics::DynamicStat<float> health(stats.getHealth());
412                 health.setCurrent(health.getCurrent() - damage);
413                 stats.setHealth(health);
414             }
415             else
416             {
417                 MWMechanics::DynamicStat<float> fatigue(stats.getFatigue());
418                 fatigue.setCurrent(fatigue.getCurrent() - damage, true);
419                 stats.setFatigue(fatigue);
420             }
421         }
422     }
423 
activate(const MWWorld::Ptr & ptr,const MWWorld::Ptr & actor) const424     std::shared_ptr<MWWorld::Action> Creature::activate (const MWWorld::Ptr& ptr,
425         const MWWorld::Ptr& actor) const
426     {
427         if(actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf())
428         {
429             const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
430             const ESM::Sound *sound = store.get<ESM::Sound>().searchRandom("WolfCreature");
431 
432             std::shared_ptr<MWWorld::Action> action(new MWWorld::FailedAction("#{sWerewolfRefusal}"));
433             if(sound) action->setSound(sound->mId);
434 
435             return action;
436         }
437 
438         const MWMechanics::CreatureStats& stats = getCreatureStats(ptr);
439 
440         if(stats.isDead())
441         {
442             bool canLoot = Settings::Manager::getBool ("can loot during death animation", "Game");
443 
444             // by default user can loot friendly actors during death animation
445             if (canLoot && !stats.getAiSequence().isInCombat())
446                 return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr));
447 
448             // otherwise wait until death animation
449             if(stats.isDeathAnimationFinished())
450                 return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr));
451         }
452         else if (!stats.getAiSequence().isInCombat() && !stats.getKnockedDown())
453             return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionTalk(ptr));
454 
455         // Tribunal and some mod companions oddly enough must use open action as fallback
456         if (!getScript(ptr).empty() && ptr.getRefData().getLocals().getIntVar(getScript(ptr), "companion"))
457             return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr));
458 
459         return std::shared_ptr<MWWorld::Action>(new MWWorld::FailedAction(""));
460     }
461 
getContainerStore(const MWWorld::Ptr & ptr) const462     MWWorld::ContainerStore& Creature::getContainerStore (const MWWorld::Ptr& ptr) const
463     {
464         ensureCustomData (ptr);
465 
466         return *ptr.getRefData().getCustomData()->asCreatureCustomData().mContainerStore;
467     }
468 
getInventoryStore(const MWWorld::Ptr & ptr) const469     MWWorld::InventoryStore& Creature::getInventoryStore(const MWWorld::Ptr &ptr) const
470     {
471         if (hasInventoryStore(ptr))
472             return dynamic_cast<MWWorld::InventoryStore&>(getContainerStore(ptr));
473         else
474             throw std::runtime_error("this creature has no inventory store");
475     }
476 
hasInventoryStore(const MWWorld::Ptr & ptr) const477     bool Creature::hasInventoryStore(const MWWorld::Ptr &ptr) const
478     {
479         return isFlagBitSet(ptr, ESM::Creature::Weapon);
480     }
481 
getScript(const MWWorld::ConstPtr & ptr) const482     std::string Creature::getScript (const MWWorld::ConstPtr& ptr) const
483     {
484         const MWWorld::LiveCellRef<ESM::Creature> *ref = ptr.get<ESM::Creature>();
485 
486         return ref->mBase->mScript;
487     }
488 
isEssential(const MWWorld::ConstPtr & ptr) const489     bool Creature::isEssential (const MWWorld::ConstPtr& ptr) const
490     {
491         return isFlagBitSet(ptr, ESM::Creature::Essential);
492     }
493 
registerSelf()494     void Creature::registerSelf()
495     {
496         std::shared_ptr<Class> instance (new Creature);
497 
498         registerClass (typeid (ESM::Creature).name(), instance);
499     }
500 
getMaxSpeed(const MWWorld::Ptr & ptr) const501     float Creature::getMaxSpeed(const MWWorld::Ptr &ptr) const
502     {
503         const MWMechanics::CreatureStats& stats = getCreatureStats(ptr);
504 
505         if (stats.isParalyzed() || stats.getKnockedDown() || stats.isDead())
506             return 0.f;
507 
508         const GMST& gmst = getGmst();
509 
510         const MWBase::World *world = MWBase::Environment::get().getWorld();
511         const MWMechanics::MagicEffects &mageffects = stats.getMagicEffects();
512 
513         float moveSpeed;
514 
515         if(getEncumbrance(ptr) > getCapacity(ptr))
516             moveSpeed = 0.0f;
517         else if(canFly(ptr) || (mageffects.get(ESM::MagicEffect::Levitate).getMagnitude() > 0 &&
518                 world->isLevitationEnabled()))
519         {
520             float flySpeed = 0.01f*(stats.getAttribute(ESM::Attribute::Speed).getModified() +
521                                     mageffects.get(ESM::MagicEffect::Levitate).getMagnitude());
522             flySpeed = gmst.fMinFlySpeed->mValue.getFloat() + flySpeed*(gmst.fMaxFlySpeed->mValue.getFloat() - gmst.fMinFlySpeed->mValue.getFloat());
523             const float normalizedEncumbrance = getNormalizedEncumbrance(ptr);
524             flySpeed *= 1.0f - gmst.fEncumberedMoveEffect->mValue.getFloat() * normalizedEncumbrance;
525             flySpeed = std::max(0.0f, flySpeed);
526             moveSpeed = flySpeed;
527         }
528         else if(world->isSwimming(ptr))
529             moveSpeed = getSwimSpeed(ptr);
530         else
531             moveSpeed = getWalkSpeed(ptr);
532 
533         return moveSpeed;
534     }
535 
getMovementSettings(const MWWorld::Ptr & ptr) const536     MWMechanics::Movement& Creature::getMovementSettings (const MWWorld::Ptr& ptr) const
537     {
538         ensureCustomData (ptr);
539 
540         return ptr.getRefData().getCustomData()->asCreatureCustomData().mMovement;
541     }
542 
hasToolTip(const MWWorld::ConstPtr & ptr) const543     bool Creature::hasToolTip(const MWWorld::ConstPtr& ptr) const
544     {
545         if (!ptr.getRefData().getCustomData() || MWBase::Environment::get().getWindowManager()->isGuiMode())
546             return true;
547 
548         const CreatureCustomData& customData = ptr.getRefData().getCustomData()->asCreatureCustomData();
549 
550         if (customData.mCreatureStats.isDead() && customData.mCreatureStats.isDeathAnimationFinished())
551             return true;
552 
553         return !customData.mCreatureStats.getAiSequence().isInCombat();
554     }
555 
getToolTipInfo(const MWWorld::ConstPtr & ptr,int count) const556     MWGui::ToolTipInfo Creature::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const
557     {
558         const MWWorld::LiveCellRef<ESM::Creature> *ref = ptr.get<ESM::Creature>();
559 
560         MWGui::ToolTipInfo info;
561         info.caption = MyGUI::TextIterator::toTagsString(getName(ptr));
562 
563         std::string text;
564         if (MWBase::Environment::get().getWindowManager()->getFullHelp())
565             text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script");
566         info.text = text;
567 
568         return info;
569     }
570 
getArmorRating(const MWWorld::Ptr & ptr) const571     float Creature::getArmorRating (const MWWorld::Ptr& ptr) const
572     {
573         // Equipment armor rating is deliberately ignored.
574         return getCreatureStats(ptr).getMagicEffects().get(ESM::MagicEffect::Shield).getMagnitude();
575     }
576 
getCapacity(const MWWorld::Ptr & ptr) const577     float Creature::getCapacity (const MWWorld::Ptr& ptr) const
578     {
579         const MWMechanics::CreatureStats& stats = getCreatureStats (ptr);
580         return stats.getAttribute(ESM::Attribute::Strength).getModified() * 5;
581     }
582 
getServices(const MWWorld::ConstPtr & actor) const583     int Creature::getServices(const MWWorld::ConstPtr &actor) const
584     {
585         return actor.get<ESM::Creature>()->mBase->mAiData.mServices;
586     }
587 
isPersistent(const MWWorld::ConstPtr & actor) const588     bool Creature::isPersistent(const MWWorld::ConstPtr &actor) const
589     {
590         const MWWorld::LiveCellRef<ESM::Creature>* ref = actor.get<ESM::Creature>();
591         return ref->mBase->mPersistent;
592     }
593 
getSoundIdFromSndGen(const MWWorld::Ptr & ptr,const std::string & name) const594     std::string Creature::getSoundIdFromSndGen(const MWWorld::Ptr &ptr, const std::string &name) const
595     {
596         int type = getSndGenTypeFromName(ptr, name);
597         if (type < 0)
598             return std::string();
599 
600         std::vector<const ESM::SoundGenerator*> sounds;
601         std::vector<const ESM::SoundGenerator*> fallbacksounds;
602 
603         MWWorld::LiveCellRef<ESM::Creature>* ref = ptr.get<ESM::Creature>();
604 
605         const std::string& ourId = (ref->mBase->mOriginal.empty()) ? ptr.getCellRef().getRefId() : ref->mBase->mOriginal;
606 
607         const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
608         auto sound = store.get<ESM::SoundGenerator>().begin();
609         while (sound != store.get<ESM::SoundGenerator>().end())
610         {
611             if (type == sound->mType && !sound->mCreature.empty() && Misc::StringUtils::ciEqual(ourId, sound->mCreature))
612                 sounds.push_back(&*sound);
613             if (type == sound->mType && sound->mCreature.empty())
614                 fallbacksounds.push_back(&*sound);
615             ++sound;
616         }
617 
618         if (sounds.empty())
619         {
620             const std::string model = getModel(ptr);
621             if (!model.empty())
622             {
623                 for (const ESM::Creature &creature : store.get<ESM::Creature>())
624                 {
625                     if (creature.mId != ourId && creature.mOriginal != ourId && !creature.mModel.empty()
626                      && Misc::StringUtils::ciEqual(model, "meshes\\" + creature.mModel))
627                     {
628                         const std::string& fallbackId = !creature.mOriginal.empty() ? creature.mOriginal : creature.mId;
629                         sound = store.get<ESM::SoundGenerator>().begin();
630                         while (sound != store.get<ESM::SoundGenerator>().end())
631                         {
632                             if (type == sound->mType && !sound->mCreature.empty()
633                              && Misc::StringUtils::ciEqual(fallbackId, sound->mCreature))
634                                 sounds.push_back(&*sound);
635                             ++sound;
636                         }
637                         break;
638                     }
639                 }
640             }
641         }
642 
643         if (!sounds.empty())
644             return sounds[Misc::Rng::rollDice(sounds.size())]->mSound;
645         if (!fallbacksounds.empty())
646             return fallbacksounds[Misc::Rng::rollDice(fallbacksounds.size())]->mSound;
647 
648         return std::string();
649     }
650 
copyToCellImpl(const MWWorld::ConstPtr & ptr,MWWorld::CellStore & cell) const651     MWWorld::Ptr Creature::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const
652     {
653         const MWWorld::LiveCellRef<ESM::Creature> *ref = ptr.get<ESM::Creature>();
654 
655         return MWWorld::Ptr(cell.insert(ref), &cell);
656     }
657 
isBipedal(const MWWorld::ConstPtr & ptr) const658     bool Creature::isBipedal(const MWWorld::ConstPtr &ptr) const
659     {
660         return isFlagBitSet(ptr, ESM::Creature::Bipedal);
661     }
662 
canFly(const MWWorld::ConstPtr & ptr) const663     bool Creature::canFly(const MWWorld::ConstPtr &ptr) const
664     {
665         return isFlagBitSet(ptr, ESM::Creature::Flies);
666     }
667 
canSwim(const MWWorld::ConstPtr & ptr) const668     bool Creature::canSwim(const MWWorld::ConstPtr &ptr) const
669     {
670         return isFlagBitSet(ptr, static_cast<ESM::Creature::Flags>(ESM::Creature::Swims | ESM::Creature::Bipedal));
671     }
672 
canWalk(const MWWorld::ConstPtr & ptr) const673     bool Creature::canWalk(const MWWorld::ConstPtr &ptr) const
674     {
675         return isFlagBitSet(ptr, static_cast<ESM::Creature::Flags>(ESM::Creature::Walks | ESM::Creature::Bipedal));
676     }
677 
getSndGenTypeFromName(const MWWorld::Ptr & ptr,const std::string & name)678     int Creature::getSndGenTypeFromName(const MWWorld::Ptr &ptr, const std::string &name)
679     {
680         if(name == "left")
681         {
682             MWBase::World *world = MWBase::Environment::get().getWorld();
683             if(world->isFlying(ptr))
684                 return -1;
685             osg::Vec3f pos(ptr.getRefData().getPosition().asVec3());
686             if(world->isUnderwater(ptr.getCell(), pos) || world->isWalkingOnWater(ptr))
687                 return ESM::SoundGenerator::SwimLeft;
688             if(world->isOnGround(ptr))
689                 return ESM::SoundGenerator::LeftFoot;
690             return -1;
691         }
692         if(name == "right")
693         {
694             MWBase::World *world = MWBase::Environment::get().getWorld();
695             if(world->isFlying(ptr))
696                 return -1;
697             osg::Vec3f pos(ptr.getRefData().getPosition().asVec3());
698             if(world->isUnderwater(ptr.getCell(), pos) || world->isWalkingOnWater(ptr))
699                 return ESM::SoundGenerator::SwimRight;
700             if(world->isOnGround(ptr))
701                 return ESM::SoundGenerator::RightFoot;
702             return -1;
703         }
704         if(name == "swimleft")
705             return ESM::SoundGenerator::SwimLeft;
706         if(name == "swimright")
707             return ESM::SoundGenerator::SwimRight;
708         if(name == "moan")
709             return ESM::SoundGenerator::Moan;
710         if(name == "roar")
711             return ESM::SoundGenerator::Roar;
712         if(name == "scream")
713             return ESM::SoundGenerator::Scream;
714         if(name == "land")
715             return ESM::SoundGenerator::Land;
716 
717         throw std::runtime_error(std::string("Unexpected soundgen type: ")+name);
718     }
719 
getSkill(const MWWorld::Ptr & ptr,int skill) const720     float Creature::getSkill(const MWWorld::Ptr &ptr, int skill) const
721     {
722         MWWorld::LiveCellRef<ESM::Creature> *ref =
723             ptr.get<ESM::Creature>();
724 
725         const ESM::Skill* skillRecord = MWBase::Environment::get().getWorld()->getStore().get<ESM::Skill>().find(skill);
726 
727         switch (skillRecord->mData.mSpecialization)
728         {
729         case ESM::Class::Combat:
730             return ref->mBase->mData.mCombat;
731         case ESM::Class::Magic:
732             return ref->mBase->mData.mMagic;
733         case ESM::Class::Stealth:
734             return ref->mBase->mData.mStealth;
735         default:
736             throw std::runtime_error("invalid specialisation");
737         }
738     }
739 
getBloodTexture(const MWWorld::ConstPtr & ptr) const740     int Creature::getBloodTexture(const MWWorld::ConstPtr &ptr) const
741     {
742         return ptr.get<ESM::Creature>()->mBase->mBloodType;
743     }
744 
readAdditionalState(const MWWorld::Ptr & ptr,const ESM::ObjectState & state) const745     void Creature::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state)
746         const
747     {
748         if (!state.mHasCustomState)
749             return;
750 
751         if (state.mVersion > 0)
752         {
753             if (!ptr.getRefData().getCustomData())
754             {
755                 // Create a CustomData, but don't fill it from ESM records (not needed)
756                 std::unique_ptr<CreatureCustomData> data (new CreatureCustomData);
757 
758                 if (hasInventoryStore(ptr))
759                     data->mContainerStore = std::make_unique<MWWorld::InventoryStore>();
760                 else
761                     data->mContainerStore = std::make_unique<MWWorld::ContainerStore>();
762 
763                 ptr.getRefData().setCustomData (std::move(data));
764             }
765         }
766         else
767             ensureCustomData(ptr); // in openmw 0.30 savegames not all state was saved yet, so need to load it regardless.
768 
769         CreatureCustomData& customData = ptr.getRefData().getCustomData()->asCreatureCustomData();
770         const ESM::CreatureState& creatureState = state.asCreatureState();
771         customData.mContainerStore->readState (creatureState.mInventory);
772         bool spellsInitialised = customData.mCreatureStats.getSpells().setSpells(ptr.get<ESM::Creature>()->mBase->mId);
773         if(spellsInitialised)
774             customData.mCreatureStats.getSpells().clear();
775         customData.mCreatureStats.readState (creatureState.mCreatureStats);
776     }
777 
writeAdditionalState(const MWWorld::ConstPtr & ptr,ESM::ObjectState & state) const778     void Creature::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state)
779         const
780     {
781         if (!ptr.getRefData().getCustomData())
782         {
783             state.mHasCustomState = false;
784             return;
785         }
786 
787         if (ptr.getRefData().getCount() <= 0)
788         {
789             state.mHasCustomState = false;
790             return;
791         }
792 
793         const CreatureCustomData& customData = ptr.getRefData().getCustomData()->asCreatureCustomData();
794         ESM::CreatureState& creatureState = state.asCreatureState();
795         customData.mContainerStore->writeState (creatureState.mInventory);
796         customData.mCreatureStats.writeState (creatureState.mCreatureStats);
797     }
798 
getBaseGold(const MWWorld::ConstPtr & ptr) const799     int Creature::getBaseGold(const MWWorld::ConstPtr& ptr) const
800     {
801         return ptr.get<ESM::Creature>()->mBase->mData.mGold;
802     }
803 
respawn(const MWWorld::Ptr & ptr) const804     void Creature::respawn(const MWWorld::Ptr &ptr) const
805     {
806         const MWMechanics::CreatureStats& creatureStats = getCreatureStats(ptr);
807         if (ptr.getRefData().getCount() > 0 && !creatureStats.isDead())
808             return;
809 
810         if (!creatureStats.isDeathAnimationFinished())
811             return;
812 
813         const MWWorld::Store<ESM::GameSetting>& gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
814         static const float fCorpseRespawnDelay = gmst.find("fCorpseRespawnDelay")->mValue.getFloat();
815         static const float fCorpseClearDelay = gmst.find("fCorpseClearDelay")->mValue.getFloat();
816 
817         float delay = ptr.getRefData().getCount() == 0 ? fCorpseClearDelay : std::min(fCorpseRespawnDelay, fCorpseClearDelay);
818 
819         if (isFlagBitSet(ptr, ESM::Creature::Respawn)
820                 && creatureStats.getTimeOfDeath() + delay <= MWBase::Environment::get().getWorld()->getTimeStamp())
821         {
822             if (ptr.getCellRef().hasContentFile())
823             {
824                 if (ptr.getRefData().getCount() == 0)
825                 {
826                     ptr.getRefData().setCount(1);
827                     const std::string& script = getScript(ptr);
828                     if(!script.empty())
829                         MWBase::Environment::get().getWorld()->getLocalScripts().add(script, ptr);
830                 }
831 
832                 MWBase::Environment::get().getWorld()->removeContainerScripts(ptr);
833                 ptr.getRefData().setCustomData(nullptr);
834 
835                 // Reset to original position
836                 MWBase::Environment::get().getWorld()->moveObject(ptr, ptr.getCellRef().getPosition().pos[0],
837                         ptr.getCellRef().getPosition().pos[1],
838                         ptr.getCellRef().getPosition().pos[2]);
839             }
840         }
841     }
842 
getBaseFightRating(const MWWorld::ConstPtr & ptr) const843     int Creature::getBaseFightRating(const MWWorld::ConstPtr &ptr) const
844     {
845         const MWWorld::LiveCellRef<ESM::Creature> *ref = ptr.get<ESM::Creature>();
846         return ref->mBase->mAiData.mFight;
847     }
848 
adjustScale(const MWWorld::ConstPtr & ptr,osg::Vec3f & scale,bool) const849     void Creature::adjustScale(const MWWorld::ConstPtr &ptr, osg::Vec3f &scale, bool /* rendering */) const
850     {
851         const MWWorld::LiveCellRef<ESM::Creature> *ref = ptr.get<ESM::Creature>();
852         scale *= ref->mBase->mScale;
853     }
854 
setBaseAISetting(const std::string & id,MWMechanics::CreatureStats::AiSetting setting,int value) const855     void Creature::setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value) const
856     {
857         MWMechanics::setBaseAISetting<ESM::Creature>(id, setting, value);
858     }
859 
modifyBaseInventory(const std::string & actorId,const std::string & itemId,int amount) const860     void Creature::modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount) const
861     {
862         MWMechanics::modifyBaseInventory<ESM::Creature>(actorId, itemId, amount);
863     }
864 
getWalkSpeed(const MWWorld::Ptr & ptr) const865     float Creature::getWalkSpeed(const MWWorld::Ptr& ptr) const
866     {
867         const MWMechanics::CreatureStats& stats = getCreatureStats(ptr);
868         const GMST& gmst = getGmst();
869 
870         return gmst.fMinWalkSpeedCreature->mValue.getFloat()
871                 + 0.01f * stats.getAttribute(ESM::Attribute::Speed).getModified()
872                 * (gmst.fMaxWalkSpeedCreature->mValue.getFloat() - gmst.fMinWalkSpeedCreature->mValue.getFloat());
873     }
874 
getRunSpeed(const MWWorld::Ptr & ptr) const875     float Creature::getRunSpeed(const MWWorld::Ptr& ptr) const
876     {
877         return getWalkSpeed(ptr);
878     }
879 
getSwimSpeed(const MWWorld::Ptr & ptr) const880     float Creature::getSwimSpeed(const MWWorld::Ptr& ptr) const
881     {
882         const MWMechanics::CreatureStats& stats = getCreatureStats(ptr);
883         const GMST& gmst = getGmst();
884         const MWMechanics::MagicEffects& mageffects = stats.getMagicEffects();
885 
886         return getWalkSpeed(ptr)
887             * (1.0f + 0.01f * mageffects.get(ESM::MagicEffect::SwiftSwim).getMagnitude())
888             * (gmst.fSwimRunBase->mValue.getFloat()
889                + 0.01f * getSkill(ptr, ESM::Skill::Athletics) * gmst.fSwimRunAthleticsMult->mValue.getFloat());
890     }
891 }
892