npc.cpp 50.8 KB
Newer Older
1 2 3

#include "npc.hpp"

4 5
#include <memory>

6 7
#include <OgreSceneNode.h>

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

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"
Marc Zinnschlag's avatar
Marc Zinnschlag committed
23

24
#include "../mwworld/ptr.hpp"
25
#include "../mwworld/actiontalk.hpp"
scrawl's avatar
scrawl committed
26
#include "../mwworld/actionopen.hpp"
27
#include "../mwworld/failedaction.hpp"
28
#include "../mwworld/inventorystore.hpp"
Marc Zinnschlag's avatar
Marc Zinnschlag committed
29
#include "../mwworld/customdata.hpp"
30
#include "../mwworld/physicssystem.hpp"
31

32
#include "../mwrender/actors.hpp"
33
#include "../mwrender/renderinginterface.hpp"
34

35
#include "../mwgui/tooltips.hpp"
36

Marc Zinnschlag's avatar
Marc Zinnschlag committed
37
namespace
38 39 40
{
    const Ogre::Radian kOgrePi (Ogre::Math::PI);
    const Ogre::Radian kOgrePiOverTwo (Ogre::Math::PI / Ogre::Real(2.0));
41 42 43 44

    struct CustomData : public MWWorld::CustomData
    {
        MWMechanics::NpcStats mNpcStats;
45
        MWMechanics::Movement mMovement;
46
        MWWorld::InventoryStore mInventoryStore;
47 48 49 50 51 52 53 54

        virtual MWWorld::CustomData *clone() const;
    };

    MWWorld::CustomData *CustomData::clone() const
    {
        return new CustomData (*this);
    }
55 56 57 58 59 60 61 62 63 64 65 66 67

    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];
68
            creatureStats.setAttribute(i, male ? attribute.mMale : attribute.mFemale);
69 70 71 72 73 74 75 76 77 78 79
        }

        // 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)
            {
80
                creatureStats.setAttribute(attribute,
81 82 83 84 85
                    creatureStats.getAttribute(attribute).getBase() + 10);
            }
        }

        // skill bonus
86
        for (int attribute=0; attribute < ESM::Attribute::Length; ++attribute)
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
        {
            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?
                float add=0.2;
                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;
            }
111
            creatureStats.setAttribute(attribute, std::min(creatureStats.getAttribute(attribute).getBase()
112 113
                + static_cast<int>((level-1) * modifierSum+0.5), 100) );
        }
114

115
        // initial health
116 117
        int strength = creatureStats.getAttribute(ESM::Attribute::Strength).getBase();
        int endurance = creatureStats.getAttribute(ESM::Attribute::Endurance).getBase();
118 119 120 121 122 123 124 125 126 127 128 129 130

        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;

        creatureStats.setHealth(static_cast<int> (0.5 * (strength + endurance)) + multiplier * (creatureStats.getLevel() - 1));
131
    }
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180

    /**
     * @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.
     */
    void autoCalculateSkills(const ESM::NPC* npc, MWMechanics::NpcStats& npcStats)
    {
        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)
            {
181 182 183 184 185
                if (race->mData.mBonus[raceSkillIndex].mSkill == skillIndex)
                {
                    raceBonus = race->mData.mBonus[raceSkillIndex].mBonus;
                    break;
                }
186 187 188 189
            }

            for (int k = 0; k < 5; ++k)
            {
190 191 192 193 194 195
                // is this a minor or major skill?
                if ((class_->mData.mSkills[k][0] == skillIndex) || (class_->mData.mSkills[k][1] == skillIndex))
                {
                    majorMultiplier = 1.0f;
                    break;
                }
196 197 198 199 200 201
            }

            // 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)
            {
202 203
                specMultiplier = 0.5f;
                specBonus = 5;
204 205 206 207 208 209 210 211
            }

            npcStats.getSkill(skillIndex).setBase(
                  std::min(
                    npcStats.getSkill(skillIndex).getBase()
                    + 5
                    + raceBonus
                    + specBonus
212
                    + static_cast<int>((level-1) * (majorMultiplier + specMultiplier)), 100));
213 214
        }
    }
215 216
}

