npc.cpp 54.4 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
            // creature stats
300
            int gold=0;
301
            if(ref->mBase->mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS)
302
            {
303 304
                gold = ref->mBase->mNpdt52.mGold;

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

308 309 310 311 312 313 314 315 316
                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);

317 318 319 320 321
                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
322
                data->mNpcStats.setBaseDisposition(ref->mBase->mNpdt52.mDisposition);
323
                data->mNpcStats.setReputation(ref->mBase->mNpdt52.mReputation);
324 325

                data->mNpcStats.setNeedRecalcDynamicStats(false);
326 327 328
            }
            else
            {
329 330
                gold = ref->mBase->mNpdt12.mGold;

331
                for (int i=0; i<3; ++i)
332
                    data->mNpcStats.setDynamic (i, 10);
333

334
                data->mNpcStats.setLevel(ref->mBase->mNpdt12.mLevel);
335 336
                data->mNpcStats.setBaseDisposition(ref->mBase->mNpdt12.mDisposition);
                data->mNpcStats.setReputation(ref->mBase->mNpdt12.mReputation);
337

338
                autoCalculateAttributes(ref->mBase, data->mNpcStats);
339
                autoCalculateSkills(ref->mBase, data->mNpcStats, ptr);
340 341

                data->mNpcStats.setNeedRecalcDynamicStats(true);
342
            }
343

344 345 346 347 348
            // race powers
            const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(ref->mBase->mRace);
            for (std::vector<std::string>::const_iterator iter (race->mPowers.mList.begin());
                iter!=race->mPowers.mList.end(); ++iter)
            {
scrawl's avatar
scrawl committed
349
                if (const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search(*iter))
350 351 352
                    data->mNpcStats.getSpells().add (spell);
                else
                    std::cerr << "Warning: ignoring nonexistent race power '" << *iter << "' on NPC '" << ref->mBase->mId << "'" << std::endl;
353 354
            }

scrawl's avatar
scrawl committed
355
            if (!ref->mBase->mFaction.empty())
356 357 358 359 360
            {
                static const int iAutoRepFacMod = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>()
                        .find("iAutoRepFacMod")->getInt();
                static const int iAutoRepLevMod = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>()
                        .find("iAutoRepLevMod")->getInt();
scrawl's avatar
scrawl committed
361
                int rank = ref->mBase->getFactionRank();
362 363 364 365

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

366
            data->mNpcStats.getAiSequence().fill(ref->mBase->mAiPackage);
367

368 369 370 371
            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);
372

373
            // spells
greye's avatar
greye committed
374 375
            for (std::vector<std::string>::const_iterator iter (ref->mBase->mSpells.mList.begin());
                iter!=ref->mBase->mSpells.mList.end(); ++iter)
376
            {
scrawl's avatar
scrawl committed
377
                if (const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search(*iter))
378 379 380 381 382 383 384
                    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;
                }
            }
385

386
            // inventory
387
            data->mInventoryStore.fill(ref->mBase->mInventory, getId(ptr), "", -1,
388 389
                                       MWBase::Environment::get().getWorld()->getStore());

jefhai's avatar
jefhai committed
390 391
            data->mNpcStats.setGoldPool(gold);

392
            // store
393
            ptr.getRefData().setCustomData (data.release());
394

395
            getInventoryStore(ptr).autoEquip(ptr); 
396 397 398
        }
    }

Marc Zinnschlag's avatar
Marc Zinnschlag committed
399 400
    std::string Npc::getId (const MWWorld::Ptr& ptr) const
    {
401
        MWWorld::LiveCellRef<ESM::NPC> *ref =
Marc Zinnschlag's avatar
Marc Zinnschlag committed
402 403
            ptr.get<ESM::NPC>();

greye's avatar
greye committed
404
        return ref->mBase->mId;
Marc Zinnschlag's avatar
Marc Zinnschlag committed
405 406
    }

407
    void Npc::adjustPosition(const MWWorld::Ptr& ptr, bool force) const
408
    {
409
        MWBase::Environment::get().getWorld()->adjustPosition(ptr, force);
410 411
    }

412
    void Npc::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const
413
    {
414
        renderingInterface.getActors().insertNPC(ptr);
Jason Hooks's avatar
Jason Hooks committed
415
    }
Jason Hooks's avatar
Jason Hooks committed
416

417
    void Npc::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const
