character.cpp 118 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
 * OpenMW - The completely unofficial reimplementation of Morrowind
 *
 * This file (character.cpp) is part of the OpenMW package.
 *
 * OpenMW is distributed as free software: you can redistribute it
 * and/or modify it under the terms of the GNU General Public License
 * version 3, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * version 3 along with this program. If not, see
17
 * https://www.gnu.org/licenses/ .
18
19
20
21
 */

#include "character.hpp"

scrawl's avatar
scrawl committed
22
23
#include <iostream>

24
#include <components/misc/mathutil.hpp>
scrawl's avatar
scrawl committed
25
#include <components/misc/rng.hpp>
Alexei Kotov's avatar
Alexei Kotov committed
26
#include <components/misc/stringops.hpp>
dteviot's avatar
dteviot committed
27

scrawl's avatar
scrawl committed
28
29
#include <components/settings/settings.hpp>

30
31
#include <components/sceneutil/positionattitudetransform.hpp>

32
#include "../mwrender/animation.hpp"
33

34
#include "../mwbase/environment.hpp"
Andrei Kortunov's avatar
Andrei Kortunov committed
35
#include "../mwbase/mechanicsmanager.hpp"
36
#include "../mwbase/world.hpp"
37
#include "../mwbase/soundmanager.hpp"
38
#include "../mwbase/windowmanager.hpp"
39

40
#include "../mwworld/class.hpp"
41
#include "../mwworld/inventorystore.hpp"
42
#include "../mwworld/esmstore.hpp"
43
#include "../mwworld/player.hpp"
44

45
#include "aicombataction.hpp"
Allofich's avatar
Allofich committed
46
47
48
49
50
#include "movement.hpp"
#include "npcstats.hpp"
#include "creaturestats.hpp"
#include "security.hpp"
#include "actorutil.hpp"
51
#include "spellcasting.hpp"
Allofich's avatar
Allofich committed
52

53
54
55
namespace
{

56
std::string getBestAttack (const ESM::Weapon* weapon)
57
58
59
60
{
    int slash = (weapon->mData.mSlash[0] + weapon->mData.mSlash[1])/2;
    int chop = (weapon->mData.mChop[0] + weapon->mData.mChop[1])/2;
    int thrust = (weapon->mData.mThrust[0] + weapon->mData.mThrust[1])/2;
61
    if (slash == chop && slash == thrust)
62
        return "slash";
63
    else if (thrust >= chop && thrust >= slash)
64
        return "thrust";
65
66
67
68
    else if (slash >= chop && slash >= thrust)
        return "slash";
    else
        return "chop";
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
// Converts a movement Run state to its equivalent Walk state.
MWMechanics::CharacterState runStateToWalkState (MWMechanics::CharacterState state)
{
    using namespace MWMechanics;
    CharacterState ret = state;
    switch (state)
    {
        case CharState_RunForward:
            ret = CharState_WalkForward;
            break;
        case CharState_RunBack:
            ret = CharState_WalkBack;
            break;
        case CharState_RunLeft:
            ret = CharState_WalkLeft;
            break;
        case CharState_RunRight:
            ret = CharState_WalkRight;
            break;
        case CharState_SwimRunForward:
            ret = CharState_SwimWalkForward;
            break;
        case CharState_SwimRunBack:
            ret = CharState_SwimWalkBack;
            break;
        case CharState_SwimRunLeft:
            ret = CharState_SwimWalkLeft;
            break;
        case CharState_SwimRunRight:
            ret = CharState_SwimWalkRight;
            break;
        default:
            break;
    }
    return ret;
}

108
109
110
111
112
float getFallDamage(const MWWorld::Ptr& ptr, float fallHeight)
{
    MWBase::World *world = MWBase::Environment::get().getWorld();
    const MWWorld::Store<ESM::GameSetting> &store = world->getStore().get<ESM::GameSetting>();

113
    const float fallDistanceMin = store.find("fFallDamageDistanceMin")->mValue.getFloat();
114
115
116

    if (fallHeight >= fallDistanceMin)
    {
117
        const float acrobaticsSkill = static_cast<float>(ptr.getClass().getSkill(ptr, ESM::Skill::Acrobatics));
118
        const float jumpSpellBonus = ptr.getClass().getCreatureStats(ptr).getMagicEffects().get(ESM::MagicEffect::Jump).getMagnitude();
119
120
121
122
        const float fallAcroBase = store.find("fFallAcroBase")->mValue.getFloat();
        const float fallAcroMult = store.find("fFallAcroMult")->mValue.getFloat();
        const float fallDistanceBase = store.find("fFallDistanceBase")->mValue.getFloat();
        const float fallDistanceMult = store.find("fFallDistanceMult")->mValue.getFloat();
123
124

        float x = fallHeight - fallDistanceMin;
125
        x -= (1.5f * acrobaticsSkill) + jumpSpellBonus;
126
127
128
129
130
131
132
133
134
135
136
        x = std::max(0.0f, x);

        float a = fallAcroBase + fallAcroMult * (100 - acrobaticsSkill);
        x = fallDistanceBase + fallDistanceMult * x;
        x *= a;

        return x;
    }
    return 0.f;
}

137
}
138

139
140
141
namespace MWMechanics
{

142
struct StateInfo {
143
144
    CharacterState state;
    const char groupname[32];
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
};

static const StateInfo sMovementList[] = {
    { CharState_WalkForward, "walkforward" },
    { CharState_WalkBack, "walkback" },
    { CharState_WalkLeft, "walkleft" },
    { CharState_WalkRight, "walkright" },

    { CharState_SwimWalkForward, "swimwalkforward" },
    { CharState_SwimWalkBack, "swimwalkback" },
    { CharState_SwimWalkLeft, "swimwalkleft" },
    { CharState_SwimWalkRight, "swimwalkright" },

    { CharState_RunForward, "runforward" },
    { CharState_RunBack, "runback" },
    { CharState_RunLeft, "runleft" },
    { CharState_RunRight, "runright" },

    { CharState_SwimRunForward, "swimrunforward" },
    { CharState_SwimRunBack, "swimrunback" },
    { CharState_SwimRunLeft, "swimrunleft" },
    { CharState_SwimRunRight, "swimrunright" },

    { CharState_SneakForward, "sneakforward" },
    { CharState_SneakBack, "sneakback" },
    { CharState_SneakLeft, "sneakleft" },
    { CharState_SneakRight, "sneakright" },

Chris Robinson's avatar
Chris Robinson committed
173
174
    { CharState_Jump, "jump" },

175
176
    { CharState_TurnLeft, "turnleft" },
    { CharState_TurnRight, "turnright" },
177
178
    { CharState_SwimTurnLeft, "swimturnleft" },
    { CharState_SwimTurnRight, "swimturnright" },
179
180
181
182
};
static const StateInfo *sMovementListEnd = &sMovementList[sizeof(sMovementList)/sizeof(sMovementList[0])];


183
184
185
186
187
188
189
190
191
192
193
class FindCharState {
    CharacterState state;

public:
    FindCharState(CharacterState _state) : state(_state) { }

