npc.cpp 48.3 KB
Newer Older
1 2 3

#include "npc.hpp"

4 5
#include <memory>

scrawl's avatar
scrawl committed
6 7
#include <boost/algorithm/string.hpp>

8 9
#include <OgreSceneNode.h>

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

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

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

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

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

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

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

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

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

    MWWorld::CustomData *CustomData::clone() const
    {
        return new CustomData (*this);
    }
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86

    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];
            creatureStats.getAttribute(i).setBase (male ? attribute.mMale : attribute.mFemale);
        }

        // 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)
            {
                creatureStats.getAttribute(attribute).setBase (
                    creatureStats.getAttribute(attribute).getBase() + 10);
            }
        }

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

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

        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));
132
    }
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215

    /**
     * @brief autoCalculateSkills
     *
     * Skills are calculated with following formulae ( http://www.uesp.net/wiki/Morrowind:NPCs#Skills ):
     *
     * Skills: (Level - 1) × (Majority Multiplier + Specialization Multiplier)
     *
     *         The Majority Multiplier is 1.0 for a Major or Minor Skill, or 0.1 for a Miscellaneous Skill.
     *
     *         The Specialization Multiplier is 0.5 for a Skill in the same Specialization as the class,
     *         zero for other Skills.
     *
     * and by adding class, race, specialization bonus.
     */
    void autoCalculateSkills(const ESM::NPC* npc, MWMechanics::NpcStats& npcStats)
    {
        const ESM::Class *class_ =
            MWBase::Environment::get().getWorld()->getStore().get<ESM::Class>().find(npc->mClass);

        unsigned int level = npcStats.getLevel();

        const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(npc->mRace);


        for (int i = 0; i < 2; ++i)
        {
            int bonus = (i==0) ? 10 : 25;

            for (int i2 = 0; i2 < 5; ++i2)
            {
                int index = class_->mData.mSkills[i2][i];
                if (index >= 0 && index < ESM::Skill::Length)
                {
                    npcStats.getSkill(index).setBase (npcStats.getSkill(index).getBase() + bonus);
                }
            }
        }

        for (int skillIndex = 0; skillIndex < ESM::Skill::Length; ++skillIndex)
        {
            float majorMultiplier = 0.1f;
            float specMultiplier = 0.0f;

            int raceBonus = 0;
            int specBonus = 0;

            for (int raceSkillIndex = 0; raceSkillIndex < 7; ++raceSkillIndex)
            {
              if (race->mData.mBonus[raceSkillIndex].mSkill == skillIndex)
              {
                  raceBonus = race->mData.mBonus[raceSkillIndex].mBonus;
                  break;
              }
            }

            for (int k = 0; k < 5; ++k)
            {
              // is this a minor or major skill?
              if ((class_->mData.mSkills[k][0] == skillIndex) || (class_->mData.mSkills[k][1] == skillIndex))
              {
                majorMultiplier = 1.0f;
                break;
              }
            }

            // 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)
            {
              specMultiplier = 0.5f;
              specBonus = 5;
            }

            npcStats.getSkill(skillIndex).setBase(
                  std::min(
                    npcStats.getSkill(skillIndex).getBase()
                    + 5
                    + raceBonus
                    + specBonus
                    + static_cast<int>((level-1) * (majorMultiplier + specMultiplier)), 100.0f));
        }
    }
216 217
}

