merge with master

This commit is contained in:
mrcheko 2014-01-27 22:38:01 +02:00
commit fe0268062d
82 changed files with 1381 additions and 488 deletions

View file

@ -74,7 +74,7 @@ add_openmw_dir (mwmechanics
mechanicsmanagerimp stat character creaturestats magiceffects movement actors objects
drawstate spells activespells npcstats aipackage aisequence alchemy aiwander aitravel aifollow
aiescort aiactivate aicombat repair enchanting pathfinding security spellsuccess spellcasting
disease pickpocket levelledlist
disease pickpocket levelledlist combat
)
add_openmw_dir (mwbase

View file

@ -509,6 +509,9 @@ void OMW::Engine::activate()
if (ptr.isEmpty())
return;
if (ptr.getClass().getName(ptr) == "") // objects without name presented to user can never be activated
return;
MWScript::InterpreterContext interpreterContext (&ptr.getRefData().getLocals(), ptr);
boost::shared_ptr<MWWorld::Action> action =

View file

@ -157,6 +157,8 @@ namespace MWBase
virtual void toggleAI() = 0;
virtual bool isAIActive() = 0;
virtual void getObjectsInRange (const Ogre::Vector3& position, float radius, std::vector<MWWorld::Ptr>& objects) = 0;
};
}

View file

@ -147,6 +147,8 @@ namespace MWBase
virtual void update(float duration) = 0;
virtual void setListenerPosDir(const Ogre::Vector3 &pos, const Ogre::Vector3 &dir, const Ogre::Vector3 &up) = 0;
virtual void updatePtr (const MWWorld::Ptr& old, const MWWorld::Ptr& updated) = 0;
};
}

View file

@ -225,7 +225,7 @@ namespace MWBase
/// Returns a pointer to the object the provided object would hit (if within the
/// specified distance), and the point where the hit occurs. This will attempt to
/// use the "Head" node as a basis.
/// use the "Head" node, or alternatively the "Bip01 Head" node as a basis.
virtual std::pair<MWWorld::Ptr,Ogre::Vector3> getHitContact(const MWWorld::Ptr &ptr, float distance) = 0;
virtual void adjustPosition (const MWWorld::Ptr& ptr) = 0;
@ -466,6 +466,9 @@ namespace MWBase
/// Spawn a blood effect for \a ptr at \a worldPosition
virtual void spawnBloodEffect (const MWWorld::Ptr& ptr, const Ogre::Vector3& worldPosition) = 0;
virtual void explodeSpell (const Ogre::Vector3& origin, const MWWorld::Ptr& object, const ESM::EffectList& effects,
const MWWorld::Ptr& caster, const std::string& id, const std::string& sourceName) = 0;
};
}

View file

@ -289,7 +289,7 @@ namespace MWClass
std::pair<int, std::string> Armor::canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const
{
MWWorld::InventoryStore& invStore = MWWorld::Class::get(npc).getInventoryStore(npc);
MWWorld::InventoryStore& invStore = npc.getClass().getInventoryStore(npc);
if (ptr.getCellRef().mCharge == 0)
return std::make_pair(0, "#{sInventoryMessage1}");
@ -300,20 +300,23 @@ namespace MWClass
if (slots_.first.empty())
return std::make_pair(0, "");
std::string npcRace = npc.get<ESM::NPC>()->mBase->mRace;
// Beast races cannot equip shoes / boots, or full helms (head part vs hair part)
const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(npcRace);
if(race->mData.mFlags & ESM::Race::Beast)
if (npc.getClass().isNpc())
{
std::vector<ESM::PartReference> parts = ptr.get<ESM::Armor>()->mBase->mParts.mParts;
std::string npcRace = npc.get<ESM::NPC>()->mBase->mRace;
for(std::vector<ESM::PartReference>::iterator itr = parts.begin(); itr != parts.end(); ++itr)
// Beast races cannot equip shoes / boots, or full helms (head part vs hair part)
const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(npcRace);
if(race->mData.mFlags & ESM::Race::Beast)
{
if((*itr).mPart == ESM::PRT_Head)
return std::make_pair(0, "#{sNotifyMessage13}");
if((*itr).mPart == ESM::PRT_LFoot || (*itr).mPart == ESM::PRT_RFoot)
return std::make_pair(0, "#{sNotifyMessage14}");
std::vector<ESM::PartReference> parts = ptr.get<ESM::Armor>()->mBase->mParts.mParts;
for(std::vector<ESM::PartReference>::iterator itr = parts.begin(); itr != parts.end(); ++itr)
{
if((*itr).mPart == ESM::PRT_Head)
return std::make_pair(0, "#{sNotifyMessage13}");
if((*itr).mPart == ESM::PRT_LFoot || (*itr).mPart == ESM::PRT_RFoot)
return std::make_pair(0, "#{sNotifyMessage14}");
}
}
}

View file

@ -238,20 +238,23 @@ namespace MWClass
if (slots_.first.empty())
return std::make_pair(0, "");
std::string npcRace = npc.get<ESM::NPC>()->mBase->mRace;
// Beast races cannot equip shoes / boots, or full helms (head part vs hair part)
const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(npcRace);
if(race->mData.mFlags & ESM::Race::Beast)
if (npc.getClass().isNpc())
{
std::vector<ESM::PartReference> parts = ptr.get<ESM::Clothing>()->mBase->mParts.mParts;
std::string npcRace = npc.get<ESM::NPC>()->mBase->mRace;
for(std::vector<ESM::PartReference>::iterator itr = parts.begin(); itr != parts.end(); ++itr)
// Beast races cannot equip shoes / boots, or full helms (head part vs hair part)
const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(npcRace);
if(race->mData.mFlags & ESM::Race::Beast)
{
if((*itr).mPart == ESM::PRT_Head)
return std::make_pair(0, "#{sNotifyMessage13}");
if((*itr).mPart == ESM::PRT_LFoot || (*itr).mPart == ESM::PRT_RFoot)
return std::make_pair(0, "#{sNotifyMessage15}");
std::vector<ESM::PartReference> parts = ptr.get<ESM::Clothing>()->mBase->mParts.mParts;
for(std::vector<ESM::PartReference>::iterator itr = parts.begin(); itr != parts.end(); ++itr)
{
if((*itr).mPart == ESM::PRT_Head)
return std::make_pair(0, "#{sNotifyMessage13}");
if((*itr).mPart == ESM::PRT_LFoot || (*itr).mPart == ESM::PRT_RFoot)
return std::make_pair(0, "#{sNotifyMessage15}");
}
}
}

View file

@ -6,6 +6,7 @@
#include "../mwmechanics/creaturestats.hpp"
#include "../mwmechanics/magiceffects.hpp"
#include "../mwmechanics/movement.hpp"
#include "../mwmechanics/spellcasting.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/mechanicsmanager.hpp"
@ -26,22 +27,30 @@
#include "../mwgui/tooltips.hpp"
#include "../mwworld/inventorystore.hpp"
#include "../mwmechanics/npcstats.hpp"
#include "../mwmechanics/combat.hpp"
namespace
{
struct CustomData : public MWWorld::CustomData
{
MWMechanics::CreatureStats mCreatureStats;
MWWorld::ContainerStore mContainerStore;
MWWorld::ContainerStore* mContainerStore; // may be InventoryStore for some creatures
MWMechanics::Movement mMovement;
virtual MWWorld::CustomData *clone() const;
CustomData() : mContainerStore(0) {}
virtual ~CustomData() { delete mContainerStore; }
};
MWWorld::CustomData *CustomData::clone() const
{
return new CustomData (*this);
CustomData* cloned = new CustomData (*this);
cloned->mContainerStore = mContainerStore->clone();
return cloned;
}
}
@ -106,15 +115,23 @@ namespace MWClass
data->mCreatureStats.getSpells().add (*iter);
// inventory
data->mContainerStore.fill(ref->mBase->mInventory, getId(ptr), "",
if (ref->mBase->mFlags & ESM::Creature::Weapon)
data->mContainerStore = new MWWorld::InventoryStore();
else
data->mContainerStore = new MWWorld::ContainerStore();
// store
ptr.getRefData().setCustomData (data.release());
getContainerStore(ptr).fill(ref->mBase->mInventory, getId(ptr), "",
MWBase::Environment::get().getWorld()->getStore());
// TODO: this is not quite correct, in vanilla the merchant's gold pool is not available in his inventory.
// (except for gold you gave him)
data->mContainerStore.add(MWWorld::ContainerStore::sGoldId, ref->mBase->mData.mGold, ptr);
getContainerStore(ptr).add(MWWorld::ContainerStore::sGoldId, ref->mBase->mData.mGold, ptr);
// store
ptr.getRefData().setCustomData (data.release());
if (ref->mBase->mFlags & ESM::Creature::Weapon)
getInventoryStore(ptr).autoEquip(ptr);
}
}
@ -133,8 +150,10 @@ namespace MWClass
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);
actors.insertCreature(ptr, ref->mBase->mFlags & ESM::Creature::Weapon);
}
void Creature::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const
@ -178,9 +197,40 @@ namespace MWClass
{
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?
std::pair<MWWorld::Ptr, Ogre::Vector3> result = MWBase::Environment::get().getWorld()->getHitContact(ptr, 100);
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
@ -191,7 +241,6 @@ namespace MWClass
Ogre::Vector3 hitPosition = result.second;
MWMechanics::CreatureStats &stats = getCreatureStats(ptr);
MWMechanics::CreatureStats &otherstats = victim.getClass().getCreatureStats(victim);
const MWMechanics::MagicEffects &mageffects = stats.getMagicEffects();
float hitchance = ref->mBase->mData.mCombat +
@ -226,10 +275,62 @@ namespace MWClass
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);
// TODO: do not do this if the attack is blocked
MWBase::Environment::get().getWorld()->spawnBloodEffect(victim, hitPosition);
if (!weapon.isEmpty())
{
const bool weaphashealth = get(weapon).hasItemHealth(weapon);
const unsigned char *attack = NULL;
if(type == MWMechanics::CreatureStats::AT_Chop)
attack = weapon.get<ESM::Weapon>()->mBase->mData.mChop;
else if(type == MWMechanics::CreatureStats::AT_Slash)
attack = weapon.get<ESM::Weapon>()->mBase->mData.mSlash;
else if(type == MWMechanics::CreatureStats::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.get<ESM::Weapon>()->mBase->mData.mHealth;
if(weapon.getCellRef().mCharge == -1)
weapon.getCellRef().mCharge = weapmaxhealth;
weaponDamage *= float(weapon.getCellRef().mCharge) / weapmaxhealth;
}
if (!MWBase::Environment::get().getWorld()->getGodModeState())
weapon.getCellRef().mCharge -= std::min(std::max(1,
(int)(damage * gmst.find("fWeaponDamageMult")->getFloat())), weapon.getCellRef().mCharge);
// Weapon broken? unequip it
if (weapon.getCellRef().mCharge == 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);
victim.getClass().onHit(victim, damage, true, MWWorld::Ptr(), ptr, true);
}
@ -258,31 +359,60 @@ namespace MWClass
ptr.getRefData().getLocals().setVarByInt(script, "onpchitme", 1);
}
// Check for knockdown
float agilityTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() * fKnockDownMult->getFloat();
float knockdownTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified()
* iKnockDownOddsMult->getInt() * 0.01 + 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);
if (damage > 0.0f && !object.isEmpty())
MWMechanics::resistNormalWeapon(ptr, attacker, object, damage);
}
else
getCreatureStats(ptr).setHitRecovery(true); // Is this supposed to always occur?
if(ishealth)
if (damage > 0.f)
{
if(damage > 0.0f)
// Check for knockdown
float agilityTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() * fKnockDownMult->getFloat();
float knockdownTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified()
* iKnockDownOddsMult->getInt() * 0.01 + 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);
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);
}
}
else
}
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))
{
MWMechanics::DynamicStat<float> fatigue(getCreatureStats(ptr).getFatigue());
fatigue.setCurrent(fatigue.getCurrent() - damage, true);
getCreatureStats(ptr).setFatigue(fatigue);
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;
}
}
@ -325,18 +455,33 @@ namespace MWClass
return boost::shared_ptr<MWWorld::Action>(new MWWorld::ActionTalk(ptr));
}
MWWorld::ContainerStore& Creature::getContainerStore (const MWWorld::Ptr& ptr)
const
MWWorld::ContainerStore& Creature::getContainerStore (const MWWorld::Ptr& ptr) const
{
ensureCustomData (ptr);
return dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData()).mContainerStore;
return *dynamic_cast<CustomData&> (*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>();
MWWorld::LiveCellRef<ESM::Creature> *ref = ptr.get<ESM::Creature>();
return ref->mBase->mScript;
}

View file

@ -56,6 +56,8 @@ namespace MWClass
virtual void hit(const MWWorld::Ptr& ptr, int type) const;
virtual void block(const MWWorld::Ptr &ptr) const;
virtual void onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, bool successful) const;
virtual void setActorHealth(const MWWorld::Ptr& ptr, float health, const MWWorld::Ptr& attacker) const;
@ -68,6 +70,11 @@ namespace MWClass
const MWWorld::Ptr& ptr) const;
///< Return container store
virtual MWWorld::InventoryStore& getInventoryStore (const MWWorld::Ptr& ptr) const;
///< Return inventory store
virtual bool hasInventoryStore (const MWWorld::Ptr &ptr) const;
virtual std::string getScript (const MWWorld::Ptr& ptr) const;
///< Return name of the script attached to ptr

View file

@ -3,6 +3,24 @@
#include <components/esm/loadlevlist.hpp>
#include "../mwmechanics/levelledlist.hpp"
#include "../mwworld/customdata.hpp"
namespace
{
struct CustomData : public MWWorld::CustomData
{
// TODO: save the creature we spawned here
virtual MWWorld::CustomData *clone() const;
};
MWWorld::CustomData *CustomData::clone() const
{
return new CustomData (*this);
}
}
namespace MWClass
{
std::string CreatureLevList::getName (const MWWorld::Ptr& ptr) const
@ -16,4 +34,33 @@ namespace MWClass
registerClass (typeid (ESM::CreatureLevList).name(), instance);
}
void CreatureLevList::insertObjectRendering(const MWWorld::Ptr &ptr, MWRender::RenderingInterface &renderingInterface) const
{
ensureCustomData(ptr);
}
void CreatureLevList::ensureCustomData(const MWWorld::Ptr &ptr) const
{
if (!ptr.getRefData().getCustomData())
{
std::auto_ptr<CustomData> data (new CustomData);
MWWorld::LiveCellRef<ESM::CreatureLevList> *ref =
ptr.get<ESM::CreatureLevList>();
std::string id = MWMechanics::getLevelledItem(ref->mBase, true);
if (!id.empty())
{
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
MWWorld::ManualRef ref(store, id);
ref.getPtr().getCellRef().mPos = ptr.getCellRef().mPos;
// TODO: hold on to this for respawn purposes later
MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(), *ptr.getCell() , ptr.getCellRef().mPos);
}
ptr.getRefData().setCustomData(data.release());
}
}
}

