npc.cpp 56.8 KB
Newer Older
1 2
#include "npc.hpp"

3 4
#include <memory>

scrawl's avatar
scrawl committed
5
#include <components/misc/rng.hpp>
dteviot's avatar
dteviot committed
6

Emanuel Guevel's avatar
Emanuel Guevel committed
7
#include <components/esm/loadmgef.hpp>
8
#include <components/esm/loadnpc.hpp>
9
#include <components/esm/npcstate.hpp>
10

11 12
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
13
#include "../mwbase/mechanicsmanager.hpp"
14
#include "../mwbase/windowmanager.hpp"
15
#include "../mwbase/dialoguemanager.hpp"
16
#include "../mwbase/soundmanager.hpp"
17

Marc Zinnschlag's avatar
Marc Zinnschlag committed
18
#include "../mwmechanics/creaturestats.hpp"
Marc Zinnschlag's avatar
Marc Zinnschlag committed
19
#include "../mwmechanics/npcstats.hpp"
20
#include "../mwmechanics/movement.hpp"
21
#include "../mwmechanics/spellcasting.hpp"
22
#include "../mwmechanics/disease.hpp"
23
#include "../mwmechanics/combat.hpp"
24
#include "../mwmechanics/autocalcspell.hpp"
25
#include "../mwmechanics/difficultyscaling.hpp"
26
#include "../mwmechanics/character.hpp"
dteviot's avatar
dteviot committed
27
#include "../mwmechanics/actorutil.hpp"
Marc Zinnschlag's avatar
Marc Zinnschlag committed
28

29
#include "../mwworld/ptr.hpp"
30
#include "../mwworld/actiontalk.hpp"
scrawl's avatar
scrawl committed
31
#include "../mwworld/actionopen.hpp"
32
#include "../mwworld/failedaction.hpp"
33
#include "../mwworld/inventorystore.hpp"
Marc Zinnschlag's avatar
Marc Zinnschlag committed
34
#include "../mwworld/customdata.hpp"
35
#include "../mwphysics/physicssystem.hpp"
36
#include "../mwworld/cellstore.hpp"
37

38
#include "../mwrender/objects.hpp"
39
#include "../mwrender/renderinginterface.hpp"
scrawl's avatar
scrawl committed
40
#include "../mwrender/npcanimation.hpp"
41

42
#include "../mwgui/tooltips.hpp"
43

