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

3 4
#include <memory>

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

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

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

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

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

38
#include "../mwrender/objects.hpp"
39
#include "../mwrender/renderinginterface.hpp"
40

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

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

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

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

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

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

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

        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;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

318 319 320 321 322 323 324 325 326
                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);

327 328 329 330 331
                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
332
                data->mNpcStats.setBaseDisposition(ref->mBase->mNpdt52.mDisposition);
333
                data->mNpcStats.setReputation(ref->mBase->mNpdt52.mReputation);
334 335

                data->mNpcStats.setNeedRecalcDynamicStats(false);
336 337 338
            }
            else
            {
339 340
                gold = ref->mBase->mNpdt12.mGold;

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

344
                data->mNpcStats.setLevel(ref->mBase->mNpdt12.mLevel);
345 346
                data->mNpcStats.setBaseDisposition(ref->mBase->mNpdt12.mDisposition);
                data->mNpcStats.setReputation(ref->mBase->mNpdt12.mReputation);
347

348
                autoCalculateAttributes(ref->mBase, data->mNpcStats);
349
                autoCalculateSkills(ref->mBase, data->mNpcStats, ptr);
350 351

                data->mNpcStats.setNeedRecalcDynamicStats(true);
352
            }
353

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

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

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

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

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

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

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

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

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

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

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

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

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

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

greye's avatar
greye committed
429
        return model;