Jason Hooks's avatar
Jason Hooks committed
418
    {
419
        physics.addActor(ptr, model);
420
        MWBase::Environment::get().getMechanicsManager()->add(ptr);
421 422
        if (getCreatureStats(ptr).isDead())
            MWBase::Environment::get().getWorld()->enableActorCollision(ptr, false);
greye's avatar
greye committed
423
    }
Jason Hooks's avatar
Jason Hooks committed
424

scrawl's avatar
scrawl committed
425 426 427 428 429 430
    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
431 432
    std::string Npc::getModel(const MWWorld::Ptr &ptr) const
    {
433
        MWWorld::LiveCellRef<ESM::NPC> *ref =
Jason Hooks's avatar
Jason Hooks committed
434
            ptr.get<ESM::NPC>();
greye's avatar
greye committed
435
        assert(ref->mBase != NULL);
436

greye's avatar
greye committed
437
        std::string model = "meshes\\base_anim.nif";
438 439
        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
440
            model = "meshes\\base_animkna.nif";
441

greye's avatar
greye committed
442
        return model;
443 444 445

    }

Marc Zinnschlag's avatar
Marc Zinnschlag committed
446 447
    std::string Npc::getName (const MWWorld::Ptr& ptr) const
    {
448 449 450
        if(getNpcStats(ptr).isWerewolf())
        {
            const MWBase::World *world = MWBase::Environment::get().getWorld();
451
            const MWWorld::Store<ESM::GameSetting> &store = world->getStore().get<ESM::GameSetting>();
452

453
            return store.find("sWerewolfPopup")->getString();
454
        }
Marc Zinnschlag's avatar
Marc Zinnschlag committed
455

456
        MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>();
greye's avatar
greye committed
457
        return ref->mBase->mName;
Marc Zinnschlag's avatar
Marc Zinnschlag committed
458 459
    }

460
    MWMechanics::CreatureStats& Npc::getCreatureStats (const MWWorld::Ptr& ptr) const
461
    {
462
        ensureCustomData (ptr);
463

464
        return dynamic_cast<NpcCustomData&> (*ptr.getRefData().getCustomData()).mNpcStats;
465 466
    }

Marc Zinnschlag's avatar
Marc Zinnschlag committed
467 468
    MWMechanics::NpcStats& Npc::getNpcStats (const MWWorld::Ptr& ptr) const
    {
469
        ensureCustomData (ptr);
Marc Zinnschlag's avatar
Marc Zinnschlag committed
470

471
        return dynamic_cast<NpcCustomData&> (*ptr.getRefData().getCustomData()).mNpcStats;
Marc Zinnschlag's avatar
Marc Zinnschlag committed
472 473
    }

Chris Robinson's avatar
Chris Robinson committed
474

475
    void Npc::hit(const MWWorld::Ptr& ptr, int type) const
