npc.cpp 56.9 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"
25
#include "../mwmechanics/autocalcspell.hpp"
26
#include "../mwmechanics/difficultyscaling.hpp"
27
#include "../mwmechanics/character.hpp"
Marc Zinnschlag's avatar
Marc Zinnschlag committed
28

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

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

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

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

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

54
    MWWorld::CustomData *NpcCustomData::clone() const
55
    {
56
        return new NpcCustomData (*this);
57
    }
58

59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
    int is_even(double d) {
        double int_part;
        modf(d / 2.0, &int_part);
        return 2.0 * int_part == d;
    }

    int round_ieee_754(double d) {
        double i = floor(d);
        d -= i;
        if(d < 0.5)
            return i;
        if(d > 0.5)
            return i + 1.0;
        if(is_even(i))
            return i;
        return i + 1.0;
    }

77 78 79 80 81 82 83 84 85 86 87 88
    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];
89
            creatureStats.setAttribute(i, male ? attribute.mMale : attribute.mFemale);
90 91 92 93 94 95 96 97 98 99 100
        }

        // 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)
            {
101
                creatureStats.setAttribute(attribute,
102 103 104 105 106
                    creatureStats.getAttribute(attribute).getBase() + 10);
            }
        }

        // skill bonus
107
        for (int attribute=0; attribute < ESM::Attribute::Length; ++attribute)
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
        {
            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;
            }
132 133 134
            creatureStats.setAttribute(attribute, std::min(
                                           round_ieee_754(creatureStats.getAttribute(attribute).getBase()
                + (level-1) * modifierSum), 100) );
135
        }
136

137
        // initial health
138 139
        int strength = creatureStats.getAttribute(ESM::Attribute::Strength).getBase();
        int endurance = creatureStats.getAttribute(ESM::Attribute::Endurance).getBase();
140 141 142 143 144 145 146 147 148 149 150 151 152

        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));
153
    }
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168

    /**
     * @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.
     */
169
    void autoCalculateSkills(const ESM::NPC* npc, MWMechanics::NpcStats& npcStats, const MWWorld::Ptr& ptr)
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202
    {
        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)
            {
203 204 205 206 207
                if (race->mData.mBonus[raceSkillIndex].mSkill == skillIndex)
                {
                    raceBonus = race->mData.mBonus[raceSkillIndex].mBonus;
                    break;
                }
208 209 210 211
            }

            for (int k = 0; k < 5; ++k)
            {
212 213 214 215 216 217
                // is this a minor or major skill?
                if ((class_->mData.mSkills[k][0] == skillIndex) || (class_->mData.mSkills[k][1] == skillIndex))
                {
                    majorMultiplier = 1.0f;
                    break;
                }
218 219 220 221 222 223
            }

            // 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)
            {
224 225
                specMultiplier = 0.5f;
                specBonus = 5;
226 227 228 229
            }

            npcStats.getSkill(skillIndex).setBase(
                  std::min(
230 231
                    round_ieee_754(
                            npcStats.getSkill(skillIndex).getBase()
232 233 234
                    + 5
                    + raceBonus
                    + specBonus
scrawl's avatar
scrawl committed
235
                    +(int(level)-1) * (majorMultiplier + specMultiplier)), 100)); // Must gracefully handle level 0
236
        }
237 238 239 240 241 242 243 244 245

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

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

246 247
        std::vector<std::string> spells = MWMechanics::autoCalcNpcSpells(skills, attributes, race);
        for (std::vector<std::string>::iterator it = spells.begin(); it != spells.end(); ++it)
248
            npcStats.getSpells().add(*it);
249
    }
250 251
}