View file

@ -7,6 +7,8 @@ namespace MWClass
{
class CreatureLevList : public MWWorld::Class
{
void ensureCustomData (const MWWorld::Ptr& ptr) const;
public:
virtual std::string getName (const MWWorld::Ptr& ptr) const;
@ -14,6 +16,9 @@ namespace MWClass
/// can return an empty string.
static void registerSelf();
virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const;
///< Add reference into a cell for rendering
};
}

View file

@ -20,6 +20,7 @@
#include "../mwmechanics/movement.hpp"
#include "../mwmechanics/spellcasting.hpp"
#include "../mwmechanics/disease.hpp"
#include "../mwmechanics/combat.hpp"
#include "../mwworld/ptr.hpp"
#include "../mwworld/actiontalk.hpp"
@ -36,9 +37,6 @@
namespace
{
const Ogre::Radian kOgrePi (Ogre::Math::PI);
const Ogre::Radian kOgrePiOverTwo (Ogre::Math::PI / Ogre::Real(2.0));
struct CustomData : public MWWorld::CustomData
{
MWMechanics::NpcStats mNpcStats;
@ -144,7 +142,7 @@ namespace
*
* and by adding class, race, specialization bonus.
*/
void autoCalculateSkills(const ESM::NPC* npc, MWMechanics::NpcStats& npcStats)
void autoCalculateSkills(const ESM::NPC* npc, MWMechanics::NpcStats& npcStats, const MWWorld::Ptr& ptr)
{
const ESM::Class *class_ =
MWBase::Environment::get().getWorld()->getStore().get<ESM::Class>().find(npc->mClass);
@ -193,6 +191,18 @@ namespace
majorMultiplier = 1.0f;
break;
}
if (class_->mData.mSkills[k][1] == skillIndex)
{
// Major skill -> add starting spells for this skill if existing
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
MWWorld::Store<ESM::Spell>::iterator it = store.get<ESM::Spell>().begin();
for (; it != store.get<ESM::Spell>().end(); ++it)
{
if (it->mData.mFlags & ESM::Spell::F_Autocalc
&& MWMechanics::spellSchoolToSkill(MWMechanics::getSpellSchool(&*it, ptr)) == skillIndex)
npcStats.getSpells().add(it->mId);
}
}
}
// is this skill in the same Specialization as the class?
@ -243,6 +253,8 @@ namespace MWClass
fKnockDownMult = gmst.find("fKnockDownMult");
iKnockDownOddsMult = gmst.find("iKnockDownOddsMult");
iKnockDownOddsBase = gmst.find("iKnockDownOddsBase");
fDamageStrengthBase = gmst.find("fDamageStrengthBase");
fDamageStrengthMult = gmst.find("fDamageStrengthMult");
inited = true;
}
@ -305,7 +317,15 @@ namespace MWClass
data->mNpcStats.setReputation(ref->mBase->mNpdt12.mReputation);
autoCalculateAttributes(ref->mBase, data->mNpcStats);
autoCalculateSkills(ref->mBase, data->mNpcStats);
autoCalculateSkills(ref->mBase, data->mNpcStats, ptr);
}
// race powers
const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(ref->mBase->mRace);
for (std::vector<std::string>::const_iterator iter (race->mPowers.mList.begin());
iter!=race->mPowers.mList.end(); ++iter)
{
data->mNpcStats.getSpells().add (*iter);
}
if (data->mNpcStats.getFactionRanks().size())
@ -450,10 +470,11 @@ namespace MWClass
fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss);
getCreatureStats(ptr).setFatigue(fatigue);
float dist = 100.0f * (!weapon.isEmpty() ?
const float fCombatDistance = gmst.find("fCombatDistance")->getFloat();
float dist = fCombatDistance * (!weapon.isEmpty() ?
weapon.get<ESM::Weapon>()->mBase->mData.mReach :
gmst.find("fHandToHandReach")->getFloat());
// TODO: Use second to work out the hit angle
std::pair<MWWorld::Ptr, Ogre::Vector3> result = world->getHitContact(ptr, dist);
MWWorld::Ptr victim = result.first;
@ -506,7 +527,8 @@ namespace MWClass
if(attack)
{
damage = attack[0] + ((attack[1]-attack[0])*stats.getAttackStrength());
damage *= 0.5f + (stats.getAttribute(ESM::Attribute::Luck).getModified() / 100.0f);
damage *= fDamageStrengthBase->getFloat() +
(stats.getAttribute(ESM::Attribute::Strength).getModified() * fDamageStrengthMult->getFloat() * 0.1);
if(weaphashealth)
{
int weapmaxhealth = weapon.get<ESM::Weapon>()->mBase->mData.mHealth;
@ -579,33 +601,19 @@ namespace MWClass
enchantmentName);
if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes)
{
// Check if we have enough charges
const float enchantCost = enchantment->mData.mCost;
int eSkill = stats.getSkill(ESM::Skill::Enchant).getModified();
const int castCost = std::max(1.f, enchantCost - (enchantCost / 100) * (eSkill - 10));
MWMechanics::CastSpell cast(ptr, victim);
cast.mHitPosition = hitPosition;
bool success = cast.cast(weapon);
if (weapon.getCellRef().mEnchantmentCharge == -1)
weapon.getCellRef().mEnchantmentCharge = enchantment->mData.mCharge;
if (weapon.getCellRef().mEnchantmentCharge < castCost)
{
if (ptr.getRefData().getHandle() == "player")
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientCharge}");
}
else
{
weapon.getCellRef().mEnchantmentCharge -= castCost;
MWMechanics::CastSpell cast(ptr, victim);
cast.cast(weapon);
if (ptr.getRefData().getHandle() == "player")
skillUsageSucceeded (ptr, ESM::Skill::Enchant, 3);
}
if (ptr.getRefData().getHandle() == "player" && success)
skillUsageSucceeded (ptr, ESM::Skill::Enchant, 3);
}
}
// TODO: do not do this if the attack is blocked
if (healthdmg)
if (!weapon.isEmpty() && MWMechanics::blockMeleeAttack(ptr, victim, weapon, damage))
damage = 0;
if (healthdmg && damage > 0)
MWBase::Environment::get().getWorld()->spawnBloodEffect(victim, hitPosition);
othercls.onHit(victim, damage, healthdmg, weapon, ptr, true);
@ -644,6 +652,9 @@ namespace MWClass
if (!attacker.isEmpty())
MWMechanics::diseaseContact(ptr, attacker);
if (damage > 0.0f && !object.isEmpty())
MWMechanics::resistNormalWeapon(ptr, attacker, object, damage);
if(damage > 0.0f)
{
// 'ptr' is losing health. Play a 'hit' voiced dialog entry if not already saying
@ -746,6 +757,30 @@ namespace MWClass
}
}
void Npc::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 Npc::setActorHealth(const MWWorld::Ptr& ptr, float health, const MWWorld::Ptr& attacker) const
{
MWMechanics::CreatureStats &crstats = getCreatureStats(ptr);
@ -1252,4 +1287,7 @@ namespace MWClass
const ESM::GameSetting *Npc::fKnockDownMult;
const ESM::GameSetting *Npc::iKnockDownOddsMult;
const ESM::GameSetting *Npc::iKnockDownOddsBase;
const ESM::GameSetting *Npc::fDamageStrengthBase;
const ESM::GameSetting *Npc::fDamageStrengthMult;
}

View file

@ -36,6 +36,8 @@ namespace MWClass
static const ESM::GameSetting *fKnockDownMult;
static const ESM::GameSetting *iKnockDownOddsMult;
static const ESM::GameSetting *iKnockDownOddsBase;
static const ESM::GameSetting *fDamageStrengthBase;
static const ESM::GameSetting *fDamageStrengthMult;
public:
@ -71,10 +73,14 @@ namespace MWClass
virtual MWWorld::InventoryStore& getInventoryStore (const MWWorld::Ptr& ptr) const;
///< Return inventory store
virtual bool hasInventoryStore(const MWWorld::Ptr &ptr) const { return true; }
virtual void hit(const MWWorld::Ptr& ptr, int type) const;
virtual void onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, bool successful) const;
virtual void block(const MWWorld::Ptr &ptr) const;
virtual void setActorHealth(const MWWorld::Ptr& ptr, float health, const MWWorld::Ptr& attacker) const;
virtual boost::shared_ptr<MWWorld::Action> activate (const MWWorld::Ptr& ptr,

View file

@ -420,7 +420,7 @@ namespace MWDialogue
MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Dialogue);
// Apply disposition change to NPC's base disposition
if (mActor.getTypeName() == typeid(ESM::NPC).name())
if (mActor.getClass().isNpc())
{
MWMechanics::NpcStats& npcStats = MWWorld::Class::get(mActor).getNpcStats(mActor);
npcStats.setBaseDisposition(npcStats.getBaseDisposition() + mPermanentDispositionChange);
@ -586,7 +586,8 @@ namespace MWDialogue
MWBase::WindowManager *winMgr = MWBase::Environment::get().getWindowManager();
if(winMgr->getSubtitlesEnabled())
winMgr->messageBox(info->mResponse);
sndMgr->say(actor, info->mSound);
if (!info->mSound.empty())
sndMgr->say(actor, info->mSound);
}
}

View file

@ -216,7 +216,7 @@ bool MWDialogue::Filter::testSelectStructNumeric (const SelectWrapper& select) c
float ratio = MWWorld::Class::get (player).getCreatureStats (player).getHealth().getCurrent() /
MWWorld::Class::get (player).getCreatureStats (player).getHealth().getModified();
return select.selectCompare (ratio);
return select.selectCompare (static_cast<int>(ratio*100));
}
case SelectWrapper::Function_PcDynamicStat:
@ -536,7 +536,7 @@ bool MWDialogue::Filter::getSelectStructBoolean (const SelectWrapper& select) co
case SelectWrapper::Function_CreatureTargetted:
return MWWorld::Class::get (mActor).getCreatureStats (mActor).getCreatureTargetted();
return mActor.getClass().getCreatureStats (mActor).getCreatureTargetted();
case SelectWrapper::Function_Werewolf:

View file

@ -39,21 +39,24 @@ namespace MWDialogue
const ESM::Dialogue *dialogue =
MWBase::Environment::get().getWorld()->getStore().get<ESM::Dialogue>().find (mTopic);
bool found=false;
for (std::vector<ESM::DialInfo>::const_iterator iter (dialogue->mInfo.begin());
iter!=dialogue->mInfo.end(); ++iter)
if (iter->mData.mDisposition==index && iter->mQuestStatus!=ESM::DialInfo::QS_Name)
{
mIndex = index;
if (iter->mQuestStatus==ESM::DialInfo::QS_Finished)
mFinished = true;
else if (iter->mQuestStatus==ESM::DialInfo::QS_Restart)
mFinished = false;
return;
found = true;
// Don't return here. Quest status may actually be in a different info record, since we don't merge these (yet?)
}
throw std::runtime_error ("unknown journal index for topic " + mTopic);
if (found)
mIndex = index;
else
throw std::runtime_error ("unknown journal index for topic " + mTopic);
}
bool Quest::isFinished() const

View file

@ -12,7 +12,7 @@ namespace MWGui
void CompanionItemModel::copyItem (const ItemStack& item, size_t count)
{
if (mActor.getTypeName() == typeid(ESM::NPC).name())
if (mActor.getClass().isNpc())
{
MWMechanics::NpcStats& stats = MWWorld::Class::get(mActor).getNpcStats(mActor);
stats.modifyProfit(MWWorld::Class::get(item.mBase).getValue(item.mBase) * count);
@ -23,7 +23,7 @@ namespace MWGui
void CompanionItemModel::removeItem (const ItemStack& item, size_t count)
{
if (mActor.getTypeName() == typeid(ESM::NPC).name())
if (mActor.getClass().isNpc())
{
MWMechanics::NpcStats& stats = MWWorld::Class::get(mActor).getNpcStats(mActor);
stats.modifyProfit(-MWWorld::Class::get(item.mBase).getValue(item.mBase) * count);

View file

@ -5,6 +5,8 @@
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "../mwbase/soundmanager.hpp"
#include "../mwbase/dialoguemanager.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/containerstore.hpp"
@ -296,6 +298,28 @@ namespace MWGui
return;
}
// check if the player is attempting to use a soulstone or item that was stolen from this actor
if (mPtr != player)
{
for (int i=0; i<2; ++i)
{
MWWorld::Ptr item = (i == 0) ? mEnchanting.getOldItem() : mEnchanting.getGem();
if (Misc::StringUtils::ciEqual(item.getCellRef().mOwner, mPtr.getCellRef().mRefID))
{
std::string msg = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("sNotifyMessage49")->getString();
if (msg.find("%s") != std::string::npos)
msg.replace(msg.find("%s"), 2, item.getClass().getName(item));
MWBase::Environment::get().getWindowManager()->messageBox(msg);
MWBase::Environment::get().getDialogueManager()->say(mPtr, "Thief");
MWBase::Environment::get().getMechanicsManager()->reportCrime(player, mPtr, MWBase::MechanicsManager::OT_Theft,
item.getClass().getValue(item));
MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Enchanting);
MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Dialogue);
return;
}
}
}
int result = mEnchanting.create();
if(result==1)

View file

@ -74,9 +74,9 @@ void InventoryItemModel::update()
ItemStack newItem (item, this, item.getRefData().getCount());
if (mActor.getTypeName() == typeid(ESM::NPC).name())
if (mActor.getClass().hasInventoryStore(mActor))
{
MWWorld::InventoryStore& store = MWWorld::Class::get(mActor).getInventoryStore(mActor);
MWWorld::InventoryStore& store = mActor.getClass().getInventoryStore(mActor);
for (int slot=0; slot<MWWorld::InventoryStore::Slots; ++slot)
{
MWWorld::ContainerStoreIterator equipped = store.getSlot(slot);

View file

@ -206,10 +206,12 @@ struct JournalViewModelImpl : JournalViewModel
if (active_only && i->second.isFinished ())
continue;
/// \todo quest.getName() is broken? returns empty string
//const MWDialogue::Quest& quest = i->second;
visitor (reinterpret_cast <QuestId> (&i->second), toUtf8Span (i->first));
const MWDialogue::Quest& quest = i->second;
// Unfortunately Morrowind.esm has no quest names, since the quest book was added with tribunal.
if (quest.getName().empty())
visitor (reinterpret_cast <QuestId> (&i->second), toUtf8Span (i->first));
else
visitor (reinterpret_cast <QuestId> (&i->second), toUtf8Span (quest.getName()));
}
}

View file

@ -287,24 +287,6 @@ namespace MWGui
else {
mainWidgetSize.width = textSize.width + 3*textPadding;
}
mainWidgetSize.height = textSize.height + 2*textPadding + textButtonPadding + buttonHeight * buttons.size() + buttonMainPadding;
mMainWidget->setSize(mainWidgetSize);
MyGUI::IntCoord absCoord;
absCoord.left = (gameWindowSize.width - mainWidgetSize.width)/2;
absCoord.top = (gameWindowSize.height - mainWidgetSize.height)/2;
mMainWidget->setCoord(absCoord);
mMainWidget->setSize(mainWidgetSize);
MyGUI::IntCoord messageWidgetCoord;
messageWidgetCoord.left = (mainWidgetSize.width - textSize.width)/2;
messageWidgetCoord.top = textPadding;
mMessageWidget->setCoord(messageWidgetCoord);
mMessageWidget->setSize(textSize);
MyGUI::IntCoord buttonCord;
MyGUI::IntSize buttonSize(0, buttonHeight);
@ -326,6 +308,21 @@ namespace MWGui
top += buttonSize.height + 2*buttonTopPadding;
}
mainWidgetSize.height = top + buttonMainPadding;
mMainWidget->setSize(mainWidgetSize);
MyGUI::IntPoint absPos;
absPos.left = (gameWindowSize.width - mainWidgetSize.width)/2;
absPos.top = (gameWindowSize.height - mainWidgetSize.height)/2;
mMainWidget->setPosition(absPos);
MyGUI::IntCoord messageWidgetCoord;
messageWidgetCoord.left = (mainWidgetSize.width - textSize.width)/2;
messageWidgetCoord.top = textPadding;
messageWidgetCoord.width = textSize.width;
messageWidgetCoord.height = textSize.height;
mMessageWidget->setCoord(messageWidgetCoord);
}
// Set key focus to "Ok" button

