npc.cpp 51.3 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());
scrawl's avatar
scrawl committed
457
        // TODO: Use second to work out the hit angle
scrawl's avatar
scrawl committed
458 459 460
        std::pair<MWWorld::Ptr, Ogre::Vector3> result = world->getHitContact(ptr, dist);
        MWWorld::Ptr victim = result.first;
        Ogre::Vector3 hitPosition = result.second;
Chris Robinson's avatar
Chris Robinson committed
461 462 463 464
        if(victim.isEmpty()) // Didn't hit anything
            return;

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

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

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

478 479 480 481 482 483
        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
484 485
        hitchance += mageffects.get(ESM::MagicEffect::FortifyAttack).mMagnitude -
                     mageffects.get(ESM::MagicEffect::Blind).mMagnitude;
486
        hitchance -= otherstats.getEvasion();
487

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

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

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

526
            }
527 528 529 530 531 532 533 534 535
            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();
536 537
            damage  = stats.getSkill(weapskill).getModified();
            damage *= minstrike + ((maxstrike-minstrike)*stats.getAttackStrength());
538

scrawl's avatar
scrawl committed
539 540
            healthdmg = (otherstats.getFatigue().getCurrent() < 1.0f)
                    || (otherstats.getMagicEffects().get(ESM::MagicEffect::Paralyze).mMagnitude > 0);
541
            if(stats.isWerewolf())
542 543 544 545 546 547
            {
                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();
            }
548
            if(healthdmg)
549
                damage *= gmst.find("fHandtoHandHealthPer")->getFloat();
550 551 552 553

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

564 565 566 567 568 569 570 571 572 573
        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();

574
        // Apply "On hit" enchanted weapons
575
        std::string enchantmentName = !weapon.isEmpty() ? weapon.getClass().getEnchantment(weapon) : "";
576 577 578 579 580 581 582 583 584
        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();
585
                const int castCost = std::max(1.f, enchantCost - (enchantCost / 100) * (eSkill - 10));
586 587 588 589 590 591 592 593 594 595 596

                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;
597 598 599

                    MWMechanics::CastSpell cast(ptr, victim);
                    cast.cast(weapon);
600 601 602

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

scrawl's avatar
scrawl committed
607
        // TODO: do not do this if the attack is blocked
608 609
        if (healthdmg)
            MWBase::Environment::get().getWorld()->spawnBloodEffect(victim, hitPosition);
scrawl's avatar
scrawl committed
610

611
        othercls.onHit(victim, damage, healthdmg, weapon, ptr, true);
Chris Robinson's avatar
Chris Robinson committed
612 613
    }

614
    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
615
    {
616 617
        MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();

618 619
        // NOTE: 'object' and/or 'attacker' may be empty.

scrawl's avatar
scrawl committed
620 621 622 623
        // 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);

624 625 626 627 628
        if(!successful)
        {
            // TODO: Handle HitAttemptOnMe script function

            // Missed
629
            sndMgr->playSound3D(ptr, "miss", 1.0f, 1.0f);
630 631
            return;
        }
Chris Robinson's avatar
Chris Robinson committed
632

633
        if(!object.isEmpty())
634
            getCreatureStats(ptr).setLastHitObject(get(object).getId(object));
635 636 637

        if(!attacker.isEmpty() && attacker.getRefData().getHandle() == "player")
        {
scrawl's avatar
scrawl committed
638
            const std::string &script = ptr.getClass().getScript(ptr);
639 640 641 642
            /* Set the OnPCHitMe script variable. The script is responsible for clearing it. */
            if(!script.empty())
                ptr.getRefData().getLocals().setVarByInt(script, "onpchitme", 1);
        }
643

644 645 646
        if (!attacker.isEmpty())
            MWMechanics::diseaseContact(ptr, attacker);

647
        if(damage > 0.0f)
Chris Robinson's avatar
Chris Robinson committed
648 649 650
        {
            // 'ptr' is losing health. Play a 'hit' voiced dialog entry if not already saying
            // something, alert the character controller, scripts, etc.
651

scrawl's avatar
scrawl committed
652 653 654 655 656 657 658
            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");
            }
659 660 661 662 663 664 665 666 667 668 669 670 671 672
            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?
673

674
            if(object.isEmpty())
675 676
            {
                if(ishealth)
677
                    damage /= std::min(1.0f + getArmorRating(ptr)/std::max(1.0f, damage), 4.0f);
678 679
            }
            else if(ishealth)
680
            {
681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698
                // 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)];

699 700 701 702
                float damagediff = damage;
                damage /= std::min(1.0f + getArmorRating(ptr)/std::max(1.0f, damage), 4.0f);
                damagediff -= damage;

703 704 705 706
                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())
707
                {
708 709 710 711 712
                    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);
713 714 715 716 717

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

718 719 720 721 722 723 724 725 726 727 728 729
                    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;
                    }
