npc.cpp 36.2 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>

10 11
#include <components/esm/loadnpc.hpp>

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"
Marc Zinnschlag's avatar
Marc Zinnschlag committed
22

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

30
#include "../mwrender/actors.hpp"
31
#include "../mwrender/renderinginterface.hpp"
32

33
#include "../mwgui/tooltips.hpp"
34

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

    struct CustomData : public MWWorld::CustomData
    {
        MWMechanics::NpcStats mNpcStats;
43
        MWMechanics::CreatureStats mCreatureStats;
44
        MWMechanics::Movement mMovement;
45
        MWWorld::InventoryStore mInventoryStore;
46 47 48 49 50 51 52 53

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

    MWWorld::CustomData *CustomData::clone() const
    {
        return new CustomData (*this);
    }
54 55 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 87 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

    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
        for (int attribute=0; attribute<ESM::Attribute::Length; ++attribute)
        {
            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) );
        }
114

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

        int multiplier = 3;

        if (class_->mData.mSpecialization == ESM::Class::Combat)
            multiplier += 2;
        else if (class_->mData.mSpecialization == ESM::Class::Stealth)
            multiplier += 1;

        if (class_->mData.mAttribute[0] == ESM::Attribute::Endurance
            || class_->mData.mAttribute[1] == ESM::Attribute::Endurance)
            multiplier += 1;

        creatureStats.setHealth(static_cast<int> (0.5 * (strength + endurance)) + multiplier * (creatureStats.getLevel() - 1));
131
    }
132 133
}

134
namespace MWClass
135
{
136 137
    void Npc::ensureCustomData (const MWWorld::Ptr& ptr) const
    {
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
        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");
154 155 156 157 158
            fJumpEncumbranceBase = gmst.find("fJumpEncumbranceBase");
            fJumpEncumbranceMultiplier = gmst.find("fJumpEncumbranceMultiplier");
            fJumpAcrobaticsBase = gmst.find("fJumpAcrobaticsBase");
            fJumpAcroMultiplier = gmst.find("fJumpAcroMultiplier");
            fJumpRunMultiplier = gmst.find("fJumpRunMultiplier");
159
            fWereWolfRunMult = gmst.find("fWereWolfRunMult");
160 161 162

            inited = true;
        }
163 164
        if (!ptr.getRefData().getCustomData())
        {
165
            std::auto_ptr<CustomData> data(new CustomData);
166

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

169
            // NPC stats
greye's avatar
greye committed
170
            if (!ref->mBase->mFaction.empty())
171
            {
greye's avatar
greye committed
172
                std::string faction = ref->mBase->mFaction;
eduard's avatar
eduard committed
173
                Misc::StringUtils::toLower(faction);
greye's avatar
greye committed
174
                if(ref->mBase->mNpdt52.mGold != -10)
175
                {
greye's avatar
greye committed
176
                    data->mNpcStats.getFactionRanks()[faction] = (int)ref->mBase->mNpdt52.mRank;
177 178 179
                }
                else
                {
greye's avatar
greye committed
180
                    data->mNpcStats.getFactionRanks()[faction] = (int)ref->mBase->mNpdt12.mRank;
181
                }
182 183
            }

184
            // creature stats
greye's avatar
greye committed
185
            if(ref->mBase->mNpdt52.mGold != -10)
186 187
            {
                for (int i=0; i<27; ++i)
greye's avatar
greye committed
188 189 190 191 192 193 194 195 196 197 198 199 200 201 202
                    data->mNpcStats.getSkill (i).setBase (ref->mBase->mNpdt52.mSkills[i]);

                data->mCreatureStats.getAttribute(0).set (ref->mBase->mNpdt52.mStrength);
                data->mCreatureStats.getAttribute(1).set (ref->mBase->mNpdt52.mIntelligence);
                data->mCreatureStats.getAttribute(2).set (ref->mBase->mNpdt52.mWillpower);
                data->mCreatureStats.getAttribute(3).set (ref->mBase->mNpdt52.mAgility);
                data->mCreatureStats.getAttribute(4).set (ref->mBase->mNpdt52.mSpeed);
                data->mCreatureStats.getAttribute(5).set (ref->mBase->mNpdt52.mEndurance);
                data->mCreatureStats.getAttribute(6).set (ref->mBase->mNpdt52.mPersonality);
                data->mCreatureStats.getAttribute(7).set (ref->mBase->mNpdt52.mLuck);
                data->mCreatureStats.setHealth (ref->mBase->mNpdt52.mHealth);
                data->mCreatureStats.setMagicka (ref->mBase->mNpdt52.mMana);
                data->mCreatureStats.setFatigue (ref->mBase->mNpdt52.mFatigue);

                data->mCreatureStats.setLevel(ref->mBase->mNpdt52.mLevel);
scrawl's avatar
scrawl committed
203
                data->mNpcStats.setBaseDisposition(ref->mBase->mNpdt52.mDisposition);
204
                data->mNpcStats.setReputation(ref->mBase->mNpdt52.mReputation);
205 206 207
            }
            else
            {
208 209 210
                for (int i=0; i<3; ++i)
                    data->mCreatureStats.setDynamic (i, 10);

211 212 213
                data->mCreatureStats.setLevel(ref->mBase->mNpdt12.mLevel);
                data->mNpcStats.setBaseDisposition(ref->mBase->mNpdt12.mDisposition);
                data->mNpcStats.setReputation(ref->mBase->mNpdt12.mReputation);
214 215

                autoCalculateAttributes(ref->mBase, data->mCreatureStats);
216
            }
217

218 219 220 221
            data->mCreatureStats.setAiSetting (0, ref->mBase->mAiData.mHello);
            data->mCreatureStats.setAiSetting (1, ref->mBase->mAiData.mFight);
            data->mCreatureStats.setAiSetting (2, ref->mBase->mAiData.mFlee);
            data->mCreatureStats.setAiSetting (3, ref->mBase->mAiData.mAlarm);
222

223
            // spells
greye's avatar
greye committed
224 225
            for (std::vector<std::string>::const_iterator iter (ref->mBase->mSpells.mList.begin());
                iter!=ref->mBase->mSpells.mList.end(); ++iter)
226 227
                data->mCreatureStats.getSpells().add (*iter);

228
            // store
229 230 231 232
            ptr.getRefData().setCustomData (data.release());
        }
    }