View file

@ -3,8 +3,15 @@
#include <boost/lexical_cast.hpp>
#include "../mwworld/inventorystore.hpp"
#include "../mwworld/actionequip.hpp"
#include "../mwworld/class.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "../mwmechanics/spellcasting.hpp"
#include "../mwmechanics/spells.hpp"
#include "../mwmechanics/creaturestats.hpp"
#include "../mwgui/inventorywindow.hpp"
#include "../mwgui/bookwindow.hpp"
#include "../mwgui/scrollwindow.hpp"
@ -17,8 +24,8 @@ namespace
{
bool sortItems(const MWWorld::Ptr& left, const MWWorld::Ptr& right)
{
int cmp = MWWorld::Class::get(left).getName(left).compare(
MWWorld::Class::get(right).getName(right));
int cmp = left.getClass().getName(left).compare(
right.getClass().getName(right));
return cmp < 0;
}
@ -334,10 +341,7 @@ namespace MWGui
// equip, if it can be equipped
if (!MWWorld::Class::get(item).getEquipmentSlots(item).first.empty())
{
// Note: can't use Class::use here because enchanted scrolls for example would then open the scroll window instead of equipping
MWWorld::ActionEquip action(item);
action.execute (MWBase::Environment::get().getWorld ()->getPlayerPtr());
MWBase::Environment::get().getWindowManager()->getInventoryWindow()->useItem(item);
}
store.setSelectedEnchantItem(it);

View file

@ -3,13 +3,17 @@
#include <boost/lexical_cast.hpp>
#include "../mwbase/windowmanager.hpp"
#include "../mwbase/soundmanager.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "../mwworld/containerstore.hpp"
#include "../mwworld/class.hpp"
#include "../mwmechanics/spellcasting.hpp"
#include "../mwmechanics/spells.hpp"
#include "../mwmechanics/creaturestats.hpp"
#include "tooltips.hpp"
#include "class.hpp"

View file

@ -4,11 +4,15 @@
#include <boost/format.hpp>
#include "../mwbase/windowmanager.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "../mwworld/inventorystore.hpp"
#include "../mwworld/actionequip.hpp"
#include "../mwworld/class.hpp"
#include "../mwmechanics/spellcasting.hpp"
#include "../mwmechanics/spells.hpp"
#include "../mwmechanics/creaturestats.hpp"
#include "spellicons.hpp"
#include "inventorywindow.hpp"
@ -231,8 +235,7 @@ namespace MWGui
MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top);
float enchantCost = enchant->mData.mCost;
MWMechanics::NpcStats &stats = player.getClass().getNpcStats(player);
int eSkill = stats.getSkill(ESM::Skill::Enchant).getModified();
int eSkill = player.getClass().getSkill(player, ESM::Skill::Enchant);
int castCost = std::max(1.f, enchantCost - (enchantCost / 100) * (eSkill - 10));
std::string cost = boost::lexical_cast<std::string>(castCost);
@ -316,13 +319,7 @@ namespace MWGui
if (_sender->getUserString("Equipped") == "false"
&& !MWWorld::Class::get(item).getEquipmentSlots(item).first.empty())
{
// Note: can't use Class::use here because enchanted scrolls for example would then open the scroll window instead of equipping
MWWorld::ActionEquip action(item);
action.execute (MWBase::Environment::get().getWorld ()->getPlayerPtr());
// since we changed equipping status, update the inventory window
MWBase::Environment::get().getWindowManager()->getInventoryWindow()->updateItemView();
MWBase::Environment::get().getWindowManager()->getInventoryWindow()->useItem(item);
}
store.setSelectedEnchantItem(it);

View file

@ -211,6 +211,7 @@ namespace MWGui
params.mMagnMin = it->mMagnMin;
params.mMagnMax = it->mMagnMax;
params.mRange = it->mRange;
params.mArea = it->mArea;
params.mIsConstant = (spell->mData.mType == ESM::Spell::ST_Ability);
params.mNoTarget = false;
effects.push_back(params);

View file

@ -162,7 +162,7 @@ namespace MWGui
}
// don't show equipped items
if(mMerchant.getTypeName() == typeid(ESM::NPC).name())
if(mMerchant.getClass().hasInventoryStore(mMerchant))
{
bool isEquipped = false;
MWWorld::InventoryStore& store = MWWorld::Class::get(mMerchant).getInventoryStore(mMerchant);

View file

@ -14,7 +14,6 @@
#include "../mwworld/containerstore.hpp"
#include "../mwmechanics/creaturestats.hpp"
#include "../mwmechanics/npcstats.hpp"
#include "inventorywindow.hpp"
#include "itemview.hpp"
@ -272,6 +271,25 @@ namespace MWGui
return;
}
// check if the player is attempting to sell back an item stolen from this actor
for (std::vector<ItemStack>::iterator it = merchantBought.begin(); it != merchantBought.end(); ++it)
{
if (Misc::StringUtils::ciEqual(it->mBase.getCellRef().mOwner, mPtr.getCellRef().mRefID))
{
std::string msg = gmst.find("sNotifyMessage49")->getString();
if (msg.find("%s") != std::string::npos)
msg.replace(msg.find("%s"), 2, it->mBase.getClass().getName(it->mBase));
MWBase::Environment::get().getWindowManager()->messageBox(msg);
MWBase::Environment::get().getDialogueManager()->say(mPtr, "Thief");
MWBase::Environment::get().getMechanicsManager()->reportCrime(player, mPtr, MWBase::MechanicsManager::OT_Theft,
it->mBase.getClass().getValue(it->mBase)
* it->mCount);
onCancelButtonClicked(mCancelButton);
MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Dialogue);
return;
}
}
if(mCurrentBalance > mCurrentMerchantOffer)
{
//if npc is a creature: reject (no haggle)
@ -293,8 +311,8 @@ namespace MWGui
float clampedDisposition = std::max<int>(0,std::min<int>(int(MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mPtr)
+ MWBase::Environment::get().getDialogueManager()->getTemporaryDispositionChange()),100));
const MWMechanics::NpcStats &sellerStats = mPtr.getClass().getNpcStats(mPtr);
const MWMechanics::NpcStats &playerStats = player.getClass().getNpcStats(player);
const MWMechanics::CreatureStats &sellerStats = mPtr.getClass().getCreatureStats(mPtr);
const MWMechanics::CreatureStats &playerStats = player.getClass().getCreatureStats(player);
float a1 = std::min(player.getClass().getSkill(player, ESM::Skill::Mercantile), 100);
float b1 = std::min(0.1f * playerStats.getAttribute(ESM::Attribute::Luck).getModified(), 10.f);

View file

@ -48,8 +48,7 @@ void adjustBoundItem (const std::string& item, bool bound, const MWWorld::Ptr& a
bool disintegrateSlot (MWWorld::Ptr ptr, int slot, float disintegrate)
{
// TODO: remove this check once creatures support inventory store
if (ptr.getTypeName() == typeid(ESM::NPC).name())
if (ptr.getClass().hasInventoryStore(ptr))
{
MWWorld::InventoryStore& inv = ptr.getClass().getInventoryStore(ptr);
MWWorld::ContainerStoreIterator item =
@ -132,6 +131,8 @@ namespace MWMechanics
static const float fSoulgemMult = world->getStore().get<ESM::GameSetting>().find("fSoulgemMult")->getFloat();
float creatureSoulValue = mCreature.get<ESM::Creature>()->mBase->mData.mSoul;
if (creatureSoulValue == 0)
return;
// Use the smallest soulgem that is large enough to hold the soul
MWWorld::ContainerStore& container = caster.getClass().getContainerStore(caster);
@ -918,4 +919,13 @@ namespace MWMechanics
return iter->second->isAnimPlaying(groupName);
return false;
}
void Actors::getObjectsInRange(const Ogre::Vector3& position, float radius, std::vector<MWWorld::Ptr>& out)
{
for (PtrControllerMap::iterator iter = mActors.begin(); iter != mActors.end(); ++iter)
{
if (Ogre::Vector3(iter->first.getRefData().getPosition().pos).squaredDistance(position) <= radius*radius)
out.push_back(iter->first);
}
}
}

View file

@ -94,6 +94,8 @@ namespace MWMechanics
void skipAnimation(const MWWorld::Ptr& ptr);
bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName);
void getObjectsInRange(const Ogre::Vector3& position, float radius, std::vector<MWWorld::Ptr>& out);
private:
PtrControllerMap mActors;

View file