252
namespace MWClass
253
{
254
    const Npc::GMST& Npc::getGmst()
255
    {
256
        static GMST gmst;
257 258 259 260
        static bool inited = false;
        if(!inited)
        {
            const MWBase::World *world = MWBase::Environment::get().getWorld();
261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283
            const MWWorld::Store<ESM::GameSetting> &store = world->getStore().get<ESM::GameSetting>();

            gmst.fMinWalkSpeed = store.find("fMinWalkSpeed");
            gmst.fMaxWalkSpeed = store.find("fMaxWalkSpeed");
            gmst.fEncumberedMoveEffect = store.find("fEncumberedMoveEffect");
            gmst.fSneakSpeedMultiplier = store.find("fSneakSpeedMultiplier");
            gmst.fAthleticsRunBonus = store.find("fAthleticsRunBonus");
            gmst.fBaseRunMultiplier = store.find("fBaseRunMultiplier");
            gmst.fMinFlySpeed = store.find("fMinFlySpeed");
            gmst.fMaxFlySpeed = store.find("fMaxFlySpeed");
            gmst.fSwimRunBase = store.find("fSwimRunBase");
            gmst.fSwimRunAthleticsMult = store.find("fSwimRunAthleticsMult");
            gmst.fJumpEncumbranceBase = store.find("fJumpEncumbranceBase");
            gmst.fJumpEncumbranceMultiplier = store.find("fJumpEncumbranceMultiplier");
            gmst.fJumpAcrobaticsBase = store.find("fJumpAcrobaticsBase");
            gmst.fJumpAcroMultiplier = store.find("fJumpAcroMultiplier");
            gmst.fJumpRunMultiplier = store.find("fJumpRunMultiplier");
            gmst.fWereWolfRunMult = store.find("fWereWolfRunMult");
            gmst.fKnockDownMult = store.find("fKnockDownMult");
            gmst.iKnockDownOddsMult = store.find("iKnockDownOddsMult");
            gmst.iKnockDownOddsBase = store.find("iKnockDownOddsBase");
            gmst.fDamageStrengthBase = store.find("fDamageStrengthBase");
            gmst.fDamageStrengthMult = store.find("fDamageStrengthMult");
284
            gmst.fCombatArmorMinMult = store.find("fCombatArmorMinMult");
285 286 287

            inited = true;
        }
288 289 290 291 292
        return gmst;
    }

    void Npc::ensureCustomData (const MWWorld::Ptr& ptr) const
    {
293 294
        if (!ptr.getRefData().getCustomData())
        {
295
            std::auto_ptr<NpcCustomData> data(new NpcCustomData);
296

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

299
            // NPC stats
greye's avatar
greye committed
300
            if (!ref->mBase->mFaction.empty())
301
            {
greye's avatar
greye committed
302
                std::string faction = ref->mBase->mFaction;
303 304
                const ESM::Faction* fact = MWBase::Environment::get().getWorld()->getStore().get<ESM::Faction>().search(faction);
                if (fact)
305
                {
306 307 308 309 310 311 312 313
                    if(ref->mBase->mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS)
                    {
                        data->mNpcStats.setFactionRank(fact->mId, (int)ref->mBase->mNpdt52.mRank);
                    }
                    else
                    {
                        data->mNpcStats.setFactionRank(fact->mId, (int)ref->mBase->mNpdt12.mRank);
                    }
314 315
                }
                else
316
                    std::cerr << "Warning: ignoring nonexistent faction '" << fact->mId << "' on NPC '" << ref->mBase->mId << "'" << std::endl;
317 318
            }

319
            // creature stats
320
            int gold=0;
321
            if(ref->mBase->mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS)
322
            {
323 324
                gold = ref->mBase->mNpdt52.mGold;

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

328 329 330 331 332 333 334 335 336
                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);

337 338 339 340 341
                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
342
                data->mNpcStats.setBaseDisposition(ref->mBase->mNpdt52.mDisposition);
343
                data->mNpcStats.setReputation(ref->mBase->mNpdt52.mReputation);
344 345

                data->mNpcStats.setNeedRecalcDynamicStats(false);
346 347 348
            }
            else
            {
349 350
                gold = ref->mBase->mNpdt12.mGold;

351
                for (int i=0; i<3; ++i)
352
                    data->mNpcStats.setDynamic (i, 10);
353

354
                data->mNpcStats.setLevel(ref->mBase->mNpdt12.mLevel);
355 356
                data->mNpcStats.setBaseDisposition(ref->mBase->mNpdt12.mDisposition);
                data->mNpcStats.setReputation(ref->mBase->mNpdt12.mReputation);
357

358
                autoCalculateAttributes(ref->mBase, data->mNpcStats);
359
                autoCalculateSkills(ref->mBase, data->mNpcStats, ptr);
360 361

                data->mNpcStats.setNeedRecalcDynamicStats(true);
362
            }
363

364 365 366 367 368
            // 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)
            {
369 370 371 372 373
                const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search(*iter);
                if (spell)
                    data->mNpcStats.getSpells().add (spell);
                else
                    std::cerr << "Warning: ignoring nonexistent race power '" << *iter << "' on NPC '" << ref->mBase->mId << "'" << std::endl;
374 375
            }

376 377 378 379 380 381 382 383 384 385 386
            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));
            }

