npc.cpp 54.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
#include <components/esm/loadnpc.hpp>
10
#include <components/esm/npcstate.hpp>
11

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

Marc Zinnschlag's avatar
Marc Zinnschlag committed
19
#include "../mwmechanics/creaturestats.hpp"
Marc Zinnschlag's avatar
Marc Zinnschlag committed
20
#include "../mwmechanics/npcstats.hpp"
21
#include "../mwmechanics/movement.hpp"
22
#include "../mwmechanics/spellcasting.hpp"
23
#include "../mwmechanics/disease.hpp"
24
#include "../mwmechanics/combat.hpp"
Marc Zinnschlag's avatar
Marc Zinnschlag committed
25

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

35
#include "../mwrender/actors.hpp"
36
#include "../mwrender/renderinginterface.hpp"
37

38
#include "../mwgui/tooltips.hpp"
39

Marc Zinnschlag's avatar
Marc Zinnschlag committed
40
namespace
41
{
42
    struct NpcCustomData : public MWWorld::CustomData
43 44
    {
        MWMechanics::NpcStats mNpcStats;
45
        MWMechanics::Movement mMovement;
46
        MWWorld::InventoryStore mInventoryStore;
47 48 49 50

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

51
    MWWorld::CustomData *NpcCustomData::clone() const
52
    {
53
        return new NpcCustomData (*this);
54
    }
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

    /**
     * @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.
     */
147
    void autoCalculateSkills(const ESM::NPC* npc, MWMechanics::NpcStats& npcStats, const MWWorld::Ptr& ptr)
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
    {
        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 202 203 204 205 206 207
                if (class_->mData.mSkills[k][1] == skillIndex)
                {
                    // Major skill -> add starting spells for this skill if existing
                    const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
                    MWWorld::Store<ESM::Spell>::iterator it = store.get<ESM::Spell>().begin();
                    for (; it != store.get<ESM::Spell>().end(); ++it)
                    {
                        if (it->mData.mFlags & ESM::Spell::F_Autocalc
                                && MWMechanics::spellSchoolToSkill(MWMechanics::getSpellSchool(&*it, ptr)) == skillIndex)
                            npcStats.getSpells().add(it->mId);
                    }
                }
208 209 210 211 212 213
            }

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

            npcStats.getSkill(skillIndex).setBase(
                  std::min(
                    npcStats.getSkill(skillIndex).getBase()
                    + 5
                    + raceBonus
                    + specBonus
224
                    + static_cast<int>((level-1) * (majorMultiplier + specMultiplier)), 100));
225 226
        }
    }
227 228
}