Marc Zinnschlag's avatar
Marc Zinnschlag committed
233 234
    std::string Npc::getId (const MWWorld::Ptr& ptr) const
    {
235
        MWWorld::LiveCellRef<ESM::NPC> *ref =
Marc Zinnschlag's avatar
Marc Zinnschlag committed
236 237
            ptr.get<ESM::NPC>();

greye's avatar
greye committed
238
        return ref->mBase->mId;
Marc Zinnschlag's avatar
Marc Zinnschlag committed
239 240
    }

241 242 243 244 245
    void Npc::adjustPosition(const MWWorld::Ptr& ptr) const
    {
        MWBase::Environment::get().getWorld()->adjustPosition(ptr);
    }

Jason Hooks's avatar
Jason Hooks committed
246
    void Npc::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const
247
    {
Jason Hooks's avatar
Jason Hooks committed
248
        renderingInterface.getActors().insertNPC(ptr, getInventoryStore(ptr));
Jason Hooks's avatar
Jason Hooks committed
249
    }
Jason Hooks's avatar
Jason Hooks committed
250

251
    void Npc::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const
Jason Hooks's avatar
Jason Hooks committed
252
    {
253
        physics.addActor(ptr);
254
        MWBase::Environment::get().getMechanicsManager()->add(ptr);
greye's avatar
greye committed
255
    }
Jason Hooks's avatar
Jason Hooks committed
256

scrawl's avatar
scrawl committed
257 258 259 260 261 262
    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
263 264
    std::string Npc::getModel(const MWWorld::Ptr &ptr) const
    {
265
        MWWorld::LiveCellRef<ESM::NPC> *ref =
Jason Hooks's avatar
Jason Hooks committed
266
            ptr.get<ESM::NPC>();
greye's avatar
greye committed
267
        assert(ref->mBase != NULL);
268

greye's avatar
greye committed
269
        std::string headID = ref->mBase->mHead;
270

greye's avatar
greye committed
271 272 273 274
        int end = headID.find_last_of("head_") - 4;
        std::string bodyRaceID = headID.substr(0, end);

        std::string model = "meshes\\base_anim.nif";
275 276
        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
277
            model = "meshes\\base_animkna.nif";
278

greye's avatar
greye committed
279
        return model;
280 281 282

    }

