npc.cpp 33.4 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 372
                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}");
                }
373
                damage /= std::min(1.0f + othercls.getArmorRating(victim)/std::max(1.0f, damage), 4.0f);
374
            }
Chris Robinson's avatar
Chris Robinson committed
375
        }
376
        skillUsageSucceeded(ptr, weapskill, 0);
377 378

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

381
    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
382
    {
383 384 385 386 387 388 389 390 391 392
        // 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
393

394 395 396
        // TODO: Handle HitOnMe script function and OnPCHitMe script variable.

        if(damage > 0.0f)
Chris Robinson's avatar
Chris Robinson committed
397 398 399
        {
            // 'ptr' is losing health. Play a 'hit' voiced dialog entry if not already saying
            // something, alert the character controller, scripts, etc.
400 401

            MWBase::Environment::get().getDialogueManager()->say(ptr, "hit");
Chris Robinson's avatar
Chris Robinson committed
402 403
        }

404 405 406 407 408 409 410
        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
411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427
        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
        }
    }


428
    boost::shared_ptr<MWWorld::Action> Npc::activate (const MWWorld::Ptr& ptr,
429
        const MWWorld::Ptr& actor) const
430
    {
scrawl's avatar
scrawl committed
431
        if (MWWorld::Class::get (ptr).getCreatureStats (ptr).isDead())
432 433 434
            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
435 436
        else
            return boost::shared_ptr<MWWorld::Action> (new MWWorld::ActionTalk (ptr));
437
    }
Marc Zinnschlag's avatar
Marc Zinnschlag committed
438

439
    MWWorld::ContainerStore& Npc::getContainerStore (const MWWorld::Ptr& ptr)
440
        const
Marc Zinnschlag's avatar
Marc Zinnschlag committed
441
    {
442
        ensureCustomData (ptr);
Marc Zinnschlag's avatar
Marc Zinnschlag committed
443

444 445 446 447 448 449 450 451 452
        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
453 454
    }

455 456
    std::string Npc::getScript (const MWWorld::Ptr& ptr) const
    {
457
        MWWorld::LiveCellRef<ESM::NPC> *ref =
458 459
            ptr.get<ESM::NPC>();

greye's avatar
greye committed
460
        return ref->mBase->mScript;
461 462
    }

Marc Zinnschlag's avatar
Marc Zinnschlag committed
463 464 465 466 467 468 469 470
    void Npc::setForceStance (const MWWorld::Ptr& ptr, Stance stance, bool force) const
    {
        MWMechanics::NpcStats& stats = getNpcStats (ptr);

        switch (stance)
        {
            case Run:

471
                stats.setMovementFlag (MWMechanics::NpcStats::Flag_ForceRun, force);
Marc Zinnschlag's avatar
Marc Zinnschlag committed
472 473 474 475
                break;

            case Sneak:

476
                stats.setMovementFlag (MWMechanics::NpcStats::Flag_ForceSneak, force);
Marc Zinnschlag's avatar
Marc Zinnschlag committed
477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492
                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:

493
                stats.setMovementFlag (MWMechanics::NpcStats::Flag_Run, set);
494
                break;
Marc Zinnschlag's avatar
Marc Zinnschlag committed
495 496 497

            case Sneak:

498
                stats.setMovementFlag (MWMechanics::NpcStats::Flag_Sneak, set);
Marc Zinnschlag's avatar
Marc Zinnschlag committed
499 500 501 502
                break;

            case Combat:

503 504
                // 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
505 506 507 508 509 510 511 512 513 514 515 516
                break;
        }
    }

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

        switch (stance)
        {
            case Run:

517
                if (!ignoreForce && stats.getMovementFlag (MWMechanics::NpcStats::Flag_ForceRun))
518 519
                    return true;

520
                return stats.getMovementFlag (MWMechanics::NpcStats::Flag_Run);
Marc Zinnschlag's avatar
Marc Zinnschlag committed
521 522 523

            case Sneak:

524
                if (!ignoreForce && stats.getMovementFlag (MWMechanics::NpcStats::Flag_ForceSneak))
Marc Zinnschlag's avatar
Marc Zinnschlag committed
525 526
                    return true;

527
                return stats.getMovementFlag (MWMechanics::NpcStats::Flag_Sneak);
Marc Zinnschlag's avatar
Marc Zinnschlag committed
528 529 530

            case Combat:

531
                return false;
Marc Zinnschlag's avatar
Marc Zinnschlag committed
532 533 534 535 536
        }

        return false;
    }

537
    float Npc::getSpeed(const MWWorld::Ptr& ptr) const