@ -50,9 +50,11 @@ namespace MWMechanics
bool AiCombat::execute (const MWWorld::Ptr& actor,float duration)
{
//General description
if(!actor.getClass().getCreatureStats(actor).isHostile())
if(!actor.getClass().getCreatureStats(actor).isHostile()
|| actor.getClass().getCreatureStats(actor).getHealth().getCurrent() <= 0)
return true;
if(actor.getClass().getCreatureStats(actor).getHealth().getCurrent() <= 0)
if(mTarget.getClass().getCreatureStats(mTarget).isDead())
return true;
//Update every frame
@ -125,9 +127,9 @@ namespace MWMechanics
actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true);
if(actor.getTypeName() == typeid(ESM::NPC).name())
if (actor.getClass().hasInventoryStore(actor))
{
MWMechanics::DrawState_ state = actor.getClass().getNpcStats(actor).getDrawState();
MWMechanics::DrawState_ state = actor.getClass().getCreatureStats(actor).getDrawState();
if (state == MWMechanics::DrawState_Spell || state == MWMechanics::DrawState_Nothing)
actor.getClass().getNpcStats(actor).setDrawState(MWMechanics::DrawState_Weapon);
@ -160,6 +162,7 @@ namespace MWMechanics
float zAngle;
float rangeMelee;
float rangeCloseUp;
bool distantCombat = false;
@ -367,12 +370,14 @@ namespace MWMechanics
return mTarget.getRefData().getHandle();
}
AiCombat *MWMechanics::AiCombat::clone() const
{
return new AiCombat(*this);
}
}
namespace
{

View file

@ -7,8 +7,8 @@
#include "../mwbase/environment.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/dialoguemanager.hpp"
#include "../mwmechanics/npcstats.hpp"
#include "creaturestats.hpp"
#include <OgreVector3.h>
namespace
@ -32,6 +32,7 @@ namespace MWMechanics
, mX(0)
, mY(0)
, mZ(0)
, mSaidGreeting(false)
{
for(unsigned short counter = 0; counter < mIdle.size(); counter++)
{
@ -66,8 +67,7 @@ namespace MWMechanics
bool AiWander::execute (const MWWorld::Ptr& actor,float duration)
{
if (actor.getClass().isNpc())
actor.getClass().getNpcStats(actor).setDrawState(DrawState_Nothing);
actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing);
MWBase::World *world = MWBase::Environment::get().getWorld();
if(mDuration)
{
@ -191,14 +191,49 @@ namespace MWMechanics
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
float chance = store.get<ESM::GameSetting>().find("fVoiceIdleOdds")->getFloat();
int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
// TODO: do not show subtitle messagebox if player is too far away? or do not say at all?
if (roll < chance)
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
// Don't bother if the player is out of hearing range
if (roll < chance && Ogre::Vector3(player.getRefData().getPosition().pos).distance(Ogre::Vector3(actor.getRefData().getPosition().pos)) < 1500)
MWBase::Environment::get().getDialogueManager()->say(actor, "idle");
}
}
if(mIdleNow)
{
// Play a random voice greeting if the player gets too close
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
float hello = actor.getClass().getCreatureStats(actor).getAiSetting(CreatureStats::AI_Hello).getModified();
float helloDistance = hello;
int iGreetDistanceMultiplier = store.get<ESM::GameSetting>().find("iGreetDistanceMultiplier")->getInt();
helloDistance *= iGreetDistanceMultiplier;
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
float playerDist = Ogre::Vector3(player.getRefData().getPosition().pos).distance(
Ogre::Vector3(actor.getRefData().getPosition().pos));
if (!mSaidGreeting)
{
// TODO: check if actor is aware / has line of sight
if (playerDist <= helloDistance
// Only play a greeting if the player is not moving
&& Ogre::Vector3(player.getClass().getMovementSettings(player).mPosition).squaredLength() == 0)
{
mSaidGreeting = true;
MWBase::Environment::get().getDialogueManager()->say(actor, "hello");
// TODO: turn to face player and interrupt the idle animation?
}
}
else
{
float fGreetDistanceReset = store.get<ESM::GameSetting>().find("fGreetDistanceReset")->getFloat();
if (playerDist >= fGreetDistanceReset * iGreetDistanceMultiplier)
mSaidGreeting = false;
}
// Check if idle animation finished
if(!checkIdle(actor, mPlayedIdle))
{
mPlayedIdle = 0;

View file

@ -32,6 +32,8 @@ namespace MWMechanics
std::vector<int> mIdle;
bool mRepeat;
bool mSaidGreeting;
float mX;
float mY;
float mZ;

View file

@ -62,26 +62,6 @@ struct StateInfo {
const char groupname[32];
};
static const std::string sDeathList[] = {
"death1" ,
"death2" ,
"death3" ,
"death4" ,
"death5" ,
"swimdeath",
};
static const int sDeathListSize = sizeof(sDeathList)/sizeof(sDeathList[0]);
static const std::string sHitList[] = {
"hit1" ,
"hit2" ,
"hit3" ,
"hit4" ,
"hit5" ,
"knockdown" ,
};
static const int sHitListSize = sizeof(sHitList)/sizeof(sHitList[0]);
static const StateInfo sMovementList[] = {
{ CharState_WalkForward, "walkforward" },
{ CharState_WalkBack, "walkback" },
@ -154,6 +134,17 @@ public:
{ return weap.type == type; }
};
std::string CharacterController::chooseRandomGroup (const std::string& prefix, int* num)
{
int numAnims=0;
while (mAnimation->hasAnimation(prefix + Ogre::StringConverter::toString(numAnims+1)))
++numAnims;
int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * numAnims + 1; // [1, numAnims]
if (num)
*num = roll;
return prefix + Ogre::StringConverter::toString(roll);
}
void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterState movement, bool force)
{
@ -162,27 +153,27 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
{
bool recovery = mPtr.getClass().getCreatureStats(mPtr).getHitRecovery();
bool knockdown = mPtr.getClass().getCreatureStats(mPtr).getKnockedDown();
bool block = mPtr.getClass().getCreatureStats(mPtr).getBlock();
if(mHitState == CharState_None)
{
if(knockdown)
{
mHitState = CharState_KnockDown;
mCurrentHit = sHitList[sHitListSize-1];
mCurrentHit = "knockdown";
mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::Group_All, true, 1, "start", "stop", 0.0f, 0);
}
else if (recovery)
{
mHitState = CharState_Hit;
int iHit = rand() % (sHitListSize-1);
mCurrentHit = sHitList[iHit];
if(mPtr.getRefData().getHandle()=="player" && !mAnimation->hasAnimation(mCurrentHit))
{
//only 3 different hit animations if player is in 1st person
int iHit = rand() % (sHitListSize-3);
mCurrentHit = sHitList[iHit];
}
mCurrentHit = chooseRandomGroup("hit");
mAnimation->play(mCurrentHit, Priority_Hit, MWRender::Animation::Group_All, true, 1, "start", "stop", 0.0f, 0);
}
else if (block)
{
mHitState = CharState_Block;
mCurrentHit = "shield";
mAnimation->play(mCurrentHit, Priority_Hit, MWRender::Animation::Group_All, true, 1, "block start", "block stop", 0.0f, 0);
}
}
else if(!mAnimation->isPlaying(mCurrentHit))
{
@ -191,6 +182,8 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(false);
if (recovery)
mPtr.getClass().getCreatureStats(mPtr).setHitRecovery(false);
if (block)
mPtr.getClass().getCreatureStats(mPtr).setBlock(false);
mHitState = CharState_None;
}
}
@ -250,14 +243,16 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
mAnimation->disable(mCurrentJump);
mCurrentJump = jump;
mAnimation->play(mCurrentJump, Priority_Jump, jumpgroup, false,
if (mAnimation->hasAnimation("jump"))
mAnimation->play(mCurrentJump, Priority_Jump, jumpgroup, false,
1.0f, ((mode!=2)?"start":"loop start"), "stop", 0.0f, ~0ul);
}
else
{
mAnimation->disable(mCurrentJump);
mCurrentJump.clear();
mAnimation->play(jump, Priority_Jump, jumpgroup, true,
if (mAnimation->hasAnimation("jump"))
mAnimation->play(jump, Priority_Jump, jumpgroup, true,
1.0f, "loop stop", "stop", 0.0f, 0);
}
}
@ -323,7 +318,7 @@ void getWeaponGroup(WeaponType weaptype, std::string &group)
}
MWWorld::ContainerStoreIterator getActiveWeapon(NpcStats &stats, MWWorld::InventoryStore &inv, WeaponType *weaptype)
MWWorld::ContainerStoreIterator getActiveWeapon(CreatureStats &stats, MWWorld::InventoryStore &inv, WeaponType *weaptype)
{
if(stats.getDrawState() == DrawState_Spell)
{
@ -385,28 +380,20 @@ MWWorld::ContainerStoreIterator getActiveWeapon(NpcStats &stats, MWWorld::Invent
void CharacterController::playRandomDeath(float startpoint)
{
if(MWWorld::Class::get(mPtr).isNpc())
if(MWBase::Environment::get().getWorld()->isSwimming(mPtr) && mAnimation->hasAnimation("swimdeath"))
{
if(MWBase::Environment::get().getWorld()->isSwimming(mPtr))
{
mDeathState = CharState_SwimDeath;
mCurrentDeath = sDeathList[sDeathListSize-1]; //last in the list is 'swimdeath'
}
else
{
int num = rand() % (sDeathListSize-1);
mDeathState = static_cast<CharacterState>(CharState_Death1 + num);
mCurrentDeath = sDeathList[num];
}
mDeathState = CharState_SwimDeath;
mCurrentDeath = "swimdeath";
}
else
{
mDeathState = CharState_Death1;
mCurrentDeath = "death1";
int selected=0;
mCurrentDeath = chooseRandomGroup("death", &selected);
mDeathState = static_cast<CharacterState>(CharState_Death1 + (selected-1));
}
mAnimation->play(mCurrentDeath, Priority_Death, MWRender::Animation::Group_All,
false, 1.0f, "start", "stop", 0.0f, 0);
false, 1.0f, "start", "stop", startpoint, 0);
}
CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim)
@ -434,15 +421,16 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim
* handle knockout and death which moves the character down. */
mAnimation->setAccumulation(Ogre::Vector3(1.0f, 1.0f, 0.0f));
if(mPtr.getTypeName() == typeid(ESM::NPC).name())
if (cls.hasInventoryStore(mPtr))
{
getActiveWeapon(cls.getNpcStats(mPtr), cls.getInventoryStore(mPtr), &mWeaponType);
getActiveWeapon(cls.getCreatureStats(mPtr), cls.getInventoryStore(mPtr), &mWeaponType);
if(mWeaponType != WeapType_None)
{
getWeaponGroup(mWeaponType, mCurrentWeapon);
mUpperBodyState = UpperCharState_WeapEquiped;
mAnimation->showWeapons(true);
}
mAnimation->showCarriedLeft(mWeaponType != WeapType_Spell && mWeaponType != WeapType_HandToHand);
}
if(!cls.getCreatureStats(mPtr).isDead())
@ -506,14 +494,14 @@ bool CharacterController::updateCreatureState()
return false;
}
bool CharacterController::updateNpcState()
bool CharacterController::updateWeaponState()
{
const MWWorld::Class &cls = MWWorld::Class::get(mPtr);
NpcStats &stats = cls.getNpcStats(mPtr);
CreatureStats &stats = cls.getCreatureStats(mPtr);
WeaponType weaptype = WeapType_None;
MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr);
MWWorld::ContainerStoreIterator weapon = getActiveWeapon(stats, inv, &weaptype);
const bool isWerewolf = stats.isWerewolf();
const bool isWerewolf = cls.isNpc() && cls.getNpcStats(mPtr).isWerewolf();
bool forcestateupdate = false;
if(weaptype != mWeaponType && mHitState != CharState_KnockDown)
@ -604,7 +592,7 @@ bool CharacterController::updateNpcState()
{
// Unset casting flag, otherwise pressing the mouse button down would
// continue casting every frame if there is no animation
mPtr.getClass().getCreatureStats(mPtr).setAttackingOrSpell(false);
stats.setAttackingOrSpell(false);
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
@ -1115,8 +1103,8 @@ void CharacterController::update(float duration)
}
}
if(cls.isNpc())
forcestateupdate = updateNpcState() || forcestateupdate;
if(cls.hasInventoryStore(mPtr))
forcestateupdate = updateWeaponState() || forcestateupdate;
else
forcestateupdate = updateCreatureState() || forcestateupdate;
@ -1314,7 +1302,7 @@ void CharacterController::determineAttackType()
if (move[0] && !move[1]) //sideway
{
mPtr.getClass().getCreatureStats(mPtr).setAttackType(MWMechanics::CreatureStats::AT_Slash);
if(mPtr.getClass().isNpc())
if(mPtr.getClass().hasInventoryStore(mPtr))
mAttackType = "slash";
else
mCurrentWeapon = "attack2";
@ -1322,7 +1310,7 @@ void CharacterController::determineAttackType()
else if (move[1]) //forward
{
mPtr.getClass().getCreatureStats(mPtr).setAttackType(MWMechanics::CreatureStats::AT_Thrust);
if(mPtr.getClass().isNpc())
if(mPtr.getClass().hasInventoryStore(mPtr))
mAttackType = "thrust";
else
mCurrentWeapon = "attack3";
@ -1330,11 +1318,11 @@ void CharacterController::determineAttackType()
else
{
mPtr.getClass().getCreatureStats(mPtr).setAttackType(MWMechanics::CreatureStats::AT_Chop);
if(mPtr.getClass().isNpc())
if(mPtr.getClass().hasInventoryStore(mPtr))
mAttackType = "chop";
else
mCurrentWeapon = "attack1";
}
}
}
}

View file

@ -22,7 +22,7 @@ namespace MWMechanics
{
class Movement;
class NpcStats;
class CreatureStats;
enum Priority {
Priority_Default,
@ -92,7 +92,8 @@ enum CharacterState {
CharState_SwimDeath,
CharState_Hit,
CharState_KnockDown
CharState_KnockDown,
CharState_Block
};
enum WeaponType {
@ -171,13 +172,17 @@ class CharacterController
void clearAnimQueue();
bool updateNpcState();
bool updateWeaponState();
bool updateCreatureState();
void updateVisibility();
void playRandomDeath(float startpoint = 0.0f);
/// choose a random animation group with \a prefix and numeric suffix
/// @param num if non-NULL, the chosen animation number will be written here
std::string chooseRandomGroup (const std::string& prefix, int* num = NULL);
public:
CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim);
virtual ~CharacterController();
@ -202,9 +207,7 @@ public:
};
void getWeaponGroup(WeaponType weaptype, std::string &group);
MWWorld::ContainerStoreIterator getActiveWeapon(NpcStats &stats,
MWWorld::InventoryStore &inv,
WeaponType *weaptype);
MWWorld::ContainerStoreIterator getActiveWeapon(CreatureStats &stats, MWWorld::InventoryStore &inv, WeaponType *weaptype);
}
#endif /* GAME_MWMECHANICS_CHARACTER_HPP */

View file

@ -0,0 +1,137 @@
#include "combat.hpp"
#include <OgreSceneNode.h>
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "../mwmechanics/npcstats.hpp"
#include "../mwmechanics/movement.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/inventorystore.hpp"
#include "../mwbase/windowmanager.hpp"
namespace
{
Ogre::Radian signedAngle(Ogre::Vector3 v1, Ogre::Vector3 v2, Ogre::Vector3 n)
{
return Ogre::Math::ATan2(
n.dotProduct( v1.crossProduct(v2) ),
v1.dotProduct(v2)
);
}
}
namespace MWMechanics
{
bool blockMeleeAttack(const MWWorld::Ptr &attacker, const MWWorld::Ptr &blocker, const MWWorld::Ptr &weapon, float damage)
{
if (!blocker.getClass().hasInventoryStore(blocker))
return false;
if (blocker.getClass().getCreatureStats(blocker).getKnockedDown()
|| blocker.getClass().getCreatureStats(blocker).getHitRecovery())
return false;
MWWorld::InventoryStore& inv = blocker.getClass().getInventoryStore(blocker);
MWWorld::ContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
if (shield == inv.end() || shield->getTypeName() != typeid(ESM::Armor).name())
return false;
Ogre::Degree angle = signedAngle (Ogre::Vector3(attacker.getRefData().getPosition().pos) - Ogre::Vector3(blocker.getRefData().getPosition().pos),
blocker.getRefData().getBaseNode()->getOrientation().yAxis(), Ogre::Vector3(0,0,1));
const MWWorld::Store<ESM::GameSetting>& gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
if (angle.valueDegrees() < gmst.find("fCombatBlockLeftAngle")->getFloat())
return false;
if (angle.valueDegrees() > gmst.find("fCombatBlockRightAngle")->getFloat())
return false;
MWMechanics::CreatureStats& blockerStats = blocker.getClass().getCreatureStats(blocker);
if (blockerStats.getDrawState() == DrawState_Spell)
return false;
MWMechanics::CreatureStats& attackerStats = attacker.getClass().getCreatureStats(attacker);
float blockTerm = blocker.getClass().getSkill(blocker, ESM::Skill::Block) + 0.2 * blockerStats.getAttribute(ESM::Attribute::Agility).getModified()
+ 0.1 * blockerStats.getAttribute(ESM::Attribute::Luck).getModified();
float enemySwing = attackerStats.getAttackStrength();
float swingTerm = enemySwing * gmst.find("fSwingBlockMult")->getFloat() + gmst.find("fSwingBlockBase")->getFloat();
float blockerTerm = blockTerm * swingTerm;
if (blocker.getClass().getMovementSettings(blocker).mPosition[1] <= 0)
blockerTerm *= gmst.find("fBlockStillBonus")->getFloat();
blockerTerm *= blockerStats.getFatigueTerm();
float attackerSkill = attacker.getClass().getSkill(attacker, weapon.getClass().getEquipmentSkill(weapon));
float attackerTerm = attackerSkill + 0.2 * attackerStats.getAttribute(ESM::Attribute::Agility).getModified()
+ 0.1 * attackerStats.getAttribute(ESM::Attribute::Luck).getModified();
attackerTerm *= attackerStats.getFatigueTerm();
int x = int(blockerTerm - attackerTerm);
int iBlockMaxChance = gmst.find("iBlockMaxChance")->getInt();
int iBlockMinChance = gmst.find("iBlockMinChance")->getInt();
x = std::min(iBlockMaxChance, std::max(iBlockMinChance, x));
int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
if (roll < x)
{
// Reduce shield durability by incoming damage
if (shield->getCellRef().mCharge == -1)
shield->getCellRef().mCharge = shield->getClass().getItemMaxHealth(*shield);
shield->getCellRef().mCharge -= std::min(shield->getCellRef().mCharge, int(damage));
if (!shield->getCellRef().mCharge)
inv.unequipItem(*shield, blocker);
// Reduce blocker fatigue
const float fFatigueBlockBase = gmst.find("fFatigueBlockBase")->getFloat();
const float fFatigueBlockMult = gmst.find("fFatigueBlockMult")->getFloat();
const float fWeaponFatigueBlockMult = gmst.find("fWeaponFatigueBlockMult")->getFloat();
MWMechanics::DynamicStat<float> fatigue = blockerStats.getFatigue();
float normalizedEncumbrance = blocker.getClass().getEncumbrance(blocker) / blocker.getClass().getCapacity(blocker);
normalizedEncumbrance = std::min(1.f, normalizedEncumbrance);
float fatigueLoss = fFatigueBlockBase + normalizedEncumbrance * fFatigueBlockMult;
fatigueLoss += weapon.getClass().getWeight(weapon) * attackerStats.getAttackStrength() * fWeaponFatigueBlockMult;
fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss);
blockerStats.setFatigue(fatigue);
blockerStats.setBlock(true);
if (blocker.getClass().isNpc())
blocker.getClass().skillUsageSucceeded(blocker, ESM::Skill::Block, 0);
return true;
}
return false;
}
void resistNormalWeapon(const MWWorld::Ptr &actor, const MWWorld::Ptr& attacker, const MWWorld::Ptr &weapon, float &damage)
{
MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor);
float resistance = std::min(100.f, stats.getMagicEffects().get(ESM::MagicEffect::ResistNormalWeapons).mMagnitude
- stats.getMagicEffects().get(ESM::MagicEffect::WeaknessToNormalWeapons).mMagnitude);
float multiplier = 0;
if (resistance >= 0)
multiplier = 1 - resistance / 100.f;
else
multiplier = -(resistance-100) / 100.f;
if (!(weapon.get<ESM::Weapon>()->mBase->mData.mFlags & ESM::Weapon::Silver
|| weapon.get<ESM::Weapon>()->mBase->mData.mFlags & ESM::Weapon::Magical))
damage *= multiplier;
if (weapon.get<ESM::Weapon>()->mBase->mData.mFlags & ESM::Weapon::Silver
& actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf())
damage *= MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fWereWolfSilverWeaponDamageMult")->getFloat();
if (damage == 0 && attacker.getRefData().getHandle() == "player")
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResistsWeapons}");
}
}