730 731
                }
            }
Chris Robinson's avatar
Chris Robinson committed
732 733
        }

734 735 736 737 738 739 740 741 742 743
        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());
744
            fatigue.setCurrent(fatigue.getCurrent() - damage, true);
745 746
            getCreatureStats(ptr).setFatigue(fatigue);
        }
747 748 749 750 751
    }

    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
752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768
        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
        }
    }


769
    boost::shared_ptr<MWWorld::Action> Npc::activate (const MWWorld::Ptr& ptr,
770
        const MWWorld::Ptr& actor) const
771
    {
772 773
        if(get(actor).isNpc() && get(actor).getNpcStats(actor).isWerewolf())
        {
774 775 776
            const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
            const ESM::Sound *sound = store.get<ESM::Sound>().searchRandom("WolfNPC");

777
            boost::shared_ptr<MWWorld::Action> action(new MWWorld::FailedAction("#{sWerewolfRefusal}"));
778 779
            if(sound) action->setSound(sound->mId);

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

791
    MWWorld::ContainerStore& Npc::getContainerStore (const MWWorld::Ptr& ptr)
792
        const
Marc Zinnschlag's avatar
Marc Zinnschlag committed
793
    {
794
        ensureCustomData (ptr);
Marc Zinnschlag's avatar
Marc Zinnschlag committed
795

796 797 798 799 800 801 802 803 804
        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
805 806
    }

807 808
    std::string Npc::getScript (const MWWorld::Ptr& ptr) const
    {
809
        MWWorld::LiveCellRef<ESM::NPC> *ref =
810 811
            ptr.get<ESM::NPC>();

greye's avatar
greye committed
812
        return ref->mBase->mScript;
813 814
    }

815
    float Npc::getSpeed(const MWWorld::Ptr& ptr) const
Marc Zinnschlag's avatar
Marc Zinnschlag committed
816
    {
817 818
        const MWBase::World *world = MWBase::Environment::get().getWorld();
        const CustomData *npcdata = static_cast<const CustomData*>(ptr.getRefData().getCustomData());
819
        const MWMechanics::MagicEffects &mageffects = npcdata->mNpcStats.getMagicEffects();
820

821 822
        const float normalizedEncumbrance = Npc::getEncumbrance(ptr) / Npc::getCapacity(ptr);

823 824 825
        bool sneaking = ptr.getClass().getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Sneak);
        bool running = ptr.getClass().getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Run);

826
        float walkSpeed = fMinWalkSpeed->getFloat() + 0.01f*npcdata->mNpcStats.getAttribute(ESM::Attribute::Speed).getModified()*
827 828 829
                                                      (fMaxWalkSpeed->getFloat() - fMinWalkSpeed->getFloat());
        walkSpeed *= 1.0f - fEncumberedMoveEffect->getFloat()*normalizedEncumbrance;
        walkSpeed = std::max(0.0f, walkSpeed);
830
        if(sneaking)
831
            walkSpeed *= fSneakSpeedMultiplier->getFloat();
832

833 834
        float runSpeed = walkSpeed*(0.01f * npcdata->mNpcStats.getSkill(ESM::Skill::Athletics).getModified() *
                                    fAthleticsRunBonus->getFloat() + fBaseRunMultiplier->getFloat());
835 836
        if(npcdata->mNpcStats.isWerewolf())
            runSpeed *= fWereWolfRunMult->getFloat();
837 838

        float moveSpeed;
839
        if(normalizedEncumbrance >= 1.0f)
840
            moveSpeed = 0.0f;
scrawl's avatar
scrawl committed
841
        else if(mageffects.get(ESM::MagicEffect::Levitate).mMagnitude > 0 &&
842
                world->isLevitationEnabled())
843
        {
844
            float flySpeed = 0.01f*(npcdata->mNpcStats.getAttribute(ESM::Attribute::Speed).getModified() +
scrawl's avatar
scrawl committed
845
                                    mageffects.get(ESM::MagicEffect::Levitate).mMagnitude);
846 847 848 849 850 851 852 853
            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;