387
            data->mNpcStats.getAiSequence().fill(ref->mBase->mAiPackage);
388

389 390 391 392
            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);
393

394
            // spells
greye's avatar
greye committed
395 396
            for (std::vector<std::string>::const_iterator iter (ref->mBase->mSpells.mList.begin());
                iter!=ref->mBase->mSpells.mList.end(); ++iter)
397 398 399 400 401 402 403 404 405 406
            {
                const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search(*iter);
                if (spell)
                    data->mNpcStats.getSpells().add (spell);
                else
                {
                    /// \todo add option to make this a fatal error message pop-up, but default to warning for vanilla compatibility
                    std::cerr << "Warning: ignoring nonexistent spell '" << *iter << "' on NPC '" << ref->mBase->mId << "'" << std::endl;
                }
            }
407

408
            // inventory
scrawl's avatar
scrawl committed
409
            data->mInventoryStore.fill(ref->mBase->mInventory, getId(ptr), "",
410 411
                                       MWBase::Environment::get().getWorld()->getStore());

jefhai's avatar
jefhai committed
412 413
            data->mNpcStats.setGoldPool(gold);

414
            // store
415
            ptr.getRefData().setCustomData (data.release());
416

417
            getInventoryStore(ptr).autoEquip(ptr); 
418 419 420
        }
    }

Marc Zinnschlag's avatar
Marc Zinnschlag committed
421 422
    std::string Npc::getId (const MWWorld::Ptr& ptr) const
    {
423
        MWWorld::LiveCellRef<ESM::NPC> *ref =
Marc Zinnschlag's avatar
Marc Zinnschlag committed
424 425
            ptr.get<ESM::NPC>();

greye's avatar
greye committed
426
        return ref->mBase->mId;
Marc Zinnschlag's avatar
Marc Zinnschlag committed
427 428
    }

429
    void Npc::adjustPosition(const MWWorld::Ptr& ptr, bool force) const
430
    {
431
        MWBase::Environment::get().getWorld()->adjustPosition(ptr, force);
432 433
    }

Jason Hooks's avatar
Jason Hooks committed
434
    void Npc::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const
435
    {
436
        renderingInterface.getActors().insertNPC(ptr);
Jason Hooks's avatar
Jason Hooks committed
437
    }
Jason Hooks's avatar
Jason Hooks committed
438

439
    void Npc::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const
Jason Hooks's avatar
Jason Hooks committed
440
    {
441
        physics.addActor(ptr);
442
        MWBase::Environment::get().getMechanicsManager()->add(ptr);
443 444
        if (getCreatureStats(ptr).isDead())
            MWBase::Environment::get().getWorld()->enableActorCollision(ptr, false);
greye's avatar
greye committed
445
    }
Jason Hooks's avatar
Jason Hooks committed
446

scrawl's avatar
scrawl committed
447 448 449 450 451 452
    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
453 454
    std::string Npc::getModel(const MWWorld::Ptr &ptr) const
    {
455
        MWWorld::LiveCellRef<ESM::NPC> *ref =
Jason Hooks's avatar
Jason Hooks committed
456
            ptr.get<ESM::NPC>();
greye's avatar
greye committed
457
        assert(ref->mBase != NULL);
458

greye's avatar
greye committed
459
        std::string model = "meshes\\base_anim.nif";
460 461
        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
462
            model = "meshes\\base_animkna.nif";
463

greye's avatar
greye committed
464
        return model;
465 466 467

    }