Chris Robinson's avatar
Chris Robinson committed
476
    {
477
        MWBase::World *world = MWBase::Environment::get().getWorld();
478 479 480
        const GMST& gmst = getGmst();

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

482 483
        // Get the weapon used (if hand-to-hand, weapon = inv.end())
        MWWorld::InventoryStore &inv = getInventoryStore(ptr);
484 485 486 487
        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();
488

scrawl's avatar
scrawl committed
489
        MWMechanics::applyFatigueLoss(ptr, weapon);
490

491
        const float fCombatDistance = store.find("fCombatDistance")->getFloat();
scrawl's avatar
scrawl committed
492
        float dist = fCombatDistance * (!weapon.isEmpty() ?
493
                               weapon.get<ESM::Weapon>()->mBase->mData.mReach :
494
                               store.find("fHandToHandReach")->getFloat());
scrawl's avatar
scrawl committed
495

scrawl's avatar
scrawl committed
496
        // TODO: Use second to work out the hit angle
scrawl's avatar
scrawl committed
497 498 499
        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
500 501 502
        if(victim.isEmpty()) // Didn't hit anything
            return;

503
        const MWWorld::Class &othercls = victim.getClass();
504 505
        if(!othercls.isActor()) // Can't hit non-actors
            return;
506
        MWMechanics::CreatureStats &otherstats = othercls.getCreatureStats(victim);
507
        if(otherstats.isDead()) // Can't hit dead actors
Chris Robinson's avatar
Chris Robinson committed
508 509
            return;

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

513
        int weapskill = ESM::Skill::HandToHand;
514
        if(!weapon.isEmpty())
515
            weapskill = weapon.getClass().getEquipmentSkill(weapon);
516

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

Chris Robinson's avatar
Chris Robinson committed
519
        if((::rand()/(RAND_MAX+1.0)) > hitchance/100.0f)
520
        {
521
            othercls.onHit(victim, 0.0f, false, weapon, ptr, false);
522
            MWMechanics::reduceWeaponCondition(0.f, false, weapon, ptr);
523 524
            return;
        }
Chris Robinson's avatar
Chris Robinson committed
525

526
        bool healthdmg;
527
        float damage = 0.0f;
528
        MWMechanics::NpcStats &stats = getNpcStats(ptr);
529
        if(!weapon.isEmpty())
Chris Robinson's avatar
Chris Robinson committed
530
        {
531
            const unsigned char *attack = NULL;
mrcheko's avatar
mrcheko committed
532
            if(type == ESM::Weapon::AT_Chop)
533
                attack = weapon.get<ESM::Weapon>()->mBase->mData.mChop;
mrcheko's avatar
mrcheko committed
534
            else if(type == ESM::Weapon::AT_Slash)
535
                attack = weapon.get<ESM::Weapon>()->mBase->mData.mSlash;
mrcheko's avatar
mrcheko committed
536
            else if(type == ESM::Weapon::AT_Thrust)
537
                attack = weapon.get<ESM::Weapon>()->mBase->mData.mThrust;
538
            if(attack)
539
            {
540
                damage  = attack[0] + ((attack[1]-attack[0])*stats.getAttackStrength());
541 542
                damage *= gmst.fDamageStrengthBase->getFloat() +
                        (stats.getAttribute(ESM::Attribute::Strength).getModified() * gmst.fDamageStrengthMult->getFloat() * 0.1);
543
            }
544 545
            MWMechanics::adjustWeaponDamage(damage, weapon);
            MWMechanics::reduceWeaponCondition(damage, true, weapon, ptr);
546 547 548 549
            healthdmg = true;
        }
        else
        {
550
            MWMechanics::getHandToHandDamage(ptr, victim, damage, healthdmg);
Chris Robinson's avatar
Chris Robinson committed
551
        }
552
        if(ptr.getRefData().getHandle() == "player")
scrawl's avatar
scrawl committed
553
        {
554
            skillUsageSucceeded(ptr, weapskill, 0);
555

scrawl's avatar
scrawl committed
556 557 558 559 560 561 562 563 564 565
            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);
            }
566
        }
scrawl's avatar
scrawl committed
567

568
        if (othercls.getCreatureStats(victim).getKnockedDown())
569
            damage *= store.find("fCombatKODamageMult")->getFloat();
570

571
        // Apply "On hit" enchanted weapons
572
        std::string enchantmentName = !weapon.isEmpty() ? weapon.getClass().getEnchantment(weapon) : "";
573 574 575 576 577 578
        if (!enchantmentName.empty())
        {
            const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().find(
                        enchantmentName);
            if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes)
            {
579 580
                MWMechanics::CastSpell cast(ptr, victim);
                cast.mHitPosition = hitPosition;
581
                cast.cast(weapon);
582 583 584
            }
        }

585 586
        MWMechanics::applyElementalShields(ptr, victim);

587
        if (MWMechanics::blockMeleeAttack(ptr, victim, weapon, damage))
588 589 590
            damage = 0;

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

593 594
        MWMechanics::diseaseContact(victim, ptr);

595
        othercls.onHit(victim, damage, healthdmg, weapon, ptr, true);
Chris Robinson's avatar
Chris Robinson committed
596 597
    }

598
    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
599
    {
600 601
        MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();

602 603
        // NOTE: 'object' and/or 'attacker' may be empty.

604 605
        bool wasDead = getCreatureStats(ptr).isDead();

606 607
        // Note OnPcHitMe is not set for friendly hits.
        bool setOnPcHitMe = true;
608 609 610
        if (!attacker.isEmpty() && !ptr.getClass().getCreatureStats(ptr).getAiSequence().isInCombat(attacker))
        {
            getCreatureStats(ptr).setAttacked(true);
611

612
            setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker);
613
        }
614

615 616 617
        if(!object.isEmpty())
            getCreatureStats(ptr).setLastHitAttemptObject(object.getClass().getId(object));