217
namespace MWClass
218
{
219 220
    void Npc::ensureCustomData (const MWWorld::Ptr& ptr) const
    {
221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236
        static bool inited = false;
        if(!inited)
        {
            const MWBase::World *world = MWBase::Environment::get().getWorld();
            const MWWorld::Store<ESM::GameSetting> &gmst = world->getStore().get<ESM::GameSetting>();

            fMinWalkSpeed = gmst.find("fMinWalkSpeed");
            fMaxWalkSpeed = gmst.find("fMaxWalkSpeed");
            fEncumberedMoveEffect = gmst.find("fEncumberedMoveEffect");
            fSneakSpeedMultiplier = gmst.find("fSneakSpeedMultiplier");
            fAthleticsRunBonus = gmst.find("fAthleticsRunBonus");
            fBaseRunMultiplier = gmst.find("fBaseRunMultiplier");
            fMinFlySpeed = gmst.find("fMinFlySpeed");
            fMaxFlySpeed = gmst.find("fMaxFlySpeed");
            fSwimRunBase = gmst.find("fSwimRunBase");
            fSwimRunAthleticsMult = gmst.find("fSwimRunAthleticsMult");
237 238 239 240 241
            fJumpEncumbranceBase = gmst.find("fJumpEncumbranceBase");
            fJumpEncumbranceMultiplier = gmst.find("fJumpEncumbranceMultiplier");
            fJumpAcrobaticsBase = gmst.find("fJumpAcrobaticsBase");
            fJumpAcroMultiplier = gmst.find("fJumpAcroMultiplier");
            fJumpRunMultiplier = gmst.find("fJumpRunMultiplier");
242
            fWereWolfRunMult = gmst.find("fWereWolfRunMult");
243 244 245
            fKnockDownMult = gmst.find("fKnockDownMult");
            iKnockDownOddsMult = gmst.find("iKnockDownOddsMult");
            iKnockDownOddsBase = gmst.find("iKnockDownOddsBase");
246 247 248

            inited = true;
        }
249 250
        if (!ptr.getRefData().getCustomData())
        {
251
            std::auto_ptr<CustomData> data(new CustomData);
252

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

255
            // NPC stats
greye's avatar
greye committed
256
            if (!ref->mBase->mFaction.empty())
257
            {
greye's avatar
greye committed
258
                std::string faction = ref->mBase->mFaction;
eduard's avatar
eduard committed
259
                Misc::StringUtils::toLower(faction);
260
                if(ref->mBase->mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS)
261
                {
greye's avatar
greye committed
262
                    data->mNpcStats.getFactionRanks()[faction] = (int)ref->mBase->mNpdt52.mRank;
263 264 265
                }
                else
                {
greye's avatar
greye committed
266
                    data->mNpcStats.getFactionRanks()[faction] = (int)ref->mBase->mNpdt12.mRank;
267
                }
268 269
            }

270
            // creature stats
271
            int gold=0;
272
            if(ref->mBase->mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS)
273
            {
274 275
                gold = ref->mBase->mNpdt52.mGold;

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

279 280 281 282 283 284 285 286 287
                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);

288 289 290 291 292
                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
293
                data->mNpcStats.setBaseDisposition(ref->mBase->mNpdt52.mDisposition);
294
                data->mNpcStats.setReputation(ref->mBase->mNpdt52.mReputation);
295 296 297
            }
            else
            {
298 299
                gold = ref->mBase->mNpdt12.mGold;

300
                for (int i=0; i<3; ++i)
301
                    data->mNpcStats.setDynamic (i, 10);
302

303
                data->mNpcStats.setLevel(ref->mBase->mNpdt12.mLevel);
304 305
                data->mNpcStats.setBaseDisposition(ref->mBase->mNpdt12.mDisposition);
                data->mNpcStats.setReputation(ref->mBase->mNpdt12.mReputation);
306

307
                autoCalculateAttributes(ref->mBase, data->mNpcStats);
308
                autoCalculateSkills(ref->mBase, data->mNpcStats);
309
            }
310

311 312 313 314 315 316 317 318 319 320 321
            if (data->mNpcStats.getFactionRanks().size())
            {
                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();
                int rank = data->mNpcStats.getFactionRanks().begin()->second;

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

322
            data->mNpcStats.getAiSequence().fill(ref->mBase->mAiPackage);
323

324 325 326 327
            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);
328

329
            // spells
greye's avatar
greye committed
330 331
            for (std::vector<std::string>::const_iterator iter (ref->mBase->mSpells.mList.begin());
                iter!=ref->mBase->mSpells.mList.end(); ++iter)
332
                data->mNpcStats.getSpells().add (*iter);
333

334
            // inventory
scrawl's avatar
scrawl committed
335
            data->mInventoryStore.fill(ref->mBase->mInventory, getId(ptr), "",
336 337
                                       MWBase::Environment::get().getWorld()->getStore());

338
            // store
339
            ptr.getRefData().setCustomData (data.release());
340

scrawl's avatar
scrawl committed
341 342
            // TODO: this is not quite correct, in vanilla the merchant's gold pool is not available in his inventory.
            // (except for gold you gave him)
343
            getContainerStore(ptr).add(MWWorld::ContainerStore::sGoldId, gold, ptr);
344

345
            getInventoryStore(ptr).autoEquip(ptr);
346 347 348
        }
    }