    bool operator()(const StateInfo &info) const
    { return info.state == state; }
};


194
std::string CharacterController::chooseRandomGroup (const std::string& prefix, int* num) const
195
196
{
    int numAnims=0;
Alexei Kotov's avatar
Alexei Kotov committed
197
    while (mAnimation->hasAnimation(prefix + std::to_string(numAnims+1)))
198
199
        ++numAnims;

scrawl's avatar
scrawl committed
200
    int roll = Misc::Rng::rollDice(numAnims) + 1; // [1, numAnims]
201
202
    if (num)
        *num = roll;
Alexei Kotov's avatar
Alexei Kotov committed
203
    return prefix + std::to_string(roll);
204
}
205

206
void CharacterController::refreshHitRecoilAnims(CharacterState& idle)
207
{
208
209
210
    bool recovery = mPtr.getClass().getCreatureStats(mPtr).getHitRecovery();
    bool knockdown = mPtr.getClass().getCreatureStats(mPtr).getKnockedDown();
    bool block = mPtr.getClass().getCreatureStats(mPtr).getBlock();
211
    bool isSwimming = MWBase::Environment::get().getWorld()->isSwimming(mPtr);
212
    if(mHitState == CharState_None)
mrcheko's avatar
mrcheko committed
213
    {
214
        if ((mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() < 0
215
                || mPtr.getClass().getCreatureStats(mPtr).getFatigue().getBase() == 0))
mrcheko's avatar
mrcheko committed
216
        {
rexelion's avatar
rexelion committed
217
            mTimeUntilWake = Misc::Rng::rollClosedProbability() * 2 + 1; // Wake up after 1 to 3 seconds
218
219
220
221
            if (isSwimming && mAnimation->hasAnimation("swimknockout"))
            {
                mHitState = CharState_SwimKnockOut;
                mCurrentHit = "swimknockout";
222
                mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, false, 1, "start", "stop", 0.0f, ~0ul);
223
            }
224
            else if (!isSwimming && mAnimation->hasAnimation("knockout"))
225
226
227
            {
                mHitState = CharState_KnockOut;
                mCurrentHit = "knockout";
228
229
230
231
232
233
                mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, false, 1, "start", "stop", 0.0f, ~0ul);
            }
            else
            {
                // Knockout animations are missing. Fall back to idle animation, so target actor still can be killed via HtH.
                mCurrentHit.erase();
234
235
            }

236
237
            mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(true);
        }
238
        else if (knockdown)
239
        {
240
241
242
243
            if (isSwimming && mAnimation->hasAnimation("swimknockdown"))
            {
                mHitState = CharState_SwimKnockDown;
                mCurrentHit = "swimknockdown";
244
                mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0);
245
            }
246
            else if (!isSwimming && mAnimation->hasAnimation("knockdown"))
247
248
249
            {
                mHitState = CharState_KnockDown;
                mCurrentHit = "knockdown";
250
251
252
253
254
255
                mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0);
            }
            else
            {
                // Knockdown animation is missing. Cancel knockdown state.
                mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(false);
256
            }
257
258
259
        }
        else if (recovery)
        {
Evgeny Kurnevsky's avatar
Evgeny Kurnevsky committed
260
            std::string anim = chooseRandomGroup("swimhit");
Andrei Kortunov's avatar
Andrei Kortunov committed
261
            if (isSwimming && mAnimation->hasAnimation(anim))
262
            {
Andrei Kortunov's avatar
Andrei Kortunov committed
263
                mHitState = CharState_SwimHit;
264
265
                mCurrentHit = anim;
                mAnimation->play(mCurrentHit, Priority_Hit, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0);
266
            }
Andrei Kortunov's avatar
Andrei Kortunov committed
267
268
269
270
271
272
273
274
275
276
            else
            {
                anim = chooseRandomGroup("hit");
                if (mAnimation->hasAnimation(anim))
                {
                    mHitState = CharState_Hit;
                    mCurrentHit = anim;
                    mAnimation->play(mCurrentHit, Priority_Hit, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0);
                }
            }
mrcheko's avatar
mrcheko committed
277
        }