229
namespace MWClass
230
{
231 232
    void Npc::ensureCustomData (const MWWorld::Ptr& ptr) const
    {
233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248
        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");
249 250 251 252 253
            fJumpEncumbranceBase = gmst.find("fJumpEncumbranceBase");
            fJumpEncumbranceMultiplier = gmst.find("fJumpEncumbranceMultiplier");
            fJumpAcrobaticsBase = gmst.find("fJumpAcrobaticsBase");
            fJumpAcroMultiplier = gmst.find("fJumpAcroMultiplier");
            fJumpRunMultiplier = gmst.find("fJumpRunMultiplier");
254
            fWereWolfRunMult = gmst.find("fWereWolfRunMult");
255 256 257
            fKnockDownMult = gmst.find("fKnockDownMult");
            iKnockDownOddsMult = gmst.find("iKnockDownOddsMult");
            iKnockDownOddsBase = gmst.find("iKnockDownOddsBase");
258 259
            fDamageStrengthBase = gmst.find("fDamageStrengthBase");
            fDamageStrengthMult = gmst.find("fDamageStrengthMult");
260 261 262

            inited = true;
        }
263 264
        if (!ptr.getRefData().getCustomData())
        {
265
            std::auto_ptr<NpcCustomData> data(new NpcCustomData);
266

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

269
            // NPC stats
greye's avatar
greye committed
270
            if (!ref->mBase->mFaction.empty())
271
            {
greye's avatar
greye committed
272
                std::string faction = ref->mBase->mFaction;
eduard's avatar
eduard committed
273
                Misc::StringUtils::toLower(faction);
274
                if(ref->mBase->mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS)
275
                {
greye's avatar
greye committed
276
                    data->mNpcStats.getFactionRanks()[faction] = (int)ref->mBase->mNpdt52.mRank;
277 278 279
                }
                else
                {
greye's avatar
greye committed
280
                    data->mNpcStats.getFactionRanks()[faction] = (int)ref->mBase->mNpdt12.mRank;
281
                }
282 283
            }

284
            // creature stats
285
            int gold=0;
286
            if(ref->mBase->mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS)
287
            {
288 289
                gold = ref->mBase->mNpdt52.mGold;

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

293 294 295 296 297 298 299 300 301
                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);

302 303 304 305 306
                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
307
                data->mNpcStats.setBaseDisposition(ref->mBase->mNpdt52.mDisposition);
308
                data->mNpcStats.setReputation(ref->mBase->mNpdt52.mReputation);
309 310 311
            }
            else
            {
312 313
                gold = ref->mBase->mNpdt12.mGold;

314
                for (int i=0; i<3; ++i)
315
                    data->mNpcStats.setDynamic (i, 10);
316

317
                data->mNpcStats.setLevel(ref->mBase->mNpdt12.mLevel);
318 319
                data->mNpcStats.setBaseDisposition(ref->mBase->mNpdt12.mDisposition);
                data->mNpcStats.setReputation(ref->mBase->mNpdt12.mReputation);
320

321
                autoCalculateAttributes(ref->mBase, data->mNpcStats);
322
                autoCalculateSkills(ref->mBase, data->mNpcStats, ptr);
323
            }
324

325 326 327 328 329 330 331 332
            // 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)
            {
                data->mNpcStats.getSpells().add (*iter);
            }

333 334 335 336 337 338 339 340 341 342 343
            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));
            }

344
            data->mNpcStats.getAiSequence().fill(ref->mBase->mAiPackage);
345

346 347 348 349
            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);
350

351
            // spells
greye's avatar
greye committed
352 353
            for (std::vector<std::string>::const_iterator iter (ref->mBase->mSpells.mList.begin());
                iter!=ref->mBase->mSpells.mList.end(); ++iter)
354
                data->mNpcStats.getSpells().add (*iter);
355

356
            // inventory
scrawl's avatar
scrawl committed
357
            data->mInventoryStore.fill(ref->mBase->mInventory, getId(ptr), "",
358 359
                                       MWBase::Environment::get().getWorld()->getStore());

jefhai's avatar
jefhai committed
360
            // Relates to NPC gold reset delay
jefhai's avatar
jefhai committed
361
            data->mNpcStats.setTradeTime(MWWorld::TimeStamp(0.0, 0));
jefhai's avatar
jefhai committed
362 363 364

            data->mNpcStats.setGoldPool(gold);

365
            // store
366
            ptr.getRefData().setCustomData (data.release());
367

368
            getInventoryStore(ptr).autoEquip(ptr); 
369 370 371
        }
    }

Marc Zinnschlag's avatar
Marc Zinnschlag committed
372 373
    std::string Npc::getId (const MWWorld::Ptr& ptr) const
    {
374
        MWWorld::LiveCellRef<ESM::NPC> *ref =
Marc Zinnschlag's avatar
Marc Zinnschlag committed
375 376
            ptr.get<ESM::NPC>();

greye's avatar
greye committed
377
        return ref->mBase->mId;
Marc Zinnschlag's avatar
Marc Zinnschlag committed
378 379
    }

380 381 382 383 384
    void Npc::adjustPosition(const MWWorld::Ptr& ptr) const
    {
        MWBase::Environment::get().getWorld()->adjustPosition(ptr);
    }

Jason Hooks's avatar
Jason Hooks committed
385
    void Npc::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const
386
    {
387
        renderingInterface.getActors().insertNPC(ptr);
Jason Hooks's avatar
Jason Hooks committed
388
    }
Jason Hooks's avatar
Jason Hooks committed
389

390
    void Npc::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const
Jason Hooks's avatar
Jason Hooks committed
391
    {
392
        physics.addActor(ptr);
393
        MWBase::Environment::get().getMechanicsManager()->add(ptr);
greye's avatar
greye committed
394
    }