430 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 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488
    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);
        }

        // FIXME: use const version of InventoryStore functions once they are available
        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)
                    {
489 490 491
                        std::string partname = (npc->mBase->mFlags & ESM::NPC::Female) ? it->mFemale : it->mMale;
                        if (partname.empty())
                            partname = (npc->mBase->mFlags & ESM::NPC::Female) ? it->mMale : it->mFemale;
492 493 494 495 496 497 498 499 500 501
                        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
502
    std::string Npc::getName (const MWWorld::ConstPtr& ptr) const
Marc Zinnschlag's avatar
Marc Zinnschlag committed
503
    {
scrawl's avatar
scrawl committed
504
        if(ptr.getRefData().getCustomData() && ptr.getRefData().getCustomData()->asNpcCustomData().mNpcStats.isWerewolf())
505 506
        {
            const MWBase::World *world = MWBase::Environment::get().getWorld();
507
            const MWWorld::Store<ESM::GameSetting> &store = world->getStore().get<ESM::GameSetting>();
508

509
            return store.find("sWerewolfPopup")->getString();
510
        }
Marc Zinnschlag's avatar
Marc Zinnschlag committed
511

scrawl's avatar
scrawl committed
512
        const MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>();
greye's avatar
greye committed
513
        return ref->mBase->mName;
Marc Zinnschlag's avatar
Marc Zinnschlag committed
514 515
    }

516
    MWMechanics::CreatureStats& Npc::getCreatureStats (const MWWorld::Ptr& ptr) const
517
    {
518
        ensureCustomData (ptr);
519

520
        return ptr.getRefData().getCustomData()->asNpcCustomData().mNpcStats;
521 522
    }

Marc Zinnschlag's avatar
Marc Zinnschlag committed
523 524
    MWMechanics::NpcStats& Npc::getNpcStats (const MWWorld::Ptr& ptr) const
    {
525
        ensureCustomData (ptr);
Marc Zinnschlag's avatar
Marc Zinnschlag committed
526

527
        return ptr.getRefData().getCustomData()->asNpcCustomData().mNpcStats;
Marc Zinnschlag's avatar
Marc Zinnschlag committed
528 529
    }

Chris Robinson's avatar
Chris Robinson committed
530

531
    void Npc::hit(const MWWorld::Ptr& ptr, float attackStrength, int type) const
Chris Robinson's avatar
Chris Robinson committed
532
    {
533
        MWBase::World *world = MWBase::Environment::get().getWorld();
534 535

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

537 538
        // Get the weapon used (if hand-to-hand, weapon = inv.end())
        MWWorld::InventoryStore &inv = getInventoryStore(ptr);
539 540 541 542
        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();
543

544
        MWMechanics::applyFatigueLoss(ptr, weapon, attackStrength);
545

546
        const float fCombatDistance = store.find("fCombatDistance")->getFloat();
scrawl's avatar
scrawl committed
547
        float dist = fCombatDistance * (!weapon.isEmpty() ?
548
                               weapon.get<ESM::Weapon>()->mBase->mData.mReach :
549
                               store.find("fHandToHandReach")->getFloat());
scrawl's avatar
scrawl committed
550

scrawl's avatar
scrawl committed
551
        // TODO: Use second to work out the hit angle
scrawl's avatar
scrawl committed
552
        std::pair<MWWorld::Ptr, osg::Vec3f> result = world->getHitContact(ptr, dist);
scrawl's avatar
scrawl committed
553
        MWWorld::Ptr victim = result.first;
scrawl's avatar
scrawl committed
554
        osg::Vec3f hitPosition (result.second);
Chris Robinson's avatar
Chris Robinson committed
555 556 557
        if(victim.isEmpty()) // Didn't hit anything
            return;

558
        const MWWorld::Class &othercls = victim.getClass();
559 560
        if(!othercls.isActor()) // Can't hit non-actors
            return;
561
        MWMechanics::CreatureStats &otherstats = othercls.getCreatureStats(victim);
562
        if(otherstats.isDead()) // Can't hit dead actors
Chris Robinson's avatar
Chris Robinson committed
563 564
            return;

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

568
        int weapskill = ESM::Skill::HandToHand;
569
        if(!weapon.isEmpty())
570
            weapskill = weapon.getClass().getEquipmentSkill(weapon);
571

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

574
        if (Misc::Rng::roll0to99() >= hitchance)
575
        {
576
            othercls.onHit(victim, 0.0f, false, weapon, ptr, false);
577
            MWMechanics::reduceWeaponCondition(0.f, false, weapon, ptr);
578 579
            return;
        }
Chris Robinson's avatar
Chris Robinson committed
580

581
        bool healthdmg;
582 583
        float damage = 0.0f;
        if(!weapon.isEmpty())
Chris Robinson's avatar
Chris Robinson committed
584
        {
585
            const unsigned char *attack = NULL;
mrcheko's avatar
mrcheko committed
586
            if(type == ESM::Weapon::AT_Chop)
587
                attack = weapon.get<ESM::Weapon>()->mBase->mData.mChop;
mrcheko's avatar
mrcheko committed
588
            else if(type == ESM::Weapon::AT_Slash)
589
                attack = weapon.get<ESM::Weapon>()->mBase->mData.mSlash;
mrcheko's avatar
mrcheko committed
590
            else if(type == ESM::Weapon::AT_Thrust)
591
                attack = weapon.get<ESM::Weapon>()->mBase->mData.mThrust;
592
            if(attack)
593
            {
594
                damage  = attack[0] + ((attack[1]-attack[0])*attackStrength);
595
            }
scrawl's avatar
scrawl committed
596
            MWMechanics::adjustWeaponDamage(damage, weapon, ptr);
597
            MWMechanics::reduceWeaponCondition(damage, true, weapon, ptr);
598 599 600 601
            healthdmg = true;
        }
        else
        {
602
            MWMechanics::getHandToHandDamage(ptr, victim, damage, healthdmg, attackStrength);
Chris Robinson's avatar
Chris Robinson committed
603
        }
dteviot's avatar
dteviot committed
604
        if(ptr == MWMechanics::getPlayer())
scrawl's avatar
scrawl committed
605
        {
606
            skillUsageSucceeded(ptr, weapskill, 0);
607

scrawl's avatar
scrawl committed
608 609 610 611 612 613 614 615 616 617
            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);
            }
618
        }
scrawl's avatar
scrawl committed
619

620
        if (othercls.getCreatureStats(victim).getKnockedDown())
621
            damage *= store.find("fCombatKODamageMult")->getFloat();
622

623
        // Apply "On hit" enchanted weapons
624
        std::string enchantmentName = !weapon.isEmpty() ? weapon.getClass().getEnchantment(weapon) : "";