View file

@ -0,0 +1,16 @@
#ifndef OPENMW_MECHANICS_COMBAT_H
#define OPENMW_MECHANICS_COMBAT_H
#include "../mwworld/ptr.hpp"
namespace MWMechanics
{
/// @return can we block the attack?
bool blockMeleeAttack (const MWWorld::Ptr& attacker, const MWWorld::Ptr& blocker, const MWWorld::Ptr& weapon, float damage);
void resistNormalWeapon (const MWWorld::Ptr& actor, const MWWorld::Ptr& attacker, const MWWorld::Ptr& weapon, float& damage);
}
#endif

View file

@ -15,8 +15,8 @@ namespace MWMechanics
mAttacked (false), mHostile (false),
mAttackingOrSpell(false), mAttackType(AT_Chop),
mIsWerewolf(false),
mFallHeight(0), mRecalcDynamicStats(false), mKnockdown(false), mHitRecovery(false),
mMovementFlags(0)
mFallHeight(0), mRecalcDynamicStats(false), mKnockdown(false), mHitRecovery(false), mBlock(false),
mMovementFlags(0), mDrawState (DrawState_Nothing), mAttackStrength(0.f)
{
for (int i=0; i<4; ++i)
mAiSettings[i] = 0;
@ -348,6 +348,13 @@ namespace MWMechanics
bool CreatureStats::getCreatureTargetted() const
{
std::string target;
if (mAiSequence.getCombatTarget(target))
{
MWWorld::Ptr targetPtr;
targetPtr = MWBase::Environment::get().getWorld()->getPtr(target, true);
return targetPtr.getTypeName() == typeid(ESM::Creature).name();
}
return false;
}
@ -427,6 +434,16 @@ namespace MWMechanics
return mHitRecovery;
}
void CreatureStats::setBlock(bool value)
{
mBlock = value;
}
bool CreatureStats::getBlock() const
{
return mBlock;
}
bool CreatureStats::getMovementFlag (Flag flag) const
{
return mMovementFlags & flag;
@ -452,4 +469,24 @@ namespace MWMechanics
return false; // shut up, compiler
}
DrawState_ CreatureStats::getDrawState() const
{
return mDrawState;
}
void CreatureStats::setDrawState(DrawState_ state)
{
mDrawState = state;
}
float CreatureStats::getAttackStrength() const
{
return mAttackStrength;
}
void CreatureStats::setAttackStrength(float value)
{
mAttackStrength = value;
}
}

View file

@ -10,6 +10,7 @@
#include "spells.hpp"
#include "activespells.hpp"
#include "aisequence.hpp"
#include "drawstate.hpp"
namespace MWMechanics
{
@ -18,6 +19,7 @@ namespace MWMechanics
///
class CreatureStats
{
DrawState_ mDrawState;
AttributeValue mAttributes[8];
DynamicStat<float> mDynamic[3]; // health, magicka, fatigue
int mLevel;
@ -37,7 +39,9 @@ namespace MWMechanics
bool mAttackingOrSpell;
bool mKnockdown;
bool mHitRecovery;
bool mBlock;
unsigned int mMovementFlags;
float mAttackStrength; // Note only some creatures attack with weapons
float mFallHeight;
@ -57,6 +61,13 @@ namespace MWMechanics
public:
CreatureStats();
DrawState_ getDrawState() const;
void setDrawState(DrawState_ state);
/// When attacking, stores how strong the attack should be (0 = weakest, 1 = strongest)
float getAttackStrength() const;
void setAttackStrength(float value);
bool needToRecalcDynamicStats();
void addToFallHeight(float height);
@ -194,6 +205,8 @@ namespace MWMechanics
bool getKnockedDown() const;
void setHitRecovery(bool value);
bool getHitRecovery() const;
void setBlock(bool value);
bool getBlock() const;
enum Flag
{

View file

@ -28,6 +28,8 @@ namespace MWMechanics
void setEnchanter(MWWorld::Ptr enchanter);
void setSelfEnchanting(bool selfEnchanting);
void setOldItem(MWWorld::Ptr oldItem);
MWWorld::Ptr getOldItem() { return mOldItemPtr; }
MWWorld::Ptr getGem() { return mSoulGemPtr; }
void setNewItemName(const std::string& s);
void setEffect(ESM::EffectList effectList);
void setSoulGem(MWWorld::Ptr soulGem);

View file

@ -6,6 +6,7 @@
#include "../mwworld/class.hpp"
#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
#include "../mwmechanics/creaturestats.hpp"
namespace MWMechanics
{

View file

@ -196,8 +196,8 @@ namespace MWMechanics
creatureStats.setDynamic (i, stat);
}
// auto-equip again. we need this for when the race is changed to a beast race
MWWorld::InventoryStore& invStore = MWWorld::Class::get(ptr).getInventoryStore(ptr);
// auto-equip again. we need this for when the race is changed to a beast race and shoes are no longer equippable
MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore(ptr);
for (int i=0; i<MWWorld::InventoryStore::Slots; ++i)
invStore.unequipAll(ptr);
invStore.autoEquip(ptr);
@ -950,4 +950,10 @@ namespace MWMechanics
return (roll >= target);
}
void MechanicsManager::getObjectsInRange(const Ogre::Vector3 &position, float radius, std::vector<MWWorld::Ptr> &objects)
{
mActors.getObjectsInRange(position, radius, objects);
mObjects.getObjectsInRange(position, radius, objects);
}
}

View file

@ -135,6 +135,8 @@ namespace MWMechanics
virtual void toggleAI();
virtual bool isAIActive();
virtual void getObjectsInRange (const Ogre::Vector3& position, float radius, std::vector<MWWorld::Ptr>& objects);
};
}

View file

@ -22,40 +22,18 @@
#include "../mwbase/soundmanager.hpp"
MWMechanics::NpcStats::NpcStats()
: mDrawState (DrawState_Nothing)
, mBounty (0)
: mBounty (0)
, mLevelProgress(0)
, mDisposition(0)
, mReputation(0)
, mWerewolfKills (0)
, mProfit(0)
, mAttackStrength(0.0f)
, mTimeToStartDrowning(20.0)
, mLastDrowningHit(0)
{
mSkillIncreases.resize (ESM::Attribute::Length, 0);
}
MWMechanics::DrawState_ MWMechanics::NpcStats::getDrawState() const
{
return mDrawState;
}
void MWMechanics::NpcStats::setDrawState (DrawState_ state)
{
mDrawState = state;
}
float MWMechanics::NpcStats::getAttackStrength() const
{
return mAttackStrength;
}
void MWMechanics::NpcStats::setAttackStrength(float value)
{
mAttackStrength = value;
}
int MWMechanics::NpcStats::getBaseDisposition() const
{
return mDisposition;
@ -289,12 +267,16 @@ bool MWMechanics::NpcStats::hasBeenUsed (const std::string& id) const
int MWMechanics::NpcStats::getBounty() const
{
return mBounty;
if (mIsWerewolf)
return MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("iWereWolfBounty")->getInt();
else
return mBounty;
}
void MWMechanics::NpcStats::setBounty (int bounty)
{
mBounty = bounty;
if (!mIsWerewolf)
mBounty = bounty;
}
int MWMechanics::NpcStats::getFactionReputation (const std::string& faction) const

View file

@ -7,7 +7,6 @@
#include <vector>
#include "stat.hpp"
#include "drawstate.hpp"
#include "creaturestats.hpp"
@ -30,7 +29,6 @@ namespace MWMechanics
/// \note the faction key must be in lowercase
std::map<std::string, int> mFactionRank;
DrawState_ mDrawState;
int mDisposition;
SkillValue mSkill[27];
SkillValue mWerewolfSkill[27];
@ -61,13 +59,6 @@ namespace MWMechanics
int getProfit() const;
void modifyProfit(int diff);
DrawState_ getDrawState() const;
void setDrawState (DrawState_ state);
/// When attacking, stores how strong the attack should be (0 = weakest, 1 = strongest)
float getAttackStrength() const;
void setAttackStrength(float value);
int getBaseDisposition() const;
void setBaseDisposition(int disposition);

View file

@ -92,4 +92,13 @@ void Objects::skipAnimation(const MWWorld::Ptr& ptr)
iter->second->skipAnim();
}
void Objects::getObjectsInRange(const Ogre::Vector3& position, float radius, std::vector<MWWorld::Ptr>& out)
{
for (PtrControllerMap::iterator iter = mObjects.begin(); iter != mObjects.end(); ++iter)
{
if (Ogre::Vector3(iter->first.getRefData().getPosition().pos).squaredDistance(position) <= radius*radius)
out.push_back(iter->first);
}
}
}

View file

@ -40,6 +40,8 @@ namespace MWMechanics
void playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number);
void skipAnimation(const MWWorld::Ptr& ptr);
void getObjectsInRange (const Ogre::Vector3& position, float radius, std::vector<MWWorld::Ptr>& out);
};
}

View file

@ -6,6 +6,7 @@
#include "OgreMath.h"
#include "OgreVector3.h"
#include <boost/graph/dijkstra_shortest_paths.hpp>
#include <boost/graph/adjacency_list.hpp>
@ -256,5 +257,6 @@ namespace MWMechanics
}
}
}

View file

@ -1,29 +1,192 @@
#include "spellcasting.hpp"
#include <cfloat>
#include <boost/format.hpp>
#include "../mwbase/windowmanager.hpp"
#include "../mwbase/soundmanager.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "../mwworld/containerstore.hpp"
#include "../mwworld/actionteleport.hpp"
#include "../mwworld/player.hpp"
#include "../mwworld/class.hpp"
#include "../mwrender/animation.hpp"
#include "magiceffects.hpp"
#include "npcstats.hpp"
namespace MWMechanics
{
ESM::Skill::SkillEnum spellSchoolToSkill(int school)
{
std::map<int, ESM::Skill::SkillEnum> schoolSkillMap; // maps spell school to skill id
schoolSkillMap[0] = ESM::Skill::Alteration;
schoolSkillMap[1] = ESM::Skill::Conjuration;
schoolSkillMap[3] = ESM::Skill::Illusion;
schoolSkillMap[2] = ESM::Skill::Destruction;
schoolSkillMap[4] = ESM::Skill::Mysticism;
schoolSkillMap[5] = ESM::Skill::Restoration;
assert(schoolSkillMap.find(school) != schoolSkillMap.end());
return schoolSkillMap[school];
}
float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool)
{
CreatureStats& stats = actor.getClass().getCreatureStats(actor);
if (stats.getMagicEffects().get(ESM::MagicEffect::Silence).mMagnitude)
return 0;
float y = FLT_MAX;
float lowestSkill = 0;
for (std::vector<ESM::ENAMstruct>::const_iterator it = spell->mEffects.mList.begin(); it != spell->mEffects.mList.end(); ++it)
{
float x = it->mDuration;
const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find(
it->mEffectID);
if (!(magicEffect->mData.mFlags & ESM::MagicEffect::UncappedDamage))
x = std::max(1.f, x);
x *= 0.1 * magicEffect->mData.mBaseCost;
x *= 0.5 * (it->mMagnMin + it->mMagnMax);
x *= it->mArea * 0.05 * magicEffect->mData.mBaseCost;
if (magicEffect->mData.mFlags & ESM::MagicEffect::CastTarget)
x *= 1.5;
static const float fEffectCostMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
"fEffectCostMult")->getFloat();
x *= fEffectCostMult;
float s = 2 * actor.getClass().getSkill(actor, spellSchoolToSkill(magicEffect->mData.mSchool));
if (s - x < y)
{
y = s - x;
if (effectiveSchool)
*effectiveSchool = magicEffect->mData.mSchool;
lowestSkill = s;
}
}
if (spell->mData.mType != ESM::Spell::ST_Spell)
return 100;
if (spell->mData.mFlags & ESM::Spell::F_Always)
return 100;
int castBonus = -stats.getMagicEffects().get(ESM::MagicEffect::Sound).mMagnitude;
int actorWillpower = stats.getAttribute(ESM::Attribute::Willpower).getModified();
int actorLuck = stats.getAttribute(ESM::Attribute::Luck).getModified();
float castChance = (lowestSkill - spell->mData.mCost + castBonus + 0.2 * actorWillpower + 0.1 * actorLuck) * stats.getFatigueTerm();
if (MWBase::Environment::get().getWorld()->getGodModeState() && actor.getRefData().getHandle() == "player")
castChance = 100;
return std::max(0.f, std::min(100.f, castChance));
}
float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool)
{
const ESM::Spell* spell =
MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find(spellId);
return getSpellSuccessChance(spell, actor, effectiveSchool);
}
int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor)
{
int school = 0;
getSpellSuccessChance(spellId, actor, &school);
return school;
}
int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor)
{
int school = 0;
getSpellSuccessChance(spell, actor, &school);
return school;
}
float getEffectResistance (short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell)
{
const ESM::MagicEffect *magicEffect =
MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
effectId);
const MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor);
float resisted = 0;
if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful)
{
short resistanceEffect = ESM::MagicEffect::getResistanceEffect(effectId);
short weaknessEffect = ESM::MagicEffect::getWeaknessEffect(effectId);
float resistance = 0;
if (resistanceEffect != -1)
resistance += stats.getMagicEffects().get(resistanceEffect).mMagnitude;
if (weaknessEffect != -1)
resistance -= stats.getMagicEffects().get(weaknessEffect).mMagnitude;
float willpower = stats.getAttribute(ESM::Attribute::Willpower).getModified();
float luck = stats.getAttribute(ESM::Attribute::Luck).getModified();
float x = (willpower + 0.1 * luck) * stats.getFatigueTerm();
// This makes spells that are easy to cast harder to resist and vice versa
if (spell != NULL && caster.getClass().isActor())
{
float castChance = getSpellSuccessChance(spell, caster);
if (castChance > 0)
x *= 50 / castChance;
}
float roll = static_cast<float>(std::rand()) / RAND_MAX * 100;
if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)
roll -= resistance;
if (x <= roll)
x = 0;
else
{
if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)
x = 100;
else
x = roll / std::min(x, 100.f);
}
x = std::min(x + resistance, 100.f);
resisted = x;
}
return resisted;
}
float getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell)
{
float resistance = getEffectResistance(effectId, actor, caster, spell);
if (resistance >= 0)
return 1 - resistance / 100.f;
else
return -(resistance-100) / 100.f;
}
CastSpell::CastSpell(const MWWorld::Ptr &caster, const MWWorld::Ptr &target)
: mCaster(caster)
, mTarget(target)
, mStack(false)
, mHitPosition(0,0,0)
{
}
void CastSpell::inflict(const MWWorld::Ptr &target, const MWWorld::Ptr &caster,
const ESM::EffectList &effects, ESM::RangeType range, bool reflected)
const ESM::EffectList &effects, ESM::RangeType range, bool reflected, bool exploded)
{
// If none of the effects need to apply, we can early-out
bool found = false;
@ -213,11 +376,12 @@ namespace MWMechanics
if (anim)
anim->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, "");
}
// TODO: For Area effects, launch a growing particle effect that applies the effect to more actors as it hits them. Best managed in World.
}
}
if (!exploded)
MWBase::Environment::get().getWorld()->explodeSpell(mHitPosition, mTarget, effects, caster, mId, mSourceName);
if (reflectedEffects.mList.size())
inflict(caster, target, reflectedEffects, range, true);
@ -230,7 +394,7 @@ namespace MWMechanics
MWBase::Environment::get().getMechanicsManager()->commitCrime(caster, target, MWBase::MechanicsManager::OT_Assault);
}
void CastSpell::applyInstantEffect(const MWWorld::Ptr &target, const MWWorld::Ptr &caster, MWMechanics::EffectKey effect, float magnitude)
void CastSpell::applyInstantEffect(const MWWorld::Ptr &target, const MWWorld::Ptr &caster, const MWMechanics::EffectKey& effect, float magnitude)
{
short effectId = effect.mId;
if (!target.getClass().isActor())
@ -359,12 +523,11 @@ namespace MWMechanics
mStack = (enchantment->mData.mType == ESM::Enchantment::CastOnce);
if (enchantment->mData.mType == ESM::Enchantment::WhenUsed)
// Check if there's enough charge left
if (enchantment->mData.mType == ESM::Enchantment::WhenUsed || enchantment->mData.mType == ESM::Enchantment::WhenStrikes)
{
// Check if there's enough charge left
const float enchantCost = enchantment->mData.mCost;
MWMechanics::NpcStats &stats = MWWorld::Class::get(mCaster).getNpcStats(mCaster);
int eSkill = stats.getSkill(ESM::Skill::Enchant).getModified();
int eSkill = mCaster.getClass().getSkill(mCaster, ESM::Skill::Enchant);
const int castCost = std::max(1.f, enchantCost - (enchantCost / 100) * (eSkill - 10));
if (item.getCellRef().mEnchantmentCharge == -1)
@ -377,10 +540,15 @@ namespace MWMechanics
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientCharge}");
return false;
}
// Reduce charge
item.getCellRef().mEnchantmentCharge -= castCost;
}
if (enchantment->mData.mType == ESM::Enchantment::WhenUsed)
{
if (mCaster.getRefData().getHandle() == "player")
mCaster.getClass().skillUsageSucceeded (mCaster, ESM::Skill::Enchant, 1);
}
if (enchantment->mData.mType == ESM::Enchantment::CastOnce)
item.getContainerStore()->remove(item, 1, mCaster);
else if (enchantment->mData.mType != ESM::Enchantment::WhenStrikes)
@ -389,9 +557,6 @@ namespace MWMechanics
MWBase::Environment::get().getWindowManager()->setSelectedEnchantItem(item); // Set again to show the modified charge
}
if (mCaster.getRefData().getHandle() == "player")
mCaster.getClass().skillUsageSucceeded (mCaster, ESM::Skill::Enchant, 1);
inflict(mCaster, mCaster, enchantment->mEffects, ESM::RT_Self);
if (!mTarget.isEmpty())