Jason Hooks's avatar
Jason Hooks committed
395

scrawl's avatar
scrawl committed
396 397 398 399 400 401
    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
402 403
    std::string Npc::getModel(const MWWorld::Ptr &ptr) const
    {
404
        MWWorld::LiveCellRef<ESM::NPC> *ref =
Jason Hooks's avatar
Jason Hooks committed
405
            ptr.get<ESM::NPC>();
greye's avatar
greye committed
406
        assert(ref->mBase != NULL);
407

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

scrawl's avatar
scrawl committed
410 411
        //int end = headID.find_last_of("head_") - 4;
        //std::string bodyRaceID = headID.substr(0, end);
greye's avatar
greye committed
412 413

        std::string model = "meshes\\base_anim.nif";
414 415
        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
416
            model = "meshes\\base_animkna.nif";
417

greye's avatar
greye committed
418
        return model;
419 420 421

    }

Marc Zinnschlag's avatar
Marc Zinnschlag committed
422 423
    std::string Npc::getName (const MWWorld::Ptr& ptr) const
    {
424 425 426 427 428 429 430
        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
431

432
        MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>();
greye's avatar
greye committed
433
        return ref->mBase->mName;
Marc Zinnschlag's avatar
Marc Zinnschlag committed
434 435
    }

436
    MWMechanics::CreatureStats& Npc::getCreatureStats (const MWWorld::Ptr& ptr) const
437
    {
438
        ensureCustomData (ptr);
439

440
        return dynamic_cast<NpcCustomData&> (*ptr.getRefData().getCustomData()).mNpcStats;
441 442
    }

Marc Zinnschlag's avatar
Marc Zinnschlag committed
443 444
    MWMechanics::NpcStats& Npc::getNpcStats (const MWWorld::Ptr& ptr) const
    {
445
        ensureCustomData (ptr);
Marc Zinnschlag's avatar
Marc Zinnschlag committed
446

447
        return dynamic_cast<NpcCustomData&> (*ptr.getRefData().getCustomData()).mNpcStats;
Marc Zinnschlag's avatar
Marc Zinnschlag committed
448 449
    }

Chris Robinson's avatar
Chris Robinson committed
450

451
    void Npc::hit(const MWWorld::Ptr& ptr, int type) const