218
namespace MWClass
219
{
220 221
    void Npc::ensureCustomData (const MWWorld::Ptr& ptr) const
    {
222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237
        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");
238 239 240 241 242
            fJumpEncumbranceBase = gmst.find("fJumpEncumbranceBase");
            fJumpEncumbranceMultiplier = gmst.find("fJumpEncumbranceMultiplier");
            fJumpAcrobaticsBase = gmst.find("fJumpAcrobaticsBase");
            fJumpAcroMultiplier = gmst.find("fJumpAcroMultiplier");
            fJumpRunMultiplier = gmst.find("fJumpRunMultiplier");
243
            fWereWolfRunMult = gmst.find("fWereWolfRunMult");
244 245 246

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

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

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

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

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

277 278 279 280 281 282 283 284 285 286 287 288 289
                data->mNpcStats.getAttribute(0).set (ref->mBase->mNpdt52.mStrength);
                data->mNpcStats.getAttribute(1).set (ref->mBase->mNpdt52.mIntelligence);
                data->mNpcStats.getAttribute(2).set (ref->mBase->mNpdt52.mWillpower);
                data->mNpcStats.getAttribute(3).set (ref->mBase->mNpdt52.mAgility);
                data->mNpcStats.getAttribute(4).set (ref->mBase->mNpdt52.mSpeed);
                data->mNpcStats.getAttribute(5).set (ref->mBase->mNpdt52.mEndurance);
                data->mNpcStats.getAttribute(6).set (ref->mBase->mNpdt52.mPersonality);
                data->mNpcStats.getAttribute(7).set (ref->mBase->mNpdt52.mLuck);
                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
290
                data->mNpcStats.setBaseDisposition(ref->mBase->mNpdt52.mDisposition);
291
                data->mNpcStats.setReputation(ref->mBase->mNpdt52.mReputation);
292 293 294
            }
            else
            {
295 296
                gold = ref->mBase->mNpdt12.mGold;

297
                for (int i=0; i<3; ++i)
298
                    data->mNpcStats.setDynamic (i, 10);
299

300
                data->mNpcStats.setLevel(ref->mBase->mNpdt12.mLevel);
301 302
                data->mNpcStats.setBaseDisposition(ref->mBase->mNpdt12.mDisposition);
                data->mNpcStats.setReputation(ref->mBase->mNpdt12.mReputation);
303

304
                autoCalculateAttributes(ref->mBase, data->mNpcStats);
305
                autoCalculateSkills(ref->mBase, data->mNpcStats);
306
            }
307

308
            data->mNpcStats.getAiSequence().fill(ref->mBase->mAiPackage);
309

310 311 312 313
            data->mNpcStats.setAiSetting (0, ref->mBase->mAiData.mHello);
            data->mNpcStats.setAiSetting (1, ref->mBase->mAiData.mFight);
            data->mNpcStats.setAiSetting (2, ref->mBase->mAiData.mFlee);
            data->mNpcStats.setAiSetting (3, ref->mBase->mAiData.mAlarm);
314

315
            // spells
greye's avatar
greye committed
316 317
            for (std::vector<std::string>::const_iterator iter (ref->mBase->mSpells.mList.begin());
                iter!=ref->mBase->mSpells.mList.end(); ++iter)
318
                data->mNpcStats.getSpells().add (*iter);
319

320 321 322 323
            // inventory
            data->mInventoryStore.fill(ref->mBase->mInventory, getId(ptr),
                                       MWBase::Environment::get().getWorld()->getStore());

324
            // store
325
            ptr.getRefData().setCustomData (data.release());
326

327 328
            getContainerStore(ptr).add("gold_001", gold, ptr);

329
            getInventoryStore(ptr).autoEquip(ptr);
330 331 332
        }
    }

Marc Zinnschlag's avatar
Marc Zinnschlag committed
333 334
    std::string Npc::getId (const MWWorld::Ptr& ptr) const
    {
335
        MWWorld::LiveCellRef<ESM::NPC> *ref =
Marc Zinnschlag's avatar
Marc Zinnschlag committed
336 337
            ptr.get<ESM::NPC>();

greye's avatar
greye committed
338
        return ref->mBase->mId;
Marc Zinnschlag's avatar
Marc Zinnschlag committed
339 340
    }

341 342 343 344 345
    void Npc::adjustPosition(const MWWorld::Ptr& ptr) const
    {
        MWBase::Environment::get().getWorld()->adjustPosition(ptr);
    }

Jason Hooks's avatar
Jason Hooks committed
346
    void Npc::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const
347
    {
348
        renderingInterface.getActors().insertNPC(ptr);
Jason Hooks's avatar
Jason Hooks committed
349
    }
Jason Hooks's avatar
Jason Hooks committed
350

351
    void Npc::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const