Marc Zinnschlag's avatar
Marc Zinnschlag committed
283 284
    std::string Npc::getName (const MWWorld::Ptr& ptr) const
    {
285
        MWWorld::LiveCellRef<ESM::NPC> *ref =
Marc Zinnschlag's avatar
Marc Zinnschlag committed
286 287
            ptr.get<ESM::NPC>();

greye's avatar
greye committed
288
        return ref->mBase->mName;
Marc Zinnschlag's avatar
Marc Zinnschlag committed
289 290
    }

291
    MWMechanics::CreatureStats& Npc::getCreatureStats (const MWWorld::Ptr& ptr) const
292
    {
293
        ensureCustomData (ptr);
294

295
        return dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData()).mCreatureStats;
296 297
    }

Marc Zinnschlag's avatar
Marc Zinnschlag committed
298 299
    MWMechanics::NpcStats& Npc::getNpcStats (const MWWorld::Ptr& ptr) const
    {
300
        ensureCustomData (ptr);
Marc Zinnschlag's avatar
Marc Zinnschlag committed
301

302
        return dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData()).mNpcStats;
Marc Zinnschlag's avatar
Marc Zinnschlag committed
303 304
    }

Chris Robinson's avatar
Chris Robinson committed
305

306
    void Npc::hit(const MWWorld::Ptr& ptr, int type) const
Chris Robinson's avatar
Chris Robinson committed
307
    {
308 309 310
        MWBase::World *world = MWBase::Environment::get().getWorld();
        const MWWorld::Store<ESM::GameSetting> &gmst = world->getStore().get<ESM::GameSetting>();

311 312
        // Get the weapon used (if hand-to-hand, weapon = inv.end())
        MWWorld::InventoryStore &inv = getInventoryStore(ptr);
313 314 315 316
        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();
317

318 319
        float dist = 100.0f * (!weapon.isEmpty() ?
                               weapon.get<ESM::Weapon>()->mBase->mData.mReach :
320 321
                               gmst.find("fHandToHandReach")->getFloat());
        MWWorld::Ptr victim = world->getFacedObject(ptr, dist);
Chris Robinson's avatar
Chris Robinson committed
322 323 324 325 326 327 328 329 330 331
        if(victim.isEmpty()) // Didn't hit anything
            return;

        const MWWorld::Class &othercls = MWWorld::Class::get(victim);
        if(!othercls.isActor() || othercls.getCreatureStats(victim).isDead())
        {
            // Can't hit non-actors, or dead actors
            return;
        }

332
        int weapskill = ESM::Skill::HandToHand;
333 334
        if(!weapon.isEmpty())
            weapskill = MWWorld::Class::get(weapon).getEquipmentSkill(weapon);
335 336 337 338 339 340 341 342 343 344 345 346

        MWMechanics::CreatureStats &crstats = getCreatureStats(ptr);
        MWMechanics::NpcStats &npcstats = getNpcStats(ptr);
        const MWMechanics::MagicEffects &mageffects = crstats.getMagicEffects();
        float hitchance = npcstats.getSkill(weapskill).getModified() +
                          (crstats.getAttribute(ESM::Attribute::Agility).getModified() / 5.0f) +
                          (crstats.getAttribute(ESM::Attribute::Luck).getModified() / 10.0f);
        hitchance *= crstats.getFatigueTerm();
        hitchance += mageffects.get(MWMechanics::EffectKey(ESM::MagicEffect::FortifyAttack)).mMagnitude -
                     mageffects.get(MWMechanics::EffectKey(ESM::MagicEffect::Blind)).mMagnitude;
        hitchance -= othercls.getEvasion(victim);

Chris Robinson's avatar
Chris Robinson committed
347
        if((::rand()/(RAND_MAX+1.0)) > hitchance/100.0f)
348
        {
349
            othercls.onHit(victim, 0.0f, weapon, ptr, false);
350 351
            return;
        }
Chris Robinson's avatar
Chris Robinson committed
352

353 354
        float damage = 0.0f;
        if(!weapon.isEmpty())
Chris Robinson's avatar
Chris Robinson committed
355
        {
356
            const unsigned char *attack = NULL;
Chris Robinson's avatar
Chris Robinson committed
357
            if(type == MWMechanics::CreatureStats::AT_Chop)
358
                attack = weapon.get<ESM::Weapon>()->mBase->mData.mChop;
Chris Robinson's avatar
Chris Robinson committed
359
            else if(type == MWMechanics::CreatureStats::AT_Slash)
360
                attack = weapon.get<ESM::Weapon>()->mBase->mData.mSlash;
Chris Robinson's avatar
Chris Robinson committed
361
            else if(type == MWMechanics::CreatureStats::AT_Thrust)
362
                attack = weapon.get<ESM::Weapon>()->mBase->mData.mThrust;
363
            if(attack)
364
            {
365
                damage  = attack[0] + ((attack[1]-attack[0])*npcstats.getAttackStrength());
366 367 368 369 370 371
                damage *= 0.5f + (crstats.getAttribute(ESM::Attribute::Luck).getModified() / 100.0f);
                //damage *= weapon_current_health / weapon_max_health;
                if(!othercls.hasDetected(victim, ptr))
                {
                    damage *= gmst.find("fCombatCriticalStrikeMult")->getFloat();
                    MWBase::Environment::get().getWindowManager()->messageBox("#{sTargetCriticalStrike}");
372
                    MWBase::Environment::get().getSoundManager()->playSound3D(victim, "critical damage", 1.0f, 1.0f);
373
                }
374
                damage /= std::min(1.0f + othercls.getArmorRating(victim)/std::max(1.0f, damage), 4.0f);
375
            }
Chris Robinson's avatar
Chris Robinson committed
376
        }
377 378
        if(ptr.getRefData().getHandle() == "player")
            skillUsageSucceeded(ptr, weapskill, 0);
379 380

        othercls.onHit(victim, damage, weapon, ptr, true);
Chris Robinson's avatar
Chris Robinson committed
381 382
    }

