1 #include "npc.hpp"
2 
3 #include <memory>
4 
5 #include <components/misc/constants.hpp>
6 #include <components/misc/rng.hpp>
7 
8 #include <components/debug/debuglog.hpp>
9 #include <components/esm/loadmgef.hpp>
10 #include <components/esm/loadnpc.hpp>
11 #include <components/esm/npcstate.hpp>
12 #include <components/settings/settings.hpp>
13 
14 #include "../mwbase/environment.hpp"
15 #include "../mwbase/world.hpp"
16 #include "../mwbase/mechanicsmanager.hpp"
17 #include "../mwbase/windowmanager.hpp"
18 #include "../mwbase/dialoguemanager.hpp"
19 #include "../mwbase/soundmanager.hpp"
20 
21 #include "../mwmechanics/creaturestats.hpp"
22 #include "../mwmechanics/npcstats.hpp"
23 #include "../mwmechanics/movement.hpp"
24 #include "../mwmechanics/spellcasting.hpp"
25 #include "../mwmechanics/disease.hpp"
26 #include "../mwmechanics/combat.hpp"
27 #include "../mwmechanics/autocalcspell.hpp"
28 #include "../mwmechanics/difficultyscaling.hpp"
29 #include "../mwmechanics/weapontype.hpp"
30 #include "../mwmechanics/actorutil.hpp"
31 
32 #include "../mwworld/ptr.hpp"
33 #include "../mwworld/actiontalk.hpp"
34 #include "../mwworld/actionopen.hpp"
35 #include "../mwworld/failedaction.hpp"
36 #include "../mwworld/inventorystore.hpp"
37 #include "../mwworld/customdata.hpp"
38 #include "../mwworld/cellstore.hpp"
39 #include "../mwworld/localscripts.hpp"
40 
41 #include "../mwrender/objects.hpp"
42 #include "../mwrender/renderinginterface.hpp"
43 #include "../mwrender/npcanimation.hpp"
44 
45 #include "../mwgui/tooltips.hpp"
46 
47 namespace
48 {
49 
is_even(double d)50     int is_even(double d) {
51         double int_part;
52         modf(d / 2.0, &int_part);
53         return 2.0 * int_part == d;
54     }
55 
round_ieee_754(double d)56     int round_ieee_754(double d) {
57         double i = floor(d);
58         d -= i;
59         if(d < 0.5)
60             return static_cast<int>(i);
61         if(d > 0.5)
62             return static_cast<int>(i) + 1;
63         if(is_even(i))
64             return static_cast<int>(i);
65         return static_cast<int>(i) + 1;
66     }
67 
autoCalculateAttributes(const ESM::NPC * npc,MWMechanics::CreatureStats & creatureStats)68     void autoCalculateAttributes (const ESM::NPC* npc, MWMechanics::CreatureStats& creatureStats)
69     {
70         // race bonus
71         const ESM::Race *race =
72             MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(npc->mRace);
73 
74         bool male = (npc->mFlags & ESM::NPC::Female) == 0;
75 
76         int level = creatureStats.getLevel();
77         for (int i=0; i<ESM::Attribute::Length; ++i)
78         {
79             const ESM::Race::MaleFemale& attribute = race->mData.mAttributeValues[i];
80             creatureStats.setAttribute(i, male ? attribute.mMale : attribute.mFemale);
81         }
82 
83         // class bonus
84         const ESM::Class *class_ =
85             MWBase::Environment::get().getWorld()->getStore().get<ESM::Class>().find(npc->mClass);
86 
87         for (int i=0; i<2; ++i)
88         {
89             int attribute = class_->mData.mAttribute[i];
90             if (attribute>=0 && attribute<8)
91             {
92                 creatureStats.setAttribute(attribute,
93                     creatureStats.getAttribute(attribute).getBase() + 10);
94             }
95         }
96 
97         // skill bonus
98         for (int attribute=0; attribute < ESM::Attribute::Length; ++attribute)
99         {
100             float modifierSum = 0;
101 
102             for (int j=0; j<ESM::Skill::Length; ++j)
103             {
104                 const ESM::Skill* skill = MWBase::Environment::get().getWorld()->getStore().get<ESM::Skill>().find(j);
105 
106                 if (skill->mData.mAttribute != attribute)
107                     continue;
108 
109                 // is this a minor or major skill?
110                 float add=0.2f;
111                 for (int k=0; k<5; ++k)
112                 {
113                     if (class_->mData.mSkills[k][0] == j)
114                         add=0.5;
115                 }
116                 for (int k=0; k<5; ++k)
117                 {
118                     if (class_->mData.mSkills[k][1] == j)
119                         add=1.0;
120                 }
121                 modifierSum += add;
122             }
123             creatureStats.setAttribute(attribute, std::min(
124                                            round_ieee_754(creatureStats.getAttribute(attribute).getBase()
125                 + (level-1) * modifierSum), 100) );
126         }
127 
128         // initial health
129         float strength = creatureStats.getAttribute(ESM::Attribute::Strength).getBase();
130         float endurance = creatureStats.getAttribute(ESM::Attribute::Endurance).getBase();
131 
132         int multiplier = 3;
133 
134         if (class_->mData.mSpecialization == ESM::Class::Combat)
135             multiplier += 2;
136         else if (class_->mData.mSpecialization == ESM::Class::Stealth)
137             multiplier += 1;
138 
139         if (class_->mData.mAttribute[0] == ESM::Attribute::Endurance
140             || class_->mData.mAttribute[1] == ESM::Attribute::Endurance)
141             multiplier += 1;
142 
143         creatureStats.setHealth(floor(0.5f * (strength + endurance)) + multiplier * (creatureStats.getLevel() - 1));
144     }
145 
146     /**
147      * @brief autoCalculateSkills
148      *
149      * Skills are calculated with following formulae ( http://www.uesp.net/wiki/Morrowind:NPCs#Skills ):
150      *
151      * Skills: (Level - 1) × (Majority Multiplier + Specialization Multiplier)
152      *
153      *         The Majority Multiplier is 1.0 for a Major or Minor Skill, or 0.1 for a Miscellaneous Skill.
154      *
155      *         The Specialization Multiplier is 0.5 for a Skill in the same Specialization as the class,
156      *         zero for other Skills.
157      *
158      * and by adding class, race, specialization bonus.
159      */
autoCalculateSkills(const ESM::NPC * npc,MWMechanics::NpcStats & npcStats,const MWWorld::Ptr & ptr,bool spellsInitialised)160     void autoCalculateSkills(const ESM::NPC* npc, MWMechanics::NpcStats& npcStats, const MWWorld::Ptr& ptr, bool spellsInitialised)
161     {
162         const ESM::Class *class_ =
163             MWBase::Environment::get().getWorld()->getStore().get<ESM::Class>().find(npc->mClass);
164 
165         unsigned int level = npcStats.getLevel();
166 
167         const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(npc->mRace);
168 
169 
170         for (int i = 0; i < 2; ++i)
171         {
172             int bonus = (i==0) ? 10 : 25;
173 
174             for (int i2 = 0; i2 < 5; ++i2)
175             {
176                 int index = class_->mData.mSkills[i2][i];
177                 if (index >= 0 && index < ESM::Skill::Length)
178                 {
179                     npcStats.getSkill(index).setBase (npcStats.getSkill(index).getBase() + bonus);
180                 }
181             }
182         }
183 
184         for (int skillIndex = 0; skillIndex < ESM::Skill::Length; ++skillIndex)
185         {
186             float majorMultiplier = 0.1f;
187             float specMultiplier = 0.0f;
188 
189             int raceBonus = 0;
190             int specBonus = 0;
191 
192             for (int raceSkillIndex = 0; raceSkillIndex < 7; ++raceSkillIndex)
193             {
194                 if (race->mData.mBonus[raceSkillIndex].mSkill == skillIndex)
195                 {
196                     raceBonus = race->mData.mBonus[raceSkillIndex].mBonus;
197                     break;
198                 }
199             }
200 
201             for (int k = 0; k < 5; ++k)
202             {
203                 // is this a minor or major skill?
204                 if ((class_->mData.mSkills[k][0] == skillIndex) || (class_->mData.mSkills[k][1] == skillIndex))
205                 {
206                     majorMultiplier = 1.0f;
207                     break;
208                 }
209             }
210 
211             // is this skill in the same Specialization as the class?
212             const ESM::Skill* skill = MWBase::Environment::get().getWorld()->getStore().get<ESM::Skill>().find(skillIndex);
213             if (skill->mData.mSpecialization == class_->mData.mSpecialization)
214             {
215                 specMultiplier = 0.5f;
216                 specBonus = 5;
217             }
218 
219             npcStats.getSkill(skillIndex).setBase(
220                   std::min(
221                     round_ieee_754(
222                             npcStats.getSkill(skillIndex).getBase()
223                     + 5
224                     + raceBonus
225                     + specBonus
226                     +(int(level)-1) * (majorMultiplier + specMultiplier)), 100)); // Must gracefully handle level 0
227         }
228 
229         int skills[ESM::Skill::Length];
230         for (int i=0; i<ESM::Skill::Length; ++i)
231             skills[i] = npcStats.getSkill(i).getBase();
232 
233         int attributes[ESM::Attribute::Length];
234         for (int i=0; i<ESM::Attribute::Length; ++i)
235             attributes[i] = npcStats.getAttribute(i).getBase();
236 
237         if (!spellsInitialised)
238         {
239             std::vector<std::string> spells = MWMechanics::autoCalcNpcSpells(skills, attributes, race);
240             npcStats.getSpells().addAllToInstance(spells);
241         }
242     }
243 }
244 
245 namespace MWClass
246 {
247 
248     class NpcCustomData : public MWWorld::TypedCustomData<NpcCustomData>
249     {
250     public:
251         MWMechanics::NpcStats mNpcStats;
252         MWMechanics::Movement mMovement;
253         MWWorld::InventoryStore mInventoryStore;
254 
asNpcCustomData()255         NpcCustomData& asNpcCustomData() override
256         {
257             return *this;
258         }
asNpcCustomData() const259         const NpcCustomData& asNpcCustomData() const override
260         {
261             return *this;
262         }
263     };
264 
getGmst()265     const Npc::GMST& Npc::getGmst()
266     {
267         static GMST gmst;
268         static bool inited = false;
269         if(!inited)
270         {
271             const MWBase::World *world = MWBase::Environment::get().getWorld();
272             const MWWorld::Store<ESM::GameSetting> &store = world->getStore().get<ESM::GameSetting>();
273 
274             gmst.fMinWalkSpeed = store.find("fMinWalkSpeed");
275             gmst.fMaxWalkSpeed = store.find("fMaxWalkSpeed");
276             gmst.fEncumberedMoveEffect = store.find("fEncumberedMoveEffect");
277             gmst.fSneakSpeedMultiplier = store.find("fSneakSpeedMultiplier");
278             gmst.fAthleticsRunBonus = store.find("fAthleticsRunBonus");
279             gmst.fBaseRunMultiplier = store.find("fBaseRunMultiplier");
280             gmst.fMinFlySpeed = store.find("fMinFlySpeed");
281             gmst.fMaxFlySpeed = store.find("fMaxFlySpeed");
282             gmst.fSwimRunBase = store.find("fSwimRunBase");
283             gmst.fSwimRunAthleticsMult = store.find("fSwimRunAthleticsMult");
284             gmst.fJumpEncumbranceBase = store.find("fJumpEncumbranceBase");
285             gmst.fJumpEncumbranceMultiplier = store.find("fJumpEncumbranceMultiplier");
286             gmst.fJumpAcrobaticsBase = store.find("fJumpAcrobaticsBase");
287             gmst.fJumpAcroMultiplier = store.find("fJumpAcroMultiplier");
288             gmst.fJumpRunMultiplier = store.find("fJumpRunMultiplier");
289             gmst.fWereWolfRunMult = store.find("fWereWolfRunMult");
290             gmst.fKnockDownMult = store.find("fKnockDownMult");
291             gmst.iKnockDownOddsMult = store.find("iKnockDownOddsMult");
292             gmst.iKnockDownOddsBase = store.find("iKnockDownOddsBase");
293             gmst.fCombatArmorMinMult = store.find("fCombatArmorMinMult");
294 
295             inited = true;
296         }
297         return gmst;
298     }
299 
ensureCustomData(const MWWorld::Ptr & ptr) const300     void Npc::ensureCustomData (const MWWorld::Ptr& ptr) const
301     {
302         if (!ptr.getRefData().getCustomData())
303         {
304             std::unique_ptr<NpcCustomData> data(new NpcCustomData);
305 
306             MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>();
307 
308             bool spellsInitialised = data->mNpcStats.getSpells().setSpells(ref->mBase->mId);
309 
310             // creature stats
311             int gold=0;
312             if(ref->mBase->mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS)
313             {
314                 gold = ref->mBase->mNpdt.mGold;
315 
316                 for (unsigned int i=0; i< ESM::Skill::Length; ++i)
317                     data->mNpcStats.getSkill (i).setBase (ref->mBase->mNpdt.mSkills[i]);
318 
319                 data->mNpcStats.setAttribute(ESM::Attribute::Strength, ref->mBase->mNpdt.mStrength);
320                 data->mNpcStats.setAttribute(ESM::Attribute::Intelligence, ref->mBase->mNpdt.mIntelligence);
321                 data->mNpcStats.setAttribute(ESM::Attribute::Willpower, ref->mBase->mNpdt.mWillpower);
322                 data->mNpcStats.setAttribute(ESM::Attribute::Agility, ref->mBase->mNpdt.mAgility);
323                 data->mNpcStats.setAttribute(ESM::Attribute::Speed, ref->mBase->mNpdt.mSpeed);
324                 data->mNpcStats.setAttribute(ESM::Attribute::Endurance, ref->mBase->mNpdt.mEndurance);
325                 data->mNpcStats.setAttribute(ESM::Attribute::Personality, ref->mBase->mNpdt.mPersonality);
326                 data->mNpcStats.setAttribute(ESM::Attribute::Luck, ref->mBase->mNpdt.mLuck);
327 
328                 data->mNpcStats.setHealth (ref->mBase->mNpdt.mHealth);
329                 data->mNpcStats.setMagicka (ref->mBase->mNpdt.mMana);
330                 data->mNpcStats.setFatigue (ref->mBase->mNpdt.mFatigue);
331 
332                 data->mNpcStats.setLevel(ref->mBase->mNpdt.mLevel);
333                 data->mNpcStats.setBaseDisposition(ref->mBase->mNpdt.mDisposition);
334                 data->mNpcStats.setReputation(ref->mBase->mNpdt.mReputation);
335 
336                 data->mNpcStats.setNeedRecalcDynamicStats(false);
337             }
338             else
339             {
340                 gold = ref->mBase->mNpdt.mGold;
341 
342                 for (int i=0; i<3; ++i)
343                     data->mNpcStats.setDynamic (i, 10);
344 
345                 data->mNpcStats.setLevel(ref->mBase->mNpdt.mLevel);
346                 data->mNpcStats.setBaseDisposition(ref->mBase->mNpdt.mDisposition);
347                 data->mNpcStats.setReputation(ref->mBase->mNpdt.mReputation);
348 
349                 autoCalculateAttributes(ref->mBase, data->mNpcStats);
350                 autoCalculateSkills(ref->mBase, data->mNpcStats, ptr, spellsInitialised);
351 
352                 data->mNpcStats.setNeedRecalcDynamicStats(true);
353             }
354 
355             // Persistent actors with 0 health do not play death animation
356             if (data->mNpcStats.isDead())
357                 data->mNpcStats.setDeathAnimationFinished(isPersistent(ptr));
358 
359             // race powers
360             const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(ref->mBase->mRace);
361             data->mNpcStats.getSpells().addAllToInstance(race->mPowers.mList);
362 
363             if (!ref->mBase->mFaction.empty())
364             {
365                 static const int iAutoRepFacMod = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>()
366                         .find("iAutoRepFacMod")->mValue.getInteger();
367                 static const int iAutoRepLevMod = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>()
368                         .find("iAutoRepLevMod")->mValue.getInteger();
369                 int rank = ref->mBase->getFactionRank();
370 
371                 data->mNpcStats.setReputation(iAutoRepFacMod * (rank+1) + iAutoRepLevMod * (data->mNpcStats.getLevel()-1));
372             }
373 
374             data->mNpcStats.getAiSequence().fill(ref->mBase->mAiPackage);
375 
376             data->mNpcStats.setAiSetting (MWMechanics::CreatureStats::AI_Hello, ref->mBase->mAiData.mHello);
377             data->mNpcStats.setAiSetting (MWMechanics::CreatureStats::AI_Fight, ref->mBase->mAiData.mFight);
378             data->mNpcStats.setAiSetting (MWMechanics::CreatureStats::AI_Flee, ref->mBase->mAiData.mFlee);
379             data->mNpcStats.setAiSetting (MWMechanics::CreatureStats::AI_Alarm, ref->mBase->mAiData.mAlarm);
380 
381             // spells
382             if (!spellsInitialised)
383                 data->mNpcStats.getSpells().addAllToInstance(ref->mBase->mSpells.mList);
384 
385             // inventory
386             // setting ownership is used to make the NPC auto-equip his initial equipment only, and not bartered items
387             data->mInventoryStore.fill(ref->mBase->mInventory, ptr.getCellRef().getRefId());
388 
389             data->mNpcStats.setGoldPool(gold);
390 
391             // store
392             ptr.getRefData().setCustomData(std::move(data));
393 
394             getInventoryStore(ptr).autoEquip(ptr);
395         }
396     }
397 
insertObjectRendering(const MWWorld::Ptr & ptr,const std::string & model,MWRender::RenderingInterface & renderingInterface) const398     void Npc::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const
399     {
400         renderingInterface.getObjects().insertNPC(ptr);
401     }
402 
isPersistent(const MWWorld::ConstPtr & actor) const403     bool Npc::isPersistent(const MWWorld::ConstPtr &actor) const
404     {
405         const MWWorld::LiveCellRef<ESM::NPC>* ref = actor.get<ESM::NPC>();
406         return ref->mBase->mPersistent;
407     }
408 
getModel(const MWWorld::ConstPtr & ptr) const409     std::string Npc::getModel(const MWWorld::ConstPtr &ptr) const
410     {
411         const MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>();
412 
413         std::string model = Settings::Manager::getString("baseanim", "Models");
414         const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(ref->mBase->mRace);
415         if(race->mData.mFlags & ESM::Race::Beast)
416             model = Settings::Manager::getString("baseanimkna", "Models");
417 
418         return model;
419     }
420 
getModelsToPreload(const MWWorld::Ptr & ptr,std::vector<std::string> & models) const421     void Npc::getModelsToPreload(const MWWorld::Ptr &ptr, std::vector<std::string> &models) const
422     {
423         const MWWorld::LiveCellRef<ESM::NPC> *npc = ptr.get<ESM::NPC>();
424         const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().search(npc->mBase->mRace);
425         if(race && race->mData.mFlags & ESM::Race::Beast)
426             models.emplace_back(Settings::Manager::getString("baseanimkna", "Models"));
427 
428         // keep these always loaded just in case
429         models.emplace_back(Settings::Manager::getString("xargonianswimkna", "Models"));
430         models.emplace_back(Settings::Manager::getString("xbaseanimfemale", "Models"));
431         models.emplace_back(Settings::Manager::getString("xbaseanim", "Models"));
432 
433         if (!npc->mBase->mModel.empty())
434             models.push_back("meshes/"+npc->mBase->mModel);
435 
436         if (!npc->mBase->mHead.empty())
437         {
438             const ESM::BodyPart* head = MWBase::Environment::get().getWorld()->getStore().get<ESM::BodyPart>().search(npc->mBase->mHead);
439             if (head)
440                 models.push_back("meshes/"+head->mModel);
441         }
442         if (!npc->mBase->mHair.empty())
443         {
444             const ESM::BodyPart* hair = MWBase::Environment::get().getWorld()->getStore().get<ESM::BodyPart>().search(npc->mBase->mHair);
445             if (hair)
446                 models.push_back("meshes/"+hair->mModel);
447         }
448 
449         bool female = (npc->mBase->mFlags & ESM::NPC::Female);
450 
451         // FIXME: use const version of InventoryStore functions once they are available
452         // preload equipped items
453         const MWWorld::InventoryStore& invStore = getInventoryStore(ptr);
454         for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot)
455         {
456             MWWorld::ConstContainerStoreIterator equipped = invStore.getSlot(slot);
457             if (equipped != invStore.end())
458             {
459                 std::vector<ESM::PartReference> parts;
460                 if(equipped->getTypeName() == typeid(ESM::Clothing).name())
461                 {
462                     const ESM::Clothing *clothes = equipped->get<ESM::Clothing>()->mBase;
463                     parts = clothes->mParts.mParts;
464                 }
465                 else if(equipped->getTypeName() == typeid(ESM::Armor).name())
466                 {
467                     const ESM::Armor *armor = equipped->get<ESM::Armor>()->mBase;
468                     parts = armor->mParts.mParts;
469                 }
470                 else
471                 {
472                     std::string model = equipped->getClass().getModel(*equipped);
473                     if (!model.empty())
474                         models.push_back(model);
475                 }
476 
477                 for (std::vector<ESM::PartReference>::const_iterator it = parts.begin(); it != parts.end(); ++it)
478                 {
479                     std::string partname = female ? it->mFemale : it->mMale;
480                     if (partname.empty())
481                         partname = female ? it->mMale : it->mFemale;
482                     const ESM::BodyPart* part = MWBase::Environment::get().getWorld()->getStore().get<ESM::BodyPart>().search(partname);
483                     if (part && !part->mModel.empty())
484                         models.push_back("meshes/"+part->mModel);
485                 }
486             }
487         }
488 
489         // preload body parts
490         if (race)
491         {
492             const std::vector<const ESM::BodyPart*>& parts = MWRender::NpcAnimation::getBodyParts(Misc::StringUtils::lowerCase(race->mId), female, false, false);
493             for (std::vector<const ESM::BodyPart*>::const_iterator it = parts.begin(); it != parts.end(); ++it)
494             {
495                 const ESM::BodyPart* part = *it;
496                 if (part && !part->mModel.empty())
497                     models.push_back("meshes/"+part->mModel);
498             }
499         }
500 
501     }
502 
getName(const MWWorld::ConstPtr & ptr) const503     std::string Npc::getName (const MWWorld::ConstPtr& ptr) const
504     {
505         if(ptr.getRefData().getCustomData() && ptr.getRefData().getCustomData()->asNpcCustomData().mNpcStats.isWerewolf())
506         {
507             const MWBase::World *world = MWBase::Environment::get().getWorld();
508             const MWWorld::Store<ESM::GameSetting> &store = world->getStore().get<ESM::GameSetting>();
509 
510             return store.find("sWerewolfPopup")->mValue.getString();
511         }
512 
513         const MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>();
514         const std::string& name = ref->mBase->mName;
515 
516         return !name.empty() ? name : ref->mBase->mId;
517     }
518 
getCreatureStats(const MWWorld::Ptr & ptr) const519     MWMechanics::CreatureStats& Npc::getCreatureStats (const MWWorld::Ptr& ptr) const
520     {
521         ensureCustomData (ptr);
522 
523         return ptr.getRefData().getCustomData()->asNpcCustomData().mNpcStats;
524     }
525 
getNpcStats(const MWWorld::Ptr & ptr) const526     MWMechanics::NpcStats& Npc::getNpcStats (const MWWorld::Ptr& ptr) const
527     {
528         ensureCustomData (ptr);
529 
530         return ptr.getRefData().getCustomData()->asNpcCustomData().mNpcStats;
531     }
532 
533 
hit(const MWWorld::Ptr & ptr,float attackStrength,int type) const534     void Npc::hit(const MWWorld::Ptr& ptr, float attackStrength, int type) const
535     {
536         MWBase::World *world = MWBase::Environment::get().getWorld();
537 
538         const MWWorld::Store<ESM::GameSetting> &store = world->getStore().get<ESM::GameSetting>();
539 
540         // Get the weapon used (if hand-to-hand, weapon = inv.end())
541         MWWorld::InventoryStore &inv = getInventoryStore(ptr);
542         MWWorld::ContainerStoreIterator weaponslot = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
543         MWWorld::Ptr weapon = ((weaponslot != inv.end()) ? *weaponslot : MWWorld::Ptr());
544         if(!weapon.isEmpty() && weapon.getTypeName() != typeid(ESM::Weapon).name())
545             weapon = MWWorld::Ptr();
546 
547         MWMechanics::applyFatigueLoss(ptr, weapon, attackStrength);
548 
549         const float fCombatDistance = store.find("fCombatDistance")->mValue.getFloat();
550         float dist = fCombatDistance * (!weapon.isEmpty() ?
551                                weapon.get<ESM::Weapon>()->mBase->mData.mReach :
552                                store.find("fHandToHandReach")->mValue.getFloat());
553 
554         // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result.
555         std::vector<MWWorld::Ptr> targetActors;
556         if (ptr != MWMechanics::getPlayer())
557             getCreatureStats(ptr).getAiSequence().getCombatTargets(targetActors);
558 
559         // TODO: Use second to work out the hit angle
560         std::pair<MWWorld::Ptr, osg::Vec3f> result = world->getHitContact(ptr, dist, targetActors);
561         MWWorld::Ptr victim = result.first;
562         osg::Vec3f hitPosition (result.second);
563         if(victim.isEmpty()) // Didn't hit anything
564             return;
565 
566         const MWWorld::Class &othercls = victim.getClass();
567         if(!othercls.isActor()) // Can't hit non-actors
568             return;
569         MWMechanics::CreatureStats &otherstats = othercls.getCreatureStats(victim);
570         if(otherstats.isDead()) // Can't hit dead actors
571             return;
572 
573         if(ptr == MWMechanics::getPlayer())
574             MWBase::Environment::get().getWindowManager()->setEnemy(victim);
575 
576         int weapskill = ESM::Skill::HandToHand;
577         if(!weapon.isEmpty())
578             weapskill = weapon.getClass().getEquipmentSkill(weapon);
579 
580         float hitchance = MWMechanics::getHitChance(ptr, victim, getSkill(ptr, weapskill));
581 
582         if (Misc::Rng::roll0to99() >= hitchance)
583         {
584             othercls.onHit(victim, 0.0f, false, weapon, ptr, osg::Vec3f(), false);
585             MWMechanics::reduceWeaponCondition(0.f, false, weapon, ptr);
586             return;
587         }
588 
589         bool healthdmg;
590         float damage = 0.0f;
591         if(!weapon.isEmpty())
592         {
593             const unsigned char *attack = nullptr;
594             if(type == ESM::Weapon::AT_Chop)
595                 attack = weapon.get<ESM::Weapon>()->mBase->mData.mChop;
596             else if(type == ESM::Weapon::AT_Slash)
597                 attack = weapon.get<ESM::Weapon>()->mBase->mData.mSlash;
598             else if(type == ESM::Weapon::AT_Thrust)
599                 attack = weapon.get<ESM::Weapon>()->mBase->mData.mThrust;
600             if(attack)
601             {
602                 damage  = attack[0] + ((attack[1]-attack[0])*attackStrength);
603             }
604             MWMechanics::adjustWeaponDamage(damage, weapon, ptr);
605             MWMechanics::resistNormalWeapon(victim, ptr, weapon, damage);
606             MWMechanics::applyWerewolfDamageMult(victim, weapon, damage);
607             MWMechanics::reduceWeaponCondition(damage, true, weapon, ptr);
608             healthdmg = true;
609         }
610         else
611         {
612             MWMechanics::getHandToHandDamage(ptr, victim, damage, healthdmg, attackStrength);
613         }
614         if(ptr == MWMechanics::getPlayer())
615         {
616             skillUsageSucceeded(ptr, weapskill, 0);
617 
618             const MWMechanics::AiSequence& seq = victim.getClass().getCreatureStats(victim).getAiSequence();
619 
620             bool unaware = !seq.isInCombat()
621                     && !MWBase::Environment::get().getMechanicsManager()->awarenessCheck(ptr, victim);
622             if(unaware)
623             {
624                 damage *= store.find("fCombatCriticalStrikeMult")->mValue.getFloat();
625                 MWBase::Environment::get().getWindowManager()->messageBox("#{sTargetCriticalStrike}");
626                 MWBase::Environment::get().getSoundManager()->playSound3D(victim, "critical damage", 1.0f, 1.0f);
627             }
628         }
629 
630         if (othercls.getCreatureStats(victim).getKnockedDown())
631             damage *= store.find("fCombatKODamageMult")->mValue.getFloat();
632 
633         // Apply "On hit" enchanted weapons
634         MWMechanics::applyOnStrikeEnchantment(ptr, victim, weapon, hitPosition);
635 
636         MWMechanics::applyElementalShields(ptr, victim);
637 
638         if (MWMechanics::blockMeleeAttack(ptr, victim, weapon, damage, attackStrength))
639             damage = 0;
640 
641         if (victim == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState())
642             damage = 0;
643 
644         MWMechanics::diseaseContact(victim, ptr);
645 
646         othercls.onHit(victim, damage, healthdmg, weapon, ptr, hitPosition, true);
647     }
648 
onHit(const MWWorld::Ptr & ptr,float damage,bool ishealth,const MWWorld::Ptr & object,const MWWorld::Ptr & attacker,const osg::Vec3f & hitPosition,bool successful) const649     void Npc::onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful) const
650     {
651         MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
652         MWMechanics::CreatureStats& stats = getCreatureStats(ptr);
653         bool wasDead = stats.isDead();
654 
655         // Note OnPcHitMe is not set for friendly hits.
656         bool setOnPcHitMe = true;
657 
658         // NOTE: 'object' and/or 'attacker' may be empty.
659         if (!attacker.isEmpty() && attacker.getClass().isActor() && !stats.getAiSequence().isInCombat(attacker))
660         {
661             stats.setAttacked(true);
662             setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker);
663         }
664 
665         // Attacker and target store each other as hitattemptactor if they have no one stored yet
666         if (!attacker.isEmpty() && attacker.getClass().isActor())
667         {
668             MWMechanics::CreatureStats& statsAttacker = attacker.getClass().getCreatureStats(attacker);
669             // First handle the attacked actor
670             if ((stats.getHitAttemptActorId() == -1)
671                 && (statsAttacker.getAiSequence().isInCombat(ptr)
672                     || attacker == MWMechanics::getPlayer()))
673                 stats.setHitAttemptActorId(statsAttacker.getActorId());
674 
675             // Next handle the attacking actor
676             if ((statsAttacker.getHitAttemptActorId() == -1)
677                 && (statsAttacker.getAiSequence().isInCombat(ptr)
678                     || attacker == MWMechanics::getPlayer()))
679                 statsAttacker.setHitAttemptActorId(stats.getActorId());
680         }
681 
682         if (!object.isEmpty())
683             stats.setLastHitAttemptObject(object.getCellRef().getRefId());
684 
685         if (setOnPcHitMe && !attacker.isEmpty() && attacker == MWMechanics::getPlayer())
686         {
687             const std::string &script = getScript(ptr);
688             /* Set the OnPCHitMe script variable. The script is responsible for clearing it. */
689             if(!script.empty())
690                 ptr.getRefData().getLocals().setVarByInt(script, "onpchitme", 1);
691         }
692 
693         if (!successful)
694         {
695             // Missed
696             if (!attacker.isEmpty() && attacker == MWMechanics::getPlayer())
697                 sndMgr->playSound3D(ptr, "miss", 1.0f, 1.0f);
698             return;
699         }
700 
701         if (!object.isEmpty())
702             stats.setLastHitObject(object.getCellRef().getRefId());
703 
704 
705         if (damage > 0.0f && !object.isEmpty())
706             MWMechanics::resistNormalWeapon(ptr, attacker, object, damage);
707 
708         if (damage < 0.001f)
709             damage = 0;
710 
711         bool godmode = ptr == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState();
712 
713         if (godmode)
714             damage = 0;
715 
716         if (damage > 0.0f && !attacker.isEmpty())
717         {
718             // 'ptr' is losing health. Play a 'hit' voiced dialog entry if not already saying
719             // something, alert the character controller, scripts, etc.
720 
721             const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
722             const GMST& gmst = getGmst();
723 
724             int chance = store.get<ESM::GameSetting>().find("iVoiceHitOdds")->mValue.getInteger();
725             if (Misc::Rng::roll0to99() < chance)
726                 MWBase::Environment::get().getDialogueManager()->say(ptr, "hit");
727 
728             // Check for knockdown
729             float agilityTerm = stats.getAttribute(ESM::Attribute::Agility).getModified() * gmst.fKnockDownMult->mValue.getFloat();
730             float knockdownTerm = stats.getAttribute(ESM::Attribute::Agility).getModified()
731                     * gmst.iKnockDownOddsMult->mValue.getInteger() * 0.01f + gmst.iKnockDownOddsBase->mValue.getInteger();
732             if (ishealth && agilityTerm <= damage && knockdownTerm <= Misc::Rng::roll0to99())
733                 stats.setKnockedDown(true);
734             else
735                 stats.setHitRecovery(true); // Is this supposed to always occur?
736 
737             if (damage > 0 && ishealth)
738             {
739                 // Hit percentages:
740                 // cuirass = 30%
741                 // shield, helmet, greaves, boots, pauldrons = 10% each
742                 // guantlets = 5% each
743                 static const int hitslots[20] = {
744                     MWWorld::InventoryStore::Slot_Cuirass, MWWorld::InventoryStore::Slot_Cuirass,
745                     MWWorld::InventoryStore::Slot_Cuirass, MWWorld::InventoryStore::Slot_Cuirass,
746                     MWWorld::InventoryStore::Slot_Cuirass, MWWorld::InventoryStore::Slot_Cuirass,
747                     MWWorld::InventoryStore::Slot_CarriedLeft, MWWorld::InventoryStore::Slot_CarriedLeft,
748                     MWWorld::InventoryStore::Slot_Helmet, MWWorld::InventoryStore::Slot_Helmet,
749                     MWWorld::InventoryStore::Slot_Greaves, MWWorld::InventoryStore::Slot_Greaves,
750                     MWWorld::InventoryStore::Slot_Boots, MWWorld::InventoryStore::Slot_Boots,
751                     MWWorld::InventoryStore::Slot_LeftPauldron, MWWorld::InventoryStore::Slot_LeftPauldron,
752                     MWWorld::InventoryStore::Slot_RightPauldron, MWWorld::InventoryStore::Slot_RightPauldron,
753                     MWWorld::InventoryStore::Slot_LeftGauntlet, MWWorld::InventoryStore::Slot_RightGauntlet
754                 };
755                 int hitslot = hitslots[Misc::Rng::rollDice(20)];
756 
757                 float unmitigatedDamage = damage;
758                 float x = damage / (damage + getArmorRating(ptr));
759                 damage *= std::max(gmst.fCombatArmorMinMult->mValue.getFloat(), x);
760                 int damageDiff = static_cast<int>(unmitigatedDamage - damage);
761                 damage = std::max(1.f, damage);
762                 damageDiff = std::max(1, damageDiff);
763 
764                 MWWorld::InventoryStore &inv = getInventoryStore(ptr);
765                 MWWorld::ContainerStoreIterator armorslot = inv.getSlot(hitslot);
766                 MWWorld::Ptr armor = ((armorslot != inv.end()) ? *armorslot : MWWorld::Ptr());
767                 bool hasArmor = !armor.isEmpty() && armor.getTypeName() == typeid(ESM::Armor).name();
768                 // If there's no item in the carried left slot or if it is not a shield redistribute the hit.
769                 if (!hasArmor && hitslot == MWWorld::InventoryStore::Slot_CarriedLeft)
770                 {
771                     if (Misc::Rng::rollDice(2) == 0)
772                         hitslot = MWWorld::InventoryStore::Slot_Cuirass;
773                     else
774                         hitslot = MWWorld::InventoryStore::Slot_LeftPauldron;
775                     armorslot = inv.getSlot(hitslot);
776                     if (armorslot != inv.end())
777                     {
778                         armor = *armorslot;
779                         hasArmor = !armor.isEmpty() && armor.getTypeName() == typeid(ESM::Armor).name();
780                     }
781                 }
782                 if (hasArmor)
783                 {
784                     if (!object.isEmpty() || attacker.isEmpty() || attacker.getClass().isNpc()) // Unarmed creature attacks don't affect armor condition
785                     {
786                         int armorhealth = armor.getClass().getItemHealth(armor);
787                         armorhealth -= std::min(damageDiff, armorhealth);
788                         armor.getCellRef().setCharge(armorhealth);
789 
790                         // Armor broken? unequip it
791                         if (armorhealth == 0)
792                             armor = *inv.unequipItem(armor, ptr);
793                     }
794 
795                     if (ptr == MWMechanics::getPlayer())
796                         skillUsageSucceeded(ptr, armor.getClass().getEquipmentSkill(armor), 0);
797 
798                     switch(armor.getClass().getEquipmentSkill(armor))
799                     {
800                         case ESM::Skill::LightArmor:
801                             sndMgr->playSound3D(ptr, "Light Armor Hit", 1.0f, 1.0f);
802                             break;
803                         case ESM::Skill::MediumArmor:
804                             sndMgr->playSound3D(ptr, "Medium Armor Hit", 1.0f, 1.0f);
805                             break;
806                         case ESM::Skill::HeavyArmor:
807                             sndMgr->playSound3D(ptr, "Heavy Armor Hit", 1.0f, 1.0f);
808                             break;
809                     }
810                 }
811                 else if(ptr == MWMechanics::getPlayer())
812                     skillUsageSucceeded(ptr, ESM::Skill::Unarmored, 0);
813             }
814         }
815 
816         if (ishealth)
817         {
818             if (!attacker.isEmpty() && !godmode)
819                 damage = scaleDamage(damage, attacker, ptr);
820 
821             if (damage > 0.0f)
822             {
823                 sndMgr->playSound3D(ptr, "Health Damage", 1.0f, 1.0f);
824                 if (ptr == MWMechanics::getPlayer())
825                     MWBase::Environment::get().getWindowManager()->activateHitOverlay();
826                 if (!attacker.isEmpty())
827                     MWBase::Environment::get().getWorld()->spawnBloodEffect(ptr, hitPosition);
828             }
829             MWMechanics::DynamicStat<float> health(getCreatureStats(ptr).getHealth());
830             health.setCurrent(health.getCurrent() - damage);
831             stats.setHealth(health);
832         }
833         else
834         {
835             MWMechanics::DynamicStat<float> fatigue(getCreatureStats(ptr).getFatigue());
836             fatigue.setCurrent(fatigue.getCurrent() - damage, true);
837             stats.setFatigue(fatigue);
838         }
839 
840         if (!wasDead && getCreatureStats(ptr).isDead())
841         {
842             // NPC was killed
843             if (!attacker.isEmpty() && attacker.getClass().isNpc() && attacker.getClass().getNpcStats(attacker).isWerewolf())
844             {
845                 attacker.getClass().getNpcStats(attacker).addWerewolfKill();
846             }
847 
848             MWBase::Environment::get().getMechanicsManager()->actorKilled(ptr, attacker);
849         }
850     }
851 
activate(const MWWorld::Ptr & ptr,const MWWorld::Ptr & actor) const852     std::shared_ptr<MWWorld::Action> Npc::activate (const MWWorld::Ptr& ptr,
853         const MWWorld::Ptr& actor) const
854     {
855         // player got activated by another NPC
856         if(ptr == MWMechanics::getPlayer())
857             return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionTalk(actor));
858 
859         // Werewolfs can't activate NPCs
860         if(actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf())
861         {
862             const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
863             const ESM::Sound *sound = store.get<ESM::Sound>().searchRandom("WolfNPC");
864 
865             std::shared_ptr<MWWorld::Action> action(new MWWorld::FailedAction("#{sWerewolfRefusal}"));
866             if(sound) action->setSound(sound->mId);
867 
868             return action;
869         }
870 
871         const MWMechanics::CreatureStats& stats = getCreatureStats(ptr);
872 
873         if(stats.isDead())
874         {
875             bool canLoot = Settings::Manager::getBool ("can loot during death animation", "Game");
876 
877             // by default user can loot friendly actors during death animation
878             if (canLoot && !stats.getAiSequence().isInCombat())
879                 return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr));
880 
881             // otherwise wait until death animation
882             if(stats.isDeathAnimationFinished())
883                 return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr));
884         }
885         else if (!stats.getAiSequence().isInCombat())
886         {
887             if (stats.getKnockedDown() || MWBase::Environment::get().getMechanicsManager()->isSneaking(actor))
888                 return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr)); // stealing
889 
890             // Can't talk to werewolves
891             if (!getNpcStats(ptr).isWerewolf())
892                 return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionTalk(ptr));
893         }
894         else // In combat
895         {
896             const bool stealingInCombat = Settings::Manager::getBool ("always allow stealing from knocked out actors", "Game");
897             if (stealingInCombat && stats.getKnockedDown())
898                 return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr)); // stealing
899         }
900 
901         // Tribunal and some mod companions oddly enough must use open action as fallback
902         if (!getScript(ptr).empty() && ptr.getRefData().getLocals().getIntVar(getScript(ptr), "companion"))
903             return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr));
904 
905         return std::shared_ptr<MWWorld::Action> (new MWWorld::FailedAction(""));
906     }
907 
getContainerStore(const MWWorld::Ptr & ptr) const908     MWWorld::ContainerStore& Npc::getContainerStore (const MWWorld::Ptr& ptr)
909         const
910     {
911         ensureCustomData (ptr);
912 
913         return ptr.getRefData().getCustomData()->asNpcCustomData().mInventoryStore;
914     }
915 
getInventoryStore(const MWWorld::Ptr & ptr) const916     MWWorld::InventoryStore& Npc::getInventoryStore (const MWWorld::Ptr& ptr)
917         const
918     {
919         ensureCustomData (ptr);
920 
921         return ptr.getRefData().getCustomData()->asNpcCustomData().mInventoryStore;
922     }
923 
getScript(const MWWorld::ConstPtr & ptr) const924     std::string Npc::getScript (const MWWorld::ConstPtr& ptr) const
925     {
926         const MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>();
927 
928         return ref->mBase->mScript;
929     }
930 
getMaxSpeed(const MWWorld::Ptr & ptr) const931     float Npc::getMaxSpeed(const MWWorld::Ptr& ptr) const
932     {
933         // TODO: This function is called several times per frame for each NPC.
934         // It would be better to calculate it only once per frame for each NPC and save the result in CreatureStats.
935         const MWMechanics::CreatureStats& stats = getCreatureStats(ptr);
936         bool godmode = ptr == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState();
937         if ((!godmode && stats.isParalyzed()) || stats.getKnockedDown() || stats.isDead())
938             return 0.f;
939 
940         const MWBase::World *world = MWBase::Environment::get().getWorld();
941         const GMST& gmst = getGmst();
942 
943         const NpcCustomData *npcdata = static_cast<const NpcCustomData*>(ptr.getRefData().getCustomData());
944         const MWMechanics::MagicEffects &mageffects = npcdata->mNpcStats.getMagicEffects();
945 
946         const float normalizedEncumbrance = getNormalizedEncumbrance(ptr);
947 
948         bool swimming = world->isSwimming(ptr);
949         bool sneaking = MWBase::Environment::get().getMechanicsManager()->isSneaking(ptr);
950         bool running = stats.getStance(MWMechanics::CreatureStats::Stance_Run);
951         bool inair = !world->isOnGround(ptr) && !swimming && !world->isFlying(ptr);
952         running = running && (inair || MWBase::Environment::get().getMechanicsManager()->isRunning(ptr));
953 
954         float moveSpeed;
955         if(getEncumbrance(ptr) > getCapacity(ptr))
956             moveSpeed = 0.0f;
957         else if(mageffects.get(ESM::MagicEffect::Levitate).getMagnitude() > 0 &&
958                 world->isLevitationEnabled())
959         {
960             float flySpeed = 0.01f*(npcdata->mNpcStats.getAttribute(ESM::Attribute::Speed).getModified() +
961                                     mageffects.get(ESM::MagicEffect::Levitate).getMagnitude());
962             flySpeed = gmst.fMinFlySpeed->mValue.getFloat() + flySpeed*(gmst.fMaxFlySpeed->mValue.getFloat() - gmst.fMinFlySpeed->mValue.getFloat());
963             flySpeed *= 1.0f - gmst.fEncumberedMoveEffect->mValue.getFloat() * normalizedEncumbrance;
964             flySpeed = std::max(0.0f, flySpeed);
965             moveSpeed = flySpeed;
966         }
967         else if (swimming)
968             moveSpeed = getSwimSpeed(ptr);
969         else if (running && !sneaking)
970             moveSpeed = getRunSpeed(ptr);
971         else
972             moveSpeed = getWalkSpeed(ptr);
973 
974         if(npcdata->mNpcStats.isWerewolf() && running && npcdata->mNpcStats.getDrawState() == MWMechanics::DrawState_Nothing)
975             moveSpeed *= gmst.fWereWolfRunMult->mValue.getFloat();
976 
977         return moveSpeed;
978     }
979 
getJump(const MWWorld::Ptr & ptr) const980     float Npc::getJump(const MWWorld::Ptr &ptr) const
981     {
982         if(getEncumbrance(ptr) > getCapacity(ptr))
983             return 0.f;
984 
985         const MWMechanics::CreatureStats& stats = getCreatureStats(ptr);
986         bool godmode = ptr == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState();
987         if ((!godmode && stats.isParalyzed()) || stats.getKnockedDown() || stats.isDead())
988             return 0.f;
989 
990         const NpcCustomData *npcdata = static_cast<const NpcCustomData*>(ptr.getRefData().getCustomData());
991         const GMST& gmst = getGmst();
992         const MWMechanics::MagicEffects &mageffects = npcdata->mNpcStats.getMagicEffects();
993         const float encumbranceTerm = gmst.fJumpEncumbranceBase->mValue.getFloat() +
994                                           gmst.fJumpEncumbranceMultiplier->mValue.getFloat() *
995                                           (1.0f - Npc::getNormalizedEncumbrance(ptr));
996 
997         float a = getSkill(ptr, ESM::Skill::Acrobatics);
998         float b = 0.0f;
999         if(a > 50.0f)
1000         {
1001             b = a - 50.0f;
1002             a = 50.0f;
1003         }
1004 
1005         float x = gmst.fJumpAcrobaticsBase->mValue.getFloat() +
1006                   std::pow(a / 15.0f, gmst.fJumpAcroMultiplier->mValue.getFloat());
1007         x += 3.0f * b * gmst.fJumpAcroMultiplier->mValue.getFloat();
1008         x += mageffects.get(ESM::MagicEffect::Jump).getMagnitude() * 64;
1009         x *= encumbranceTerm;
1010 
1011         if(stats.getStance(MWMechanics::CreatureStats::Stance_Run))
1012             x *= gmst.fJumpRunMultiplier->mValue.getFloat();
1013         x *= npcdata->mNpcStats.getFatigueTerm();
1014         x -= -Constants::GravityConst * Constants::UnitsPerMeter;
1015         x /= 3.0f;
1016 
1017         return x;
1018     }
1019 
getMovementSettings(const MWWorld::Ptr & ptr) const1020     MWMechanics::Movement& Npc::getMovementSettings (const MWWorld::Ptr& ptr) const
1021     {
1022         ensureCustomData (ptr);
1023 
1024         return ptr.getRefData().getCustomData()->asNpcCustomData().mMovement;
1025     }
1026 
isEssential(const MWWorld::ConstPtr & ptr) const1027     bool Npc::isEssential (const MWWorld::ConstPtr& ptr) const
1028     {
1029         const MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>();
1030 
1031         return (ref->mBase->mFlags & ESM::NPC::Essential) != 0;
1032     }
1033 
registerSelf()1034     void Npc::registerSelf()
1035     {
1036         std::shared_ptr<Class> instance (new Npc);
1037         registerClass (typeid (ESM::NPC).name(), instance);
1038     }
1039 
hasToolTip(const MWWorld::ConstPtr & ptr) const1040     bool Npc::hasToolTip(const MWWorld::ConstPtr& ptr) const
1041     {
1042         if (!ptr.getRefData().getCustomData() || MWBase::Environment::get().getWindowManager()->isGuiMode())
1043             return true;
1044 
1045         const NpcCustomData& customData = ptr.getRefData().getCustomData()->asNpcCustomData();
1046 
1047         if (customData.mNpcStats.isDead() && customData.mNpcStats.isDeathAnimationFinished())
1048             return true;
1049 
1050         if (!customData.mNpcStats.getAiSequence().isInCombat())
1051             return true;
1052 
1053         const bool stealingInCombat = Settings::Manager::getBool ("always allow stealing from knocked out actors", "Game");
1054         if (stealingInCombat && customData.mNpcStats.getKnockedDown())
1055             return true;
1056 
1057         return false;
1058     }
1059 
getToolTipInfo(const MWWorld::ConstPtr & ptr,int count) const1060     MWGui::ToolTipInfo Npc::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const
1061     {
1062         const MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>();
1063 
1064         bool fullHelp = MWBase::Environment::get().getWindowManager()->getFullHelp();
1065         MWGui::ToolTipInfo info;
1066 
1067         info.caption = MyGUI::TextIterator::toTagsString(getName(ptr));
1068         if(fullHelp && !ref->mBase->mName.empty() && ptr.getRefData().getCustomData() && ptr.getRefData().getCustomData()->asNpcCustomData().mNpcStats.isWerewolf())
1069         {
1070             info.caption += " (";
1071             info.caption += MyGUI::TextIterator::toTagsString(ref->mBase->mName);
1072             info.caption += ")";
1073         }
1074 
1075         if(fullHelp)
1076             info.text = MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script");
1077 
1078         return info;
1079     }
1080 
getCapacity(const MWWorld::Ptr & ptr) const1081     float Npc::getCapacity (const MWWorld::Ptr& ptr) const
1082     {
1083         const MWMechanics::CreatureStats& stats = getCreatureStats (ptr);
1084         static const float fEncumbranceStrMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fEncumbranceStrMult")->mValue.getFloat();
1085         return stats.getAttribute(ESM::Attribute::Strength).getModified()*fEncumbranceStrMult;
1086     }
1087 
getEncumbrance(const MWWorld::Ptr & ptr) const1088     float Npc::getEncumbrance (const MWWorld::Ptr& ptr) const
1089     {
1090         // According to UESP, inventory weight is ignored in werewolf form. Does that include
1091         // feather and burden effects?
1092         return getNpcStats(ptr).isWerewolf() ? 0.0f : Actor::getEncumbrance(ptr);
1093     }
1094 
apply(const MWWorld::Ptr & ptr,const std::string & id,const MWWorld::Ptr & actor) const1095     bool Npc::apply (const MWWorld::Ptr& ptr, const std::string& id,
1096         const MWWorld::Ptr& actor) const
1097     {
1098         MWMechanics::CastSpell cast(ptr, ptr);
1099         return cast.cast(id);
1100     }
1101 
skillUsageSucceeded(const MWWorld::Ptr & ptr,int skill,int usageType,float extraFactor) const1102     void Npc::skillUsageSucceeded (const MWWorld::Ptr& ptr, int skill, int usageType, float extraFactor) const
1103     {
1104         MWMechanics::NpcStats& stats = getNpcStats (ptr);
1105 
1106         if (stats.isWerewolf())
1107             return;
1108 
1109         MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>();
1110 
1111         const ESM::Class *class_ =
1112             MWBase::Environment::get().getWorld()->getStore().get<ESM::Class>().find (
1113                 ref->mBase->mClass
1114             );
1115 
1116         stats.useSkill (skill, *class_, usageType, extraFactor);
1117     }
1118 
getArmorRating(const MWWorld::Ptr & ptr) const1119     float Npc::getArmorRating (const MWWorld::Ptr& ptr) const
1120     {
1121         const MWBase::World *world = MWBase::Environment::get().getWorld();
1122         const MWWorld::Store<ESM::GameSetting> &store = world->getStore().get<ESM::GameSetting>();
1123 
1124         MWMechanics::NpcStats &stats = getNpcStats(ptr);
1125         const MWWorld::InventoryStore &invStore = getInventoryStore(ptr);
1126 
1127         float fUnarmoredBase1 = store.find("fUnarmoredBase1")->mValue.getFloat();
1128         float fUnarmoredBase2 = store.find("fUnarmoredBase2")->mValue.getFloat();
1129         float unarmoredSkill = getSkill(ptr, ESM::Skill::Unarmored);
1130 
1131         float ratings[MWWorld::InventoryStore::Slots];
1132         for(int i = 0;i < MWWorld::InventoryStore::Slots;i++)
1133         {
1134             MWWorld::ConstContainerStoreIterator it = invStore.getSlot(i);
1135             if (it == invStore.end() || it->getTypeName() != typeid(ESM::Armor).name())
1136             {
1137                 // unarmored
1138                 ratings[i] = (fUnarmoredBase1 * unarmoredSkill) * (fUnarmoredBase2 * unarmoredSkill);
1139             }
1140             else
1141             {
1142                 ratings[i] = it->getClass().getEffectiveArmorRating(*it, ptr);
1143 
1144                 // Take in account armor condition
1145                 const bool hasHealth = it->getClass().hasItemHealth(*it);
1146                 if (hasHealth)
1147                 {
1148                     ratings[i] *= it->getClass().getItemNormalizedHealth(*it);
1149                 }
1150             }
1151         }
1152 
1153         float shield = stats.getMagicEffects().get(ESM::MagicEffect::Shield).getMagnitude();
1154 
1155         return ratings[MWWorld::InventoryStore::Slot_Cuirass] * 0.3f
1156                 + (ratings[MWWorld::InventoryStore::Slot_CarriedLeft] + ratings[MWWorld::InventoryStore::Slot_Helmet]
1157                     + ratings[MWWorld::InventoryStore::Slot_Greaves] + ratings[MWWorld::InventoryStore::Slot_Boots]
1158                     + ratings[MWWorld::InventoryStore::Slot_LeftPauldron] + ratings[MWWorld::InventoryStore::Slot_RightPauldron]
1159                     ) * 0.1f
1160                 + (ratings[MWWorld::InventoryStore::Slot_LeftGauntlet] + ratings[MWWorld::InventoryStore::Slot_RightGauntlet])
1161                     * 0.05f
1162                 + shield;
1163     }
1164 
adjustScale(const MWWorld::ConstPtr & ptr,osg::Vec3f & scale,bool rendering) const1165     void Npc::adjustScale(const MWWorld::ConstPtr &ptr, osg::Vec3f&scale, bool rendering) const
1166     {
1167         if (!rendering)
1168             return; // collision meshes are not scaled based on race height
1169                     // having the same collision extents for all races makes the environments easier to test
1170 
1171         const MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>();
1172 
1173         const ESM::Race* race =
1174                 MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(ref->mBase->mRace);
1175 
1176         // Race weight should not affect 1st-person meshes, otherwise it will change hand proportions and can break aiming.
1177         if (ptr == MWMechanics::getPlayer() && ptr.isInCell() && MWBase::Environment::get().getWorld()->isFirstPerson())
1178         {
1179             if (ref->mBase->isMale())
1180                 scale *= race->mData.mHeight.mMale;
1181             else
1182                 scale *= race->mData.mHeight.mFemale;
1183 
1184             return;
1185         }
1186 
1187         if (ref->mBase->isMale())
1188         {
1189             scale.x() *= race->mData.mWeight.mMale;
1190             scale.y() *= race->mData.mWeight.mMale;
1191             scale.z() *= race->mData.mHeight.mMale;
1192         }
1193         else
1194         {
1195             scale.x() *= race->mData.mWeight.mFemale;
1196             scale.y() *= race->mData.mWeight.mFemale;
1197             scale.z() *= race->mData.mHeight.mFemale;
1198         }
1199     }
1200 
getServices(const MWWorld::ConstPtr & actor) const1201     int Npc::getServices(const MWWorld::ConstPtr &actor) const
1202     {
1203         return actor.get<ESM::NPC>()->mBase->mAiData.mServices;
1204     }
1205 
1206 
getSoundIdFromSndGen(const MWWorld::Ptr & ptr,const std::string & name) const1207     std::string Npc::getSoundIdFromSndGen(const MWWorld::Ptr &ptr, const std::string &name) const
1208     {
1209         if(name == "left" || name == "right")
1210         {
1211             MWBase::World *world = MWBase::Environment::get().getWorld();
1212             if(world->isFlying(ptr))
1213                 return std::string();
1214             osg::Vec3f pos(ptr.getRefData().getPosition().asVec3());
1215             if(world->isSwimming(ptr))
1216                 return (name == "left") ? "Swim Left" : "Swim Right";
1217             if(world->isUnderwater(ptr.getCell(), pos) || world->isWalkingOnWater(ptr))
1218                 return (name == "left") ? "FootWaterLeft" : "FootWaterRight";
1219             if(world->isOnGround(ptr))
1220             {
1221                 if (getNpcStats(ptr).isWerewolf()
1222                         && getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Run))
1223                 {
1224                     int weaponType = ESM::Weapon::None;
1225                     MWMechanics::getActiveWeapon(ptr, &weaponType);
1226                     if (weaponType == ESM::Weapon::None)
1227                         return std::string();
1228                 }
1229 
1230                 const MWWorld::InventoryStore &inv = Npc::getInventoryStore(ptr);
1231                 MWWorld::ConstContainerStoreIterator boots = inv.getSlot(MWWorld::InventoryStore::Slot_Boots);
1232                 if(boots == inv.end() || boots->getTypeName() != typeid(ESM::Armor).name())
1233                     return (name == "left") ? "FootBareLeft" : "FootBareRight";
1234 
1235                 switch(boots->getClass().getEquipmentSkill(*boots))
1236                 {
1237                     case ESM::Skill::LightArmor:
1238                         return (name == "left") ? "FootLightLeft" : "FootLightRight";
1239                     case ESM::Skill::MediumArmor:
1240                         return (name == "left") ? "FootMedLeft" : "FootMedRight";
1241                     case ESM::Skill::HeavyArmor:
1242                         return (name == "left") ? "FootHeavyLeft" : "FootHeavyRight";
1243                 }
1244             }
1245             return std::string();
1246         }
1247 
1248         // Morrowind ignores land soundgen for NPCs
1249         if(name == "land")
1250             return std::string();
1251         if(name == "swimleft")
1252             return "Swim Left";
1253         if(name == "swimright")
1254             return "Swim Right";
1255         // TODO: I have no idea what these are supposed to do for NPCs since they use
1256         // voiced dialog for various conditions like health loss and combat taunts. Maybe
1257         // only for biped creatures?
1258 
1259         if(name == "moan")
1260             return std::string();
1261         if(name == "roar")
1262             return std::string();
1263         if(name == "scream")
1264             return std::string();
1265 
1266         throw std::runtime_error(std::string("Unexpected soundgen type: ")+name);
1267     }
1268 
copyToCellImpl(const MWWorld::ConstPtr & ptr,MWWorld::CellStore & cell) const1269     MWWorld::Ptr Npc::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const
1270     {
1271         const MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>();
1272 
1273         return MWWorld::Ptr(cell.insert(ref), &cell);
1274     }
1275 
getSkill(const MWWorld::Ptr & ptr,int skill) const1276     float Npc::getSkill(const MWWorld::Ptr& ptr, int skill) const
1277     {
1278         return getNpcStats(ptr).getSkill(skill).getModified();
1279     }
1280 
getBloodTexture(const MWWorld::ConstPtr & ptr) const1281     int Npc::getBloodTexture(const MWWorld::ConstPtr &ptr) const
1282     {
1283         return ptr.get<ESM::NPC>()->mBase->mBloodType;
1284     }
1285 
readAdditionalState(const MWWorld::Ptr & ptr,const ESM::ObjectState & state) const1286     void Npc::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state)
1287         const
1288     {
1289         if (!state.mHasCustomState)
1290             return;
1291 
1292         if (state.mVersion > 0)
1293         {
1294             if (!ptr.getRefData().getCustomData())
1295             {
1296                 // Create a CustomData, but don't fill it from ESM records (not needed)
1297                 ptr.getRefData().setCustomData(std::make_unique<NpcCustomData>());
1298             }
1299         }
1300         else
1301             ensureCustomData(ptr); // in openmw 0.30 savegames not all state was saved yet, so need to load it regardless.
1302 
1303         NpcCustomData& customData = ptr.getRefData().getCustomData()->asNpcCustomData();
1304         const ESM::NpcState& npcState = state.asNpcState();
1305         customData.mInventoryStore.readState (npcState.mInventory);
1306         customData.mNpcStats.readState (npcState.mNpcStats);
1307         bool spellsInitialised = customData.mNpcStats.getSpells().setSpells(ptr.get<ESM::NPC>()->mBase->mId);
1308         if(spellsInitialised)
1309             customData.mNpcStats.getSpells().clear();
1310         customData.mNpcStats.readState (npcState.mCreatureStats);
1311     }
1312 
writeAdditionalState(const MWWorld::ConstPtr & ptr,ESM::ObjectState & state) const1313     void Npc::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state)
1314         const
1315     {
1316         if (!ptr.getRefData().getCustomData())
1317         {
1318             state.mHasCustomState = false;
1319             return;
1320         }
1321 
1322         if (ptr.getRefData().getCount() <= 0)
1323         {
1324             state.mHasCustomState = false;
1325             return;
1326         }
1327 
1328         const NpcCustomData& customData = ptr.getRefData().getCustomData()->asNpcCustomData();
1329         ESM::NpcState& npcState = state.asNpcState();
1330         customData.mInventoryStore.writeState (npcState.mInventory);
1331         customData.mNpcStats.writeState (npcState.mNpcStats);
1332         customData.mNpcStats.writeState (npcState.mCreatureStats);
1333     }
1334 
getBaseGold(const MWWorld::ConstPtr & ptr) const1335     int Npc::getBaseGold(const MWWorld::ConstPtr& ptr) const
1336     {
1337         const MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>();
1338         return ref->mBase->mNpdt.mGold;
1339     }
1340 
isClass(const MWWorld::ConstPtr & ptr,const std::string & className) const1341     bool Npc::isClass(const MWWorld::ConstPtr& ptr, const std::string &className) const
1342     {
1343         return Misc::StringUtils::ciEqual(ptr.get<ESM::NPC>()->mBase->mClass, className);
1344     }
1345 
canSwim(const MWWorld::ConstPtr & ptr) const1346     bool Npc::canSwim(const MWWorld::ConstPtr &ptr) const
1347     {
1348         return true;
1349     }
1350 
canWalk(const MWWorld::ConstPtr & ptr) const1351     bool Npc::canWalk(const MWWorld::ConstPtr &ptr) const
1352     {
1353         return true;
1354     }
1355 
respawn(const MWWorld::Ptr & ptr) const1356     void Npc::respawn(const MWWorld::Ptr &ptr) const
1357     {
1358         const MWMechanics::CreatureStats& creatureStats = getCreatureStats(ptr);
1359         if (ptr.getRefData().getCount() > 0 && !creatureStats.isDead())
1360             return;
1361 
1362         if (!creatureStats.isDeathAnimationFinished())
1363             return;
1364 
1365         const MWWorld::Store<ESM::GameSetting>& gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
1366         static const float fCorpseRespawnDelay = gmst.find("fCorpseRespawnDelay")->mValue.getFloat();
1367         static const float fCorpseClearDelay = gmst.find("fCorpseClearDelay")->mValue.getFloat();
1368 
1369         float delay = ptr.getRefData().getCount() == 0 ? fCorpseClearDelay : std::min(fCorpseRespawnDelay, fCorpseClearDelay);
1370 
1371         if (ptr.get<ESM::NPC>()->mBase->mFlags & ESM::NPC::Respawn
1372                 && creatureStats.getTimeOfDeath() + delay <= MWBase::Environment::get().getWorld()->getTimeStamp())
1373         {
1374             if (ptr.getCellRef().hasContentFile())
1375             {
1376                 if (ptr.getRefData().getCount() == 0)
1377                 {
1378                     ptr.getRefData().setCount(1);
1379                     const std::string& script = getScript(ptr);
1380                     if (!script.empty())
1381                         MWBase::Environment::get().getWorld()->getLocalScripts().add(script, ptr);
1382                 }
1383 
1384                 MWBase::Environment::get().getWorld()->removeContainerScripts(ptr);
1385                 ptr.getRefData().setCustomData(nullptr);
1386 
1387                 // Reset to original position
1388                 MWBase::Environment::get().getWorld()->moveObject(ptr, ptr.getCellRef().getPosition().pos[0],
1389                         ptr.getCellRef().getPosition().pos[1],
1390                         ptr.getCellRef().getPosition().pos[2]);
1391             }
1392         }
1393     }
1394 
getBaseFightRating(const MWWorld::ConstPtr & ptr) const1395     int Npc::getBaseFightRating (const MWWorld::ConstPtr& ptr) const
1396     {
1397         const MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>();
1398         return ref->mBase->mAiData.mFight;
1399     }
1400 
isBipedal(const MWWorld::ConstPtr & ptr) const1401     bool Npc::isBipedal(const MWWorld::ConstPtr &ptr) const
1402     {
1403         return true;
1404     }
1405 
getPrimaryFaction(const MWWorld::ConstPtr & ptr) const1406     std::string Npc::getPrimaryFaction (const MWWorld::ConstPtr& ptr) const
1407     {
1408         const MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>();
1409         return ref->mBase->mFaction;
1410     }
1411 
getPrimaryFactionRank(const MWWorld::ConstPtr & ptr) const1412     int Npc::getPrimaryFactionRank (const MWWorld::ConstPtr& ptr) const
1413     {
1414         std::string factionID = ptr.getClass().getPrimaryFaction(ptr);
1415         if(factionID.empty())
1416             return -1;
1417 
1418         // Search in the NPC data first
1419         if (const MWWorld::CustomData* data = ptr.getRefData().getCustomData())
1420         {
1421             int rank = data->asNpcCustomData().mNpcStats.getFactionRank(factionID);
1422             if (rank >= 0)
1423                 return rank;
1424         }
1425 
1426         // Use base NPC record as a fallback
1427         const MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>();
1428         return ref->mBase->getFactionRank();
1429     }
1430 
setBaseAISetting(const std::string & id,MWMechanics::CreatureStats::AiSetting setting,int value) const1431     void Npc::setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value) const
1432     {
1433         MWMechanics::setBaseAISetting<ESM::NPC>(id, setting, value);
1434     }
1435 
modifyBaseInventory(const std::string & actorId,const std::string & itemId,int amount) const1436     void Npc::modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount) const
1437     {
1438         MWMechanics::modifyBaseInventory<ESM::NPC>(actorId, itemId, amount);
1439     }
1440 
getWalkSpeed(const MWWorld::Ptr & ptr) const1441     float Npc::getWalkSpeed(const MWWorld::Ptr& ptr) const
1442     {
1443         const GMST& gmst = getGmst();
1444         const NpcCustomData* npcdata = static_cast<const NpcCustomData*>(ptr.getRefData().getCustomData());
1445         const float normalizedEncumbrance = getNormalizedEncumbrance(ptr);
1446         const bool sneaking = MWBase::Environment::get().getMechanicsManager()->isSneaking(ptr);
1447 
1448         float walkSpeed = gmst.fMinWalkSpeed->mValue.getFloat()
1449                 + 0.01f * npcdata->mNpcStats.getAttribute(ESM::Attribute::Speed).getModified()
1450                 * (gmst.fMaxWalkSpeed->mValue.getFloat() - gmst.fMinWalkSpeed->mValue.getFloat());
1451         walkSpeed *= 1.0f - gmst.fEncumberedMoveEffect->mValue.getFloat()*normalizedEncumbrance;
1452         walkSpeed = std::max(0.0f, walkSpeed);
1453         if(sneaking)
1454             walkSpeed *= gmst.fSneakSpeedMultiplier->mValue.getFloat();
1455 
1456         return walkSpeed;
1457     }
1458 
getRunSpeed(const MWWorld::Ptr & ptr) const1459     float Npc::getRunSpeed(const MWWorld::Ptr& ptr) const
1460     {
1461         const GMST& gmst = getGmst();
1462         return getWalkSpeed(ptr)
1463                 * (0.01f * getSkill(ptr, ESM::Skill::Athletics) * gmst.fAthleticsRunBonus->mValue.getFloat()
1464                    + gmst.fBaseRunMultiplier->mValue.getFloat());
1465     }
1466 
getSwimSpeed(const MWWorld::Ptr & ptr) const1467     float Npc::getSwimSpeed(const MWWorld::Ptr& ptr) const
1468     {
1469         const GMST& gmst = getGmst();
1470         const MWBase::World* world = MWBase::Environment::get().getWorld();
1471         const MWMechanics::CreatureStats& stats = getCreatureStats(ptr);
1472         const NpcCustomData* npcdata = static_cast<const NpcCustomData*>(ptr.getRefData().getCustomData());
1473         const MWMechanics::MagicEffects& mageffects = npcdata->mNpcStats.getMagicEffects();
1474         const bool swimming = world->isSwimming(ptr);
1475         const bool inair = !world->isOnGround(ptr) && !swimming && !world->isFlying(ptr);
1476         const bool running = stats.getStance(MWMechanics::CreatureStats::Stance_Run)
1477                 && (inair || MWBase::Environment::get().getMechanicsManager()->isRunning(ptr));
1478 
1479         float swimSpeed;
1480 
1481         if (running)
1482             swimSpeed = getRunSpeed(ptr);
1483         else
1484             swimSpeed = getWalkSpeed(ptr);
1485 
1486         swimSpeed *= 1.0f + 0.01f * mageffects.get(ESM::MagicEffect::SwiftSwim).getMagnitude();
1487         swimSpeed *= gmst.fSwimRunBase->mValue.getFloat()
1488                 + 0.01f * getSkill(ptr, ESM::Skill::Athletics) * gmst.fSwimRunAthleticsMult->mValue.getFloat();
1489 
1490         return swimSpeed;
1491     }
1492 }
1493