Marc Zinnschlag's avatar
Marc Zinnschlag committed
538
    {
539 540
        const MWBase::World *world = MWBase::Environment::get().getWorld();
        const CustomData *npcdata = static_cast<const CustomData*>(ptr.getRefData().getCustomData());
541 542
        const MWMechanics::MagicEffects &mageffects = npcdata->mCreatureStats.getMagicEffects();

543 544 545 546 547 548 549 550
        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();
551

552 553
        float runSpeed = walkSpeed*(0.01f * npcdata->mNpcStats.getSkill(ESM::Skill::Athletics).getModified() *
                                    fAthleticsRunBonus->getFloat() + fBaseRunMultiplier->getFloat());
554 555
        if(npcdata->mNpcStats.isWerewolf())
            runSpeed *= fWereWolfRunMult->getFloat();
556 557

        float moveSpeed;
558
        if(normalizedEncumbrance >= 1.0f)
559
            moveSpeed = 0.0f;
560
        else if(mageffects.get(MWMechanics::EffectKey(10/*levitate*/)).mMagnitude > 0)
561 562
        {
            float flySpeed = 0.01f*(npcdata->mCreatureStats.getAttribute(ESM::Attribute::Speed).getModified() +
563
                                    mageffects.get(MWMechanics::EffectKey(10/*levitate*/)).mMagnitude);
564 565 566 567 568 569 570 571 572 573
            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;
574
            swimSpeed *= 1.0f + 0.01f * mageffects.get(MWMechanics::EffectKey(1/*swift swim*/)).mMagnitude;
575 576 577 578
            swimSpeed *= fSwimRunBase->getFloat() + 0.01f*npcdata->mNpcStats.getSkill(ESM::Skill::Athletics).getModified()*
                                                    fSwimRunAthleticsMult->getFloat();
            moveSpeed = swimSpeed;
        }
scrawl's avatar
scrawl committed
579
        else if(Npc::getStance(ptr, Run, false) && !Npc::getStance(ptr, Sneak, false))
580 581 582
            moveSpeed = runSpeed;
        else
            moveSpeed = walkSpeed;
583
        if(getMovementSettings(ptr).mPosition[0] != 0 && getMovementSettings(ptr).mPosition[1] == 0)
584 585 586
            moveSpeed *= 0.75f;

        return moveSpeed;
Marc Zinnschlag's avatar
Marc Zinnschlag committed
587 588
    }

589 590 591 592 593 594
    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() *
595
                                          (1.0f - Npc::getEncumbrance(ptr)/Npc::getCapacity(ptr));
596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619

        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;
    }

620 621
    MWMechanics::Movement& Npc::getMovementSettings (const MWWorld::Ptr& ptr) const
    {
622
        ensureCustomData (ptr);
623

624
        return dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData()).mMovement;
625 626 627 628
    }

    Ogre::Vector3 Npc::getMovementVector (const MWWorld::Ptr& ptr) const
    {
629 630 631 632 633 634
        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;
635
    }
636 637 638

    Ogre::Vector3 Npc::getRotationVector (const MWWorld::Ptr& ptr) const
    {
639 640 641 642 643 644
        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;
645 646
    }

647 648 649 650
    bool Npc::isEssential (const MWWorld::Ptr& ptr) const
    {
        MWWorld::LiveCellRef<ESM::NPC> *ref =
            ptr.get<ESM::NPC>();
651

greye's avatar
greye committed
652
        return ref->mBase->mFlags & ESM::NPC::Essential;
653 654
    }
    
655 656 657 658 659
    void Npc::registerSelf()
    {
        boost::shared_ptr<Class> instance (new Npc);
        registerClass (typeid (ESM::NPC).name(), instance);
    }
660 661 662 663 664 665 666 667

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

        return true;
    }

668
    MWGui::ToolTipInfo Npc::getToolTipInfo (const MWWorld::Ptr& ptr) const
669
    {
670
        MWWorld::LiveCellRef<ESM::NPC> *ref =
671 672 673
            ptr.get<ESM::NPC>();

        MWGui::ToolTipInfo info;
greye's avatar
greye committed
674
        info.caption = ref->mBase->mName;
675 676

        std::string text;
677
        if (MWBase::Environment::get().getWindowManager()->getFullHelp())
greye's avatar
greye committed
678
            text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script");
679 680 681 682
        info.text = text;

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

scrawl's avatar
scrawl committed
684
    float Npc::getCapacity (const MWWorld::Ptr& ptr) const
Marc Zinnschlag's avatar
Marc Zinnschlag committed
685 686
    {
        const MWMechanics::CreatureStats& stats = getCreatureStats (ptr);
greye's avatar
greye committed
687
        return stats.getAttribute(0).getModified()*5;
Marc Zinnschlag's avatar
Marc Zinnschlag committed
688
    }
689 690 691 692 693 694 695

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

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

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

698
        weight += stats.getMagicEffects().get (MWMechanics::EffectKey (ESM::MagicEffect::Burden)).mMagnitude;
699 700 701 702 703 704

        if (weight<0)
            weight = 0;

        return weight;
    }
705 706 707 708 709 710 711 712

    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

713
        return stats.getActiveSpells().addSpell (id, actor);
714 715 716 717 718 719 720 721
    }

    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>();