383
    void Npc::onHit(const MWWorld::Ptr &ptr, float damage, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, bool successful) const
Chris Robinson's avatar
Chris Robinson committed
384
    {
385 386 387 388 389 390 391 392 393 394
        // NOTE: 'object' and/or 'attacker' may be empty.

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

            // Missed
            MWBase::Environment::get().getSoundManager()->playSound3D(ptr, "miss", 1.0f, 1.0f);
            return;
        }
Chris Robinson's avatar
Chris Robinson committed
395

396
        if(!object.isEmpty())
397
            getCreatureStats(ptr).setLastHitObject(get(object).getId(object));
398 399 400 401 402 403 404 405

        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);
        }
406 407

        if(damage > 0.0f)
Chris Robinson's avatar
Chris Robinson committed
408 409 410
        {
            // 'ptr' is losing health. Play a 'hit' voiced dialog entry if not already saying
            // something, alert the character controller, scripts, etc.
411 412

            MWBase::Environment::get().getDialogueManager()->say(ptr, "hit");
413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452

            // 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)];

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

            sndMgr->playSound3D(ptr, "Health Damage", 1.0f, 1.0f);
Chris Robinson's avatar
Chris Robinson committed
453 454
        }

455 456 457 458 459 460 461
        float health = getCreatureStats(ptr).getHealth().getCurrent() - damage;
        setActorHealth(ptr, health, attacker);
    }

    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
462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478
        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
        }
    }


479
    boost::shared_ptr<MWWorld::Action> Npc::activate (const MWWorld::Ptr& ptr,
480
        const MWWorld::Ptr& actor) const
481
    {
scrawl's avatar
scrawl committed
482
        if (MWWorld::Class::get (ptr).getCreatureStats (ptr).isDead())
483 484 485
            return boost::shared_ptr<MWWorld::Action> (new MWWorld::ActionOpen(ptr, true));
        else if (MWWorld::Class::get(actor).getStance(actor, MWWorld::Class::Sneak))
            return boost::shared_ptr<MWWorld::Action> (new MWWorld::ActionOpen(ptr)); // stealing
scrawl's avatar
scrawl committed
486 487
        else
            return boost::shared_ptr<MWWorld::Action> (new MWWorld::ActionTalk (ptr));
488
    }
Marc Zinnschlag's avatar
Marc Zinnschlag committed
489

490
    MWWorld::ContainerStore& Npc::getContainerStore (const MWWorld::Ptr& ptr)
491
        const
Marc Zinnschlag's avatar
Marc Zinnschlag committed
492
    {
493
        ensureCustomData (ptr);
Marc Zinnschlag's avatar
Marc Zinnschlag committed
494

495 496 497 498 499 500 501 502 503
        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
504 505
    }

506 507
    std::string Npc::getScript (const MWWorld::Ptr& ptr) const
    {
508
        MWWorld::LiveCellRef<ESM::NPC> *ref =
509 510
            ptr.get<ESM::NPC>();

greye's avatar
greye committed
511
        return ref->mBase->mScript;
512 513
    }