278
        else if (block && mAnimation->hasAnimation("shield"))
mrcheko's avatar
mrcheko committed
279
        {
280
281
282
283
            mHitState = CharState_Block;
            mCurrentHit = "shield";
            MWRender::Animation::AnimPriority priorityBlock (Priority_Hit);
            priorityBlock[MWRender::Animation::BoneGroup_LeftArm] = Priority_Block;
284
            priorityBlock[MWRender::Animation::BoneGroup_LowerBody] = Priority_WeaponLowerBody;
285
            mAnimation->play(mCurrentHit, priorityBlock, MWRender::Animation::BlendMask_All, true, 1, "block start", "block stop", 0.0f, 0);
mrcheko's avatar
mrcheko committed
286
        }
287
288

        // Cancel upper body animations
289
        if (isKnockedOut() || isKnockedDown())
290
        {
291
292
293
294
            if (mUpperBodyState > UpperCharState_WeapEquiped)
            {
                mAnimation->disable(mCurrentWeapon);
                mUpperBodyState = UpperCharState_WeapEquiped;
295
                if (mWeaponType > ESM::Weapon::None)
296
                    mAnimation->showWeapons(true);
297
298
299
300
301
302
            }
            else if (mUpperBodyState > UpperCharState_Nothing && mUpperBodyState < UpperCharState_WeapEquiped)
            {
                mAnimation->disable(mCurrentWeapon);
                mUpperBodyState = UpperCharState_Nothing;
            }
303
        }
304
305
        if (mHitState != CharState_None)
            idle = CharState_None;
mrcheko's avatar
mrcheko committed
306
    }
307
308
309
310
311
312
313
314
315
316
317
    else if(!mAnimation->isPlaying(mCurrentHit))
    {
        mCurrentHit.erase();
        if (knockdown)
            mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(false);
        if (recovery)
            mPtr.getClass().getCreatureStats(mPtr).setHitRecovery(false);
        if (block)
            mPtr.getClass().getCreatureStats(mPtr).setBlock(false);
        mHitState = CharState_None;
    }
318
    else if (isKnockedOut() && mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() > 0 
rexelion's avatar
rexelion committed
319
            && mTimeUntilWake <= 0)
320
    {
321
        mHitState = isSwimming ? CharState_SwimKnockDown : CharState_KnockDown;
322
323
324
325
        mAnimation->disable(mCurrentHit);
        mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "loop stop", "stop", 0.0f, 0);
    }
}
mrcheko's avatar
mrcheko committed
326

327
void CharacterController::refreshJumpAnims(const std::string& weapShortGroup, JumpingState jump, CharacterState& idle, bool force)
328
{
Alexei Kotov's avatar
Alexei Kotov committed
329
    if (!force && jump == mJumpState && idle == CharState_None)
330
        return;
331

332
333
334
335
336
    std::string jumpAnimName;
    MWRender::Animation::BlendMask jumpmask = MWRender::Animation::BlendMask_All;
    if (jump != JumpState_None)
    {
        jumpAnimName = "jump";
337
        if(!weapShortGroup.empty())
Chris Robinson's avatar
Chris Robinson committed
338
        {
339
            jumpAnimName += weapShortGroup;
340
            if(!mAnimation->hasAnimation(jumpAnimName))
Chris Robinson's avatar
Chris Robinson committed
341
            {
342
                jumpAnimName = fallbackShortWeaponGroup("jump", &jumpmask);
343

344
                // If we apply jump only for lower body, do not reset idle animations.
345
                // For upper body there will be idle animation.
346
                if (jumpmask == MWRender::Animation::BlendMask_LowerBody && idle == CharState_None)
347
                    idle = CharState_Idle;
Chris Robinson's avatar
Chris Robinson committed
348
349
            }
        }
350
    }
Chris Robinson's avatar
Chris Robinson committed
351

352
353
354
    if (!force && jump == mJumpState)
        return;

Alexei Kotov's avatar
Alexei Kotov committed
355
    bool startAtLoop = (jump == mJumpState);
356
    mJumpState = jump;
357

358
359
360
361
362
363
364
365
366
    if (!mCurrentJump.empty())
    {
        mAnimation->disable(mCurrentJump);
        mCurrentJump.clear();
    }

    if(mJumpState == JumpState_InAir)
    {
        if (mAnimation->hasAnimation(jumpAnimName))
367
        {
368
            mAnimation->play(jumpAnimName, Priority_Jump, jumpmask, false,
Alexei Kotov's avatar
Alexei Kotov committed
369
                         1.0f, startAtLoop ? "loop start" : "start", "stop", 0.f, ~0ul);
370
            mCurrentJump = jumpAnimName;
Chris Robinson's avatar
Chris Robinson committed
371
        }
372
373
374
375
    }
    else if (mJumpState == JumpState_Landing)
    {
        if (mAnimation->hasAnimation(jumpAnimName))
Chris Robinson's avatar
Chris Robinson committed
376
        {
377
378
379
            mAnimation->play(jumpAnimName, Priority_Jump, jumpmask, true,
                         1.0f, "loop stop", "stop", 0.0f, 0);
            mCurrentJump = jumpAnimName;
Miloslav Číž's avatar
Miloslav Číž committed
380
        }
Chris Robinson's avatar
Chris Robinson committed
381
    }
382
}
Chris Robinson's avatar
Chris Robinson committed
383