Jason Hooks's avatar
Jason Hooks committed
352
    {
353
        physics.addActor(ptr);
354
        MWBase::Environment::get().getMechanicsManager()->add(ptr);
greye's avatar
greye committed
355
    }
Jason Hooks's avatar
Jason Hooks committed
356

scrawl's avatar
scrawl committed
357 358 359 360 361 362
    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
363 364
    std::string Npc::getModel(const MWWorld::Ptr &ptr) const
    {
365
        MWWorld::LiveCellRef<ESM::NPC> *ref =
Jason Hooks's avatar
Jason Hooks committed
366
            ptr.get<ESM::NPC>();
greye's avatar
greye committed
367
        assert(ref->mBase != NULL);
368

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

scrawl's avatar
scrawl committed
371 372
        //int end = headID.find_last_of("head_") - 4;
        //std::string bodyRaceID = headID.substr(0, end);
greye's avatar
greye committed
373 374

        std::string model = "meshes\\base_anim.nif";
375 376
        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
377
            model = "meshes\\base_animkna.nif";
378

greye's avatar
greye committed
379
        return model;
380 381 382

    }

Marc Zinnschlag's avatar
Marc Zinnschlag committed
383 384
    std::string Npc::getName (const MWWorld::Ptr& ptr) const
    {
385 386 387 388 389 390 391
        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
392

393
        MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>();
greye's avatar
greye committed
394
        return ref->mBase->mName;
Marc Zinnschlag's avatar
Marc Zinnschlag committed
395 396
    }

397
    MWMechanics::CreatureStats& Npc::getCreatureStats (const MWWorld::Ptr& ptr) const
398
    {
399
        ensureCustomData (ptr);
400

401
        return dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData()).mNpcStats;
402 403
    }

Marc Zinnschlag's avatar
Marc Zinnschlag committed
404 405
    MWMechanics::NpcStats& Npc::getNpcStats (const MWWorld::Ptr& ptr) const
    {
406
        ensureCustomData (ptr);
Marc Zinnschlag's avatar
Marc Zinnschlag committed
407

408
        return dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData()).mNpcStats;
Marc Zinnschlag's avatar
Marc Zinnschlag committed
409 410
    }

Chris Robinson's avatar
Chris Robinson committed
411

412
    void Npc::hit(const MWWorld::Ptr& ptr, int type) const