Marc Zinnschlag's avatar
Marc Zinnschlag committed
468 469
    std::string Npc::getName (const MWWorld::Ptr& ptr) const
    {
470 471 472
        if(getNpcStats(ptr).isWerewolf())
        {
            const MWBase::World *world = MWBase::Environment::get().getWorld();
473
            const MWWorld::Store<ESM::GameSetting> &store = world->getStore().get<ESM::GameSetting>();
474

475
            return store.find("sWerewolfPopup")->getString();
476
        }
Marc Zinnschlag's avatar
Marc Zinnschlag committed
477

478
        MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>();
greye's avatar
greye committed
479
        return ref->mBase->mName;
Marc Zinnschlag's avatar
Marc Zinnschlag committed
480 481
    }

482
    MWMechanics::CreatureStats& Npc::getCreatureStats (const MWWorld::Ptr& ptr) const
483
    {
484
        ensureCustomData (ptr);
485

486
        return dynamic_cast<NpcCustomData&> (*ptr.getRefData().getCustomData()).mNpcStats;
487 488
    }

Marc Zinnschlag's avatar
Marc Zinnschlag committed
489 490
    MWMechanics::NpcStats& Npc::getNpcStats (const MWWorld::Ptr& ptr) const
    {
491
        ensureCustomData (ptr);
Marc Zinnschlag's avatar
Marc Zinnschlag committed
492

493
        return dynamic_cast<NpcCustomData&> (*ptr.getRefData().getCustomData()).mNpcStats;
Marc Zinnschlag's avatar
Marc Zinnschlag committed
494 495
    }

Chris Robinson's avatar
Chris Robinson committed
496

497
    void Npc::hit(const MWWorld::Ptr& ptr, int type) const
Chris Robinson's avatar
Chris Robinson committed
498
    {
499
        MWBase::World *world = MWBase::Environment::get().getWorld();
500 501 502
        const GMST& gmst = getGmst();

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

504 505
        // Get the weapon used (if hand-to-hand, weapon = inv.end())
        MWWorld::InventoryStore &inv = getInventoryStore(ptr);
506 507 508 509
        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();
510

511 512
        // Reduce fatigue
        // somewhat of a guess, but using the weapon weight makes sense
513 514 515
        const float fFatigueAttackBase = store.find("fFatigueAttackBase")->getFloat();
        const float fFatigueAttackMult = store.find("fFatigueAttackMult")->getFloat();
        const float fWeaponFatigueMult = store.find("fWeaponFatigueMult")->getFloat();
516
        MWMechanics::DynamicStat<float> fatigue = getCreatureStats(ptr).getFatigue();
517
        const float normalizedEncumbrance = getNormalizedEncumbrance(ptr);
518 519 520 521 522 523
        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);

524
        const float fCombatDistance = store.find("fCombatDistance")->getFloat();
scrawl's avatar
scrawl committed
525
        float dist = fCombatDistance * (!weapon.isEmpty() ?
526
                               weapon.get<ESM::Weapon>()->mBase->mData.mReach :
527
                               store.find("fHandToHandReach")->getFloat());
scrawl's avatar
scrawl committed
528

scrawl's avatar
scrawl committed
529
        // TODO: Use second to work out the hit angle
scrawl's avatar
scrawl committed
530 531 532
        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
533 534 535
        if(victim.isEmpty()) // Didn't hit anything
            return;

536
        const MWWorld::Class &othercls = victim.getClass();
537 538
        if(!othercls.isActor()) // Can't hit non-actors
            return;
539
        MWMechanics::CreatureStats &otherstats = othercls.getCreatureStats(victim);
540
        if(otherstats.isDead()) // Can't hit dead actors
Chris Robinson's avatar
Chris Robinson committed
541 542
            return;

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

546
        int weapskill = ESM::Skill::HandToHand;
547
        if(!weapon.isEmpty())
548
            weapskill = weapon.getClass().getEquipmentSkill(weapon);
549

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

Chris Robinson's avatar
Chris Robinson committed
552
        if((::rand()/(RAND_MAX+1.0)) > hitchance/100.0f)