384
385
bool CharacterController::onOpen()
{
Bo Svensson's avatar
Bo Svensson committed
386
    if (mPtr.getType() == ESM::Container::sRecordId)
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
    {
        if (!mAnimation->hasAnimation("containeropen"))
            return true;

        if (mAnimation->isPlaying("containeropen"))
            return false;

        if (mAnimation->isPlaying("containerclose"))
            return false;

        mAnimation->play("containeropen", Priority_Persistent, MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", 0.f, 0);
        if (mAnimation->isPlaying("containeropen"))
            return false;
    }

    return true;
}

void CharacterController::onClose()
{
Bo Svensson's avatar
Bo Svensson committed
407
    if (mPtr.getType() == ESM::Container::sRecordId)
408
409
410
411
412
413
414
415
416
417
418
419
420
    {
        if (!mAnimation->hasAnimation("containerclose"))
            return;

        float complete, startPoint = 0.f;
        bool animPlaying = mAnimation->getInfo("containeropen", &complete);
        if (animPlaying)
            startPoint = 1.f - complete;

        mAnimation->play("containerclose", Priority_Persistent, MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", startPoint, 0);
    }
}

421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
std::string CharacterController::getWeaponAnimation(int weaponType) const
{
    std::string weaponGroup = getWeaponType(weaponType)->mLongGroup;
    bool isRealWeapon = weaponType != ESM::Weapon::HandToHand && weaponType != ESM::Weapon::Spell && weaponType != ESM::Weapon::None;
    if (isRealWeapon && !mAnimation->hasAnimation(weaponGroup))
    {
        static const std::string oneHandFallback = getWeaponType(ESM::Weapon::LongBladeOneHand)->mLongGroup;
        static const std::string twoHandFallback = getWeaponType(ESM::Weapon::LongBladeTwoHand)->mLongGroup;

        const ESM::WeaponType* weapInfo = getWeaponType(weaponType);

        // For real two-handed melee weapons use 2h swords animations as fallback, otherwise use the 1h ones
        if (weapInfo->mFlags & ESM::WeaponType::TwoHanded && weapInfo->mWeaponClass == ESM::WeaponType::Melee)
            weaponGroup = twoHandFallback;
        else if (isRealWeapon)
            weaponGroup = oneHandFallback;
    }

    return weaponGroup;
}

442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
std::string CharacterController::fallbackShortWeaponGroup(const std::string& baseGroupName, MWRender::Animation::BlendMask* blendMask)
{
    bool isRealWeapon = mWeaponType != ESM::Weapon::HandToHand && mWeaponType != ESM::Weapon::Spell && mWeaponType != ESM::Weapon::None;
    if (!isRealWeapon)
    {
        if (blendMask != nullptr)
            *blendMask = MWRender::Animation::BlendMask_LowerBody;

        return baseGroupName;
    }

    static const std::string oneHandFallback = getWeaponType(ESM::Weapon::LongBladeOneHand)->mShortGroup;
    static const std::string twoHandFallback = getWeaponType(ESM::Weapon::LongBladeTwoHand)->mShortGroup;

    std::string groupName = baseGroupName;
    const ESM::WeaponType* weapInfo = getWeaponType(mWeaponType);

    // For real two-handed melee weapons use 2h swords animations as fallback, otherwise use the 1h ones
Andrei Kortunov's avatar
Andrei Kortunov committed
460
    if (weapInfo->mFlags & ESM::WeaponType::TwoHanded && weapInfo->mWeaponClass == ESM::WeaponType::Melee)
461
        groupName += twoHandFallback;
Andrei Kortunov's avatar
Andrei Kortunov committed
462
    else
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
        groupName += oneHandFallback;

    // Special case for crossbows - we shouls apply 1h animations a fallback only for lower body
    if (mWeaponType == ESM::Weapon::MarksmanCrossbow && blendMask != nullptr)
        *blendMask = MWRender::Animation::BlendMask_LowerBody;

    if (!mAnimation->hasAnimation(groupName))
    {
        groupName = baseGroupName;
        if (blendMask != nullptr)
            *blendMask = MWRender::Animation::BlendMask_LowerBody;
    }

    return groupName;
}

479
void CharacterController::refreshMovementAnims(const std::string& weapShortGroup, CharacterState movement, CharacterState& idle, bool force)
480
{
481
482
483
    if (movement == mMovementState && idle == mIdleState && !force)
        return;

484
485
486
487
488
    // Reset idle if we actually play movement animations excepts of these cases:
    // 1. When we play turning animations
    // 2. When we use a fallback animation for lower body since movement animation for given weapon is missing (e.g. for crossbows and spellcasting)
    bool resetIdle = (movement != CharState_None && !isTurning());

489
490
491
    std::string movementAnimName;
    MWRender::Animation::BlendMask movemask;
    const StateInfo *movestate;
492
493
494
495

    movemask = MWRender::Animation::BlendMask_All;
    movestate = std::find_if(sMovementList, sMovementListEnd, FindCharState(movement));
    if(movestate != sMovementListEnd)
496
    {
497
        movementAnimName = movestate->groupname;
498
        if(!weapShortGroup.empty())
499
        {
500
501
502
            std::string::size_type swimpos = movementAnimName.find("swim");
            if (swimpos == std::string::npos)
            {
503
504
                if (mWeaponType == ESM::Weapon::Spell && (movement == CharState_TurnLeft || movement == CharState_TurnRight)) // Spellcasting stance turning is a special case
                    movementAnimName = weapShortGroup + movementAnimName;
505
                else
506
                    movementAnimName += weapShortGroup;
507
            }
508
509

            if(!mAnimation->hasAnimation(movementAnimName))
510
            {
511
                movementAnimName = movestate->groupname;
512
513
                if (swimpos == std::string::npos)
                {
514
515
516
                    movementAnimName = fallbackShortWeaponGroup(movementAnimName, &movemask);

                    // If we apply movement only for lower body, do not reset idle animations.
517
                    // For upper body there will be idle animation.
518
                    if (movemask == MWRender::Animation::BlendMask_LowerBody && idle == CharState_None)
519
                        idle = CharState_Idle;
520
521
522

                    if (movemask == MWRender::Animation::BlendMask_LowerBody)
                        resetIdle = false;
523
                }
524
            }
525
526
        }
    }
527

528
529
530
531
532
    if(force || movement != mMovementState)
    {
        mMovementState = movement;
        if(movestate != sMovementListEnd)
        {
533
            if(!mAnimation->hasAnimation(movementAnimName))
534
            {
535
                std::string::size_type swimpos = movementAnimName.find("swim");
536
                if (swimpos != std::string::npos)
537
                {
538
                    movementAnimName.erase(swimpos, 4);
539
                    if (!weapShortGroup.empty())
540
                    {
541
                        std::string weapMovementAnimName = movementAnimName + weapShortGroup;
542
543
544
                        if(mAnimation->hasAnimation(weapMovementAnimName))
                            movementAnimName = weapMovementAnimName;
                        else
545
                        {
546
                            movementAnimName = fallbackShortWeaponGroup(movementAnimName, &movemask);
547
548
549
                            if (movemask == MWRender::Animation::BlendMask_LowerBody)
                                resetIdle = false;
                        }
550
                    }
551
                }
552

553
554
555
556
557
558
559
560
561
562
                if (swimpos == std::string::npos || !mAnimation->hasAnimation(movementAnimName))
                {
                    std::string::size_type runpos = movementAnimName.find("run");
                    if (runpos != std::string::npos)
                    {
                        movementAnimName.replace(runpos, runpos+3, "walk");
                        if (!mAnimation->hasAnimation(movementAnimName))
                            movementAnimName.clear();
                    }
                    else
563
                        movementAnimName.clear();
564
565
566
567
                }
            }
        }

568
        // If we're playing the same animation, start it from the point it ended
569
570
571
        float startpoint = 0.f;
        if (!mCurrentMovement.empty() && movementAnimName == mCurrentMovement)
            mAnimation->getInfo(mCurrentMovement, &startpoint);
572

573
574
        mMovementAnimationControlled = true;

575
        mAnimation->disable(mCurrentMovement);
576
577
578
579

        if (!mAnimation->hasAnimation(movementAnimName))
            movementAnimName.clear();

580
        mCurrentMovement = movementAnimName;
581
        if(!mCurrentMovement.empty())
582
        {
Alexei Kotov's avatar
Alexei Kotov committed
583
584
585
586
587
588
589
            if (resetIdle)
            {
                mAnimation->disable(mCurrentIdle);
                mIdleState = CharState_None;
                idle = CharState_None;
            }

590
591
592
            // For non-flying creatures, MW uses the Walk animation to calculate the animation velocity
            // even if we are running. This must be replicated, otherwise the observed speed would differ drastically.
            std::string anim = mCurrentMovement;
593
            mAdjustMovementAnimSpeed = true;
Bo Svensson's avatar
Bo Svensson committed
594
            if (mPtr.getClass().getType() == ESM::Creature::sRecordId
595
596
597
598
599
600
                    && !(mPtr.get<ESM::Creature>()->mBase->mFlags & ESM::Creature::Flies))
            {
                CharacterState walkState = runStateToWalkState(mMovementState);
                const StateInfo *stateinfo = std::find_if(sMovementList, sMovementListEnd, FindCharState(walkState));
                anim = stateinfo->groupname;

601
602
603
                mMovementAnimSpeed = mAnimation->getVelocity(anim);
                if (mMovementAnimSpeed <= 1.0f)
                {
604
605
606
607
608
                    // Another bug: when using a fallback animation (e.g. RunForward as fallback to SwimRunForward),
                    // then the equivalent Walk animation will not use a fallback, and if that animation doesn't exist
                    // we will play without any scaling.
                    // Makes the speed attribute of most water creatures totally useless.
                    // And again, this can not be fixed without patching game data.
609
610
611
                    mAdjustMovementAnimSpeed = false;
                    mMovementAnimSpeed = 1.f;
                }
scrawl's avatar
scrawl committed
612
            }
613
            else
614
            {
615
616
617
                mMovementAnimSpeed = mAnimation->getVelocity(anim);

                if (mMovementAnimSpeed <= 1.0f)
618
619
620
621
                {
                    // The first person anims don't have any velocity to calculate a speed multiplier from.
                    // We use the third person velocities instead.
                    // FIXME: should be pulled from the actual animation, but it is not presently loaded.
622
623
624
                    bool sneaking = mMovementState == CharState_SneakForward || mMovementState == CharState_SneakBack
                                 || mMovementState == CharState_SneakLeft    || mMovementState == CharState_SneakRight;
                    mMovementAnimSpeed = (sneaking ? 33.5452f : (isRunning() ? 222.857f : 154.064f));
625
626
                    mMovementAnimationControlled = false;
                }
627
            }
628

629
            mAnimation->play(mCurrentMovement, Priority_Movement, movemask, false,
630
                             1.f, "start", "stop", startpoint, ~0ul, true);
631
        }
632
633
        else
            mMovementState = CharState_None;
634
    }
635
}
636

637
void CharacterController::refreshIdleAnims(const std::string& weapShortGroup, CharacterState idle, bool force)
638
{
639
640
641
642
643
644
645
646
647
    // FIXME: if one of the below states is close to their last animation frame (i.e. will be disabled in the coming update),
    // the idle animation should be displayed
    if (((mUpperBodyState != UpperCharState_Nothing && mUpperBodyState != UpperCharState_WeapEquiped)
            || (mMovementState != CharState_None && !isTurning())
            || mHitState != CharState_None)
            && !mPtr.getClass().isBipedal(mPtr))
        idle = CharState_None;

    if(force || idle != mIdleState || (!mAnimation->isPlaying(mCurrentIdle) && mAnimQueue.empty()))
648
649
    {
        mIdleState = idle;
650
        size_t numLoops = ~0ul;
651

Allofich's avatar
Allofich committed
652
        std::string idleGroup;
653
        MWRender::Animation::AnimPriority idlePriority (Priority_Default);
654
655
656
        // Only play "idleswim" or "idlesneak" if they exist. Otherwise, fallback to
        // "idle"+weapon or "idle".
        if(mIdleState == CharState_IdleSwim && mAnimation->hasAnimation("idleswim"))
657
        {
Allofich's avatar
Allofich committed
658
            idleGroup = "idleswim";
659
660
            idlePriority = Priority_SwimIdle;
        }
661
        else if(mIdleState == CharState_IdleSneak && mAnimation->hasAnimation("idlesneak"))
662
        {
Allofich's avatar
Allofich committed
663
            idleGroup = "idlesneak";
664
665
            idlePriority[MWRender::Animation::BoneGroup_LowerBody] = Priority_SneakIdleLowerBody;
        }
666
667
        else if(mIdleState != CharState_None)
        {
Allofich's avatar
Allofich committed
668
            idleGroup = "idle";
669
            if(!weapShortGroup.empty())
670
            {
671
                idleGroup += weapShortGroup;
Allofich's avatar
Allofich committed
672
                if(!mAnimation->hasAnimation(idleGroup))
673
674
675
                {
                    idleGroup = fallbackShortWeaponGroup("idle");
                }
Allofich's avatar
Allofich committed
676
677
678
679

                // play until the Loop Stop key 2 to 5 times, then play until the Stop key
                // this replicates original engine behavior for the "Idle1h" 1st-person animation
                numLoops = 1 + Misc::Rng::rollDice(4); 
680
            }
681
682
        }

683
684
685
686
687
688
689
690
        // There is no need to restart anim if the new and old anims are the same.
        // Just update a number of loops.
        float startPoint = 0;
        if (!mCurrentIdle.empty() && mCurrentIdle == idleGroup)
        {
            mAnimation->getInfo(mCurrentIdle, &startPoint);
        }

691
692
693
        if(!mCurrentIdle.empty())
            mAnimation->disable(mCurrentIdle);

Allofich's avatar
Allofich committed
694
        mCurrentIdle = idleGroup;
695
        if(!mCurrentIdle.empty())
696
            mAnimation->play(mCurrentIdle, idlePriority, MWRender::Animation::BlendMask_All, false,
697
                             1.0f, "start", "stop", startPoint, numLoops, true);
698
    }
699
700
}

701
702
void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterState movement, JumpingState jump, bool force)
{
703
    // If the current animation is persistent, do not touch it
704
705
    if (isPersistentAnimPlaying())
        return;
706

707
    if (mPtr.getClass().isActor())
708
        refreshHitRecoilAnims(idle);
709

710
711
712
    std::string weap;
    if (mPtr.getClass().hasInventoryStore(mPtr))
        weap = getWeaponType(mWeaponType)->mShortGroup;
713

Alexei Kotov's avatar
Alexei Kotov committed
714
    refreshJumpAnims(weap, jump, idle, force);
715
    refreshMovementAnims(weap, movement, idle, force);
716
717
718
719
720

    // idle handled last as it can depend on the other states
    refreshIdleAnims(weap, idle, force);
}

721
722
void CharacterController::playDeath(float startpoint, CharacterState death)
{
723
724
725
    // Make sure the character was swimming upon death for forward-compatibility
    const bool wasSwimming = MWBase::Environment::get().getWorld()->isSwimming(mPtr);

726
727
728
729
730
    switch (death)
    {
    case CharState_SwimDeath:
        mCurrentDeath = "swimdeath";
        break;
731
    case CharState_SwimDeathKnockDown:
732
        mCurrentDeath = (wasSwimming ? "swimdeathknockdown" : "deathknockdown");
733
734
        break;
    case CharState_SwimDeathKnockOut:
735
        mCurrentDeath = (wasSwimming ? "swimdeathknockout" : "deathknockout");
736
        break;
737
738
739
740
741
742
743
    case CharState_DeathKnockDown:
        mCurrentDeath = "deathknockdown";
        break;
    case CharState_DeathKnockOut:
        mCurrentDeath = "deathknockout";
        break;
    default:
Alexei Kotov's avatar
Alexei Kotov committed
744
        mCurrentDeath = "death" + std::to_string(death - CharState_Death1 + 1);
745
746
747
748
749
750
    }
    mDeathState = death;

    mPtr.getClass().getCreatureStats(mPtr).setDeathAnimation(mDeathState - CharState_Death1);

    // For dead actors, refreshCurrentAnims is no longer called, so we need to disable the movement state manually.
751
752
    // Note that these animations wouldn't actually be visible (due to the Death animation's priority being higher).
    // However, they could still trigger text keys, such as Hit events, or sounds.
753
754
755
    mMovementState = CharState_None;
    mAnimation->disable(mCurrentMovement);
    mCurrentMovement = "";
756
757
758
759
760
761
762
763
764
765
766
767
    mUpperBodyState = UpperCharState_Nothing;
    mAnimation->disable(mCurrentWeapon);
    mCurrentWeapon = "";
    mHitState = CharState_None;
    mAnimation->disable(mCurrentHit);
    mCurrentHit = "";
    mIdleState = CharState_None;
    mAnimation->disable(mCurrentIdle);
    mCurrentIdle = "";
    mJumpState = JumpState_None;
    mAnimation->disable(mCurrentJump);
    mCurrentJump = "";
768
    mMovementAnimationControlled = true;
769

770
    mAnimation->play(mCurrentDeath, Priority_Death, MWRender::Animation::BlendMask_All,
771
772
773
                    false, 1.0f, "start", "stop", startpoint, 0);
}

774
CharacterState CharacterController::chooseRandomDeathState() const
775
776
777
778
779
780
{
    int selected=0;
    chooseRandomGroup("death", &selected);
    return static_cast<CharacterState>(CharState_Death1 + (selected-1));
}

mrcheko's avatar
mrcheko committed
781
782
void CharacterController::playRandomDeath(float startpoint)
{
dteviot's avatar
dteviot committed
783
    if (mPtr == getPlayer())
scrawl's avatar
scrawl committed
784
785
786
787
788
789
    {
        // The first-person animations do not include death, so we need to
        // force-switch to third person before playing the death animation.
        MWBase::Environment::get().getWorld()->useDeathCamera();
    }

790
    if(mHitState == CharState_SwimKnockDown && mAnimation->hasAnimation("swimdeathknockdown"))
791
792
793
    {
        mDeathState = CharState_SwimDeathKnockDown;
    }
794
    else if(mHitState == CharState_SwimKnockOut && mAnimation->hasAnimation("swimdeathknockout"))
795
796
797
    {
        mDeathState = CharState_SwimDeathKnockOut;
    }
798
    else if(MWBase::Environment::get().getWorld()->isSwimming(mPtr) && mAnimation->hasAnimation("swimdeath"))
mrcheko's avatar
mrcheko committed
799
    {
800
        mDeathState = CharState_SwimDeath;
mrcheko's avatar
mrcheko committed
801
    }
802
    else if (mHitState == CharState_KnockDown && mAnimation->hasAnimation("deathknockdown"))
803
804
805
    {
        mDeathState = CharState_DeathKnockDown;
    }
806
    else if (mHitState == CharState_KnockOut && mAnimation->hasAnimation("deathknockout"))
807
808
809
    {
        mDeathState = CharState_DeathKnockOut;
    }
mrcheko's avatar
mrcheko committed
810
811
    else
    {
812
        mDeathState = chooseRandomDeathState();
mrcheko's avatar
mrcheko committed
813
    }
Andrei Kortunov's avatar
Andrei Kortunov committed
814
815

    // Do not interrupt scripted animation by death
816
817
    if (isPersistentAnimPlaying())
        return;
Andrei Kortunov's avatar
Andrei Kortunov committed
818

819
    playDeath(startpoint, mDeathState);
mrcheko's avatar
mrcheko committed
820
}
821

822
823
824
825
826
827
828
829
830
831
832
833
834
835
std::string CharacterController::chooseRandomAttackAnimation() const
{
    std::string result;
    bool isSwimming = MWBase::Environment::get().getWorld()->isSwimming(mPtr);

    if (isSwimming)
        result = chooseRandomGroup("swimattack");

    if (!isSwimming || !mAnimation->hasAnimation(result))
        result = chooseRandomGroup("attack");

    return result;
}

836
CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim)
837
    : mPtr(ptr)
