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

3 4
#include <memory>

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        int multiplier = 3;

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

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

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

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

        unsigned int level = npcStats.getLevel();

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


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

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

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

            int raceBonus = 0;
            int specBonus = 0;

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

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

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

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

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

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

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

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

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

        virtual MWWorld::CustomData *clone() const;

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

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

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

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

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

    void Npc::ensureCustomData (const MWWorld::Ptr& ptr) const
    {
304 305
        if (!ptr.getRefData().getCustomData())
        {
306
            std::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 356 357 358 359
            // 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
360
                if (const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search(*iter))
361 362 363
                    data->mNpcStats.getSpells().add (spell);
                else
                    std::cerr << "Warning: ignoring nonexistent race power '" << *iter << "' on NPC '" << ref->mBase->mId << "'" << std::endl;
364 365
            }

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

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

377
            data->mNpcStats.getAiSequence().fill(ref->mBase->mAiPackage);
378

379 380 381 382
            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);
383

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

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

jefhai's avatar
jefhai committed
401 402
            data->mNpcStats.setGoldPool(gold);

403
            // store
404
            ptr.getRefData().setCustomData (data.release());
405

406
            getInventoryStore(ptr).autoEquip(ptr); 
407 408 409
        }
    }

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

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

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

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

greye's avatar
greye committed
430
        return model;
431 432
    }

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
    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
461 462
        bool female = (npc->mBase->mFlags & ESM::NPC::Female);

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

                    for (std::vector<ESM::PartReference>::const_iterator it = parts.begin(); it != parts.end(); ++it)
                    {
scrawl's avatar
scrawl committed
493
                        std::string partname = female ? it->mFemale : it->mMale;
494
                        if (partname.empty())
scrawl's avatar
scrawl committed
495
                            partname = female ? it->mMale : it->mFemale;
496 497 498 499 500 501 502 503
                        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
504 505 506 507 508 509 510 511 512 513 514 515
        // 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);
            }
        }

516 517
    }

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

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

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

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

536
        return ptr.getRefData().getCustomData()->asNpcCustomData().mNpcStats;
537 538
    }

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

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

Chris Robinson's avatar
Chris Robinson committed
546

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

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

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

560
        MWMechanics::applyFatigueLoss(ptr, weapon, attackStrength);
561

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

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

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

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

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

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

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

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

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

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

639
        // Apply "On hit" enchanted weapons
640
        std::string enchantmentName = !weapon.isEmpty() ? weapon.getClass().getEnchantment(weapon) : "";
641 642 643 644 645 646
        if (!enchantmentName.empty())
        {
            const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().find(
                        enchantmentName);
            if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes)
            {
647 648
                MWMechanics::CastSpell cast(ptr, victim);
                cast.mHitPosition = hitPosition;
649
                cast.cast(weapon);
650 651 652
            }
        }

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

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

        if (healthdmg && damage > 0)
scrawl's avatar
scrawl committed
659
            MWBase::Environment::get().getWorld()->spawnBloodEffect(victim, hitPosition);
scrawl's avatar
scrawl committed
660

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

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

666
    void Npc::onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, bool successful) const
Chris Robinson's avatar
Chris Robinson committed
667
    {
668 669
        MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();

670 671
        // NOTE: 'object' and/or 'attacker' may be empty.

672 673
        bool wasDead = getCreatureStats(ptr).isDead();

674 675
        // Note OnPcHitMe is not set for friendly hits.
        bool setOnPcHitMe = true;
676 677 678
        if (!attacker.isEmpty() && !ptr.getClass().getCreatureStats(ptr).getAiSequence().isInCombat(attacker))
        {
            getCreatureStats(ptr).setAttacked(true);
679

680
            setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker);
681
        }
682

683
        if(!object.isEmpty())
scrawl's avatar
scrawl committed
684
            getCreatureStats(ptr).setLastHitAttemptObject(object.getCellRef().getRefId());
685

dteviot's avatar
dteviot committed
686
        if(setOnPcHitMe && !attacker.isEmpty() && attacker == MWMechanics::getPlayer())
scrawl's avatar
scrawl committed
687 688 689 690 691 692 693
        {
            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);
        }

694 695 696
        if(!successful)
        {
            // Missed
697
            sndMgr->playSound3D(ptr, "miss", 1.0f, 1.0f);
698 699
            return;
        }
Chris Robinson's avatar
Chris Robinson committed
700

701
        if(!object.isEmpty())
scrawl's avatar
scrawl committed
702
            getCreatureStats(ptr).setLastHitObject(object.getCellRef().getRefId());
703

704

705 706 707
        if (damage > 0.0f && !object.isEmpty())
            MWMechanics::resistNormalWeapon(ptr, attacker, object, damage);

708 709 710
        if (damage < 0.001f)
            damage = 0;

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

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

scrawl's avatar
scrawl committed
719
            int chance = store.get<ESM::GameSetting>().find("iVoiceHitOdds")->getInt();
scrawl's avatar
scrawl committed
720
            if (Misc::Rng::roll0to99() < chance)
scrawl's avatar
scrawl committed
721 722 723
            {
                MWBase::Environment::get().getDialogueManager()->say(ptr, "hit");
            }
724 725

            // Check for knockdown
726
            float agilityTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() * gmst.fKnockDownMult->getFloat();
727
            float knockdownTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified()
728
                    * gmst.iKnockDownOddsMult->getInt() * 0.01f + gmst.iKnockDownOddsBase->getInt();
scrawl's avatar
scrawl committed
729
            if (ishealth && agilityTerm <= damage && knockdownTerm <= Misc::Rng::roll0to99())
730 731 732 733 734 735
            {
                getCreatureStats(ptr).setKnockedDown(true);

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

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

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

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

                    // Armor broken? unequip it
775
                    if (armorhealth == 0)
776
                        armor = *inv.unequipItem(armor, ptr);
777

dteviot's avatar
dteviot committed
778
                    if (ptr == MWMechanics::getPlayer())
779
                        skillUsageSucceeded(ptr, armor.getClass().getEquipmentSkill(armor), 0);
780

781
                    switch(armor.getClass().getEquipmentSkill(armor))
782 783 784 785 786 787 788 789 790 791 792
                    {
                        case ESM::Skill::LightArmor:
                            sndMgr->playSound3D(ptr, "Light Armor Hit", 1.0f, 1.0f);
                            break;
                        case ESM::Skill::MediumArmor:
                            sndMgr->playSound3D(ptr, "Medium Armor Hit", 1.0f, 1.0f);
                            break;
                        case ESM::Skill::HeavyArmor:
                            sndMgr->playSound3D(ptr, "Heavy Armor Hit", 1.0f, 1.0f);
                            break;
                    }
793
                }
dteviot's avatar
dteviot committed
794
                else if(ptr == MWMechanics::getPlayer())
795
                    skillUsageSucceeded(ptr, ESM::Skill::Unarmored, 0);
796
            }