Chris Robinson's avatar
Chris Robinson committed
413
    {
414 415 416
        MWBase::World *world = MWBase::Environment::get().getWorld();
        const MWWorld::Store<ESM::GameSetting> &gmst = world->getStore().get<ESM::GameSetting>();

417 418
        // Get the weapon used (if hand-to-hand, weapon = inv.end())
        MWWorld::InventoryStore &inv = getInventoryStore(ptr);
419 420 421 422
        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();
423

424 425
        float dist = 100.0f * (!weapon.isEmpty() ?
                               weapon.get<ESM::Weapon>()->mBase->mData.mReach :
426
                               gmst.find("fHandToHandReach")->getFloat());
427 428
        // TODO: Use second to work out the hit angle and where to spawn the blood effect
        MWWorld::Ptr victim = world->getHitContact(ptr, dist).first;
Chris Robinson's avatar
Chris Robinson committed
429 430 431 432
        if(victim.isEmpty()) // Didn't hit anything
            return;

        const MWWorld::Class &othercls = MWWorld::Class::get(victim);
433 434
        if(!othercls.isActor()) // Can't hit non-actors
            return;
435
        MWMechanics::CreatureStats &otherstats = victim.getClass().getCreatureStats(victim);
436
        if(otherstats.isDead()) // Can't hit dead actors
Chris Robinson's avatar
Chris Robinson committed
437 438
            return;

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

442
        int weapskill = ESM::Skill::HandToHand;
443
        if(!weapon.isEmpty())
444
            weapskill = get(weapon).getEquipmentSkill(weapon);
445

446 447 448 449 450 451
        MWMechanics::NpcStats &stats = getNpcStats(ptr);
        const MWMechanics::MagicEffects &mageffects = stats.getMagicEffects();
        float hitchance = stats.getSkill(weapskill).getModified() +
                          (stats.getAttribute(ESM::Attribute::Agility).getModified() / 5.0f) +
                          (stats.getAttribute(ESM::Attribute::Luck).getModified() / 10.0f);
        hitchance *= stats.getFatigueTerm();
452 453
        hitchance += mageffects.get(MWMechanics::EffectKey(ESM::MagicEffect::FortifyAttack)).mMagnitude -
                     mageffects.get(MWMechanics::EffectKey(ESM::MagicEffect::Blind)).mMagnitude;
454
        hitchance -= otherstats.getEvasion();
455

Chris Robinson's avatar
Chris Robinson committed
456
        if((::rand()/(RAND_MAX+1.0)) > hitchance/100.0f)
457
        {
458
            othercls.onHit(victim, 0.0f, false, weapon, ptr, false);
459 460
            return;
        }
Chris Robinson's avatar
Chris Robinson committed
461

462
        bool healthdmg;
463 464
        float damage = 0.0f;
        if(!weapon.isEmpty())
Chris Robinson's avatar
Chris Robinson committed
465
        {
466
            const bool weaphashealth = get(weapon).hasItemHealth(weapon);
467
            const unsigned char *attack = NULL;
Chris Robinson's avatar
Chris Robinson committed
468
            if(type == MWMechanics::CreatureStats::AT_Chop)
469
                attack = weapon.get<ESM::Weapon>()->mBase->mData.mChop;
Chris Robinson's avatar
Chris Robinson committed
470
            else if(type == MWMechanics::CreatureStats::AT_Slash)
471
                attack = weapon.get<ESM::Weapon>()->mBase->mData.mSlash;
Chris Robinson's avatar
Chris Robinson committed
472
            else if(type == MWMechanics::CreatureStats::AT_Thrust)
473
                attack = weapon.get<ESM::Weapon>()->mBase->mData.mThrust;
474
            if(attack)
475
            {
476 477
                damage  = attack[0] + ((attack[1]-attack[0])*stats.getAttackStrength());
                damage *= 0.5f + (stats.getAttribute(ESM::Attribute::Luck).getModified() / 100.0f);
478 479 480 481 482 483 484
                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;
                }
485 486 487 488
                if(!othercls.hasDetected(victim, ptr))
                {
                    damage *= gmst.find("fCombatCriticalStrikeMult")->getFloat();
                    MWBase::Environment::get().getWindowManager()->messageBox("#{sTargetCriticalStrike}");
489
                    MWBase::Environment::get().getSoundManager()->playSound3D(victim, "critical damage", 1.0f, 1.0f);
490
                }
491 492 493 494
                
                if (!MWBase::Environment::get().getWorld()->getGodModeState())
                    weapon.getCellRef().mCharge -= std::min(std::max(1,
                        (int)(damage * gmst.find("fWeaponDamageMult")->getFloat())), weapon.getCellRef().mCharge);
495
            }
496 497 498 499 500 501 502 503 504
            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();
505 506
            damage  = stats.getSkill(weapskill).getModified();
            damage *= minstrike + ((maxstrike-minstrike)*stats.getAttackStrength());
507 508 509 510 511 512 513
            if(!othercls.hasDetected(victim, ptr))
            {
                damage *= gmst.find("fCombatCriticalStrikeMult")->getFloat();
                MWBase::Environment::get().getWindowManager()->messageBox("#{sTargetCriticalStrike}");
                MWBase::Environment::get().getSoundManager()->playSound3D(victim, "critical damage", 1.0f, 1.0f);
            }

514
            healthdmg = (otherstats.getFatigue().getCurrent() < 1.0f);
515
            if(stats.isWerewolf())
516 517 518 519 520 521
            {
                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();
            }
522
            if(healthdmg)
523
                damage *= gmst.find("fHandtoHandHealthPer")->getFloat();
524 525 526 527

            MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
            if(stats.isWerewolf())
            {
528 529 530
                const ESM::Sound *sound = world->getStore().get<ESM::Sound>().searchRandom("WolfHit");
                if(sound)
                    sndMgr->playSound3D(victim, sound->mId, 1.0f, 1.0f);
531 532 533
            }
            else
                sndMgr->playSound3D(victim, "Hand To Hand Hit", 1.0f, 1.0f);
Chris Robinson's avatar
Chris Robinson committed
534
        }