Marc Zinnschlag's avatar
Marc Zinnschlag committed
44
namespace
45
{
46

47 48 49 50 51 52 53 54 55 56
    int is_even(double d) {
        double int_part;
        modf(d / 2.0, &int_part);
        return 2.0 * int_part == d;
    }

    int round_ieee_754(double d) {
        double i = floor(d);
        d -= i;
        if(d < 0.5)
57
            return static_cast<int>(i);
58
        if(d > 0.5)
59
            return static_cast<int>(i) + 1;
60
        if(is_even(i))
61 62
            return static_cast<int>(i);
        return static_cast<int>(i) + 1;
63 64
    }

65 66 67 68 69 70 71 72 73 74 75 76
    void autoCalculateAttributes (const ESM::NPC* npc, MWMechanics::CreatureStats& creatureStats)
    {
        // race bonus
        const ESM::Race *race =
            MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(npc->mRace);

        bool male = (npc->mFlags & ESM::NPC::Female) == 0;

        int level = creatureStats.getLevel();
        for (int i=0; i<ESM::Attribute::Length; ++i)
        {
            const ESM::Race::MaleFemale& attribute = race->mData.mAttributeValues[i];
77
            creatureStats.setAttribute(i, male ? attribute.mMale : attribute.mFemale);
78 79 80 81 82 83 84 85 86 87 88
        }

        // class bonus
        const ESM::Class *class_ =
            MWBase::Environment::get().getWorld()->getStore().get<ESM::Class>().find(npc->mClass);

        for (int i=0; i<2; ++i)
        {
            int attribute = class_->mData.mAttribute[i];
            if (attribute>=0 && attribute<8)
            {
89
                creatureStats.setAttribute(attribute,
90 91 92 93 94
                    creatureStats.getAttribute(attribute).getBase() + 10);
            }
        }

        // skill bonus
95
        for (int attribute=0; attribute < ESM::Attribute::Length; ++attribute)
96 97 98 99 100 101 102 103 104 105 106
        {
            float modifierSum = 0;

            for (int j=0; j<ESM::Skill::Length; ++j)
            {
                const ESM::Skill* skill = MWBase::Environment::get().getWorld()->getStore().get<ESM::Skill>().find(j);

                if (skill->mData.mAttribute != attribute)
                    continue;

                // is this a minor or major skill?
107
                float add=0.2f;
108 109 110 111 112 113 114 115 116 117 118 119
                for (int k=0; k<5; ++k)
                {
                    if (class_->mData.mSkills[k][0] == j)
                        add=0.5;
                }
                for (int k=0; k<5; ++k)
                {
                    if (class_->mData.mSkills[k][1] == j)
                        add=1.0;
                }
                modifierSum += add;
            }
120 121 122
            creatureStats.setAttribute(attribute, std::min(
                                           round_ieee_754(creatureStats.getAttribute(attribute).getBase()
                + (level-1) * modifierSum), 100) );
123
        }
124

125
        // initial health
126 127
        int strength = creatureStats.getAttribute(ESM::Attribute::Strength).getBase();
        int endurance = creatureStats.getAttribute(ESM::Attribute::Endurance).getBase();
128 129 130 131 132 133 134 135 136 137 138 139

        int multiplier = 3;

        if (class_->mData.mSpecialization == ESM::Class::Combat)
            multiplier += 2;
        else if (class_->mData.mSpecialization == ESM::Class::Stealth)
            multiplier += 1;

        if (class_->mData.mAttribute[0] == ESM::Attribute::Endurance
            || class_->mData.mAttribute[1] == ESM::Attribute::Endurance)
            multiplier += 1;

140
        creatureStats.setHealth(floor(0.5f * (strength + endurance)) + multiplier * (creatureStats.getLevel() - 1));
141
    }
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156

    /**
     * @brief autoCalculateSkills
     *
     * Skills are calculated with following formulae ( http://www.uesp.net/wiki/Morrowind:NPCs#Skills ):
     *
     * Skills: (Level - 1) × (Majority Multiplier + Specialization Multiplier)
     *
     *         The Majority Multiplier is 1.0 for a Major or Minor Skill, or 0.1 for a Miscellaneous Skill.
     *
     *         The Specialization Multiplier is 0.5 for a Skill in the same Specialization as the class,
     *         zero for other Skills.
     *
     * and by adding class, race, specialization bonus.
     */
157
    void autoCalculateSkills(const ESM::NPC* npc, MWMechanics::NpcStats& npcStats, const MWWorld::Ptr& ptr)
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
    {
        const ESM::Class *class_ =
            MWBase::Environment::get().getWorld()->getStore().get<ESM::Class>().find(npc->mClass);

        unsigned int level = npcStats.getLevel();

        const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(npc->mRace);


        for (int i = 0; i < 2; ++i)
        {
            int bonus = (i==0) ? 10 : 25;

            for (int i2 = 0; i2 < 5; ++i2)
            {
                int index = class_->mData.mSkills[i2][i];
                if (index >= 0 && index < ESM::Skill::Length)
                {
                    npcStats.getSkill(index).setBase (npcStats.getSkill(index).getBase() + bonus);
                }
            }
        }

        for (int skillIndex = 0; skillIndex < ESM::Skill::Length; ++skillIndex)
        {
            float majorMultiplier = 0.1f;
            float specMultiplier = 0.0f;

            int raceBonus = 0;
            int specBonus = 0;

            for (int raceSkillIndex = 0; raceSkillIndex < 7; ++raceSkillIndex)
            {
191 192 193 194 195
                if (race->mData.mBonus[raceSkillIndex].mSkill == skillIndex)
                {
                    raceBonus = race->mData.mBonus[raceSkillIndex].mBonus;
                    break;
                }
196 197 198 199
            }

            for (int k = 0; k < 5; ++k)
            {
200 201 202 203 204 205
                // is this a minor or major skill?
                if ((class_->mData.mSkills[k][0] == skillIndex) || (class_->mData.mSkills[k][1] == skillIndex))
                {
                    majorMultiplier = 1.0f;
                    break;
                }
206 207 208 209 210 211
            }

            // is this skill in the same Specialization as the class?
            const ESM::Skill* skill = MWBase::Environment::get().getWorld()->getStore().get<ESM::Skill>().find(skillIndex);
            if (skill->mData.mSpecialization == class_->mData.mSpecialization)
            {
212 213
                specMultiplier = 0.5f;
                specBonus = 5;
214 215 216 217
            }

            npcStats.getSkill(skillIndex).setBase(
                  std::min(
218 219
                    round_ieee_754(
                            npcStats.getSkill(skillIndex).getBase()
220 221 222
                    + 5
                    + raceBonus
                    + specBonus
scrawl's avatar
scrawl committed
223
                    +(int(level)-1) * (majorMultiplier + specMultiplier)), 100)); // Must gracefully handle level 0
224
        }
225 226 227 228 229 230 231 232 233

        int skills[ESM::Skill::Length];
        for (int i=0; i<ESM::Skill::Length; ++i)
            skills[i] = npcStats.getSkill(i).getBase();

        int attributes[ESM::Attribute::Length];
        for (int i=0; i<ESM::Attribute::Length; ++i)
            attributes[i] = npcStats.getAttribute(i).getBase();

234 235
        std::vector<std::string> spells = MWMechanics::autoCalcNpcSpells(skills, attributes, race);
        for (std::vector<std::string>::iterator it = spells.begin(); it != spells.end(); ++it)
236
            npcStats.getSpells().add(*it);
237
    }
238 239
}