View file

@ -1,33 +1,15 @@
#ifndef MWMECHANICS_SPELLSUCCESS_H
#define MWMECHANICS_SPELLSUCCESS_H
#include <cfloat>
#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
#include "../mwworld/ptr.hpp"
#include "../mwworld/class.hpp"
#include "../mwmechanics/creaturestats.hpp"
#include "../mwworld/esmstore.hpp"
#include "npcstats.hpp"
#include <OgreVector3.h>
namespace MWMechanics
{
inline ESM::Skill::SkillEnum spellSchoolToSkill(int school)
{
std::map<int, ESM::Skill::SkillEnum> schoolSkillMap; // maps spell school to skill id
schoolSkillMap[0] = ESM::Skill::Alteration;
schoolSkillMap[1] = ESM::Skill::Conjuration;
schoolSkillMap[3] = ESM::Skill::Illusion;
schoolSkillMap[2] = ESM::Skill::Destruction;
schoolSkillMap[4] = ESM::Skill::Mysticism;
schoolSkillMap[5] = ESM::Skill::Restoration;
assert(schoolSkillMap.find(school) != schoolSkillMap.end());
return schoolSkillMap[school];
}
class EffectKey;
ESM::Skill::SkillEnum spellSchoolToSkill(int school);
/**
* @param spell spell to cast
@ -36,147 +18,16 @@ namespace MWMechanics
* @attention actor has to be an NPC and not a creature!
* @return success chance from 0 to 100 (in percent)
*/
inline float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool = NULL)
{
CreatureStats& stats = actor.getClass().getCreatureStats(actor);
float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool = NULL);
float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool = NULL);
if (stats.getMagicEffects().get(ESM::MagicEffect::Silence).mMagnitude)
return 0;
float y = FLT_MAX;
float lowestSkill = 0;
for (std::vector<ESM::ENAMstruct>::const_iterator it = spell->mEffects.mList.begin(); it != spell->mEffects.mList.end(); ++it)
{
float x = it->mDuration;
const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find(
it->mEffectID);
if (!(magicEffect->mData.mFlags & ESM::MagicEffect::UncappedDamage))
x = std::max(1.f, x);
x *= 0.1 * magicEffect->mData.mBaseCost;
x *= 0.5 * (it->mMagnMin + it->mMagnMax);
x *= it->mArea * 0.05 * magicEffect->mData.mBaseCost;
if (magicEffect->mData.mFlags & ESM::MagicEffect::CastTarget)
x *= 1.5;
static const float fEffectCostMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
"fEffectCostMult")->getFloat();
x *= fEffectCostMult;
float s = 2 * actor.getClass().getSkill(actor, spellSchoolToSkill(magicEffect->mData.mSchool));
if (s - x < y)
{
y = s - x;
if (effectiveSchool)
*effectiveSchool = magicEffect->mData.mSchool;
lowestSkill = s;
}
}
if (spell->mData.mType != ESM::Spell::ST_Spell)
return 100;
if (spell->mData.mFlags & ESM::Spell::F_Always)
return 100;
int castBonus = -stats.getMagicEffects().get(ESM::MagicEffect::Sound).mMagnitude;
int actorWillpower = stats.getAttribute(ESM::Attribute::Willpower).getModified();
int actorLuck = stats.getAttribute(ESM::Attribute::Luck).getModified();
float castChance = (lowestSkill - spell->mData.mCost + castBonus + 0.2 * actorWillpower + 0.1 * actorLuck) * stats.getFatigueTerm();
if (MWBase::Environment::get().getWorld()->getGodModeState() && actor.getRefData().getHandle() == "player")
castChance = 100;
return std::max(0.f, std::min(100.f, castChance));
}
inline float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool = NULL)
{
const ESM::Spell* spell =
MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find(spellId);
return getSpellSuccessChance(spell, actor, effectiveSchool);
}
inline int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor)
{
int school = 0;
getSpellSuccessChance(spellId, actor, &school);
return school;
}
inline int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor)
{
int school = 0;
getSpellSuccessChance(spell, actor, &school);
return school;
}
int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor);
int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor);
/// @return >=100 for fully resisted. can also return negative value for damage amplification.
inline float getEffectResistance (short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell = NULL)
{
const ESM::MagicEffect *magicEffect =
MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
effectId);
const MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor);
float resisted = 0;
if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful)
{
short resistanceEffect = ESM::MagicEffect::getResistanceEffect(effectId);
short weaknessEffect = ESM::MagicEffect::getWeaknessEffect(effectId);
float resistance = 0;
if (resistanceEffect != -1)
resistance += stats.getMagicEffects().get(resistanceEffect).mMagnitude;
if (weaknessEffect != -1)
resistance -= stats.getMagicEffects().get(weaknessEffect).mMagnitude;
float willpower = stats.getAttribute(ESM::Attribute::Willpower).getModified();
float luck = stats.getAttribute(ESM::Attribute::Luck).getModified();
float x = (willpower + 0.1 * luck) * stats.getFatigueTerm();
// This makes spells that are easy to cast harder to resist and vice versa
if (spell != NULL && caster.getClass().isActor())
{
float castChance = getSpellSuccessChance(spell, caster);
if (castChance > 0)
x *= 50 / castChance;
}
float roll = static_cast<float>(std::rand()) / RAND_MAX * 100;
if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)
roll -= resistance;
if (x <= roll)
x = 0;
else
{
if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)
x = 100;
else
x = roll / std::min(x, 100.f);
}
x = std::min(x + resistance, 100.f);
resisted = x;
}
return resisted;
}
inline float getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell = NULL)
{
float resistance = getEffectResistance(effectId, actor, caster, spell);
if (resistance >= 0)
return 1 - resistance / 100.f;
else
return -(resistance-100) / 100.f;
}
float getEffectResistance (short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell = NULL);
float getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell = NULL);
class CastSpell
{
@ -187,6 +38,7 @@ namespace MWMechanics
bool mStack;
std::string mId; // ID of spell, potion, item etc
std::string mSourceName; // Display name for spell, potion, etc
Ogre::Vector3 mHitPosition; // Used for spawning area orb
public:
CastSpell(const MWWorld::Ptr& caster, const MWWorld::Ptr& target);
@ -200,9 +52,9 @@ namespace MWMechanics
bool cast (const std::string& id);
void inflict (const MWWorld::Ptr& target, const MWWorld::Ptr& caster,
const ESM::EffectList& effects, ESM::RangeType range, bool reflected=false);
const ESM::EffectList& effects, ESM::RangeType range, bool reflected=false, bool exploded=false);
void applyInstantEffect (const MWWorld::Ptr& target, const MWWorld::Ptr& caster, MWMechanics::EffectKey effect, float magnitude);
void applyInstantEffect (const MWWorld::Ptr& target, const MWWorld::Ptr& caster, const MWMechanics::EffectKey& effect, float magnitude);
};
}

View file