Chris Robinson's avatar
Chris Robinson committed
452
    {
453 454 455
        MWBase::World *world = MWBase::Environment::get().getWorld();
        const MWWorld::Store<ESM::GameSetting> &gmst = world->getStore().get<ESM::GameSetting>();

456 457
        // Get the weapon used (if hand-to-hand, weapon = inv.end())
        MWWorld::InventoryStore &inv = getInventoryStore(ptr);
458 459 460 461
        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();
462

463 464 465 466 467 468 469 470 471 472 473 474 475
        // 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);

scrawl's avatar
scrawl committed
476 477
        const float fCombatDistance = gmst.find("fCombatDistance")->getFloat();
        float dist = fCombatDistance * (!weapon.isEmpty() ?
478
                               weapon.get<ESM::Weapon>()->mBase->mData.mReach :
479
                               gmst.find("fHandToHandReach")->getFloat());
scrawl's avatar
scrawl committed
480

scrawl's avatar
scrawl committed
481
        // TODO: Use second to work out the hit angle
scrawl's avatar
scrawl committed
482 483 484
        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
485 486 487 488
        if(victim.isEmpty()) // Didn't hit anything
            return;

        const MWWorld::Class &othercls = MWWorld::Class::get(victim);
489 490
        if(!othercls.isActor()) // Can't hit non-actors
            return;
491
        MWMechanics::CreatureStats &otherstats = victim.getClass().getCreatureStats(victim);
492
        if(otherstats.isDead()) // Can't hit dead actors
Chris Robinson's avatar
Chris Robinson committed
493 494
            return;

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

498
        int weapskill = ESM::Skill::HandToHand;
499
        if(!weapon.isEmpty())
500
            weapskill = get(weapon).getEquipmentSkill(weapon);
501

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

Chris Robinson's avatar
Chris Robinson committed
504
        if((::rand()/(RAND_MAX+1.0)) > hitchance/100.0f)
505
        {
506
            othercls.onHit(victim, 0.0f, false, weapon, ptr, false);
507 508
            return;
        }
Chris Robinson's avatar
Chris Robinson committed
509

510
        bool healthdmg;
511
        float damage = 0.0f;
512
        MWMechanics::NpcStats &stats = getNpcStats(ptr);
513
        if(!weapon.isEmpty())
Chris Robinson's avatar
Chris Robinson committed
514
        {
515
            const bool weaphashealth = get(weapon).hasItemHealth(weapon);
516
            const unsigned char *attack = NULL;
mrcheko's avatar
mrcheko committed
517
            if(type == ESM::Weapon::AT_Chop)
518
                attack = weapon.get<ESM::Weapon>()->mBase->mData.mChop;
mrcheko's avatar
mrcheko committed
519
            else if(type == ESM::Weapon::AT_Slash)
520
                attack = weapon.get<ESM::Weapon>()->mBase->mData.mSlash;
mrcheko's avatar
mrcheko committed
521
            else if(type == ESM::Weapon::AT_Thrust)
522
                attack = weapon.get<ESM::Weapon>()->mBase->mData.mThrust;
523
            if(attack)
524
            {
525
                damage  = attack[0] + ((attack[1]-attack[0])*stats.getAttackStrength());
526 527
                damage *= fDamageStrengthBase->getFloat() +
                        (stats.getAttribute(ESM::Attribute::Strength).getModified() * fDamageStrengthMult->getFloat() * 0.1);
528 529 530 531 532 533 534
                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;
                }
535

536 537 538
                if (!MWBase::Environment::get().getWorld()->getGodModeState())
                    weapon.getCellRef().mCharge -= std::min(std::max(1,
                        (int)(damage * gmst.find("fWeaponDamageMult")->getFloat())), weapon.getCellRef().mCharge);
539 540 541 542 543

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

544
            }
545 546 547 548 549 550 551 552 553
            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();
554 555
            damage  = stats.getSkill(weapskill).getModified();
            damage *= minstrike + ((maxstrike-minstrike)*stats.getAttackStrength());
556

scrawl's avatar
scrawl committed
557 558
            healthdmg = (otherstats.getFatigue().getCurrent() < 1.0f)
                    || (otherstats.getMagicEffects().get(ESM::MagicEffect::Paralyze).mMagnitude > 0);
559
            if(stats.isWerewolf())
560 561 562 563 564 565
            {
                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();
            }
566
            if(healthdmg)
567
                damage *= gmst.find("fHandtoHandHealthPer")->getFloat();
568 569 570 571

            MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
            if(stats.isWerewolf())
            {
572 573 574
                const ESM::Sound *sound = world->getStore().get<ESM::Sound>().searchRandom("WolfHit");
                if(sound)
                    sndMgr->playSound3D(victim, sound->mId, 1.0f, 1.0f);
575 576 577
            }
            else
                sndMgr->playSound3D(victim, "Hand To Hand Hit", 1.0f, 1.0f);
Chris Robinson's avatar
Chris Robinson committed
578
        }
579 580
        if(ptr.getRefData().getHandle() == "player")
            skillUsageSucceeded(ptr, weapskill, 0);
581

582 583 584 585 586 587 588 589 590 591
        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();

592
        // Apply "On hit" enchanted weapons
593
        std::string enchantmentName = !weapon.isEmpty() ? weapon.getClass().getEnchantment(weapon) : "";
594 595 596 597 598 599
        if (!enchantmentName.empty())
        {
            const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().find(
                        enchantmentName);
            if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes)
            {
600 601
                MWMechanics::CastSpell cast(ptr, victim);
                cast.mHitPosition = hitPosition;
602
                cast.cast(weapon);
603 604 605
            }
        }

606 607 608 609
        if (!weapon.isEmpty() && MWMechanics::blockMeleeAttack(ptr, victim, weapon, damage))
            damage = 0;

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

612 613
        MWMechanics::diseaseContact(victim, ptr);

614
        othercls.onHit(victim, damage, healthdmg, weapon, ptr, true);
Chris Robinson's avatar
Chris Robinson committed
615 616
    }

617
    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