Marc Zinnschlag's avatar
Marc Zinnschlag committed
514 515 516 517 518 519 520 521
    void Npc::setForceStance (const MWWorld::Ptr& ptr, Stance stance, bool force) const
    {
        MWMechanics::NpcStats& stats = getNpcStats (ptr);

        switch (stance)
        {
            case Run:

522
                stats.setMovementFlag (MWMechanics::NpcStats::Flag_ForceRun, force);
Marc Zinnschlag's avatar
Marc Zinnschlag committed
523 524 525 526
                break;

            case Sneak:

527
                stats.setMovementFlag (MWMechanics::NpcStats::Flag_ForceSneak, force);
Marc Zinnschlag's avatar
Marc Zinnschlag committed
528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543
                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:

544
                stats.setMovementFlag (MWMechanics::NpcStats::Flag_Run, set);
545
                break;
Marc Zinnschlag's avatar
Marc Zinnschlag committed
546 547 548

            case Sneak:

549
                stats.setMovementFlag (MWMechanics::NpcStats::Flag_Sneak, set);
Marc Zinnschlag's avatar
Marc Zinnschlag committed
550 551 552 553
                break;

            case Combat:

554 555
                // 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
556 557 558 559 560 561 562 563 564 565 566 567
                break;
        }
    }

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

        switch (stance)
        {
            case Run:

568
                if (!ignoreForce && stats.getMovementFlag (MWMechanics::NpcStats::Flag_ForceRun))
569 570
                    return true;

571
                return stats.getMovementFlag (MWMechanics::NpcStats::Flag_Run);
Marc Zinnschlag's avatar
Marc Zinnschlag committed
572 573 574

            case Sneak:

575
                if (!ignoreForce && stats.getMovementFlag (MWMechanics::NpcStats::Flag_ForceSneak))
Marc Zinnschlag's avatar
Marc Zinnschlag committed
576 577
                    return true;

578
                return stats.getMovementFlag (MWMechanics::NpcStats::Flag_Sneak);
Marc Zinnschlag's avatar
Marc Zinnschlag committed
579 580 581

            case Combat:

582
                return false;
Marc Zinnschlag's avatar
Marc Zinnschlag committed
583 584 585 586 587
        }

        return false;
    }

588
    float Npc::getSpeed(const MWWorld::Ptr& ptr) const
Marc Zinnschlag's avatar
Marc Zinnschlag committed
589
    {
590 591
        const MWBase::World *world = MWBase::Environment::get().getWorld();
        const CustomData *npcdata = static_cast<const CustomData*>(ptr.getRefData().getCustomData());
592 593
        const MWMechanics::MagicEffects &mageffects = npcdata->mCreatureStats.getMagicEffects();

594 595 596 597 598 599 600 601
        const float normalizedEncumbrance = Npc::getEncumbrance(ptr) / Npc::getCapacity(ptr);

        float walkSpeed = fMinWalkSpeed->getFloat() + 0.01f*npcdata->mCreatureStats.getAttribute(ESM::Attribute::Speed).getModified()*
                                                      (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();
602

603 604
        float runSpeed = walkSpeed*(0.01f * npcdata->mNpcStats.getSkill(ESM::Skill::Athletics).getModified() *
                                    fAthleticsRunBonus->getFloat() + fBaseRunMultiplier->getFloat());
605 606
        if(npcdata->mNpcStats.isWerewolf())
            runSpeed *= fWereWolfRunMult->getFloat();
607 608

        float moveSpeed;
609
        if(normalizedEncumbrance >= 1.0f)
610
            moveSpeed = 0.0f;
611
        else if(mageffects.get(MWMechanics::EffectKey(10/*levitate*/)).mMagnitude > 0)
612 613
        {
            float flySpeed = 0.01f*(npcdata->mCreatureStats.getAttribute(ESM::Attribute::Speed).getModified() +
614
                                    mageffects.get(MWMechanics::EffectKey(10/*levitate*/)).mMagnitude);
615 616 617 618 619 620 621 622 623 624
            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;
625
            swimSpeed *= 1.0f + 0.01f * mageffects.get(MWMechanics::EffectKey(1/*swift swim*/)).mMagnitude;
626 627 628 629
            swimSpeed *= fSwimRunBase->getFloat() + 0.01f*npcdata->mNpcStats.getSkill(ESM::Skill::Athletics).getModified()*
                                                    fSwimRunAthleticsMult->getFloat();
            moveSpeed = swimSpeed;
        }
scrawl's avatar
scrawl committed
630
        else if(Npc::getStance(ptr, Run, false) && !Npc::getStance(ptr, Sneak, false))
631 632 633
            moveSpeed = runSpeed;
        else
            moveSpeed = walkSpeed;
634
        if(getMovementSettings(ptr).mPosition[0] != 0 && getMovementSettings(ptr).mPosition[1] == 0)
635 636 637
            moveSpeed *= 0.75f;

        return moveSpeed;
Marc Zinnschlag's avatar
Marc Zinnschlag committed
638 639
    }

640 641 642 643 644 645
    float Npc::getJump(const MWWorld::Ptr &ptr) const
    {
        const CustomData *npcdata = static_cast<const CustomData*>(ptr.getRefData().getCustomData());
        const MWMechanics::MagicEffects &mageffects = npcdata->mCreatureStats.getMagicEffects();
        const float encumbranceTerm = fJumpEncumbranceBase->getFloat() +
                                          fJumpEncumbranceMultiplier->getFloat() *
646
                                          (1.0f - Npc::getEncumbrance(ptr)/Npc::getCapacity(ptr));
647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670

        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());
        x += 3 * b * fJumpAcroMultiplier->getFloat();
        x += mageffects.get(MWMechanics::EffectKey(9/*jump*/)).mMagnitude * 64;
        x *= encumbranceTerm;

        if(Npc::getStance(ptr, Run, false))
            x *= fJumpRunMultiplier->getFloat();
        x *= 1.25f;//fatigueTerm;
        x -= -627.2/*gravity constant*/;
        x /= 3;

        return x;
    }

