mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-02-05 15:40:10 +00:00
ce263af393
Remove WindowManager wrappers. It's not safe to use WindowManager in all places and it's not required. Environment stores resource system providing VFS required to call these functions. In the case of ObjectPaging it's available from the member variable. Also ObjectPaging::createChunk may access WindowManager when it's already destructed when exiting the game because it's destructed before CellPreloader finishes all background jobs. Engine::mResourceSystem is destructed after all other systems so it's safe to use it.
899 lines
36 KiB
C++
899 lines
36 KiB
C++
#include "creature.hpp"
|
|
|
|
#include <components/misc/rng.hpp>
|
|
#include <components/debug/debuglog.hpp>
|
|
#include <components/esm3/loadcrea.hpp>
|
|
#include <components/esm3/creaturestate.hpp>
|
|
#include <components/settings/settings.hpp>
|
|
|
|
#include "../mwmechanics/creaturestats.hpp"
|
|
#include "../mwmechanics/magiceffects.hpp"
|
|
#include "../mwmechanics/movement.hpp"
|
|
#include "../mwmechanics/disease.hpp"
|
|
#include "../mwmechanics/spellcasting.hpp"
|
|
#include "../mwmechanics/difficultyscaling.hpp"
|
|
#include "../mwmechanics/npcstats.hpp"
|
|
#include "../mwmechanics/combat.hpp"
|
|
#include "../mwmechanics/actorutil.hpp"
|
|
#include "../mwmechanics/creaturecustomdataresetter.hpp"
|
|
#include "../mwmechanics/aisetting.hpp"
|
|
#include "../mwmechanics/inventory.hpp"
|
|
|
|
#include "../mwbase/environment.hpp"
|
|
#include "../mwbase/mechanicsmanager.hpp"
|
|
#include "../mwbase/windowmanager.hpp"
|
|
#include "../mwbase/world.hpp"
|
|
#include "../mwbase/soundmanager.hpp"
|
|
|
|
#include "../mwworld/ptr.hpp"
|
|
#include "../mwworld/actiontalk.hpp"
|
|
#include "../mwworld/actionopen.hpp"
|
|
#include "../mwworld/failedaction.hpp"
|
|
#include "../mwworld/customdata.hpp"
|
|
#include "../mwworld/containerstore.hpp"
|
|
#include "../mwworld/cellstore.hpp"
|
|
#include "../mwworld/localscripts.hpp"
|
|
#include "../mwworld/inventorystore.hpp"
|
|
#include "../mwworld/esmstore.hpp"
|
|
|
|
#include "../mwrender/renderinginterface.hpp"
|
|
#include "../mwrender/objects.hpp"
|
|
|
|
#include "../mwgui/tooltips.hpp"
|
|
|
|
#include "classmodel.hpp"
|
|
|
|
namespace
|
|
{
|
|
bool isFlagBitSet(const MWWorld::ConstPtr &ptr, ESM::Creature::Flags bitMask)
|
|
{
|
|
return (ptr.get<ESM::Creature>()->mBase->mFlags & bitMask) != 0;
|
|
}
|
|
}
|
|
|
|
namespace MWClass
|
|
{
|
|
|
|
class CreatureCustomData : public MWWorld::TypedCustomData<CreatureCustomData>
|
|
{
|
|
public:
|
|
MWMechanics::CreatureStats mCreatureStats;
|
|
std::unique_ptr<MWWorld::ContainerStore> 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<Creature, Actor>(ESM::Creature::sRecordId)
|
|
{
|
|
}
|
|
|
|
const Creature::GMST& Creature::getGmst()
|
|
{
|
|
static const GMST staticGmst = []
|
|
{
|
|
GMST gmst;
|
|
|
|
const MWBase::World *world = MWBase::Environment::get().getWorld();
|
|
const MWWorld::Store<ESM::GameSetting> &store = world->getStore().get<ESM::GameSetting>();
|
|
|
|
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())
|
|
{
|
|
auto tempData = std::make_unique<CreatureCustomData>();
|
|
CreatureCustomData* data = tempData.get();
|
|
MWMechanics::CreatureCustomDataResetter resetter {ptr};
|
|
ptr.getRefData().setCustomData(std::move(tempData));
|
|
|
|
MWWorld::LiveCellRef<ESM::Creature> *ref = ptr.get<ESM::Creature>();
|
|
|
|
// creature stats
|
|
data->mCreatureStats.setAttribute(ESM::Attribute::Strength, ref->mBase->mData.mStrength);
|
|
data->mCreatureStats.setAttribute(ESM::Attribute::Intelligence, ref->mBase->mData.mIntelligence);
|
|
data->mCreatureStats.setAttribute(ESM::Attribute::Willpower, ref->mBase->mData.mWillpower);
|
|
data->mCreatureStats.setAttribute(ESM::Attribute::Agility, ref->mBase->mData.mAgility);
|
|
data->mCreatureStats.setAttribute(ESM::Attribute::Speed, ref->mBase->mData.mSpeed);
|
|
data->mCreatureStats.setAttribute(ESM::Attribute::Endurance, ref->mBase->mData.mEndurance);
|
|
data->mCreatureStats.setAttribute(ESM::Attribute::Personality, ref->mBase->mData.mPersonality);
|
|
data->mCreatureStats.setAttribute(ESM::Attribute::Luck, ref->mBase->mData.mLuck);
|
|
data->mCreatureStats.setHealth(static_cast<float>(ref->mBase->mData.mHealth));
|
|
data->mCreatureStats.setMagicka(static_cast<float>(ref->mBase->mData.mMana));
|
|
data->mCreatureStats.setFatigue(static_cast<float>(ref->mBase->mData.mFatigue));
|
|
|
|
data->mCreatureStats.setLevel(ref->mBase->mData.mLevel);
|
|
|
|
data->mCreatureStats.getAiSequence().fill(ref->mBase->mAiPackage);
|
|
|
|
data->mCreatureStats.setAiSetting (MWMechanics::CreatureStats::AI_Hello, ref->mBase->mAiData.mHello);
|
|
data->mCreatureStats.setAiSetting (MWMechanics::CreatureStats::AI_Fight, ref->mBase->mAiData.mFight);
|
|
data->mCreatureStats.setAiSetting (MWMechanics::CreatureStats::AI_Flee, ref->mBase->mAiData.mFlee);
|
|
data->mCreatureStats.setAiSetting (MWMechanics::CreatureStats::AI_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<MWWorld::InventoryStore>();
|
|
else
|
|
data->mContainerStore = std::make_unique<MWWorld::ContainerStore>();
|
|
|
|
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(ptr);
|
|
}
|
|
}
|
|
|
|
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 Creature::getModel(const MWWorld::ConstPtr &ptr) const
|
|
{
|
|
return getClassModel<ESM::Creature>(ptr);
|
|
}
|
|
|
|
void Creature::getModelsToPreload(const MWWorld::Ptr &ptr, std::vector<std::string> &models) const
|
|
{
|
|
std::string model = getModel(ptr);
|
|
if (!model.empty())
|
|
models.push_back(model);
|
|
|
|
// FIXME: use const version of InventoryStore functions once they are available
|
|
if (hasInventoryStore(ptr))
|
|
{
|
|
const MWWorld::InventoryStore& invStore = getInventoryStore(ptr);
|
|
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 Creature::getName (const MWWorld::ConstPtr& ptr) const
|
|
{
|
|
const MWWorld::LiveCellRef<ESM::Creature> *ref = ptr.get<ESM::Creature>();
|
|
const std::string& name = ref->mBase->mName;
|
|
|
|
return !name.empty() ? name : ref->mBase->mId;
|
|
}
|
|
|
|
MWMechanics::CreatureStats& Creature::getCreatureStats (const MWWorld::Ptr& ptr) const
|
|
{
|
|
ensureCustomData (ptr);
|
|
|
|
return ptr.getRefData().getCustomData()->asCreatureCustomData().mCreatureStats;
|
|
}
|
|
|
|
|
|
void Creature::hit(const MWWorld::Ptr& ptr, float attackStrength, int type) const
|
|
{
|
|
MWWorld::LiveCellRef<ESM::Creature> *ref =
|
|
ptr.get<ESM::Creature>();
|
|
const MWWorld::Store<ESM::GameSetting> &gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
|
|
MWMechanics::CreatureStats &stats = getCreatureStats(ptr);
|
|
|
|
if (stats.getDrawState() != MWMechanics::DrawState_Weapon)
|
|
return;
|
|
|
|
// 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;
|
|
}
|
|
|
|
MWMechanics::applyFatigueLoss(ptr, weapon, attackStrength);
|
|
|
|
float dist = gmst.find("fCombatDistance")->mValue.getFloat();
|
|
if (!weapon.isEmpty())
|
|
dist *= weapon.get<ESM::Weapon>()->mBase->mData.mReach;
|
|
|
|
// For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result.
|
|
std::vector<MWWorld::Ptr> targetActors;
|
|
stats.getAiSequence().getCombatTargets(targetActors);
|
|
|
|
std::pair<MWWorld::Ptr, osg::Vec3f> result = MWBase::Environment::get().getWorld()->getHitContact(ptr, dist, targetActors);
|
|
if (result.first.isEmpty())
|
|
return; // Didn't hit anything
|
|
|
|
MWWorld::Ptr victim = result.first;
|
|
|
|
if (!victim.getClass().isActor())
|
|
return; // Can't hit non-actors
|
|
|
|
osg::Vec3f hitPosition (result.second);
|
|
|
|
float hitchance = MWMechanics::getHitChance(ptr, victim, ref->mBase->mData.mCombat);
|
|
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
|
|
if(Misc::Rng::roll0to99(prng) >= hitchance)
|
|
{
|
|
victim.getClass().onHit(victim, 0.0f, false, MWWorld::Ptr(), ptr, osg::Vec3f(), false);
|
|
MWMechanics::reduceWeaponCondition(0.f, false, weapon, ptr);
|
|
return;
|
|
}
|
|
|
|
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<ESM::Weapon>()->mBase->mData.mChop;
|
|
else if(type == ESM::Weapon::AT_Slash)
|
|
attack = weapon.get<ESM::Weapon>()->mBase->mData.mSlash;
|
|
else if(type == ESM::Weapon::AT_Thrust)
|
|
attack = weapon.get<ESM::Weapon>()->mBase->mData.mThrust;
|
|
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);
|
|
}
|
|
|
|
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::CreatureStats& stats = getCreatureStats(ptr);
|
|
|
|
// NOTE: 'object' and/or 'attacker' may be empty.
|
|
if (!attacker.isEmpty() && attacker.getClass().isActor() && !stats.getAiSequence().isInCombat(attacker))
|
|
stats.setAttacked(true);
|
|
|
|
// Self defense
|
|
bool setOnPcHitMe = true; // Note OnPcHitMe is not set for friendly hits.
|
|
|
|
// No retaliation for totally static creatures (they have no movement or attacks anyway)
|
|
if (isMobile(ptr) && !attacker.isEmpty())
|
|
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 std::string &script = ptr.get<ESM::Creature>()->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, "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, "Health Damage", 1.0f, 1.0f);
|
|
|
|
MWMechanics::DynamicStat<float> health(stats.getHealth());
|
|
health.setCurrent(health.getCurrent() - damage);
|
|
stats.setHealth(health);
|
|
}
|
|
else
|
|
{
|
|
MWMechanics::DynamicStat<float> fatigue(stats.getFatigue());
|
|
fatigue.setCurrent(fatigue.getCurrent() - damage, true);
|
|
stats.setFatigue(fatigue);
|
|
}
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<MWWorld::Action> 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().getWorld()->getStore();
|
|
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
|
|
const ESM::Sound *sound = store.get<ESM::Sound>().searchRandom("WolfCreature", prng);
|
|
|
|
std::unique_ptr<MWWorld::Action> action = std::make_unique<MWWorld::FailedAction>("#{sWerewolfRefusal}");
|
|
if(sound) action->setSound(sound->mId);
|
|
|
|
return action;
|
|
}
|
|
|
|
const MWMechanics::CreatureStats& stats = getCreatureStats(ptr);
|
|
|
|
if(stats.isDead())
|
|
{
|
|
bool canLoot = Settings::Manager::getBool ("can loot during death animation", "Game");
|
|
|
|
// by default user can loot friendly actors during death animation
|
|
if (canLoot && !stats.getAiSequence().isInCombat())
|
|
return std::make_unique<MWWorld::ActionOpen>(ptr);
|
|
|
|
// otherwise wait until death animation
|
|
if(stats.isDeathAnimationFinished())
|
|
return std::make_unique<MWWorld::ActionOpen>(ptr);
|
|
}
|
|
else if (!stats.getAiSequence().isInCombat() && !stats.getKnockedDown())
|
|
return std::make_unique<MWWorld::ActionTalk>(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<MWWorld::ActionOpen>(ptr);
|
|
|
|
return std::make_unique<MWWorld::FailedAction>();
|
|
}
|
|
|
|
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 dynamic_cast<MWWorld::InventoryStore&>(getContainerStore(ptr));
|
|
else
|
|
throw std::runtime_error("this creature has no inventory store");
|
|
}
|
|
|
|
bool Creature::hasInventoryStore(const MWWorld::Ptr &ptr) const
|
|
{
|
|
return isFlagBitSet(ptr, ESM::Creature::Weapon);
|
|
}
|
|
|
|
std::string Creature::getScript (const MWWorld::ConstPtr& ptr) const
|
|
{
|
|
const MWWorld::LiveCellRef<ESM::Creature> *ref = ptr.get<ESM::Creature>();
|
|
|
|
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.get(ESM::MagicEffect::Levitate).getMagnitude() > 0 &&
|
|
world->isLevitationEnabled()))
|
|
{
|
|
float flySpeed = 0.01f*(stats.getAttribute(ESM::Attribute::Speed).getModified() +
|
|
mageffects.get(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;
|
|
|
|
return !customData.mCreatureStats.getAiSequence().isInCombat();
|
|
}
|
|
|
|
MWGui::ToolTipInfo Creature::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const
|
|
{
|
|
const MWWorld::LiveCellRef<ESM::Creature> *ref = ptr.get<ESM::Creature>();
|
|
|
|
MWGui::ToolTipInfo info;
|
|
info.caption = MyGUI::TextIterator::toTagsString(getName(ptr));
|
|
|
|
std::string text;
|
|
if (MWBase::Environment::get().getWindowManager()->getFullHelp())
|
|
text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script");
|
|
info.text = text;
|
|
|
|
return info;
|
|
}
|
|
|
|
float Creature::getArmorRating (const MWWorld::Ptr& ptr) const
|
|
{
|
|
// Equipment armor rating is deliberately ignored.
|
|
return getCreatureStats(ptr).getMagicEffects().get(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<ESM::Creature>()->mBase->mAiData.mServices;
|
|
}
|
|
|
|
bool Creature::isPersistent(const MWWorld::ConstPtr &actor) const
|
|
{
|
|
const MWWorld::LiveCellRef<ESM::Creature>* ref = actor.get<ESM::Creature>();
|
|
return (ref->mBase->mRecordFlags & ESM::FLAG_Persistent) != 0;
|
|
}
|
|
|
|
std::string Creature::getSoundIdFromSndGen(const MWWorld::Ptr &ptr, const std::string &name) const
|
|
{
|
|
int type = getSndGenTypeFromName(ptr, name);
|
|
if (type < 0)
|
|
return std::string();
|
|
|
|
std::vector<const ESM::SoundGenerator*> sounds;
|
|
std::vector<const ESM::SoundGenerator*> fallbacksounds;
|
|
|
|
MWWorld::LiveCellRef<ESM::Creature>* ref = ptr.get<ESM::Creature>();
|
|
|
|
const std::string& ourId = (ref->mBase->mOriginal.empty()) ? ptr.getCellRef().getRefId() : ref->mBase->mOriginal;
|
|
|
|
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
|
|
auto sound = store.get<ESM::SoundGenerator>().begin();
|
|
while (sound != store.get<ESM::SoundGenerator>().end())
|
|
{
|
|
if (type == sound->mType && !sound->mCreature.empty() && Misc::StringUtils::ciEqual(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 model = getModel(ptr);
|
|
if (!model.empty())
|
|
{
|
|
const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS();
|
|
for (const ESM::Creature &creature : store.get<ESM::Creature>())
|
|
{
|
|
if (creature.mId != ourId && creature.mOriginal != ourId && !creature.mModel.empty()
|
|
&& Misc::StringUtils::ciEqual(model,
|
|
Misc::ResourceHelpers::correctMeshPath(creature.mModel, vfs)))
|
|
{
|
|
const std::string& fallbackId = !creature.mOriginal.empty() ? creature.mOriginal : creature.mId;
|
|
sound = store.get<ESM::SoundGenerator>().begin();
|
|
while (sound != store.get<ESM::SoundGenerator>().end())
|
|
{
|
|
if (type == sound->mType && !sound->mCreature.empty()
|
|
&& Misc::StringUtils::ciEqual(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 std::string();
|
|
}
|
|
|
|
MWWorld::Ptr Creature::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const
|
|
{
|
|
const MWWorld::LiveCellRef<ESM::Creature> *ref = ptr.get<ESM::Creature>();
|
|
|
|
return MWWorld::Ptr(cell.insert(ref), &cell);
|
|
}
|
|
|
|
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::Flags>(ESM::Creature::Swims | ESM::Creature::Bipedal));
|
|
}
|
|
|
|
bool Creature::canWalk(const MWWorld::ConstPtr &ptr) const
|
|
{
|
|
return isFlagBitSet(ptr, static_cast<ESM::Creature::Flags>(ESM::Creature::Walks | ESM::Creature::Bipedal));
|
|
}
|
|
|
|
int Creature::getSndGenTypeFromName(const MWWorld::Ptr &ptr, const std::string &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(std::string("Unexpected soundgen type: ")+name);
|
|
}
|
|
|
|
float Creature::getSkill(const MWWorld::Ptr &ptr, int skill) const
|
|
{
|
|
MWWorld::LiveCellRef<ESM::Creature> *ref =
|
|
ptr.get<ESM::Creature>();
|
|
|
|
const ESM::Skill* skillRecord = MWBase::Environment::get().getWorld()->getStore().get<ESM::Skill>().find(skill);
|
|
|
|
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<ESM::Creature>()->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 (state.mVersion > 0)
|
|
{
|
|
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<CreatureCustomData>();
|
|
|
|
if (hasInventoryStore(ptr))
|
|
data->mContainerStore = std::make_unique<MWWorld::InventoryStore>();
|
|
else
|
|
data->mContainerStore = std::make_unique<MWWorld::ContainerStore>();
|
|
|
|
ptr.getRefData().setCustomData (std::move(data));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
ensureCustomData(ptr); // in openmw 0.30 savegames not all state was saved yet, so need to load it regardless.
|
|
|
|
CreatureCustomData& customData = ptr.getRefData().getCustomData()->asCreatureCustomData();
|
|
|
|
customData.mContainerStore->readState (creatureState.mInventory);
|
|
bool spellsInitialised = customData.mCreatureStats.getSpells().setSpells(ptr.get<ESM::Creature>()->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;
|
|
}
|
|
|
|
if (ptr.getRefData().getCount() <= 0)
|
|
{
|
|
state.mHasCustomState = false;
|
|
return;
|
|
}
|
|
|
|
const CreatureCustomData& customData = ptr.getRefData().getCustomData()->asCreatureCustomData();
|
|
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<ESM::Creature>()->mBase->mData.mGold;
|
|
}
|
|
|
|
void Creature::respawn(const MWWorld::Ptr &ptr) const
|
|
{
|
|
const MWMechanics::CreatureStats& creatureStats = getCreatureStats(ptr);
|
|
if (ptr.getRefData().getCount() > 0 && !creatureStats.isDead())
|
|
return;
|
|
|
|
if (!creatureStats.isDeathAnimationFinished())
|
|
return;
|
|
|
|
const MWWorld::Store<ESM::GameSetting>& gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
|
|
static const float fCorpseRespawnDelay = gmst.find("fCorpseRespawnDelay")->mValue.getFloat();
|
|
static const float fCorpseClearDelay = gmst.find("fCorpseClearDelay")->mValue.getFloat();
|
|
|
|
float delay = ptr.getRefData().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.getRefData().getCount() == 0)
|
|
{
|
|
ptr.getRefData().setCount(1);
|
|
const std::string& 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<ESM::Creature> *ref = ptr.get<ESM::Creature>();
|
|
return ref->mBase->mAiData.mFight;
|
|
}
|
|
|
|
void Creature::adjustScale(const MWWorld::ConstPtr &ptr, osg::Vec3f &scale, bool /* rendering */) const
|
|
{
|
|
const MWWorld::LiveCellRef<ESM::Creature> *ref = ptr.get<ESM::Creature>();
|
|
scale *= ref->mBase->mScale;
|
|
}
|
|
|
|
void Creature::setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value) const
|
|
{
|
|
MWMechanics::setBaseAISetting<ESM::Creature>(id, setting, value);
|
|
}
|
|
|
|
void Creature::modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount) const
|
|
{
|
|
MWMechanics::modifyBaseInventory<ESM::Creature>(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));
|
|
}
|
|
}
|