Marc Zinnschlag's avatar
Marc Zinnschlag committed
349 350
    std::string Npc::getId (const MWWorld::Ptr& ptr) const
    {
351
        MWWorld::LiveCellRef<ESM::NPC> *ref =
Marc Zinnschlag's avatar
Marc Zinnschlag committed
352 353
            ptr.get<ESM::NPC>();

greye's avatar
greye committed
354
        return ref->mBase->mId;
Marc Zinnschlag's avatar
Marc Zinnschlag committed
355 356
    }

357 358 359 360 361
    void Npc::adjustPosition(const MWWorld::Ptr& ptr) const
    {
        MWBase::Environment::get().getWorld()->adjustPosition(ptr);
    }

Jason Hooks's avatar
Jason Hooks committed
362
    void Npc::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const
363
    {
364
        renderingInterface.getActors().insertNPC(ptr);
Jason Hooks's avatar
Jason Hooks committed
365
    }
Jason Hooks's avatar
Jason Hooks committed
366

367
    void Npc::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const
Jason Hooks's avatar
Jason Hooks committed
368
    {
369
        physics.addActor(ptr);
370
        MWBase::Environment::get().getMechanicsManager()->add(ptr);
greye's avatar
greye committed
371
    }
Jason Hooks's avatar
Jason Hooks committed
372

scrawl's avatar
scrawl committed
373 374 375 376 377 378
    bool Npc::isPersistent(const MWWorld::Ptr &actor) const
    {
        MWWorld::LiveCellRef<ESM::NPC>* ref = actor.get<ESM::NPC>();
        return ref->mBase->mPersistent;
    }

greye's avatar
greye committed
379 380
    std::string Npc::getModel(const MWWorld::Ptr &ptr) const
    {
381
        MWWorld::LiveCellRef<ESM::NPC> *ref =
Jason Hooks's avatar
Jason Hooks committed
382
            ptr.get<ESM::NPC>();
greye's avatar
greye committed
383
        assert(ref->mBase != NULL);
384

scrawl's avatar
scrawl committed
385
        //std::string headID = ref->mBase->mHead;
386

scrawl's avatar
scrawl committed
387 388
        //int end = headID.find_last_of("head_") - 4;
        //std::string bodyRaceID = headID.substr(0, end);
greye's avatar
greye committed
389 390

        std::string model = "meshes\\base_anim.nif";
391 392
        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
393
            model = "meshes\\base_animkna.nif";
394

greye's avatar
greye committed
395
        return model;
396 397 398

    }

Marc Zinnschlag's avatar
Marc Zinnschlag committed
399 400
    std::string Npc::getName (const MWWorld::Ptr& ptr) const
    {
401 402 403 404 405 406 407
        if(getNpcStats(ptr).isWerewolf())
        {
            const MWBase::World *world = MWBase::Environment::get().getWorld();
            const MWWorld::Store<ESM::GameSetting> &gmst = world->getStore().get<ESM::GameSetting>();

            return gmst.find("sWerewolfPopup")->getString();
        }
Marc Zinnschlag's avatar
Marc Zinnschlag committed
408

409
        MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>();
greye's avatar
greye committed
410
        return ref->mBase->mName;
Marc Zinnschlag's avatar
Marc Zinnschlag committed
411 412
    }

413
    MWMechanics::CreatureStats& Npc::getCreatureStats (const MWWorld::Ptr& ptr) const
414
    {
415
        ensureCustomData (ptr);
416

417
        return dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData()).mNpcStats;
418 419
    }