240
namespace MWClass
241
{
242 243 244 245 246 247 248 249 250 251 252 253 254 255

    class NpcCustomData : public MWWorld::CustomData
    {
    public:
        MWMechanics::NpcStats mNpcStats;
        MWMechanics::Movement mMovement;
        MWWorld::InventoryStore mInventoryStore;

        virtual MWWorld::CustomData *clone() const;

        virtual NpcCustomData& asNpcCustomData()
        {
            return *this;
        }
scrawl's avatar
scrawl committed
256 257 258 259
        virtual const NpcCustomData& asNpcCustomData() const
        {
            return *this;
        }
260 261 262 263 264 265 266
    };

    MWWorld::CustomData *NpcCustomData::clone() const
    {
        return new NpcCustomData (*this);
    }

267
    const Npc::GMST& Npc::getGmst()
268
    {
269
        static GMST gmst;
270 271 272 273
        static bool inited = false;
        if(!inited)
        {
            const MWBase::World *world = MWBase::Environment::get().getWorld();
274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294
            const MWWorld::Store<ESM::GameSetting> &store = world->getStore().get<ESM::GameSetting>();

            gmst.fMinWalkSpeed = store.find("fMinWalkSpeed");
            gmst.fMaxWalkSpeed = store.find("fMaxWalkSpeed");
            gmst.fEncumberedMoveEffect = store.find("fEncumberedMoveEffect");
            gmst.fSneakSpeedMultiplier = store.find("fSneakSpeedMultiplier");
            gmst.fAthleticsRunBonus = store.find("fAthleticsRunBonus");
            gmst.fBaseRunMultiplier = store.find("fBaseRunMultiplier");
            gmst.fMinFlySpeed = store.find("fMinFlySpeed");
            gmst.fMaxFlySpeed = store.find("fMaxFlySpeed");
            gmst.fSwimRunBase = store.find("fSwimRunBase");
            gmst.fSwimRunAthleticsMult = store.find("fSwimRunAthleticsMult");
            gmst.fJumpEncumbranceBase = store.find("fJumpEncumbranceBase");
            gmst.fJumpEncumbranceMultiplier = store.find("fJumpEncumbranceMultiplier");
            gmst.fJumpAcrobaticsBase = store.find("fJumpAcrobaticsBase");
            gmst.fJumpAcroMultiplier = store.find("fJumpAcroMultiplier");
            gmst.fJumpRunMultiplier = store.find("fJumpRunMultiplier");
            gmst.fWereWolfRunMult = store.find("fWereWolfRunMult");
            gmst.fKnockDownMult = store.find("fKnockDownMult");
            gmst.iKnockDownOddsMult = store.find("iKnockDownOddsMult");
            gmst.iKnockDownOddsBase = store.find("iKnockDownOddsBase");
295
            gmst.fCombatArmorMinMult = store.find("fCombatArmorMinMult");
296 297 298

            inited = true;
        }
299 300 301 302 303
        return gmst;
    }

    void Npc::ensureCustomData (const MWWorld::Ptr& ptr) const
    {
304 305
        if (!ptr.getRefData().getCustomData())
        {
306
            std::unique_ptr<NpcCustomData> data(new NpcCustomData);
307

308
            MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>();
309

310
            // creature stats
311
            int gold=0;
312
            if(ref->mBase->mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS)
313
            {
314 315
                gold = ref->mBase->mNpdt52.mGold;

316
                for (unsigned int i=0; i< ESM::Skill::Length; ++i)
greye's avatar
greye committed
317 318
                    data->mNpcStats.getSkill (i).setBase (ref->mBase->mNpdt52.mSkills[i]);

319 320 321 322 323 324 325 326 327
                data->mNpcStats.setAttribute(ESM::Attribute::Strength, ref->mBase->mNpdt52.mStrength);
                data->mNpcStats.setAttribute(ESM::Attribute::Intelligence, ref->mBase->mNpdt52.mIntelligence);
                data->mNpcStats.setAttribute(ESM::Attribute::Willpower, ref->mBase->mNpdt52.mWillpower);
                data->mNpcStats.setAttribute(ESM::Attribute::Agility, ref->mBase->mNpdt52.mAgility);
                data->mNpcStats.setAttribute(ESM::Attribute::Speed, ref->mBase->mNpdt52.mSpeed);
                data->mNpcStats.setAttribute(ESM::Attribute::Endurance, ref->mBase->mNpdt52.mEndurance);
                data->mNpcStats.setAttribute(ESM::Attribute::Personality, ref->mBase->mNpdt52.mPersonality);
                data->mNpcStats.setAttribute(ESM::Attribute::Luck, ref->mBase->mNpdt52.mLuck);

328 329 330 331 332
                data->mNpcStats.setHealth (ref->mBase->mNpdt52.mHealth);
                data->mNpcStats.setMagicka (ref->mBase->mNpdt52.mMana);
                data->mNpcStats.setFatigue (ref->mBase->mNpdt52.mFatigue);

                data->mNpcStats.setLevel(ref->mBase->mNpdt52.mLevel);
scrawl's avatar
scrawl committed
333
                data->mNpcStats.setBaseDisposition(ref->mBase->mNpdt52.mDisposition);
334
                data->mNpcStats.setReputation(ref->mBase->mNpdt52.mReputation);
335 336

                data->mNpcStats.setNeedRecalcDynamicStats(false);
337 338 339
            }
            else
            {
340 341
                gold = ref->mBase->mNpdt12.mGold;

342
                for (int i=0; i<3; ++i)
343
                    data->mNpcStats.setDynamic (i, 10);
344

345
                data->mNpcStats.setLevel(ref->mBase->mNpdt12.mLevel);
346 347
                data->mNpcStats.setBaseDisposition(ref->mBase->mNpdt12.mDisposition);
                data->mNpcStats.setReputation(ref->mBase->mNpdt12.mReputation);
348

349
                autoCalculateAttributes(ref->mBase, data->mNpcStats);
350
                autoCalculateSkills(ref->mBase, data->mNpcStats, ptr);
351 352

                data->mNpcStats.setNeedRecalcDynamicStats(true);
353
            }
354 355
            if (data->mNpcStats.isDead())
                data->mNpcStats.setDeathAnimationFinished(true);
356

357 358 359 360 361
            // race powers
            const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(ref->mBase->mRace);
            for (std::vector<std::string>::const_iterator iter (race->mPowers.mList.begin());
                iter!=race->mPowers.mList.end(); ++iter)
            {
scrawl's avatar
scrawl committed
362
                if (const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search(*iter))
363 364 365
                    data->mNpcStats.getSpells().add (spell);
                else
                    std::cerr << "Warning: ignoring nonexistent race power '" << *iter << "' on NPC '" << ref->mBase->mId << "'" << std::endl;
366 367
            }

scrawl's avatar
scrawl committed
368
            if (!ref->mBase->mFaction.empty())
369 370 371 372 373
            {
                static const int iAutoRepFacMod = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>()
                        .find("iAutoRepFacMod")->getInt();
                static const int iAutoRepLevMod = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>()
                        .find("iAutoRepLevMod")->getInt();
scrawl's avatar
scrawl committed
374
                int rank = ref->mBase->getFactionRank();
375 376 377 378

                data->mNpcStats.setReputation(iAutoRepFacMod * (rank+1) + iAutoRepLevMod * (data->mNpcStats.getLevel()-1));
            }

379
            data->mNpcStats.getAiSequence().fill(ref->mBase->mAiPackage);
380

381 382 383 384
            data->mNpcStats.setAiSetting (MWMechanics::CreatureStats::AI_Hello, ref->mBase->mAiData.mHello);
            data->mNpcStats.setAiSetting (MWMechanics::CreatureStats::AI_Fight, ref->mBase->mAiData.mFight);
            data->mNpcStats.setAiSetting (MWMechanics::CreatureStats::AI_Flee, ref->mBase->mAiData.mFlee);
            data->mNpcStats.setAiSetting (MWMechanics::CreatureStats::AI_Alarm, ref->mBase->mAiData.mAlarm);
385

386
            // spells
greye's avatar
greye committed
387 388
            for (std::vector<std::string>::const_iterator iter (ref->mBase->mSpells.mList.begin());
                iter!=ref->mBase->mSpells.mList.end(); ++iter)
389
            {
scrawl's avatar
scrawl committed
390
                if (const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search(*iter))
391 392 393 394 395 396 397
                    data->mNpcStats.getSpells().add (spell);
                else
                {
                    /// \todo add option to make this a fatal error message pop-up, but default to warning for vanilla compatibility
                    std::cerr << "Warning: ignoring nonexistent spell '" << *iter << "' on NPC '" << ref->mBase->mId << "'" << std::endl;
                }
            }
398

399
            // inventory
400
            // setting ownership is used to make the NPC auto-equip his initial equipment only, and not bartered items
scrawl's avatar
scrawl committed
401
            data->mInventoryStore.fill(ref->mBase->mInventory, ptr.getCellRef().getRefId());
402

jefhai's avatar
jefhai committed
403 404
            data->mNpcStats.setGoldPool(gold);

405
            // store
406
            ptr.getRefData().setCustomData (data.release());
407

408
            getInventoryStore(ptr).autoEquip(ptr); 
409 410 411
        }
    }

412
    void Npc::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const
413
    {
414
        renderingInterface.getObjects().insertNPC(ptr);
Jason Hooks's avatar
Jason Hooks committed
415
    }
Jason Hooks's avatar
Jason Hooks committed
416

scrawl's avatar
scrawl committed
417
    bool Npc::isPersistent(const MWWorld::ConstPtr &actor) const
scrawl's avatar
scrawl committed
418
    {
scrawl's avatar
scrawl committed
419
        const MWWorld::LiveCellRef<ESM::NPC>* ref = actor.get<ESM::NPC>();
scrawl's avatar
scrawl committed
420 421 422
        return ref->mBase->mPersistent;
    }

scrawl's avatar
scrawl committed
423
    std::string Npc::getModel(const MWWorld::ConstPtr &ptr) const
greye's avatar
greye committed
424
    {
scrawl's avatar
scrawl committed
425
        const MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>();
426

greye's avatar
greye committed
427
        std::string model = "meshes\\base_anim.nif";
428 429
        const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(ref->mBase->mRace);
        if(race->mData.mFlags & ESM::Race::Beast)
greye's avatar
greye committed
430
            model = "meshes\\base_animkna.nif";
431

greye's avatar
greye committed
432
        return model;
433 434
    }

435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462
    void Npc::getModelsToPreload(const MWWorld::Ptr &ptr, std::vector<std::string> &models) const
    {
        const MWWorld::LiveCellRef<ESM::NPC> *npc = ptr.get<ESM::NPC>();
        const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().search(npc->mBase->mRace);
        if(race && race->mData.mFlags & ESM::Race::Beast)
            models.push_back("meshes\\base_animkna.nif");

        // keep these always loaded just in case
        models.push_back("meshes/xargonian_swimkna.nif");
        models.push_back("meshes/xbase_anim_female.nif");
        models.push_back("meshes/xbase_anim.nif");

        if (!npc->mBase->mModel.empty())
            models.push_back("meshes/"+npc->mBase->mModel);

        if (!npc->mBase->mHead.empty())
        {
            const ESM::BodyPart* head = MWBase::Environment::get().getWorld()->getStore().get<ESM::BodyPart>().search(npc->mBase->mHead);
            if (head)
                models.push_back("meshes/"+head->mModel);
        }
        if (!npc->mBase->mHair.empty())
        {
            const ESM::BodyPart* hair = MWBase::Environment::get().getWorld()->getStore().get<ESM::BodyPart>().search(npc->mBase->mHair);
            if (hair)
                models.push_back("meshes/"+hair->mModel);
        }

scrawl's avatar
scrawl committed
463 464
        bool female = (npc->mBase->mFlags & ESM::NPC::Female);

465
        // FIXME: use const version of InventoryStore functions once they are available
scrawl's avatar
scrawl committed
466
        // preload equipped items
467 468
        if (ptr.getClass().hasInventoryStore(ptr))
        {
469
            const MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore(ptr);
470 471
            for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot)
            {
472
                MWWorld::ConstContainerStoreIterator equipped = invStore.getSlot(slot);
473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494
                if (equipped != invStore.end())
                {
                    std::vector<ESM::PartReference> parts;
                    if(equipped->getTypeName() == typeid(ESM::Clothing).name())
                    {
                        const ESM::Clothing *clothes = equipped->get<ESM::Clothing>()->mBase;
                        parts = clothes->mParts.mParts;
                    }
                    else if(equipped->getTypeName() == typeid(ESM::Armor).name())
                    {
                        const ESM::Armor *armor = equipped->get<ESM::Armor>()->mBase;
                        parts = armor->mParts.mParts;
                    }
                    else
                    {
                        std::string model = equipped->getClass().getModel(*equipped);
                        if (!model.empty())
                            models.push_back(model);
                    }

                    for (std::vector<ESM::PartReference>::const_iterator it = parts.begin(); it != parts.end(); ++it)
                    {
scrawl's avatar
scrawl committed
495
                        std::string partname = female ? it->mFemale : it->mMale;
496
                        if (partname.empty())
scrawl's avatar
scrawl committed
497
                            partname = female ? it->mMale : it->mFemale;
498 499 500 501 502 503 504 505
                        const ESM::BodyPart* part = MWBase::Environment::get().getWorld()->getStore().get<ESM::BodyPart>().search(partname);
                        if (part && !part->mModel.empty())
                            models.push_back("meshes/"+part->mModel);
                    }
                }
            }
        }

scrawl's avatar
scrawl committed
506 507 508 509 510 511 512 513 514 515 516 517
        // preload body parts
        if (race)
        {
            const std::vector<const ESM::BodyPart*>& parts = MWRender::NpcAnimation::getBodyParts(Misc::StringUtils::lowerCase(race->mId), female, false, false);
            for (std::vector<const ESM::BodyPart*>::const_iterator it = parts.begin(); it != parts.end(); ++it)
            {
                const ESM::BodyPart* part = *it;
                if (part && !part->mModel.empty())
                    models.push_back("meshes/"+part->mModel);
            }
        }

518 519
    }