838
    , mWeapon(MWWorld::Ptr())
839
    , mAnimation(anim)
840
    , mIdleState(CharState_None)
841
    , mMovementState(CharState_None)
Allofich's avatar
Allofich committed
842
    , mMovementAnimSpeed(0.f)
843
    , mAdjustMovementAnimSpeed(false)
844
    , mHasMovedInXY(false)
845
    , mMovementAnimationControlled(true)
846
    , mDeathState(CharState_None)
847
    , mFloatToSurface(true)
mrcheko's avatar
mrcheko committed
848
    , mHitState(CharState_None)
gus's avatar
gus committed
849
    , mUpperBodyState(UpperCharState_Nothing)
Chris Robinson's avatar
Chris Robinson committed
850
    , mJumpState(JumpState_None)
851
    , mWeaponType(ESM::Weapon::None)
852
    , mAttackStrength(0.f)
853
854
    , mSkipAnim(false)
    , mSecondsOfSwimming(0)
855
    , mSecondsOfRunning(0)
856
    , mTurnAnimationThreshold(0)
857
    , mAttackingOrSpell(false)
858
    , mCastingManualSpell(false)
859
    , mTimeUntilWake(0.f)
860
    , mIsMovingBackward(false)
861
{
862
    if(!mAnimation)
863
864
        return;

scrawl's avatar
scrawl committed
865
866
    mAnimation->setTextKeyListener(this);

867
    const MWWorld::Class &cls = mPtr.getClass();
868
    if(cls.isActor())
869
870
871
    {
        /* Accumulate along X/Y only for now, until we can figure out how we should
         * handle knockout and death which moves the character down. */
scrawl's avatar
scrawl committed
872
        mAnimation->setAccumulation(osg::Vec3f(1.0f, 1.0f, 0.0f));
873

874
        if (cls.hasInventoryStore(mPtr))
875
        {
876
877
            getActiveWeapon(mPtr, &mWeaponType);
            if (mWeaponType != ESM::Weapon::None)
878
879
            {
                mUpperBodyState = UpperCharState_WeapEquiped;
880
                mCurrentWeapon = getWeaponAnimation(mWeaponType);
881
882
            }

883
            if(mWeaponType != ESM::Weapon::None && mWeaponType != ESM::Weapon::Spell && mWeaponType != ESM::Weapon::HandToHand)
884
            {
mrcheko's avatar
mrcheko committed
885
                mAnimation->showWeapons(true);
886
887
                // Note: controllers for ranged weapon should use time for beginning of animation to play shooting properly,
                // for other weapons they should use absolute time. Some mods rely on this behaviour (to rotate throwing projectiles, for example)
888
889
                ESM::WeaponType::Class weaponClass = getWeaponType(mWeaponType)->mWeaponClass;
                bool useRelativeDuration = weaponClass == ESM::WeaponType::Ranged;
890
                mAnimation->setWeaponGroup(mCurrentWeapon, useRelativeDuration);
891
            }
892
893

            mAnimation->showCarriedLeft(updateCarriedLeftVisible(mWeaponType));
894
895
896
        }

        if(!cls.getCreatureStats(mPtr).isDead())
897
        {
898
            mIdleState = CharState_Idle;
899
900
901
            if (cls.getCreatureStats(mPtr).getFallHeight() > 0)
                mJumpState = JumpState_InAir;
        }
902
        else
903
        {
904
905
906
907
908
909
910
911
912
913
            const MWMechanics::CreatureStats& cStats = mPtr.getClass().getCreatureStats(mPtr);
            if (cStats.isDeathAnimationFinished())
            {
                // Set the death state, but don't play it yet
                // We will play it in the first frame, but only if no script set the skipAnim flag
                signed char deathanim = cStats.getDeathAnimation();
                if (deathanim == -1)
                    mDeathState = chooseRandomDeathState();
                else
                    mDeathState = static_cast<CharacterState>(CharState_Death1 + deathanim);
914

915
916
917
                mFloatToSurface = false;
            }
            // else: nothing to do, will detect death in the next frame and start playing death animation
918
        }
919
    }
920
921
922
    else
    {
        /* Don't accumulate with non-actors. */
scrawl's avatar
scrawl committed
923
        mAnimation->setAccumulation(osg::Vec3f(0.f, 0.f, 0.f));
924
925

        mIdleState = CharState_Idle;
926
    }
927

928
    // Do not update animation status for dead actors
929
    if(mDeathState == CharState_None && (!cls.isActor() || !cls.getCreatureStats(mPtr).isDead()))
930
        refreshCurrentAnims(mIdleState, mMovementState, mJumpState, true);
931
932

    mAnimation->runAnimation(0.f);
933
934

    unpersistAnimationState();
935
936
}

