npc.cpp 56.5 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
        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
        MWMechanics::diseaseContact(victim, ptr);

656
        othercls.onHit(victim, damage, healthdmg, weapon, ptr, hitPosition, true);
Chris Robinson's avatar
Chris Robinson committed
657 658
    }

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

665 666
        // Note OnPcHitMe is not set for friendly hits.
        bool setOnPcHitMe = true;
667

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

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

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

692
        if (!object.isEmpty())
693
            stats.setLastHitAttemptObject(object.getCellRef().getRefId());
694

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

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

710
        if (!object.isEmpty())
711
            stats.setLastHitObject(object.getCellRef().getRefId());
712

713

714 715 716
        if (damage > 0.0f && !object.isEmpty())
            MWMechanics::resistNormalWeapon(ptr, attacker, object, damage);

717 718 719
        if (damage < 0.001f)
            damage = 0;

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

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

scrawl's avatar
scrawl committed
728
            int chance = store.get<ESM::GameSetting>().find("iVoiceHitOdds")->getInt();
scrawl's avatar
scrawl committed
729
            if (Misc::Rng::roll0to99() < chance)
scrawl's avatar
scrawl committed
730
                MWBase::Environment::get().getDialogueManager()->say(ptr, "hit");
731 732

            // Check for knockdown
733 734
            float agilityTerm = stats.getAttribute(ESM::Attribute::Agility).getModified() * gmst.fKnockDownMult->getFloat();
            float knockdownTerm = stats.getAttribute(ESM::Attribute::Agility).getModified()
735
                    * gmst.iKnockDownOddsMult->getInt() * 0.01f + gmst.iKnockDownOddsBase->getInt();
scrawl's avatar
scrawl committed
736
            if (ishealth && agilityTerm <= damage && knockdownTerm <= Misc::Rng::roll0to99())
737
                stats.setKnockedDown(true);
738
            else
739
                stats.setHitRecovery(true); // Is this supposed to always occur?
740

741
            if (damage > 0 && ishealth)
742
            {
743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758
                // 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
759
                int hitslot = hitslots[Misc::Rng::rollDice(20)];
760

761 762 763
                float unmitigatedDamage = damage;
                float x = damage / (damage + getArmorRating(ptr));
                damage *= std::max(gmst.fCombatArmorMinMult->getFloat(), x);
764
                int damageDiff = static_cast<int>(unmitigatedDamage - damage);
765 766
                if (damage < 1)
                    damage = 1;
767

768 769 770 771
                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())
772
                {
773
                    int armorhealth = armor.getClass().getItemHealth(armor);
774
                    armorhealth -= std::min(std::max(1, damageDiff),
775 776
                                                 armorhealth);
                    armor.getCellRef().setCharge(armorhealth);
777 778

                    // Armor broken? unequip it
779
                    if (armorhealth == 0)