#include "creature.hpp" #include #include #include #include #include #include #include #include #include #include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/aisetting.hpp" #include "../mwmechanics/combat.hpp" #include "../mwmechanics/creaturecustomdataresetter.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/difficultyscaling.hpp" #include "../mwmechanics/disease.hpp" #include "../mwmechanics/inventory.hpp" #include "../mwmechanics/magiceffects.hpp" #include "../mwmechanics/movement.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/setbaseaisetting.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" #include "../mwworld/actionopen.hpp" #include "../mwworld/actiontalk.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/customdata.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/failedaction.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/localscripts.hpp" #include "../mwworld/ptr.hpp" #include "../mwworld/worldmodel.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" #include "../mwgui/tooltips.hpp" #include "classmodel.hpp" #include "nameorid.hpp" namespace { bool isFlagBitSet(const MWWorld::ConstPtr& ptr, ESM::Creature::Flags bitMask) { return (ptr.get()->mBase->mFlags & bitMask) != 0; } } namespace MWClass { class CreatureCustomData : public MWWorld::TypedCustomData { public: MWMechanics::CreatureStats mCreatureStats; std::unique_ptr mContainerStore; // may be InventoryStore for some creatures MWMechanics::Movement mMovement; CreatureCustomData() = default; CreatureCustomData(const CreatureCustomData& other); CreatureCustomData(CreatureCustomData&& other) = default; CreatureCustomData& asCreatureCustomData() override { return *this; } const CreatureCustomData& asCreatureCustomData() const override { return *this; } }; CreatureCustomData::CreatureCustomData(const CreatureCustomData& other) : mCreatureStats(other.mCreatureStats) , mContainerStore(other.mContainerStore->clone()) , mMovement(other.mMovement) { } Creature::Creature() : MWWorld::RegisteredClass(ESM::Creature::sRecordId) { } const Creature::GMST& Creature::getGmst() { static const GMST staticGmst = [] { GMST gmst; const MWWorld::Store& store = MWBase::Environment::get().getESMStore()->get(); gmst.fMinWalkSpeedCreature = store.find("fMinWalkSpeedCreature"); gmst.fMaxWalkSpeedCreature = store.find("fMaxWalkSpeedCreature"); gmst.fEncumberedMoveEffect = store.find("fEncumberedMoveEffect"); gmst.fSneakSpeedMultiplier = store.find("fSneakSpeedMultiplier"); gmst.fAthleticsRunBonus = store.find("fAthleticsRunBonus"); gmst.fBaseRunMultiplier = store.find("fBaseRunMultiplier"); gmst.fMinFlySpeed = store.find("fMinFlySpeed"); gmst.fMaxFlySpeed = store.find("fMaxFlySpeed"); gmst.fSwimRunBase = store.find("fSwimRunBase"); gmst.fSwimRunAthleticsMult = store.find("fSwimRunAthleticsMult"); gmst.fKnockDownMult = store.find("fKnockDownMult"); gmst.iKnockDownOddsMult = store.find("iKnockDownOddsMult"); gmst.iKnockDownOddsBase = store.find("iKnockDownOddsBase"); return gmst; }(); return staticGmst; } void Creature::ensureCustomData(const MWWorld::Ptr& ptr) const { if (!ptr.getRefData().getCustomData()) { MWBase::Environment::get().getWorldModel()->registerPtr(ptr); auto tempData = std::make_unique(); CreatureCustomData* data = tempData.get(); MWMechanics::CreatureCustomDataResetter resetter{ ptr }; ptr.getRefData().setCustomData(std::move(tempData)); MWWorld::LiveCellRef* ref = ptr.get(); // creature stats for (size_t i = 0; i < ref->mBase->mData.mAttributes.size(); ++i) data->mCreatureStats.setAttribute(ESM::Attribute::indexToRefId(i), ref->mBase->mData.mAttributes[i]); data->mCreatureStats.setHealth(static_cast(ref->mBase->mData.mHealth)); data->mCreatureStats.setMagicka(static_cast(ref->mBase->mData.mMana)); data->mCreatureStats.setFatigue(static_cast(ref->mBase->mData.mFatigue)); data->mCreatureStats.setLevel(ref->mBase->mData.mLevel); data->mCreatureStats.getAiSequence().fill(ref->mBase->mAiPackage); data->mCreatureStats.setAiSetting(MWMechanics::AiSetting::Hello, ref->mBase->mAiData.mHello); data->mCreatureStats.setAiSetting(MWMechanics::AiSetting::Fight, ref->mBase->mAiData.mFight); data->mCreatureStats.setAiSetting(MWMechanics::AiSetting::Flee, ref->mBase->mAiData.mFlee); data->mCreatureStats.setAiSetting(MWMechanics::AiSetting::Alarm, ref->mBase->mAiData.mAlarm); // Persistent actors with 0 health do not play death animation if (data->mCreatureStats.isDead()) data->mCreatureStats.setDeathAnimationFinished(isPersistent(ptr)); // spells bool spellsInitialised = data->mCreatureStats.getSpells().setSpells(ref->mBase->mId); if (!spellsInitialised) data->mCreatureStats.getSpells().addAllToInstance(ref->mBase->mSpells.mList); // inventory bool hasInventory = hasInventoryStore(ptr); if (hasInventory) data->mContainerStore = std::make_unique(); else data->mContainerStore = std::make_unique(); data->mContainerStore->setPtr(ptr); data->mCreatureStats.setGoldPool(ref->mBase->mData.mGold); resetter.mPtr = {}; auto& prng = MWBase::Environment::get().getWorld()->getPrng(); getContainerStore(ptr).fill(ref->mBase->mInventory, ptr.getCellRef().getRefId(), prng); if (hasInventory) getInventoryStore(ptr).autoEquip(); } } void Creature::insertObjectRendering( const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { MWRender::Objects& objects = renderingInterface.getObjects(); objects.insertCreature(ptr, model, hasInventoryStore(ptr)); } std::string_view Creature::getModel(const MWWorld::ConstPtr& ptr) const { return getClassModel(ptr); } void Creature::getModelsToPreload(const MWWorld::ConstPtr& ptr, std::vector& models) const { std::string_view model = getModel(ptr); if (!model.empty()) models.push_back(model); const MWWorld::CustomData* customData = ptr.getRefData().getCustomData(); if (customData && hasInventoryStore(ptr)) { const auto& invStore = static_cast(*customData->asCreatureCustomData().mContainerStore); for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) { MWWorld::ConstContainerStoreIterator equipped = invStore.getSlot(slot); if (equipped != invStore.end()) { model = equipped->getClass().getModel(*equipped); if (!model.empty()) models.push_back(model); } } } } std::string_view Creature::getName(const MWWorld::ConstPtr& ptr) const { return getNameOrId(ptr); } MWMechanics::CreatureStats& Creature::getCreatureStats(const MWWorld::Ptr& ptr) const { ensureCustomData(ptr); return ptr.getRefData().getCustomData()->asCreatureCustomData().mCreatureStats; } bool Creature::evaluateHit(const MWWorld::Ptr& ptr, MWWorld::Ptr& victim, osg::Vec3f& hitPosition) const { victim = MWWorld::Ptr(); hitPosition = osg::Vec3f(); // Get the weapon used (if hand-to-hand, weapon = inv.end()) MWWorld::Ptr weapon; if (hasInventoryStore(ptr)) { MWWorld::InventoryStore& inv = getInventoryStore(ptr); MWWorld::ContainerStoreIterator weaponslot = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); if (weaponslot != inv.end() && weaponslot->getType() == ESM::Weapon::sRecordId) weapon = *weaponslot; } MWBase::World* world = MWBase::Environment::get().getWorld(); const MWWorld::Store& store = world->getStore().get(); float dist = store.find("fCombatDistance")->mValue.getFloat(); if (!weapon.isEmpty()) dist *= weapon.get()->mBase->mData.mReach; const std::pair result = MWMechanics::getHitContact(ptr, dist); if (result.first.isEmpty()) // Didn't hit anything return true; // Note that earlier we returned true in spite of an apparent failure to hit anything alive. // This is because hitting nothing is not a "miss" and should be handled as such character controller-side. victim = result.first; hitPosition = result.second; float hitchance = MWMechanics::getHitChance(ptr, victim, ptr.get()->mBase->mData.mCombat); return Misc::Rng::roll0to99(world->getPrng()) < hitchance; } void Creature::hit(const MWWorld::Ptr& ptr, float attackStrength, int type, const MWWorld::Ptr& victim, const osg::Vec3f& hitPosition, bool success) const { MWMechanics::CreatureStats& stats = getCreatureStats(ptr); if (stats.getDrawState() != MWMechanics::DrawState::Weapon) return; MWWorld::Ptr weapon; if (hasInventoryStore(ptr)) { MWWorld::InventoryStore& inv = getInventoryStore(ptr); MWWorld::ContainerStoreIterator weaponslot = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); if (weaponslot != inv.end() && weaponslot->getType() == ESM::Weapon::sRecordId) weapon = *weaponslot; } MWMechanics::applyFatigueLoss(ptr, weapon, attackStrength); if (victim.isEmpty()) return; // Didn't hit anything const MWWorld::Class& othercls = victim.getClass(); MWMechanics::CreatureStats& otherstats = othercls.getCreatureStats(victim); if (otherstats.isDead()) // Can't hit dead actors return; if (!success) { victim.getClass().onHit( victim, 0.0f, false, MWWorld::Ptr(), ptr, osg::Vec3f(), false, MWMechanics::DamageSourceType::Melee); MWMechanics::reduceWeaponCondition(0.f, false, weapon, ptr); return; } MWWorld::LiveCellRef* ref = ptr.get(); int min, max; switch (type) { case 0: min = ref->mBase->mData.mAttack[0]; max = ref->mBase->mData.mAttack[1]; break; case 1: min = ref->mBase->mData.mAttack[2]; max = ref->mBase->mData.mAttack[3]; break; case 2: default: min = ref->mBase->mData.mAttack[4]; max = ref->mBase->mData.mAttack[5]; break; } float damage = min + (max - min) * attackStrength; bool healthdmg = true; if (!weapon.isEmpty()) { const unsigned char* attack = nullptr; if (type == ESM::Weapon::AT_Chop) attack = weapon.get()->mBase->mData.mChop.data(); else if (type == ESM::Weapon::AT_Slash) attack = weapon.get()->mBase->mData.mSlash.data(); else if (type == ESM::Weapon::AT_Thrust) attack = weapon.get()->mBase->mData.mThrust.data(); if (attack) { damage = attack[0] + ((attack[1] - attack[0]) * attackStrength); MWMechanics::adjustWeaponDamage(damage, weapon, ptr); MWMechanics::reduceWeaponCondition(damage, true, weapon, ptr); MWMechanics::resistNormalWeapon(victim, ptr, weapon, damage); } // Apply "On hit" enchanted weapons MWMechanics::applyOnStrikeEnchantment(ptr, victim, weapon, hitPosition); } else if (isBipedal(ptr)) { MWMechanics::getHandToHandDamage(ptr, victim, damage, healthdmg, attackStrength); } MWMechanics::applyElementalShields(ptr, victim); if (MWMechanics::blockMeleeAttack(ptr, victim, weapon, damage, attackStrength)) damage = 0; MWMechanics::diseaseContact(victim, ptr); victim.getClass().onHit( victim, damage, healthdmg, weapon, ptr, hitPosition, true, MWMechanics::DamageSourceType::Melee); } 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 MWMechanics::DamageSourceType sourceType) const { MWMechanics::CreatureStats& stats = getCreatureStats(ptr); // Self defense bool setOnPcHitMe = true; // NOTE: 'object' and/or 'attacker' may be empty. if (!attacker.isEmpty() && attacker.getClass().isActor() && !stats.getAiSequence().isInCombat(attacker)) { stats.setAttacked(true); // No retaliation for totally static creatures (they have no movement or attacks anyway) if (isMobile(ptr)) { bool complain = sourceType == MWMechanics::DamageSourceType::Melee; bool supportFriendlyFire = sourceType != MWMechanics::DamageSourceType::Ranged; if (supportFriendlyFire && MWMechanics::friendlyHit(attacker, ptr, complain)) setOnPcHitMe = false; else setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker); } } // Attacker and target store each other as hitattemptactor if they have no one stored yet if (!attacker.isEmpty() && attacker.getClass().isActor()) { MWMechanics::CreatureStats& statsAttacker = attacker.getClass().getCreatureStats(attacker); // First handle the attacked actor if ((stats.getHitAttemptActorId() == -1) && (statsAttacker.getAiSequence().isInCombat(ptr) || attacker == MWMechanics::getPlayer())) stats.setHitAttemptActorId(statsAttacker.getActorId()); // Next handle the attacking actor if ((statsAttacker.getHitAttemptActorId() == -1) && (statsAttacker.getAiSequence().isInCombat(ptr) || attacker == MWMechanics::getPlayer())) statsAttacker.setHitAttemptActorId(stats.getActorId()); } if (!object.isEmpty()) stats.setLastHitAttemptObject(object.getCellRef().getRefId()); if (setOnPcHitMe && !attacker.isEmpty() && attacker == MWMechanics::getPlayer()) { const ESM::RefId& script = ptr.get()->mBase->mScript; /* Set the OnPCHitMe script variable. The script is responsible for clearing it. */ if (!script.empty()) ptr.getRefData().getLocals().setVarByInt(script, "onpchitme", 1); } if (!successful) { // Missed if (!attacker.isEmpty() && attacker == MWMechanics::getPlayer()) MWBase::Environment::get().getSoundManager()->playSound3D( ptr, ESM::RefId::stringRefId("miss"), 1.0f, 1.0f); return; } if (!object.isEmpty()) stats.setLastHitObject(object.getCellRef().getRefId()); if (damage < 0.001f) damage = 0; if (damage > 0.f) { if (!attacker.isEmpty()) { // Check for knockdown float agilityTerm = stats.getAttribute(ESM::Attribute::Agility).getModified() * getGmst().fKnockDownMult->mValue.getFloat(); float knockdownTerm = stats.getAttribute(ESM::Attribute::Agility).getModified() * getGmst().iKnockDownOddsMult->mValue.getInteger() * 0.01f + getGmst().iKnockDownOddsBase->mValue.getInteger(); auto& prng = MWBase::Environment::get().getWorld()->getPrng(); if (ishealth && agilityTerm <= damage && knockdownTerm <= Misc::Rng::roll0to99(prng)) stats.setKnockedDown(true); else stats.setHitRecovery(true); // Is this supposed to always occur? } if (ishealth) { damage *= damage / (damage + getArmorRating(ptr)); damage = std::max(1.f, damage); if (!attacker.isEmpty()) { damage = scaleDamage(damage, attacker, ptr); MWBase::Environment::get().getWorld()->spawnBloodEffect(ptr, hitPosition); } MWBase::Environment::get().getSoundManager()->playSound3D( ptr, ESM::RefId::stringRefId("Health Damage"), 1.0f, 1.0f); MWMechanics::DynamicStat health(stats.getHealth()); health.setCurrent(health.getCurrent() - damage); stats.setHealth(health); } else { MWMechanics::DynamicStat fatigue(stats.getFatigue()); fatigue.setCurrent(fatigue.getCurrent() - damage, true); stats.setFatigue(fatigue); } } } std::unique_ptr Creature::activate(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { if (actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) { const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); auto& prng = MWBase::Environment::get().getWorld()->getPrng(); const ESM::Sound* sound = store.get().searchRandom("WolfCreature", prng); std::unique_ptr action = std::make_unique("#{sWerewolfRefusal}"); if (sound) action->setSound(sound->mId); return action; } const MWMechanics::CreatureStats& stats = getCreatureStats(ptr); if (stats.isDead()) { // by default user can loot non-fighting actors during death animation if (Settings::game().mCanLootDuringDeathAnimation) return std::make_unique(ptr); // otherwise wait until death animation if (stats.isDeathAnimationFinished()) return std::make_unique(ptr); } else if (!stats.getKnockedDown()) return std::make_unique(ptr); // Tribunal and some mod companions oddly enough must use open action as fallback if (!getScript(ptr).empty() && ptr.getRefData().getLocals().getIntVar(getScript(ptr), "companion")) return std::make_unique(ptr); return std::make_unique(); } MWWorld::ContainerStore& Creature::getContainerStore(const MWWorld::Ptr& ptr) const { ensureCustomData(ptr); return *ptr.getRefData().getCustomData()->asCreatureCustomData().mContainerStore; } MWWorld::InventoryStore& Creature::getInventoryStore(const MWWorld::Ptr& ptr) const { if (hasInventoryStore(ptr)) return static_cast(getContainerStore(ptr)); else throw std::runtime_error("this creature has no inventory store"); } bool Creature::hasInventoryStore(const MWWorld::ConstPtr& ptr) const { return isFlagBitSet(ptr, ESM::Creature::Weapon); } ESM::RefId Creature::getScript(const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mScript; } bool Creature::isEssential(const MWWorld::ConstPtr& ptr) const { return isFlagBitSet(ptr, ESM::Creature::Essential); } float Creature::getMaxSpeed(const MWWorld::Ptr& ptr) const { const MWMechanics::CreatureStats& stats = getCreatureStats(ptr); if (stats.isParalyzed() || stats.getKnockedDown() || stats.isDead()) return 0.f; const GMST& gmst = getGmst(); const MWBase::World* world = MWBase::Environment::get().getWorld(); const MWMechanics::MagicEffects& mageffects = stats.getMagicEffects(); float moveSpeed; if (getEncumbrance(ptr) > getCapacity(ptr)) moveSpeed = 0.0f; else if (canFly(ptr) || (mageffects.getOrDefault(ESM::MagicEffect::Levitate).getMagnitude() > 0 && world->isLevitationEnabled())) { float flySpeed = 0.01f * (stats.getAttribute(ESM::Attribute::Speed).getModified() + mageffects.getOrDefault(ESM::MagicEffect::Levitate).getMagnitude()); flySpeed = gmst.fMinFlySpeed->mValue.getFloat() + flySpeed * (gmst.fMaxFlySpeed->mValue.getFloat() - gmst.fMinFlySpeed->mValue.getFloat()); const float normalizedEncumbrance = getNormalizedEncumbrance(ptr); flySpeed *= 1.0f - gmst.fEncumberedMoveEffect->mValue.getFloat() * normalizedEncumbrance; flySpeed = std::max(0.0f, flySpeed); moveSpeed = flySpeed; } else if (world->isSwimming(ptr)) moveSpeed = getSwimSpeed(ptr); else moveSpeed = getWalkSpeed(ptr); return moveSpeed; } MWMechanics::Movement& Creature::getMovementSettings(const MWWorld::Ptr& ptr) const { ensureCustomData(ptr); return ptr.getRefData().getCustomData()->asCreatureCustomData().mMovement; } bool Creature::hasToolTip(const MWWorld::ConstPtr& ptr) const { if (!ptr.getRefData().getCustomData() || MWBase::Environment::get().getWindowManager()->isGuiMode()) return true; const CreatureCustomData& customData = ptr.getRefData().getCustomData()->asCreatureCustomData(); if (customData.mCreatureStats.isDead() && customData.mCreatureStats.isDeathAnimationFinished()) return true; const MWMechanics::AiSequence& aiSeq = customData.mCreatureStats.getAiSequence(); return !aiSeq.isInCombat() || aiSeq.isFleeing(); } MWGui::ToolTipInfo Creature::getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const { const MWWorld::LiveCellRef* ref = ptr.get(); MWGui::ToolTipInfo info; std::string_view name = getName(ptr); info.caption = MyGUI::TextIterator::toTagsString(MyGUI::UString(name)); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) info.extra += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); return info; } float Creature::getArmorRating(const MWWorld::Ptr& ptr) const { // Equipment armor rating is deliberately ignored. return getCreatureStats(ptr).getMagicEffects().getOrDefault(ESM::MagicEffect::Shield).getMagnitude(); } float Creature::getCapacity(const MWWorld::Ptr& ptr) const { const MWMechanics::CreatureStats& stats = getCreatureStats(ptr); return stats.getAttribute(ESM::Attribute::Strength).getModified() * 5; } int Creature::getServices(const MWWorld::ConstPtr& actor) const { return actor.get()->mBase->mAiData.mServices; } bool Creature::isPersistent(const MWWorld::ConstPtr& actor) const { const MWWorld::LiveCellRef* ref = actor.get(); return (ref->mBase->mRecordFlags & ESM::FLAG_Persistent) != 0; } ESM::RefId Creature::getSoundIdFromSndGen(const MWWorld::Ptr& ptr, std::string_view name) const { int type = getSndGenTypeFromName(ptr, name); if (type < 0) return ESM::RefId(); std::vector sounds; std::vector fallbacksounds; MWWorld::LiveCellRef* ref = ptr.get(); const ESM::RefId& ourId = (ref->mBase->mOriginal.empty()) ? ptr.getCellRef().getRefId() : ref->mBase->mOriginal; const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); auto sound = store.get().begin(); while (sound != store.get().end()) { if (type == sound->mType && !sound->mCreature.empty() && ourId == sound->mCreature) sounds.push_back(&*sound); if (type == sound->mType && sound->mCreature.empty()) fallbacksounds.push_back(&*sound); ++sound; } if (sounds.empty()) { const std::string_view model = getModel(ptr); if (!model.empty()) { for (const ESM::Creature& creature : store.get()) { if (creature.mId != ourId && creature.mOriginal != ourId && !creature.mModel.empty() && Misc::StringUtils::ciEqual(model, creature.mModel)) { const ESM::RefId& fallbackId = !creature.mOriginal.empty() ? creature.mOriginal : creature.mId; sound = store.get().begin(); while (sound != store.get().end()) { if (type == sound->mType && !sound->mCreature.empty() && fallbackId == sound->mCreature) sounds.push_back(&*sound); ++sound; } break; } } } } auto& prng = MWBase::Environment::get().getWorld()->getPrng(); if (!sounds.empty()) return sounds[Misc::Rng::rollDice(sounds.size(), prng)]->mSound; if (!fallbacksounds.empty()) return fallbacksounds[Misc::Rng::rollDice(fallbacksounds.size(), prng)]->mSound; return ESM::RefId(); } MWWorld::Ptr Creature::copyToCellImpl(const MWWorld::ConstPtr& ptr, MWWorld::CellStore& cell) const { const MWWorld::LiveCellRef* ref = ptr.get(); MWWorld::Ptr newPtr(cell.insert(ref), &cell); if (newPtr.getRefData().getCustomData()) { MWBase::Environment::get().getWorldModel()->registerPtr(newPtr); newPtr.getClass().getContainerStore(newPtr).setPtr(newPtr); } return newPtr; } bool Creature::isBipedal(const MWWorld::ConstPtr& ptr) const { return isFlagBitSet(ptr, ESM::Creature::Bipedal); } bool Creature::canFly(const MWWorld::ConstPtr& ptr) const { return isFlagBitSet(ptr, ESM::Creature::Flies); } bool Creature::canSwim(const MWWorld::ConstPtr& ptr) const { return isFlagBitSet(ptr, static_cast(ESM::Creature::Swims | ESM::Creature::Bipedal)); } bool Creature::canWalk(const MWWorld::ConstPtr& ptr) const { return isFlagBitSet(ptr, static_cast(ESM::Creature::Walks | ESM::Creature::Bipedal)); } int Creature::getSndGenTypeFromName(const MWWorld::Ptr& ptr, std::string_view name) { if (name == "left") { MWBase::World* world = MWBase::Environment::get().getWorld(); if (world->isFlying(ptr)) return -1; osg::Vec3f pos(ptr.getRefData().getPosition().asVec3()); if (world->isUnderwater(ptr.getCell(), pos) || world->isWalkingOnWater(ptr)) return ESM::SoundGenerator::SwimLeft; if (world->isOnGround(ptr)) return ESM::SoundGenerator::LeftFoot; return -1; } if (name == "right") { MWBase::World* world = MWBase::Environment::get().getWorld(); if (world->isFlying(ptr)) return -1; osg::Vec3f pos(ptr.getRefData().getPosition().asVec3()); if (world->isUnderwater(ptr.getCell(), pos) || world->isWalkingOnWater(ptr)) return ESM::SoundGenerator::SwimRight; if (world->isOnGround(ptr)) return ESM::SoundGenerator::RightFoot; return -1; } if (name == "swimleft") return ESM::SoundGenerator::SwimLeft; if (name == "swimright") return ESM::SoundGenerator::SwimRight; if (name == "moan") return ESM::SoundGenerator::Moan; if (name == "roar") return ESM::SoundGenerator::Roar; if (name == "scream") return ESM::SoundGenerator::Scream; if (name == "land") return ESM::SoundGenerator::Land; throw std::runtime_error("Unexpected soundgen type: " + std::string(name)); } float Creature::getSkill(const MWWorld::Ptr& ptr, ESM::RefId id) const { MWWorld::LiveCellRef* ref = ptr.get(); const ESM::Skill* skillRecord = MWBase::Environment::get().getESMStore()->get().find(id); switch (skillRecord->mData.mSpecialization) { case ESM::Class::Combat: return ref->mBase->mData.mCombat; case ESM::Class::Magic: return ref->mBase->mData.mMagic; case ESM::Class::Stealth: return ref->mBase->mData.mStealth; default: throw std::runtime_error("invalid specialisation"); } } int Creature::getBloodTexture(const MWWorld::ConstPtr& ptr) const { return ptr.get()->mBase->mBloodType; } void Creature::readAdditionalState(const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const { if (!state.mHasCustomState) return; const ESM::CreatureState& creatureState = state.asCreatureState(); if (!ptr.getRefData().getCustomData()) { if (creatureState.mCreatureStats.mMissingACDT) ensureCustomData(ptr); else { // Create a CustomData, but don't fill it from ESM records (not needed) auto data = std::make_unique(); if (hasInventoryStore(ptr)) data->mContainerStore = std::make_unique(); else data->mContainerStore = std::make_unique(); MWBase::Environment::get().getWorldModel()->registerPtr(ptr); data->mContainerStore->setPtr(ptr); ptr.getRefData().setCustomData(std::move(data)); } } CreatureCustomData& customData = ptr.getRefData().getCustomData()->asCreatureCustomData(); customData.mContainerStore->readState(creatureState.mInventory); bool spellsInitialised = customData.mCreatureStats.getSpells().setSpells(ptr.get()->mBase->mId); if (spellsInitialised) customData.mCreatureStats.getSpells().clear(); customData.mCreatureStats.readState(creatureState.mCreatureStats); } void Creature::writeAdditionalState(const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const { if (!ptr.getRefData().getCustomData()) { state.mHasCustomState = false; return; } const CreatureCustomData& customData = ptr.getRefData().getCustomData()->asCreatureCustomData(); if (ptr.getCellRef().getCount() <= 0 && (!isFlagBitSet(ptr, ESM::Creature::Respawn) || !customData.mCreatureStats.isDead())) { state.mHasCustomState = false; return; } ESM::CreatureState& creatureState = state.asCreatureState(); customData.mContainerStore->writeState(creatureState.mInventory); customData.mCreatureStats.writeState(creatureState.mCreatureStats); } int Creature::getBaseGold(const MWWorld::ConstPtr& ptr) const { return ptr.get()->mBase->mData.mGold; } void Creature::respawn(const MWWorld::Ptr& ptr) const { const MWMechanics::CreatureStats& creatureStats = getCreatureStats(ptr); if (ptr.getCellRef().getCount() > 0 && !creatureStats.isDead()) return; if (!creatureStats.isDeathAnimationFinished()) return; const MWWorld::Store& gmst = MWBase::Environment::get().getESMStore()->get(); static const float fCorpseRespawnDelay = gmst.find("fCorpseRespawnDelay")->mValue.getFloat(); static const float fCorpseClearDelay = gmst.find("fCorpseClearDelay")->mValue.getFloat(); float delay = ptr.getCellRef().getCount() == 0 ? fCorpseClearDelay : std::min(fCorpseRespawnDelay, fCorpseClearDelay); if (isFlagBitSet(ptr, ESM::Creature::Respawn) && creatureStats.getTimeOfDeath() + delay <= MWBase::Environment::get().getWorld()->getTimeStamp()) { if (ptr.getCellRef().hasContentFile()) { if (ptr.getCellRef().getCount() == 0) { ptr.getCellRef().setCount(1); const ESM::RefId& script = getScript(ptr); if (!script.empty()) MWBase::Environment::get().getWorld()->getLocalScripts().add(script, ptr); } MWBase::Environment::get().getWorld()->removeContainerScripts(ptr); MWBase::Environment::get().getWindowManager()->onDeleteCustomData(ptr); ptr.getRefData().setCustomData(nullptr); // Reset to original position MWBase::Environment::get().getWorld()->moveObject(ptr, ptr.getCellRef().getPosition().asVec3()); MWBase::Environment::get().getWorld()->rotateObject( ptr, ptr.getCellRef().getPosition().asRotationVec3(), MWBase::RotationFlag_none); } } } int Creature::getBaseFightRating(const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef* ref = ptr.get(); return ref->mBase->mAiData.mFight; } void Creature::adjustScale(const MWWorld::ConstPtr& ptr, osg::Vec3f& scale, bool /* rendering */) const { const MWWorld::LiveCellRef* ref = ptr.get(); scale *= ref->mBase->mScale; } void Creature::setBaseAISetting(const ESM::RefId& id, MWMechanics::AiSetting setting, int value) const { MWMechanics::setBaseAISetting(id, setting, value); } void Creature::modifyBaseInventory(const ESM::RefId& actorId, const ESM::RefId& itemId, int amount) const { MWMechanics::modifyBaseInventory(actorId, itemId, amount); } float Creature::getWalkSpeed(const MWWorld::Ptr& ptr) const { const MWMechanics::CreatureStats& stats = getCreatureStats(ptr); const GMST& gmst = getGmst(); return gmst.fMinWalkSpeedCreature->mValue.getFloat() + 0.01f * stats.getAttribute(ESM::Attribute::Speed).getModified() * (gmst.fMaxWalkSpeedCreature->mValue.getFloat() - gmst.fMinWalkSpeedCreature->mValue.getFloat()); } float Creature::getRunSpeed(const MWWorld::Ptr& ptr) const { return getWalkSpeed(ptr); } float Creature::getSwimSpeed(const MWWorld::Ptr& ptr) const { const MWMechanics::CreatureStats& stats = getCreatureStats(ptr); const MWMechanics::MagicEffects& mageffects = stats.getMagicEffects(); return getSwimSpeedImpl(ptr, getGmst(), mageffects, getWalkSpeed(ptr)); } }