535 536
        if(ptr.getRefData().getHandle() == "player")
            skillUsageSucceeded(ptr, weapskill, 0);
537

538
        // Apply "On hit" enchanted weapons
539
        std::string enchantmentName = !weapon.isEmpty() ? weapon.getClass().getEnchantment(weapon) : "";
540 541 542 543 544 545 546 547 548
        if (!enchantmentName.empty())
        {
            const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().find(
                        enchantmentName);
            if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes)
            {
                // Check if we have enough charges
                const float enchantCost = enchantment->mData.mCost;
                int eSkill = stats.getSkill(ESM::Skill::Enchant).getModified();
549
                const int castCost = std::max(1.f, enchantCost - (enchantCost / 100) * (eSkill - 10));
550 551 552 553 554 555 556 557 558 559 560

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

                    MWMechanics::CastSpell cast(ptr, victim);
                    cast.cast(weapon);
564 565 566

                    if (ptr.getRefData().getHandle() == "player")
                        skillUsageSucceeded (ptr, ESM::Skill::Enchant, 3);
567 568 569 570
                }
            }
        }

571
        othercls.onHit(victim, damage, healthdmg, weapon, ptr, true);
Chris Robinson's avatar
Chris Robinson committed
572 573
    }

574
    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
575
    {
576 577
        MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();

578 579 580 581 582 583 584
        // NOTE: 'object' and/or 'attacker' may be empty.

        if(!successful)
        {
            // TODO: Handle HitAttemptOnMe script function

            // Missed
585
            sndMgr->playSound3D(ptr, "miss", 1.0f, 1.0f);
586 587
            return;
        }
Chris Robinson's avatar
Chris Robinson committed
588

589
        if(!object.isEmpty())
590
            getCreatureStats(ptr).setLastHitObject(get(object).getId(object));
591 592 593 594 595 596 597 598

        if(!attacker.isEmpty() && attacker.getRefData().getHandle() == "player")
        {
            const std::string &script = ptr.get<ESM::NPC>()->mBase->mScript;
            /* Set the OnPCHitMe script variable. The script is responsible for clearing it. */
            if(!script.empty())
                ptr.getRefData().getLocals().setVarByInt(script, "onpchitme", 1);
        }
599 600

        if(damage > 0.0f)
Chris Robinson's avatar
Chris Robinson committed
601 602 603
        {
            // 'ptr' is losing health. Play a 'hit' voiced dialog entry if not already saying
            // something, alert the character controller, scripts, etc.
604 605

            MWBase::Environment::get().getDialogueManager()->say(ptr, "hit");
606

607
            if(object.isEmpty())
608 609
            {
                if(ishealth)
610
                    damage /= std::min(1.0f + getArmorRating(ptr)/std::max(1.0f, damage), 4.0f);
611 612
            }
            else if(ishealth)
613
            {
614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631
                // 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)];

632 633 634 635
                float damagediff = damage;
                damage /= std::min(1.0f + getArmorRating(ptr)/std::max(1.0f, damage), 4.0f);
                damagediff -= damage;

636 637 638 639
                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())
640
                {
641 642 643 644 645
                    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);
646 647 648 649 650 651 652 653 654 655 656 657
                    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;
                    }
658 659
                }
            }
Chris Robinson's avatar
Chris Robinson committed
660 661
        }

662 663 664 665 666 667 668 669 670 671
        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());
672
            fatigue.setCurrent(fatigue.getCurrent() - damage, true);
673 674
            getCreatureStats(ptr).setFatigue(fatigue);
        }
675 676 677 678 679
    }

    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
680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696
        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
        }
    }


697
    boost::shared_ptr<MWWorld::Action> Npc::activate (const MWWorld::Ptr& ptr,
698
        const MWWorld::Ptr& actor) const