618
    {
619 620
        MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();

621 622
        // NOTE: 'object' and/or 'attacker' may be empty.

scrawl's avatar
scrawl committed
623 624 625 626
        // 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);

627 628 629 630 631
        if(!successful)
        {
            // TODO: Handle HitAttemptOnMe script function

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

636
        if(!object.isEmpty())
637
            getCreatureStats(ptr).setLastHitObject(get(object).getId(object));
638 639 640

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

647 648 649
        if (damage > 0.0f && !object.isEmpty())
            MWMechanics::resistNormalWeapon(ptr, attacker, object, damage);

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

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

677
            if(ishealth)
678
            {
679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696
                // 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)];

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

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

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

716 717 718
                    if (ptr.getRefData().getHandle() == "player")
                        skillUsageSucceeded(ptr, get(armor).getEquipmentSkill(armor), 0);

719 720 721 722 723 724 725 726 727 728 729 730
                    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;
                    }
731
                }
732 733
                else if(ptr.getRefData().getHandle() == "player")
                    skillUsageSucceeded(ptr, ESM::Skill::Unarmored, 0);
734
            }
Chris Robinson's avatar
Chris Robinson committed
735 736
        }

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

752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775
    void Npc::block(const MWWorld::Ptr &ptr) const
    {
        MWWorld::InventoryStore& inv = getInventoryStore(ptr);
        MWWorld::ContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
        if (shield == inv.end())
            return;

        MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
        switch(shield->getClass().getEquipmentSkill(*shield))
        {
            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;
            default:
                return;
        }
    }

776 777 778
    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
779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795
        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
        }
    }


796
    boost::shared_ptr<MWWorld::Action> Npc::activate (const MWWorld::Ptr& ptr,
797
        const MWWorld::Ptr& actor) const
798
    {
799 800
        if(get(actor).isNpc() && get(actor).getNpcStats(actor).isWerewolf())
        {
801 802 803
            const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
            const ESM::Sound *sound = store.get<ESM::Sound>().searchRandom("WolfNPC");

804
            boost::shared_ptr<MWWorld::Action> action(new MWWorld::FailedAction("#{sWerewolfRefusal}"));
805 806
            if(sound) action->setSound(sound->mId);

807 808 809 810
            return action;
        }
        if(getCreatureStats(ptr).isDead())
            return boost::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr, true));
811 812
        if(get(ptr).getCreatureStats(ptr).isHostile())
            return boost::shared_ptr<MWWorld::Action>(new MWWorld::FailedAction("#{sActorInCombat}"));
scrawl's avatar
scrawl committed
813 814
        if(getCreatureStats(actor).getStance(MWMechanics::CreatureStats::Stance_Sneak))
            return boost::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr)); // stealing
815 816 817 818 819
        
        // player got activated by another NPC
        if(ptr.getRefData().getHandle() == "player")
            return boost::shared_ptr<MWWorld::Action>(new MWWorld::ActionTalk(actor));

820
        return boost::shared_ptr<MWWorld::Action>(new MWWorld::ActionTalk(ptr));
821

822
    }
Marc Zinnschlag's avatar
Marc Zinnschlag committed
823

824
    MWWorld::ContainerStore& Npc::getContainerStore (const MWWorld::Ptr& ptr)
825
        const
Marc Zinnschlag's avatar
Marc Zinnschlag committed
826
    {
827
        ensureCustomData (ptr);
Marc Zinnschlag's avatar
Marc Zinnschlag committed
828

829
        return dynamic_cast<NpcCustomData&> (*ptr.getRefData().getCustomData()).mInventoryStore;
830 831 832 833 834 835 836
    }

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

837
        return dynamic_cast<NpcCustomData&> (*ptr.getRefData().getCustomData()).mInventoryStore;
Marc Zinnschlag's avatar
Marc Zinnschlag committed
838 839
    }

840 841
    std::string Npc::getScript (const MWWorld::Ptr& ptr) const
    {
842
        MWWorld::LiveCellRef<ESM::NPC> *ref =
843 844
            ptr.get<ESM::NPC>();

greye's avatar
greye committed
845
        return ref->mBase->mScript;
846 847
    }

848
    float Npc::getSpeed(const MWWorld::Ptr& ptr) const
Marc Zinnschlag's avatar
Marc Zinnschlag committed