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

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

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

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

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

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

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

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

    MWWorld::CustomData *CustomData::clone() const
    {
        return new CustomData (*this);
    }
52 53 54 55 56 57 58 59 60 61 62 63 64

    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];
65
            creatureStats.setAttribute(i, male ? attribute.mMale : attribute.mFemale);
66 67 68 69 70 71 72 73 74 75 76
        }

        // 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)
            {
77
                creatureStats.setAttribute(attribute,
78 79 80 81 82
                    creatureStats.getAttribute(attribute).getBase() + 10);
            }
        }

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

112
        // initial health
113 114
        int strength = creatureStats.getAttribute(ESM::Attribute::Strength).getBase();
        int endurance = creatureStats.getAttribute(ESM::Attribute::Endurance).getBase();
115 116 117 118 119 120 121 122 123 124 125 126 127

        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));
128
    }
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143

    /**
     * @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.
     */
144
    void autoCalculateSkills(const ESM::NPC* npc, MWMechanics::NpcStats& npcStats, const MWWorld::Ptr& ptr)
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
    {
        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)
            {
178 179 180 181 182
                if (race->mData.mBonus[raceSkillIndex].mSkill == skillIndex)
                {
                    raceBonus = race->mData.mBonus[raceSkillIndex].mBonus;
                    break;
                }
183 184 185 186
            }

            for (int k = 0; k < 5; ++k)
            {
187 188 189 190 191 192
                // is this a minor or major skill?
                if ((class_->mData.mSkills[k][0] == skillIndex) || (class_->mData.mSkills[k][1] == skillIndex))
                {
                    majorMultiplier = 1.0f;
                    break;
                }
193 194 195 196 197 198 199 200 201 202 203 204
                if (class_->mData.mSkills[k][1] == skillIndex)
                {
                    // Major skill -> add starting spells for this skill if existing
                    const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
                    MWWorld::Store<ESM::Spell>::iterator it = store.get<ESM::Spell>().begin();
                    for (; it != store.get<ESM::Spell>().end(); ++it)
                    {
                        if (it->mData.mFlags & ESM::Spell::F_Autocalc
                                && MWMechanics::spellSchoolToSkill(MWMechanics::getSpellSchool(&*it, ptr)) == skillIndex)
                            npcStats.getSpells().add(it->mId);
                    }
                }
205 206 207 208 209 210
            }

            // 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)
            {
211 212
                specMultiplier = 0.5f;
                specBonus = 5;
213 214 215 216 217 218 219 220
            }

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

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

            inited = true;
        }
260 261
        if (!ptr.getRefData().getCustomData())
        {
262
            std::auto_ptr<CustomData> data(new CustomData);
263

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

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

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

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

290 291 292 293 294 295 296 297 298
                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);

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

311
                for (int i=0; i<3; ++i)
312
                    data->mNpcStats.setDynamic (i, 10);
313

314
                data->mNpcStats.setLevel(ref->mBase->mNpdt12.mLevel);
315 316
                data->mNpcStats.setBaseDisposition(ref->mBase->mNpdt12.mDisposition);
                data->mNpcStats.setReputation(ref->mBase->mNpdt12.mReputation);
317

318
                autoCalculateAttributes(ref->mBase, data->mNpcStats);
319
                autoCalculateSkills(ref->mBase, data->mNpcStats, ptr);
320
            }
321

322 323 324 325 326 327 328 329
            // race powers
            const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(ref->mBase->mRace);
            for (std::vector<std::string>::const_iterator iter (race->mPowers.mList.begin());
                iter!=race->mPowers.mList.end(); ++iter)
            {
                data->mNpcStats.getSpells().add (*iter);
            }

330 331 332 333 334 335 336 337 338 339 340
            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));
            }

341
            data->mNpcStats.getAiSequence().fill(ref->mBase->mAiPackage);
342

343 344 345 346
            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);
347

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

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

357
            // store
358
            ptr.getRefData().setCustomData (data.release());
359