Marc Zinnschlag's avatar
Marc Zinnschlag committed
420 421
    MWMechanics::NpcStats& Npc::getNpcStats (const MWWorld::Ptr& ptr) const
    {
422
        ensureCustomData (ptr);
Marc Zinnschlag's avatar
Marc Zinnschlag committed
423

424
        return dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData()).mNpcStats;
Marc Zinnschlag's avatar
Marc Zinnschlag committed
425 426
    }

Chris Robinson's avatar
Chris Robinson committed
427

428
    void Npc::hit(const MWWorld::Ptr& ptr, int type) const
Chris Robinson's avatar
Chris Robinson committed
429
    {
430 431 432
        MWBase::World *world = MWBase::Environment::get().getWorld();
        const MWWorld::Store<ESM::GameSetting> &gmst = world->getStore().get<ESM::GameSetting>();

433 434
        // Get the weapon used (if hand-to-hand, weapon = inv.end())
        MWWorld::InventoryStore &inv = getInventoryStore(ptr);
435 436 437 438
        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();
439

440 441 442 443 444 445 446 447 448 449 450 451 452 453
        // Reduce fatigue
        // somewhat of a guess, but using the weapon weight makes sense
        const float fFatigueAttackBase = gmst.find("fFatigueAttackBase")->getFloat();
        const float fFatigueAttackMult = gmst.find("fFatigueAttackMult")->getFloat();
        const float fWeaponFatigueMult = gmst.find("fWeaponFatigueMult")->getFloat();
        MWMechanics::DynamicStat<float> fatigue = getCreatureStats(ptr).getFatigue();
        const float normalizedEncumbrance = getEncumbrance(ptr) / getCapacity(ptr);
        float fatigueLoss = fFatigueAttackBase + normalizedEncumbrance * fFatigueAttackMult;
        if (!weapon.isEmpty())
            fatigueLoss += weapon.getClass().getWeight(weapon) * getNpcStats(ptr).getAttackStrength() * fWeaponFatigueMult;
        fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss);
        getCreatureStats(ptr).setFatigue(fatigue);


454 455
        float dist = 100.0f * (!weapon.isEmpty() ?
                               weapon.get<ESM::Weapon>()->mBase->mData.mReach :
456
                               gmst.find("fHandToHandReach")->getFloat());
457 458
        // TODO: Use second to work out the hit angle and where to spawn the blood effect
        MWWorld::Ptr victim = world->getHitContact(ptr, dist).first;
Chris Robinson's avatar
Chris Robinson committed
459 460 461 462
        if(victim.isEmpty()) // Didn't hit anything
            return;

        const MWWorld::Class &othercls = MWWorld::Class::get(victim);
463 464
        if(!othercls.isActor()) // Can't hit non-actors
            return;
465
        MWMechanics::CreatureStats &otherstats = victim.getClass().getCreatureStats(victim);
466
        if(otherstats.isDead()) // Can't hit dead actors
Chris Robinson's avatar
Chris Robinson committed
467 468
            return;

scrawl's avatar
scrawl committed
469
        if(ptr.getRefData().getHandle() == "player")
scrawl's avatar
scrawl committed
470
            MWBase::Environment::get().getWindowManager()->setEnemy(victim);
scrawl's avatar
scrawl committed
471

472
        int weapskill = ESM::Skill::HandToHand;
473
        if(!weapon.isEmpty())
474
            weapskill = get(weapon).getEquipmentSkill(weapon);
475

476 477 478 479 480 481
        MWMechanics::NpcStats &stats = getNpcStats(ptr);
        const MWMechanics::MagicEffects &mageffects = stats.getMagicEffects();
        float hitchance = stats.getSkill(weapskill).getModified() +
                          (stats.getAttribute(ESM::Attribute::Agility).getModified() / 5.0f) +
                          (stats.getAttribute(ESM::Attribute::Luck).getModified() / 10.0f);
        hitchance *= stats.getFatigueTerm();
scrawl's avatar
scrawl committed
482 483
        hitchance += mageffects.get(ESM::MagicEffect::FortifyAttack).mMagnitude -
                     mageffects.get(ESM::MagicEffect::Blind).mMagnitude;
484
        hitchance -= otherstats.getEvasion();
485

Chris Robinson's avatar
Chris Robinson committed
486
        if((::rand()/(RAND_MAX+1.0)) > hitchance/100.0f)