625 626 627 628 629 630
        if (!enchantmentName.empty())
        {
            const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().find(
                        enchantmentName);
            if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes)
            {
631 632
                MWMechanics::CastSpell cast(ptr, victim);
                cast.mHitPosition = hitPosition;
633
                cast.cast(weapon);
634 635 636
            }
        }

637 638
        MWMechanics::applyElementalShields(ptr, victim);

639
        if (MWMechanics::blockMeleeAttack(ptr, victim, weapon, damage, attackStrength))
640 641 642
            damage = 0;

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

645 646
        MWMechanics::diseaseContact(victim, ptr);

647
        othercls.onHit(victim, damage, healthdmg, weapon, ptr, true);
Chris Robinson's avatar
Chris Robinson committed
648 649
    }

650
    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
651
    {
652 653
        MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();

654 655
        // NOTE: 'object' and/or 'attacker' may be empty.

656 657
        bool wasDead = getCreatureStats(ptr).isDead();

658 659
        // Note OnPcHitMe is not set for friendly hits.
        bool setOnPcHitMe = true;
660 661 662
        if (!attacker.isEmpty() && !ptr.getClass().getCreatureStats(ptr).getAiSequence().isInCombat(attacker))
        {
            getCreatureStats(ptr).setAttacked(true);
663

664
            setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker);
665
        }
666

667
        if(!object.isEmpty())
scrawl's avatar
scrawl committed
668
            getCreatureStats(ptr).setLastHitAttemptObject(object.getCellRef().getRefId());
669

dteviot's avatar
dteviot committed
670
        if(setOnPcHitMe && !attacker.isEmpty() && attacker == MWMechanics::getPlayer())
scrawl's avatar
scrawl committed
671 672 673 674 675 676 677
        {
            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);
        }

678 679 680
        if(!successful)
        {
            // Missed
681
            sndMgr->playSound3D(ptr, "miss", 1.0f, 1.0f);
682 683
            return;
        }
Chris Robinson's avatar
Chris Robinson committed
684

685
        if(!object.isEmpty())
scrawl's avatar
scrawl committed
686
            getCreatureStats(ptr).setLastHitObject(object.getCellRef().getRefId());
687

688

689 690 691
        if (damage > 0.0f && !object.isEmpty())
            MWMechanics::resistNormalWeapon(ptr, attacker, object, damage);

692 693 694
        if (damage < 0.001f)
            damage = 0;

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

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

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

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

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

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

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

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

                    // Armor broken? unequip it
759
                    if (armorhealth == 0)
760
                        armor = *inv.unequipItem(armor, ptr);
761

dteviot's avatar
dteviot committed
762
                    if (ptr == MWMechanics::getPlayer())
763
                        skillUsageSucceeded(ptr, armor.getClass().getEquipmentSkill(armor), 0);
764

765
                    switch(armor.getClass().getEquipmentSkill(armor))
766 767 768 769 770 771 772 773 774 775 776
                    {
                        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;
                    }
777
                }
dteviot's avatar
dteviot committed
778
                else if(ptr == MWMechanics::getPlayer())
779
                    skillUsageSucceeded(ptr, ESM::Skill::Unarmored, 0);
780
            }
Chris Robinson's avatar
Chris Robinson committed
781 782
        }

783 784
        if(ishealth)
        {
785 786 787
            if (!attacker.isEmpty())
                damage = scaleDamage(damage, attacker, ptr);

788
            if(damage > 0.0f)
789
            {
790
                sndMgr->playSound3D(ptr, "Health Damage", 1.0f, 1.0f);
dteviot's avatar
dteviot committed
791
                if (ptr == MWMechanics::getPlayer())
792 793
                    MWBase::Environment::get().getWindowManager()->activateHitOverlay();
            }
scrawl's avatar
scrawl committed
794 795 796
            MWMechanics::DynamicStat<float> health(getCreatureStats(ptr).getHealth());
            health.setCurrent(health.getCurrent() - damage);
            getCreatureStats(ptr).setHealth(health);