scrawl's avatar
scrawl committed
520
    std::string Npc::getName (const MWWorld::ConstPtr& ptr) const
Marc Zinnschlag's avatar
Marc Zinnschlag committed
521
    {
scrawl's avatar
scrawl committed
522
        if(ptr.getRefData().getCustomData() && ptr.getRefData().getCustomData()->asNpcCustomData().mNpcStats.isWerewolf())
523 524
        {
            const MWBase::World *world = MWBase::Environment::get().getWorld();
525
            const MWWorld::Store<ESM::GameSetting> &store = world->getStore().get<ESM::GameSetting>();
526

527
            return store.find("sWerewolfPopup")->getString();
528
        }
Marc Zinnschlag's avatar
Marc Zinnschlag committed
529

scrawl's avatar
scrawl committed
530
        const MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>();
greye's avatar
greye committed
531
        return ref->mBase->mName;
Marc Zinnschlag's avatar
Marc Zinnschlag committed
532 533
    }

534
    MWMechanics::CreatureStats& Npc::getCreatureStats (const MWWorld::Ptr& ptr) const
535
    {
536
        ensureCustomData (ptr);
537

538
        return ptr.getRefData().getCustomData()->asNpcCustomData().mNpcStats;
539 540
    }

Marc Zinnschlag's avatar
Marc Zinnschlag committed
541 542
    MWMechanics::NpcStats& Npc::getNpcStats (const MWWorld::Ptr& ptr) const
    {
543
        ensureCustomData (ptr);
Marc Zinnschlag's avatar
Marc Zinnschlag committed
544

545
        return ptr.getRefData().getCustomData()->asNpcCustomData().mNpcStats;
Marc Zinnschlag's avatar
Marc Zinnschlag committed
546 547
    }

