1 /*
2 * OpenMW - The completely unofficial reimplementation of Morrowind
3 *
4 * This file (character.cpp) is part of the OpenMW package.
5 *
6 * OpenMW is distributed as free software: you can redistribute it
7 * and/or modify it under the terms of the GNU General Public License
8 * version 3, as published by the Free Software Foundation.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * version 3 along with this program. If not, see
17 * https://www.gnu.org/licenses/ .
18 */
19
20 #include "character.hpp"
21
22 #include <iostream>
23
24 #include <components/misc/mathutil.hpp>
25 #include <components/misc/rng.hpp>
26
27 #include <components/settings/settings.hpp>
28
29 #include <components/sceneutil/positionattitudetransform.hpp>
30
31 #include "../mwrender/animation.hpp"
32
33 #include "../mwbase/environment.hpp"
34 #include "../mwbase/mechanicsmanager.hpp"
35 #include "../mwbase/world.hpp"
36 #include "../mwbase/soundmanager.hpp"
37 #include "../mwbase/windowmanager.hpp"
38
39 #include "../mwworld/class.hpp"
40 #include "../mwworld/inventorystore.hpp"
41 #include "../mwworld/esmstore.hpp"
42 #include "../mwworld/player.hpp"
43
44 #include "aicombataction.hpp"
45 #include "movement.hpp"
46 #include "npcstats.hpp"
47 #include "creaturestats.hpp"
48 #include "security.hpp"
49 #include "actorutil.hpp"
50 #include "spellcasting.hpp"
51
52 namespace
53 {
54
getBestAttack(const ESM::Weapon * weapon)55 std::string getBestAttack (const ESM::Weapon* weapon)
56 {
57 int slash = (weapon->mData.mSlash[0] + weapon->mData.mSlash[1])/2;
58 int chop = (weapon->mData.mChop[0] + weapon->mData.mChop[1])/2;
59 int thrust = (weapon->mData.mThrust[0] + weapon->mData.mThrust[1])/2;
60 if (slash == chop && slash == thrust)
61 return "slash";
62 else if (thrust >= chop && thrust >= slash)
63 return "thrust";
64 else if (slash >= chop && slash >= thrust)
65 return "slash";
66 else
67 return "chop";
68 }
69
70 // Converts a movement Run state to its equivalent Walk state.
runStateToWalkState(MWMechanics::CharacterState state)71 MWMechanics::CharacterState runStateToWalkState (MWMechanics::CharacterState state)
72 {
73 using namespace MWMechanics;
74 CharacterState ret = state;
75 switch (state)
76 {
77 case CharState_RunForward:
78 ret = CharState_WalkForward;
79 break;
80 case CharState_RunBack:
81 ret = CharState_WalkBack;
82 break;
83 case CharState_RunLeft:
84 ret = CharState_WalkLeft;
85 break;
86 case CharState_RunRight:
87 ret = CharState_WalkRight;
88 break;
89 case CharState_SwimRunForward:
90 ret = CharState_SwimWalkForward;
91 break;
92 case CharState_SwimRunBack:
93 ret = CharState_SwimWalkBack;
94 break;
95 case CharState_SwimRunLeft:
96 ret = CharState_SwimWalkLeft;
97 break;
98 case CharState_SwimRunRight:
99 ret = CharState_SwimWalkRight;
100 break;
101 default:
102 break;
103 }
104 return ret;
105 }
106
getFallDamage(const MWWorld::Ptr & ptr,float fallHeight)107 float getFallDamage(const MWWorld::Ptr& ptr, float fallHeight)
108 {
109 MWBase::World *world = MWBase::Environment::get().getWorld();
110 const MWWorld::Store<ESM::GameSetting> &store = world->getStore().get<ESM::GameSetting>();
111
112 const float fallDistanceMin = store.find("fFallDamageDistanceMin")->mValue.getFloat();
113
114 if (fallHeight >= fallDistanceMin)
115 {
116 const float acrobaticsSkill = static_cast<float>(ptr.getClass().getSkill(ptr, ESM::Skill::Acrobatics));
117 const float jumpSpellBonus = ptr.getClass().getCreatureStats(ptr).getMagicEffects().get(ESM::MagicEffect::Jump).getMagnitude();
118 const float fallAcroBase = store.find("fFallAcroBase")->mValue.getFloat();
119 const float fallAcroMult = store.find("fFallAcroMult")->mValue.getFloat();
120 const float fallDistanceBase = store.find("fFallDistanceBase")->mValue.getFloat();
121 const float fallDistanceMult = store.find("fFallDistanceMult")->mValue.getFloat();
122
123 float x = fallHeight - fallDistanceMin;
124 x -= (1.5f * acrobaticsSkill) + jumpSpellBonus;
125 x = std::max(0.0f, x);
126
127 float a = fallAcroBase + fallAcroMult * (100 - acrobaticsSkill);
128 x = fallDistanceBase + fallDistanceMult * x;
129 x *= a;
130
131 return x;
132 }
133 return 0.f;
134 }
135
136 }
137
138 namespace MWMechanics
139 {
140
141 struct StateInfo {
142 CharacterState state;
143 const char groupname[32];
144 };
145
146 static const StateInfo sMovementList[] = {
147 { CharState_WalkForward, "walkforward" },
148 { CharState_WalkBack, "walkback" },
149 { CharState_WalkLeft, "walkleft" },
150 { CharState_WalkRight, "walkright" },
151
152 { CharState_SwimWalkForward, "swimwalkforward" },
153 { CharState_SwimWalkBack, "swimwalkback" },
154 { CharState_SwimWalkLeft, "swimwalkleft" },
155 { CharState_SwimWalkRight, "swimwalkright" },
156
157 { CharState_RunForward, "runforward" },
158 { CharState_RunBack, "runback" },
159 { CharState_RunLeft, "runleft" },
160 { CharState_RunRight, "runright" },
161
162 { CharState_SwimRunForward, "swimrunforward" },
163 { CharState_SwimRunBack, "swimrunback" },
164 { CharState_SwimRunLeft, "swimrunleft" },
165 { CharState_SwimRunRight, "swimrunright" },
166
167 { CharState_SneakForward, "sneakforward" },
168 { CharState_SneakBack, "sneakback" },
169 { CharState_SneakLeft, "sneakleft" },
170 { CharState_SneakRight, "sneakright" },
171
172 { CharState_Jump, "jump" },
173
174 { CharState_TurnLeft, "turnleft" },
175 { CharState_TurnRight, "turnright" },
176 { CharState_SwimTurnLeft, "swimturnleft" },
177 { CharState_SwimTurnRight, "swimturnright" },
178 };
179 static const StateInfo *sMovementListEnd = &sMovementList[sizeof(sMovementList)/sizeof(sMovementList[0])];
180
181
182 class FindCharState {
183 CharacterState state;
184
185 public:
FindCharState(CharacterState _state)186 FindCharState(CharacterState _state) : state(_state) { }
187
operator ()(const StateInfo & info) const188 bool operator()(const StateInfo &info) const
189 { return info.state == state; }
190 };
191
192
chooseRandomGroup(const std::string & prefix,int * num) const193 std::string CharacterController::chooseRandomGroup (const std::string& prefix, int* num) const
194 {
195 int numAnims=0;
196 while (mAnimation->hasAnimation(prefix + std::to_string(numAnims+1)))
197 ++numAnims;
198
199 int roll = Misc::Rng::rollDice(numAnims) + 1; // [1, numAnims]
200 if (num)
201 *num = roll;
202 return prefix + std::to_string(roll);
203 }
204
refreshHitRecoilAnims(CharacterState & idle)205 void CharacterController::refreshHitRecoilAnims(CharacterState& idle)
206 {
207 bool recovery = mPtr.getClass().getCreatureStats(mPtr).getHitRecovery();
208 bool knockdown = mPtr.getClass().getCreatureStats(mPtr).getKnockedDown();
209 bool block = mPtr.getClass().getCreatureStats(mPtr).getBlock();
210 bool isSwimming = MWBase::Environment::get().getWorld()->isSwimming(mPtr);
211 if(mHitState == CharState_None)
212 {
213 if ((mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() < 0
214 || mPtr.getClass().getCreatureStats(mPtr).getFatigue().getBase() == 0))
215 {
216 mTimeUntilWake = Misc::Rng::rollClosedProbability() * 2 + 1; // Wake up after 1 to 3 seconds
217 if (isSwimming && mAnimation->hasAnimation("swimknockout"))
218 {
219 mHitState = CharState_SwimKnockOut;
220 mCurrentHit = "swimknockout";
221 mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, false, 1, "start", "stop", 0.0f, ~0ul);
222 }
223 else if (!isSwimming && mAnimation->hasAnimation("knockout"))
224 {
225 mHitState = CharState_KnockOut;
226 mCurrentHit = "knockout";
227 mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, false, 1, "start", "stop", 0.0f, ~0ul);
228 }
229 else
230 {
231 // Knockout animations are missing. Fall back to idle animation, so target actor still can be killed via HtH.
232 mCurrentHit.erase();
233 }
234
235 mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(true);
236 }
237 else if (knockdown)
238 {
239 if (isSwimming && mAnimation->hasAnimation("swimknockdown"))
240 {
241 mHitState = CharState_SwimKnockDown;
242 mCurrentHit = "swimknockdown";
243 mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0);
244 }
245 else if (!isSwimming && mAnimation->hasAnimation("knockdown"))
246 {
247 mHitState = CharState_KnockDown;
248 mCurrentHit = "knockdown";
249 mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0);
250 }
251 else
252 {
253 // Knockdown animation is missing. Cancel knockdown state.
254 mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(false);
255 }
256 }
257 else if (recovery)
258 {
259 std::string anim = chooseRandomGroup("swimhit");
260 if (isSwimming && mAnimation->hasAnimation(anim))
261 {
262 mHitState = CharState_SwimHit;
263 mCurrentHit = anim;
264 mAnimation->play(mCurrentHit, Priority_Hit, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0);
265 }
266 else
267 {
268 anim = chooseRandomGroup("hit");
269 if (mAnimation->hasAnimation(anim))
270 {
271 mHitState = CharState_Hit;
272 mCurrentHit = anim;
273 mAnimation->play(mCurrentHit, Priority_Hit, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0);
274 }
275 }
276 }
277 else if (block && mAnimation->hasAnimation("shield"))
278 {
279 mHitState = CharState_Block;
280 mCurrentHit = "shield";
281 MWRender::Animation::AnimPriority priorityBlock (Priority_Hit);
282 priorityBlock[MWRender::Animation::BoneGroup_LeftArm] = Priority_Block;
283 priorityBlock[MWRender::Animation::BoneGroup_LowerBody] = Priority_WeaponLowerBody;
284 mAnimation->play(mCurrentHit, priorityBlock, MWRender::Animation::BlendMask_All, true, 1, "block start", "block stop", 0.0f, 0);
285 }
286
287 // Cancel upper body animations
288 if (isKnockedOut() || isKnockedDown())
289 {
290 if (mUpperBodyState > UpperCharState_WeapEquiped)
291 {
292 mAnimation->disable(mCurrentWeapon);
293 mUpperBodyState = UpperCharState_WeapEquiped;
294 if (mWeaponType > ESM::Weapon::None)
295 mAnimation->showWeapons(true);
296 }
297 else if (mUpperBodyState > UpperCharState_Nothing && mUpperBodyState < UpperCharState_WeapEquiped)
298 {
299 mAnimation->disable(mCurrentWeapon);
300 mUpperBodyState = UpperCharState_Nothing;
301 }
302 }
303 if (mHitState != CharState_None)
304 idle = CharState_None;
305 }
306 else if(!mAnimation->isPlaying(mCurrentHit))
307 {
308 mCurrentHit.erase();
309 if (knockdown)
310 mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(false);
311 if (recovery)
312 mPtr.getClass().getCreatureStats(mPtr).setHitRecovery(false);
313 if (block)
314 mPtr.getClass().getCreatureStats(mPtr).setBlock(false);
315 mHitState = CharState_None;
316 }
317 else if (isKnockedOut() && mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() > 0
318 && mTimeUntilWake <= 0)
319 {
320 mHitState = isSwimming ? CharState_SwimKnockDown : CharState_KnockDown;
321 mAnimation->disable(mCurrentHit);
322 mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "loop stop", "stop", 0.0f, 0);
323 }
324 }
325
refreshJumpAnims(const std::string & weapShortGroup,JumpingState jump,CharacterState & idle,bool force)326 void CharacterController::refreshJumpAnims(const std::string& weapShortGroup, JumpingState jump, CharacterState& idle, bool force)
327 {
328 if (!force && jump == mJumpState && idle == CharState_None)
329 return;
330
331 std::string jumpAnimName;
332 MWRender::Animation::BlendMask jumpmask = MWRender::Animation::BlendMask_All;
333 if (jump != JumpState_None)
334 {
335 jumpAnimName = "jump";
336 if(!weapShortGroup.empty())
337 {
338 jumpAnimName += weapShortGroup;
339 if(!mAnimation->hasAnimation(jumpAnimName))
340 {
341 jumpAnimName = fallbackShortWeaponGroup("jump", &jumpmask);
342
343 // If we apply jump only for lower body, do not reset idle animations.
344 // For upper body there will be idle animation.
345 if (jumpmask == MWRender::Animation::BlendMask_LowerBody && idle == CharState_None)
346 idle = CharState_Idle;
347 }
348 }
349 }
350
351 if (!force && jump == mJumpState)
352 return;
353
354 bool startAtLoop = (jump == mJumpState);
355 mJumpState = jump;
356
357 if (!mCurrentJump.empty())
358 {
359 mAnimation->disable(mCurrentJump);
360 mCurrentJump.clear();
361 }
362
363 if(mJumpState == JumpState_InAir)
364 {
365 if (mAnimation->hasAnimation(jumpAnimName))
366 {
367 mAnimation->play(jumpAnimName, Priority_Jump, jumpmask, false,
368 1.0f, startAtLoop ? "loop start" : "start", "stop", 0.f, ~0ul);
369 mCurrentJump = jumpAnimName;
370 }
371 }
372 else if (mJumpState == JumpState_Landing)
373 {
374 if (mAnimation->hasAnimation(jumpAnimName))
375 {
376 mAnimation->play(jumpAnimName, Priority_Jump, jumpmask, true,
377 1.0f, "loop stop", "stop", 0.0f, 0);
378 mCurrentJump = jumpAnimName;
379 }
380 }
381 }
382
onOpen()383 bool CharacterController::onOpen()
384 {
385 if (mPtr.getTypeName() == typeid(ESM::Container).name())
386 {
387 if (!mAnimation->hasAnimation("containeropen"))
388 return true;
389
390 if (mAnimation->isPlaying("containeropen"))
391 return false;
392
393 if (mAnimation->isPlaying("containerclose"))
394 return false;
395
396 mAnimation->play("containeropen", Priority_Persistent, MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", 0.f, 0);
397 if (mAnimation->isPlaying("containeropen"))
398 return false;
399 }
400
401 return true;
402 }
403
onClose()404 void CharacterController::onClose()
405 {
406 if (mPtr.getTypeName() == typeid(ESM::Container).name())
407 {
408 if (!mAnimation->hasAnimation("containerclose"))
409 return;
410
411 float complete, startPoint = 0.f;
412 bool animPlaying = mAnimation->getInfo("containeropen", &complete);
413 if (animPlaying)
414 startPoint = 1.f - complete;
415
416 mAnimation->play("containerclose", Priority_Persistent, MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", startPoint, 0);
417 }
418 }
419
getWeaponAnimation(int weaponType) const420 std::string CharacterController::getWeaponAnimation(int weaponType) const
421 {
422 std::string weaponGroup = getWeaponType(weaponType)->mLongGroup;
423 bool isRealWeapon = weaponType != ESM::Weapon::HandToHand && weaponType != ESM::Weapon::Spell && weaponType != ESM::Weapon::None;
424 if (isRealWeapon && !mAnimation->hasAnimation(weaponGroup))
425 {
426 static const std::string oneHandFallback = getWeaponType(ESM::Weapon::LongBladeOneHand)->mLongGroup;
427 static const std::string twoHandFallback = getWeaponType(ESM::Weapon::LongBladeTwoHand)->mLongGroup;
428
429 const ESM::WeaponType* weapInfo = getWeaponType(weaponType);
430
431 // For real two-handed melee weapons use 2h swords animations as fallback, otherwise use the 1h ones
432 if (weapInfo->mFlags & ESM::WeaponType::TwoHanded && weapInfo->mWeaponClass == ESM::WeaponType::Melee)
433 weaponGroup = twoHandFallback;
434 else if (isRealWeapon)
435 weaponGroup = oneHandFallback;
436 }
437
438 return weaponGroup;
439 }
440
fallbackShortWeaponGroup(const std::string & baseGroupName,MWRender::Animation::BlendMask * blendMask)441 std::string CharacterController::fallbackShortWeaponGroup(const std::string& baseGroupName, MWRender::Animation::BlendMask* blendMask)
442 {
443 bool isRealWeapon = mWeaponType != ESM::Weapon::HandToHand && mWeaponType != ESM::Weapon::Spell && mWeaponType != ESM::Weapon::None;
444 if (!isRealWeapon)
445 {
446 if (blendMask != nullptr)
447 *blendMask = MWRender::Animation::BlendMask_LowerBody;
448
449 return baseGroupName;
450 }
451
452 static const std::string oneHandFallback = getWeaponType(ESM::Weapon::LongBladeOneHand)->mShortGroup;
453 static const std::string twoHandFallback = getWeaponType(ESM::Weapon::LongBladeTwoHand)->mShortGroup;
454
455 std::string groupName = baseGroupName;
456 const ESM::WeaponType* weapInfo = getWeaponType(mWeaponType);
457
458 // For real two-handed melee weapons use 2h swords animations as fallback, otherwise use the 1h ones
459 if (weapInfo->mFlags & ESM::WeaponType::TwoHanded && weapInfo->mWeaponClass == ESM::WeaponType::Melee)
460 groupName += twoHandFallback;
461 else
462 groupName += oneHandFallback;
463
464 // Special case for crossbows - we shouls apply 1h animations a fallback only for lower body
465 if (mWeaponType == ESM::Weapon::MarksmanCrossbow && blendMask != nullptr)
466 *blendMask = MWRender::Animation::BlendMask_LowerBody;
467
468 if (!mAnimation->hasAnimation(groupName))
469 {
470 groupName = baseGroupName;
471 if (blendMask != nullptr)
472 *blendMask = MWRender::Animation::BlendMask_LowerBody;
473 }
474
475 return groupName;
476 }
477
refreshMovementAnims(const std::string & weapShortGroup,CharacterState movement,CharacterState & idle,bool force)478 void CharacterController::refreshMovementAnims(const std::string& weapShortGroup, CharacterState movement, CharacterState& idle, bool force)
479 {
480 if (movement == mMovementState && idle == mIdleState && !force)
481 return;
482
483 // Reset idle if we actually play movement animations excepts of these cases:
484 // 1. When we play turning animations
485 // 2. When we use a fallback animation for lower body since movement animation for given weapon is missing (e.g. for crossbows and spellcasting)
486 bool resetIdle = (movement != CharState_None && !isTurning());
487
488 std::string movementAnimName;
489 MWRender::Animation::BlendMask movemask;
490 const StateInfo *movestate;
491
492 movemask = MWRender::Animation::BlendMask_All;
493 movestate = std::find_if(sMovementList, sMovementListEnd, FindCharState(movement));
494 if(movestate != sMovementListEnd)
495 {
496 movementAnimName = movestate->groupname;
497 if(!weapShortGroup.empty())
498 {
499 std::string::size_type swimpos = movementAnimName.find("swim");
500 if (swimpos == std::string::npos)
501 {
502 if (mWeaponType == ESM::Weapon::Spell && (movement == CharState_TurnLeft || movement == CharState_TurnRight)) // Spellcasting stance turning is a special case
503 movementAnimName = weapShortGroup + movementAnimName;
504 else
505 movementAnimName += weapShortGroup;
506 }
507
508 if(!mAnimation->hasAnimation(movementAnimName))
509 {
510 movementAnimName = movestate->groupname;
511 if (swimpos == std::string::npos)
512 {
513 movementAnimName = fallbackShortWeaponGroup(movementAnimName, &movemask);
514
515 // If we apply movement only for lower body, do not reset idle animations.
516 // For upper body there will be idle animation.
517 if (movemask == MWRender::Animation::BlendMask_LowerBody && idle == CharState_None)
518 idle = CharState_Idle;
519
520 if (movemask == MWRender::Animation::BlendMask_LowerBody)
521 resetIdle = false;
522 }
523 }
524 }
525 }
526
527 if(force || movement != mMovementState)
528 {
529 mMovementState = movement;
530 if(movestate != sMovementListEnd)
531 {
532 if(!mAnimation->hasAnimation(movementAnimName))
533 {
534 std::string::size_type swimpos = movementAnimName.find("swim");
535 if (swimpos != std::string::npos)
536 {
537 movementAnimName.erase(swimpos, 4);
538 if (!weapShortGroup.empty())
539 {
540 std::string weapMovementAnimName = movementAnimName + weapShortGroup;
541 if(mAnimation->hasAnimation(weapMovementAnimName))
542 movementAnimName = weapMovementAnimName;
543 else
544 {
545 movementAnimName = fallbackShortWeaponGroup(movementAnimName, &movemask);
546 if (movemask == MWRender::Animation::BlendMask_LowerBody)
547 resetIdle = false;
548 }
549 }
550 }
551
552 if (swimpos == std::string::npos || !mAnimation->hasAnimation(movementAnimName))
553 {
554 std::string::size_type runpos = movementAnimName.find("run");
555 if (runpos != std::string::npos)
556 {
557 movementAnimName.replace(runpos, runpos+3, "walk");
558 if (!mAnimation->hasAnimation(movementAnimName))
559 movementAnimName.clear();
560 }
561 else
562 movementAnimName.clear();
563 }
564 }
565 }
566
567 // If we're playing the same animation, start it from the point it ended
568 float startpoint = 0.f;
569 if (!mCurrentMovement.empty() && movementAnimName == mCurrentMovement)
570 mAnimation->getInfo(mCurrentMovement, &startpoint);
571
572 mMovementAnimationControlled = true;
573
574 mAnimation->disable(mCurrentMovement);
575
576 if (!mAnimation->hasAnimation(movementAnimName))
577 movementAnimName.clear();
578
579 mCurrentMovement = movementAnimName;
580 if(!mCurrentMovement.empty())
581 {
582 if (resetIdle)
583 {
584 mAnimation->disable(mCurrentIdle);
585 mIdleState = CharState_None;
586 idle = CharState_None;
587 }
588
589 // For non-flying creatures, MW uses the Walk animation to calculate the animation velocity
590 // even if we are running. This must be replicated, otherwise the observed speed would differ drastically.
591 std::string anim = mCurrentMovement;
592 mAdjustMovementAnimSpeed = true;
593 if (mPtr.getClass().getTypeName() == typeid(ESM::Creature).name()
594 && !(mPtr.get<ESM::Creature>()->mBase->mFlags & ESM::Creature::Flies))
595 {
596 CharacterState walkState = runStateToWalkState(mMovementState);
597 const StateInfo *stateinfo = std::find_if(sMovementList, sMovementListEnd, FindCharState(walkState));
598 anim = stateinfo->groupname;
599
600 mMovementAnimSpeed = mAnimation->getVelocity(anim);
601 if (mMovementAnimSpeed <= 1.0f)
602 {
603 // Another bug: when using a fallback animation (e.g. RunForward as fallback to SwimRunForward),
604 // then the equivalent Walk animation will not use a fallback, and if that animation doesn't exist
605 // we will play without any scaling.
606 // Makes the speed attribute of most water creatures totally useless.
607 // And again, this can not be fixed without patching game data.
608 mAdjustMovementAnimSpeed = false;
609 mMovementAnimSpeed = 1.f;
610 }
611 }
612 else
613 {
614 mMovementAnimSpeed = mAnimation->getVelocity(anim);
615
616 if (mMovementAnimSpeed <= 1.0f)
617 {
618 // The first person anims don't have any velocity to calculate a speed multiplier from.
619 // We use the third person velocities instead.
620 // FIXME: should be pulled from the actual animation, but it is not presently loaded.
621 bool sneaking = mMovementState == CharState_SneakForward || mMovementState == CharState_SneakBack
622 || mMovementState == CharState_SneakLeft || mMovementState == CharState_SneakRight;
623 mMovementAnimSpeed = (sneaking ? 33.5452f : (isRunning() ? 222.857f : 154.064f));
624 mMovementAnimationControlled = false;
625 }
626 }
627
628 mAnimation->play(mCurrentMovement, Priority_Movement, movemask, false,
629 1.f, "start", "stop", startpoint, ~0ul, true);
630 }
631 else
632 mMovementState = CharState_None;
633 }
634 }
635
refreshIdleAnims(const std::string & weapShortGroup,CharacterState idle,bool force)636 void CharacterController::refreshIdleAnims(const std::string& weapShortGroup, CharacterState idle, bool force)
637 {
638 // FIXME: if one of the below states is close to their last animation frame (i.e. will be disabled in the coming update),
639 // the idle animation should be displayed
640 if (((mUpperBodyState != UpperCharState_Nothing && mUpperBodyState != UpperCharState_WeapEquiped)
641 || (mMovementState != CharState_None && !isTurning())
642 || mHitState != CharState_None)
643 && !mPtr.getClass().isBipedal(mPtr))
644 idle = CharState_None;
645
646 if(force || idle != mIdleState || (!mAnimation->isPlaying(mCurrentIdle) && mAnimQueue.empty()))
647 {
648 mIdleState = idle;
649 size_t numLoops = ~0ul;
650
651 std::string idleGroup;
652 MWRender::Animation::AnimPriority idlePriority (Priority_Default);
653 // Only play "idleswim" or "idlesneak" if they exist. Otherwise, fallback to
654 // "idle"+weapon or "idle".
655 if(mIdleState == CharState_IdleSwim && mAnimation->hasAnimation("idleswim"))
656 {
657 idleGroup = "idleswim";
658 idlePriority = Priority_SwimIdle;
659 }
660 else if(mIdleState == CharState_IdleSneak && mAnimation->hasAnimation("idlesneak"))
661 {
662 idleGroup = "idlesneak";
663 idlePriority[MWRender::Animation::BoneGroup_LowerBody] = Priority_SneakIdleLowerBody;
664 }
665 else if(mIdleState != CharState_None)
666 {
667 idleGroup = "idle";
668 if(!weapShortGroup.empty())
669 {
670 idleGroup += weapShortGroup;
671 if(!mAnimation->hasAnimation(idleGroup))
672 {
673 idleGroup = fallbackShortWeaponGroup("idle");
674 }
675
676 // play until the Loop Stop key 2 to 5 times, then play until the Stop key
677 // this replicates original engine behavior for the "Idle1h" 1st-person animation
678 numLoops = 1 + Misc::Rng::rollDice(4);
679 }
680 }
681
682 // There is no need to restart anim if the new and old anims are the same.
683 // Just update a number of loops.
684 float startPoint = 0;
685 if (!mCurrentIdle.empty() && mCurrentIdle == idleGroup)
686 {
687 mAnimation->getInfo(mCurrentIdle, &startPoint);
688 }
689
690 if(!mCurrentIdle.empty())
691 mAnimation->disable(mCurrentIdle);
692
693 mCurrentIdle = idleGroup;
694 if(!mCurrentIdle.empty())
695 mAnimation->play(mCurrentIdle, idlePriority, MWRender::Animation::BlendMask_All, false,
696 1.0f, "start", "stop", startPoint, numLoops, true);
697 }
698 }
699
refreshCurrentAnims(CharacterState idle,CharacterState movement,JumpingState jump,bool force)700 void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterState movement, JumpingState jump, bool force)
701 {
702 // If the current animation is persistent, do not touch it
703 if (isPersistentAnimPlaying())
704 return;
705
706 if (mPtr.getClass().isActor())
707 refreshHitRecoilAnims(idle);
708
709 std::string weap;
710 if (mPtr.getClass().hasInventoryStore(mPtr))
711 weap = getWeaponType(mWeaponType)->mShortGroup;
712
713 refreshJumpAnims(weap, jump, idle, force);
714 refreshMovementAnims(weap, movement, idle, force);
715
716 // idle handled last as it can depend on the other states
717 refreshIdleAnims(weap, idle, force);
718 }
719
playDeath(float startpoint,CharacterState death)720 void CharacterController::playDeath(float startpoint, CharacterState death)
721 {
722 // Make sure the character was swimming upon death for forward-compatibility
723 const bool wasSwimming = MWBase::Environment::get().getWorld()->isSwimming(mPtr);
724
725 switch (death)
726 {
727 case CharState_SwimDeath:
728 mCurrentDeath = "swimdeath";
729 break;
730 case CharState_SwimDeathKnockDown:
731 mCurrentDeath = (wasSwimming ? "swimdeathknockdown" : "deathknockdown");
732 break;
733 case CharState_SwimDeathKnockOut:
734 mCurrentDeath = (wasSwimming ? "swimdeathknockout" : "deathknockout");
735 break;
736 case CharState_DeathKnockDown:
737 mCurrentDeath = "deathknockdown";
738 break;
739 case CharState_DeathKnockOut:
740 mCurrentDeath = "deathknockout";
741 break;
742 default:
743 mCurrentDeath = "death" + std::to_string(death - CharState_Death1 + 1);
744 }
745 mDeathState = death;
746
747 mPtr.getClass().getCreatureStats(mPtr).setDeathAnimation(mDeathState - CharState_Death1);
748
749 // For dead actors, refreshCurrentAnims is no longer called, so we need to disable the movement state manually.
750 // Note that these animations wouldn't actually be visible (due to the Death animation's priority being higher).
751 // However, they could still trigger text keys, such as Hit events, or sounds.
752 mMovementState = CharState_None;
753 mAnimation->disable(mCurrentMovement);
754 mCurrentMovement = "";
755 mUpperBodyState = UpperCharState_Nothing;
756 mAnimation->disable(mCurrentWeapon);
757 mCurrentWeapon = "";
758 mHitState = CharState_None;
759 mAnimation->disable(mCurrentHit);
760 mCurrentHit = "";
761 mIdleState = CharState_None;
762 mAnimation->disable(mCurrentIdle);
763 mCurrentIdle = "";
764 mJumpState = JumpState_None;
765 mAnimation->disable(mCurrentJump);
766 mCurrentJump = "";
767 mMovementAnimationControlled = true;
768
769 mAnimation->play(mCurrentDeath, Priority_Death, MWRender::Animation::BlendMask_All,
770 false, 1.0f, "start", "stop", startpoint, 0);
771 }
772
chooseRandomDeathState() const773 CharacterState CharacterController::chooseRandomDeathState() const
774 {
775 int selected=0;
776 chooseRandomGroup("death", &selected);
777 return static_cast<CharacterState>(CharState_Death1 + (selected-1));
778 }
779
playRandomDeath(float startpoint)780 void CharacterController::playRandomDeath(float startpoint)
781 {
782 if (mPtr == getPlayer())
783 {
784 // The first-person animations do not include death, so we need to
785 // force-switch to third person before playing the death animation.
786 MWBase::Environment::get().getWorld()->useDeathCamera();
787 }
788
789 if(mHitState == CharState_SwimKnockDown && mAnimation->hasAnimation("swimdeathknockdown"))
790 {
791 mDeathState = CharState_SwimDeathKnockDown;
792 }
793 else if(mHitState == CharState_SwimKnockOut && mAnimation->hasAnimation("swimdeathknockout"))
794 {
795 mDeathState = CharState_SwimDeathKnockOut;
796 }
797 else if(MWBase::Environment::get().getWorld()->isSwimming(mPtr) && mAnimation->hasAnimation("swimdeath"))
798 {
799 mDeathState = CharState_SwimDeath;
800 }
801 else if (mHitState == CharState_KnockDown && mAnimation->hasAnimation("deathknockdown"))
802 {
803 mDeathState = CharState_DeathKnockDown;
804 }
805 else if (mHitState == CharState_KnockOut && mAnimation->hasAnimation("deathknockout"))
806 {
807 mDeathState = CharState_DeathKnockOut;
808 }
809 else
810 {
811 mDeathState = chooseRandomDeathState();
812 }
813
814 // Do not interrupt scripted animation by death
815 if (isPersistentAnimPlaying())
816 return;
817
818 playDeath(startpoint, mDeathState);
819 }
820
chooseRandomAttackAnimation() const821 std::string CharacterController::chooseRandomAttackAnimation() const
822 {
823 std::string result;
824 bool isSwimming = MWBase::Environment::get().getWorld()->isSwimming(mPtr);
825
826 if (isSwimming)
827 result = chooseRandomGroup("swimattack");
828
829 if (!isSwimming || !mAnimation->hasAnimation(result))
830 result = chooseRandomGroup("attack");
831
832 return result;
833 }
834
CharacterController(const MWWorld::Ptr & ptr,MWRender::Animation * anim)835 CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim)
836 : mPtr(ptr)
837 , mWeapon(MWWorld::Ptr())
838 , mAnimation(anim)
839 , mIdleState(CharState_None)
840 , mMovementState(CharState_None)
841 , mMovementAnimSpeed(0.f)
842 , mAdjustMovementAnimSpeed(false)
843 , mHasMovedInXY(false)
844 , mMovementAnimationControlled(true)
845 , mDeathState(CharState_None)
846 , mFloatToSurface(true)
847 , mHitState(CharState_None)
848 , mUpperBodyState(UpperCharState_Nothing)
849 , mJumpState(JumpState_None)
850 , mWeaponType(ESM::Weapon::None)
851 , mAttackStrength(0.f)
852 , mSkipAnim(false)
853 , mSecondsOfSwimming(0)
854 , mSecondsOfRunning(0)
855 , mTurnAnimationThreshold(0)
856 , mAttackingOrSpell(false)
857 , mCastingManualSpell(false)
858 , mTimeUntilWake(0.f)
859 , mIsMovingBackward(false)
860 {
861 if(!mAnimation)
862 return;
863
864 mAnimation->setTextKeyListener(this);
865
866 const MWWorld::Class &cls = mPtr.getClass();
867 if(cls.isActor())
868 {
869 /* Accumulate along X/Y only for now, until we can figure out how we should
870 * handle knockout and death which moves the character down. */
871 mAnimation->setAccumulation(osg::Vec3f(1.0f, 1.0f, 0.0f));
872
873 if (cls.hasInventoryStore(mPtr))
874 {
875 getActiveWeapon(mPtr, &mWeaponType);
876 if (mWeaponType != ESM::Weapon::None)
877 {
878 mUpperBodyState = UpperCharState_WeapEquiped;
879 mCurrentWeapon = getWeaponAnimation(mWeaponType);
880 }
881
882 if(mWeaponType != ESM::Weapon::None && mWeaponType != ESM::Weapon::Spell && mWeaponType != ESM::Weapon::HandToHand)
883 {
884 mAnimation->showWeapons(true);
885 // Note: controllers for ranged weapon should use time for beginning of animation to play shooting properly,
886 // for other weapons they should use absolute time. Some mods rely on this behaviour (to rotate throwing projectiles, for example)
887 ESM::WeaponType::Class weaponClass = getWeaponType(mWeaponType)->mWeaponClass;
888 bool useRelativeDuration = weaponClass == ESM::WeaponType::Ranged;
889 mAnimation->setWeaponGroup(mCurrentWeapon, useRelativeDuration);
890 }
891
892 mAnimation->showCarriedLeft(updateCarriedLeftVisible(mWeaponType));
893 }
894
895 if(!cls.getCreatureStats(mPtr).isDead())
896 {
897 mIdleState = CharState_Idle;
898 if (cls.getCreatureStats(mPtr).getFallHeight() > 0)
899 mJumpState = JumpState_InAir;
900 }
901 else
902 {
903 const MWMechanics::CreatureStats& cStats = mPtr.getClass().getCreatureStats(mPtr);
904 if (cStats.isDeathAnimationFinished())
905 {
906 // Set the death state, but don't play it yet
907 // We will play it in the first frame, but only if no script set the skipAnim flag
908 signed char deathanim = cStats.getDeathAnimation();
909 if (deathanim == -1)
910 mDeathState = chooseRandomDeathState();
911 else
912 mDeathState = static_cast<CharacterState>(CharState_Death1 + deathanim);
913
914 mFloatToSurface = false;
915 }
916 // else: nothing to do, will detect death in the next frame and start playing death animation
917 }
918 }
919 else
920 {
921 /* Don't accumulate with non-actors. */
922 mAnimation->setAccumulation(osg::Vec3f(0.f, 0.f, 0.f));
923
924 mIdleState = CharState_Idle;
925 }
926
927 // Do not update animation status for dead actors
928 if(mDeathState == CharState_None && (!cls.isActor() || !cls.getCreatureStats(mPtr).isDead()))
929 refreshCurrentAnims(mIdleState, mMovementState, mJumpState, true);
930
931 mAnimation->runAnimation(0.f);
932
933 unpersistAnimationState();
934 }
935
~CharacterController()936 CharacterController::~CharacterController()
937 {
938 if (mAnimation)
939 {
940 persistAnimationState();
941 mAnimation->setTextKeyListener(nullptr);
942 }
943 }
944
split(const std::string & s,char delim,std::vector<std::string> & elems)945 void split(const std::string &s, char delim, std::vector<std::string> &elems) {
946 std::stringstream ss(s);
947 std::string item;
948 while (std::getline(ss, item, delim)) {
949 elems.push_back(item);
950 }
951 }
952
handleTextKey(const std::string & groupname,SceneUtil::TextKeyMap::ConstIterator key,const SceneUtil::TextKeyMap & map)953 void CharacterController::handleTextKey(const std::string &groupname, SceneUtil::TextKeyMap::ConstIterator key, const SceneUtil::TextKeyMap& map)
954 {
955 const std::string &evt = key->second;
956
957 if(evt.compare(0, 7, "sound: ") == 0)
958 {
959 MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
960 sndMgr->playSound3D(mPtr, evt.substr(7), 1.0f, 1.0f);
961 return;
962 }
963 if(evt.compare(0, 10, "soundgen: ") == 0)
964 {
965 std::string soundgen = evt.substr(10);
966
967 // The event can optionally contain volume and pitch modifiers
968 float volume=1.f, pitch=1.f;
969 if (soundgen.find(' ') != std::string::npos)
970 {
971 std::vector<std::string> tokens;
972 split(soundgen, ' ', tokens);
973 soundgen = tokens[0];
974 if (tokens.size() >= 2)
975 {
976 std::stringstream stream;
977 stream << tokens[1];
978 stream >> volume;
979 }
980 if (tokens.size() >= 3)
981 {
982 std::stringstream stream;
983 stream << tokens[2];
984 stream >> pitch;
985 }
986 }
987
988 std::string sound = mPtr.getClass().getSoundIdFromSndGen(mPtr, soundgen);
989 if(!sound.empty())
990 {
991 MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
992 // NB: landing sound is not played for NPCs here
993 if(soundgen == "left" || soundgen == "right" || soundgen == "land")
994 {
995 sndMgr->playSound3D(mPtr, sound, volume, pitch, MWSound::Type::Foot,
996 MWSound::PlayMode::NoPlayerLocal);
997 }
998 else
999 {
1000 sndMgr->playSound3D(mPtr, sound, volume, pitch);
1001 }
1002 }
1003 return;
1004 }
1005
1006 if(evt.compare(0, groupname.size(), groupname) != 0 ||
1007 evt.compare(groupname.size(), 2, ": ") != 0)
1008 {
1009 // Not ours, skip it
1010 return;
1011 }
1012 size_t off = groupname.size()+2;
1013 size_t len = evt.size() - off;
1014
1015 if(groupname == "shield" && evt.compare(off, len, "equip attach") == 0)
1016 mAnimation->showCarriedLeft(true);
1017 else if(groupname == "shield" && evt.compare(off, len, "unequip detach") == 0)
1018 mAnimation->showCarriedLeft(false);
1019 else if(evt.compare(off, len, "equip attach") == 0)
1020 mAnimation->showWeapons(true);
1021 else if(evt.compare(off, len, "unequip detach") == 0)
1022 mAnimation->showWeapons(false);
1023 else if(evt.compare(off, len, "chop hit") == 0)
1024 mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Chop);
1025 else if(evt.compare(off, len, "slash hit") == 0)
1026 mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Slash);
1027 else if(evt.compare(off, len, "thrust hit") == 0)
1028 mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Thrust);
1029 else if(evt.compare(off, len, "hit") == 0)
1030 {
1031 if (groupname == "attack1" || groupname == "swimattack1")
1032 mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Chop);
1033 else if (groupname == "attack2" || groupname == "swimattack2")
1034 mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Slash);
1035 else if (groupname == "attack3" || groupname == "swimattack3")
1036 mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Thrust);
1037 else
1038 mPtr.getClass().hit(mPtr, mAttackStrength);
1039 }
1040 else if (!groupname.empty()
1041 && (groupname.compare(0, groupname.size()-1, "attack") == 0 || groupname.compare(0, groupname.size()-1, "swimattack") == 0)
1042 && evt.compare(off, len, "start") == 0)
1043 {
1044 std::multimap<float, std::string>::const_iterator hitKey = key;
1045
1046 // Not all animations have a hit key defined. If there is none, the hit happens with the start key.
1047 bool hasHitKey = false;
1048 while (hitKey != map.end())
1049 {
1050 if (hitKey->second == groupname + ": hit")
1051 {
1052 hasHitKey = true;
1053 break;
1054 }
1055 if (hitKey->second == groupname + ": stop")
1056 break;
1057 ++hitKey;
1058 }
1059 if (!hasHitKey)
1060 {
1061 if (groupname == "attack1" || groupname == "swimattack1")
1062 mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Chop);
1063 else if (groupname == "attack2" || groupname == "swimattack2")
1064 mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Slash);
1065 else if (groupname == "attack3" || groupname == "swimattack3")
1066 mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Thrust);
1067 }
1068 }
1069 else if (evt.compare(off, len, "shoot attach") == 0)
1070 mAnimation->attachArrow();
1071 else if (evt.compare(off, len, "shoot release") == 0)
1072 mAnimation->releaseArrow(mAttackStrength);
1073 else if (evt.compare(off, len, "shoot follow attach") == 0)
1074 mAnimation->attachArrow();
1075
1076 else if (groupname == "spellcast" && evt.substr(evt.size()-7, 7) == "release"
1077 // Make sure this key is actually for the RangeType we are casting. The flame atronach has
1078 // the same animation for all range types, so there are 3 "release" keys on the same time, one for each range type.
1079 && evt.compare(off, len, mAttackType + " release") == 0)
1080 {
1081 MWBase::Environment::get().getWorld()->castSpell(mPtr, mCastingManualSpell);
1082 mCastingManualSpell = false;
1083 }
1084
1085 else if (groupname == "shield" && evt.compare(off, len, "block hit") == 0)
1086 mPtr.getClass().block(mPtr);
1087 else if (groupname == "containeropen" && evt.compare(off, len, "loot") == 0)
1088 MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Container, mPtr);
1089 }
1090
updatePtr(const MWWorld::Ptr & ptr)1091 void CharacterController::updatePtr(const MWWorld::Ptr &ptr)
1092 {
1093 mPtr = ptr;
1094 }
1095
updateIdleStormState(bool inwater)1096 void CharacterController::updateIdleStormState(bool inwater)
1097 {
1098 if (!mAnimation->hasAnimation("idlestorm") || mUpperBodyState != UpperCharState_Nothing || inwater)
1099 {
1100 mAnimation->disable("idlestorm");
1101 return;
1102 }
1103
1104 if (MWBase::Environment::get().getWorld()->isInStorm())
1105 {
1106 osg::Vec3f stormDirection = MWBase::Environment::get().getWorld()->getStormDirection();
1107 osg::Vec3f characterDirection = mPtr.getRefData().getBaseNode()->getAttitude() * osg::Vec3f(0,1,0);
1108 stormDirection.normalize();
1109 characterDirection.normalize();
1110 if (stormDirection * characterDirection < -0.5f)
1111 {
1112 if (!mAnimation->isPlaying("idlestorm"))
1113 {
1114 int mask = MWRender::Animation::BlendMask_Torso | MWRender::Animation::BlendMask_RightArm;
1115 mAnimation->play("idlestorm", Priority_Storm, mask, true, 1.0f, "start", "stop", 0.0f, ~0ul);
1116 }
1117 else
1118 {
1119 mAnimation->setLoopingEnabled("idlestorm", true);
1120 }
1121 return;
1122 }
1123 }
1124
1125 if (mAnimation->isPlaying("idlestorm"))
1126 {
1127 mAnimation->setLoopingEnabled("idlestorm", false);
1128 }
1129 }
1130
updateCreatureState()1131 bool CharacterController::updateCreatureState()
1132 {
1133 const MWWorld::Class &cls = mPtr.getClass();
1134 CreatureStats &stats = cls.getCreatureStats(mPtr);
1135
1136 int weapType = ESM::Weapon::None;
1137 if(stats.getDrawState() == DrawState_Weapon)
1138 weapType = ESM::Weapon::HandToHand;
1139 else if (stats.getDrawState() == DrawState_Spell)
1140 weapType = ESM::Weapon::Spell;
1141
1142 if (weapType != mWeaponType)
1143 {
1144 mWeaponType = weapType;
1145 if (mAnimation->isPlaying(mCurrentWeapon))
1146 mAnimation->disable(mCurrentWeapon);
1147 }
1148
1149 if(mAttackingOrSpell)
1150 {
1151 if(mUpperBodyState == UpperCharState_Nothing && mHitState == CharState_None)
1152 {
1153 MWBase::Environment::get().getWorld()->breakInvisibility(mPtr);
1154
1155 std::string startKey = "start";
1156 std::string stopKey = "stop";
1157 if (weapType == ESM::Weapon::Spell)
1158 {
1159 const std::string spellid = stats.getSpells().getSelectedSpell();
1160 bool canCast = mCastingManualSpell || MWBase::Environment::get().getWorld()->startSpellCast(mPtr);
1161
1162 if (!spellid.empty() && canCast)
1163 {
1164 MWMechanics::CastSpell cast(mPtr, nullptr, false, mCastingManualSpell);
1165 cast.playSpellCastingEffects(spellid, false);
1166
1167 if (!mAnimation->hasAnimation("spellcast"))
1168 {
1169 MWBase::Environment::get().getWorld()->castSpell(mPtr, mCastingManualSpell); // No "release" text key to use, so cast immediately
1170 mCastingManualSpell = false;
1171 }
1172 else
1173 {
1174 const ESM::Spell *spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find(spellid);
1175 const ESM::ENAMstruct &effectentry = spell->mEffects.mList.at(0);
1176
1177 switch(effectentry.mRange)
1178 {
1179 case 0: mAttackType = "self"; break;
1180 case 1: mAttackType = "touch"; break;
1181 case 2: mAttackType = "target"; break;
1182 }
1183
1184 startKey = mAttackType + " " + startKey;
1185 stopKey = mAttackType + " " + stopKey;
1186 mCurrentWeapon = "spellcast";
1187 }
1188 }
1189 else
1190 mCurrentWeapon = "";
1191 }
1192
1193 if (weapType != ESM::Weapon::Spell || !mAnimation->hasAnimation("spellcast")) // Not all creatures have a dedicated spellcast animation
1194 {
1195 mCurrentWeapon = chooseRandomAttackAnimation();
1196 }
1197
1198 if (!mCurrentWeapon.empty())
1199 {
1200 mAnimation->play(mCurrentWeapon, Priority_Weapon,
1201 MWRender::Animation::BlendMask_All, true,
1202 1, startKey, stopKey,
1203 0.0f, 0);
1204 mUpperBodyState = UpperCharState_StartToMinAttack;
1205
1206 mAttackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability());
1207
1208 if (weapType == ESM::Weapon::HandToHand)
1209 playSwishSound(0.0f);
1210 }
1211 }
1212
1213 mAttackingOrSpell = false;
1214 }
1215
1216 bool animPlaying = mAnimation->getInfo(mCurrentWeapon);
1217 if (!animPlaying)
1218 mUpperBodyState = UpperCharState_Nothing;
1219 return false;
1220 }
1221
updateCarriedLeftVisible(const int weaptype) const1222 bool CharacterController::updateCarriedLeftVisible(const int weaptype) const
1223 {
1224 // Shields/torches shouldn't be visible during any operation involving two hands
1225 // There seems to be no text keys for this purpose, except maybe for "[un]equip start/stop",
1226 // but they are also present in weapon drawing animation.
1227 return mAnimation->updateCarriedLeftVisible(weaptype);
1228 }
1229
updateWeaponState(CharacterState & idle)1230 bool CharacterController::updateWeaponState(CharacterState& idle)
1231 {
1232 const MWWorld::Class &cls = mPtr.getClass();
1233 CreatureStats &stats = cls.getCreatureStats(mPtr);
1234 int weaptype = ESM::Weapon::None;
1235 if(stats.getDrawState() == DrawState_Weapon)
1236 weaptype = ESM::Weapon::HandToHand;
1237 else if (stats.getDrawState() == DrawState_Spell)
1238 weaptype = ESM::Weapon::Spell;
1239
1240 const bool isWerewolf = cls.isNpc() && cls.getNpcStats(mPtr).isWerewolf();
1241
1242 std::string upSoundId;
1243 std::string downSoundId;
1244 bool weaponChanged = false;
1245 if (mPtr.getClass().hasInventoryStore(mPtr))
1246 {
1247 MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr);
1248 MWWorld::ContainerStoreIterator weapon = getActiveWeapon(mPtr, &weaptype);
1249 if(stats.getDrawState() == DrawState_Spell)
1250 weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
1251
1252 if(weapon != inv.end() && mWeaponType != ESM::Weapon::HandToHand && weaptype != ESM::Weapon::HandToHand && weaptype != ESM::Weapon::Spell && weaptype != ESM::Weapon::None)
1253 upSoundId = weapon->getClass().getUpSoundId(*weapon);
1254
1255 if(weapon != inv.end() && mWeaponType != ESM::Weapon::HandToHand && mWeaponType != ESM::Weapon::Spell && mWeaponType != ESM::Weapon::None)
1256 downSoundId = weapon->getClass().getDownSoundId(*weapon);
1257
1258 // weapon->HtH switch: weapon is empty already, so we need to take sound from previous weapon
1259 if(weapon == inv.end() && !mWeapon.isEmpty() && weaptype == ESM::Weapon::HandToHand && mWeaponType != ESM::Weapon::Spell)
1260 downSoundId = mWeapon.getClass().getDownSoundId(mWeapon);
1261
1262 MWWorld::Ptr newWeapon = weapon != inv.end() ? *weapon : MWWorld::Ptr();
1263
1264 if (mWeapon != newWeapon)
1265 {
1266 mWeapon = newWeapon;
1267 weaponChanged = true;
1268 }
1269 }
1270
1271 // For biped actors, blend weapon animations with lower body animations with higher priority
1272 MWRender::Animation::AnimPriority priorityWeapon(Priority_Weapon);
1273 if (mPtr.getClass().isBipedal(mPtr))
1274 priorityWeapon[MWRender::Animation::BoneGroup_LowerBody] = Priority_WeaponLowerBody;
1275
1276 bool forcestateupdate = false;
1277
1278 // We should not play equipping animation and sound during weapon->weapon transition
1279 bool isStillWeapon = weaptype != ESM::Weapon::HandToHand && weaptype != ESM::Weapon::Spell && weaptype != ESM::Weapon::None &&
1280 mWeaponType != ESM::Weapon::HandToHand && mWeaponType != ESM::Weapon::Spell && mWeaponType != ESM::Weapon::None;
1281
1282 // If the current weapon type was changed in the middle of attack (e.g. by Equip console command or when bound spell expires),
1283 // we should force actor to the "weapon equipped" state, interrupt attack and update animations.
1284 if (isStillWeapon && mWeaponType != weaptype && mUpperBodyState > UpperCharState_WeapEquiped)
1285 {
1286 forcestateupdate = true;
1287 mUpperBodyState = UpperCharState_WeapEquiped;
1288 mAttackingOrSpell = false;
1289 mAnimation->disable(mCurrentWeapon);
1290 mAnimation->showWeapons(true);
1291 if (mPtr == getPlayer())
1292 MWBase::Environment::get().getWorld()->getPlayer().setAttackingOrSpell(false);
1293 }
1294
1295 if(!isKnockedOut() && !isKnockedDown() && !isRecovery())
1296 {
1297 std::string weapgroup;
1298 if ((!isWerewolf || mWeaponType != ESM::Weapon::Spell)
1299 && weaptype != mWeaponType
1300 && mUpperBodyState != UpperCharState_UnEquipingWeap
1301 && !isStillWeapon)
1302 {
1303 // We can not play un-equip animation if weapon changed since last update
1304 if (!weaponChanged)
1305 {
1306 // Note: we do not disable unequipping animation automatically to avoid body desync
1307 weapgroup = getWeaponAnimation(mWeaponType);
1308 int unequipMask = MWRender::Animation::BlendMask_All;
1309 bool useShieldAnims = mAnimation->useShieldAnimations();
1310 if (useShieldAnims && mWeaponType != ESM::Weapon::HandToHand && mWeaponType != ESM::Weapon::Spell && !(mWeaponType == ESM::Weapon::None && weaptype == ESM::Weapon::Spell))
1311 {
1312 unequipMask = unequipMask |~MWRender::Animation::BlendMask_LeftArm;
1313 mAnimation->play("shield", Priority_Block,
1314 MWRender::Animation::BlendMask_LeftArm, true,
1315 1.0f, "unequip start", "unequip stop", 0.0f, 0);
1316 }
1317 else if (mWeaponType == ESM::Weapon::HandToHand)
1318 mAnimation->showCarriedLeft(false);
1319
1320 mAnimation->play(weapgroup, priorityWeapon, unequipMask, false,
1321 1.0f, "unequip start", "unequip stop", 0.0f, 0);
1322 mUpperBodyState = UpperCharState_UnEquipingWeap;
1323
1324 mAnimation->detachArrow();
1325
1326 // If we do not have the "unequip detach" key, hide weapon manually.
1327 if (mAnimation->getTextKeyTime(weapgroup+": unequip detach") < 0)
1328 mAnimation->showWeapons(false);
1329 }
1330
1331 if(!downSoundId.empty())
1332 {
1333 MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
1334 sndMgr->playSound3D(mPtr, downSoundId, 1.0f, 1.0f);
1335 }
1336 }
1337
1338 float complete;
1339 bool animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete);
1340 if (!animPlaying || complete >= 1.0f)
1341 {
1342 // Weapon is changed, no current animation (e.g. unequipping or attack).
1343 // Start equipping animation now.
1344 if (weaptype != mWeaponType)
1345 {
1346 forcestateupdate = true;
1347 bool useShieldAnims = mAnimation->useShieldAnimations();
1348 if (!useShieldAnims)
1349 mAnimation->showCarriedLeft(updateCarriedLeftVisible(weaptype));
1350
1351 weapgroup = getWeaponAnimation(weaptype);
1352
1353 // Note: controllers for ranged weapon should use time for beginning of animation to play shooting properly,
1354 // for other weapons they should use absolute time. Some mods rely on this behaviour (to rotate throwing projectiles, for example)
1355 ESM::WeaponType::Class weaponClass = getWeaponType(weaptype)->mWeaponClass;
1356 bool useRelativeDuration = weaponClass == ESM::WeaponType::Ranged;
1357 mAnimation->setWeaponGroup(weapgroup, useRelativeDuration);
1358
1359 if (!isStillWeapon)
1360 {
1361 mAnimation->disable(mCurrentWeapon);
1362 if (weaptype != ESM::Weapon::None)
1363 {
1364 mAnimation->showWeapons(false);
1365 int equipMask = MWRender::Animation::BlendMask_All;
1366 if (useShieldAnims && weaptype != ESM::Weapon::Spell)
1367 {
1368 equipMask = equipMask |~MWRender::Animation::BlendMask_LeftArm;
1369 mAnimation->play("shield", Priority_Block,
1370 MWRender::Animation::BlendMask_LeftArm, true,
1371 1.0f, "equip start", "equip stop", 0.0f, 0);
1372 }
1373
1374 mAnimation->play(weapgroup, priorityWeapon, equipMask, true,
1375 1.0f, "equip start", "equip stop", 0.0f, 0);
1376 mUpperBodyState = UpperCharState_EquipingWeap;
1377
1378 // If we do not have the "equip attach" key, show weapon manually.
1379 if (weaptype != ESM::Weapon::Spell)
1380 {
1381 if (mAnimation->getTextKeyTime(weapgroup+": equip attach") < 0)
1382 mAnimation->showWeapons(true);
1383 }
1384 }
1385 }
1386
1387 if(isWerewolf)
1388 {
1389 const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
1390 const ESM::Sound *sound = store.get<ESM::Sound>().searchRandom("WolfEquip");
1391 if(sound)
1392 {
1393 MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
1394 sndMgr->playSound3D(mPtr, sound->mId, 1.0f, 1.0f);
1395 }
1396 }
1397
1398 mWeaponType = weaptype;
1399 mCurrentWeapon = getWeaponAnimation(mWeaponType);
1400
1401 if(!upSoundId.empty() && !isStillWeapon)
1402 {
1403 MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
1404 sndMgr->playSound3D(mPtr, upSoundId, 1.0f, 1.0f);
1405 }
1406 }
1407
1408 // Make sure that we disabled unequipping animation
1409 if (mUpperBodyState == UpperCharState_UnEquipingWeap)
1410 {
1411 mUpperBodyState = UpperCharState_Nothing;
1412 mAnimation->disable(mCurrentWeapon);
1413 mWeaponType = ESM::Weapon::None;
1414 mCurrentWeapon = getWeaponAnimation(mWeaponType);
1415 }
1416 }
1417 }
1418
1419 if(isWerewolf)
1420 {
1421 MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
1422 if(cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run)
1423 && mHasMovedInXY
1424 && !MWBase::Environment::get().getWorld()->isSwimming(mPtr)
1425 && mWeaponType == ESM::Weapon::None)
1426 {
1427 if(!sndMgr->getSoundPlaying(mPtr, "WolfRun"))
1428 sndMgr->playSound3D(mPtr, "WolfRun", 1.0f, 1.0f, MWSound::Type::Sfx,
1429 MWSound::PlayMode::Loop);
1430 }
1431 else
1432 sndMgr->stopSound3D(mPtr, "WolfRun");
1433 }
1434
1435 // Cancel attack if we no longer have ammunition
1436 bool ammunition = true;
1437 bool isWeapon = false;
1438 float weapSpeed = 1.f;
1439 if (mPtr.getClass().hasInventoryStore(mPtr))
1440 {
1441 MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr);
1442 MWWorld::ConstContainerStoreIterator weapon = getActiveWeapon(mPtr, &weaptype);
1443 isWeapon = (weapon != inv.end() && weapon->getTypeName() == typeid(ESM::Weapon).name());
1444 if (isWeapon)
1445 {
1446 weapSpeed = weapon->get<ESM::Weapon>()->mBase->mData.mSpeed;
1447 MWWorld::ConstContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition);
1448 int ammotype = getWeaponType(weapon->get<ESM::Weapon>()->mBase->mData.mType)->mAmmoType;
1449 if (ammotype != ESM::Weapon::None && (ammo == inv.end() || ammo->get<ESM::Weapon>()->mBase->mData.mType != ammotype))
1450 ammunition = false;
1451 }
1452
1453 if (!ammunition && mUpperBodyState > UpperCharState_WeapEquiped)
1454 {
1455 mAnimation->disable(mCurrentWeapon);
1456 mUpperBodyState = UpperCharState_WeapEquiped;
1457 }
1458 }
1459
1460 // Combat for actors with persistent animations obviously will be buggy
1461 if (isPersistentAnimPlaying())
1462 return forcestateupdate;
1463
1464 float complete;
1465 bool animPlaying;
1466 ESM::WeaponType::Class weapclass = getWeaponType(mWeaponType)->mWeaponClass;
1467 if(mAttackingOrSpell)
1468 {
1469 MWWorld::Ptr player = getPlayer();
1470
1471 bool resetIdle = ammunition;
1472 if(mUpperBodyState == UpperCharState_WeapEquiped && (mHitState == CharState_None || mHitState == CharState_Block))
1473 {
1474 MWBase::Environment::get().getWorld()->breakInvisibility(mPtr);
1475 mAttackStrength = 0;
1476
1477 // Randomize attacks for non-bipedal creatures with Weapon flag
1478 if (mPtr.getClass().getTypeName() == typeid(ESM::Creature).name() &&
1479 !mPtr.getClass().isBipedal(mPtr) &&
1480 (!mAnimation->hasAnimation(mCurrentWeapon) || isRandomAttackAnimation(mCurrentWeapon)))
1481 {
1482 mCurrentWeapon = chooseRandomAttackAnimation();
1483 }
1484
1485 if(mWeaponType == ESM::Weapon::Spell)
1486 {
1487 // Unset casting flag, otherwise pressing the mouse button down would
1488 // continue casting every frame if there is no animation
1489 mAttackingOrSpell = false;
1490 if (mPtr == player)
1491 {
1492 MWBase::Environment::get().getWorld()->getPlayer().setAttackingOrSpell(false);
1493
1494 // For the player, set the spell we want to cast
1495 // This has to be done at the start of the casting animation,
1496 // *not* when selecting a spell in the GUI (otherwise you could change the spell mid-animation)
1497 std::string selectedSpell = MWBase::Environment::get().getWindowManager()->getSelectedSpell();
1498 stats.getSpells().setSelectedSpell(selectedSpell);
1499 }
1500 std::string spellid = stats.getSpells().getSelectedSpell();
1501 bool isMagicItem = false;
1502 bool canCast = mCastingManualSpell || MWBase::Environment::get().getWorld()->startSpellCast(mPtr);
1503
1504 if (spellid.empty())
1505 {
1506 if (mPtr.getClass().hasInventoryStore(mPtr))
1507 {
1508 MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr);
1509 if (inv.getSelectedEnchantItem() != inv.end())
1510 {
1511 const MWWorld::Ptr& enchantItem = *inv.getSelectedEnchantItem();
1512 spellid = enchantItem.getClass().getEnchantment(enchantItem);
1513 isMagicItem = true;
1514 }
1515 }
1516 }
1517
1518 static const bool useCastingAnimations = Settings::Manager::getBool("use magic item animations", "Game");
1519 if (isMagicItem && !useCastingAnimations)
1520 {
1521 // Enchanted items by default do not use casting animations
1522 MWBase::Environment::get().getWorld()->castSpell(mPtr);
1523 resetIdle = false;
1524 }
1525 else if(!spellid.empty() && canCast)
1526 {
1527 MWMechanics::CastSpell cast(mPtr, nullptr, false, mCastingManualSpell);
1528 cast.playSpellCastingEffects(spellid, isMagicItem);
1529
1530 std::vector<ESM::ENAMstruct> effects;
1531 const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
1532 if (isMagicItem)
1533 {
1534 const ESM::Enchantment *enchantment = store.get<ESM::Enchantment>().find(spellid);
1535 effects = enchantment->mEffects.mList;
1536 }
1537 else
1538 {
1539 const ESM::Spell *spell = store.get<ESM::Spell>().find(spellid);
1540 effects = spell->mEffects.mList;
1541 }
1542
1543 const ESM::MagicEffect *effect = store.get<ESM::MagicEffect>().find(effects.back().mEffectID); // use last effect of list for color of VFX_Hands
1544
1545 const ESM::Static* castStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find ("VFX_Hands");
1546
1547 for (size_t iter = 0; iter < effects.size(); ++iter) // play hands vfx for each effect
1548 {
1549 if (mAnimation->getNode("Bip01 L Hand"))
1550 mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Bip01 L Hand", effect->mParticle);
1551
1552 if (mAnimation->getNode("Bip01 R Hand"))
1553 mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Bip01 R Hand", effect->mParticle);
1554 }
1555
1556 const ESM::ENAMstruct &firstEffect = effects.at(0); // first effect used for casting animation
1557
1558 std::string startKey;
1559 std::string stopKey;
1560 if (isRandomAttackAnimation(mCurrentWeapon))
1561 {
1562 startKey = "start";
1563 stopKey = "stop";
1564 MWBase::Environment::get().getWorld()->castSpell(mPtr, mCastingManualSpell); // No "release" text key to use, so cast immediately
1565 mCastingManualSpell = false;
1566 }
1567 else
1568 {
1569 switch(firstEffect.mRange)
1570 {
1571 case 0: mAttackType = "self"; break;
1572 case 1: mAttackType = "touch"; break;
1573 case 2: mAttackType = "target"; break;
1574 }
1575
1576 startKey = mAttackType+" start";
1577 stopKey = mAttackType+" stop";
1578 }
1579
1580 mAnimation->play(mCurrentWeapon, priorityWeapon,
1581 MWRender::Animation::BlendMask_All, true,
1582 1, startKey, stopKey,
1583 0.0f, 0);
1584 mUpperBodyState = UpperCharState_CastingSpell;
1585 }
1586 else
1587 {
1588 resetIdle = false;
1589 }
1590 }
1591 else if(mWeaponType == ESM::Weapon::PickProbe)
1592 {
1593 MWWorld::ContainerStoreIterator weapon = mPtr.getClass().getInventoryStore(mPtr).getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
1594 MWWorld::Ptr item = *weapon;
1595 // TODO: this will only work for the player, and needs to be fixed if NPCs should ever use lockpicks/probes.
1596 MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getFacedObject();
1597 std::string resultMessage, resultSound;
1598
1599 if(!target.isEmpty())
1600 {
1601 if(item.getTypeName() == typeid(ESM::Lockpick).name())
1602 Security(mPtr).pickLock(target, item, resultMessage, resultSound);
1603 else if(item.getTypeName() == typeid(ESM::Probe).name())
1604 Security(mPtr).probeTrap(target, item, resultMessage, resultSound);
1605 }
1606 mAnimation->play(mCurrentWeapon, priorityWeapon,
1607 MWRender::Animation::BlendMask_All, true,
1608 1.0f, "start", "stop", 0.0, 0);
1609 mUpperBodyState = UpperCharState_FollowStartToFollowStop;
1610
1611 if(!resultMessage.empty())
1612 MWBase::Environment::get().getWindowManager()->messageBox(resultMessage);
1613 if(!resultSound.empty())
1614 MWBase::Environment::get().getSoundManager()->playSound3D(target, resultSound,
1615 1.0f, 1.0f);
1616 }
1617 else if (ammunition)
1618 {
1619 std::string startKey;
1620 std::string stopKey;
1621
1622 if(weapclass == ESM::WeaponType::Ranged || weapclass == ESM::WeaponType::Thrown)
1623 {
1624 mAttackType = "shoot";
1625 startKey = mAttackType+" start";
1626 stopKey = mAttackType+" min attack";
1627 }
1628 else if (isRandomAttackAnimation(mCurrentWeapon))
1629 {
1630 startKey = "start";
1631 stopKey = "stop";
1632 }
1633 else
1634 {
1635 if(mPtr == getPlayer())
1636 {
1637 if (Settings::Manager::getBool("best attack", "Game"))
1638 {
1639 if (isWeapon)
1640 {
1641 MWWorld::ConstContainerStoreIterator weapon = mPtr.getClass().getInventoryStore(mPtr).getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
1642 mAttackType = getBestAttack(weapon->get<ESM::Weapon>()->mBase);
1643 }
1644 else
1645 {
1646 // There is no "best attack" for Hand-to-Hand
1647 setAttackTypeRandomly(mAttackType);
1648 }
1649 }
1650 else
1651 {
1652 setAttackTypeBasedOnMovement();
1653 }
1654 }
1655 // else if (mPtr != getPlayer()) use mAttackType set by AiCombat
1656 startKey = mAttackType+" start";
1657 stopKey = mAttackType+" min attack";
1658 }
1659
1660 mAnimation->play(mCurrentWeapon, priorityWeapon,
1661 MWRender::Animation::BlendMask_All, false,
1662 weapSpeed, startKey, stopKey,
1663 0.0f, 0);
1664 mUpperBodyState = UpperCharState_StartToMinAttack;
1665 }
1666 }
1667
1668 // We should not break swim and sneak animations
1669 if (resetIdle &&
1670 idle != CharState_IdleSneak && idle != CharState_IdleSwim &&
1671 mIdleState != CharState_IdleSneak && mIdleState != CharState_IdleSwim)
1672 {
1673 mAnimation->disable(mCurrentIdle);
1674 mIdleState = CharState_None;
1675 }
1676
1677 animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete);
1678 if(mUpperBodyState == UpperCharState_MinAttackToMaxAttack && !isKnockedDown())
1679 mAttackStrength = complete;
1680 }
1681 else
1682 {
1683 animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete);
1684 if(mUpperBodyState == UpperCharState_MinAttackToMaxAttack && !isKnockedDown())
1685 {
1686 float attackStrength = complete;
1687 float minAttackTime = mAnimation->getTextKeyTime(mCurrentWeapon+": "+mAttackType+" "+"min attack");
1688 float maxAttackTime = mAnimation->getTextKeyTime(mCurrentWeapon+": "+mAttackType+" "+"max attack");
1689 if (minAttackTime == maxAttackTime)
1690 {
1691 // most creatures don't actually have an attack wind-up animation, so use a uniform random value
1692 // (even some creatures that can use weapons don't have a wind-up animation either, e.g. Rieklings)
1693 // Note: vanilla MW uses a random value for *all* non-player actors, but we probably don't need to go that far.
1694 attackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability());
1695 }
1696
1697 if(weapclass != ESM::WeaponType::Ranged && weapclass != ESM::WeaponType::Thrown)
1698 {
1699 MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
1700
1701 if(isWerewolf)
1702 {
1703 const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
1704 const ESM::Sound *sound = store.get<ESM::Sound>().searchRandom("WolfSwing");
1705 if(sound)
1706 sndMgr->playSound3D(mPtr, sound->mId, 1.0f, 1.0f);
1707 }
1708 else
1709 {
1710 playSwishSound(attackStrength);
1711 }
1712 }
1713 mAttackStrength = attackStrength;
1714
1715 mAnimation->disable(mCurrentWeapon);
1716 mAnimation->play(mCurrentWeapon, priorityWeapon,
1717 MWRender::Animation::BlendMask_All, false,
1718 weapSpeed, mAttackType+" max attack", mAttackType+" min hit",
1719 1.0f-complete, 0);
1720
1721 complete = 0.f;
1722 mUpperBodyState = UpperCharState_MaxAttackToMinHit;
1723 }
1724 else if (isKnockedDown())
1725 {
1726 if (mUpperBodyState > UpperCharState_WeapEquiped)
1727 {
1728 mUpperBodyState = UpperCharState_WeapEquiped;
1729 if (mWeaponType > ESM::Weapon::None)
1730 mAnimation->showWeapons(true);
1731 }
1732 mAnimation->disable(mCurrentWeapon);
1733 }
1734 }
1735
1736 mAnimation->setPitchFactor(0.f);
1737 if (weapclass == ESM::WeaponType::Ranged || weapclass == ESM::WeaponType::Thrown)
1738 {
1739 switch (mUpperBodyState)
1740 {
1741 case UpperCharState_StartToMinAttack:
1742 mAnimation->setPitchFactor(complete);
1743 break;
1744 case UpperCharState_MinAttackToMaxAttack:
1745 case UpperCharState_MaxAttackToMinHit:
1746 case UpperCharState_MinHitToHit:
1747 mAnimation->setPitchFactor(1.f);
1748 break;
1749 case UpperCharState_FollowStartToFollowStop:
1750 if (animPlaying)
1751 {
1752 // technically we do not need a pitch for crossbow reload animation,
1753 // but we should avoid abrupt repositioning
1754 if (mWeaponType == ESM::Weapon::MarksmanCrossbow)
1755 mAnimation->setPitchFactor(std::max(0.f, 1.f-complete*10.f));
1756 else
1757 mAnimation->setPitchFactor(1.f-complete);
1758 }
1759 break;
1760 default:
1761 break;
1762 }
1763 }
1764
1765 if(!animPlaying)
1766 {
1767 if(mUpperBodyState == UpperCharState_EquipingWeap ||
1768 mUpperBodyState == UpperCharState_FollowStartToFollowStop ||
1769 mUpperBodyState == UpperCharState_CastingSpell)
1770 {
1771 if (ammunition && mWeaponType == ESM::Weapon::MarksmanCrossbow)
1772 mAnimation->attachArrow();
1773
1774 mUpperBodyState = UpperCharState_WeapEquiped;
1775 }
1776 else if(mUpperBodyState == UpperCharState_UnEquipingWeap)
1777 mUpperBodyState = UpperCharState_Nothing;
1778 }
1779 else if(complete >= 1.0f && !isRandomAttackAnimation(mCurrentWeapon))
1780 {
1781 std::string start, stop;
1782 switch(mUpperBodyState)
1783 {
1784 case UpperCharState_MinAttackToMaxAttack:
1785 //hack to avoid body pos desync when jumping/sneaking in 'max attack' state
1786 if(!mAnimation->isPlaying(mCurrentWeapon))
1787 mAnimation->play(mCurrentWeapon, priorityWeapon,
1788 MWRender::Animation::BlendMask_All, false,
1789 0, mAttackType+" min attack", mAttackType+" max attack", 0.999f, 0);
1790 break;
1791 case UpperCharState_StartToMinAttack:
1792 case UpperCharState_MaxAttackToMinHit:
1793 {
1794 if (mUpperBodyState == UpperCharState_StartToMinAttack)
1795 {
1796 // If actor is already stopped preparing attack, do not play the "min attack -> max attack" part.
1797 // Happens if the player did not hold the attack button.
1798 // Note: if the "min attack"->"max attack" is a stub, "play" it anyway. Attack strength will be random.
1799 float minAttackTime = mAnimation->getTextKeyTime(mCurrentWeapon+": "+mAttackType+" "+"min attack");
1800 float maxAttackTime = mAnimation->getTextKeyTime(mCurrentWeapon+": "+mAttackType+" "+"max attack");
1801 if (mAttackingOrSpell || minAttackTime == maxAttackTime)
1802 {
1803 start = mAttackType+" min attack";
1804 stop = mAttackType+" max attack";
1805 mUpperBodyState = UpperCharState_MinAttackToMaxAttack;
1806 break;
1807 }
1808
1809 if(weapclass != ESM::WeaponType::Ranged && weapclass != ESM::WeaponType::Thrown)
1810 playSwishSound(0.0f);
1811 }
1812
1813 if(mAttackType == "shoot")
1814 {
1815 start = mAttackType+" min hit";
1816 stop = mAttackType+" release";
1817 }
1818 else
1819 {
1820 start = mAttackType+" min hit";
1821 stop = mAttackType+" hit";
1822 }
1823 mUpperBodyState = UpperCharState_MinHitToHit;
1824 break;
1825 }
1826 case UpperCharState_MinHitToHit:
1827 if(mAttackType == "shoot")
1828 {
1829 start = mAttackType+" follow start";
1830 stop = mAttackType+" follow stop";
1831 }
1832 else
1833 {
1834 float str = mAttackStrength;
1835 start = mAttackType+((str < 0.5f) ? " small follow start"
1836 : (str < 1.0f) ? " medium follow start"
1837 : " large follow start");
1838 stop = mAttackType+((str < 0.5f) ? " small follow stop"
1839 : (str < 1.0f) ? " medium follow stop"
1840 : " large follow stop");
1841 }
1842 mUpperBodyState = UpperCharState_FollowStartToFollowStop;
1843 break;
1844 default:
1845 break;
1846 }
1847
1848 // Note: apply crossbow reload animation only for upper body
1849 // since blending with movement animations can give weird result.
1850 if(!start.empty())
1851 {
1852 int mask = MWRender::Animation::BlendMask_All;
1853 if (mWeaponType == ESM::Weapon::MarksmanCrossbow)
1854 mask = MWRender::Animation::BlendMask_UpperBody;
1855
1856 mAnimation->disable(mCurrentWeapon);
1857 if (mUpperBodyState == UpperCharState_FollowStartToFollowStop)
1858 mAnimation->play(mCurrentWeapon, priorityWeapon,
1859 mask, true,
1860 weapSpeed, start, stop, 0.0f, 0);
1861 else
1862 mAnimation->play(mCurrentWeapon, priorityWeapon,
1863 mask, false,
1864 weapSpeed, start, stop, 0.0f, 0);
1865 }
1866 }
1867 else if(complete >= 1.0f && isRandomAttackAnimation(mCurrentWeapon))
1868 {
1869 mAnimation->disable(mCurrentWeapon);
1870 mUpperBodyState = UpperCharState_WeapEquiped;
1871 }
1872
1873 if (mPtr.getClass().hasInventoryStore(mPtr))
1874 {
1875 const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr);
1876 MWWorld::ConstContainerStoreIterator torch = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
1877 if(torch != inv.end() && torch->getTypeName() == typeid(ESM::Light).name()
1878 && updateCarriedLeftVisible(mWeaponType))
1879 {
1880 if (mAnimation->isPlaying("shield"))
1881 mAnimation->disable("shield");
1882
1883 mAnimation->play("torch", Priority_Torch, MWRender::Animation::BlendMask_LeftArm,
1884 false, 1.0f, "start", "stop", 0.0f, (~(size_t)0), true);
1885 }
1886 else if (mAnimation->isPlaying("torch"))
1887 {
1888 mAnimation->disable("torch");
1889 }
1890 }
1891
1892 mAnimation->setAccurateAiming(mUpperBodyState > UpperCharState_WeapEquiped);
1893
1894 return forcestateupdate;
1895 }
1896
updateAnimQueue()1897 void CharacterController::updateAnimQueue()
1898 {
1899 if(mAnimQueue.size() > 1)
1900 {
1901 if(mAnimation->isPlaying(mAnimQueue.front().mGroup) == false)
1902 {
1903 mAnimation->disable(mAnimQueue.front().mGroup);
1904 mAnimQueue.pop_front();
1905
1906 bool loopfallback = (mAnimQueue.front().mGroup.compare(0,4,"idle") == 0);
1907 mAnimation->play(mAnimQueue.front().mGroup, Priority_Default,
1908 MWRender::Animation::BlendMask_All, false,
1909 1.0f, "start", "stop", 0.0f, mAnimQueue.front().mLoopCount, loopfallback);
1910 }
1911 }
1912
1913 if(!mAnimQueue.empty())
1914 mAnimation->setLoopingEnabled(mAnimQueue.front().mGroup, mAnimQueue.size() <= 1);
1915 }
1916
update(float duration)1917 void CharacterController::update(float duration)
1918 {
1919 MWBase::World *world = MWBase::Environment::get().getWorld();
1920 const MWWorld::Class &cls = mPtr.getClass();
1921 osg::Vec3f movement(0.f, 0.f, 0.f);
1922 float speed = 0.f;
1923
1924 updateMagicEffects();
1925
1926 if (isKnockedOut())
1927 mTimeUntilWake -= duration;
1928
1929 bool isPlayer = mPtr == MWMechanics::getPlayer();
1930 bool isFirstPersonPlayer = isPlayer && MWBase::Environment::get().getWorld()->isFirstPerson();
1931 bool godmode = isPlayer && MWBase::Environment::get().getWorld()->getGodModeState();
1932
1933 float scale = mPtr.getCellRef().getScale();
1934
1935 static const bool normalizeSpeed = Settings::Manager::getBool("normalise race speed", "Game");
1936 if (!normalizeSpeed && mPtr.getClass().isNpc())
1937 {
1938 const ESM::NPC* npc = mPtr.get<ESM::NPC>()->mBase;
1939 const ESM::Race* race = world->getStore().get<ESM::Race>().find(npc->mRace);
1940 float weight = npc->isMale() ? race->mData.mWeight.mMale : race->mData.mWeight.mFemale;
1941 scale *= weight;
1942 }
1943
1944 if(!cls.isActor())
1945 updateAnimQueue();
1946 else if(!cls.getCreatureStats(mPtr).isDead())
1947 {
1948 bool onground = world->isOnGround(mPtr);
1949 bool incapacitated = ((!godmode && cls.getCreatureStats(mPtr).isParalyzed()) || cls.getCreatureStats(mPtr).getKnockedDown());
1950 bool inwater = world->isSwimming(mPtr);
1951 bool flying = world->isFlying(mPtr);
1952 bool solid = world->isActorCollisionEnabled(mPtr);
1953 // Can't run and sneak while flying (see speed formula in Npc/Creature::getSpeed)
1954 bool sneak = cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Sneak) && !flying;
1955 bool isrunning = cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run) && !flying;
1956 CreatureStats &stats = cls.getCreatureStats(mPtr);
1957 Movement& movementSettings = cls.getMovementSettings(mPtr);
1958
1959 //Force Jump Logic
1960
1961 bool isMoving = (std::abs(movementSettings.mPosition[0]) > .5 || std::abs(movementSettings.mPosition[1]) > .5);
1962 if(!inwater && !flying && solid)
1963 {
1964 //Force Jump
1965 if(stats.getMovementFlag(MWMechanics::CreatureStats::Flag_ForceJump))
1966 movementSettings.mPosition[2] = onground ? 1 : 0;
1967 //Force Move Jump, only jump if they're otherwise moving
1968 if(stats.getMovementFlag(MWMechanics::CreatureStats::Flag_ForceMoveJump) && isMoving)
1969 movementSettings.mPosition[2] = onground ? 1 : 0;
1970 }
1971
1972 osg::Vec3f rot = cls.getRotationVector(mPtr);
1973 osg::Vec3f vec(movementSettings.asVec3());
1974 movementSettings.mSpeedFactor = std::min(vec.length(), 1.f);
1975 vec.normalize();
1976
1977 // TODO: Move this check to mwinput.
1978 // Joystick analogue movement.
1979 // Due to the half way split between walking/running, we multiply speed by 2 while walking, unless a keyboard was used.
1980 if (isPlayer && !isrunning && !sneak && !flying && movementSettings.mSpeedFactor <= 0.5f)
1981 movementSettings.mSpeedFactor *= 2.f;
1982
1983 static const bool smoothMovement = Settings::Manager::getBool("smooth movement", "Game");
1984 if (smoothMovement)
1985 {
1986 static const float playerTurningCoef = 1.0 / std::max(0.01f, Settings::Manager::getFloat("smooth movement player turning delay", "Game"));
1987 float angle = mPtr.getRefData().getPosition().rot[2];
1988 osg::Vec2f targetSpeed = Misc::rotateVec2f(osg::Vec2f(vec.x(), vec.y()), -angle) * movementSettings.mSpeedFactor;
1989 osg::Vec2f delta = targetSpeed - mSmoothedSpeed;
1990 float speedDelta = movementSettings.mSpeedFactor - mSmoothedSpeed.length();
1991 float deltaLen = delta.length();
1992
1993 float maxDelta;
1994 if (isFirstPersonPlayer)
1995 maxDelta = 1;
1996 else if (std::abs(speedDelta) < deltaLen / 2)
1997 // Turning is smooth for player and less smooth for NPCs (otherwise NPC can miss a path point).
1998 maxDelta = duration * (isPlayer ? playerTurningCoef : 6.f);
1999 else if (isPlayer && speedDelta < -deltaLen / 2)
2000 // As soon as controls are released, mwinput switches player from running to walking.
2001 // So stopping should be instant for player, otherwise it causes a small twitch.
2002 maxDelta = 1;
2003 else // In all other cases speeding up and stopping are smooth.
2004 maxDelta = duration * 3.f;
2005
2006 if (deltaLen > maxDelta)
2007 delta *= maxDelta / deltaLen;
2008 mSmoothedSpeed += delta;
2009
2010 osg::Vec2f newSpeed = Misc::rotateVec2f(mSmoothedSpeed, angle);
2011 movementSettings.mSpeedFactor = newSpeed.normalize();
2012 vec.x() = newSpeed.x();
2013 vec.y() = newSpeed.y();
2014
2015 const float eps = 0.001f;
2016 if (movementSettings.mSpeedFactor < eps)
2017 {
2018 movementSettings.mSpeedFactor = 0;
2019 vec.x() = 0;
2020 vec.y() = 1;
2021 }
2022 else if ((vec.y() < 0) != mIsMovingBackward)
2023 {
2024 if (targetSpeed.length() < eps || (movementSettings.mPosition[1] < 0) == mIsMovingBackward)
2025 vec.y() = mIsMovingBackward ? -eps : eps;
2026 }
2027 vec.normalize();
2028 }
2029
2030 float effectiveRotation = rot.z();
2031 bool canMove = cls.getMaxSpeed(mPtr) > 0;
2032 static const bool turnToMovementDirection = Settings::Manager::getBool("turn to movement direction", "Game");
2033 if (!turnToMovementDirection || isFirstPersonPlayer)
2034 {
2035 movementSettings.mIsStrafing = std::abs(vec.x()) > std::abs(vec.y()) * 2;
2036 stats.setSideMovementAngle(0);
2037 }
2038 else if (canMove)
2039 {
2040 float targetMovementAngle = vec.y() >= 0 ? std::atan2(-vec.x(), vec.y()) : std::atan2(vec.x(), -vec.y());
2041 movementSettings.mIsStrafing = (stats.getDrawState() != MWMechanics::DrawState_Nothing || inwater)
2042 && std::abs(targetMovementAngle) > osg::DegreesToRadians(60.0f);
2043 if (movementSettings.mIsStrafing)
2044 targetMovementAngle = 0;
2045 float delta = targetMovementAngle - stats.getSideMovementAngle();
2046 float cosDelta = cosf(delta);
2047
2048 if ((vec.y() < 0) == mIsMovingBackward)
2049 movementSettings.mSpeedFactor *= std::min(std::max(cosDelta, 0.f) + 0.3f, 1.f); // slow down when turn
2050 if (std::abs(delta) < osg::DegreesToRadians(20.0f))
2051 mIsMovingBackward = vec.y() < 0;
2052
2053 float maxDelta = osg::PI * duration * (2.5f - cosDelta);
2054 delta = osg::clampBetween(delta, -maxDelta, maxDelta);
2055 stats.setSideMovementAngle(stats.getSideMovementAngle() + delta);
2056 effectiveRotation += delta;
2057 }
2058
2059 mAnimation->setLegsYawRadians(stats.getSideMovementAngle());
2060 if (stats.getDrawState() == MWMechanics::DrawState_Nothing || inwater)
2061 mAnimation->setUpperBodyYawRadians(stats.getSideMovementAngle() / 2);
2062 else
2063 mAnimation->setUpperBodyYawRadians(stats.getSideMovementAngle() / 4);
2064 if (smoothMovement && !isPlayer && !inwater)
2065 mAnimation->setUpperBodyYawRadians(mAnimation->getUpperBodyYawRadians() + mAnimation->getHeadYaw() / 2);
2066
2067 speed = cls.getCurrentSpeed(mPtr);
2068 vec.x() *= speed;
2069 vec.y() *= speed;
2070
2071 if(mHitState != CharState_None && mJumpState == JumpState_None)
2072 vec = osg::Vec3f();
2073
2074 CharacterState movestate = CharState_None;
2075 CharacterState idlestate = CharState_SpecialIdle;
2076 JumpingState jumpstate = JumpState_None;
2077
2078 bool forcestateupdate = false;
2079
2080 mHasMovedInXY = std::abs(vec.x())+std::abs(vec.y()) > 0.0f;
2081 isrunning = isrunning && mHasMovedInXY;
2082
2083 // advance athletics
2084 if(mHasMovedInXY && isPlayer)
2085 {
2086 if(inwater)
2087 {
2088 mSecondsOfSwimming += duration;
2089 while(mSecondsOfSwimming > 1)
2090 {
2091 cls.skillUsageSucceeded(mPtr, ESM::Skill::Athletics, 1);
2092 mSecondsOfSwimming -= 1;
2093 }
2094 }
2095 else if(isrunning && !sneak)
2096 {
2097 mSecondsOfRunning += duration;
2098 while(mSecondsOfRunning > 1)
2099 {
2100 cls.skillUsageSucceeded(mPtr, ESM::Skill::Athletics, 0);
2101 mSecondsOfRunning -= 1;
2102 }
2103 }
2104 }
2105
2106 // reduce fatigue
2107 const MWWorld::Store<ESM::GameSetting> &gmst = world->getStore().get<ESM::GameSetting>();
2108 float fatigueLoss = 0;
2109 static const float fFatigueRunBase = gmst.find("fFatigueRunBase")->mValue.getFloat();
2110 static const float fFatigueRunMult = gmst.find("fFatigueRunMult")->mValue.getFloat();
2111 static const float fFatigueSwimWalkBase = gmst.find("fFatigueSwimWalkBase")->mValue.getFloat();
2112 static const float fFatigueSwimRunBase = gmst.find("fFatigueSwimRunBase")->mValue.getFloat();
2113 static const float fFatigueSwimWalkMult = gmst.find("fFatigueSwimWalkMult")->mValue.getFloat();
2114 static const float fFatigueSwimRunMult = gmst.find("fFatigueSwimRunMult")->mValue.getFloat();
2115 static const float fFatigueSneakBase = gmst.find("fFatigueSneakBase")->mValue.getFloat();
2116 static const float fFatigueSneakMult = gmst.find("fFatigueSneakMult")->mValue.getFloat();
2117
2118 if (cls.getEncumbrance(mPtr) <= cls.getCapacity(mPtr))
2119 {
2120 const float encumbrance = cls.getNormalizedEncumbrance(mPtr);
2121 if (sneak)
2122 fatigueLoss = fFatigueSneakBase + encumbrance * fFatigueSneakMult;
2123 else
2124 {
2125 if (inwater)
2126 {
2127 if (!isrunning)
2128 fatigueLoss = fFatigueSwimWalkBase + encumbrance * fFatigueSwimWalkMult;
2129 else
2130 fatigueLoss = fFatigueSwimRunBase + encumbrance * fFatigueSwimRunMult;
2131 }
2132 else if (isrunning)
2133 fatigueLoss = fFatigueRunBase + encumbrance * fFatigueRunMult;
2134 }
2135 }
2136 fatigueLoss *= duration;
2137 fatigueLoss *= movementSettings.mSpeedFactor;
2138 DynamicStat<float> fatigue = cls.getCreatureStats(mPtr).getFatigue();
2139
2140 if (!godmode)
2141 {
2142 fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss, fatigue.getCurrent() < 0);
2143 cls.getCreatureStats(mPtr).setFatigue(fatigue);
2144 }
2145
2146 float z = cls.getJump(mPtr);
2147 if(sneak || inwater || flying || incapacitated || !solid || z <= 0)
2148 vec.z() = 0.0f;
2149
2150 bool inJump = true;
2151 bool playLandingSound = false;
2152 if(!onground && !flying && !inwater && solid)
2153 {
2154 // In the air (either getting up —ascending part of jump— or falling).
2155
2156 forcestateupdate = (mJumpState != JumpState_InAir);
2157 jumpstate = JumpState_InAir;
2158
2159 static const float fJumpMoveBase = gmst.find("fJumpMoveBase")->mValue.getFloat();
2160 static const float fJumpMoveMult = gmst.find("fJumpMoveMult")->mValue.getFloat();
2161 float factor = fJumpMoveBase + fJumpMoveMult * mPtr.getClass().getSkill(mPtr, ESM::Skill::Acrobatics)/100.f;
2162 factor = std::min(1.f, factor);
2163 vec.x() *= factor;
2164 vec.y() *= factor;
2165 vec.z() = 0.0f;
2166 }
2167 else if(vec.z() > 0.0f && mJumpState != JumpState_InAir)
2168 {
2169 // Started a jump.
2170 if (z > 0)
2171 {
2172 if(vec.x() == 0 && vec.y() == 0)
2173 vec = osg::Vec3f(0.0f, 0.0f, z);
2174 else
2175 {
2176 osg::Vec3f lat (vec.x(), vec.y(), 0.0f);
2177 lat.normalize();
2178 vec = osg::Vec3f(lat.x(), lat.y(), 1.0f) * z * 0.707f;
2179 }
2180 }
2181 }
2182 else if(mJumpState == JumpState_InAir && !inwater && !flying && solid)
2183 {
2184 forcestateupdate = true;
2185 jumpstate = JumpState_Landing;
2186 vec.z() = 0.0f;
2187
2188 // We should reset idle animation during landing
2189 mAnimation->disable(mCurrentIdle);
2190
2191 float height = cls.getCreatureStats(mPtr).land(isPlayer);
2192 float healthLost = getFallDamage(mPtr, height);
2193
2194 if (healthLost > 0.0f)
2195 {
2196 const float fatigueTerm = cls.getCreatureStats(mPtr).getFatigueTerm();
2197
2198 // inflict fall damages
2199 if (!godmode)
2200 {
2201 float realHealthLost = static_cast<float>(healthLost * (1.0f - 0.25f * fatigueTerm));
2202 cls.onHit(mPtr, realHealthLost, true, MWWorld::Ptr(), MWWorld::Ptr(), osg::Vec3f(), true);
2203 }
2204
2205 const float acrobaticsSkill = cls.getSkill(mPtr, ESM::Skill::Acrobatics);
2206 if (healthLost > (acrobaticsSkill * fatigueTerm))
2207 {
2208 if (!godmode)
2209 cls.getCreatureStats(mPtr).setKnockedDown(true);
2210 }
2211 else
2212 {
2213 // report acrobatics progression
2214 if (isPlayer)
2215 cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 1);
2216 }
2217 }
2218
2219 if (mPtr.getClass().isNpc())
2220 playLandingSound = true;
2221 }
2222 else
2223 {
2224 if(mPtr.getClass().isNpc() && mJumpState == JumpState_InAir && !flying && solid)
2225 playLandingSound = true;
2226
2227 jumpstate = mAnimation->isPlaying(mCurrentJump) ? JumpState_Landing : JumpState_None;
2228
2229 vec.x() *= scale;
2230 vec.y() *= scale;
2231 vec.z() = 0.0f;
2232
2233 inJump = false;
2234
2235 if (movementSettings.mIsStrafing)
2236 {
2237 if(vec.x() > 0.0f)
2238 movestate = (inwater ? (isrunning ? CharState_SwimRunRight : CharState_SwimWalkRight)
2239 : (sneak ? CharState_SneakRight
2240 : (isrunning ? CharState_RunRight : CharState_WalkRight)));
2241 else if(vec.x() < 0.0f)
2242 movestate = (inwater ? (isrunning ? CharState_SwimRunLeft : CharState_SwimWalkLeft)
2243 : (sneak ? CharState_SneakLeft
2244 : (isrunning ? CharState_RunLeft : CharState_WalkLeft)));
2245 }
2246 else if (vec.length2() > 0.0f)
2247 {
2248 if (vec.y() >= 0.0f)
2249 movestate = (inwater ? (isrunning ? CharState_SwimRunForward : CharState_SwimWalkForward)
2250 : (sneak ? CharState_SneakForward
2251 : (isrunning ? CharState_RunForward : CharState_WalkForward)));
2252 else
2253 movestate = (inwater ? (isrunning ? CharState_SwimRunBack : CharState_SwimWalkBack)
2254 : (sneak ? CharState_SneakBack
2255 : (isrunning ? CharState_RunBack : CharState_WalkBack)));
2256 }
2257 else
2258 {
2259 // Do not play turning animation for player if rotation speed is very slow.
2260 // Actual threshold should take framerate in account.
2261 float rotationThreshold = (isPlayer ? 0.015f : 0.001f) * 60 * duration;
2262
2263 // It seems only bipedal actors use turning animations.
2264 // Also do not use turning animations in the first-person view and when sneaking.
2265 if (!sneak && jumpstate == JumpState_None && !isFirstPersonPlayer && mPtr.getClass().isBipedal(mPtr))
2266 {
2267 if(effectiveRotation > rotationThreshold)
2268 movestate = inwater ? CharState_SwimTurnRight : CharState_TurnRight;
2269 else if(effectiveRotation < -rotationThreshold)
2270 movestate = inwater ? CharState_SwimTurnLeft : CharState_TurnLeft;
2271 }
2272 }
2273 }
2274
2275 if (playLandingSound)
2276 {
2277 MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
2278 std::string sound;
2279 osg::Vec3f pos(mPtr.getRefData().getPosition().asVec3());
2280 if (world->isUnderwater(mPtr.getCell(), pos) || world->isWalkingOnWater(mPtr))
2281 sound = "DefaultLandWater";
2282 else if (onground)
2283 sound = "DefaultLand";
2284
2285 if (!sound.empty())
2286 sndMgr->playSound3D(mPtr, sound, 1.f, 1.f, MWSound::Type::Foot, MWSound::PlayMode::NoPlayerLocal);
2287 }
2288
2289 if (turnToMovementDirection && !isFirstPersonPlayer &&
2290 (movestate == CharState_SwimRunForward || movestate == CharState_SwimWalkForward ||
2291 movestate == CharState_SwimRunBack || movestate == CharState_SwimWalkBack))
2292 {
2293 float swimmingPitch = mAnimation->getBodyPitchRadians();
2294 float targetSwimmingPitch = -mPtr.getRefData().getPosition().rot[0];
2295 float maxSwimPitchDelta = 3.0f * duration;
2296 swimmingPitch += osg::clampBetween(targetSwimmingPitch - swimmingPitch, -maxSwimPitchDelta, maxSwimPitchDelta);
2297 mAnimation->setBodyPitchRadians(swimmingPitch);
2298 }
2299 else
2300 mAnimation->setBodyPitchRadians(0);
2301
2302 static const bool swimUpwardCorrection = Settings::Manager::getBool("swim upward correction", "Game");
2303 if (inwater && isPlayer && !isFirstPersonPlayer && swimUpwardCorrection)
2304 {
2305 static const float swimUpwardCoef = Settings::Manager::getFloat("swim upward coef", "Game");
2306 static const float swimForwardCoef = sqrtf(1.0f - swimUpwardCoef * swimUpwardCoef);
2307 vec.z() = std::abs(vec.y()) * swimUpwardCoef;
2308 vec.y() *= swimForwardCoef;
2309 }
2310
2311 // Player can not use smooth turning as NPCs, so we play turning animation a bit to avoid jittering
2312 if (isPlayer)
2313 {
2314 float threshold = mCurrentMovement.find("swim") == std::string::npos ? 0.4f : 0.8f;
2315 float complete;
2316 bool animPlaying = mAnimation->getInfo(mCurrentMovement, &complete);
2317 if (movestate == CharState_None && jumpstate == JumpState_None && isTurning())
2318 {
2319 if (animPlaying && complete < threshold)
2320 movestate = mMovementState;
2321 }
2322 }
2323 else
2324 {
2325 if (mPtr.getClass().isBipedal(mPtr))
2326 {
2327 if (mTurnAnimationThreshold > 0)
2328 mTurnAnimationThreshold -= duration;
2329
2330 if (movestate == CharState_TurnRight || movestate == CharState_TurnLeft ||
2331 movestate == CharState_SwimTurnRight || movestate == CharState_SwimTurnLeft)
2332 {
2333 mTurnAnimationThreshold = 0.05f;
2334 }
2335 else if (movestate == CharState_None && isTurning()
2336 && mTurnAnimationThreshold > 0)
2337 {
2338 movestate = mMovementState;
2339 }
2340 }
2341 }
2342
2343 if(movestate != CharState_None && !isTurning())
2344 clearAnimQueue();
2345
2346 if(mAnimQueue.empty() || inwater || (sneak && mIdleState != CharState_SpecialIdle))
2347 {
2348 if (inwater)
2349 idlestate = CharState_IdleSwim;
2350 else if (sneak && !inJump)
2351 idlestate = CharState_IdleSneak;
2352 else
2353 idlestate = CharState_Idle;
2354 }
2355 else
2356 updateAnimQueue();
2357
2358 if (!mSkipAnim)
2359 {
2360 // bipedal means hand-to-hand could be used (which is handled in updateWeaponState). an existing InventoryStore means an actual weapon could be used.
2361 if(cls.isBipedal(mPtr) || cls.hasInventoryStore(mPtr))
2362 forcestateupdate = updateWeaponState(idlestate) || forcestateupdate;
2363 else
2364 forcestateupdate = updateCreatureState() || forcestateupdate;
2365
2366 refreshCurrentAnims(idlestate, movestate, jumpstate, forcestateupdate);
2367 updateIdleStormState(inwater);
2368 }
2369
2370 if (inJump)
2371 mMovementAnimationControlled = false;
2372
2373 if (isTurning())
2374 {
2375 // Adjust animation speed from 1.0 to 1.5 multiplier
2376 if (duration > 0)
2377 {
2378 float turnSpeed = std::min(1.5f, std::abs(rot.z()) / duration / static_cast<float>(osg::PI));
2379 mAnimation->adjustSpeedMult(mCurrentMovement, std::max(turnSpeed, 1.0f));
2380 }
2381 }
2382 else if (mMovementState != CharState_None && mAdjustMovementAnimSpeed)
2383 {
2384 // Vanilla caps the played animation speed.
2385 const float maxSpeedMult = 10.f;
2386 const float speedMult = speed / mMovementAnimSpeed;
2387 mAnimation->adjustSpeedMult(mCurrentMovement, std::min(maxSpeedMult, speedMult));
2388 // Make sure the actual speed is the "expected" speed even though the animation is slower
2389 scale *= std::max(1.f, speedMult / maxSpeedMult);
2390 }
2391
2392 if (!mSkipAnim)
2393 {
2394 if(!isKnockedDown() && !isKnockedOut())
2395 {
2396 if (rot != osg::Vec3f())
2397 world->rotateObject(mPtr, rot.x(), rot.y(), rot.z(), true);
2398 }
2399 else //avoid z-rotating for knockdown
2400 {
2401 if (rot.x() != 0 && rot.y() != 0)
2402 world->rotateObject(mPtr, rot.x(), rot.y(), 0.0f, true);
2403 }
2404
2405 if (!mMovementAnimationControlled)
2406 world->queueMovement(mPtr, vec);
2407 }
2408
2409 movement = vec;
2410 movementSettings.mPosition[0] = movementSettings.mPosition[1] = 0;
2411 if (movement.z() == 0.f)
2412 movementSettings.mPosition[2] = 0;
2413 // Can't reset jump state (mPosition[2]) here in full; we don't know for sure whether the PhysicSystem will actually handle it in this frame
2414 // due to the fixed minimum timestep used for the physics update. It will be reset in PhysicSystem::move once the jump is handled.
2415
2416 if (!mSkipAnim)
2417 updateHeadTracking(duration);
2418 }
2419 else if(cls.getCreatureStats(mPtr).isDead())
2420 {
2421 // initial start of death animation for actors that started the game as dead
2422 // not done in constructor since we need to give scripts a chance to set the mSkipAnim flag
2423 if (!mSkipAnim && mDeathState != CharState_None && mCurrentDeath.empty())
2424 {
2425 // Fast-forward death animation to end for persisting corpses or corpses after end of death animation
2426 if (cls.isPersistent(mPtr) || cls.getCreatureStats(mPtr).isDeathAnimationFinished())
2427 playDeath(1.f, mDeathState);
2428 }
2429 }
2430
2431 bool isPersist = isPersistentAnimPlaying();
2432 osg::Vec3f moved = mAnimation->runAnimation(mSkipAnim && !isPersist ? 0.f : duration);
2433 if(duration > 0.0f)
2434 moved /= duration;
2435 else
2436 moved = osg::Vec3f(0.f, 0.f, 0.f);
2437
2438 moved.x() *= scale;
2439 moved.y() *= scale;
2440
2441 // Ensure we're moving in generally the right direction...
2442 if (speed > 0.f && moved != osg::Vec3f())
2443 {
2444 float l = moved.length();
2445 if (std::abs(movement.x() - moved.x()) > std::abs(moved.x()) / 2 ||
2446 std::abs(movement.y() - moved.y()) > std::abs(moved.y()) / 2 ||
2447 std::abs(movement.z() - moved.z()) > std::abs(moved.z()) / 2)
2448 {
2449 moved = movement;
2450 // For some creatures getSpeed doesn't work, so we adjust speed to the animation.
2451 // TODO: Fix Creature::getSpeed.
2452 float newLength = moved.length();
2453 if (newLength > 0 && !cls.isNpc())
2454 moved *= (l / newLength);
2455 }
2456 }
2457
2458 if (mFloatToSurface && cls.isActor())
2459 {
2460 if (cls.getCreatureStats(mPtr).isDead()
2461 || (!godmode && cls.getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Paralyze).getModifier() > 0))
2462 {
2463 moved.z() = 1.0;
2464 }
2465 }
2466
2467 // Update movement
2468 if(mMovementAnimationControlled && mPtr.getClass().isActor())
2469 world->queueMovement(mPtr, moved);
2470
2471 mSkipAnim = false;
2472
2473 mAnimation->enableHeadAnimation(cls.isActor() && !cls.getCreatureStats(mPtr).isDead());
2474 }
2475
persistAnimationState()2476 void CharacterController::persistAnimationState()
2477 {
2478 ESM::AnimationState& state = mPtr.getRefData().getAnimationState();
2479
2480 state.mScriptedAnims.clear();
2481 for (AnimationQueue::const_iterator iter = mAnimQueue.begin(); iter != mAnimQueue.end(); ++iter)
2482 {
2483 if (!iter->mPersist)
2484 continue;
2485
2486 ESM::AnimationState::ScriptedAnimation anim;
2487 anim.mGroup = iter->mGroup;
2488
2489 if (iter == mAnimQueue.begin())
2490 {
2491 anim.mLoopCount = mAnimation->getCurrentLoopCount(anim.mGroup);
2492 float complete;
2493 mAnimation->getInfo(anim.mGroup, &complete, nullptr);
2494 anim.mTime = complete;
2495 }
2496 else
2497 {
2498 anim.mLoopCount = iter->mLoopCount;
2499 anim.mTime = 0.f;
2500 }
2501
2502 state.mScriptedAnims.push_back(anim);
2503 }
2504 }
2505
unpersistAnimationState()2506 void CharacterController::unpersistAnimationState()
2507 {
2508 const ESM::AnimationState& state = mPtr.getRefData().getAnimationState();
2509
2510 if (!state.mScriptedAnims.empty())
2511 {
2512 clearAnimQueue();
2513 for (ESM::AnimationState::ScriptedAnimations::const_iterator iter = state.mScriptedAnims.begin(); iter != state.mScriptedAnims.end(); ++iter)
2514 {
2515 AnimationQueueEntry entry;
2516 entry.mGroup = iter->mGroup;
2517 entry.mLoopCount = iter->mLoopCount;
2518 entry.mPersist = true;
2519
2520 mAnimQueue.push_back(entry);
2521 }
2522
2523 const ESM::AnimationState::ScriptedAnimation& anim = state.mScriptedAnims.front();
2524 float complete = anim.mTime;
2525 if (anim.mAbsolute)
2526 {
2527 float start = mAnimation->getTextKeyTime(anim.mGroup+": start");
2528 float stop = mAnimation->getTextKeyTime(anim.mGroup+": stop");
2529 float time = std::max(start, std::min(stop, anim.mTime));
2530 complete = (time - start) / (stop - start);
2531 }
2532
2533 mAnimation->disable(mCurrentIdle);
2534 mCurrentIdle.clear();
2535 mIdleState = CharState_SpecialIdle;
2536
2537 bool loopfallback = (mAnimQueue.front().mGroup.compare(0,4,"idle") == 0);
2538 mAnimation->play(anim.mGroup,
2539 Priority_Persistent, MWRender::Animation::BlendMask_All, false, 1.0f,
2540 "start", "stop", complete, anim.mLoopCount, loopfallback);
2541 }
2542 }
2543
playGroup(const std::string & groupname,int mode,int count,bool persist)2544 bool CharacterController::playGroup(const std::string &groupname, int mode, int count, bool persist)
2545 {
2546 if(!mAnimation || !mAnimation->hasAnimation(groupname))
2547 return false;
2548
2549 // We should not interrupt persistent animations by non-persistent ones
2550 if (isPersistentAnimPlaying() && !persist)
2551 return false;
2552
2553 // If this animation is a looped animation (has a "loop start" key) that is already playing
2554 // and has not yet reached the end of the loop, allow it to continue animating with its existing loop count
2555 // and remove any other animations that were queued.
2556 // This emulates observed behavior from the original allows the script "OutsideBanner" to animate banners correctly.
2557 if (!mAnimQueue.empty() && mAnimQueue.front().mGroup == groupname &&
2558 mAnimation->getTextKeyTime(mAnimQueue.front().mGroup + ": loop start") >= 0 &&
2559 mAnimation->isPlaying(groupname))
2560 {
2561 float endOfLoop = mAnimation->getTextKeyTime(mAnimQueue.front().mGroup+": loop stop");
2562
2563 if (endOfLoop < 0) // if no Loop Stop key was found, use the Stop key
2564 endOfLoop = mAnimation->getTextKeyTime(mAnimQueue.front().mGroup+": stop");
2565
2566 if (endOfLoop > 0 && (mAnimation->getCurrentTime(mAnimQueue.front().mGroup) < endOfLoop))
2567 {
2568 mAnimQueue.resize(1);
2569 return true;
2570 }
2571 }
2572
2573 count = std::max(count, 1);
2574
2575 AnimationQueueEntry entry;
2576 entry.mGroup = groupname;
2577 entry.mLoopCount = count-1;
2578 entry.mPersist = persist;
2579
2580 if(mode != 0 || mAnimQueue.empty() || !isAnimPlaying(mAnimQueue.front().mGroup))
2581 {
2582 clearAnimQueue(persist);
2583
2584 mAnimation->disable(mCurrentIdle);
2585 mCurrentIdle.clear();
2586
2587 mIdleState = CharState_SpecialIdle;
2588 bool loopfallback = (entry.mGroup.compare(0,4,"idle") == 0);
2589 mAnimation->play(groupname, persist && groupname != "idle" ? Priority_Persistent : Priority_Default,
2590 MWRender::Animation::BlendMask_All, false, 1.0f,
2591 ((mode==2) ? "loop start" : "start"), "stop", 0.0f, count-1, loopfallback);
2592 }
2593 else
2594 {
2595 mAnimQueue.resize(1);
2596 }
2597
2598 // "PlayGroup idle" is a special case, used to remove to stop scripted animations playing
2599 if (groupname == "idle")
2600 entry.mPersist = false;
2601
2602 mAnimQueue.push_back(entry);
2603
2604 return true;
2605 }
2606
skipAnim()2607 void CharacterController::skipAnim()
2608 {
2609 mSkipAnim = true;
2610 }
2611
isPersistentAnimPlaying()2612 bool CharacterController::isPersistentAnimPlaying()
2613 {
2614 if (!mAnimQueue.empty())
2615 {
2616 AnimationQueueEntry& first = mAnimQueue.front();
2617 return first.mPersist && isAnimPlaying(first.mGroup);
2618 }
2619
2620 return false;
2621 }
2622
isAnimPlaying(const std::string & groupName)2623 bool CharacterController::isAnimPlaying(const std::string &groupName)
2624 {
2625 if(mAnimation == nullptr)
2626 return false;
2627 return mAnimation->isPlaying(groupName);
2628 }
2629
clearAnimQueue(bool clearPersistAnims)2630 void CharacterController::clearAnimQueue(bool clearPersistAnims)
2631 {
2632 // Do not interrupt scripted animations, if we want to keep them
2633 if ((!isPersistentAnimPlaying() || clearPersistAnims) && !mAnimQueue.empty())
2634 mAnimation->disable(mAnimQueue.front().mGroup);
2635
2636 for (AnimationQueue::iterator it = mAnimQueue.begin(); it != mAnimQueue.end();)
2637 {
2638 if (clearPersistAnims || !it->mPersist)
2639 it = mAnimQueue.erase(it);
2640 else
2641 ++it;
2642 }
2643 }
2644
forceStateUpdate()2645 void CharacterController::forceStateUpdate()
2646 {
2647 if(!mAnimation)
2648 return;
2649 clearAnimQueue();
2650
2651 // Make sure we canceled the current attack or spellcasting,
2652 // because we disabled attack animations anyway.
2653 mCastingManualSpell = false;
2654 mAttackingOrSpell = false;
2655 if (mUpperBodyState != UpperCharState_Nothing)
2656 mUpperBodyState = UpperCharState_WeapEquiped;
2657
2658 refreshCurrentAnims(mIdleState, mMovementState, mJumpState, true);
2659
2660 if(mDeathState != CharState_None)
2661 {
2662 playRandomDeath();
2663 }
2664
2665 mAnimation->runAnimation(0.f);
2666 }
2667
kill()2668 CharacterController::KillResult CharacterController::kill()
2669 {
2670 if (mDeathState == CharState_None)
2671 {
2672 playRandomDeath();
2673
2674 mAnimation->disable(mCurrentIdle);
2675
2676 mIdleState = CharState_None;
2677 mCurrentIdle.clear();
2678 return Result_DeathAnimStarted;
2679 }
2680
2681 MWMechanics::CreatureStats& cStats = mPtr.getClass().getCreatureStats(mPtr);
2682 if (isAnimPlaying(mCurrentDeath))
2683 return Result_DeathAnimPlaying;
2684 if (!cStats.isDeathAnimationFinished())
2685 {
2686 cStats.setDeathAnimationFinished(true);
2687 return Result_DeathAnimJustFinished;
2688 }
2689 return Result_DeathAnimFinished;
2690 }
2691
resurrect()2692 void CharacterController::resurrect()
2693 {
2694 if(mDeathState == CharState_None)
2695 return;
2696
2697 if(mAnimation)
2698 mAnimation->disable(mCurrentDeath);
2699 mCurrentDeath.clear();
2700 mDeathState = CharState_None;
2701 mWeaponType = ESM::Weapon::None;
2702 }
2703
updateContinuousVfx()2704 void CharacterController::updateContinuousVfx()
2705 {
2706 // Keeping track of when to stop a continuous VFX seems to be very difficult to do inside the spells code,
2707 // as it's extremely spread out (ActiveSpells, Spells, InventoryStore effects, etc...) so we do it here.
2708
2709 // Stop any effects that are no longer active
2710 std::vector<int> effects;
2711 mAnimation->getLoopingEffects(effects);
2712
2713 for (int effectId : effects)
2714 {
2715 if (mPtr.getClass().getCreatureStats(mPtr).isDeathAnimationFinished()
2716 || mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(MWMechanics::EffectKey(effectId)).getMagnitude() <= 0)
2717 mAnimation->removeEffect(effectId);
2718 }
2719 }
2720
updateMagicEffects()2721 void CharacterController::updateMagicEffects()
2722 {
2723 if (!mPtr.getClass().isActor())
2724 return;
2725
2726 float light = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Light).getMagnitude();
2727 mAnimation->setLightEffect(light);
2728
2729 // If you're dead you don't care about whether you've started/stopped being a vampire or not
2730 if (mPtr.getClass().getCreatureStats(mPtr).isDead())
2731 return;
2732
2733 bool vampire = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Vampirism).getMagnitude() > 0.0f;
2734 mAnimation->setVampire(vampire);
2735 }
2736
setVisibility(float visibility)2737 void CharacterController::setVisibility(float visibility)
2738 {
2739 // We should take actor's invisibility in account
2740 if (mPtr.getClass().isActor())
2741 {
2742 float alpha = 1.f;
2743 if (mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Invisibility).getModifier()) // Ignore base magnitude (see bug #3555).
2744 {
2745 if (mPtr == getPlayer())
2746 alpha = 0.25f;
2747 else
2748 alpha = 0.05f;
2749 }
2750 float chameleon = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Chameleon).getMagnitude();
2751 if (chameleon)
2752 {
2753 alpha *= std::min(0.75f, std::max(0.25f, (100.f - chameleon)/100.f));
2754 }
2755
2756 visibility = std::min(visibility, alpha);
2757 }
2758
2759 // TODO: implement a dithering shader rather than just change object transparency.
2760 mAnimation->setAlpha(visibility);
2761 }
2762
setAttackTypeBasedOnMovement()2763 void CharacterController::setAttackTypeBasedOnMovement()
2764 {
2765 float *move = mPtr.getClass().getMovementSettings(mPtr).mPosition;
2766 if (std::abs(move[1]) > std::abs(move[0]) + 0.2f) // forward-backward
2767 mAttackType = "thrust";
2768 else if (std::abs(move[0]) > std::abs(move[1]) + 0.2f) // sideway
2769 mAttackType = "slash";
2770 else
2771 mAttackType = "chop";
2772 }
2773
isRandomAttackAnimation(const std::string & group) const2774 bool CharacterController::isRandomAttackAnimation(const std::string& group) const
2775 {
2776 return (group == "attack1" || group == "swimattack1" ||
2777 group == "attack2" || group == "swimattack2" ||
2778 group == "attack3" || group == "swimattack3");
2779 }
2780
isAttackPreparing() const2781 bool CharacterController::isAttackPreparing() const
2782 {
2783 return mUpperBodyState == UpperCharState_StartToMinAttack ||
2784 mUpperBodyState == UpperCharState_MinAttackToMaxAttack;
2785 }
2786
isCastingSpell() const2787 bool CharacterController::isCastingSpell() const
2788 {
2789 return mCastingManualSpell || mUpperBodyState == UpperCharState_CastingSpell;
2790 }
2791
isReadyToBlock() const2792 bool CharacterController::isReadyToBlock() const
2793 {
2794 return updateCarriedLeftVisible(mWeaponType);
2795 }
2796
isKnockedDown() const2797 bool CharacterController::isKnockedDown() const
2798 {
2799 return mHitState == CharState_KnockDown ||
2800 mHitState == CharState_SwimKnockDown;
2801 }
2802
isKnockedOut() const2803 bool CharacterController::isKnockedOut() const
2804 {
2805 return mHitState == CharState_KnockOut ||
2806 mHitState == CharState_SwimKnockOut;
2807 }
2808
isTurning() const2809 bool CharacterController::isTurning() const
2810 {
2811 return mMovementState == CharState_TurnLeft ||
2812 mMovementState == CharState_TurnRight ||
2813 mMovementState == CharState_SwimTurnLeft ||
2814 mMovementState == CharState_SwimTurnRight;
2815 }
2816
isRecovery() const2817 bool CharacterController::isRecovery() const
2818 {
2819 return mHitState == CharState_Hit ||
2820 mHitState == CharState_SwimHit;
2821 }
2822
isAttackingOrSpell() const2823 bool CharacterController::isAttackingOrSpell() const
2824 {
2825 return mUpperBodyState != UpperCharState_Nothing &&
2826 mUpperBodyState != UpperCharState_WeapEquiped;
2827 }
2828
isSneaking() const2829 bool CharacterController::isSneaking() const
2830 {
2831 return mIdleState == CharState_IdleSneak ||
2832 mMovementState == CharState_SneakForward ||
2833 mMovementState == CharState_SneakBack ||
2834 mMovementState == CharState_SneakLeft ||
2835 mMovementState == CharState_SneakRight;
2836 }
2837
isRunning() const2838 bool CharacterController::isRunning() const
2839 {
2840 return mMovementState == CharState_RunForward ||
2841 mMovementState == CharState_RunBack ||
2842 mMovementState == CharState_RunLeft ||
2843 mMovementState == CharState_RunRight ||
2844 mMovementState == CharState_SwimRunForward ||
2845 mMovementState == CharState_SwimRunBack ||
2846 mMovementState == CharState_SwimRunLeft ||
2847 mMovementState == CharState_SwimRunRight;
2848 }
2849
setAttackingOrSpell(bool attackingOrSpell)2850 void CharacterController::setAttackingOrSpell(bool attackingOrSpell)
2851 {
2852 mAttackingOrSpell = attackingOrSpell;
2853 }
2854
castSpell(const std::string spellId,bool manualSpell)2855 void CharacterController::castSpell(const std::string spellId, bool manualSpell)
2856 {
2857 mAttackingOrSpell = true;
2858 mCastingManualSpell = manualSpell;
2859 ActionSpell action = ActionSpell(spellId);
2860 action.prepare(mPtr);
2861 }
2862
setAIAttackType(const std::string & attackType)2863 void CharacterController::setAIAttackType(const std::string& attackType)
2864 {
2865 mAttackType = attackType;
2866 }
2867
setAttackTypeRandomly(std::string & attackType)2868 void CharacterController::setAttackTypeRandomly(std::string& attackType)
2869 {
2870 float random = Misc::Rng::rollProbability();
2871 if (random >= 2/3.f)
2872 attackType = "thrust";
2873 else if (random >= 1/3.f)
2874 attackType = "slash";
2875 else
2876 attackType = "chop";
2877 }
2878
readyToPrepareAttack() const2879 bool CharacterController::readyToPrepareAttack() const
2880 {
2881 return (mHitState == CharState_None || mHitState == CharState_Block)
2882 && mUpperBodyState <= UpperCharState_WeapEquiped;
2883 }
2884
readyToStartAttack() const2885 bool CharacterController::readyToStartAttack() const
2886 {
2887 if (mHitState != CharState_None && mHitState != CharState_Block)
2888 return false;
2889
2890 if (mPtr.getClass().hasInventoryStore(mPtr) || mPtr.getClass().isBipedal(mPtr))
2891 return mUpperBodyState == UpperCharState_WeapEquiped;
2892 else
2893 return mUpperBodyState == UpperCharState_Nothing;
2894 }
2895
getAttackStrength() const2896 float CharacterController::getAttackStrength() const
2897 {
2898 return mAttackStrength;
2899 }
2900
setActive(int active)2901 void CharacterController::setActive(int active)
2902 {
2903 mAnimation->setActive(active);
2904 }
2905
setHeadTrackTarget(const MWWorld::ConstPtr & target)2906 void CharacterController::setHeadTrackTarget(const MWWorld::ConstPtr &target)
2907 {
2908 mHeadTrackTarget = target;
2909 }
2910
playSwishSound(float attackStrength)2911 void CharacterController::playSwishSound(float attackStrength)
2912 {
2913 MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
2914
2915 std::string sound = "Weapon Swish";
2916 if(attackStrength < 0.5f)
2917 sndMgr->playSound3D(mPtr, sound, 1.0f, 0.8f); //Weak attack
2918 else if(attackStrength < 1.0f)
2919 sndMgr->playSound3D(mPtr, sound, 1.0f, 1.0f); //Medium attack
2920 else
2921 sndMgr->playSound3D(mPtr, sound, 1.0f, 1.2f); //Strong attack
2922 }
2923
updateHeadTracking(float duration)2924 void CharacterController::updateHeadTracking(float duration)
2925 {
2926 const osg::Node* head = mAnimation->getNode("Bip01 Head");
2927 if (!head)
2928 return;
2929
2930 double zAngleRadians = 0.f;
2931 double xAngleRadians = 0.f;
2932
2933 if (!mHeadTrackTarget.isEmpty())
2934 {
2935 osg::NodePathList nodepaths = head->getParentalNodePaths();
2936 if (nodepaths.empty())
2937 return;
2938 osg::Matrixf mat = osg::computeLocalToWorld(nodepaths[0]);
2939 osg::Vec3f headPos = mat.getTrans();
2940
2941 osg::Vec3f direction;
2942 if (const MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(mHeadTrackTarget))
2943 {
2944 const osg::Node* node = anim->getNode("Head");
2945 if (node == nullptr)
2946 node = anim->getNode("Bip01 Head");
2947 if (node != nullptr)
2948 {
2949 nodepaths = node->getParentalNodePaths();
2950 if (!nodepaths.empty())
2951 direction = osg::computeLocalToWorld(nodepaths[0]).getTrans() - headPos;
2952 }
2953 else
2954 // no head node to look at, fall back to look at center of collision box
2955 direction = MWBase::Environment::get().getWorld()->aimToTarget(mPtr, mHeadTrackTarget, false);
2956 }
2957 direction.normalize();
2958
2959 if (!mPtr.getRefData().getBaseNode())
2960 return;
2961 const osg::Vec3f actorDirection = mPtr.getRefData().getBaseNode()->getAttitude() * osg::Vec3f(0,1,0);
2962
2963 zAngleRadians = std::atan2(actorDirection.x(), actorDirection.y()) - std::atan2(direction.x(), direction.y());
2964 zAngleRadians = Misc::normalizeAngle(zAngleRadians - mAnimation->getHeadYaw()) + mAnimation->getHeadYaw();
2965 zAngleRadians *= (1 - direction.z() * direction.z());
2966 xAngleRadians = std::asin(direction.z());
2967 }
2968
2969 const double xLimit = osg::DegreesToRadians(40.0);
2970 const double zLimit = osg::DegreesToRadians(30.0);
2971 double zLimitOffset = mAnimation->getUpperBodyYawRadians();
2972 xAngleRadians = osg::clampBetween(xAngleRadians, -xLimit, xLimit);
2973 zAngleRadians = osg::clampBetween(zAngleRadians, -zLimit + zLimitOffset, zLimit + zLimitOffset);
2974
2975 float factor = duration*5;
2976 factor = std::min(factor, 1.f);
2977 xAngleRadians = (1.f-factor) * mAnimation->getHeadPitch() + factor * xAngleRadians;
2978 zAngleRadians = (1.f-factor) * mAnimation->getHeadYaw() + factor * zAngleRadians;
2979
2980 mAnimation->setHeadPitch(xAngleRadians);
2981 mAnimation->setHeadYaw(zAngleRadians);
2982 }
2983
2984 }
2985