618 619 620
        if(!successful)
        {
            // Missed
621
            sndMgr->playSound3D(ptr, "miss", 1.0f, 1.0f);
622 623
            return;
        }
Chris Robinson's avatar
Chris Robinson committed
624

625
        if(!object.isEmpty())
626
            getCreatureStats(ptr).setLastHitObject(object.getClass().getId(object));
627

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

636 637 638
        if (damage > 0.0f && !object.isEmpty())
            MWMechanics::resistNormalWeapon(ptr, attacker, object, damage);

639 640 641
        if (damage < 0.001f)
            damage = 0;

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

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

scrawl's avatar
scrawl committed
650 651 652 653 654 655
            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");
            }
656 657

            // Check for knockdown
658
            float agilityTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() * gmst.fKnockDownMult->getFloat();
659
            float knockdownTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified()
660
                    * gmst.iKnockDownOddsMult->getInt() * 0.01 + gmst.iKnockDownOddsBase->getInt();
661 662 663 664 665 666 667 668
            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?
669

670
            if(damage > 0 && ishealth)
671
            {
672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689
                // 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)];

690 691 692 693 694 695
                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;
696

697 698 699 700
                MWWorld::InventoryStore &inv = getInventoryStore(ptr);
                MWWorld::ContainerStoreIterator armorslot = inv.getSlot(hitslot);
                MWWorld::Ptr armor = ((armorslot != inv.end()) ? *armorslot : MWWorld::Ptr());
                if(!armor.isEmpty() && armor.getTypeName() == typeid(ESM::Armor).name())
701
                {
702
                    int armorhealth = armor.getClass().getItemHealth(armor);
703
                    armorhealth -= std::min(std::max(1, damageDiff),
704 705
                                                 armorhealth);
                    armor.getCellRef().setCharge(armorhealth);
706 707

                    // Armor broken? unequip it
708
                    if (armorhealth == 0)
709
                        armor = *inv.unequipItem(armor, ptr);
710

711
                    if (ptr.getRefData().getHandle() == "player")
712
                        skillUsageSucceeded(ptr, armor.getClass().getEquipmentSkill(armor), 0);
713

714
                    switch(armor.getClass().getEquipmentSkill(armor))
715 716 717 718 719 720 721 722 723 724 725
                    {
                        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;
                    }
726
                }
727 728
                else if(ptr.getRefData().getHandle() == "player")
                    skillUsageSucceeded(ptr, ESM::Skill::Unarmored, 0);
729
            }
Chris Robinson's avatar
Chris Robinson committed
730 731
        }

732 733
        if(ishealth)
        {
734 735 736
            if (!attacker.isEmpty())
                damage = scaleDamage(damage, attacker, ptr);

737
            if(damage > 0.0f)
738
            {
739
                sndMgr->playSound3D(ptr, "Health Damage", 1.0f, 1.0f);
740 741 742
                if (ptr.getRefData().getHandle() == "player")
                    MWBase::Environment::get().getWindowManager()->activateHitOverlay();
            }
743 744 745 746 747 748
            float health = getCreatureStats(ptr).getHealth().getCurrent() - damage;
            setActorHealth(ptr, health, attacker);
        }
        else
        {
            MWMechanics::DynamicStat<float> fatigue(getCreatureStats(ptr).getFatigue());
749
            fatigue.setCurrent(fatigue.getCurrent() - damage, true);
750 751
            getCreatureStats(ptr).setFatigue(fatigue);
        }
752 753 754 755

        if (!wasDead && getCreatureStats(ptr).isDead())
        {
            // NPC was killed
scrawl's avatar
scrawl committed
756 757 758 759 760
            if (!attacker.isEmpty() && attacker.getClass().isNpc() && attacker.getClass().getNpcStats(attacker).isWerewolf())
            {
                attacker.getClass().getNpcStats(attacker).addWerewolfKill();
            }

761 762 763 764
            // Simple check for who attacked first: if the player attacked first, a crimeId should be set
            // Doesn't handle possible edge case where no one reported the assault, but in such a case,
            // for bystanders it is not possible to tell who attacked first, anyway.
            MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
scrawl's avatar
scrawl committed
765
            if (attacker == player && ptr.getClass().getNpcStats(ptr).getCrimeId() != -1 && ptr != player)
766 767
                MWBase::Environment::get().getMechanicsManager()->commitCrime(player, ptr, MWBase::MechanicsManager::OT_Murder);
        }
768 769
    }

770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793
    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::