scrawl's avatar
scrawl committed
360 361
            // TODO: this is not quite correct, in vanilla the merchant's gold pool is not available in his inventory.
            // (except for gold you gave him)
362
            getContainerStore(ptr).add(MWWorld::ContainerStore::sGoldId, gold, ptr);
363

364
            getInventoryStore(ptr).autoEquip(ptr);
365 366 367
        }
    }

Marc Zinnschlag's avatar
Marc Zinnschlag committed
368 369
    std::string Npc::getId (const MWWorld::Ptr& ptr) const
    {
370
        MWWorld::LiveCellRef<ESM::NPC> *ref =
Marc Zinnschlag's avatar
Marc Zinnschlag committed
371 372
            ptr.get<ESM::NPC>();

greye's avatar
greye committed
373
        return ref->mBase->mId;
Marc Zinnschlag's avatar
Marc Zinnschlag committed
374 375
    }

376 377 378 379 380
    void Npc::adjustPosition(const MWWorld::Ptr& ptr) const
    {
        MWBase::Environment::get().getWorld()->adjustPosition(ptr);
    }

Jason Hooks's avatar
Jason Hooks committed
381
    void Npc::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const
382
    {
383
        renderingInterface.getActors().insertNPC(ptr);
Jason Hooks's avatar
Jason Hooks committed
384
    }
Jason Hooks's avatar
Jason Hooks committed
385

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

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

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

scrawl's avatar
scrawl committed
406 407
        //int end = headID.find_last_of("head_") - 4;
        //std::string bodyRaceID = headID.substr(0, end);
greye's avatar
greye committed
408 409

        std::string model = "meshes\\base_anim.nif";
410 411
        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
412
            model = "meshes\\base_animkna.nif";
413

greye's avatar
greye committed
414
        return model;
415 416 417

    }

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

428
        MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>();
greye's avatar
greye committed
429
        return ref->mBase->mName;
Marc Zinnschlag's avatar
Marc Zinnschlag committed
430 431
    }

432
    MWMechanics::CreatureStats& Npc::getCreatureStats (const MWWorld::Ptr& ptr) const
433
    {
434
        ensureCustomData (ptr);
435

436
        return dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData()).mNpcStats;
437 438
    }

Marc Zinnschlag's avatar
Marc Zinnschlag committed
439 440
    MWMechanics::NpcStats& Npc::getNpcStats (const MWWorld::Ptr& ptr) const
    {
441
        ensureCustomData (ptr);
Marc Zinnschlag's avatar
Marc Zinnschlag committed
442

443
        return dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData()).mNpcStats;
Marc Zinnschlag's avatar
Marc Zinnschlag committed
444 445
    }

Chris Robinson's avatar
Chris Robinson committed
446

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

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

459 460 461 462 463 464 465 466 467 468 469 470 471
        // Reduce fatigue
        // somewhat of a guess, but using the weapon weight makes sense
        const float fFatigueAttackBase = gmst.find("fFatigueAttackBase")->getFloat();
        const float fFatigueAttackMult = gmst.find("fFatigueAttackMult")->getFloat();
        const float fWeaponFatigueMult = gmst.find("fWeaponFatigueMult")->getFloat();
        MWMechanics::DynamicStat<float> fatigue = getCreatureStats(ptr).getFatigue();
        const float normalizedEncumbrance = getEncumbrance(ptr) / getCapacity(ptr);
        float fatigueLoss = fFatigueAttackBase + normalizedEncumbrance * fFatigueAttackMult;
        if (!weapon.isEmpty())
            fatigueLoss += weapon.getClass().getWeight(weapon) * getNpcStats(ptr).getAttackStrength() * fWeaponFatigueMult;
        fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss);
        getCreatureStats(ptr).setFatigue(fatigue);

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

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

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

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

494
        int weapskill = ESM::Skill::HandToHand;
495
        if(!weapon.isEmpty())
496
            weapskill = get(weapon).getEquipmentSkill(weapon);
497