Chris Robinson's avatar
Chris Robinson committed
548

549
    void Npc::hit(const MWWorld::Ptr& ptr, float attackStrength, int type) const
Chris Robinson's avatar
Chris Robinson committed
550
    {
551
        MWBase::World *world = MWBase::Environment::get().getWorld();
552 553

        const MWWorld::Store<ESM::GameSetting> &store = world->getStore().get<ESM::GameSetting>();
554

555 556
        // Get the weapon used (if hand-to-hand, weapon = inv.end())
        MWWorld::InventoryStore &inv = getInventoryStore(ptr);
557 558 559 560
        MWWorld::ContainerStoreIterator weaponslot = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
        MWWorld::Ptr weapon = ((weaponslot != inv.end()) ? *weaponslot : MWWorld::Ptr());
        if(!weapon.isEmpty() && weapon.getTypeName() != typeid(ESM::Weapon).name())
            weapon = MWWorld::Ptr();
561

562
        MWMechanics::applyFatigueLoss(ptr, weapon, attackStrength);
563

564
        const float fCombatDistance = store.find("fCombatDistance")->getFloat();
scrawl's avatar
scrawl committed
565
        float dist = fCombatDistance * (!weapon.isEmpty() ?
566
                               weapon.get<ESM::Weapon>()->mBase->mData.mReach :
567
                               store.find("fHandToHandReach")->getFloat());
scrawl's avatar
scrawl committed
568

569 570 571 572 573
        // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result.
        std::vector<MWWorld::Ptr> targetActors;
        if (!ptr.isEmpty() && ptr.getClass().isActor() && ptr != MWMechanics::getPlayer())
            ptr.getClass().getCreatureStats(ptr).getAiSequence().getCombatTargets(targetActors);

scrawl's avatar
scrawl committed
574
        // TODO: Use second to work out the hit angle
575
        std::pair<MWWorld::Ptr, osg::Vec3f> result = world->getHitContact(ptr, dist, targetActors);
scrawl's avatar
scrawl committed
576
        MWWorld::Ptr victim = result.first;
scrawl's avatar
scrawl committed
577
        osg::Vec3f hitPosition (result.second);
Chris Robinson's avatar
Chris Robinson committed
578 579 580
        if(victim.isEmpty()) // Didn't hit anything
            return;

581
        const MWWorld::Class &othercls = victim.getClass();
582 583
        if(!othercls.isActor()) // Can't hit non-actors
            return;
584
        MWMechanics::CreatureStats &otherstats = othercls.getCreatureStats(victim);
585
        if(otherstats.isDead()) // Can't hit dead actors
Chris Robinson's avatar
Chris Robinson committed
586 587
            return;

dteviot's avatar
dteviot committed
588
        if(ptr == MWMechanics::getPlayer())
scrawl's avatar
scrawl committed
589
            MWBase::Environment::get().getWindowManager()->setEnemy(victim);
scrawl's avatar
scrawl committed
590

591
        int weapskill = ESM::Skill::HandToHand;
592
        if(!weapon.isEmpty())
593
            weapskill = weapon.getClass().getEquipmentSkill(weapon);
594

595
        float hitchance = MWMechanics::getHitChance(ptr, victim, ptr.getClass().getSkill(ptr, weapskill));
596

597
        if (Misc::Rng::roll0to99() >= hitchance)
598
        {
599
            othercls.onHit(victim, 0.0f, false, weapon, ptr, osg::Vec3f(), false);
600
            MWMechanics::reduceWeaponCondition(0.f, false, weapon, ptr);
601 602
            return;
        }
Chris Robinson's avatar
Chris Robinson committed
603

604
        bool healthdmg;
605 606
        float damage = 0.0f;
        if(!weapon.isEmpty())
Chris Robinson's avatar
Chris Robinson committed
607
        {
608
            const unsigned char *attack = NULL;
mrcheko's avatar
mrcheko committed
609
            if(type == ESM::Weapon::AT_Chop)
610
                attack = weapon.get<ESM::Weapon>()->mBase->mData.mChop;
mrcheko's avatar
mrcheko committed
611
            else if(type == ESM::Weapon::AT_Slash)
612
                attack = weapon.get<ESM::Weapon>()->mBase->mData.mSlash;
mrcheko's avatar
mrcheko committed
613
            else if(type == ESM::Weapon::AT_Thrust)
614
                attack = weapon.get<ESM::Weapon>()->mBase->mData.mThrust;
615
            if(attack)
616
            {
617
                damage  = attack[0] + ((attack[1]-attack[0])*attackStrength);
618
            }
scrawl's avatar
scrawl committed
619
            MWMechanics::adjustWeaponDamage(damage, weapon, ptr);
620
            MWMechanics::reduceWeaponCondition(damage, true, weapon, ptr);
621 622 623 624
            healthdmg = true;
        }
        else
        {
625
            MWMechanics::getHandToHandDamage(ptr, victim, damage, healthdmg, attackStrength);
Chris Robinson's avatar
Chris Robinson committed
626
        }
dteviot's avatar
dteviot committed
627
        if(ptr == MWMechanics::getPlayer())
scrawl's avatar
scrawl committed
628
        {
629
            skillUsageSucceeded(ptr, weapskill, 0);
630

scrawl's avatar
scrawl committed
631 632 633 634 635 636 637 638 639 640
            const MWMechanics::AiSequence& seq = victim.getClass().getCreatureStats(victim).getAiSequence();

            bool unaware = !seq.isInCombat()
                    && !MWBase::Environment::get().getMechanicsManager()->awarenessCheck(ptr, victim);
            if(unaware)
            {
                damage *= store.find("fCombatCriticalStrikeMult")->getFloat();
                MWBase::Environment::get().getWindowManager()->messageBox("#{sTargetCriticalStrike}");
                MWBase::Environment::get().getSoundManager()->playSound3D(victim, "critical damage", 1.0f, 1.0f);
            }
641
        }
scrawl's avatar
scrawl committed
642

643
        if (othercls.getCreatureStats(victim).getKnockedDown())
644
            damage *= store.find("fCombatKODamageMult")->getFloat();
645

646
        // Apply "On hit" enchanted weapons
647
        MWMechanics::applyOnStrikeEnchantment(ptr, victim, weapon, hitPosition);
648

649 650
        MWMechanics::applyElementalShields(ptr, victim);

651
        if (MWMechanics::blockMeleeAttack(ptr, victim, weapon, damage, attackStrength))
652 653
            damage = 0;

654 655 656
        if (victim == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState())
            damage = 0;

657 658
        MWMechanics::diseaseContact(victim, ptr);

659
        othercls.onHit(victim, damage, healthdmg, weapon, ptr, hitPosition, true);
Chris Robinson's avatar
Chris Robinson committed
660 661
    }