487
        {
488
            othercls.onHit(victim, 0.0f, false, weapon, ptr, false);
489 490
            return;
        }
Chris Robinson's avatar
Chris Robinson committed
491

492
        bool healthdmg;
493 494
        float damage = 0.0f;
        if(!weapon.isEmpty())
Chris Robinson's avatar
Chris Robinson committed
495
        {
496
            const bool weaphashealth = get(weapon).hasItemHealth(weapon);
497
            const unsigned char *attack = NULL;
Chris Robinson's avatar
Chris Robinson committed
498
            if(type == MWMechanics::CreatureStats::AT_Chop)
499
                attack = weapon.get<ESM::Weapon>()->mBase->mData.mChop;
Chris Robinson's avatar
Chris Robinson committed
500
            else if(type == MWMechanics::CreatureStats::AT_Slash)
501
                attack = weapon.get<ESM::Weapon>()->mBase->mData.mSlash;
Chris Robinson's avatar
Chris Robinson committed
502
            else if(type == MWMechanics::CreatureStats::AT_Thrust)
503
                attack = weapon.get<ESM::Weapon>()->mBase->mData.mThrust;
504
            if(attack)
505
            {
506 507
                damage  = attack[0] + ((attack[1]-attack[0])*stats.getAttackStrength());
                damage *= 0.5f + (stats.getAttribute(ESM::Attribute::Luck).getModified() / 100.0f);
508 509 510 511 512 513 514
                if(weaphashealth)
                {
                    int weapmaxhealth = weapon.get<ESM::Weapon>()->mBase->mData.mHealth;
                    if(weapon.getCellRef().mCharge == -1)
                        weapon.getCellRef().mCharge = weapmaxhealth;
                    damage *= float(weapon.getCellRef().mCharge) / weapmaxhealth;
                }
515 516 517 518
                
                if (!MWBase::Environment::get().getWorld()->getGodModeState())
                    weapon.getCellRef().mCharge -= std::min(std::max(1,
                        (int)(damage * gmst.find("fWeaponDamageMult")->getFloat())), weapon.getCellRef().mCharge);
519 520 521 522 523

                // Weapon broken? unequip it
                if (weapon.getCellRef().mCharge == 0)
                    weapon = *inv.unequipItem(weapon, ptr);

524
            }
525 526 527 528 529 530 531 532 533
            healthdmg = true;
        }
        else
        {
            // Note: MCP contains an option to include Strength in hand-to-hand damage
            // calculations. Some mods recommend using it, so we may want to include am
            // option for it.
            float minstrike = gmst.find("fMinHandToHandMult")->getFloat();
            float maxstrike = gmst.find("fMaxHandToHandMult")->getFloat();
534 535
            damage  = stats.getSkill(weapskill).getModified();
            damage *= minstrike + ((maxstrike-minstrike)*stats.getAttackStrength());
536

scrawl's avatar
scrawl committed
537 538
            healthdmg = (otherstats.getFatigue().getCurrent() < 1.0f)
                    || (otherstats.getMagicEffects().get(ESM::MagicEffect::Paralyze).mMagnitude > 0);
539
            if(stats.isWerewolf())
540 541 542 543 544 545
            {
                healthdmg = true;
                // GLOB instead of GMST because it gets updated during a quest
                const MWWorld::Store<ESM::Global> &glob = world->getStore().get<ESM::Global>();
                damage *= glob.find("WerewolfClawMult")->mValue.getFloat();
            }
546
            if(healthdmg)
547
                damage *= gmst.find("fHandtoHandHealthPer")->getFloat();
548 549 550 551

            MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
            if(stats.isWerewolf())
            {
552 553 554
                const ESM::Sound *sound = world->getStore().get<ESM::Sound>().searchRandom("WolfHit");
                if(sound)
                    sndMgr->playSound3D(victim, sound->mId, 1.0f, 1.0f);
555 556 557
            }
            else
                sndMgr->playSound3D(victim, "Hand To Hand Hit", 1.0f, 1.0f);
Chris Robinson's avatar
Chris Robinson committed
558
        }
559 560
        if(ptr.getRefData().getHandle() == "player")
            skillUsageSucceeded(ptr, weapskill, 0);
561

