npc.cpp 58.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

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

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

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

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

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

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

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

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

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

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

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

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

        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;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

321 322 323 324 325 326 327 328
                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);
329

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

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

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

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

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

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

                data->mNpcStats.setNeedRecalcDynamicStats(true);
355
            }
356 357

            // Persistent actors with 0 health do not play death animation
358
            if (data->mNpcStats.isDead())
359
                data->mNpcStats.setDeathAnimationFinished(ptr.getClass().isPersistent(ptr));
360

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

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

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

383
            data->mNpcStats.getAiSequence().fill(ref->mBase->mAiPackage);
384

385 386 387 388
            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);
389

390
            // spells
greye's avatar
greye committed
391 392
            for (std::vector<std::string>::const_iterator iter (ref->mBase->mSpells.mList.begin());
                iter!=ref->mBase->mSpells.mList.end(); ++iter)
393
            {
scrawl's avatar
scrawl committed
394
                if (const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search(*iter))
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
399
                    Log(Debug::Warning) << "Warning: ignoring nonexistent spell '" << *iter << "' on NPC '" << ref->mBase->mId << "'";
400 401
                }
            }
402

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

jefhai's avatar
jefhai committed
407 408
            data->mNpcStats.setGoldPool(gold);

409
            // store
410
            ptr.getRefData().setCustomData (data.release());
411

412
            getInventoryStore(ptr).autoEquip(ptr);
413 414 415
        }
    }

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

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

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

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

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

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

522 523
    }

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

531
            return store.find("sWerewolfPopup")->mValue.getString();
532
        }
Marc Zinnschlag's avatar
Marc Zinnschlag committed
533

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

538
    MWMechanics::CreatureStats& Npc::getCreatureStats (const MWWorld::Ptr& ptr) const
539
    {
540
        ensureCustomData (ptr);
541

542
        return ptr.getRefData().getCustomData()->asNpcCustomData().mNpcStats;
543 544
    }

Marc Zinnschlag's avatar
Marc Zinnschlag committed
545 546
    MWMechanics::NpcStats& Npc::getNpcStats (const MWWorld::Ptr& ptr) const
    {
547
        ensureCustomData (ptr);
Marc Zinnschlag's avatar
Marc Zinnschlag committed
548

549
        return ptr.getRefData().getCustomData()->asNpcCustomData().mNpcStats;
Marc Zinnschlag's avatar
Marc Zinnschlag committed
550 551
    }

Chris Robinson's avatar
Chris Robinson committed
552

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

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

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

566
        MWMechanics::applyFatigueLoss(ptr, weapon, attackStrength);
567

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

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

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

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

595
        int weapskill = ESM::Skill::HandToHand;
596
        if(!weapon.isEmpty())
597
            weapskill = weapon.getClass().getEquipmentSkill(weapon);
598

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

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

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

scrawl's avatar
scrawl committed
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)
            {
641
                damage *= store.find("fCombatCriticalStrikeMult")->mValue.getFloat();
scrawl's avatar
scrawl committed
642 643 644
                MWBase::Environment::get().getWindowManager()->messageBox("#{sTargetCriticalStrike}");
                MWBase::Environment::get().getSoundManager()->playSound3D(victim, "critical damage", 1.0f, 1.0f);
            }
645
        }
scrawl's avatar
scrawl committed
646

647
        if (othercls.getCreatureStats(victim).getKnockedDown())
648
            damage *= store.find("fCombatKODamageMult")->mValue.getFloat();
649

650
        // Apply "On hit" enchanted weapons
651
        MWMechanics::applyOnStrikeEnchantment(ptr, victim, weapon, hitPosition);
652

653 654
        MWMechanics::applyElementalShields(ptr, victim);

655
        if (MWMechanics::blockMeleeAttack(ptr, victim, weapon, damage, attackStrength))
656 657
            damage = 0;

658 659 660
        if (victim == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState())
            damage = 0;

661 662
        MWMechanics::diseaseContact(victim, ptr);

663
        othercls.onHit(victim, damage, healthdmg, weapon, ptr, hitPosition, true);
Chris Robinson's avatar
Chris Robinson committed
664 665
    }

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

672 673
        // Note OnPcHitMe is not set for friendly hits.
        bool setOnPcHitMe = true;
674

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

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

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

699
        if (!object.isEmpty())
700
            stats.setLastHitAttemptObject(object.getCellRef().getRefId());
701

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

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

717
        if (!object.isEmpty())
718
            stats.setLastHitObject(object.getCellRef().getRefId());
719

720

721 722 723
        if (damage > 0.0f && !object.isEmpty())
            MWMechanics::resistNormalWeapon(ptr, attacker, object, damage);

724 725 726
        if (damage < 0.001f)
            damage = 0;

727 728 729 730 731
        bool godmode = ptr == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState();

        if (godmode)
            damage = 0;

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

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

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

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

753
            if (damage > 0 && ishealth)
754
            {
755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770
                // 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