forked from mirror/openmw-tes3mp
cc3c6ae7b8
Neither fAthleticsRunBonus, fBaseRunMultiplier or the creature's athletics skill (i.e. Combat stat) have any effect on the run speed (tested by setting those to absurd values). The new formula is just a guess and doesn't seem to be completely accurate.
878 lines
33 KiB
C++
878 lines
33 KiB
C++
|
|
#include "creature.hpp"
|
|
|
|
#include <components/esm/loadcrea.hpp>
|
|
#include <components/esm/creaturestate.hpp>
|
|
|
|
#include "../mwmechanics/creaturestats.hpp"
|
|
#include "../mwmechanics/magiceffects.hpp"
|
|
#include "../mwmechanics/movement.hpp"
|
|
#include "../mwmechanics/disease.hpp"
|
|
#include "../mwmechanics/spellcasting.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/physicssystem.hpp"
|
|
#include "../mwworld/cellstore.hpp"
|
|
|
|
#include "../mwrender/renderinginterface.hpp"
|
|
#include "../mwrender/actors.hpp"
|
|
|
|
#include "../mwgui/tooltips.hpp"
|
|
|
|
#include "../mwworld/inventorystore.hpp"
|
|
|
|
#include "../mwmechanics/npcstats.hpp"
|
|
#include "../mwmechanics/combat.hpp"
|
|
|
|
namespace
|
|
{
|
|
struct CreatureCustomData : public MWWorld::CustomData
|
|
{
|
|
MWMechanics::CreatureStats mCreatureStats;
|
|
MWWorld::ContainerStore* mContainerStore; // may be InventoryStore for some creatures
|
|
MWMechanics::Movement mMovement;
|
|
|
|
virtual MWWorld::CustomData *clone() const;
|
|
|
|
CreatureCustomData() : mContainerStore(0) {}
|
|
virtual ~CreatureCustomData() { delete mContainerStore; }
|
|
};
|
|
|
|
MWWorld::CustomData *CreatureCustomData::clone() const
|
|
{
|
|
CreatureCustomData* cloned = new CreatureCustomData (*this);
|
|
cloned->mContainerStore = mContainerStore->clone();
|
|
return cloned;
|
|
}
|
|
}
|
|
|
|
namespace MWClass
|
|
{
|
|
const Creature::GMST& Creature::getGmst()
|
|
{
|
|
static GMST gmst;
|
|
static bool inited = false;
|
|
if (!inited)
|
|
{
|
|
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");
|
|
inited = true;
|
|
}
|
|
return gmst;
|
|
}
|
|
|
|
void Creature::ensureCustomData (const MWWorld::Ptr& ptr) const
|
|
{
|
|
if (!ptr.getRefData().getCustomData())
|
|
{
|
|
std::auto_ptr<CreatureCustomData> data (new CreatureCustomData);
|
|
|
|
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 (ref->mBase->mData.mHealth);
|
|
data->mCreatureStats.setMagicka (ref->mBase->mData.mMana);
|
|
data->mCreatureStats.setFatigue (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);
|
|
|
|
// spells
|
|
for (std::vector<std::string>::const_iterator iter (ref->mBase->mSpells.mList.begin());
|
|
iter!=ref->mBase->mSpells.mList.end(); ++iter)
|
|
data->mCreatureStats.getSpells().add (*iter);
|
|
|
|
// inventory
|
|
if (ref->mBase->mFlags & ESM::Creature::Weapon)
|
|
data->mContainerStore = new MWWorld::InventoryStore();
|
|
else
|
|
data->mContainerStore = new MWWorld::ContainerStore();
|
|
|
|
data->mCreatureStats.setGoldPool(ref->mBase->mData.mGold);
|
|
|
|
// store
|
|
ptr.getRefData().setCustomData(data.release());
|
|
|
|
getContainerStore(ptr).fill(ref->mBase->mInventory, getId(ptr), "",
|
|
MWBase::Environment::get().getWorld()->getStore());
|
|
|
|
if (ref->mBase->mFlags & ESM::Creature::Weapon)
|
|
getInventoryStore(ptr).autoEquip(ptr);
|
|
}
|
|
}
|
|
|
|
std::string Creature::getId (const MWWorld::Ptr& ptr) const
|
|
{
|
|
MWWorld::LiveCellRef<ESM::Creature> *ref =
|
|
ptr.get<ESM::Creature>();
|
|
|
|
return ref->mBase->mId;
|
|
}
|
|
|
|
void Creature::adjustPosition(const MWWorld::Ptr& ptr) const
|
|
{
|
|
MWBase::Environment::get().getWorld()->adjustPosition(ptr);
|
|
}
|
|
|
|
void Creature::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const
|
|
{
|
|
MWWorld::LiveCellRef<ESM::Creature> *ref = ptr.get<ESM::Creature>();
|
|
|
|
MWRender::Actors& actors = renderingInterface.getActors();
|
|
actors.insertCreature(ptr, ref->mBase->mFlags & ESM::Creature::Weapon);
|
|
}
|
|
|
|
void Creature::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const
|
|
{
|
|
const std::string model = getModel(ptr);
|
|
if(!model.empty())
|
|
{
|
|
physics.addActor(ptr);
|
|
if (getCreatureStats(ptr).isDead())
|
|
MWBase::Environment::get().getWorld()->enableActorCollision(ptr, false);
|
|
}
|
|
MWBase::Environment::get().getMechanicsManager()->add(ptr);
|
|
}
|
|
|
|
std::string Creature::getModel(const MWWorld::Ptr &ptr) const
|
|
{
|
|
MWWorld::LiveCellRef<ESM::Creature> *ref =
|
|
ptr.get<ESM::Creature>();
|
|
assert (ref->mBase != NULL);
|
|
|
|
const std::string &model = ref->mBase->mModel;
|
|
if (!model.empty()) {
|
|
return "meshes\\" + model;
|
|
}
|
|
return "";
|
|
}
|
|
|
|
std::string Creature::getName (const MWWorld::Ptr& ptr) const
|
|
{
|
|
MWWorld::LiveCellRef<ESM::Creature> *ref =
|
|
ptr.get<ESM::Creature>();
|
|
|
|
return ref->mBase->mName;
|
|
}
|
|
|
|
MWMechanics::CreatureStats& Creature::getCreatureStats (const MWWorld::Ptr& ptr) const
|
|
{
|
|
ensureCustomData (ptr);
|
|
|
|
return dynamic_cast<CreatureCustomData&> (*ptr.getRefData().getCustomData()).mCreatureStats;
|
|
}
|
|
|
|
|
|
void Creature::hit(const MWWorld::Ptr& ptr, 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);
|
|
|
|
// Get the weapon used (if hand-to-hand, weapon = inv.end())
|
|
MWWorld::Ptr weapon;
|
|
if (ptr.getClass().hasInventoryStore(ptr))
|
|
{
|
|
MWWorld::InventoryStore &inv = getInventoryStore(ptr);
|
|
MWWorld::ContainerStoreIterator weaponslot = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
|
|
if (weaponslot != inv.end() && weaponslot->getTypeName() == typeid(ESM::Weapon).name())
|
|
weapon = *weaponslot;
|
|
}
|
|
|
|
// Reduce fatigue
|
|
// somewhat of a guess, but using the weapon weight makes sense
|
|
const float fFatigueAttackBase = gmst.find("fFatigueAttackBase")->getFloat();
|
|
const float fFatigueAttackMult = gmst.find("fFatigueAttackMult")->getFloat();
|
|
const float fWeaponFatigueMult = gmst.find("fWeaponFatigueMult")->getFloat();
|
|
MWMechanics::DynamicStat<float> fatigue = stats.getFatigue();
|
|
const float normalizedEncumbrance = getEncumbrance(ptr) / getCapacity(ptr);
|
|
float fatigueLoss = fFatigueAttackBase + normalizedEncumbrance * fFatigueAttackMult;
|
|
if (!weapon.isEmpty())
|
|
fatigueLoss += weapon.getClass().getWeight(weapon) * stats.getAttackStrength() * fWeaponFatigueMult;
|
|
fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss);
|
|
stats.setFatigue(fatigue);
|
|
|
|
// TODO: where is the distance defined?
|
|
float dist = 200.f;
|
|
if (!weapon.isEmpty())
|
|
{
|
|
const float fCombatDistance = gmst.find("fCombatDistance")->getFloat();
|
|
dist = fCombatDistance * weapon.get<ESM::Weapon>()->mBase->mData.mReach;
|
|
}
|
|
std::pair<MWWorld::Ptr, Ogre::Vector3> result = MWBase::Environment::get().getWorld()->getHitContact(ptr, dist);
|
|
if (result.first.isEmpty())
|
|
return; // Didn't hit anything
|
|
|
|
MWWorld::Ptr victim = result.first;
|
|
|
|
if (!victim.getClass().isActor())
|
|
return; // Can't hit non-actors
|
|
|
|
Ogre::Vector3 hitPosition = result.second;
|
|
|
|
float hitchance = MWMechanics::getHitChance(ptr, victim, ref->mBase->mData.mCombat);
|
|
|
|
if((::rand()/(RAND_MAX+1.0)) > hitchance/100.0f)
|
|
{
|
|
victim.getClass().onHit(victim, 0.0f, false, MWWorld::Ptr(), ptr, false);
|
|
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;
|
|
}
|
|
|
|
// I think this should be random, since attack1-3 animations don't have an attack strength like NPCs do
|
|
float damage = min + (max - min) * ::rand()/(RAND_MAX+1.0);
|
|
|
|
if (!weapon.isEmpty())
|
|
{
|
|
const bool weaphashealth = weapon.getClass().hasItemHealth(weapon);
|
|
const unsigned char *attack = NULL;
|
|
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)
|
|
{
|
|
float weaponDamage = attack[0] + ((attack[1]-attack[0])*stats.getAttackStrength());
|
|
weaponDamage *= 0.5f + (stats.getAttribute(ESM::Attribute::Luck).getModified() / 100.0f);
|
|
if(weaphashealth)
|
|
{
|
|
int weapmaxhealth = weapon.getClass().getItemMaxHealth(weapon);
|
|
int weaphealth = weapon.getClass().getItemHealth(weapon);
|
|
weaponDamage *= float(weaphealth) / weapmaxhealth;
|
|
|
|
if (!MWBase::Environment::get().getWorld()->getGodModeState())
|
|
{
|
|
// Reduce weapon charge by at least one, but cap at 0
|
|
weaphealth -= std::min(std::max(1,
|
|
(int)(damage * gmst.find("fWeaponDamageMult")->getFloat())), weaphealth);
|
|
|
|
weapon.getCellRef().setCharge(weaphealth);
|
|
}
|
|
|
|
// Weapon broken? unequip it
|
|
if (weapon.getCellRef().getCharge() == 0)
|
|
weapon = *getInventoryStore(ptr).unequipItem(weapon, ptr);
|
|
}
|
|
|
|
damage += weaponDamage;
|
|
}
|
|
|
|
// Apply "On hit" enchanted weapons
|
|
std::string enchantmentName = !weapon.isEmpty() ? weapon.getClass().getEnchantment(weapon) : "";
|
|
if (!enchantmentName.empty())
|
|
{
|
|
const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().find(
|
|
enchantmentName);
|
|
if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes)
|
|
{
|
|
MWMechanics::CastSpell cast(ptr, victim);
|
|
cast.mHitPosition = hitPosition;
|
|
cast.cast(weapon);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!weapon.isEmpty() && MWMechanics::blockMeleeAttack(ptr, victim, weapon, damage))
|
|
damage = 0;
|
|
|
|
if (damage > 0)
|
|
MWBase::Environment::get().getWorld()->spawnBloodEffect(victim, hitPosition);
|
|
|
|
MWMechanics::diseaseContact(victim, ptr);
|
|
|
|
victim.getClass().onHit(victim, damage, true, weapon, ptr, true);
|
|
}
|
|
|
|
void Creature::onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, bool successful) const
|
|
{
|
|
// NOTE: 'object' and/or 'attacker' may be empty.
|
|
|
|
getCreatureStats(ptr).setAttacked(true);
|
|
|
|
// Self defense
|
|
if (!attacker.isEmpty() && !MWBase::Environment::get().getMechanicsManager()->isAggressive(ptr, attacker)
|
|
&& (canWalk(ptr) || canFly(ptr) || canSwim(ptr))) // No retaliation for totally static creatures
|
|
// (they have no movement or attacks anyway)
|
|
MWBase::Environment::get().getMechanicsManager()->startCombat(ptr, attacker);
|
|
|
|
if(!successful)
|
|
{
|
|
// TODO: Handle HitAttemptOnMe script function
|
|
|
|
// Missed
|
|
MWBase::Environment::get().getSoundManager()->playSound3D(ptr, "miss", 1.0f, 1.0f);
|
|
return;
|
|
}
|
|
|
|
if(!object.isEmpty())
|
|
getCreatureStats(ptr).setLastHitObject(object.getClass().getId(object));
|
|
|
|
if(!attacker.isEmpty() && attacker.getRefData().getHandle() == "player")
|
|
{
|
|
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 (damage > 0.0f && !object.isEmpty())
|
|
MWMechanics::resistNormalWeapon(ptr, attacker, object, damage);
|
|
|
|
if (damage > 0.f)
|
|
{
|
|
// Check for knockdown
|
|
float agilityTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() * getGmst().fKnockDownMult->getFloat();
|
|
float knockdownTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified()
|
|
* getGmst().iKnockDownOddsMult->getInt() * 0.01 + getGmst().iKnockDownOddsBase->getInt();
|
|
int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
|
|
if (ishealth && agilityTerm <= damage && knockdownTerm <= roll)
|
|
{
|
|
getCreatureStats(ptr).setKnockedDown(true);
|
|
|
|
}
|
|
else
|
|
getCreatureStats(ptr).setHitRecovery(true); // Is this supposed to always occur?
|
|
|
|
if(ishealth)
|
|
{
|
|
MWBase::Environment::get().getSoundManager()->playSound3D(ptr, "Health Damage", 1.0f, 1.0f);
|
|
float health = getCreatureStats(ptr).getHealth().getCurrent() - damage;
|
|
setActorHealth(ptr, health, attacker);
|
|
}
|
|
else
|
|
{
|
|
MWMechanics::DynamicStat<float> fatigue(getCreatureStats(ptr).getFatigue());
|
|
fatigue.setCurrent(fatigue.getCurrent() - damage, true);
|
|
getCreatureStats(ptr).setFatigue(fatigue);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Creature::block(const MWWorld::Ptr &ptr) const
|
|
{
|
|
MWWorld::InventoryStore& inv = getInventoryStore(ptr);
|
|
MWWorld::ContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
|
|
if (shield == inv.end())
|
|
return;
|
|
|
|
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
|
|
switch(shield->getClass().getEquipmentSkill(*shield))
|
|
{
|
|
case ESM::Skill::LightArmor:
|
|
sndMgr->playSound3D(ptr, "Light Armor Hit", 1.0f, 1.0f);
|
|
break;
|
|
case ESM::Skill::MediumArmor:
|
|
sndMgr->playSound3D(ptr, "Medium Armor Hit", 1.0f, 1.0f);
|
|
break;
|
|
case ESM::Skill::HeavyArmor:
|
|
sndMgr->playSound3D(ptr, "Heavy Armor Hit", 1.0f, 1.0f);
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
|
|
void Creature::setActorHealth(const MWWorld::Ptr& ptr, float health, const MWWorld::Ptr& attacker) const
|
|
{
|
|
MWMechanics::CreatureStats &crstats = getCreatureStats(ptr);
|
|
bool wasDead = crstats.isDead();
|
|
|
|
MWMechanics::DynamicStat<float> stat(crstats.getHealth());
|
|
stat.setCurrent(health);
|
|
crstats.setHealth(stat);
|
|
|
|
if(!wasDead && crstats.isDead())
|
|
{
|
|
// actor was just killed
|
|
}
|
|
else if(wasDead && !crstats.isDead())
|
|
{
|
|
// actor was just resurrected
|
|
}
|
|
}
|
|
|
|
|
|
boost::shared_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();
|
|
const ESM::Sound *sound = store.get<ESM::Sound>().searchRandom("WolfCreature");
|
|
|
|
boost::shared_ptr<MWWorld::Action> action(new MWWorld::FailedAction("#{sWerewolfRefusal}"));
|
|
if(sound) action->setSound(sound->mId);
|
|
|
|
return action;
|
|
}
|
|
|
|
if(getCreatureStats(ptr).isDead())
|
|
return boost::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr, true));
|
|
return boost::shared_ptr<MWWorld::Action>(new MWWorld::ActionTalk(ptr));
|
|
}
|
|
|
|
MWWorld::ContainerStore& Creature::getContainerStore (const MWWorld::Ptr& ptr) const
|
|
{
|
|
ensureCustomData (ptr);
|
|
|
|
return *dynamic_cast<CreatureCustomData&> (*ptr.getRefData().getCustomData()).mContainerStore;
|
|
}
|
|
|
|
MWWorld::InventoryStore& Creature::getInventoryStore(const MWWorld::Ptr &ptr) const
|
|
{
|
|
MWWorld::LiveCellRef<ESM::Creature> *ref = ptr.get<ESM::Creature>();
|
|
|
|
if (ref->mBase->mFlags & ESM::Creature::Weapon)
|
|
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
|
|
{
|
|
MWWorld::LiveCellRef<ESM::Creature> *ref = ptr.get<ESM::Creature>();
|
|
|
|
return (ref->mBase->mFlags & ESM::Creature::Weapon);
|
|
}
|
|
|
|
std::string Creature::getScript (const MWWorld::Ptr& ptr) const
|
|
{
|
|
MWWorld::LiveCellRef<ESM::Creature> *ref = ptr.get<ESM::Creature>();
|
|
|
|
return ref->mBase->mScript;
|
|
}
|
|
|
|
bool Creature::isEssential (const MWWorld::Ptr& ptr) const
|
|
{
|
|
MWWorld::LiveCellRef<ESM::Creature> *ref =
|
|
ptr.get<ESM::Creature>();
|
|
|
|
return ref->mBase->mFlags & ESM::Creature::Essential;
|
|
}
|
|
|
|
void Creature::registerSelf()
|
|
{
|
|
boost::shared_ptr<Class> instance (new Creature);
|
|
|
|
registerClass (typeid (ESM::Creature).name(), instance);
|
|
}
|
|
|
|
bool Creature::hasToolTip (const MWWorld::Ptr& ptr) const
|
|
{
|
|
/// \todo We don't want tooltips for Creatures in combat mode.
|
|
|
|
return true;
|
|
}
|
|
|
|
float Creature::getSpeed(const MWWorld::Ptr &ptr) const
|
|
{
|
|
MWMechanics::CreatureStats& stats = getCreatureStats(ptr);
|
|
const GMST& gmst = getGmst();
|
|
|
|
float walkSpeed = gmst.fMinWalkSpeedCreature->getFloat() + 0.01 * stats.getAttribute(ESM::Attribute::Speed).getModified()
|
|
* (gmst.fMaxWalkSpeedCreature->getFloat() - gmst.fMinWalkSpeedCreature->getFloat());
|
|
|
|
const MWBase::World *world = MWBase::Environment::get().getWorld();
|
|
const MWMechanics::MagicEffects &mageffects = stats.getMagicEffects();
|
|
|
|
const float normalizedEncumbrance = getEncumbrance(ptr) / getCapacity(ptr);
|
|
|
|
bool running = ptr.getClass().getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Run);
|
|
|
|
float runSpeed = walkSpeed*6;
|
|
runSpeed = std::min(gmst.fMaxWalkSpeedCreature->getFloat(), runSpeed); // flame atronach runs way too fast without this
|
|
|
|
float moveSpeed;
|
|
if(normalizedEncumbrance >= 1.0f)
|
|
moveSpeed = 0.0f;
|
|
else if(canFly(ptr) || (mageffects.get(ESM::MagicEffect::Levitate).mMagnitude > 0 &&
|
|
world->isLevitationEnabled()))
|
|
{
|
|
float flySpeed = 0.01f*(stats.getAttribute(ESM::Attribute::Speed).getModified() +
|
|
mageffects.get(ESM::MagicEffect::Levitate).mMagnitude);
|
|
flySpeed = gmst.fMinFlySpeed->getFloat() + flySpeed*(gmst.fMaxFlySpeed->getFloat() - gmst.fMinFlySpeed->getFloat());
|
|
flySpeed *= 1.0f - gmst.fEncumberedMoveEffect->getFloat() * normalizedEncumbrance;
|
|
flySpeed = std::max(0.0f, flySpeed);
|
|
moveSpeed = flySpeed;
|
|
}
|
|
else if(world->isSwimming(ptr))
|
|
{
|
|
float swimSpeed = walkSpeed;
|
|
if(running)
|
|
swimSpeed = runSpeed;
|
|
swimSpeed *= 1.0f + 0.01f * mageffects.get(ESM::MagicEffect::SwiftSwim).mMagnitude;
|
|
swimSpeed *= gmst.fSwimRunBase->getFloat() + 0.01f*getSkill(ptr, ESM::Skill::Athletics) *
|
|
gmst.fSwimRunAthleticsMult->getFloat();
|
|
moveSpeed = swimSpeed;
|
|
}
|
|
else if(running)
|
|
moveSpeed = runSpeed;
|
|
else
|
|
moveSpeed = walkSpeed;
|
|
if(getMovementSettings(ptr).mPosition[0] != 0 && getMovementSettings(ptr).mPosition[1] == 0)
|
|
moveSpeed *= 0.75f;
|
|
|
|
return moveSpeed;
|
|
}
|
|
|
|
MWMechanics::Movement& Creature::getMovementSettings (const MWWorld::Ptr& ptr) const
|
|
{
|
|
ensureCustomData (ptr);
|
|
|
|
return dynamic_cast<CreatureCustomData&> (*ptr.getRefData().getCustomData()).mMovement;
|
|
}
|
|
|
|
Ogre::Vector3 Creature::getMovementVector (const MWWorld::Ptr& ptr) const
|
|
{
|
|
MWMechanics::Movement &movement = getMovementSettings(ptr);
|
|
Ogre::Vector3 vec(movement.mPosition);
|
|
movement.mPosition[0] = 0.0f;
|
|
movement.mPosition[1] = 0.0f;
|
|
movement.mPosition[2] = 0.0f;
|
|
return vec;
|
|
}
|
|
|
|
Ogre::Vector3 Creature::getRotationVector (const MWWorld::Ptr& ptr) const
|
|
{
|
|
MWMechanics::Movement &movement = getMovementSettings(ptr);
|
|
Ogre::Vector3 vec(movement.mRotation);
|
|
movement.mRotation[0] = 0.0f;
|
|
movement.mRotation[1] = 0.0f;
|
|
movement.mRotation[2] = 0.0f;
|
|
return vec;
|
|
}
|
|
|
|
MWGui::ToolTipInfo Creature::getToolTipInfo (const MWWorld::Ptr& ptr) const
|
|
{
|
|
MWWorld::LiveCellRef<ESM::Creature> *ref =
|
|
ptr.get<ESM::Creature>();
|
|
|
|
MWGui::ToolTipInfo info;
|
|
info.caption = ref->mBase->mName;
|
|
|
|
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
|
|
{
|
|
/// \todo add Shield magic effect magnitude here, controlled by a GMST (Vanilla vs MCP behaviour)
|
|
return 0.f;
|
|
}
|
|
|
|
float Creature::getCapacity (const MWWorld::Ptr& ptr) const
|
|
{
|
|
const MWMechanics::CreatureStats& stats = getCreatureStats (ptr);
|
|
return stats.getAttribute(0).getModified()*5;
|
|
}
|
|
|
|
float Creature::getEncumbrance (const MWWorld::Ptr& ptr) const
|
|
{
|
|
float weight = getContainerStore (ptr).getWeight();
|
|
|
|
const MWMechanics::CreatureStats& stats = getCreatureStats (ptr);
|
|
|
|
weight -= stats.getMagicEffects().get (MWMechanics::EffectKey (ESM::MagicEffect::Feather)).mMagnitude;
|
|
|
|
weight += stats.getMagicEffects().get (MWMechanics::EffectKey (ESM::MagicEffect::Burden)).mMagnitude;
|
|
|
|
if (weight<0)
|
|
weight = 0;
|
|
|
|
return weight;
|
|
}
|
|
|
|
|
|
int Creature::getServices(const MWWorld::Ptr &actor) const
|
|
{
|
|
MWWorld::LiveCellRef<ESM::Creature>* ref = actor.get<ESM::Creature>();
|
|
if (ref->mBase->mHasAI)
|
|
return ref->mBase->mAiData.mServices;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
bool Creature::isPersistent(const MWWorld::Ptr &actor) const
|
|
{
|
|
MWWorld::LiveCellRef<ESM::Creature>* ref = actor.get<ESM::Creature>();
|
|
return ref->mBase->mPersistent;
|
|
}
|
|
|
|
std::string Creature::getSoundIdFromSndGen(const MWWorld::Ptr &ptr, const std::string &name) const
|
|
{
|
|
const MWWorld::Store<ESM::SoundGenerator> &store = MWBase::Environment::get().getWorld()->getStore().get<ESM::SoundGenerator>();
|
|
|
|
int type = getSndGenTypeFromName(ptr, name);
|
|
if(type >= 0)
|
|
{
|
|
std::vector<const ESM::SoundGenerator*> sounds;
|
|
sounds.reserve(8);
|
|
|
|
std::string ptrid = Creature::getId(ptr);
|
|
MWWorld::Store<ESM::SoundGenerator>::iterator sound = store.begin();
|
|
while(sound != store.end())
|
|
{
|
|
if(type == sound->mType && !sound->mCreature.empty() &&
|
|
Misc::StringUtils::ciEqual(ptrid.substr(0, sound->mCreature.size()),
|
|
sound->mCreature))
|
|
sounds.push_back(&*sound);
|
|
++sound;
|
|
}
|
|
if(!sounds.empty())
|
|
return sounds[(int)(rand()/(RAND_MAX+1.0)*sounds.size())]->mSound;
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
MWWorld::Ptr
|
|
Creature::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const
|
|
{
|
|
MWWorld::LiveCellRef<ESM::Creature> *ref =
|
|
ptr.get<ESM::Creature>();
|
|
|
|
return MWWorld::Ptr(&cell.get<ESM::Creature>().insert(*ref), &cell);
|
|
}
|
|
|
|
bool Creature::isBipedal(const MWWorld::Ptr &ptr) const
|
|
{
|
|
MWWorld::LiveCellRef<ESM::Creature> *ref =
|
|
ptr.get<ESM::Creature>();
|
|
|
|
return ref->mBase->mFlags & ESM::Creature::Bipedal;
|
|
}
|
|
|
|
bool Creature::canFly(const MWWorld::Ptr &ptr) const
|
|
{
|
|
MWWorld::LiveCellRef<ESM::Creature> *ref =
|
|
ptr.get<ESM::Creature>();
|
|
|
|
return ref->mBase->mFlags & ESM::Creature::Flies;
|
|
}
|
|
|
|
bool Creature::canSwim(const MWWorld::Ptr &ptr) const
|
|
{
|
|
MWWorld::LiveCellRef<ESM::Creature> *ref =
|
|
ptr.get<ESM::Creature>();
|
|
|
|
return ref->mBase->mFlags & ESM::Creature::Swims || ref->mBase->mFlags & ESM::Creature::Bipedal;
|
|
}
|
|
|
|
bool Creature::canWalk(const MWWorld::Ptr &ptr) const
|
|
{
|
|
MWWorld::LiveCellRef<ESM::Creature> *ref =
|
|
ptr.get<ESM::Creature>();
|
|
|
|
return ref->mBase->mFlags & ESM::Creature::Walks || ref->mBase->mFlags & ESM::Creature::Bipedal;
|
|
}
|
|
|
|
int Creature::getSndGenTypeFromName(const MWWorld::Ptr &ptr, const std::string &name)
|
|
{
|
|
if(name == "left")
|
|
{
|
|
MWBase::World *world = MWBase::Environment::get().getWorld();
|
|
Ogre::Vector3 pos(ptr.getRefData().getPosition().pos);
|
|
if(world->isUnderwater(ptr.getCell(), pos))
|
|
return 2;
|
|
if(world->isOnGround(ptr))
|
|
return 0;
|
|
return -1;
|
|
}
|
|
if(name == "right")
|
|
{
|
|
MWBase::World *world = MWBase::Environment::get().getWorld();
|
|
Ogre::Vector3 pos(ptr.getRefData().getPosition().pos);
|
|
if(world->isUnderwater(ptr.getCell(), pos))
|
|
return 3;
|
|
if(world->isOnGround(ptr))
|
|
return 1;
|
|
return -1;
|
|
}
|
|
if(name == "swimleft")
|
|
return 2;
|
|
if(name == "swimright")
|
|
return 3;
|
|
if(name == "moan")
|
|
return 4;
|
|
if(name == "roar")
|
|
return 5;
|
|
if(name == "scream")
|
|
return 6;
|
|
if(name == "land")
|
|
return 7;
|
|
|
|
throw std::runtime_error(std::string("Unexpected soundgen type: ")+name);
|
|
}
|
|
|
|
int 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::Ptr &ptr) const
|
|
{
|
|
MWWorld::LiveCellRef<ESM::Creature> *ref = ptr.get<ESM::Creature>();
|
|
|
|
if (ref->mBase->mFlags & ESM::Creature::Skeleton)
|
|
return 1;
|
|
if (ref->mBase->mFlags & ESM::Creature::Metal)
|
|
return 2;
|
|
return 0;
|
|
}
|
|
|
|
void Creature::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state)
|
|
const
|
|
{
|
|
const ESM::CreatureState& state2 = dynamic_cast<const ESM::CreatureState&> (state);
|
|
|
|
ensureCustomData(ptr);
|
|
|
|
// If we do the following instead we get a sizable speedup, but this causes compatibility issues
|
|
// with 0.30 savegames, where some state in CreatureStats was not saved yet,
|
|
// and therefore needs to be loaded from ESM records. TODO: re-enable this in a future release.
|
|
/*
|
|
if (!ptr.getRefData().getCustomData())
|
|
{
|
|
// Create a CustomData, but don't fill it from ESM records (not needed)
|
|
std::auto_ptr<CreatureCustomData> data (new CreatureCustomData);
|
|
|
|
MWWorld::LiveCellRef<ESM::Creature> *ref = ptr.get<ESM::Creature>();
|
|
|
|
if (ref->mBase->mFlags & ESM::Creature::Weapon)
|
|
data->mContainerStore = new MWWorld::InventoryStore();
|
|
else
|
|
data->mContainerStore = new MWWorld::ContainerStore();
|
|
|
|
ptr.getRefData().setCustomData (data.release());
|
|
}
|
|
*/
|
|
|
|
CreatureCustomData& customData = dynamic_cast<CreatureCustomData&> (*ptr.getRefData().getCustomData());
|
|
|
|
customData.mContainerStore->readState (state2.mInventory);
|
|
customData.mCreatureStats.readState (state2.mCreatureStats);
|
|
|
|
}
|
|
|
|
void Creature::writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state)
|
|
const
|
|
{
|
|
ESM::CreatureState& state2 = dynamic_cast<ESM::CreatureState&> (state);
|
|
|
|
ensureCustomData (ptr);
|
|
|
|
CreatureCustomData& customData = dynamic_cast<CreatureCustomData&> (*ptr.getRefData().getCustomData());
|
|
|
|
customData.mContainerStore->writeState (state2.mInventory);
|
|
customData.mCreatureStats.writeState (state2.mCreatureStats);
|
|
}
|
|
|
|
int Creature::getBaseGold(const MWWorld::Ptr& ptr) const
|
|
{
|
|
return ptr.get<ESM::Creature>()->mBase->mData.mGold;
|
|
}
|
|
|
|
void Creature::respawn(const MWWorld::Ptr &ptr) const
|
|
{
|
|
if (ptr.get<ESM::Creature>()->mBase->mFlags & ESM::Creature::Respawn)
|
|
{
|
|
// Note we do not respawn moved references in the cell they were moved to. Instead they are respawned in the original cell.
|
|
// This also means we cannot respawn dynamically placed references with no content file connection.
|
|
if (ptr.getCellRef().getRefNum().mContentFile != -1)
|
|
{
|
|
if (ptr.getRefData().getCount() == 0)
|
|
ptr.getRefData().setCount(1);
|
|
|
|
// Reset to original position
|
|
ptr.getRefData().setPosition(ptr.getCellRef().getPosition());
|
|
|
|
ptr.getRefData().setCustomData(NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Creature::restock(const MWWorld::Ptr& ptr) const
|
|
{
|
|
MWWorld::LiveCellRef<ESM::Creature> *ref = ptr.get<ESM::Creature>();
|
|
const ESM::InventoryList& list = ref->mBase->mInventory;
|
|
MWWorld::ContainerStore& store = getContainerStore(ptr);
|
|
store.restock(list, ptr, ptr.getCellRef().getRefId(), ptr.getCellRef().getFaction());
|
|
}
|
|
}
|