@ -20,7 +20,7 @@ namespace MWMechanics
/// \brief Spell list
///
/// This class manages known spells as well as abilities, powers and permanent negative effects like
/// diseaes.
/// diseases.
class Spells
{
public:

View file

@ -75,14 +75,15 @@ void Actors::insertNPC(const MWWorld::Ptr& ptr)
delete mAllActors[ptr];
mAllActors[ptr] = anim;
mRendering->addWaterRippleEmitter (ptr);
// Create CustomData, will do autoEquip and trigger animation parts update
ptr.getClass().getInventoryStore(ptr);
}
void Actors::insertCreature (const MWWorld::Ptr& ptr)
void Actors::insertCreature (const MWWorld::Ptr& ptr, bool weaponsShields)
{
insertBegin(ptr);
CreatureAnimation* anim = new CreatureAnimation(ptr);
Animation* anim = NULL;
if (weaponsShields)
anim = new CreatureWeaponAnimation(ptr);
else
anim = new CreatureAnimation(ptr);
delete mAllActors[ptr];
mAllActors[ptr] = anim;
mRendering->addWaterRippleEmitter (ptr);

View file

@ -40,7 +40,7 @@ namespace MWRender
void setRootNode(Ogre::SceneNode* root);
void insertNPC(const MWWorld::Ptr& ptr);
void insertCreature (const MWWorld::Ptr& ptr);
void insertCreature (const MWWorld::Ptr& ptr, bool weaponsShields);
void insertActivator (const MWWorld::Ptr& ptr);
bool deleteObject (const MWWorld::Ptr& ptr);
///< \return found?

View file

@ -584,7 +584,11 @@ bool Animation::reset(AnimState &state, const NifOgre::TextKeyMap &keys, const s
const std::string stoptag = groupname+": "+stop;
NifOgre::TextKeyMap::const_iterator stopkey(groupstart);
while(stopkey != keys.end() && stopkey->second != stoptag)
while(stopkey != keys.end()
// We have to ignore extra garbage at the end.
// The Scrib's idle3 animation has "Idle3: Stop." instead of "Idle3: Stop".
// Why, just why? :(
&& (stopkey->second.size() < stoptag.size() || stopkey->second.substr(0,stoptag.size()) != stoptag))
stopkey++;
if(stopkey == keys.end())
return false;
@ -616,6 +620,13 @@ bool Animation::reset(AnimState &state, const NifOgre::TextKeyMap &keys, const s
return true;
}
void split(const std::string &s, char delim, std::vector<std::string> &elems) {
std::stringstream ss(s);
std::string item;
while (std::getline(ss, item, delim)) {
elems.push_back(item);
}
}
void Animation::handleTextKey(AnimState &state, const std::string &groupname, const NifOgre::TextKeyMap::const_iterator &key)
{
@ -630,14 +641,29 @@ void Animation::handleTextKey(AnimState &state, const std::string &groupname, co
}
if(evt.compare(0, 10, "soundgen: ") == 0)
{
std::string sound = MWWorld::Class::get(mPtr).getSoundIdFromSndGen(mPtr, evt.substr(10));
std::string soundgen = evt.substr(10);
// The event can optionally contain volume and pitch modifiers
float volume=1.f, pitch=1.f;
if (soundgen.find(" ") != std::string::npos)
{
std::vector<std::string> tokens;
split(soundgen, ' ', tokens);
soundgen = tokens[0];
if (tokens.size() >= 2)
volume = Ogre::StringConverter::parseReal(tokens[1]);
if (tokens.size() >= 3)
pitch = Ogre::StringConverter::parseReal(tokens[2]);
}
std::string sound = mPtr.getClass().getSoundIdFromSndGen(mPtr, soundgen);
if(!sound.empty())
{
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
MWBase::SoundManager::PlayType type = MWBase::SoundManager::Play_TypeSfx;
if(evt.compare(10, evt.size()-10, "left") == 0 || evt.compare(10, evt.size()-10, "right") == 0)
type = MWBase::SoundManager::Play_TypeFoot;
sndMgr->playSound3D(mPtr, sound, 1.0f, 1.0f, type);
sndMgr->playSound3D(mPtr, sound, volume, pitch, type);
}
return;
}
@ -679,6 +705,9 @@ void Animation::handleTextKey(AnimState &state, const std::string &groupname, co
else if (groupname == "spellcast" && evt.substr(evt.size()-7, 7) == "release")
MWBase::Environment::get().getWorld()->castSpell(mPtr);
else if (groupname == "shield" && evt.compare(off, len, "block hit") == 0)
mPtr.getClass().block(mPtr);
}
void Animation::changeGroups(const std::string &groupname, int groups)

View file

@ -142,7 +142,7 @@ namespace MWRender
{
mAnimation->updateParts();
MWWorld::InventoryStore &inv = MWWorld::Class::get(mCharacter).getInventoryStore(mCharacter);
MWWorld::InventoryStore &inv = mCharacter.getClass().getInventoryStore(mCharacter);
MWWorld::ContainerStoreIterator iter = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
std::string groupname;
if(iter == inv.end())

View file

@ -1,26 +1,27 @@
#include "creatureanimation.hpp"
#include <OgreEntity.h>
#include <OgreSkeletonInstance.h>
#include <OgreBone.h>
#include "renderconst.hpp"
#include "../mwbase/world.hpp"
#include "../mwworld/class.hpp"
namespace MWRender
{
CreatureAnimation::~CreatureAnimation()
{
}
CreatureAnimation::CreatureAnimation(const MWWorld::Ptr &ptr)
: Animation(ptr, ptr.getRefData().getBaseNode())
{
MWWorld::LiveCellRef<ESM::Creature> *ref = mPtr.get<ESM::Creature>();
assert (ref->mBase != NULL);
if(!ref->mBase->mModel.empty())
std::string model = ptr.getClass().getModel(ptr);
if(!model.empty())
{
std::string model = "meshes\\"+ref->mBase->mModel;
setObjectRoot(model, false);
setRenderProperties(mObjectRoot, RV_Actors, RQG_Main, RQG_Alpha);
@ -30,4 +31,115 @@ CreatureAnimation::CreatureAnimation(const MWWorld::Ptr &ptr)
}
}
CreatureWeaponAnimation::CreatureWeaponAnimation(const MWWorld::Ptr &ptr)
: Animation(ptr, ptr.getRefData().getBaseNode())
, mShowWeapons(false)
, mShowCarriedLeft(false)
{
MWWorld::LiveCellRef<ESM::Creature> *ref = mPtr.get<ESM::Creature>();
std::string model = ptr.getClass().getModel(ptr);
if(!model.empty())
{
setObjectRoot(model, false);
setRenderProperties(mObjectRoot, RV_Actors, RQG_Main, RQG_Alpha);
if((ref->mBase->mFlags&ESM::Creature::Bipedal))
addAnimSource("meshes\\base_anim.nif");
addAnimSource(model);
mPtr.getClass().getInventoryStore(mPtr).setListener(this, mPtr);
updateParts();
}
}
void CreatureWeaponAnimation::showWeapons(bool showWeapon)
{
if (showWeapon != mShowWeapons)
{
mShowWeapons = showWeapon;
updateParts();
}
}
void CreatureWeaponAnimation::showCarriedLeft(bool show)
{
if (show != mShowCarriedLeft)
{
mShowCarriedLeft = show;
updateParts();
}
}
void CreatureWeaponAnimation::updateParts()
{
mWeapon.setNull();
mShield.setNull();
if (mShowWeapons)
updatePart(mWeapon, MWWorld::InventoryStore::Slot_CarriedRight);
if (mShowCarriedLeft)
updatePart(mShield, MWWorld::InventoryStore::Slot_CarriedLeft);
}
void CreatureWeaponAnimation::updatePart(NifOgre::ObjectScenePtr& scene, int slot)
{
MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr);
MWWorld::ContainerStoreIterator it = inv.getSlot(slot);
if (it == inv.end())
{
scene.setNull();
return;
}
MWWorld::Ptr item = *it;
std::string bonename;
if (slot == MWWorld::InventoryStore::Slot_CarriedRight)
bonename = "Weapon Bone";
else
bonename = "Shield Bone";
scene = NifOgre::Loader::createObjects(mSkelBase, bonename, mInsert, item.getClass().getModel(item));
Ogre::Vector3 glowColor = getEnchantmentColor(item);
setRenderProperties(scene, RV_Actors, RQG_Main, RQG_Alpha, 0,
!item.getClass().getEnchantment(item).empty(), &glowColor);
if(scene->mSkelBase)
{
Ogre::SkeletonInstance *skel = scene->mSkelBase->getSkeleton();
if(scene->mSkelBase->isParentTagPoint())
{
Ogre::Node *root = scene->mSkelBase->getParentNode();
if(skel->hasBone("BoneOffset"))
{
Ogre::Bone *offset = skel->getBone("BoneOffset");
root->translate(offset->getPosition());
// It appears that the BoneOffset rotation is completely bogus, at least for light models.
//root->rotate(offset->getOrientation());
root->pitch(Ogre::Degree(-90.0f));
root->scale(offset->getScale());
root->setInitialState();
}
}
updateSkeletonInstance(mSkelBase->getSkeleton(), skel);
}
// TODO:
// type == ESM::PRT_Weapon should get an animation source based on the current offset
// of the weapon attack animation (from its beginning, or start marker?)
std::vector<Ogre::Controller<Ogre::Real> >::iterator ctrl(scene->mControllers.begin());
for(;ctrl != scene->mControllers.end();ctrl++)
{
if(ctrl->getSource().isNull())
ctrl->setSource(Ogre::SharedPtr<NullAnimationTime>(new NullAnimationTime()));
}
}
}

View file

@ -2,6 +2,7 @@
#define GAME_RENDER_CREATUREANIMATION_H
#include "animation.hpp"
#include "../mwworld/inventorystore.hpp"
namespace MWWorld
{
@ -14,7 +15,32 @@ namespace MWRender
{
public:
CreatureAnimation(const MWWorld::Ptr& ptr);
virtual ~CreatureAnimation();
virtual ~CreatureAnimation() {}
};
// For creatures with weapons and shields
// Animation is already virtual anyway, so might as well make a separate class.
// Most creatures don't need weapons/shields, so this will save some memory.
class CreatureWeaponAnimation : public Animation, public MWWorld::InventoryStoreListener
{
public:
CreatureWeaponAnimation(const MWWorld::Ptr& ptr);
virtual ~CreatureWeaponAnimation() {}
virtual void equipmentChanged() { updateParts(); }
virtual void showWeapons(bool showWeapon);
virtual void showCarriedLeft(bool show);
void updateParts();
void updatePart(NifOgre::ObjectScenePtr& scene, int slot);
private:
NifOgre::ObjectScenePtr mWeapon;
NifOgre::ObjectScenePtr mShield;
bool mShowWeapons;
bool mShowCarriedLeft;
};
}

View file

@ -26,9 +26,10 @@ EffectManager::EffectManager(Ogre::SceneManager *sceneMgr)
{
}
void EffectManager::addEffect(const std::string &model, std::string textureOverride, const Ogre::Vector3 &worldPosition)
void EffectManager::addEffect(const std::string &model, std::string textureOverride, const Ogre::Vector3 &worldPosition, float scale)
{
Ogre::SceneNode* sceneNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(worldPosition);
sceneNode->setScale(scale,scale,scale);
// fix texture extension to .dds
if (textureOverride.size() > 4)
@ -78,7 +79,7 @@ void EffectManager::addEffect(const std::string &model, std::string textureOverr
mEffects.push_back(std::make_pair(sceneNode, scene));
}
void EffectManager::update(float dt)
void EffectManager::update(float dt, Ogre::Camera* camera)
{
for (std::vector<std::pair<Ogre::SceneNode*, NifOgre::ObjectScenePtr> >::iterator it = mEffects.begin(); it != mEffects.end(); )
{
@ -91,6 +92,7 @@ void EffectManager::update(float dt)
objects->mControllers[i].update();
}
objects->rotateBillboardNodes(camera);
// Finished playing?
if (objects->mControllers[0].getSource()->getValue() >= objects->mMaxControllerLength)

View file

@ -14,9 +14,9 @@ namespace MWRender
~EffectManager() { clear(); }
/// Add an effect. When it's finished playing, it will be removed automatically.
void addEffect (const std::string& model, std::string textureOverride, const Ogre::Vector3& worldPosition);
void addEffect (const std::string& model, std::string textureOverride, const Ogre::Vector3& worldPosition, float scale);
void update(float dt);
void update(float dt, Ogre::Camera* camera);
/// Remove all effects
void clear();

View file

@ -378,7 +378,7 @@ void RenderingManager::update (float duration, bool paused)
if(paused)
return;
mEffectManager->update(duration);
mEffectManager->update(duration, mRendering.getCamera());
mActors->update (mRendering.getCamera());
mPlayerAnimation->preRender(mRendering.getCamera());
@ -892,8 +892,6 @@ void RenderingManager::renderPlayer(const MWWorld::Ptr &ptr)
mPlayerAnimation->~NpcAnimation();
new(mPlayerAnimation) NpcAnimation(ptr, ptr.getRefData().getBaseNode(), RV_Actors);
}
// Ensure CustomData -> autoEquip -> animation update
ptr.getClass().getInventoryStore(ptr);
mCamera->setAnimation(mPlayerAnimation);
mWater->removeEmitter(ptr);
@ -1026,9 +1024,9 @@ float RenderingManager::getCameraDistance() const
return mCamera->getCameraDistance();
}
void RenderingManager::spawnEffect(const std::string &model, const std::string &texture, const Vector3 &worldPosition)
void RenderingManager::spawnEffect(const std::string &model, const std::string &texture, const Vector3 &worldPosition, float scale)
{
mEffectManager->addEffect(model, texture, worldPosition);
mEffectManager->addEffect(model, "", worldPosition, scale);
}
} // namespace

View file

@ -207,7 +207,7 @@ public:
void stopVideo();
void frameStarted(float dt, bool paused);
void spawnEffect (const std::string& model, const std::string& texture, const Ogre::Vector3& worldPosition);
void spawnEffect (const std::string& model, const std::string& texture, const Ogre::Vector3& worldPosition, float scale=1.f);
protected:
virtual void windowResized(int x, int y);

View file

@ -437,6 +437,7 @@ namespace MWScript
MWMechanics::CreatureStats& creatureStats = actor.getClass().getCreatureStats(actor);
creatureStats.setHostile(true);
creatureStats.getAiSequence().stack(
MWMechanics::AiCombat(MWBase::Environment::get().getWorld()->getPtr(targetID, true) ));

View file

@ -115,6 +115,7 @@ namespace MWScript
current = region->mName;
}
Misc::StringUtils::toLower(current);
bool match = current.length()>=name.length() &&
current.substr (0, name.length())==name;

View file

@ -159,7 +159,7 @@ namespace MWScript
std::string item = runtime.getStringLiteral (runtime[0].mInteger);
runtime.pop();
MWWorld::InventoryStore& invStore = MWWorld::Class::get(ptr).getInventoryStore (ptr);
MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore (ptr);
MWWorld::ContainerStoreIterator it = invStore.begin();
for (; it != invStore.end(); ++it)
{
@ -171,6 +171,9 @@ namespace MWScript
MWWorld::ActionEquip action (*it);
action.execute(ptr);
if (ptr.getRefData().getHandle() == "player" && !ptr.getClass().getScript(ptr).empty())
ptr.getRefData().getLocals().setVarByInt(ptr.getClass().getScript(ptr), "onpcequip", 1);
}
};

View file

@ -745,6 +745,7 @@ namespace MWScript
MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr (targetId, false);
MWMechanics::CastSpell cast(ptr, target);
cast.mHitPosition = Ogre::Vector3(target.getRefData().getPosition().pos);
cast.cast(spell);
}
};
@ -761,6 +762,7 @@ namespace MWScript
runtime.pop();
MWMechanics::CastSpell cast(ptr, ptr);
cast.mHitPosition = Ogre::Vector3(ptr.getRefData().getPosition().pos);
cast.cast(spell);
}
};

View file

@ -640,6 +640,15 @@ namespace MWSound
mListenerUp = up;
}
void SoundManager::updatePtr(const MWWorld::Ptr &old, const MWWorld::Ptr &updated)
{
for (SoundMap::iterator snditer = mActiveSounds.begin(); snditer != mActiveSounds.end(); ++snditer)
{
if (snditer->second.first == old)
snditer->second.first = updated;
}
}
// Default readAll implementation, for decoders that can't do anything
// better
void Sound_Decoder::readAll(std::vector<char> &output)

View file

@ -148,6 +148,8 @@ namespace MWSound
virtual void update(float duration);
virtual void setListenerPosDir(const Ogre::Vector3 &pos, const Ogre::Vector3 &dir, const Ogre::Vector3 &up);
virtual void updatePtr (const MWWorld::Ptr& old, const MWWorld::Ptr& updated);
};
}

View file