553
        {
554
            othercls.onHit(victim, 0.0f, false, weapon, ptr, false);
555
            MWMechanics::reduceWeaponCondition(0.f, false, weapon, ptr);
556 557
            return;
        }
Chris Robinson's avatar
Chris Robinson committed
558

559
        bool healthdmg;
560
        float damage = 0.0f;
561
        MWMechanics::NpcStats &stats = getNpcStats(ptr);
562
        if(!weapon.isEmpty())
Chris Robinson's avatar
Chris Robinson committed
563
        {
564
            const unsigned char *attack = NULL;
mrcheko's avatar
mrcheko committed
565
            if(type == ESM::Weapon::AT_Chop)
566
                attack = weapon.get<ESM::Weapon>()->mBase->mData.mChop;
mrcheko's avatar
mrcheko committed
567
            else if(type == ESM::Weapon::AT_Slash)
568
                attack = weapon.get<ESM::Weapon>()->mBase->mData.mSlash;
mrcheko's avatar
mrcheko committed
569
            else if(type == ESM::Weapon::AT_Thrust)
570
                attack = weapon.get<ESM::Weapon>()->mBase->mData.mThrust;
571
            if(attack)
572
            {
573
                damage  = attack[0] + ((attack[1]-attack[0])*stats.getAttackStrength());
574 575
                damage *= gmst.fDamageStrengthBase->getFloat() +
                        (stats.getAttribute(ESM::Attribute::Strength).getModified() * gmst.fDamageStrengthMult->getFloat() * 0.1);
576
            }
577 578
            MWMechanics::adjustWeaponDamage(damage, weapon);
            MWMechanics::reduceWeaponCondition(damage, true, weapon, ptr);
579 580 581 582 583 584 585
            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.
586 587
            float minstrike = store.find("fMinHandToHandMult")->getFloat();
            float maxstrike = store.find("fMaxHandToHandMult")->getFloat();
588 589
            damage  = stats.getSkill(weapskill).getModified();
            damage *= minstrike + ((maxstrike-minstrike)*stats.getAttackStrength());
590

591
            healthdmg = (otherstats.getMagicEffects().get(ESM::MagicEffect::Paralyze).getMagnitude() > 0)
scrawl's avatar
scrawl committed
592
                    || otherstats.getKnockedDown();
593
            if(stats.isWerewolf())
594 595 596
            {
                healthdmg = true;
                // GLOB instead of GMST because it gets updated during a quest
Miroslav R.'s avatar
Miroslav R. committed
597
                damage *= world->getGlobalFloat("werewolfclawmult");
598
            }
599
            if(healthdmg)
600
                damage *= store.find("fHandtoHandHealthPer")->getFloat();
601 602 603 604

            MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
            if(stats.isWerewolf())
            {
605 606 607
                const ESM::Sound *sound = world->getStore().get<ESM::Sound>().searchRandom("WolfHit");
                if(sound)
                    sndMgr->playSound3D(victim, sound->mId, 1.0f, 1.0f);
608 609 610
            }
            else
                sndMgr->playSound3D(victim, "Hand To Hand Hit", 1.0f, 1.0f);
Chris Robinson's avatar
Chris Robinson committed
611
        }
612
        if(ptr.getRefData().getHandle() == "player")
scrawl's avatar
scrawl committed
613
        {
614
            skillUsageSucceeded(ptr, weapskill, 0);
615

scrawl's avatar
scrawl committed
616 617 618 619 620 621 622 623 624 625
            const MWMechanics::AiSequence& seq = victim.getClass().getCreatureStats(victim).getAiSequence();

            bool unaware = !seq.isInCombat()
                    && !MWBase::Environment::get().getMechanicsManager()->awarenessCheck(ptr, victim);
            if(unaware)
            {
                damage *= store.find("fCombatCriticalStrikeMult")->getFloat();
                MWBase::Environment::get().getWindowManager()->messageBox("#{sTargetCriticalStrike}");
                MWBase::Environment::get().getSoundManager()->playSound3D(victim, "critical damage", 1.0f, 1.0f);
            }
626
        }
scrawl's avatar
scrawl committed
627