562 563 564 565 566 567 568 569 570 571
        bool detected = MWBase::Environment::get().getMechanicsManager()->awarenessCheck(ptr, victim);
        if(!detected)
        {
            damage *= gmst.find("fCombatCriticalStrikeMult")->getFloat();
            MWBase::Environment::get().getWindowManager()->messageBox("#{sTargetCriticalStrike}");
            MWBase::Environment::get().getSoundManager()->playSound3D(victim, "critical damage", 1.0f, 1.0f);
        }
        if (othercls.getCreatureStats(victim).getKnockedDown())
            damage *= gmst.find("fCombatKODamageMult")->getFloat();

572
        // Apply "On hit" enchanted weapons
573
        std::string enchantmentName = !weapon.isEmpty() ? weapon.getClass().getEnchantment(weapon) : "";
574 575 576 577 578 579 580 581 582
        if (!enchantmentName.empty())
        {
            const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().find(
                        enchantmentName);
            if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes)
            {
                // Check if we have enough charges
                const float enchantCost = enchantment->mData.mCost;
                int eSkill = stats.getSkill(ESM::Skill::Enchant).getModified();
583
                const int castCost = std::max(1.f, enchantCost - (enchantCost / 100) * (eSkill - 10));
584 585 586 587 588 589 590 591 592 593 594

                if (weapon.getCellRef().mEnchantmentCharge == -1)
                    weapon.getCellRef().mEnchantmentCharge = enchantment->mData.mCharge;
                if (weapon.getCellRef().mEnchantmentCharge < castCost)
                {
                    if (ptr.getRefData().getHandle() == "player")
                        MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientCharge}");
                }
                else
                {
                    weapon.getCellRef().mEnchantmentCharge -= castCost;
595 596 597

                    MWMechanics::CastSpell cast(ptr, victim);
                    cast.cast(weapon);
598 599 600

                    if (ptr.getRefData().getHandle() == "player")
                        skillUsageSucceeded (ptr, ESM::Skill::Enchant, 3);
601 602 603 604
                }
            }
        }

605
        othercls.onHit(victim, damage, healthdmg, weapon, ptr, true);
Chris Robinson's avatar
Chris Robinson committed
606 607
    }

608
    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
609
    {
610 611
        MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();

612 613
        // NOTE: 'object' and/or 'attacker' may be empty.

scrawl's avatar
scrawl committed
614 615 616 617
        // Attacking peaceful NPCs is a crime
        if (!attacker.isEmpty() && ptr.getClass().isNpc() && ptr.getClass().getCreatureStats(ptr).getAiSetting(MWMechanics::CreatureStats::AI_Fight).getModified() <= 30)
            MWBase::Environment::get().getMechanicsManager()->commitCrime(attacker, ptr, MWBase::MechanicsManager::OT_Assault);

618 619 620 621 622
        if(!successful)
        {
            // TODO: Handle HitAttemptOnMe script function

            // Missed
623
            sndMgr->playSound3D(ptr, "miss", 1.0f, 1.0f);
624 625
            return;
        }
Chris Robinson's avatar
Chris Robinson committed
626

627
        if(!object.isEmpty())
628
            getCreatureStats(ptr).setLastHitObject(get(object).getId(object));
629 630 631

        if(!attacker.isEmpty() && attacker.getRefData().getHandle() == "player")
        {
scrawl's avatar
scrawl committed
632
            const std::string &script = ptr.getClass().getScript(ptr);
633 634 635 636
            /* Set the OnPCHitMe script variable. The script is responsible for clearing it. */
            if(!script.empty())
                ptr.getRefData().getLocals().setVarByInt(script, "onpchitme", 1);
        }
637

638 639 640
        if (!attacker.isEmpty())
            MWMechanics::diseaseContact(ptr, attacker);

641
        if(damage > 0.0f)
Chris Robinson's avatar
Chris Robinson committed
642 643 644
        {
            // 'ptr' is losing health. Play a 'hit' voiced dialog entry if not already saying
            // something, alert the character controller, scripts, etc.
645

scrawl's avatar
scrawl committed
646 647 648 649 650 651 652
            const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
            int chance = store.get<ESM::GameSetting>().find("iVoiceHitOdds")->getInt();
            int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
            if (roll < chance)
            {
                MWBase::Environment::get().getDialogueManager()->say(ptr, "hit");
            }
653 654 655 656 657 658 659 660 661 662 663 664 665 666
            getCreatureStats(ptr).setAttacked(true);

            // Check for knockdown
            float agilityTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() * fKnockDownMult->getFloat();
            float knockdownTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified()
                    * iKnockDownOddsMult->getInt() * 0.01 + iKnockDownOddsBase->getInt();
            roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
            if (ishealth && agilityTerm <= damage && knockdownTerm <= roll)
            {
                getCreatureStats(ptr).setKnockedDown(true);

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

668
            if(object.isEmpty())
669 670
            {
                if(ishealth)
671
                    damage /= std::min(1.0f + getArmorRating(ptr)/std::max(1.0f, damage), 4.0f);
672 673
            }
            else if(ishealth)
674
            {
675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692
                // 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
                };
                int hitslot = hitslots[(int)(::rand()/(RAND_MAX+1.0)*20.0)];

693 694 695 696
                float damagediff = damage;
                damage /= std::min(1.0f + getArmorRating(ptr)/std::max(1.0f, damage), 4.0f);
                damagediff -= damage;

697 698 699 700
                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())
701
                {
702 703 704 705 706
                    ESM::CellRef &armorref = armor.getCellRef();
                    if(armorref.mCharge == -1)
                        armorref.mCharge = armor.get<ESM::Armor>()->mBase->mData.mHealth;
                    armorref.mCharge -= std::min(std::max(1, (int)damagediff),
                                                 armorref.mCharge);
707 708 709 710 711

                    // Armor broken? unequip it
                    if (armorref.mCharge == 0)
                        inv.unequipItem(armor, ptr);

712 713 714 715 716 717 718 719 720 721 722 723
                    switch(get(armor).getEquipmentSkill(armor))
                    {
                        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;
                    }
724 725
                }
            }
Chris Robinson's avatar
Chris Robinson committed
726 727
        }