498 499 500 501 502 503
        MWMechanics::NpcStats &stats = getNpcStats(ptr);
        const MWMechanics::MagicEffects &mageffects = stats.getMagicEffects();
        float hitchance = stats.getSkill(weapskill).getModified() +
                          (stats.getAttribute(ESM::Attribute::Agility).getModified() / 5.0f) +
                          (stats.getAttribute(ESM::Attribute::Luck).getModified() / 10.0f);
        hitchance *= stats.getFatigueTerm();
scrawl's avatar
scrawl committed
504 505
        hitchance += mageffects.get(ESM::MagicEffect::FortifyAttack).mMagnitude -
                     mageffects.get(ESM::MagicEffect::Blind).mMagnitude;
506
        hitchance -= otherstats.getEvasion();
507

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

514
        bool healthdmg;
515 516
        float damage = 0.0f;
        if(!weapon.isEmpty())
Chris Robinson's avatar
Chris Robinson committed
517
        {
518
            const bool weaphashealth = get(weapon).hasItemHealth(weapon);
519
            const unsigned char *attack = NULL;
Chris Robinson's avatar
Chris Robinson committed
520
            if(type == MWMechanics::CreatureStats::AT_Chop)
521
                attack = weapon.get<ESM::Weapon>()->mBase->mData.mChop;
Chris Robinson's avatar
Chris Robinson committed
522
            else if(type == MWMechanics::CreatureStats::AT_Slash)
523
                attack = weapon.get<ESM::Weapon>()->mBase->mData.mSlash;
Chris Robinson's avatar
Chris Robinson committed
524
            else if(type == MWMechanics::CreatureStats::AT_Thrust)
525
                attack = weapon.get<ESM::Weapon>()->mBase->mData.mThrust;
526
            if(attack)
527
            {
528
                damage  = attack[0] + ((attack[1]-attack[0])*stats.getAttackStrength());
529 530
                damage *= fDamageStrengthBase->getFloat() +
                        (stats.getAttribute(ESM::Attribute::Strength).getModified() * fDamageStrengthMult->getFloat() * 0.1);
531 532 533 534 535 536 537
                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;
                }
538 539 540 541
                
                if (!MWBase::Environment::get().getWorld()->getGodModeState())
                    weapon.getCellRef().mCharge -= std::min(std::max(1,
                        (int)(damage * gmst.find("fWeaponDamageMult")->getFloat())), weapon.getCellRef().mCharge);
542 543 544 545 546

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

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

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

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

585 586 587 588 589 590 591 592 593 594
        bool detected = MWBase::Environment::get().getMechanicsManager()->awarenessCheck(ptr, victim);
        if(!detected)
        {
            damage *= gmst.find("fCombatCriticalStrikeMult")->getFloat();
            MWBase::Environment::get().getWindowManager()->messageBox("#{sTargetCriticalStrike}");
            MWBase::Environment::get().getSoundManager()->playSound3D(victim, "critical damage", 1.0f, 1.0f);
        }
        if (othercls.getCreatureStats(victim).getKnockedDown())
            damage *= gmst.find("fCombatKODamageMult")->getFloat();

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

607 608
                if (ptr.getRefData().getHandle() == "player" && success)
                    skillUsageSucceeded (ptr, ESM::Skill::Enchant, 3);
609 610 611
            }
        }

scrawl's avatar
scrawl committed
612
        // TODO: do not do this if the attack is blocked
613 614
        if (healthdmg)
            MWBase::Environment::get().getWorld()->spawnBloodEffect(victim, hitPosition);
scrawl's avatar
scrawl committed
615

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

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

623 624
        // NOTE: 'object' and/or 'attacker' may be empty.

scrawl's avatar
scrawl committed
625 626 627 628
        // Attacking peaceful NPCs is a crime
        if (!attacker.isEmpty() && ptr.getClass().isNpc() && ptr.getClass().getCreatureStats(ptr).getAiSetting(MWMechanics::CreatureStats::AI_Fight).getModified() <= 30)
            MWBase::Environment::get().getMechanicsManager()->commitCrime(attacker, ptr, MWBase::MechanicsManager::OT_Assault);

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

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

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

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