671 672
    MWMechanics::Movement& Npc::getMovementSettings (const MWWorld::Ptr& ptr) const
    {
673
        ensureCustomData (ptr);
674

675
        return dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData()).mMovement;
676 677 678 679
    }

    Ogre::Vector3 Npc::getMovementVector (const MWWorld::Ptr& ptr) const
    {
680 681 682 683 684 685
        MWMechanics::Movement &movement = getMovementSettings(ptr);
        Ogre::Vector3 vec(movement.mPosition);
        movement.mPosition[0] = 0.0f;
        movement.mPosition[1] = 0.0f;
        movement.mPosition[2] = 0.0f;
        return vec;
686
    }
687 688 689

    Ogre::Vector3 Npc::getRotationVector (const MWWorld::Ptr& ptr) const
    {
690 691 692 693 694 695
        MWMechanics::Movement &movement = getMovementSettings(ptr);
        Ogre::Vector3 vec(movement.mRotation);
        movement.mRotation[0] = 0.0f;
        movement.mRotation[1] = 0.0f;
        movement.mRotation[2] = 0.0f;
        return vec;
696 697
    }

698 699 700 701
    bool Npc::isEssential (const MWWorld::Ptr& ptr) const
    {
        MWWorld::LiveCellRef<ESM::NPC> *ref =
            ptr.get<ESM::NPC>();
702

greye's avatar
greye committed
703
        return ref->mBase->mFlags & ESM::NPC::Essential;
704 705
    }
    
706 707 708 709 710
    void Npc::registerSelf()
    {
        boost::shared_ptr<Class> instance (new Npc);
        registerClass (typeid (ESM::NPC).name(), instance);
    }
711 712 713 714 715 716 717 718

    bool Npc::hasToolTip (const MWWorld::Ptr& ptr) const
    {
        /// \todo We don't want tooltips for NPCs in combat mode.

        return true;
    }

719
    MWGui::ToolTipInfo Npc::getToolTipInfo (const MWWorld::Ptr& ptr) const
720
    {
721
        MWWorld::LiveCellRef<ESM::NPC> *ref =
722 723 724
            ptr.get<ESM::NPC>();

        MWGui::ToolTipInfo info;
greye's avatar
greye committed
725
        info.caption = ref->mBase->mName;
726 727

        std::string text;
728
        if (MWBase::Environment::get().getWindowManager()->getFullHelp())
greye's avatar
greye committed
729
            text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script");
730 731 732 733
        info.text = text;

        return info;
    }
Marc Zinnschlag's avatar
Marc Zinnschlag committed
734

scrawl's avatar
scrawl committed
735
    float Npc::getCapacity (const MWWorld::Ptr& ptr) const