Allofich's avatar
Allofich committed
662
    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
Chris Robinson's avatar
Chris Robinson committed
663
    {
664
        MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
665 666
        MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
        bool wasDead = stats.isDead();
667

668 669
        // Note OnPcHitMe is not set for friendly hits.
        bool setOnPcHitMe = true;
670

671
        // NOTE: 'object' and/or 'attacker' may be empty.
672
        if (!attacker.isEmpty() && attacker.getClass().isActor() && !stats.getAiSequence().isInCombat(attacker))
673 674
        {
            stats.setAttacked(true);
675
            setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker);
676
        }
677

678
        // Attacker and target store each other as hitattemptactor if they have no one stored yet
679
        if (!attacker.isEmpty() && attacker.getClass().isActor() && !ptr.isEmpty() && ptr.getClass().isActor())
680 681 682
        {
            MWMechanics::CreatureStats& statsAttacker = attacker.getClass().getCreatureStats(attacker);
            // First handle the attacked actor
683
            if ((stats.getHitAttemptActorId() == -1)
684 685
                && (statsAttacker.getAiSequence().isInCombat(ptr)
                    || attacker == MWMechanics::getPlayer()))
686
                stats.setHitAttemptActorId(statsAttacker.getActorId());
687 688

            // Next handle the attacking actor
689
            if ((statsAttacker.getHitAttemptActorId() == -1)
690 691
                && (statsAttacker.getAiSequence().isInCombat(ptr)
                    || attacker == MWMechanics::getPlayer()))
692
                statsAttacker.setHitAttemptActorId(stats.getActorId());
693 694
        }

