npc.cpp 55.3 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::auto_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 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494
        if (ptr.getClass().hasInventoryStore(ptr))
        {
            MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore(ptr);
            for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot)
            {
                MWWorld::ContainerStoreIterator equipped = invStore.getSlot(slot);
                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

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

576
        const MWWorld::Class &othercls = victim.getClass();
577 578
        if(!othercls.isActor()) // Can't hit non-actors
            return;
579
        MWMechanics::CreatureStats &otherstats = othercls.getCreatureStats(victim);
580
        if(otherstats.isDead()) // Can't hit dead actors
Chris Robinson's avatar
Chris Robinson committed
581 582
            return;

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

586
        int weapskill = ESM::Skill::HandToHand;
587
        if(!weapon.isEmpty())
588
            weapskill = weapon.getClass().getEquipmentSkill(weapon);
589

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

592
        if (Misc::Rng::roll0to99() >= hitchance)
593
        {
594
            othercls.onHit(victim, 0.0f, false, weapon, ptr, osg::Vec3f(), false);
595
            MWMechanics::reduceWeaponCondition(0.f, false, weapon, ptr);
596 597
            return;
        }
Chris Robinson's avatar
Chris Robinson committed
598

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

scrawl's avatar
scrawl committed
626 627 628 629 630 631 632 633 634 635
            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);
            }
636
        }
scrawl's avatar
scrawl committed
637

638
        if (othercls.getCreatureStats(victim).getKnockedDown())
639
            damage *= store.find("fCombatKODamageMult")->getFloat();
640

641
        // Apply "On hit" enchanted weapons
642
        MWMechanics::applyOnStrikeEnchantment(ptr, victim, weapon, hitPosition);
643

644 645
        MWMechanics::applyElementalShields(ptr, victim);

646
        if (MWMechanics::blockMeleeAttack(ptr, victim, weapon, damage, attackStrength))
647 648
            damage = 0;

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

651
        othercls.onHit(victim, damage, healthdmg, weapon, ptr, hitPosition, true);
Chris Robinson's avatar
Chris Robinson committed
652 653
    }

Allofich's avatar
Allofich committed
654
    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
655
    {
656 657
        MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();

658 659
        // NOTE: 'object' and/or 'attacker' may be empty.

660 661
        bool wasDead = getCreatureStats(ptr).isDead();

662 663
        // Note OnPcHitMe is not set for friendly hits.
        bool setOnPcHitMe = true;
664 665 666
        if (!attacker.isEmpty() && !ptr.getClass().getCreatureStats(ptr).getAiSequence().isInCombat(attacker))
        {
            getCreatureStats(ptr).setAttacked(true);
667

668
            setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker);
669
        }
670

671
        if (!object.isEmpty())
scrawl's avatar
scrawl committed
672
            getCreatureStats(ptr).setLastHitAttemptObject(object.getCellRef().getRefId());
673

674
        if (setOnPcHitMe && !attacker.isEmpty() && attacker == MWMechanics::getPlayer())
scrawl's avatar
scrawl committed
675 676 677 678 679 680 681
        {
            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);
        }

682
        if (!successful)
683 684
        {
            // Missed
685
            sndMgr->playSound3D(ptr, "miss", 1.0f, 1.0f);
686 687
            return;
        }
Chris Robinson's avatar
Chris Robinson committed
688

689
        if (!object.isEmpty())
scrawl's avatar
scrawl committed
690
            getCreatureStats(ptr).setLastHitObject(object.getCellRef().getRefId());
691

692

693 694 695
        if (damage > 0.0f && !object.isEmpty())
            MWMechanics::resistNormalWeapon(ptr, attacker, object, damage);

696 697 698
        if (damage < 0.001f)
            damage = 0;

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

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

scrawl's avatar
scrawl committed
707
            int chance = store.get<ESM::GameSetting>().find("iVoiceHitOdds")->getInt();
scrawl's avatar
scrawl committed
708
            if (Misc::Rng::roll0to99() < chance)
scrawl's avatar
scrawl committed
709 710 711
            {
                MWBase::Environment::get().getDialogueManager()->say(ptr, "hit");
            }
712 713

            // Check for knockdown
714
            float agilityTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() * gmst.fKnockDownMult->getFloat();
715
            float knockdownTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified()
716
                    * gmst.iKnockDownOddsMult->getInt() * 0.01f + gmst.iKnockDownOddsBase->getInt();
scrawl's avatar
scrawl committed
717
            if (ishealth && agilityTerm <= damage && knockdownTerm <= Misc::Rng::roll0to99())
718 719 720 721 722 723
            {
                getCreatureStats(ptr).setKnockedDown(true);

            }
            else
                getCreatureStats(ptr).setHitRecovery(true); // Is this supposed to always occur?
724

725
            if (damage > 0 && ishealth)
726
            {
727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742
                // 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
743
                int hitslot = hitslots[Misc::Rng::rollDice(20)];
744

745 746 747
                float unmitigatedDamage = damage;
                float x = damage / (damage + getArmorRating(ptr));
                damage *= std::max(gmst.fCombatArmorMinMult->getFloat(), x);
748
                int damageDiff = static_cast<int>(unmitigatedDamage - damage);
749 750
                if (damage < 1)
                    damage = 1;
751

752 753 754 755
                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())
756
                {
757
                    int armorhealth = armor.getClass().getItemHealth(armor);
758
                    armorhealth -= std::min(std::max(1, damageDiff),
759 760
                                                 armorhealth);
                    armor.getCellRef().setCharge(armorhealth);
761 762

                    // Armor broken? unequip it
763
                    if (armorhealth == 0)
764
                        armor = *inv.unequipItem(armor, ptr);
765

dteviot's avatar
dteviot committed
766
                    if (ptr == MWMechanics::getPlayer())
767
                        skillUsageSucceeded(ptr, armor.getClass().getEquipmentSkill(armor), 0);
768

769
                    switch(armor.getClass().getEquipmentSkill(armor))
770 771 772 773 774 775 776 777 778 779 780
                    {
                        case ESM::Skill::LightArmor:
                            sndMgr->playSound3D(ptr, "Light Armor Hit", 1.0f, 1.0f);
                            break;
                        case ESM::Skill::MediumArmor:
                            sndMgr->playSound3D(ptr, "Medium Armor Hit", 1.0f, 1.0f);
                            break;
                        case ESM::Skill::HeavyArmor:
                            sndMgr->playSound3D(ptr, "Heavy Armor Hit", 1.0f, 1.0f);
                            break;
                    }
781
                }
dteviot's avatar
dteviot committed
782
                else if(ptr == MWMechanics::getPlayer())
783
                    skillUsageSucceeded(ptr, ESM::Skill::Unarmored, 0);
784
            }
Chris Robinson's avatar
Chris Robinson committed
785 786
        }

787
        if (ishealth)
788
        {
789 790 791
            if (!attacker.isEmpty())
                damage = scaleDamage(damage, attacker, ptr);

792
            if (damage > 0.0f)