628
        if (othercls.getCreatureStats(victim).getKnockedDown())
629
            damage *= store.find("fCombatKODamageMult")->getFloat();
630

631
        // Apply "On hit" enchanted weapons
632
        std::string enchantmentName = !weapon.isEmpty() ? weapon.getClass().getEnchantment(weapon) : "";
633 634 635 636 637 638
        if (!enchantmentName.empty())
        {
            const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().find(
                        enchantmentName);
            if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes)
            {
639 640
                MWMechanics::CastSpell cast(ptr, victim);
                cast.mHitPosition = hitPosition;
641
                cast.cast(weapon);
642 643 644
            }
        }

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

647 648 649 650
        if (!weapon.isEmpty() && MWMechanics::blockMeleeAttack(ptr, victim, weapon, damage))
            damage = 0;

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

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

655
        othercls.onHit(victim, damage, healthdmg, weapon, ptr, true);
Chris Robinson's avatar
Chris Robinson committed
656 657
    }

658
    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
659
    {
660 661
        MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();

662 663
        // NOTE: 'object' and/or 'attacker' may be empty.

664 665
        bool wasDead = getCreatureStats(ptr).isDead();

666 667
        // Note OnPcHitMe is not set for friendly hits.
        bool setOnPcHitMe = true;
668 669 670
        if (!attacker.isEmpty() && !ptr.getClass().getCreatureStats(ptr).getAiSequence().isInCombat(attacker))
        {
            getCreatureStats(ptr).setAttacked(true);
671

672
            setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker);
673
        }
674

675 676 677
        if(!object.isEmpty())
            getCreatureStats(ptr).setLastHitAttemptObject(object.getClass().getId(object));

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

685
        if(!object.isEmpty())
686
            getCreatureStats(ptr).setLastHitObject(object.getClass().getId(object));
687

688
        if(setOnPcHitMe && !attacker.isEmpty() && attacker.getRefData().getHandle() == "player")
689
        {
scrawl's avatar
scrawl committed
690
            const std::string &script = ptr.getClass().getScript(ptr);
691 692 693 694
            /* Set the OnPCHitMe script variable. The script is responsible for clearing it. */
            if(!script.empty())
                ptr.getRefData().getLocals().setVarByInt(script, "onpchitme", 1);
        }
695

696 697 698
        if (damage > 0.0f && !object.isEmpty())
            MWMechanics::resistNormalWeapon(ptr, attacker, object, damage);

699 700 701
        if (damage < 0.001f)
            damage = 0;

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

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

scrawl's avatar
scrawl committed
710 711 712 713 714 715
            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");
            }
716 717

            // Check for knockdown
718
            float agilityTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() * gmst.fKnockDownMult->getFloat();
719
            float knockdownTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified()
720
                    * gmst.iKnockDownOddsMult->getInt() * 0.01 + gmst.iKnockDownOddsBase->getInt();
721 722 723 724 725 726 727 728
            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?
729

730
            if(damage > 0 && ishealth)
731
            {
732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749
                // 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)];

750 751 752 753 754 755
                float unmitigatedDamage = damage;
                float x = damage / (damage + getArmorRating(ptr));
                damage *= std::max(gmst.fCombatArmorMinMult->getFloat(), x);
                int damageDiff = unmitigatedDamage - damage;
                if (damage < 1)
                    damage = 1;
756

757 758 759 760
                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())
761
                {
762
                    int armorhealth = armor.getClass().getItemHealth(armor);
763
                    armorhealth -= std::min(std::max(1, damageDiff),
764 765
                                                 armorhealth);
                    armor.getCellRef().setCharge(armorhealth);
766 767

                    // Armor broken? unequip it
768
                    if (armorhealth == 0)
769
                        armor = *inv.unequipItem(armor, ptr);
770

771
                    if (ptr.getRefData().getHandle() == "player")
772
                        skillUsageSucceeded(ptr, armor.getClass().getEquipmentSkill(armor), 0);
773

774
                    switch(armor.getClass().getEquipmentSkill(armor))
775 776 777 778 779 780 781 782 783 784 785
                    {
                        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;
                    }
786
                }