937
938
CharacterController::~CharacterController()
{
scrawl's avatar
scrawl committed
939
    if (mAnimation)
940
941
    {
        persistAnimationState();
Andrei Kortunov's avatar
Andrei Kortunov committed
942
        mAnimation->setTextKeyListener(nullptr);
943
    }
944
945
}

946
void CharacterController::handleTextKey(const std::string &groupname, SceneUtil::TextKeyMap::ConstIterator key, const SceneUtil::TextKeyMap& map)
scrawl's avatar
scrawl committed
947
948
949
950
951
952
953
954
955
{
    const std::string &evt = key->second;

    if(evt.compare(0, 7, "sound: ") == 0)
    {
        MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
        sndMgr->playSound3D(mPtr, evt.substr(7), 1.0f, 1.0f);
        return;
    }
956
    if(evt.compare(0, 10, "soundgen: ") == 0)
scrawl's avatar
scrawl committed
957
958
959
960
961
    {
        std::string soundgen = evt.substr(10);

        // The event can optionally contain volume and pitch modifiers
        float volume=1.f, pitch=1.f;
962
        if (soundgen.find(' ') != std::string::npos)
scrawl's avatar
scrawl committed
963
964
        {
            std::vector<std::string> tokens;
Alexei Kotov's avatar
Alexei Kotov committed
965
            Misc::StringUtils::split(soundgen, tokens);
scrawl's avatar
scrawl committed
966
967
            soundgen = tokens[0];
            if (tokens.size() >= 2)
968
969
970
971
972
            {
                std::stringstream stream;
                stream << tokens[1];
                stream >> volume;
            }
scrawl's avatar
scrawl committed
973
            if (tokens.size() >= 3)
974
975
976
977
978
            {
                std::stringstream stream;
                stream << tokens[2];
                stream >> pitch;
            }
scrawl's avatar
scrawl committed
979
980
981
        }

        std::string sound = mPtr.getClass().getSoundIdFromSndGen(mPtr, soundgen);
Capostrophic's avatar
Capostrophic committed
982
        if(!sound.empty())
scrawl's avatar
scrawl committed
983
984
        {
            MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();