699
    {
700 701
        if(get(actor).isNpc() && get(actor).getNpcStats(actor).isWerewolf())
        {
702 703 704
            const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
            const ESM::Sound *sound = store.get<ESM::Sound>().searchRandom("WolfNPC");

705
            boost::shared_ptr<MWWorld::Action> action(new MWWorld::FailedAction("#{sWerewolfRefusal}"));
706 707
            if(sound) action->setSound(sound->mId);

708 709 710 711 712 713
            return action;
        }
        if(getCreatureStats(ptr).isDead())
            return boost::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr, true));
        if(get(actor).getStance(actor, MWWorld::Class::Sneak))
            return boost::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr)); // stealing
714 715
        if(get(ptr).getCreatureStats(ptr).isHostile())
            return boost::shared_ptr<MWWorld::Action>(new MWWorld::FailedAction("#{sActorInCombat}"));
716
        return boost::shared_ptr<MWWorld::Action>(new MWWorld::ActionTalk(ptr));
717
    }
Marc Zinnschlag's avatar
Marc Zinnschlag committed
718

719
    MWWorld::ContainerStore& Npc::getContainerStore (const MWWorld::Ptr& ptr)
720
        const
Marc Zinnschlag's avatar
Marc Zinnschlag committed
721
    {
722
        ensureCustomData (ptr);
Marc Zinnschlag's avatar
Marc Zinnschlag committed
723

724 725 726 727 728 729 730 731 732
        return dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData()).mInventoryStore;
    }

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

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

735 736
    std::string Npc::getScript (const MWWorld::Ptr& ptr) const
    {
737
        MWWorld::LiveCellRef<ESM::NPC> *ref =
738 739
            ptr.get<ESM::NPC>();

greye's avatar
greye committed
740
        return ref->mBase->mScript;
741 742
    }

Marc Zinnschlag's avatar
Marc Zinnschlag committed
743 744 745 746 747 748 749 750
    void Npc::setForceStance (const MWWorld::Ptr& ptr, Stance stance, bool force) const
    {
        MWMechanics::NpcStats& stats = getNpcStats (ptr);

        switch (stance)
        {
            case Run:

751
                stats.setMovementFlag (MWMechanics::NpcStats::Flag_ForceRun, force);
Marc Zinnschlag's avatar
Marc Zinnschlag committed
752 753 754 755
                break;

            case Sneak:

756
                stats.setMovementFlag (MWMechanics::NpcStats::Flag_ForceSneak, force);
Marc Zinnschlag's avatar
Marc Zinnschlag committed
757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772
                break;

            case Combat:

                throw std::runtime_error ("combat stance not enforcable for NPCs");
        }
    }

    void Npc::setStance (const MWWorld::Ptr& ptr, Stance stance, bool set) const
    {
        MWMechanics::NpcStats& stats = getNpcStats (ptr);

        switch (stance)
        {
            case Run:

773
                stats.setMovementFlag (MWMechanics::NpcStats::Flag_Run, set);
774
                break;
Marc Zinnschlag's avatar
Marc Zinnschlag committed
775 776 777

            case Sneak:

778
                stats.setMovementFlag (MWMechanics::NpcStats::Flag_Sneak, set);
Marc Zinnschlag's avatar
Marc Zinnschlag committed
779 780 781 782
                break;

            case Combat:

783 784
                // Combat stance ignored for now; need to be determined based on draw state instead of
                // being maunally set.
Marc Zinnschlag's avatar
Marc Zinnschlag committed
785 786 787 788 789 790 791 792 793 794 795 796
                break;
        }
    }

    bool Npc::getStance (const MWWorld::Ptr& ptr, Stance stance, bool ignoreForce) const
    {
        MWMechanics::NpcStats& stats = getNpcStats (ptr);

        switch (stance)
        {
            case Run:

797
                if (!ignoreForce && stats.getMovementFlag (MWMechanics::NpcStats::Flag_ForceRun))
798 799
                    return true;

800
                return stats.getMovementFlag (MWMechanics::NpcStats::Flag_Run);
Marc Zinnschlag's avatar
Marc Zinnschlag committed
801 802 803

            case Sneak:

804
                if (!ignoreForce && stats.getMovementFlag (MWMechanics::NpcStats::Flag_ForceSneak))
Marc Zinnschlag's avatar
Marc Zinnschlag committed
805 806
                    return true;

807
                return stats.getMovementFlag (MWMechanics::NpcStats::Flag_Sneak);
Marc Zinnschlag's avatar
Marc Zinnschlag committed
808 809 810

            case Combat:

811
                return false;
Marc Zinnschlag's avatar
Marc Zinnschlag committed
812 813 814 815 816
        }

        return false;
    }

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