Marc Zinnschlag's avatar
Marc Zinnschlag committed
736 737
    {
        const MWMechanics::CreatureStats& stats = getCreatureStats (ptr);
greye's avatar
greye committed
738
        return stats.getAttribute(0).getModified()*5;
Marc Zinnschlag's avatar
Marc Zinnschlag committed
739
    }
740 741 742 743 744 745 746

    float Npc::getEncumbrance (const MWWorld::Ptr& ptr) const
    {
        float weight = getContainerStore (ptr).getWeight();

        const MWMechanics::CreatureStats& stats = getCreatureStats (ptr);

747
        weight -= stats.getMagicEffects().get (MWMechanics::EffectKey (ESM::MagicEffect::Feather)).mMagnitude;
748

749
        weight += stats.getMagicEffects().get (MWMechanics::EffectKey (ESM::MagicEffect::Burden)).mMagnitude;
750 751 752 753 754 755

        if (weight<0)
            weight = 0;

        return weight;
    }
756 757 758 759 760 761 762 763

    bool Npc::apply (const MWWorld::Ptr& ptr, const std::string& id,
        const MWWorld::Ptr& actor) const
    {
        MWMechanics::CreatureStats& stats = getCreatureStats (ptr);

        /// \todo consider instant effects

764
        return stats.getActiveSpells().addSpell (id, actor);
765 766 767 768 769 770 771 772
    }

    void Npc::skillUsageSucceeded (const MWWorld::Ptr& ptr, int skill, int usageType) const
    {
        MWMechanics::NpcStats& stats = getNpcStats (ptr);

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

773 774 775 776
        const ESM::Class *class_ =
            MWBase::Environment::get().getWorld()->getStore().get<ESM::Class>().find (
                ref->mBase->mClass
            );
777 778 779

        stats.useSkill (skill, *class_, usageType);
    }
780

781 782
    float Npc::getArmorRating (const MWWorld::Ptr& ptr) const
    {
783 784
        const MWBase::World *world = MWBase::Environment::get().getWorld();
        const MWWorld::Store<ESM::GameSetting> &gmst = world->getStore().get<ESM::GameSetting>();
785

786 787
        MWMechanics::NpcStats &stats = getNpcStats(ptr);
        MWWorld::InventoryStore &invStore = getInventoryStore(ptr);
788 789 790 791

        int iBaseArmorSkill = gmst.find("iBaseArmorSkill")->getInt();
        float fUnarmoredBase1 = gmst.find("fUnarmoredBase1")->getFloat();
        float fUnarmoredBase2 = gmst.find("fUnarmoredBase2")->getFloat();
792
        int unarmoredSkill = stats.getSkill(ESM::Skill::Unarmored).getModified();
793

794 795
        int ratings[MWWorld::InventoryStore::Slots];
        for(int i = 0;i < MWWorld::InventoryStore::Slots;i++)
796 797 798 799 800 801 802 803 804
        {
            MWWorld::ContainerStoreIterator it = invStore.getSlot(i);
            if (it == invStore.end() || it->getTypeName() != typeid(ESM::Armor).name())
            {
                // unarmored
                ratings[i] = (fUnarmoredBase1 * unarmoredSkill) * (fUnarmoredBase2 * unarmoredSkill);
            }
            else
            {
805
                MWWorld::LiveCellRef<ESM::Armor> *ref = it->get<ESM::Armor>();
806 807

                int armorSkillType = MWWorld::Class::get(*it).getEquipmentSkill(*it);
808
                int armorSkill = stats.getSkill(armorSkillType).getModified();
809

810
                if(ref->mBase->mData.mWeight == 0)
811 812 813 814 815 816
                    ratings[i] = ref->mBase->mData.mArmor;
                else
                    ratings[i] = ref->mBase->mData.mArmor * armorSkill / iBaseArmorSkill;
            }
        }

817
        float shield = getCreatureStats(ptr).getMagicEffects().get(ESM::MagicEffect::Shield).mMagnitude;
818

819
        return ratings[MWWorld::InventoryStore::Slot_Cuirass] * 0.3f
820 821 822
                + (ratings[MWWorld::InventoryStore::Slot_CarriedLeft] + ratings[MWWorld::InventoryStore::Slot_Helmet]
                    + ratings[MWWorld::InventoryStore::Slot_Greaves] + ratings[MWWorld::InventoryStore::Slot_Boots]
                    + ratings[MWWorld::InventoryStore::Slot_LeftPauldron] + ratings[MWWorld::InventoryStore::Slot_RightPauldron]
823 824 825
                    ) * 0.1f
                + (ratings[MWWorld::InventoryStore::Slot_LeftGauntlet] + ratings[MWWorld::InventoryStore::Slot_RightGauntlet])
                    * 0.05f
826 827 828 829
                + shield;
    }