728 729 730 731 732 733 734 735 736 737
        if(ishealth)
        {
            if(damage > 0.0f)
                sndMgr->playSound3D(ptr, "Health Damage", 1.0f, 1.0f);
            float health = getCreatureStats(ptr).getHealth().getCurrent() - damage;
            setActorHealth(ptr, health, attacker);
        }
        else
        {
            MWMechanics::DynamicStat<float> fatigue(getCreatureStats(ptr).getFatigue());
738
            fatigue.setCurrent(fatigue.getCurrent() - damage, true);
739 740
            getCreatureStats(ptr).setFatigue(fatigue);
        }
741 742 743 744 745
    }

    void Npc::setActorHealth(const MWWorld::Ptr& ptr, float health, const MWWorld::Ptr& attacker) const
    {
        MWMechanics::CreatureStats &crstats = getCreatureStats(ptr);
Chris Robinson's avatar
Chris Robinson committed
746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762
        bool wasDead = crstats.isDead();

        MWMechanics::DynamicStat<float> stat(crstats.getHealth());
        stat.setCurrent(health);
        crstats.setHealth(stat);

        if(!wasDead && crstats.isDead())
        {
            // actor was just killed
        }
        else if(wasDead && !crstats.isDead())
        {
            // actor was just resurrected
        }
    }


763
    boost::shared_ptr<MWWorld::Action> Npc::activate (const MWWorld::Ptr& ptr,
764
        const MWWorld::Ptr& actor) const
765
    {
766 767
        if(get(actor).isNpc() && get(actor).getNpcStats(actor).isWerewolf())
        {
768 769 770
            const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
            const ESM::Sound *sound = store.get<ESM::Sound>().searchRandom("WolfNPC");

771
            boost::shared_ptr<MWWorld::Action> action(new MWWorld::FailedAction("#{sWerewolfRefusal}"));
772 773
            if(sound) action->setSound(sound->mId);

774 775 776 777
            return action;
        }
        if(getCreatureStats(ptr).isDead())
            return boost::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr, true));
778 779
        if(get(ptr).getCreatureStats(ptr).isHostile())
            return boost::shared_ptr<MWWorld::Action>(new MWWorld::FailedAction("#{sActorInCombat}"));
scrawl's avatar
scrawl committed
780 781
        if(getCreatureStats(actor).getStance(MWMechanics::CreatureStats::Stance_Sneak))
            return boost::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr)); // stealing
782
        return boost::shared_ptr<MWWorld::Action>(new MWWorld::ActionTalk(ptr));