823 824
        const float normalizedEncumbrance = Npc::getEncumbrance(ptr) / Npc::getCapacity(ptr);

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

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

        float moveSpeed;
838
        if(normalizedEncumbrance >= 1.0f)
839
            moveSpeed = 0.0f;
840
        else if(mageffects.get(MWMechanics::EffectKey(10/*levitate*/)).mMagnitude > 0)
841
        {
842
            float flySpeed = 0.01f*(npcdata->mNpcStats.getAttribute(ESM::Attribute::Speed).getModified() +
843
                                    mageffects.get(MWMechanics::EffectKey(10/*levitate*/)).mMagnitude);
844 845 846 847 848 849 850 851 852 853
            flySpeed = fMinFlySpeed->getFloat() + flySpeed*(fMaxFlySpeed->getFloat() - fMinFlySpeed->getFloat());
            flySpeed *= 1.0f - fEncumberedMoveEffect->getFloat() * normalizedEncumbrance;
            flySpeed = std::max(0.0f, flySpeed);
            moveSpeed = flySpeed;
        }
        else if(world->isSwimming(ptr))
        {
            float swimSpeed = walkSpeed;
            if(Npc::getStance(ptr, Run, false))
                swimSpeed = runSpeed;
854
            swimSpeed *= 1.0f + 0.01f * mageffects.get(MWMechanics::EffectKey(1/*swift swim*/)).mMagnitude;
855 856 857 858
            swimSpeed *= fSwimRunBase->getFloat() + 0.01f*npcdata->mNpcStats.getSkill(ESM::Skill::Athletics).getModified()*
                                                    fSwimRunAthleticsMult->getFloat();
            moveSpeed = swimSpeed;
        }
scrawl's avatar
scrawl committed
859
        else if(Npc::getStance(ptr, Run, false) && !Npc::getStance(ptr, Sneak, false))
860 861 862
            moveSpeed = runSpeed;
        else
            moveSpeed = walkSpeed;
863
        if(getMovementSettings(ptr).mPosition[0] != 0 && getMovementSettings(ptr).mPosition[1] == 0)
864 865 866
            moveSpeed *= 0.75f;

        return moveSpeed;
Marc Zinnschlag's avatar
Marc Zinnschlag committed
867 868
    }

869 870 871
    float Npc::getJump(const MWWorld::Ptr &ptr) const
    {
        const CustomData *npcdata = static_cast<const CustomData*>(ptr.getRefData().getCustomData());
872
        const MWMechanics::MagicEffects &mageffects = npcdata->mNpcStats.getMagicEffects();
873 874
        const float encumbranceTerm = fJumpEncumbranceBase->getFloat() +
                                          fJumpEncumbranceMultiplier->getFloat() *
875
                                          (1.0f - Npc::getEncumbrance(ptr)/Npc::getCapacity(ptr));
876 877 878 879 880 881 882 883 884 885 886

        float a = npcdata->mNpcStats.getSkill(ESM::Skill::Acrobatics).getModified();
        float b = 0.0f;
        if(a > 50.0f)
        {
            b = a - 50.0f;
            a = 50.0f;
        }

        float x = fJumpAcrobaticsBase->getFloat() +
                  std::pow(a / 15.0f, fJumpAcroMultiplier->getFloat());
887 888
        x += 3.0f * b * fJumpAcroMultiplier->getFloat();
        x += mageffects.get(MWMechanics::EffectKey(ESM::MagicEffect::Jump)).mMagnitude * 64;
889