@ -19,9 +19,9 @@ namespace MWWorld
void ActionEquip::executeImp (const Ptr& actor)
{
MWWorld::Ptr object = getTarget();
MWWorld::InventoryStore& invStore = MWWorld::Class::get(actor).getInventoryStore(actor);
MWWorld::InventoryStore& invStore = actor.getClass().getInventoryStore(actor);
std::pair <int, std::string> result = MWWorld::Class::get (object).canBeEquipped (object, actor);
std::pair <int, std::string> result = object.getClass().canBeEquipped (object, actor);
// display error message if the player tried to equip something
if (!result.second.empty() && actor == MWBase::Environment::get().getWorld()->getPlayerPtr())

View file

@ -8,6 +8,7 @@ namespace MWWorld
void ActionTrap::executeImp(const Ptr &actor)
{
MWMechanics::CastSpell cast(mTrapSource, actor);
cast.mHitPosition = Ogre::Vector3(actor.getRefData().getPosition().pos);
cast.cast(mSpellId);
mTrapSource.getCellRef().mTrap = "";

View file

@ -92,6 +92,11 @@ namespace MWWorld
throw std::runtime_error("class cannot hit");
}
void Class::block(const Ptr &ptr) const
{
throw std::runtime_error("class cannot block");
}
void Class::onHit(const Ptr& ptr, float damage, bool ishealth, const Ptr& object, const Ptr& attacker, bool successful) const
{
throw std::runtime_error("class cannot be hit");
@ -122,6 +127,11 @@ namespace MWWorld
throw std::runtime_error ("class does not have an inventory store");
}
bool Class::hasInventoryStore(const Ptr &ptr) const
{
return false;
}
void Class::lock (const Ptr& ptr, int lockLevel) const
{
throw std::runtime_error ("class does not support locking");

View file

@ -128,6 +128,10 @@ namespace MWWorld
/// actor responsible for the attack, and \a successful specifies if the hit is
/// successful or not.
virtual void block (const Ptr& ptr) const;
///< Play the appropriate sound for a blocked attack, depending on the currently equipped shield
/// (default implementation: throw an exception)
virtual void setActorHealth(const Ptr& ptr, float health, const Ptr& attacker=Ptr()) const;
///< Sets a new current health value for the actor, optionally specifying the object causing
/// the change. Use this instead of using CreatureStats directly as this will make sure the
@ -150,6 +154,9 @@ namespace MWWorld
///< Return inventory store or throw an exception, if class does not have a
/// inventory store (default implementation: throw an exceoption)
virtual bool hasInventoryStore (const Ptr& ptr) const;
///< Does this object have an inventory store, i.e. equipment slots? (default implementation: false)
virtual void lock (const Ptr& ptr, int lockLevel) const;
///< Lock object (default implementation: throw an exception)

View file

@ -62,6 +62,8 @@ namespace MWWorld
virtual ~ContainerStore();
virtual ContainerStore* clone() { return new ContainerStore(*this); }
ContainerStoreIterator begin (int mask = Type_All);
ContainerStoreIterator end();

View file

@ -81,7 +81,7 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::add(const Ptr& itemPtr,
// Auto-equip items if an armor/clothing or weapon item is added, but not for the player nor werewolves
if ((actorPtr.getRefData().getHandle() != "player")
&& !(MWWorld::Class::get(actorPtr).getNpcStats(actorPtr).isWerewolf())
&& !(actorPtr.getClass().isNpc() && actorPtr.getClass().getNpcStats(actorPtr).isWerewolf())
&& !actorPtr.getClass().getCreatureStats(actorPtr).isDead())
{
std::string type = itemPtr.getTypeName();
@ -457,7 +457,7 @@ int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor
// If an armor/clothing item is removed, try to find a replacement,
// but not for the player nor werewolves.
if ((actor.getRefData().getHandle() != "player")
&& !(MWWorld::Class::get(actor).getNpcStats(actor).isWerewolf()))
&& !(actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()))
{
std::string type = item.getTypeName();
if (((type == typeid(ESM::Armor).name()) || (type == typeid(ESM::Clothing).name()))

View file

@ -113,6 +113,8 @@ namespace MWWorld
InventoryStore& operator= (const InventoryStore& store);
virtual InventoryStore* clone() { return new InventoryStore(*this); }
virtual ContainerStoreIterator add (const Ptr& itemPtr, int count, const Ptr& actorPtr, bool setOwner=false);
///< Add the item pointed to by \a ptr to this container. (Stacks automatically if needed)
/// Auto-equip items if specific conditions are fulfilled (see the implementation).

View file

@ -487,7 +487,6 @@ namespace MWWorld
insertCellRefList(mRendering, cell.mContainers, cell, *mPhysics, rescale, loadingListener);
insertCellRefList(mRendering, cell.mDoors, cell, *mPhysics, rescale, loadingListener);
insertCellRefList(mRendering, cell.mIngreds, cell, *mPhysics, rescale, loadingListener);
insertCellRefList(mRendering, cell.mCreatureLists, cell, *mPhysics, rescale, loadingListener);
insertCellRefList(mRendering, cell.mItemLists, cell, *mPhysics, rescale, loadingListener);
insertCellRefList(mRendering, cell.mLights, cell, *mPhysics, rescale, loadingListener);
insertCellRefList(mRendering, cell.mLockpicks, cell, *mPhysics, rescale, loadingListener);
@ -499,6 +498,8 @@ namespace MWWorld
// Load NPCs and creatures _after_ everything else (important for adjustPosition to work correctly)
insertCellRefList(mRendering, cell.mCreatures, cell, *mPhysics, rescale, loadingListener);
insertCellRefList(mRendering, cell.mNpcs, cell, *mPhysics, rescale, loadingListener);
// Since this adds additional creatures, load afterwards, or they would be loaded twice
insertCellRefList(mRendering, cell.mCreatureLists, cell, *mPhysics, rescale, loadingListener);
}
void Scene::addObjectToScene (const Ptr& ptr)

View file

@ -56,7 +56,7 @@ void Store<ESM::Cell>::load(ESM::ESMReader &esm, const std::string &id)
// copy list into new cell
cell->mContextList = oldcell->mContextList;
// have new cell replace old cell
*oldcell = *cell;
ESM::Cell::merge(oldcell, cell);
} else
mInt[idLower] = *cell;
}
@ -83,7 +83,7 @@ void Store<ESM::Cell>::load(ESM::ESMReader &esm, const std::string &id)
}
cell->mMovedRefs = oldcell->mMovedRefs;
// have new cell replace old cell
*oldcell = *cell;
ESM::Cell::merge(oldcell, cell);
} else
mExt[std::make_pair(cell->mData.mX, cell->mData.mY)] = *cell;
}

View file

@ -821,6 +821,8 @@ namespace MWWorld
if(anim != NULL)
{
Ogre::Node *node = anim->getNode("Head");
if (node == NULL)
node = anim->getNode("Bip01 Head");
if(node != NULL)
pos += node->_getDerivedPosition();
}
@ -903,6 +905,7 @@ namespace MWWorld
MWWorld::Class::get(ptr).copyToCell(ptr, newCell, pos);
mRendering->updateObjectCell(ptr, copy);
MWBase::Environment::get().getSoundManager()->updatePtr (ptr, copy);
MWBase::MechanicsManager *mechMgr = MWBase::Environment::get().getMechanicsManager();
mechMgr->updateCell(ptr, copy);
@ -2097,13 +2100,14 @@ namespace MWWorld
void World::castSpell(const Ptr &actor)
{
MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor);
InventoryStore& inv = actor.getClass().getInventoryStore(actor);
MWWorld::Ptr target = getFacedObject();
std::string selectedSpell = stats.getSpells().getSelectedSpell();
MWMechanics::CastSpell cast(actor, target);
if (!target.isEmpty())
cast.mHitPosition = Ogre::Vector3(target.getRefData().getPosition().pos);
if (!selectedSpell.empty())
{
@ -2111,9 +2115,11 @@ namespace MWWorld
cast.cast(spell);
}
else if (inv.getSelectedEnchantItem() != inv.end())
else if (actor.getClass().hasInventoryStore(actor))
{
cast.cast(*inv.getSelectedEnchantItem());
MWWorld::InventoryStore& inv = actor.getClass().getInventoryStore(actor);
if (inv.getSelectedEnchantItem() != inv.end())
cast.cast(*inv.getSelectedEnchantItem());
}
}
@ -2170,11 +2176,18 @@ namespace MWWorld
state.mId = id;
state.mActorHandle = actor.getRefData().getHandle();
state.mSpeed = speed;
state.mEffects = effects;
state.mStack = stack;
// Only interested in "on target" effects
for (std::vector<ESM::ENAMstruct>::const_iterator iter (effects.mList.begin());
iter!=effects.mList.end(); ++iter)
{
if (iter->mRange == ESM::RT_Target)
state.mEffects.mList.push_back(*iter);
}
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
sndMgr->playSound3D(ptr, sound, 1.0f, 1.0f);
sndMgr->playSound3D(ptr, sound, 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop);
mProjectiles[ptr] = state;
}
@ -2186,6 +2199,7 @@ namespace MWWorld
{
if (!mWorldScene->isCellActive(*it->first.getCell()))
{
deleteObject(it->first);
mProjectiles.erase(it++);
continue;
}
@ -2218,11 +2232,10 @@ namespace MWWorld
if (obstacle == ptr)
continue;
explode = true;
MWWorld::Ptr caster = searchPtrViaHandle(it->second.mActorHandle);
if (caster.isEmpty())
caster = obstacle;
if (obstacle.isEmpty())
{
// Terrain
@ -2230,19 +2243,23 @@ namespace MWWorld
else
{
MWMechanics::CastSpell cast(caster, obstacle);
cast.mStack = it->second.mStack;
cast.mHitPosition = pos;
cast.mId = it->second.mId;
cast.mSourceName = it->second.mSourceName;
cast.inflict(obstacle, caster, it->second.mEffects, ESM::RT_Target, false);
cast.mStack = it->second.mStack;
cast.inflict(obstacle, caster, it->second.mEffects, ESM::RT_Target, false, true);
}
deleteObject(ptr);
mProjectiles.erase(it++);
explode = true;
}
if (explode)
{
// TODO: Explode
MWWorld::Ptr caster = searchPtrViaHandle(it->second.mActorHandle);
explodeSpell(Ogre::Vector3(ptr.getRefData().getPosition().pos), ptr, it->second.mEffects, caster, it->second.mId, it->second.mSourceName);
deleteObject(ptr);
mProjectiles.erase(it++);
continue;
}
@ -2282,7 +2299,7 @@ namespace MWWorld
void World::breakInvisibility(const Ptr &actor)
{
actor.getClass().getCreatureStats(actor).getActiveSpells().purgeEffect(ESM::MagicEffect::Invisibility);
if (actor.getClass().isNpc())
if (actor.getClass().hasInventoryStore(actor))
actor.getClass().getInventoryStore(actor).purgeEffect(ESM::MagicEffect::Invisibility);
}
@ -2638,4 +2655,67 @@ namespace MWWorld
mRendering->spawnEffect(model, texture, worldPosition);
}
void World::explodeSpell(const Vector3 &origin, const MWWorld::Ptr& object, const ESM::EffectList &effects, const Ptr &caster,
const std::string& id, const std::string& sourceName)
{
std::map<MWWorld::Ptr, std::vector<ESM::ENAMstruct> > toApply;
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt = effects.mList.begin();
effectIt != effects.mList.end(); ++effectIt)
{
const ESM::MagicEffect* effect = getStore().get<ESM::MagicEffect>().find(effectIt->mEffectID);
if (effectIt->mArea <= 0)
continue; // Not an area effect
// Spawn the explosion orb effect
const ESM::Static* areaStatic;
if (!effect->mCasting.empty())
areaStatic = getStore().get<ESM::Static>().find (effect->mArea);
else
areaStatic = getStore().get<ESM::Static>().find ("VFX_DefaultArea");
mRendering->spawnEffect("meshes\\" + areaStatic->mModel, "", origin, effectIt->mArea);
// Play explosion sound (make sure to use NoTrack, since we will delete the projectile now)
static const std::string schools[] = {
"alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration"
};
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
if(!effect->mAreaSound.empty())
sndMgr->playSound3D(object, effect->mAreaSound, 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_NoTrack);
else
sndMgr->playSound3D(object, schools[effect->mData.mSchool]+" area", 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_NoTrack);
// Get the actors in range of the effect
std::vector<MWWorld::Ptr> objects;
MWBase::Environment::get().getMechanicsManager()->getObjectsInRange(
origin, feetToGameUnits(effectIt->mArea), objects);
for (std::vector<MWWorld::Ptr>::iterator affected = objects.begin(); affected != objects.end(); ++affected)
toApply[*affected].push_back(*effectIt);
}
// Now apply the appropriate effects to each actor in range
for (std::map<MWWorld::Ptr, std::vector<ESM::ENAMstruct> >::iterator apply = toApply.begin(); apply != toApply.end(); ++apply)
{
MWWorld::Ptr source = caster;
// Vanilla-compatible behaviour of never applying the spell to the caster
// (could be changed by mods later)
if (apply->first == caster)
continue;
if (source.isEmpty())
source = apply->first;
MWMechanics::CastSpell cast(source, apply->first);
cast.mHitPosition = origin;
cast.mId = id;
cast.mSourceName = sourceName;
cast.mStack = false;
ESM::EffectList effects;
effects.mList = apply->second;
cast.inflict(apply->first, caster, effects, ESM::RT_Target, false, true);
}
}
}

View file

@ -552,6 +552,9 @@ namespace MWWorld
/// Spawn a blood effect for \a ptr at \a worldPosition
virtual void spawnBloodEffect (const MWWorld::Ptr& ptr, const Ogre::Vector3& worldPosition);
virtual void explodeSpell (const Ogre::Vector3& origin, const MWWorld::Ptr& object, const ESM::EffectList& effects,
const MWWorld::Ptr& caster, const std::string& id, const std::string& sourceName);
};
}

View file

@ -47,9 +47,13 @@ void Cell::load(ESMReader &esm, bool saveContext)
esm.getHT(waterl);
mWater = (float) waterl;
mWaterInt = true;
mHasWaterLevelRecord = true;
}
else if (esm.isNextSub("WHGT"))
{
esm.getHT(mWater);
mHasWaterLevelRecord = true;
}
// Quasi-exterior cells have a region (which determines the
// weather), pure interior cells have ambient lighting
@ -94,7 +98,7 @@ void Cell::save(ESMWriter &esm) const
esm.writeHNT("DATA", mData, 12);
if (mData.mFlags & Interior)
{
if (mWater != -1) {
if (mHasWaterLevelRecord) {
if (mWaterInt) {
int water =
(mWater >= 0) ? (int) (mWater + 0.5) : (int) (mWater - 0.5);
@ -301,4 +305,17 @@ bool Cell::getNextMVRF(ESMReader &esm, MovedCellRef &mref)
mAmbi.mFog = 0;
mAmbi.mFogDensity = 0;
}
void Cell::merge(Cell *original, Cell *modified)
{
float waterLevel = original->mWater;
if (modified->mHasWaterLevelRecord)
{
waterLevel = modified->mWater;
}
// else: keep original water level, instead of resetting to 0
*original = *modified;
original->mWater = waterLevel;
}
}

View file

@ -77,7 +77,10 @@ struct Cell
float mFogDensity;
};
Cell() : mWater(-1) {}
Cell() : mWater(0), mHasWaterLevelRecord(false) {}
/// Merge \a modified into \a original
static void merge (Cell* original, Cell* modified);
// Interior cells are indexed by this (it's the 'id'), for exterior
// cells it is optional.
@ -90,6 +93,7 @@ struct Cell
DATAstruct mData;
AMBIstruct mAmbi;
float mWater; // Water level
bool mHasWaterLevelRecord;
bool mWaterInt;
int mMapColor;
int mNAM0;

View file

@ -70,7 +70,7 @@ struct DialInfo
// Sound and text associated with this item
std::string mSound, mResponse;
// Result script (uncomiled) to run whenever this dialog item is
// Result script (uncompiled) to run whenever this dialog item is
// selected
std::string mResultScript;

View file

@ -917,6 +917,8 @@ class NIFObjectLoader
{
int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, ctrl->target->recIndex);
Ogre::Bone *trgtbone = scene->mSkelBase->getSkeleton()->getBone(trgtid);
// The keyframe controller will control this bone manually
trgtbone->setManuallyControlled(true);
Ogre::ControllerValueRealPtr srcval((animflags&Nif::NiNode::AnimFlag_AutoPlay) ?
Ogre::ControllerManager::getSingleton().getFrameTimeSource() :
Ogre::ControllerValueRealPtr());

View file

@ -433,6 +433,7 @@ void QuadTreeNode::prepareForCompositeMap(Ogre::TRect<float> area)
LayerInfo info;
info.mDiffuseMap = "textures\\_land_default.dds";
info.mParallax = false;
info.mSpecular = false;
layer.push_back(info);
matGen.setLayerList(layer);
makeQuad(sceneMgr, area.left, area.top, area.right, area.bottom, matGen.generateForCompositeMapRTT(Ogre::MaterialPtr()));