npc.cpp 57.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
#include <components/settings/settings.hpp>
11

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

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

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

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

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

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

48 49 50 51 52 53 54 55 56 57
    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)
58
            return static_cast<int>(i);
59
        if(d > 0.5)
60
            return static_cast<int>(i) + 1;
61
        if(is_even(i))
62 63
            return static_cast<int>(i);
        return static_cast<int>(i) + 1;
64 65
    }

66 67 68 69 70 71 72 73 74 75 76 77
    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];
78
            creatureStats.setAttribute(i, male ? attribute.mMale : attribute.mFemale);
79 80 81 82 83 84 85 86 87 88 89
        }

        // 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)
            {
90
                creatureStats.setAttribute(attribute,
91 92 93 94 95
                    creatureStats.getAttribute(attribute).getBase() + 10);
            }
        }

        // skill bonus
96
        for (int attribute=0; attribute < ESM::Attribute::Length; ++attribute)
97 98 99 100 101 102 103 104 105 106 107
        {
            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?
108
                float add=0.2f;
109 110 111 112 113 114 115 116 117 118 119 120
                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;
            }
121 122 123
            creatureStats.setAttribute(attribute, std::min(
                                           round_ieee_754(creatureStats.getAttribute(attribute).getBase()
                + (level-1) * modifierSum), 100) );
124
        }
125

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

        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;

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

    /**
     * @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.
     */
158
    void autoCalculateSkills(const ESM::NPC* npc, MWMechanics::NpcStats& npcStats, const MWWorld::Ptr& ptr)
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 191
    {
        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)
            {
192 193 194 195 196
                if (race->mData.mBonus[raceSkillIndex].mSkill == skillIndex)
                {
                    raceBonus = race->mData.mBonus[raceSkillIndex].mBonus;
                    break;
                }
197 198 199 200
            }

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

            // 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)
            {
213 214
                specMultiplier = 0.5f;
                specBonus = 5;
215 216 217 218
            }

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

        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();

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

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

    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
257 258 259 260
        virtual const NpcCustomData& asNpcCustomData() const
        {
            return *this;
        }
261 262 263 264 265 266 267
    };

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

268
    const Npc::GMST& Npc::getGmst()
269
    {
270
        static GMST gmst;
271 272 273 274
        static bool inited = false;
        if(!inited)
        {
            const MWBase::World *world = MWBase::Environment::get().getWorld();
275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295
            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");
296
            gmst.fCombatArmorMinMult = store.find("fCombatArmorMinMult");
297 298 299

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

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

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

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

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

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

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

333 334 335
                data->mNpcStats.setLevel(ref->mBase->mNpdt.mLevel);
                data->mNpcStats.setBaseDisposition(ref->mBase->mNpdt.mDisposition);
                data->mNpcStats.setReputation(ref->mBase->mNpdt.mReputation);
336 337

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

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

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

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

                data->mNpcStats.setNeedRecalcDynamicStats(true);
354
            }
355
            if (data->mNpcStats.isDead())
356
                data->mNpcStats.setDeathAnimationFinished(ptr.getClass().isPersistent(ptr));
357

358 359 360 361 362
            // 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
363
                if (const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search(*iter))
364 365 366
                    data->mNpcStats.getSpells().add (spell);
                else
                    std::cerr << "Warning: ignoring nonexistent race power '" << *iter << "' on NPC '" << ref->mBase->mId << "'" << std::endl;
367 368
            }

scrawl's avatar
scrawl committed
369
            if (!ref->mBase->mFaction.empty())
370 371 372 373 374
            {
                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
375
                int rank = ref->mBase->getFactionRank();
376 377 378 379

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

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

382 383 384 385
            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);
386

387
            // spells
greye's avatar
greye committed
388 389
            for (std::vector<std::string>::const_iterator iter (ref->mBase->mSpells.mList.begin());
                iter!=ref->mBase->mSpells.mList.end(); ++iter)
390
            {
scrawl's avatar
scrawl committed
391
                if (const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search(*iter))
392 393 394 395 396 397 398
                    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;
                }
            }
399

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

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

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

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

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

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

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

greye's avatar
greye committed
428
        std::string model = "meshes\\base_anim.nif";
429 430
        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
431
            model = "meshes\\base_animkna.nif";
432

greye's avatar
greye committed
433
        return model;
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 463
    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
464 465
        bool female = (npc->mBase->mFlags & ESM::NPC::Female);

466
        // FIXME: use const version of InventoryStore functions once they are available
scrawl's avatar
scrawl committed
467
        // preload equipped items
468 469
        if (ptr.getClass().hasInventoryStore(ptr))
        {
470
            const MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore(ptr);
471 472
            for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot)
            {
473
                MWWorld::ConstContainerStoreIterator equipped = invStore.getSlot(slot);
474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495
                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
496
                        std::string partname = female ? it->mFemale : it->mMale;
497
                        if (partname.empty())
scrawl's avatar
scrawl committed
498
                            partname = female ? it->mMale : it->mFemale;
499 500 501 502 503 504 505 506
                        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
507 508 509 510 511 512 513 514 515 516 517 518
        // 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);
            }
        }

519 520
    }

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

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

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

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

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

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

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

Chris Robinson's avatar
Chris Robinson committed
549

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

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

556 557
        // Get the weapon used (if hand-to-hand, weapon = inv.end())
        MWWorld::InventoryStore &inv = getInventoryStore(ptr);
558 559 560 561
        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();
562

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

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

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

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

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

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

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

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

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

scrawl's avatar
scrawl committed
632 633 634 635 636 637 638 639 640 641
            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);
            }
642
        }
scrawl's avatar
scrawl committed
643

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

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

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

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

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

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

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

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

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

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

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

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

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

699
        if (setOnPcHitMe && !attacker.isEmpty() && attacker == MWMechanics::getPlayer())
scrawl's avatar
scrawl committed
700 701 702 703 704 705 706
        {
            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);
        }

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

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

717

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

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

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

        if (godmode)
            damage = 0;

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

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

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

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

750
            if (damage > 0 && ishealth)
751
            {
752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767
                // 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
768
                int hitslot = hitslots[Misc::Rng::rollDice(20)];
769

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

777 778 779 780
                MWWorld::InventoryStore &inv = getInventoryStore(ptr);
                MWWorld::ContainerStoreIterator armorslot = inv.getSlot(hitslot);
                MWWorld::Ptr armor