649 650 651
        if (!attacker.isEmpty())
            MWMechanics::diseaseContact(ptr, attacker);

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

scrawl's avatar
scrawl committed
657 658 659 660 661 662 663
            const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
            int chance = store.get<ESM::GameSetting>().find("iVoiceHitOdds")->getInt();
            int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
            if (roll < chance)
            {
                MWBase::Environment::get().getDialogueManager()->say(ptr, "hit");
            }
664 665 666 667 668 669 670 671 672 673 674 675 676 677
            getCreatureStats(ptr).setAttacked(true);

            // Check for knockdown
            float agilityTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() * fKnockDownMult->getFloat();
            float knockdownTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified()
                    * iKnockDownOddsMult->getInt() * 0.01 + iKnockDownOddsBase->getInt();
            roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
            if (ishealth && agilityTerm <= damage && knockdownTerm <= roll)
            {
                getCreatureStats(ptr).setKnockedDown(true);

            }
            else
                getCreatureStats(ptr).setHitRecovery(true); // Is this supposed to always occur?
678

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

704 705 706 707
                float damagediff = damage;
                damage /= std::min(1.0f + getArmorRating(ptr)/std::max(1.0f, damage), 4.0f);
                damagediff -= damage;

708 709 710 711
                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())
712
                {
713 714 715 716 717
                    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);
718 719 720 721 722

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

723 724 725 726 727 728 729 730 731 732 733 734
                    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;
                    }
735 736
                }
            }
Chris Robinson's avatar
Chris Robinson committed
737 738
        }

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

    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
757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773
        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
        }
    }


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

782
            boost::shared_ptr<MWWorld::Action> action(new MWWorld::FailedAction("#{sWerewolfRefusal}"));
783 784
            if(sound) action->setSound(sound->mId);

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

796
    MWWorld::ContainerStore& Npc::getContainerStore (const MWWorld::Ptr& ptr)
797
        const
Marc Zinnschlag's avatar
Marc Zinnschlag committed
798
    {
799
        ensureCustomData (ptr);
Marc Zinnschlag's avatar
Marc Zinnschlag committed
800

801 802 803 804 805 806 807 808 809
        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
810 811
    }

812 813
    std::string Npc::getScript (const MWWorld::Ptr& ptr) const
    {
814
        MWWorld::LiveCellRef<ESM::NPC> *ref =
815 816
            ptr.get<ESM::NPC>();

greye's avatar
greye committed
817
        return ref->mBase->mScript;
818 819
    }

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

826 827
        const float normalizedEncumbrance = Npc::getEncumbrance(ptr) / Npc::getCapacity(ptr);

828 829 830
        bool sneaking = ptr.getClass().getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Sneak);
        bool running = ptr.getClass().getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Run);

831
        float walkSpeed = fMinWalkSpeed->getFloat() + 0.01f*npcdata->mNpcStats.getAttribute(ESM::Attribute::Speed).getModified()*
832 833 834
                                                      (fMaxWalkSpeed->getFloat() - fMinWalkSpeed->getFloat());
        walkSpeed *= 1.0f - fEncumberedMoveEffect->getFloat()*normalizedEncumbrance;
        walkSpeed = std::max(0.0f, walkSpeed);
835
        if(sneaking)
836
            walkSpeed *= fSneakSpeedMultiplier->getFloat();
837

838 839
        float runSpeed = walkSpeed*(0.01f * npcdata->mNpcStats.getSkill(ESM::Skill::Athletics).getModified() *
                                    fAthleticsRunBonus->getFloat() + fBaseRunMultiplier->getFloat());
840 841
        if(npcdata->mNpcStats.isWerewolf())
            runSpeed *= fWereWolfRunMult->getFloat();
842 843

        float moveSpeed;
844
        if(normalizedEncumbrance >= 1.0f)
845
            moveSpeed = 0.0f;