695
        if (!object.isEmpty())
696
            stats.setLastHitAttemptObject(object.getCellRef().getRefId());
697

698
        if (setOnPcHitMe && !attacker.isEmpty() && attacker == MWMechanics::getPlayer())
scrawl's avatar
scrawl committed
699 700 701 702 703 704 705
        {
            const std::string &script = ptr.getClass().getScript(ptr);
            /* Set the OnPCHitMe script variable. The script is responsible for clearing it. */
            if(!script.empty())
                ptr.getRefData().getLocals().setVarByInt(script, "onpchitme", 1);
        }

706
        if (!successful)
707 708
        {
            // Missed
709
            sndMgr->playSound3D(ptr, "miss", 1.0f, 1.0f);
710 711
            return;
        }
Chris Robinson's avatar
Chris Robinson committed
712

713
        if (!object.isEmpty())
714
            stats.setLastHitObject(object.getCellRef().getRefId());
715

716

717 718 719
        if (damage > 0.0f && !object.isEmpty())
            MWMechanics::resistNormalWeapon(ptr, attacker, object, damage);

720 721 722
        if (damage < 0.001f)
            damage = 0;

723 724 725 726 727
        bool godmode = ptr == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState();

        if (godmode)
            damage = 0;

728
        if (damage > 0.0f && !attacker.isEmpty())
