1 #include "creature.hpp" 2 3 #include <components/misc/rng.hpp> 4 #include <components/debug/debuglog.hpp> 5 #include <components/esm/loadcrea.hpp> 6 #include <components/esm/creaturestate.hpp> 7 #include <components/settings/settings.hpp> 8 9 #include "../mwmechanics/creaturestats.hpp" 10 #include "../mwmechanics/magiceffects.hpp" 11 #include "../mwmechanics/movement.hpp" 12 #include "../mwmechanics/disease.hpp" 13 #include "../mwmechanics/spellcasting.hpp" 14 #include "../mwmechanics/difficultyscaling.hpp" 15 16 #include "../mwbase/environment.hpp" 17 #include "../mwbase/mechanicsmanager.hpp" 18 #include "../mwbase/windowmanager.hpp" 19 #include "../mwbase/world.hpp" 20 #include "../mwbase/soundmanager.hpp" 21 22 #include "../mwworld/ptr.hpp" 23 #include "../mwworld/actiontalk.hpp" 24 #include "../mwworld/actionopen.hpp" 25 #include "../mwworld/failedaction.hpp" 26 #include "../mwworld/customdata.hpp" 27 #include "../mwworld/containerstore.hpp" 28 #include "../mwworld/cellstore.hpp" 29 #include "../mwworld/localscripts.hpp" 30 31 #include "../mwrender/renderinginterface.hpp" 32 #include "../mwrender/objects.hpp" 33 34 #include "../mwgui/tooltips.hpp" 35 36 #include "../mwworld/inventorystore.hpp" 37 38 #include "../mwmechanics/npcstats.hpp" 39 #include "../mwmechanics/combat.hpp" 40 #include "../mwmechanics/actorutil.hpp" 41 42 namespace 43 { isFlagBitSet(const MWWorld::ConstPtr & ptr,ESM::Creature::Flags bitMask)44 bool isFlagBitSet(const MWWorld::ConstPtr &ptr, ESM::Creature::Flags bitMask) 45 { 46 return (ptr.get<ESM::Creature>()->mBase->mFlags & bitMask) != 0; 47 } 48 } 49 50 namespace MWClass 51 { 52 53 class CreatureCustomData : public MWWorld::TypedCustomData<CreatureCustomData> 54 { 55 public: 56 MWMechanics::CreatureStats mCreatureStats; 57 std::unique_ptr<MWWorld::ContainerStore> mContainerStore; // may be InventoryStore for some creatures 58 MWMechanics::Movement mMovement; 59 60 CreatureCustomData() = default; 61 CreatureCustomData(const CreatureCustomData& other); 62 CreatureCustomData(CreatureCustomData&& other) = default; 63 asCreatureCustomData()64 CreatureCustomData& asCreatureCustomData() override 65 { 66 return *this; 67 } asCreatureCustomData() const68 const CreatureCustomData& asCreatureCustomData() const override 69 { 70 return *this; 71 } 72 }; 73 CreatureCustomData(const CreatureCustomData & other)74 CreatureCustomData::CreatureCustomData(const CreatureCustomData& other) 75 : mCreatureStats(other.mCreatureStats), 76 mContainerStore(other.mContainerStore->clone()), 77 mMovement(other.mMovement) 78 { 79 } 80 getGmst()81 const Creature::GMST& Creature::getGmst() 82 { 83 static GMST gmst; 84 static bool inited = false; 85 if (!inited) 86 { 87 const MWBase::World *world = MWBase::Environment::get().getWorld(); 88 const MWWorld::Store<ESM::GameSetting> &store = world->getStore().get<ESM::GameSetting>(); 89 gmst.fMinWalkSpeedCreature = store.find("fMinWalkSpeedCreature"); 90 gmst.fMaxWalkSpeedCreature = store.find("fMaxWalkSpeedCreature"); 91 gmst.fEncumberedMoveEffect = store.find("fEncumberedMoveEffect"); 92 gmst.fSneakSpeedMultiplier = store.find("fSneakSpeedMultiplier"); 93 gmst.fAthleticsRunBonus = store.find("fAthleticsRunBonus"); 94 gmst.fBaseRunMultiplier = store.find("fBaseRunMultiplier"); 95 gmst.fMinFlySpeed = store.find("fMinFlySpeed"); 96 gmst.fMaxFlySpeed = store.find("fMaxFlySpeed"); 97 gmst.fSwimRunBase = store.find("fSwimRunBase"); 98 gmst.fSwimRunAthleticsMult = store.find("fSwimRunAthleticsMult"); 99 gmst.fKnockDownMult = store.find("fKnockDownMult"); 100 gmst.iKnockDownOddsMult = store.find("iKnockDownOddsMult"); 101 gmst.iKnockDownOddsBase = store.find("iKnockDownOddsBase"); 102 inited = true; 103 } 104 return gmst; 105 } 106 ensureCustomData(const MWWorld::Ptr & ptr) const107 void Creature::ensureCustomData (const MWWorld::Ptr& ptr) const 108 { 109 if (!ptr.getRefData().getCustomData()) 110 { 111 std::unique_ptr<CreatureCustomData> data (new CreatureCustomData); 112 113 MWWorld::LiveCellRef<ESM::Creature> *ref = ptr.get<ESM::Creature>(); 114 115 // creature stats 116 data->mCreatureStats.setAttribute(ESM::Attribute::Strength, ref->mBase->mData.mStrength); 117 data->mCreatureStats.setAttribute(ESM::Attribute::Intelligence, ref->mBase->mData.mIntelligence); 118 data->mCreatureStats.setAttribute(ESM::Attribute::Willpower, ref->mBase->mData.mWillpower); 119 data->mCreatureStats.setAttribute(ESM::Attribute::Agility, ref->mBase->mData.mAgility); 120 data->mCreatureStats.setAttribute(ESM::Attribute::Speed, ref->mBase->mData.mSpeed); 121 data->mCreatureStats.setAttribute(ESM::Attribute::Endurance, ref->mBase->mData.mEndurance); 122 data->mCreatureStats.setAttribute(ESM::Attribute::Personality, ref->mBase->mData.mPersonality); 123 data->mCreatureStats.setAttribute(ESM::Attribute::Luck, ref->mBase->mData.mLuck); 124 data->mCreatureStats.setHealth(static_cast<float>(ref->mBase->mData.mHealth)); 125 data->mCreatureStats.setMagicka(static_cast<float>(ref->mBase->mData.mMana)); 126 data->mCreatureStats.setFatigue(static_cast<float>(ref->mBase->mData.mFatigue)); 127 128 data->mCreatureStats.setLevel(ref->mBase->mData.mLevel); 129 130 data->mCreatureStats.getAiSequence().fill(ref->mBase->mAiPackage); 131 132 data->mCreatureStats.setAiSetting (MWMechanics::CreatureStats::AI_Hello, ref->mBase->mAiData.mHello); 133 data->mCreatureStats.setAiSetting (MWMechanics::CreatureStats::AI_Fight, ref->mBase->mAiData.mFight); 134 data->mCreatureStats.setAiSetting (MWMechanics::CreatureStats::AI_Flee, ref->mBase->mAiData.mFlee); 135 data->mCreatureStats.setAiSetting (MWMechanics::CreatureStats::AI_Alarm, ref->mBase->mAiData.mAlarm); 136 137 // Persistent actors with 0 health do not play death animation 138 if (data->mCreatureStats.isDead()) 139 data->mCreatureStats.setDeathAnimationFinished(isPersistent(ptr)); 140 141 // spells 142 bool spellsInitialised = data->mCreatureStats.getSpells().setSpells(ref->mBase->mId); 143 if (!spellsInitialised) 144 data->mCreatureStats.getSpells().addAllToInstance(ref->mBase->mSpells.mList); 145 146 // inventory 147 bool hasInventory = hasInventoryStore(ptr); 148 if (hasInventory) 149 data->mContainerStore = std::make_unique<MWWorld::InventoryStore>(); 150 else 151 data->mContainerStore = std::make_unique<MWWorld::ContainerStore>(); 152 153 data->mCreatureStats.setGoldPool(ref->mBase->mData.mGold); 154 155 data->mCreatureStats.setNeedRecalcDynamicStats(false); 156 157 // store 158 ptr.getRefData().setCustomData(std::move(data)); 159 160 getContainerStore(ptr).fill(ref->mBase->mInventory, ptr.getCellRef().getRefId()); 161 162 if (hasInventory) 163 getInventoryStore(ptr).autoEquip(ptr); 164 } 165 } 166 insertObjectRendering(const MWWorld::Ptr & ptr,const std::string & model,MWRender::RenderingInterface & renderingInterface) const167 void Creature::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const 168 { 169 MWRender::Objects& objects = renderingInterface.getObjects(); 170 objects.insertCreature(ptr, model, hasInventoryStore(ptr)); 171 } 172 getModel(const MWWorld::ConstPtr & ptr) const173 std::string Creature::getModel(const MWWorld::ConstPtr &ptr) const 174 { 175 const MWWorld::LiveCellRef<ESM::Creature> *ref = ptr.get<ESM::Creature>(); 176 177 const std::string &model = ref->mBase->mModel; 178 if (!model.empty()) { 179 return "meshes\\" + model; 180 } 181 return ""; 182 } 183 getModelsToPreload(const MWWorld::Ptr & ptr,std::vector<std::string> & models) const184 void Creature::getModelsToPreload(const MWWorld::Ptr &ptr, std::vector<std::string> &models) const 185 { 186 std::string model = getModel(ptr); 187 if (!model.empty()) 188 models.push_back(model); 189 190 // FIXME: use const version of InventoryStore functions once they are available 191 if (hasInventoryStore(ptr)) 192 { 193 const MWWorld::InventoryStore& invStore = getInventoryStore(ptr); 194 for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) 195 { 196 MWWorld::ConstContainerStoreIterator equipped = invStore.getSlot(slot); 197 if (equipped != invStore.end()) 198 { 199 model = equipped->getClass().getModel(*equipped); 200 if (!model.empty()) 201 models.push_back(model); 202 } 203 } 204 } 205 } 206 getName(const MWWorld::ConstPtr & ptr) const207 std::string Creature::getName (const MWWorld::ConstPtr& ptr) const 208 { 209 const MWWorld::LiveCellRef<ESM::Creature> *ref = ptr.get<ESM::Creature>(); 210 const std::string& name = ref->mBase->mName; 211 212 return !name.empty() ? name : ref->mBase->mId; 213 } 214 getCreatureStats(const MWWorld::Ptr & ptr) const215 MWMechanics::CreatureStats& Creature::getCreatureStats (const MWWorld::Ptr& ptr) const 216 { 217 ensureCustomData (ptr); 218 219 return ptr.getRefData().getCustomData()->asCreatureCustomData().mCreatureStats; 220 } 221 222 hit(const MWWorld::Ptr & ptr,float attackStrength,int type) const223 void Creature::hit(const MWWorld::Ptr& ptr, float attackStrength, int type) const 224 { 225 MWWorld::LiveCellRef<ESM::Creature> *ref = 226 ptr.get<ESM::Creature>(); 227 const MWWorld::Store<ESM::GameSetting> &gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>(); 228 MWMechanics::CreatureStats &stats = getCreatureStats(ptr); 229 230 if (stats.getDrawState() != MWMechanics::DrawState_Weapon) 231 return; 232 233 // Get the weapon used (if hand-to-hand, weapon = inv.end()) 234 MWWorld::Ptr weapon; 235 if (hasInventoryStore(ptr)) 236 { 237 MWWorld::InventoryStore &inv = getInventoryStore(ptr); 238 MWWorld::ContainerStoreIterator weaponslot = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); 239 if (weaponslot != inv.end() && weaponslot->getTypeName() == typeid(ESM::Weapon).name()) 240 weapon = *weaponslot; 241 } 242 243 MWMechanics::applyFatigueLoss(ptr, weapon, attackStrength); 244 245 float dist = gmst.find("fCombatDistance")->mValue.getFloat(); 246 if (!weapon.isEmpty()) 247 dist *= weapon.get<ESM::Weapon>()->mBase->mData.mReach; 248 249 // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result. 250 std::vector<MWWorld::Ptr> targetActors; 251 stats.getAiSequence().getCombatTargets(targetActors); 252 253 std::pair<MWWorld::Ptr, osg::Vec3f> result = MWBase::Environment::get().getWorld()->getHitContact(ptr, dist, targetActors); 254 if (result.first.isEmpty()) 255 return; // Didn't hit anything 256 257 MWWorld::Ptr victim = result.first; 258 259 if (!victim.getClass().isActor()) 260 return; // Can't hit non-actors 261 262 osg::Vec3f hitPosition (result.second); 263 264 float hitchance = MWMechanics::getHitChance(ptr, victim, ref->mBase->mData.mCombat); 265 266 if(Misc::Rng::roll0to99() >= hitchance) 267 { 268 victim.getClass().onHit(victim, 0.0f, false, MWWorld::Ptr(), ptr, osg::Vec3f(), false); 269 MWMechanics::reduceWeaponCondition(0.f, false, weapon, ptr); 270 return; 271 } 272 273 int min,max; 274 switch (type) 275 { 276 case 0: 277 min = ref->mBase->mData.mAttack[0]; 278 max = ref->mBase->mData.mAttack[1]; 279 break; 280 case 1: 281 min = ref->mBase->mData.mAttack[2]; 282 max = ref->mBase->mData.mAttack[3]; 283 break; 284 case 2: 285 default: 286 min = ref->mBase->mData.mAttack[4]; 287 max = ref->mBase->mData.mAttack[5]; 288 break; 289 } 290 291 float damage = min + (max - min) * attackStrength; 292 bool healthdmg = true; 293 if (!weapon.isEmpty()) 294 { 295 const unsigned char *attack = nullptr; 296 if(type == ESM::Weapon::AT_Chop) 297 attack = weapon.get<ESM::Weapon>()->mBase->mData.mChop; 298 else if(type == ESM::Weapon::AT_Slash) 299 attack = weapon.get<ESM::Weapon>()->mBase->mData.mSlash; 300 else if(type == ESM::Weapon::AT_Thrust) 301 attack = weapon.get<ESM::Weapon>()->mBase->mData.mThrust; 302 if(attack) 303 { 304 damage = attack[0] + ((attack[1]-attack[0])*attackStrength); 305 MWMechanics::adjustWeaponDamage(damage, weapon, ptr); 306 MWMechanics::resistNormalWeapon(victim, ptr, weapon, damage); 307 MWMechanics::reduceWeaponCondition(damage, true, weapon, ptr); 308 } 309 310 // Apply "On hit" enchanted weapons 311 MWMechanics::applyOnStrikeEnchantment(ptr, victim, weapon, hitPosition); 312 } 313 else if (isBipedal(ptr)) 314 { 315 MWMechanics::getHandToHandDamage(ptr, victim, damage, healthdmg, attackStrength); 316 } 317 318 MWMechanics::applyElementalShields(ptr, victim); 319 320 if (MWMechanics::blockMeleeAttack(ptr, victim, weapon, damage, attackStrength)) 321 damage = 0; 322 323 MWMechanics::diseaseContact(victim, ptr); 324 325 victim.getClass().onHit(victim, damage, healthdmg, weapon, ptr, hitPosition, true); 326 } 327 onHit(const MWWorld::Ptr & ptr,float damage,bool ishealth,const MWWorld::Ptr & object,const MWWorld::Ptr & attacker,const osg::Vec3f & hitPosition,bool successful) const328 void Creature::onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful) const 329 { 330 MWMechanics::CreatureStats& stats = getCreatureStats(ptr); 331 332 // NOTE: 'object' and/or 'attacker' may be empty. 333 if (!attacker.isEmpty() && attacker.getClass().isActor() && !stats.getAiSequence().isInCombat(attacker)) 334 stats.setAttacked(true); 335 336 // Self defense 337 bool setOnPcHitMe = true; // Note OnPcHitMe is not set for friendly hits. 338 339 // No retaliation for totally static creatures (they have no movement or attacks anyway) 340 if (isMobile(ptr) && !attacker.isEmpty()) 341 setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker); 342 343 // Attacker and target store each other as hitattemptactor if they have no one stored yet 344 if (!attacker.isEmpty() && attacker.getClass().isActor()) 345 { 346 MWMechanics::CreatureStats& statsAttacker = attacker.getClass().getCreatureStats(attacker); 347 // First handle the attacked actor 348 if ((stats.getHitAttemptActorId() == -1) 349 && (statsAttacker.getAiSequence().isInCombat(ptr) 350 || attacker == MWMechanics::getPlayer())) 351 stats.setHitAttemptActorId(statsAttacker.getActorId()); 352 353 // Next handle the attacking actor 354 if ((statsAttacker.getHitAttemptActorId() == -1) 355 && (statsAttacker.getAiSequence().isInCombat(ptr) 356 || attacker == MWMechanics::getPlayer())) 357 statsAttacker.setHitAttemptActorId(stats.getActorId()); 358 } 359 360 if (!object.isEmpty()) 361 stats.setLastHitAttemptObject(object.getCellRef().getRefId()); 362 363 if (setOnPcHitMe && !attacker.isEmpty() && attacker == MWMechanics::getPlayer()) 364 { 365 const std::string &script = ptr.get<ESM::Creature>()->mBase->mScript; 366 /* Set the OnPCHitMe script variable. The script is responsible for clearing it. */ 367 if(!script.empty()) 368 ptr.getRefData().getLocals().setVarByInt(script, "onpchitme", 1); 369 } 370 371 if (!successful) 372 { 373 // Missed 374 if (!attacker.isEmpty() && attacker == MWMechanics::getPlayer()) 375 MWBase::Environment::get().getSoundManager()->playSound3D(ptr, "miss", 1.0f, 1.0f); 376 return; 377 } 378 379 if (!object.isEmpty()) 380 stats.setLastHitObject(object.getCellRef().getRefId()); 381 382 if (damage < 0.001f) 383 damage = 0; 384 385 if (damage > 0.f) 386 { 387 if (!attacker.isEmpty()) 388 { 389 // Check for knockdown 390 float agilityTerm = stats.getAttribute(ESM::Attribute::Agility).getModified() * getGmst().fKnockDownMult->mValue.getFloat(); 391 float knockdownTerm = stats.getAttribute(ESM::Attribute::Agility).getModified() 392 * getGmst().iKnockDownOddsMult->mValue.getInteger() * 0.01f + getGmst().iKnockDownOddsBase->mValue.getInteger(); 393 if (ishealth && agilityTerm <= damage && knockdownTerm <= Misc::Rng::roll0to99()) 394 stats.setKnockedDown(true); 395 else 396 stats.setHitRecovery(true); // Is this supposed to always occur? 397 } 398 399 if(ishealth) 400 { 401 damage *= damage / (damage + getArmorRating(ptr)); 402 damage = std::max(1.f, damage); 403 if (!attacker.isEmpty()) 404 { 405 damage = scaleDamage(damage, attacker, ptr); 406 MWBase::Environment::get().getWorld()->spawnBloodEffect(ptr, hitPosition); 407 } 408 409 MWBase::Environment::get().getSoundManager()->playSound3D(ptr, "Health Damage", 1.0f, 1.0f); 410 411 MWMechanics::DynamicStat<float> health(stats.getHealth()); 412 health.setCurrent(health.getCurrent() - damage); 413 stats.setHealth(health); 414 } 415 else 416 { 417 MWMechanics::DynamicStat<float> fatigue(stats.getFatigue()); 418 fatigue.setCurrent(fatigue.getCurrent() - damage, true); 419 stats.setFatigue(fatigue); 420 } 421 } 422 } 423 activate(const MWWorld::Ptr & ptr,const MWWorld::Ptr & actor) const424 std::shared_ptr<MWWorld::Action> Creature::activate (const MWWorld::Ptr& ptr, 425 const MWWorld::Ptr& actor) const 426 { 427 if(actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) 428 { 429 const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); 430 const ESM::Sound *sound = store.get<ESM::Sound>().searchRandom("WolfCreature"); 431 432 std::shared_ptr<MWWorld::Action> action(new MWWorld::FailedAction("#{sWerewolfRefusal}")); 433 if(sound) action->setSound(sound->mId); 434 435 return action; 436 } 437 438 const MWMechanics::CreatureStats& stats = getCreatureStats(ptr); 439 440 if(stats.isDead()) 441 { 442 bool canLoot = Settings::Manager::getBool ("can loot during death animation", "Game"); 443 444 // by default user can loot friendly actors during death animation 445 if (canLoot && !stats.getAiSequence().isInCombat()) 446 return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr)); 447 448 // otherwise wait until death animation 449 if(stats.isDeathAnimationFinished()) 450 return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr)); 451 } 452 else if (!stats.getAiSequence().isInCombat() && !stats.getKnockedDown()) 453 return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionTalk(ptr)); 454 455 // Tribunal and some mod companions oddly enough must use open action as fallback 456 if (!getScript(ptr).empty() && ptr.getRefData().getLocals().getIntVar(getScript(ptr), "companion")) 457 return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr)); 458 459 return std::shared_ptr<MWWorld::Action>(new MWWorld::FailedAction("")); 460 } 461 getContainerStore(const MWWorld::Ptr & ptr) const462 MWWorld::ContainerStore& Creature::getContainerStore (const MWWorld::Ptr& ptr) const 463 { 464 ensureCustomData (ptr); 465 466 return *ptr.getRefData().getCustomData()->asCreatureCustomData().mContainerStore; 467 } 468 getInventoryStore(const MWWorld::Ptr & ptr) const469 MWWorld::InventoryStore& Creature::getInventoryStore(const MWWorld::Ptr &ptr) const 470 { 471 if (hasInventoryStore(ptr)) 472 return dynamic_cast<MWWorld::InventoryStore&>(getContainerStore(ptr)); 473 else 474 throw std::runtime_error("this creature has no inventory store"); 475 } 476 hasInventoryStore(const MWWorld::Ptr & ptr) const477 bool Creature::hasInventoryStore(const MWWorld::Ptr &ptr) const 478 { 479 return isFlagBitSet(ptr, ESM::Creature::Weapon); 480 } 481 getScript(const MWWorld::ConstPtr & ptr) const482 std::string Creature::getScript (const MWWorld::ConstPtr& ptr) const 483 { 484 const MWWorld::LiveCellRef<ESM::Creature> *ref = ptr.get<ESM::Creature>(); 485 486 return ref->mBase->mScript; 487 } 488 isEssential(const MWWorld::ConstPtr & ptr) const489 bool Creature::isEssential (const MWWorld::ConstPtr& ptr) const 490 { 491 return isFlagBitSet(ptr, ESM::Creature::Essential); 492 } 493 registerSelf()494 void Creature::registerSelf() 495 { 496 std::shared_ptr<Class> instance (new Creature); 497 498 registerClass (typeid (ESM::Creature).name(), instance); 499 } 500 getMaxSpeed(const MWWorld::Ptr & ptr) const501 float Creature::getMaxSpeed(const MWWorld::Ptr &ptr) const 502 { 503 const MWMechanics::CreatureStats& stats = getCreatureStats(ptr); 504 505 if (stats.isParalyzed() || stats.getKnockedDown() || stats.isDead()) 506 return 0.f; 507 508 const GMST& gmst = getGmst(); 509 510 const MWBase::World *world = MWBase::Environment::get().getWorld(); 511 const MWMechanics::MagicEffects &mageffects = stats.getMagicEffects(); 512 513 float moveSpeed; 514 515 if(getEncumbrance(ptr) > getCapacity(ptr)) 516 moveSpeed = 0.0f; 517 else if(canFly(ptr) || (mageffects.get(ESM::MagicEffect::Levitate).getMagnitude() > 0 && 518 world->isLevitationEnabled())) 519 { 520 float flySpeed = 0.01f*(stats.getAttribute(ESM::Attribute::Speed).getModified() + 521 mageffects.get(ESM::MagicEffect::Levitate).getMagnitude()); 522 flySpeed = gmst.fMinFlySpeed->mValue.getFloat() + flySpeed*(gmst.fMaxFlySpeed->mValue.getFloat() - gmst.fMinFlySpeed->mValue.getFloat()); 523 const float normalizedEncumbrance = getNormalizedEncumbrance(ptr); 524 flySpeed *= 1.0f - gmst.fEncumberedMoveEffect->mValue.getFloat() * normalizedEncumbrance; 525 flySpeed = std::max(0.0f, flySpeed); 526 moveSpeed = flySpeed; 527 } 528 else if(world->isSwimming(ptr)) 529 moveSpeed = getSwimSpeed(ptr); 530 else 531 moveSpeed = getWalkSpeed(ptr); 532 533 return moveSpeed; 534 } 535 getMovementSettings(const MWWorld::Ptr & ptr) const536 MWMechanics::Movement& Creature::getMovementSettings (const MWWorld::Ptr& ptr) const 537 { 538 ensureCustomData (ptr); 539 540 return ptr.getRefData().getCustomData()->asCreatureCustomData().mMovement; 541 } 542 hasToolTip(const MWWorld::ConstPtr & ptr) const543 bool Creature::hasToolTip(const MWWorld::ConstPtr& ptr) const 544 { 545 if (!ptr.getRefData().getCustomData() || MWBase::Environment::get().getWindowManager()->isGuiMode()) 546 return true; 547 548 const CreatureCustomData& customData = ptr.getRefData().getCustomData()->asCreatureCustomData(); 549 550 if (customData.mCreatureStats.isDead() && customData.mCreatureStats.isDeathAnimationFinished()) 551 return true; 552 553 return !customData.mCreatureStats.getAiSequence().isInCombat(); 554 } 555 getToolTipInfo(const MWWorld::ConstPtr & ptr,int count) const556 MWGui::ToolTipInfo Creature::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const 557 { 558 const MWWorld::LiveCellRef<ESM::Creature> *ref = ptr.get<ESM::Creature>(); 559 560 MWGui::ToolTipInfo info; 561 info.caption = MyGUI::TextIterator::toTagsString(getName(ptr)); 562 563 std::string text; 564 if (MWBase::Environment::get().getWindowManager()->getFullHelp()) 565 text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); 566 info.text = text; 567 568 return info; 569 } 570 getArmorRating(const MWWorld::Ptr & ptr) const571 float Creature::getArmorRating (const MWWorld::Ptr& ptr) const 572 { 573 // Equipment armor rating is deliberately ignored. 574 return getCreatureStats(ptr).getMagicEffects().get(ESM::MagicEffect::Shield).getMagnitude(); 575 } 576 getCapacity(const MWWorld::Ptr & ptr) const577 float Creature::getCapacity (const MWWorld::Ptr& ptr) const 578 { 579 const MWMechanics::CreatureStats& stats = getCreatureStats (ptr); 580 return stats.getAttribute(ESM::Attribute::Strength).getModified() * 5; 581 } 582 getServices(const MWWorld::ConstPtr & actor) const583 int Creature::getServices(const MWWorld::ConstPtr &actor) const 584 { 585 return actor.get<ESM::Creature>()->mBase->mAiData.mServices; 586 } 587 isPersistent(const MWWorld::ConstPtr & actor) const588 bool Creature::isPersistent(const MWWorld::ConstPtr &actor) const 589 { 590 const MWWorld::LiveCellRef<ESM::Creature>* ref = actor.get<ESM::Creature>(); 591 return ref->mBase->mPersistent; 592 } 593 getSoundIdFromSndGen(const MWWorld::Ptr & ptr,const std::string & name) const594 std::string Creature::getSoundIdFromSndGen(const MWWorld::Ptr &ptr, const std::string &name) const 595 { 596 int type = getSndGenTypeFromName(ptr, name); 597 if (type < 0) 598 return std::string(); 599 600 std::vector<const ESM::SoundGenerator*> sounds; 601 std::vector<const ESM::SoundGenerator*> fallbacksounds; 602 603 MWWorld::LiveCellRef<ESM::Creature>* ref = ptr.get<ESM::Creature>(); 604 605 const std::string& ourId = (ref->mBase->mOriginal.empty()) ? ptr.getCellRef().getRefId() : ref->mBase->mOriginal; 606 607 const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); 608 auto sound = store.get<ESM::SoundGenerator>().begin(); 609 while (sound != store.get<ESM::SoundGenerator>().end()) 610 { 611 if (type == sound->mType && !sound->mCreature.empty() && Misc::StringUtils::ciEqual(ourId, sound->mCreature)) 612 sounds.push_back(&*sound); 613 if (type == sound->mType && sound->mCreature.empty()) 614 fallbacksounds.push_back(&*sound); 615 ++sound; 616 } 617 618 if (sounds.empty()) 619 { 620 const std::string model = getModel(ptr); 621 if (!model.empty()) 622 { 623 for (const ESM::Creature &creature : store.get<ESM::Creature>()) 624 { 625 if (creature.mId != ourId && creature.mOriginal != ourId && !creature.mModel.empty() 626 && Misc::StringUtils::ciEqual(model, "meshes\\" + creature.mModel)) 627 { 628 const std::string& fallbackId = !creature.mOriginal.empty() ? creature.mOriginal : creature.mId; 629 sound = store.get<ESM::SoundGenerator>().begin(); 630 while (sound != store.get<ESM::SoundGenerator>().end()) 631 { 632 if (type == sound->mType && !sound->mCreature.empty() 633 && Misc::StringUtils::ciEqual(fallbackId, sound->mCreature)) 634 sounds.push_back(&*sound); 635 ++sound; 636 } 637 break; 638 } 639 } 640 } 641 } 642 643 if (!sounds.empty()) 644 return sounds[Misc::Rng::rollDice(sounds.size())]->mSound; 645 if (!fallbacksounds.empty()) 646 return fallbacksounds[Misc::Rng::rollDice(fallbacksounds.size())]->mSound; 647 648 return std::string(); 649 } 650 copyToCellImpl(const MWWorld::ConstPtr & ptr,MWWorld::CellStore & cell) const651 MWWorld::Ptr Creature::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const 652 { 653 const MWWorld::LiveCellRef<ESM::Creature> *ref = ptr.get<ESM::Creature>(); 654 655 return MWWorld::Ptr(cell.insert(ref), &cell); 656 } 657 isBipedal(const MWWorld::ConstPtr & ptr) const658 bool Creature::isBipedal(const MWWorld::ConstPtr &ptr) const 659 { 660 return isFlagBitSet(ptr, ESM::Creature::Bipedal); 661 } 662 canFly(const MWWorld::ConstPtr & ptr) const663 bool Creature::canFly(const MWWorld::ConstPtr &ptr) const 664 { 665 return isFlagBitSet(ptr, ESM::Creature::Flies); 666 } 667 canSwim(const MWWorld::ConstPtr & ptr) const668 bool Creature::canSwim(const MWWorld::ConstPtr &ptr) const 669 { 670 return isFlagBitSet(ptr, static_cast<ESM::Creature::Flags>(ESM::Creature::Swims | ESM::Creature::Bipedal)); 671 } 672 canWalk(const MWWorld::ConstPtr & ptr) const673 bool Creature::canWalk(const MWWorld::ConstPtr &ptr) const 674 { 675 return isFlagBitSet(ptr, static_cast<ESM::Creature::Flags>(ESM::Creature::Walks | ESM::Creature::Bipedal)); 676 } 677 getSndGenTypeFromName(const MWWorld::Ptr & ptr,const std::string & name)678 int Creature::getSndGenTypeFromName(const MWWorld::Ptr &ptr, const std::string &name) 679 { 680 if(name == "left") 681 { 682 MWBase::World *world = MWBase::Environment::get().getWorld(); 683 if(world->isFlying(ptr)) 684 return -1; 685 osg::Vec3f pos(ptr.getRefData().getPosition().asVec3()); 686 if(world->isUnderwater(ptr.getCell(), pos) || world->isWalkingOnWater(ptr)) 687 return ESM::SoundGenerator::SwimLeft; 688 if(world->isOnGround(ptr)) 689 return ESM::SoundGenerator::LeftFoot; 690 return -1; 691 } 692 if(name == "right") 693 { 694 MWBase::World *world = MWBase::Environment::get().getWorld(); 695 if(world->isFlying(ptr)) 696 return -1; 697 osg::Vec3f pos(ptr.getRefData().getPosition().asVec3()); 698 if(world->isUnderwater(ptr.getCell(), pos) || world->isWalkingOnWater(ptr)) 699 return ESM::SoundGenerator::SwimRight; 700 if(world->isOnGround(ptr)) 701 return ESM::SoundGenerator::RightFoot; 702 return -1; 703 } 704 if(name == "swimleft") 705 return ESM::SoundGenerator::SwimLeft; 706 if(name == "swimright") 707 return ESM::SoundGenerator::SwimRight; 708 if(name == "moan") 709 return ESM::SoundGenerator::Moan; 710 if(name == "roar") 711 return ESM::SoundGenerator::Roar; 712 if(name == "scream") 713 return ESM::SoundGenerator::Scream; 714 if(name == "land") 715 return ESM::SoundGenerator::Land; 716 717 throw std::runtime_error(std::string("Unexpected soundgen type: ")+name); 718 } 719 getSkill(const MWWorld::Ptr & ptr,int skill) const720 float Creature::getSkill(const MWWorld::Ptr &ptr, int skill) const 721 { 722 MWWorld::LiveCellRef<ESM::Creature> *ref = 723 ptr.get<ESM::Creature>(); 724 725 const ESM::Skill* skillRecord = MWBase::Environment::get().getWorld()->getStore().get<ESM::Skill>().find(skill); 726 727 switch (skillRecord->mData.mSpecialization) 728 { 729 case ESM::Class::Combat: 730 return ref->mBase->mData.mCombat; 731 case ESM::Class::Magic: 732 return ref->mBase->mData.mMagic; 733 case ESM::Class::Stealth: 734 return ref->mBase->mData.mStealth; 735 default: 736 throw std::runtime_error("invalid specialisation"); 737 } 738 } 739 getBloodTexture(const MWWorld::ConstPtr & ptr) const740 int Creature::getBloodTexture(const MWWorld::ConstPtr &ptr) const 741 { 742 return ptr.get<ESM::Creature>()->mBase->mBloodType; 743 } 744 readAdditionalState(const MWWorld::Ptr & ptr,const ESM::ObjectState & state) const745 void Creature::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) 746 const 747 { 748 if (!state.mHasCustomState) 749 return; 750 751 if (state.mVersion > 0) 752 { 753 if (!ptr.getRefData().getCustomData()) 754 { 755 // Create a CustomData, but don't fill it from ESM records (not needed) 756 std::unique_ptr<CreatureCustomData> data (new CreatureCustomData); 757 758 if (hasInventoryStore(ptr)) 759 data->mContainerStore = std::make_unique<MWWorld::InventoryStore>(); 760 else 761 data->mContainerStore = std::make_unique<MWWorld::ContainerStore>(); 762 763 ptr.getRefData().setCustomData (std::move(data)); 764 } 765 } 766 else 767 ensureCustomData(ptr); // in openmw 0.30 savegames not all state was saved yet, so need to load it regardless. 768 769 CreatureCustomData& customData = ptr.getRefData().getCustomData()->asCreatureCustomData(); 770 const ESM::CreatureState& creatureState = state.asCreatureState(); 771 customData.mContainerStore->readState (creatureState.mInventory); 772 bool spellsInitialised = customData.mCreatureStats.getSpells().setSpells(ptr.get<ESM::Creature>()->mBase->mId); 773 if(spellsInitialised) 774 customData.mCreatureStats.getSpells().clear(); 775 customData.mCreatureStats.readState (creatureState.mCreatureStats); 776 } 777 writeAdditionalState(const MWWorld::ConstPtr & ptr,ESM::ObjectState & state) const778 void Creature::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) 779 const 780 { 781 if (!ptr.getRefData().getCustomData()) 782 { 783 state.mHasCustomState = false; 784 return; 785 } 786 787 if (ptr.getRefData().getCount() <= 0) 788 { 789 state.mHasCustomState = false; 790 return; 791 } 792 793 const CreatureCustomData& customData = ptr.getRefData().getCustomData()->asCreatureCustomData(); 794 ESM::CreatureState& creatureState = state.asCreatureState(); 795 customData.mContainerStore->writeState (creatureState.mInventory); 796 customData.mCreatureStats.writeState (creatureState.mCreatureStats); 797 } 798 getBaseGold(const MWWorld::ConstPtr & ptr) const799 int Creature::getBaseGold(const MWWorld::ConstPtr& ptr) const 800 { 801 return ptr.get<ESM::Creature>()->mBase->mData.mGold; 802 } 803 respawn(const MWWorld::Ptr & ptr) const804 void Creature::respawn(const MWWorld::Ptr &ptr) const 805 { 806 const MWMechanics::CreatureStats& creatureStats = getCreatureStats(ptr); 807 if (ptr.getRefData().getCount() > 0 && !creatureStats.isDead()) 808 return; 809 810 if (!creatureStats.isDeathAnimationFinished()) 811 return; 812 813 const MWWorld::Store<ESM::GameSetting>& gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>(); 814 static const float fCorpseRespawnDelay = gmst.find("fCorpseRespawnDelay")->mValue.getFloat(); 815 static const float fCorpseClearDelay = gmst.find("fCorpseClearDelay")->mValue.getFloat(); 816 817 float delay = ptr.getRefData().getCount() == 0 ? fCorpseClearDelay : std::min(fCorpseRespawnDelay, fCorpseClearDelay); 818 819 if (isFlagBitSet(ptr, ESM::Creature::Respawn) 820 && creatureStats.getTimeOfDeath() + delay <= MWBase::Environment::get().getWorld()->getTimeStamp()) 821 { 822 if (ptr.getCellRef().hasContentFile()) 823 { 824 if (ptr.getRefData().getCount() == 0) 825 { 826 ptr.getRefData().setCount(1); 827 const std::string& script = getScript(ptr); 828 if(!script.empty()) 829 MWBase::Environment::get().getWorld()->getLocalScripts().add(script, ptr); 830 } 831 832 MWBase::Environment::get().getWorld()->removeContainerScripts(ptr); 833 ptr.getRefData().setCustomData(nullptr); 834 835 // Reset to original position 836 MWBase::Environment::get().getWorld()->moveObject(ptr, ptr.getCellRef().getPosition().pos[0], 837 ptr.getCellRef().getPosition().pos[1], 838 ptr.getCellRef().getPosition().pos[2]); 839 } 840 } 841 } 842 getBaseFightRating(const MWWorld::ConstPtr & ptr) const843 int Creature::getBaseFightRating(const MWWorld::ConstPtr &ptr) const 844 { 845 const MWWorld::LiveCellRef<ESM::Creature> *ref = ptr.get<ESM::Creature>(); 846 return ref->mBase->mAiData.mFight; 847 } 848 adjustScale(const MWWorld::ConstPtr & ptr,osg::Vec3f & scale,bool) const849 void Creature::adjustScale(const MWWorld::ConstPtr &ptr, osg::Vec3f &scale, bool /* rendering */) const 850 { 851 const MWWorld::LiveCellRef<ESM::Creature> *ref = ptr.get<ESM::Creature>(); 852 scale *= ref->mBase->mScale; 853 } 854 setBaseAISetting(const std::string & id,MWMechanics::CreatureStats::AiSetting setting,int value) const855 void Creature::setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value) const 856 { 857 MWMechanics::setBaseAISetting<ESM::Creature>(id, setting, value); 858 } 859 modifyBaseInventory(const std::string & actorId,const std::string & itemId,int amount) const860 void Creature::modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount) const 861 { 862 MWMechanics::modifyBaseInventory<ESM::Creature>(actorId, itemId, amount); 863 } 864 getWalkSpeed(const MWWorld::Ptr & ptr) const865 float Creature::getWalkSpeed(const MWWorld::Ptr& ptr) const 866 { 867 const MWMechanics::CreatureStats& stats = getCreatureStats(ptr); 868 const GMST& gmst = getGmst(); 869 870 return gmst.fMinWalkSpeedCreature->mValue.getFloat() 871 + 0.01f * stats.getAttribute(ESM::Attribute::Speed).getModified() 872 * (gmst.fMaxWalkSpeedCreature->mValue.getFloat() - gmst.fMinWalkSpeedCreature->mValue.getFloat()); 873 } 874 getRunSpeed(const MWWorld::Ptr & ptr) const875 float Creature::getRunSpeed(const MWWorld::Ptr& ptr) const 876 { 877 return getWalkSpeed(ptr); 878 } 879 getSwimSpeed(const MWWorld::Ptr & ptr) const880 float Creature::getSwimSpeed(const MWWorld::Ptr& ptr) const 881 { 882 const MWMechanics::CreatureStats& stats = getCreatureStats(ptr); 883 const GMST& gmst = getGmst(); 884 const MWMechanics::MagicEffects& mageffects = stats.getMagicEffects(); 885 886 return getWalkSpeed(ptr) 887 * (1.0f + 0.01f * mageffects.get(ESM::MagicEffect::SwiftSwim).getMagnitude()) 888 * (gmst.fSwimRunBase->mValue.getFloat() 889 + 0.01f * getSkill(ptr, ESM::Skill::Athletics) * gmst.fSwimRunAthleticsMult->mValue.getFloat()); 890 } 891 } 892