gugus's avatar
gugus committed
830 831 832 833 834
    void Npc::adjustRotation(const MWWorld::Ptr& ptr,float& x,float& y,float& z) const
    {
        y = 0;
        x = 0;
    }
835

scrawl's avatar
scrawl committed
836 837 838 839 840 841 842 843 844 845 846 847 848 849
    void Npc::adjustScale(const MWWorld::Ptr &ptr, float &scale) const
    {
        MWWorld::LiveCellRef<ESM::NPC> *ref =
            ptr.get<ESM::NPC>();

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

        if (ref->mBase->isMale())
            scale *= race->mData.mHeight.mMale;
        else
            scale *= race->mData.mHeight.mFemale;
    }

scrawl's avatar
scrawl committed
850 851 852 853 854 855 856 857 858
    int Npc::getServices(const MWWorld::Ptr &actor) const
    {
        MWWorld::LiveCellRef<ESM::NPC>* ref = actor.get<ESM::NPC>();
        if (ref->mBase->mHasAI)
            return ref->mBase->mAiData.mServices;
        else
            return 0;
    }

859 860 861 862 863 864 865 866 867 868

    std::string Npc::getSoundIdFromSndGen(const MWWorld::Ptr &ptr, const std::string &name) const
    {
        if(name == "left")
        {
            MWBase::World *world = MWBase::Environment::get().getWorld();
            Ogre::Vector3 pos(ptr.getRefData().getPosition().pos);
            if(world->isUnderwater(ptr.getCell(), pos))
                return "FootWaterLeft";
            if(world->isOnGround(ptr))
869 870 871 872 873 874 875 876 877 878 879
            {
                MWWorld::InventoryStore &inv = Npc::getInventoryStore(ptr);
                MWWorld::ContainerStoreIterator boots = inv.getSlot(MWWorld::InventoryStore::Slot_Boots);
                if(boots == inv.end() || boots->getTypeName() != typeid(ESM::Armor).name())
                    return "FootBareLeft";

                switch(Class::get(*boots).getEquipmentSkill(*boots))
                {
                    case ESM::Skill::LightArmor:
                        return "FootLightLeft";
                    case ESM::Skill::MediumArmor:
880
                        return "FootMedLeft";
881 882 883 884
                    case ESM::Skill::HeavyArmor:
                        return "FootHeavyLeft";
                }
            }
885 886 887 888 889 890 891 892 893
            return "";
        }
        if(name == "right")
        {
            MWBase::World *world = MWBase::Environment::get().getWorld();
            Ogre::Vector3 pos(ptr.getRefData().getPosition().pos);
            if(world->isUnderwater(ptr.getCell(), pos))
                return "FootWaterRight";
            if(world->isOnGround(ptr))
894 895 896 897 898 899 900 901 902 903 904
            {
                MWWorld::InventoryStore &inv = Npc::getInventoryStore(ptr);
                MWWorld::ContainerStoreIterator boots = inv.getSlot(MWWorld::InventoryStore::Slot_Boots);
                if(boots == inv.end() || boots->getTypeName() != typeid(ESM::Armor).name())
                    return "FootBareRight";

                switch(Class::get(*boots).getEquipmentSkill(*boots))
                {
                    case ESM::Skill::LightArmor:
                        return "FootLightRight";
                    case ESM::Skill::MediumArmor:
905
                        return "FootMedRight";
906 907 908 909
                    case ESM::Skill::HeavyArmor:
                        return "FootHeavyRight";
                }
            }
910 911 912 913 914 915 916 917 918 919 920
            return "";
        }
        // TODO: I have no idea what these are supposed to do for NPCs since they use
        // voiced dialog for various conditions like health loss and combat taunts. Maybe
        // only for biped creatures?
        if(name == "moan")
            return "";
        if(name == "roar")
            return "";
        if(name == "scream")
            return "";
921 922
        if(name == "land")
            return "";
923 924 925 926

        throw std::runtime_error(std::string("Unexpected soundgen type: ")+name);
    }

927
    MWWorld::Ptr
928
    Npc::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const