Chris Robinson's avatar
Chris Robinson committed
729 730 731
        {
            // 'ptr' is losing health. Play a 'hit' voiced dialog entry if not already saying
            // something, alert the character controller, scripts, etc.
732

scrawl's avatar
scrawl committed
733
            const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
734 735
            const GMST& gmst = getGmst();

scrawl's avatar
scrawl committed
736
            int chance = store.get<ESM::GameSetting>().find("iVoiceHitOdds")->getInt();
scrawl's avatar
scrawl committed
737
            if (Misc::Rng::roll0to99() < chance)
scrawl's avatar
scrawl committed
738
                MWBase::Environment::get().getDialogueManager()->say(ptr, "hit");
739 740

            // Check for knockdown
741 742
            float agilityTerm = stats.getAttribute(ESM::Attribute::Agility).getModified() * gmst.fKnockDownMult->getFloat();
            float knockdownTerm = stats.getAttribute(ESM::Attribute::Agility).getModified()
743
                    * gmst.iKnockDownOddsMult->getInt() * 0.01f + gmst.iKnockDownOddsBase->getInt();
scrawl's avatar
scrawl committed
744
            if (ishealth && agilityTerm <= damage && knockdownTerm <= Misc::Rng::roll0to99())
745
                stats.setKnockedDown(true);
746
            else
747
                stats.setHitRecovery(true); // Is this supposed to always occur?
748

749
            if (damage > 0 && ishealth)
750
            {
751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766
                // Hit percentages:
                // cuirass = 30%
                // shield, helmet, greaves, boots, pauldrons = 10% each
                // guantlets = 5% each
                static const int hitslots[20] = {
                    MWWorld::InventoryStore::Slot_Cuirass, MWWorld::InventoryStore::Slot_Cuirass,
                    MWWorld::InventoryStore::Slot_Cuirass, MWWorld::InventoryStore::Slot_Cuirass,
                    MWWorld::InventoryStore::Slot_Cuirass, MWWorld::InventoryStore::Slot_Cuirass,
                    MWWorld::InventoryStore::Slot_CarriedLeft, MWWorld::InventoryStore::Slot_CarriedLeft,
                    MWWorld::InventoryStore::Slot_Helmet, MWWorld::InventoryStore::Slot_Helmet,
                    MWWorld::InventoryStore::Slot_Greaves, MWWorld::InventoryStore::Slot_Greaves,
                    MWWorld::InventoryStore::Slot_Boots, MWWorld::InventoryStore::Slot_Boots,
                    MWWorld::InventoryStore::Slot_LeftPauldron, MWWorld::InventoryStore::Slot_LeftPauldron,
                    MWWorld::InventoryStore::Slot_RightPauldron, MWWorld::InventoryStore::Slot_RightPauldron,
                    MWWorld::InventoryStore::Slot_LeftGauntlet, MWWorld::InventoryStore::Slot_RightGauntlet
                };
scrawl's avatar
scrawl committed
767
                int hitslot = hitslots[Misc::Rng::rollDice(20)];
768

769 770 771
                float unmitigatedDamage = damage;
                float x = damage / (damage + getArmorRating(ptr));
                damage *= std::max(gmst.fCombatArmorMinMult->getFloat(), x);
772
                int damageDiff = static_cast<int>(unmitigatedDamage - damage);
773 774
                if (damage < 1)
                    damage = 1;
775

776 777 778 779
                MWWorld::InventoryStore &inv = getInventoryStore(ptr);
                MWWorld::ContainerStoreIterator armorslot = inv.getSlot(hitslot);
                MWWorld::Ptr armor = ((armorslot != inv.end()) ? *armorslot : MWWorld::Ptr());
                if(!armor.isEmpty() && armor.getTypeName() == typeid(ESM::Armor).name())
780
                {
781
                    int armorhealth = armor.getClass().getItemHealth(armor);
782
                    armorhealth -= std::min(std::max(1, damageDiff),