722 723 724 725
        const ESM::Class *class_ =
            MWBase::Environment::get().getWorld()->getStore().get<ESM::Class>().find (
                ref->mBase->mClass
            );
726 727 728

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

730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778
    float Npc::getArmorRating (const MWWorld::Ptr& ptr) const
    {
        MWWorld::InventoryStore& invStore = MWWorld::Class::get(ptr).getInventoryStore(ptr);
        const MWWorld::Store<ESM::GameSetting> &gmst =
            MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();

        int ratings[MWWorld::InventoryStore::Slots];

        int iBaseArmorSkill = gmst.find("iBaseArmorSkill")->getInt();
        float fUnarmoredBase1 = gmst.find("fUnarmoredBase1")->getFloat();
        float fUnarmoredBase2 = gmst.find("fUnarmoredBase2")->getFloat();
        int unarmoredSkill = MWWorld::Class::get(ptr).getNpcStats(ptr).getSkill(ESM::Skill::Unarmored).getModified();

        for (int i = 0; i < MWWorld::InventoryStore::Slots; ++i)
        {
            MWWorld::ContainerStoreIterator it = invStore.getSlot(i);
            if (it == invStore.end() || it->getTypeName() != typeid(ESM::Armor).name())
            {
                // unarmored
                ratings[i] = (fUnarmoredBase1 * unarmoredSkill) * (fUnarmoredBase2 * unarmoredSkill);
            }
            else
            {
                MWWorld::LiveCellRef<ESM::Armor> *ref =
                    it->get<ESM::Armor>();

                int armorSkillType = MWWorld::Class::get(*it).getEquipmentSkill(*it);
                int armorSkill = MWWorld::Class::get(ptr).getNpcStats(ptr).getSkill(armorSkillType).getModified();

                if (ref->mBase->mData.mWeight == 0)
                    ratings[i] = ref->mBase->mData.mArmor;
                else
                    ratings[i] = ref->mBase->mData.mArmor * armorSkill / iBaseArmorSkill;
            }
        }

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

        return ratings[MWWorld::InventoryStore::Slot_Cuirass] * 0.3
                + (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]
                    ) * 0.1
                + (ratings[MWWorld::InventoryStore::Slot_LeftGauntlet] + MWWorld::InventoryStore::Slot_RightGauntlet)
                    * 0.05
                + shield;
    }


gugus's avatar
gugus committed
779 780 781 782 783
    void Npc::adjustRotation(const MWWorld::Ptr& ptr,float& x,float& y,float& z) const
    {
        y = 0;
        x = 0;
    }
784

scrawl's avatar
scrawl committed
785 786 787 788 789 790 791 792 793 794 795 796 797 798
    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
799 800 801 802 803 804 805 806 807
    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;
    }

808 809 810 811 812 813 814 815 816 817

    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))
818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833
            {
                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:
                        return "FootMediumLeft";
                    case ESM::Skill::HeavyArmor:
                        return "FootHeavyLeft";
                }
            }
834 835 836 837 838 839 840 841 842
            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))
843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858
            {
                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:
                        return "FootMediumRight";
                    case ESM::Skill::HeavyArmor:
                        return "FootHeavyRight";
                }
            }
859 860 861 862 863 864 865 866 867 868 869
            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 "";
870 871
        if(name == "land")
            return "";
872 873 874 875

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

876
    MWWorld::Ptr
877
    Npc::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const
878 879 880 881
    {
        MWWorld::LiveCellRef<ESM::NPC> *ref =
            ptr.get<ESM::NPC>();

greye's avatar
greye committed
882
        return MWWorld::Ptr(&cell.mNpcs.insert(*ref), &cell);
883
    }
884 885 886 887 888 889 890 891 892 893 894

    const ESM::GameSetting *Npc::fMinWalkSpeed;
    const ESM::GameSetting *Npc::fMaxWalkSpeed;
    const ESM::GameSetting *Npc::fEncumberedMoveEffect;
    const ESM::GameSetting *Npc::fSneakSpeedMultiplier;
    const ESM::GameSetting *Npc::fAthleticsRunBonus;
    const ESM::GameSetting *Npc::fBaseRunMultiplier;
    const ESM::GameSetting *Npc::fMinFlySpeed;
    const ESM::GameSetting *Npc::fMaxFlySpeed;
    const ESM::GameSetting *Npc::fSwimRunBase;
    const ESM::GameSetting *Npc::fSwimRunAthleticsMult;
895 896 897 898 899
    const ESM::GameSetting *Npc::fJumpEncumbranceBase;
    const ESM::GameSetting *Npc::fJumpEncumbranceMultiplier;
    const ESM::GameSetting *Npc::fJumpAcrobaticsBase;
    const ESM::GameSetting *Npc::fJumpAcroMultiplier;
    const ESM::GameSetting *Npc::fJumpRunMultiplier;
900
    const ESM::GameSetting *Npc::fWereWolfRunMult;
901
}