783
    }
Marc Zinnschlag's avatar
Marc Zinnschlag committed
784

785
    MWWorld::ContainerStore& Npc::getContainerStore (const MWWorld::Ptr& ptr)
786
        const
Marc Zinnschlag's avatar
Marc Zinnschlag committed
787
    {
788
        ensureCustomData (ptr);
Marc Zinnschlag's avatar
Marc Zinnschlag committed
789

790 791 792 793 794 795 796 797 798
        return dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData()).mInventoryStore;
    }

    MWWorld::InventoryStore& Npc::getInventoryStore (const MWWorld::Ptr& ptr)
        const
    {
        ensureCustomData (ptr);

        return dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData()).mInventoryStore;
Marc Zinnschlag's avatar
Marc Zinnschlag committed
799 800
    }

801 802
    std::string Npc::getScript (const MWWorld::Ptr& ptr) const
    {
803
        MWWorld::LiveCellRef<ESM::NPC> *ref =
804 805
            ptr.get<ESM::NPC>();

greye's avatar
greye committed
806
        return ref->mBase->mScript;
807 808
    }

809
    float Npc::getSpeed(const MWWorld::Ptr& ptr) const
Marc Zinnschlag's avatar
Marc Zinnschlag committed
810
    {
811 812
        const MWBase::World *world = MWBase::Environment::get().getWorld();
        const CustomData *npcdata = static_cast<const CustomData*>(ptr.getRefData().getCustomData());
813
        const MWMechanics::MagicEffects &mageffects = npcdata->mNpcStats.getMagicEffects();
814

815 816
        const float normalizedEncumbrance = Npc::getEncumbrance(ptr) / Npc::getCapacity(ptr);

817 818 819
        bool sneaking = ptr.getClass().getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Sneak);
        bool running = ptr.getClass().getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Run);

820
        float walkSpeed = fMinWalkSpeed->getFloat() + 0.01f*npcdata->mNpcStats.getAttribute(ESM::Attribute::Speed).getModified()*
821 822 823
                                                      (fMaxWalkSpeed->getFloat() - fMinWalkSpeed->getFloat());
        walkSpeed *= 1.0f - fEncumberedMoveEffect->getFloat()*normalizedEncumbrance;
        walkSpeed = std::max(0.0f, walkSpeed);
824
        if(sneaking)
825
            walkSpeed *= fSneakSpeedMultiplier->getFloat();
826

827 828
        float runSpeed = walkSpeed*(0.01f * npcdata->mNpcStats.getSkill(ESM::Skill::Athletics).getModified() *
                                    fAthleticsRunBonus->getFloat() + fBaseRunMultiplier->getFloat());
829 830
        if(npcdata->mNpcStats.isWerewolf())
            runSpeed *= fWereWolfRunMult->getFloat();
831 832

        float moveSpeed;
833
        if(normalizedEncumbrance >= 1.0f)
834
            moveSpeed = 0.0f;
scrawl's avatar
scrawl committed
835
        else if(mageffects.get(ESM::MagicEffect::Levitate).mMagnitude > 0 &&
836
                world->isLevitationEnabled())
837
        {
838
            float flySpeed = 0.01f*(npcdata->mNpcStats.getAttribute(ESM::Attribute::Speed).getModified() +
scrawl's avatar
scrawl committed
839
                                    mageffects.get(ESM::MagicEffect::Levitate).mMagnitude);
840 841 842 843 844 845 846 847
            flySpeed = fMinFlySpeed->getFloat() + flySpeed*(fMaxFlySpeed->getFloat() - fMinFlySpeed->getFloat());
            flySpeed *= 1.0f - fEncumberedMoveEffect->getFloat() * normalizedEncumbrance;
            flySpeed = std::max(0.0f, flySpeed);
            moveSpeed = flySpeed;
        }
        else if(world->isSwimming(ptr))
        {
            float swimSpeed = walkSpeed;
848
            if(running)
849
                swimSpeed = runSpeed;
scrawl's avatar
scrawl committed
850
            swimSpeed *= 1.0f + 0.01f * mageffects.get(ESM::MagicEffect::SwiftSwim).mMagnitude;
851 852 853 854
            swimSpeed *= fSwimRunBase->getFloat() + 0.01f*npcdata->mNpcStats.getSkill(ESM::Skill::Athletics).getModified()*