mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-04-01 11:06:41 +00:00
merge with master
This commit is contained in:
commit
fe0268062d
82 changed files with 1381 additions and 488 deletions
|
@ -74,7 +74,7 @@ add_openmw_dir (mwmechanics
|
||||||
mechanicsmanagerimp stat character creaturestats magiceffects movement actors objects
|
mechanicsmanagerimp stat character creaturestats magiceffects movement actors objects
|
||||||
drawstate spells activespells npcstats aipackage aisequence alchemy aiwander aitravel aifollow
|
drawstate spells activespells npcstats aipackage aisequence alchemy aiwander aitravel aifollow
|
||||||
aiescort aiactivate aicombat repair enchanting pathfinding security spellsuccess spellcasting
|
aiescort aiactivate aicombat repair enchanting pathfinding security spellsuccess spellcasting
|
||||||
disease pickpocket levelledlist
|
disease pickpocket levelledlist combat
|
||||||
)
|
)
|
||||||
|
|
||||||
add_openmw_dir (mwbase
|
add_openmw_dir (mwbase
|
||||||
|
|
|
@ -509,6 +509,9 @@ void OMW::Engine::activate()
|
||||||
if (ptr.isEmpty())
|
if (ptr.isEmpty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (ptr.getClass().getName(ptr) == "") // objects without name presented to user can never be activated
|
||||||
|
return;
|
||||||
|
|
||||||
MWScript::InterpreterContext interpreterContext (&ptr.getRefData().getLocals(), ptr);
|
MWScript::InterpreterContext interpreterContext (&ptr.getRefData().getLocals(), ptr);
|
||||||
|
|
||||||
boost::shared_ptr<MWWorld::Action> action =
|
boost::shared_ptr<MWWorld::Action> action =
|
||||||
|
|
|
@ -157,6 +157,8 @@ namespace MWBase
|
||||||
|
|
||||||
virtual void toggleAI() = 0;
|
virtual void toggleAI() = 0;
|
||||||
virtual bool isAIActive() = 0;
|
virtual bool isAIActive() = 0;
|
||||||
|
|
||||||
|
virtual void getObjectsInRange (const Ogre::Vector3& position, float radius, std::vector<MWWorld::Ptr>& objects) = 0;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -147,6 +147,8 @@ namespace MWBase
|
||||||
virtual void update(float duration) = 0;
|
virtual void update(float duration) = 0;
|
||||||
|
|
||||||
virtual void setListenerPosDir(const Ogre::Vector3 &pos, const Ogre::Vector3 &dir, const Ogre::Vector3 &up) = 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;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -225,7 +225,7 @@ namespace MWBase
|
||||||
|
|
||||||
/// Returns a pointer to the object the provided object would hit (if within the
|
/// 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
|
/// 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 std::pair<MWWorld::Ptr,Ogre::Vector3> getHitContact(const MWWorld::Ptr &ptr, float distance) = 0;
|
||||||
|
|
||||||
virtual void adjustPosition (const MWWorld::Ptr& ptr) = 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
|
/// Spawn a blood effect for \a ptr at \a worldPosition
|
||||||
virtual void spawnBloodEffect (const MWWorld::Ptr& ptr, const Ogre::Vector3& worldPosition) = 0;
|
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;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -289,7 +289,7 @@ namespace MWClass
|
||||||
|
|
||||||
std::pair<int, std::string> Armor::canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const
|
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)
|
if (ptr.getCellRef().mCharge == 0)
|
||||||
return std::make_pair(0, "#{sInventoryMessage1}");
|
return std::make_pair(0, "#{sInventoryMessage1}");
|
||||||
|
@ -300,20 +300,23 @@ namespace MWClass
|
||||||
if (slots_.first.empty())
|
if (slots_.first.empty())
|
||||||
return std::make_pair(0, "");
|
return std::make_pair(0, "");
|
||||||
|
|
||||||
std::string npcRace = npc.get<ESM::NPC>()->mBase->mRace;
|
if (npc.getClass().isNpc())
|
||||||
|
|
||||||
// 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)
|
|
||||||
{
|
{
|
||||||
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)
|
std::vector<ESM::PartReference> parts = ptr.get<ESM::Armor>()->mBase->mParts.mParts;
|
||||||
return std::make_pair(0, "#{sNotifyMessage13}");
|
|
||||||
if((*itr).mPart == ESM::PRT_LFoot || (*itr).mPart == ESM::PRT_RFoot)
|
for(std::vector<ESM::PartReference>::iterator itr = parts.begin(); itr != parts.end(); ++itr)
|
||||||
return std::make_pair(0, "#{sNotifyMessage14}");
|
{
|
||||||
|
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}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -238,20 +238,23 @@ namespace MWClass
|
||||||
if (slots_.first.empty())
|
if (slots_.first.empty())
|
||||||
return std::make_pair(0, "");
|
return std::make_pair(0, "");
|
||||||
|
|
||||||
std::string npcRace = npc.get<ESM::NPC>()->mBase->mRace;
|
if (npc.getClass().isNpc())
|
||||||
|
|
||||||
// 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)
|
|
||||||
{
|
{
|
||||||
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)
|
std::vector<ESM::PartReference> parts = ptr.get<ESM::Clothing>()->mBase->mParts.mParts;
|
||||||
return std::make_pair(0, "#{sNotifyMessage13}");
|
|
||||||
if((*itr).mPart == ESM::PRT_LFoot || (*itr).mPart == ESM::PRT_RFoot)
|
for(std::vector<ESM::PartReference>::iterator itr = parts.begin(); itr != parts.end(); ++itr)
|
||||||
return std::make_pair(0, "#{sNotifyMessage15}");
|
{
|
||||||
|
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}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#include "../mwmechanics/creaturestats.hpp"
|
#include "../mwmechanics/creaturestats.hpp"
|
||||||
#include "../mwmechanics/magiceffects.hpp"
|
#include "../mwmechanics/magiceffects.hpp"
|
||||||
#include "../mwmechanics/movement.hpp"
|
#include "../mwmechanics/movement.hpp"
|
||||||
|
#include "../mwmechanics/spellcasting.hpp"
|
||||||
|
|
||||||
#include "../mwbase/environment.hpp"
|
#include "../mwbase/environment.hpp"
|
||||||
#include "../mwbase/mechanicsmanager.hpp"
|
#include "../mwbase/mechanicsmanager.hpp"
|
||||||
|
@ -26,22 +27,30 @@
|
||||||
|
|
||||||
#include "../mwgui/tooltips.hpp"
|
#include "../mwgui/tooltips.hpp"
|
||||||
|
|
||||||
|
#include "../mwworld/inventorystore.hpp"
|
||||||
|
|
||||||
#include "../mwmechanics/npcstats.hpp"
|
#include "../mwmechanics/npcstats.hpp"
|
||||||
|
#include "../mwmechanics/combat.hpp"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
struct CustomData : public MWWorld::CustomData
|
struct CustomData : public MWWorld::CustomData
|
||||||
{
|
{
|
||||||
MWMechanics::CreatureStats mCreatureStats;
|
MWMechanics::CreatureStats mCreatureStats;
|
||||||
MWWorld::ContainerStore mContainerStore;
|
MWWorld::ContainerStore* mContainerStore; // may be InventoryStore for some creatures
|
||||||
MWMechanics::Movement mMovement;
|
MWMechanics::Movement mMovement;
|
||||||
|
|
||||||
virtual MWWorld::CustomData *clone() const;
|
virtual MWWorld::CustomData *clone() const;
|
||||||
|
|
||||||
|
CustomData() : mContainerStore(0) {}
|
||||||
|
virtual ~CustomData() { delete mContainerStore; }
|
||||||
};
|
};
|
||||||
|
|
||||||
MWWorld::CustomData *CustomData::clone() const
|
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);
|
data->mCreatureStats.getSpells().add (*iter);
|
||||||
|
|
||||||
// inventory
|
// 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());
|
MWBase::Environment::get().getWorld()->getStore());
|
||||||
|
|
||||||
// TODO: this is not quite correct, in vanilla the merchant's gold pool is not available in his inventory.
|
// 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)
|
// (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
|
if (ref->mBase->mFlags & ESM::Creature::Weapon)
|
||||||
ptr.getRefData().setCustomData (data.release());
|
getInventoryStore(ptr).autoEquip(ptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,8 +150,10 @@ namespace MWClass
|
||||||
|
|
||||||
void Creature::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const
|
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();
|
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
|
void Creature::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const
|
||||||
|
@ -178,9 +197,40 @@ namespace MWClass
|
||||||
{
|
{
|
||||||
MWWorld::LiveCellRef<ESM::Creature> *ref =
|
MWWorld::LiveCellRef<ESM::Creature> *ref =
|
||||||
ptr.get<ESM::Creature>();
|
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?
|
// 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())
|
if (result.first.isEmpty())
|
||||||
return; // Didn't hit anything
|
return; // Didn't hit anything
|
||||||
|
|
||||||
|
@ -191,7 +241,6 @@ namespace MWClass
|
||||||
|
|
||||||
Ogre::Vector3 hitPosition = result.second;
|
Ogre::Vector3 hitPosition = result.second;
|
||||||
|
|
||||||
MWMechanics::CreatureStats &stats = getCreatureStats(ptr);
|
|
||||||
MWMechanics::CreatureStats &otherstats = victim.getClass().getCreatureStats(victim);
|
MWMechanics::CreatureStats &otherstats = victim.getClass().getCreatureStats(victim);
|
||||||
const MWMechanics::MagicEffects &mageffects = stats.getMagicEffects();
|
const MWMechanics::MagicEffects &mageffects = stats.getMagicEffects();
|
||||||
float hitchance = ref->mBase->mData.mCombat +
|
float hitchance = ref->mBase->mData.mCombat +
|
||||||
|
@ -226,10 +275,62 @@ namespace MWClass
|
||||||
break;
|
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);
|
float damage = min + (max - min) * ::rand()/(RAND_MAX+1.0);
|
||||||
|
|
||||||
// TODO: do not do this if the attack is blocked
|
if (!weapon.isEmpty())
|
||||||
MWBase::Environment::get().getWorld()->spawnBloodEffect(victim, hitPosition);
|
{
|
||||||
|
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);
|
victim.getClass().onHit(victim, damage, true, MWWorld::Ptr(), ptr, true);
|
||||||
}
|
}
|
||||||
|
@ -258,31 +359,60 @@ namespace MWClass
|
||||||
ptr.getRefData().getLocals().setVarByInt(script, "onpchitme", 1);
|
ptr.getRefData().getLocals().setVarByInt(script, "onpchitme", 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for knockdown
|
if (damage > 0.0f && !object.isEmpty())
|
||||||
float agilityTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() * fKnockDownMult->getFloat();
|
MWMechanics::resistNormalWeapon(ptr, attacker, object, damage);
|
||||||
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.f)
|
||||||
else
|
|
||||||
getCreatureStats(ptr).setHitRecovery(true); // Is this supposed to always occur?
|
|
||||||
|
|
||||||
if(ishealth)
|
|
||||||
{
|
{
|
||||||
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);
|
MWBase::Environment::get().getSoundManager()->playSound3D(ptr, "Health Damage", 1.0f, 1.0f);
|
||||||
float health = getCreatureStats(ptr).getHealth().getCurrent() - damage;
|
float health = getCreatureStats(ptr).getHealth().getCurrent() - damage;
|
||||||
setActorHealth(ptr, health, attacker);
|
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());
|
case ESM::Skill::LightArmor:
|
||||||
fatigue.setCurrent(fatigue.getCurrent() - damage, true);
|
sndMgr->playSound3D(ptr, "Light Armor Hit", 1.0f, 1.0f);
|
||||||
getCreatureStats(ptr).setFatigue(fatigue);
|
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));
|
return boost::shared_ptr<MWWorld::Action>(new MWWorld::ActionTalk(ptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
MWWorld::ContainerStore& Creature::getContainerStore (const MWWorld::Ptr& ptr)
|
MWWorld::ContainerStore& Creature::getContainerStore (const MWWorld::Ptr& ptr) const
|
||||||
const
|
|
||||||
{
|
{
|
||||||
ensureCustomData (ptr);
|
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
|
std::string Creature::getScript (const MWWorld::Ptr& ptr) const
|
||||||
{
|
{
|
||||||
MWWorld::LiveCellRef<ESM::Creature> *ref =
|
MWWorld::LiveCellRef<ESM::Creature> *ref = ptr.get<ESM::Creature>();
|
||||||
ptr.get<ESM::Creature>();
|
|
||||||
|
|
||||||
return ref->mBase->mScript;
|
return ref->mBase->mScript;
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,6 +56,8 @@ namespace MWClass
|
||||||
|
|
||||||
virtual void hit(const MWWorld::Ptr& ptr, int type) const;
|
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 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;
|
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;
|
const MWWorld::Ptr& ptr) const;
|
||||||
///< Return container store
|
///< 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;
|
virtual std::string getScript (const MWWorld::Ptr& ptr) const;
|
||||||
///< Return name of the script attached to ptr
|
///< Return name of the script attached to ptr
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,24 @@
|
||||||
|
|
||||||
#include <components/esm/loadlevlist.hpp>
|
#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
|
namespace MWClass
|
||||||
{
|
{
|
||||||
std::string CreatureLevList::getName (const MWWorld::Ptr& ptr) const
|
std::string CreatureLevList::getName (const MWWorld::Ptr& ptr) const
|
||||||
|
@ -16,4 +34,33 @@ namespace MWClass
|
||||||
|
|
||||||
registerClass (typeid (ESM::CreatureLevList).name(), instance);
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,8 @@ namespace MWClass
|
||||||
{
|
{
|
||||||
class CreatureLevList : public MWWorld::Class
|
class CreatureLevList : public MWWorld::Class
|
||||||
{
|
{
|
||||||
|
void ensureCustomData (const MWWorld::Ptr& ptr) const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
virtual std::string getName (const MWWorld::Ptr& ptr) const;
|
virtual std::string getName (const MWWorld::Ptr& ptr) const;
|
||||||
|
@ -14,6 +16,9 @@ namespace MWClass
|
||||||
/// can return an empty string.
|
/// can return an empty string.
|
||||||
|
|
||||||
static void registerSelf();
|
static void registerSelf();
|
||||||
|
|
||||||
|
virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const;
|
||||||
|
///< Add reference into a cell for rendering
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
#include "../mwmechanics/movement.hpp"
|
#include "../mwmechanics/movement.hpp"
|
||||||
#include "../mwmechanics/spellcasting.hpp"
|
#include "../mwmechanics/spellcasting.hpp"
|
||||||
#include "../mwmechanics/disease.hpp"
|
#include "../mwmechanics/disease.hpp"
|
||||||
|
#include "../mwmechanics/combat.hpp"
|
||||||
|
|
||||||
#include "../mwworld/ptr.hpp"
|
#include "../mwworld/ptr.hpp"
|
||||||
#include "../mwworld/actiontalk.hpp"
|
#include "../mwworld/actiontalk.hpp"
|
||||||
|
@ -36,9 +37,6 @@
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
const Ogre::Radian kOgrePi (Ogre::Math::PI);
|
|
||||||
const Ogre::Radian kOgrePiOverTwo (Ogre::Math::PI / Ogre::Real(2.0));
|
|
||||||
|
|
||||||
struct CustomData : public MWWorld::CustomData
|
struct CustomData : public MWWorld::CustomData
|
||||||
{
|
{
|
||||||
MWMechanics::NpcStats mNpcStats;
|
MWMechanics::NpcStats mNpcStats;
|
||||||
|
@ -144,7 +142,7 @@ namespace
|
||||||
*
|
*
|
||||||
* and by adding class, race, specialization bonus.
|
* 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_ =
|
const ESM::Class *class_ =
|
||||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Class>().find(npc->mClass);
|
MWBase::Environment::get().getWorld()->getStore().get<ESM::Class>().find(npc->mClass);
|
||||||
|
@ -193,6 +191,18 @@ namespace
|
||||||
majorMultiplier = 1.0f;
|
majorMultiplier = 1.0f;
|
||||||
break;
|
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?
|
// is this skill in the same Specialization as the class?
|
||||||
|
@ -243,6 +253,8 @@ namespace MWClass
|
||||||
fKnockDownMult = gmst.find("fKnockDownMult");
|
fKnockDownMult = gmst.find("fKnockDownMult");
|
||||||
iKnockDownOddsMult = gmst.find("iKnockDownOddsMult");
|
iKnockDownOddsMult = gmst.find("iKnockDownOddsMult");
|
||||||
iKnockDownOddsBase = gmst.find("iKnockDownOddsBase");
|
iKnockDownOddsBase = gmst.find("iKnockDownOddsBase");
|
||||||
|
fDamageStrengthBase = gmst.find("fDamageStrengthBase");
|
||||||
|
fDamageStrengthMult = gmst.find("fDamageStrengthMult");
|
||||||
|
|
||||||
inited = true;
|
inited = true;
|
||||||
}
|
}
|
||||||
|
@ -305,7 +317,15 @@ namespace MWClass
|
||||||
data->mNpcStats.setReputation(ref->mBase->mNpdt12.mReputation);
|
data->mNpcStats.setReputation(ref->mBase->mNpdt12.mReputation);
|
||||||
|
|
||||||
autoCalculateAttributes(ref->mBase, data->mNpcStats);
|
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())
|
if (data->mNpcStats.getFactionRanks().size())
|
||||||
|
@ -450,10 +470,11 @@ namespace MWClass
|
||||||
fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss);
|
fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss);
|
||||||
getCreatureStats(ptr).setFatigue(fatigue);
|
getCreatureStats(ptr).setFatigue(fatigue);
|
||||||
|
|
||||||
|
const float fCombatDistance = gmst.find("fCombatDistance")->getFloat();
|
||||||
float dist = 100.0f * (!weapon.isEmpty() ?
|
float dist = fCombatDistance * (!weapon.isEmpty() ?
|
||||||
weapon.get<ESM::Weapon>()->mBase->mData.mReach :
|
weapon.get<ESM::Weapon>()->mBase->mData.mReach :
|
||||||
gmst.find("fHandToHandReach")->getFloat());
|
gmst.find("fHandToHandReach")->getFloat());
|
||||||
|
|
||||||
// TODO: Use second to work out the hit angle
|
// TODO: Use second to work out the hit angle
|
||||||
std::pair<MWWorld::Ptr, Ogre::Vector3> result = world->getHitContact(ptr, dist);
|
std::pair<MWWorld::Ptr, Ogre::Vector3> result = world->getHitContact(ptr, dist);
|
||||||
MWWorld::Ptr victim = result.first;
|
MWWorld::Ptr victim = result.first;
|
||||||
|
@ -506,7 +527,8 @@ namespace MWClass
|
||||||
if(attack)
|
if(attack)
|
||||||
{
|
{
|
||||||
damage = attack[0] + ((attack[1]-attack[0])*stats.getAttackStrength());
|
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)
|
if(weaphashealth)
|
||||||
{
|
{
|
||||||
int weapmaxhealth = weapon.get<ESM::Weapon>()->mBase->mData.mHealth;
|
int weapmaxhealth = weapon.get<ESM::Weapon>()->mBase->mData.mHealth;
|
||||||
|
@ -579,33 +601,19 @@ namespace MWClass
|
||||||
enchantmentName);
|
enchantmentName);
|
||||||
if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes)
|
if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes)
|
||||||
{
|
{
|
||||||
// Check if we have enough charges
|
MWMechanics::CastSpell cast(ptr, victim);
|
||||||
const float enchantCost = enchantment->mData.mCost;
|
cast.mHitPosition = hitPosition;
|
||||||
int eSkill = stats.getSkill(ESM::Skill::Enchant).getModified();
|
bool success = cast.cast(weapon);
|
||||||
const int castCost = std::max(1.f, enchantCost - (enchantCost / 100) * (eSkill - 10));
|
|
||||||
|
|
||||||
if (weapon.getCellRef().mEnchantmentCharge == -1)
|
if (ptr.getRefData().getHandle() == "player" && success)
|
||||||
weapon.getCellRef().mEnchantmentCharge = enchantment->mData.mCharge;
|
skillUsageSucceeded (ptr, ESM::Skill::Enchant, 3);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: do not do this if the attack is blocked
|
if (!weapon.isEmpty() && MWMechanics::blockMeleeAttack(ptr, victim, weapon, damage))
|
||||||
if (healthdmg)
|
damage = 0;
|
||||||
|
|
||||||
|
if (healthdmg && damage > 0)
|
||||||
MWBase::Environment::get().getWorld()->spawnBloodEffect(victim, hitPosition);
|
MWBase::Environment::get().getWorld()->spawnBloodEffect(victim, hitPosition);
|
||||||
|
|
||||||
othercls.onHit(victim, damage, healthdmg, weapon, ptr, true);
|
othercls.onHit(victim, damage, healthdmg, weapon, ptr, true);
|
||||||
|
@ -644,6 +652,9 @@ namespace MWClass
|
||||||
if (!attacker.isEmpty())
|
if (!attacker.isEmpty())
|
||||||
MWMechanics::diseaseContact(ptr, attacker);
|
MWMechanics::diseaseContact(ptr, attacker);
|
||||||
|
|
||||||
|
if (damage > 0.0f && !object.isEmpty())
|
||||||
|
MWMechanics::resistNormalWeapon(ptr, attacker, object, damage);
|
||||||
|
|
||||||
if(damage > 0.0f)
|
if(damage > 0.0f)
|
||||||
{
|
{
|
||||||
// 'ptr' is losing health. Play a 'hit' voiced dialog entry if not already saying
|
// '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
|
void Npc::setActorHealth(const MWWorld::Ptr& ptr, float health, const MWWorld::Ptr& attacker) const
|
||||||
{
|
{
|
||||||
MWMechanics::CreatureStats &crstats = getCreatureStats(ptr);
|
MWMechanics::CreatureStats &crstats = getCreatureStats(ptr);
|
||||||
|
@ -1252,4 +1287,7 @@ namespace MWClass
|
||||||
const ESM::GameSetting *Npc::fKnockDownMult;
|
const ESM::GameSetting *Npc::fKnockDownMult;
|
||||||
const ESM::GameSetting *Npc::iKnockDownOddsMult;
|
const ESM::GameSetting *Npc::iKnockDownOddsMult;
|
||||||
const ESM::GameSetting *Npc::iKnockDownOddsBase;
|
const ESM::GameSetting *Npc::iKnockDownOddsBase;
|
||||||
|
const ESM::GameSetting *Npc::fDamageStrengthBase;
|
||||||
|
const ESM::GameSetting *Npc::fDamageStrengthMult;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,8 @@ namespace MWClass
|
||||||
static const ESM::GameSetting *fKnockDownMult;
|
static const ESM::GameSetting *fKnockDownMult;
|
||||||
static const ESM::GameSetting *iKnockDownOddsMult;
|
static const ESM::GameSetting *iKnockDownOddsMult;
|
||||||
static const ESM::GameSetting *iKnockDownOddsBase;
|
static const ESM::GameSetting *iKnockDownOddsBase;
|
||||||
|
static const ESM::GameSetting *fDamageStrengthBase;
|
||||||
|
static const ESM::GameSetting *fDamageStrengthMult;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
@ -71,10 +73,14 @@ namespace MWClass
|
||||||
virtual MWWorld::InventoryStore& getInventoryStore (const MWWorld::Ptr& ptr) const;
|
virtual MWWorld::InventoryStore& getInventoryStore (const MWWorld::Ptr& ptr) const;
|
||||||
///< Return inventory store
|
///< 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 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 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 void setActorHealth(const MWWorld::Ptr& ptr, float health, const MWWorld::Ptr& attacker) const;
|
||||||
|
|
||||||
virtual boost::shared_ptr<MWWorld::Action> activate (const MWWorld::Ptr& ptr,
|
virtual boost::shared_ptr<MWWorld::Action> activate (const MWWorld::Ptr& ptr,
|
||||||
|
|
|
@ -420,7 +420,7 @@ namespace MWDialogue
|
||||||
MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Dialogue);
|
MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Dialogue);
|
||||||
|
|
||||||
// Apply disposition change to NPC's base disposition
|
// 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);
|
MWMechanics::NpcStats& npcStats = MWWorld::Class::get(mActor).getNpcStats(mActor);
|
||||||
npcStats.setBaseDisposition(npcStats.getBaseDisposition() + mPermanentDispositionChange);
|
npcStats.setBaseDisposition(npcStats.getBaseDisposition() + mPermanentDispositionChange);
|
||||||
|
@ -586,7 +586,8 @@ namespace MWDialogue
|
||||||
MWBase::WindowManager *winMgr = MWBase::Environment::get().getWindowManager();
|
MWBase::WindowManager *winMgr = MWBase::Environment::get().getWindowManager();
|
||||||
if(winMgr->getSubtitlesEnabled())
|
if(winMgr->getSubtitlesEnabled())
|
||||||
winMgr->messageBox(info->mResponse);
|
winMgr->messageBox(info->mResponse);
|
||||||
sndMgr->say(actor, info->mSound);
|
if (!info->mSound.empty())
|
||||||
|
sndMgr->say(actor, info->mSound);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -216,7 +216,7 @@ bool MWDialogue::Filter::testSelectStructNumeric (const SelectWrapper& select) c
|
||||||
float ratio = MWWorld::Class::get (player).getCreatureStats (player).getHealth().getCurrent() /
|
float ratio = MWWorld::Class::get (player).getCreatureStats (player).getHealth().getCurrent() /
|
||||||
MWWorld::Class::get (player).getCreatureStats (player).getHealth().getModified();
|
MWWorld::Class::get (player).getCreatureStats (player).getHealth().getModified();
|
||||||
|
|
||||||
return select.selectCompare (ratio);
|
return select.selectCompare (static_cast<int>(ratio*100));
|
||||||
}
|
}
|
||||||
|
|
||||||
case SelectWrapper::Function_PcDynamicStat:
|
case SelectWrapper::Function_PcDynamicStat:
|
||||||
|
@ -536,7 +536,7 @@ bool MWDialogue::Filter::getSelectStructBoolean (const SelectWrapper& select) co
|
||||||
|
|
||||||
case SelectWrapper::Function_CreatureTargetted:
|
case SelectWrapper::Function_CreatureTargetted:
|
||||||
|
|
||||||
return MWWorld::Class::get (mActor).getCreatureStats (mActor).getCreatureTargetted();
|
return mActor.getClass().getCreatureStats (mActor).getCreatureTargetted();
|
||||||
|
|
||||||
case SelectWrapper::Function_Werewolf:
|
case SelectWrapper::Function_Werewolf:
|
||||||
|
|
||||||
|
|
|
@ -39,21 +39,24 @@ namespace MWDialogue
|
||||||
const ESM::Dialogue *dialogue =
|
const ESM::Dialogue *dialogue =
|
||||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Dialogue>().find (mTopic);
|
MWBase::Environment::get().getWorld()->getStore().get<ESM::Dialogue>().find (mTopic);
|
||||||
|
|
||||||
|
bool found=false;
|
||||||
for (std::vector<ESM::DialInfo>::const_iterator iter (dialogue->mInfo.begin());
|
for (std::vector<ESM::DialInfo>::const_iterator iter (dialogue->mInfo.begin());
|
||||||
iter!=dialogue->mInfo.end(); ++iter)
|
iter!=dialogue->mInfo.end(); ++iter)
|
||||||
if (iter->mData.mDisposition==index && iter->mQuestStatus!=ESM::DialInfo::QS_Name)
|
if (iter->mData.mDisposition==index && iter->mQuestStatus!=ESM::DialInfo::QS_Name)
|
||||||
{
|
{
|
||||||
mIndex = index;
|
|
||||||
|
|
||||||
if (iter->mQuestStatus==ESM::DialInfo::QS_Finished)
|
if (iter->mQuestStatus==ESM::DialInfo::QS_Finished)
|
||||||
mFinished = true;
|
mFinished = true;
|
||||||
else if (iter->mQuestStatus==ESM::DialInfo::QS_Restart)
|
else if (iter->mQuestStatus==ESM::DialInfo::QS_Restart)
|
||||||
mFinished = false;
|
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
|
bool Quest::isFinished() const
|
||||||
|
|
|
@ -12,7 +12,7 @@ namespace MWGui
|
||||||
|
|
||||||
void CompanionItemModel::copyItem (const ItemStack& item, size_t count)
|
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);
|
MWMechanics::NpcStats& stats = MWWorld::Class::get(mActor).getNpcStats(mActor);
|
||||||
stats.modifyProfit(MWWorld::Class::get(item.mBase).getValue(item.mBase) * count);
|
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)
|
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);
|
MWMechanics::NpcStats& stats = MWWorld::Class::get(mActor).getNpcStats(mActor);
|
||||||
stats.modifyProfit(-MWWorld::Class::get(item.mBase).getValue(item.mBase) * count);
|
stats.modifyProfit(-MWWorld::Class::get(item.mBase).getValue(item.mBase) * count);
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
#include "../mwbase/environment.hpp"
|
#include "../mwbase/environment.hpp"
|
||||||
#include "../mwbase/world.hpp"
|
#include "../mwbase/world.hpp"
|
||||||
#include "../mwbase/soundmanager.hpp"
|
#include "../mwbase/soundmanager.hpp"
|
||||||
|
#include "../mwbase/dialoguemanager.hpp"
|
||||||
|
#include "../mwbase/mechanicsmanager.hpp"
|
||||||
#include "../mwworld/class.hpp"
|
#include "../mwworld/class.hpp"
|
||||||
#include "../mwworld/containerstore.hpp"
|
#include "../mwworld/containerstore.hpp"
|
||||||
|
|
||||||
|
@ -296,6 +298,28 @@ namespace MWGui
|
||||||
return;
|
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();
|
int result = mEnchanting.create();
|
||||||
|
|
||||||
if(result==1)
|
if(result==1)
|
||||||
|
|
|
@ -74,9 +74,9 @@ void InventoryItemModel::update()
|
||||||
|
|
||||||
ItemStack newItem (item, this, item.getRefData().getCount());
|
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)
|
for (int slot=0; slot<MWWorld::InventoryStore::Slots; ++slot)
|
||||||
{
|
{
|
||||||
MWWorld::ContainerStoreIterator equipped = store.getSlot(slot);
|
MWWorld::ContainerStoreIterator equipped = store.getSlot(slot);
|
||||||
|
|
|
@ -206,10 +206,12 @@ struct JournalViewModelImpl : JournalViewModel
|
||||||
if (active_only && i->second.isFinished ())
|
if (active_only && i->second.isFinished ())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
/// \todo quest.getName() is broken? returns empty string
|
const MWDialogue::Quest& quest = i->second;
|
||||||
//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));
|
visitor (reinterpret_cast <QuestId> (&i->second), toUtf8Span (i->first));
|
||||||
|
else
|
||||||
|
visitor (reinterpret_cast <QuestId> (&i->second), toUtf8Span (quest.getName()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -287,24 +287,6 @@ namespace MWGui
|
||||||
else {
|
else {
|
||||||
mainWidgetSize.width = textSize.width + 3*textPadding;
|
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::IntCoord buttonCord;
|
||||||
MyGUI::IntSize buttonSize(0, buttonHeight);
|
MyGUI::IntSize buttonSize(0, buttonHeight);
|
||||||
|
@ -326,6 +308,21 @@ namespace MWGui
|
||||||
top += buttonSize.height + 2*buttonTopPadding;
|
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
|
// Set key focus to "Ok" button
|
||||||
|
|
|
@ -3,8 +3,15 @@
|
||||||
#include <boost/lexical_cast.hpp>
|
#include <boost/lexical_cast.hpp>
|
||||||
|
|
||||||
#include "../mwworld/inventorystore.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/spellcasting.hpp"
|
||||||
|
#include "../mwmechanics/spells.hpp"
|
||||||
|
#include "../mwmechanics/creaturestats.hpp"
|
||||||
|
|
||||||
#include "../mwgui/inventorywindow.hpp"
|
#include "../mwgui/inventorywindow.hpp"
|
||||||
#include "../mwgui/bookwindow.hpp"
|
#include "../mwgui/bookwindow.hpp"
|
||||||
#include "../mwgui/scrollwindow.hpp"
|
#include "../mwgui/scrollwindow.hpp"
|
||||||
|
@ -17,8 +24,8 @@ namespace
|
||||||
{
|
{
|
||||||
bool sortItems(const MWWorld::Ptr& left, const MWWorld::Ptr& right)
|
bool sortItems(const MWWorld::Ptr& left, const MWWorld::Ptr& right)
|
||||||
{
|
{
|
||||||
int cmp = MWWorld::Class::get(left).getName(left).compare(
|
int cmp = left.getClass().getName(left).compare(
|
||||||
MWWorld::Class::get(right).getName(right));
|
right.getClass().getName(right));
|
||||||
return cmp < 0;
|
return cmp < 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -334,10 +341,7 @@ namespace MWGui
|
||||||
// equip, if it can be equipped
|
// equip, if it can be equipped
|
||||||
if (!MWWorld::Class::get(item).getEquipmentSlots(item).first.empty())
|
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
|
MWBase::Environment::get().getWindowManager()->getInventoryWindow()->useItem(item);
|
||||||
|
|
||||||
MWWorld::ActionEquip action(item);
|
|
||||||
action.execute (MWBase::Environment::get().getWorld ()->getPlayerPtr());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
store.setSelectedEnchantItem(it);
|
store.setSelectedEnchantItem(it);
|
||||||
|
|
|
@ -3,13 +3,17 @@
|
||||||
#include <boost/lexical_cast.hpp>
|
#include <boost/lexical_cast.hpp>
|
||||||
|
|
||||||
#include "../mwbase/windowmanager.hpp"
|
#include "../mwbase/windowmanager.hpp"
|
||||||
|
|
||||||
#include "../mwbase/soundmanager.hpp"
|
#include "../mwbase/soundmanager.hpp"
|
||||||
#include "../mwbase/mechanicsmanager.hpp"
|
#include "../mwbase/mechanicsmanager.hpp"
|
||||||
|
#include "../mwbase/environment.hpp"
|
||||||
|
#include "../mwbase/world.hpp"
|
||||||
|
|
||||||
#include "../mwworld/containerstore.hpp"
|
#include "../mwworld/containerstore.hpp"
|
||||||
|
#include "../mwworld/class.hpp"
|
||||||
|
|
||||||
#include "../mwmechanics/spellcasting.hpp"
|
#include "../mwmechanics/spellcasting.hpp"
|
||||||
|
#include "../mwmechanics/spells.hpp"
|
||||||
|
#include "../mwmechanics/creaturestats.hpp"
|
||||||
|
|
||||||
#include "tooltips.hpp"
|
#include "tooltips.hpp"
|
||||||
#include "class.hpp"
|
#include "class.hpp"
|
||||||
|
|
|
@ -4,11 +4,15 @@
|
||||||
#include <boost/format.hpp>
|
#include <boost/format.hpp>
|
||||||
|
|
||||||
#include "../mwbase/windowmanager.hpp"
|
#include "../mwbase/windowmanager.hpp"
|
||||||
|
#include "../mwbase/environment.hpp"
|
||||||
|
#include "../mwbase/world.hpp"
|
||||||
|
|
||||||
#include "../mwworld/inventorystore.hpp"
|
#include "../mwworld/inventorystore.hpp"
|
||||||
#include "../mwworld/actionequip.hpp"
|
#include "../mwworld/class.hpp"
|
||||||
|
|
||||||
#include "../mwmechanics/spellcasting.hpp"
|
#include "../mwmechanics/spellcasting.hpp"
|
||||||
|
#include "../mwmechanics/spells.hpp"
|
||||||
|
#include "../mwmechanics/creaturestats.hpp"
|
||||||
|
|
||||||
#include "spellicons.hpp"
|
#include "spellicons.hpp"
|
||||||
#include "inventorywindow.hpp"
|
#include "inventorywindow.hpp"
|
||||||
|
@ -231,8 +235,7 @@ namespace MWGui
|
||||||
MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top);
|
MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top);
|
||||||
|
|
||||||
float enchantCost = enchant->mData.mCost;
|
float enchantCost = enchant->mData.mCost;
|
||||||
MWMechanics::NpcStats &stats = player.getClass().getNpcStats(player);
|
int eSkill = player.getClass().getSkill(player, ESM::Skill::Enchant);
|
||||||
int eSkill = stats.getSkill(ESM::Skill::Enchant).getModified();
|
|
||||||
int castCost = std::max(1.f, enchantCost - (enchantCost / 100) * (eSkill - 10));
|
int castCost = std::max(1.f, enchantCost - (enchantCost / 100) * (eSkill - 10));
|
||||||
|
|
||||||
std::string cost = boost::lexical_cast<std::string>(castCost);
|
std::string cost = boost::lexical_cast<std::string>(castCost);
|
||||||
|
@ -316,13 +319,7 @@ namespace MWGui
|
||||||
if (_sender->getUserString("Equipped") == "false"
|
if (_sender->getUserString("Equipped") == "false"
|
||||||
&& !MWWorld::Class::get(item).getEquipmentSlots(item).first.empty())
|
&& !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
|
MWBase::Environment::get().getWindowManager()->getInventoryWindow()->useItem(item);
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
store.setSelectedEnchantItem(it);
|
store.setSelectedEnchantItem(it);
|
||||||
|
|
|
@ -211,6 +211,7 @@ namespace MWGui
|
||||||
params.mMagnMin = it->mMagnMin;
|
params.mMagnMin = it->mMagnMin;
|
||||||
params.mMagnMax = it->mMagnMax;
|
params.mMagnMax = it->mMagnMax;
|
||||||
params.mRange = it->mRange;
|
params.mRange = it->mRange;
|
||||||
|
params.mArea = it->mArea;
|
||||||
params.mIsConstant = (spell->mData.mType == ESM::Spell::ST_Ability);
|
params.mIsConstant = (spell->mData.mType == ESM::Spell::ST_Ability);
|
||||||
params.mNoTarget = false;
|
params.mNoTarget = false;
|
||||||
effects.push_back(params);
|
effects.push_back(params);
|
||||||
|
|
|
@ -162,7 +162,7 @@ namespace MWGui
|
||||||
}
|
}
|
||||||
|
|
||||||
// don't show equipped items
|
// don't show equipped items
|
||||||
if(mMerchant.getTypeName() == typeid(ESM::NPC).name())
|
if(mMerchant.getClass().hasInventoryStore(mMerchant))
|
||||||
{
|
{
|
||||||
bool isEquipped = false;
|
bool isEquipped = false;
|
||||||
MWWorld::InventoryStore& store = MWWorld::Class::get(mMerchant).getInventoryStore(mMerchant);
|
MWWorld::InventoryStore& store = MWWorld::Class::get(mMerchant).getInventoryStore(mMerchant);
|
||||||
|
|
|
@ -14,7 +14,6 @@
|
||||||
#include "../mwworld/containerstore.hpp"
|
#include "../mwworld/containerstore.hpp"
|
||||||
|
|
||||||
#include "../mwmechanics/creaturestats.hpp"
|
#include "../mwmechanics/creaturestats.hpp"
|
||||||
#include "../mwmechanics/npcstats.hpp"
|
|
||||||
|
|
||||||
#include "inventorywindow.hpp"
|
#include "inventorywindow.hpp"
|
||||||
#include "itemview.hpp"
|
#include "itemview.hpp"
|
||||||
|
@ -272,6 +271,25 @@ namespace MWGui
|
||||||
return;
|
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(mCurrentBalance > mCurrentMerchantOffer)
|
||||||
{
|
{
|
||||||
//if npc is a creature: reject (no haggle)
|
//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)
|
float clampedDisposition = std::max<int>(0,std::min<int>(int(MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mPtr)
|
||||||
+ MWBase::Environment::get().getDialogueManager()->getTemporaryDispositionChange()),100));
|
+ MWBase::Environment::get().getDialogueManager()->getTemporaryDispositionChange()),100));
|
||||||
|
|
||||||
const MWMechanics::NpcStats &sellerStats = mPtr.getClass().getNpcStats(mPtr);
|
const MWMechanics::CreatureStats &sellerStats = mPtr.getClass().getCreatureStats(mPtr);
|
||||||
const MWMechanics::NpcStats &playerStats = player.getClass().getNpcStats(player);
|
const MWMechanics::CreatureStats &playerStats = player.getClass().getCreatureStats(player);
|
||||||
|
|
||||||
float a1 = std::min(player.getClass().getSkill(player, ESM::Skill::Mercantile), 100);
|
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);
|
float b1 = std::min(0.1f * playerStats.getAttribute(ESM::Attribute::Luck).getModified(), 10.f);
|
||||||
|
|
|
@ -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)
|
bool disintegrateSlot (MWWorld::Ptr ptr, int slot, float disintegrate)
|
||||||
{
|
{
|
||||||
// TODO: remove this check once creatures support inventory store
|
if (ptr.getClass().hasInventoryStore(ptr))
|
||||||
if (ptr.getTypeName() == typeid(ESM::NPC).name())
|
|
||||||
{
|
{
|
||||||
MWWorld::InventoryStore& inv = ptr.getClass().getInventoryStore(ptr);
|
MWWorld::InventoryStore& inv = ptr.getClass().getInventoryStore(ptr);
|
||||||
MWWorld::ContainerStoreIterator item =
|
MWWorld::ContainerStoreIterator item =
|
||||||
|
@ -132,6 +131,8 @@ namespace MWMechanics
|
||||||
static const float fSoulgemMult = world->getStore().get<ESM::GameSetting>().find("fSoulgemMult")->getFloat();
|
static const float fSoulgemMult = world->getStore().get<ESM::GameSetting>().find("fSoulgemMult")->getFloat();
|
||||||
|
|
||||||
float creatureSoulValue = mCreature.get<ESM::Creature>()->mBase->mData.mSoul;
|
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
|
// Use the smallest soulgem that is large enough to hold the soul
|
||||||
MWWorld::ContainerStore& container = caster.getClass().getContainerStore(caster);
|
MWWorld::ContainerStore& container = caster.getClass().getContainerStore(caster);
|
||||||
|
@ -918,4 +919,13 @@ namespace MWMechanics
|
||||||
return iter->second->isAnimPlaying(groupName);
|
return iter->second->isAnimPlaying(groupName);
|
||||||
return false;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,6 +94,8 @@ namespace MWMechanics
|
||||||
void skipAnimation(const MWWorld::Ptr& ptr);
|
void skipAnimation(const MWWorld::Ptr& ptr);
|
||||||
bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName);
|
bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName);
|
||||||
|
|
||||||
|
void getObjectsInRange(const Ogre::Vector3& position, float radius, std::vector<MWWorld::Ptr>& out);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
PtrControllerMap mActors;
|
PtrControllerMap mActors;
|
||||||
|
|
||||||
|
|
|
@ -50,9 +50,11 @@ namespace MWMechanics
|
||||||
bool AiCombat::execute (const MWWorld::Ptr& actor,float duration)
|
bool AiCombat::execute (const MWWorld::Ptr& actor,float duration)
|
||||||
{
|
{
|
||||||
//General description
|
//General description
|
||||||
if(!actor.getClass().getCreatureStats(actor).isHostile())
|
if(!actor.getClass().getCreatureStats(actor).isHostile()
|
||||||
|
|| actor.getClass().getCreatureStats(actor).getHealth().getCurrent() <= 0)
|
||||||
return true;
|
return true;
|
||||||
if(actor.getClass().getCreatureStats(actor).getHealth().getCurrent() <= 0)
|
|
||||||
|
if(mTarget.getClass().getCreatureStats(mTarget).isDead())
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
//Update every frame
|
//Update every frame
|
||||||
|
@ -125,9 +127,9 @@ namespace MWMechanics
|
||||||
|
|
||||||
actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true);
|
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)
|
if (state == MWMechanics::DrawState_Spell || state == MWMechanics::DrawState_Nothing)
|
||||||
actor.getClass().getNpcStats(actor).setDrawState(MWMechanics::DrawState_Weapon);
|
actor.getClass().getNpcStats(actor).setDrawState(MWMechanics::DrawState_Weapon);
|
||||||
|
|
||||||
|
@ -160,6 +162,7 @@ namespace MWMechanics
|
||||||
|
|
||||||
float zAngle;
|
float zAngle;
|
||||||
|
|
||||||
|
|
||||||
float rangeMelee;
|
float rangeMelee;
|
||||||
float rangeCloseUp;
|
float rangeCloseUp;
|
||||||
bool distantCombat = false;
|
bool distantCombat = false;
|
||||||
|
@ -367,12 +370,14 @@ namespace MWMechanics
|
||||||
return mTarget.getRefData().getHandle();
|
return mTarget.getRefData().getHandle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
AiCombat *MWMechanics::AiCombat::clone() const
|
AiCombat *MWMechanics::AiCombat::clone() const
|
||||||
{
|
{
|
||||||
return new AiCombat(*this);
|
return new AiCombat(*this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,8 @@
|
||||||
#include "../mwbase/environment.hpp"
|
#include "../mwbase/environment.hpp"
|
||||||
#include "../mwbase/mechanicsmanager.hpp"
|
#include "../mwbase/mechanicsmanager.hpp"
|
||||||
#include "../mwbase/dialoguemanager.hpp"
|
#include "../mwbase/dialoguemanager.hpp"
|
||||||
#include "../mwmechanics/npcstats.hpp"
|
|
||||||
|
|
||||||
|
#include "creaturestats.hpp"
|
||||||
#include <OgreVector3.h>
|
#include <OgreVector3.h>
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
|
@ -32,6 +32,7 @@ namespace MWMechanics
|
||||||
, mX(0)
|
, mX(0)
|
||||||
, mY(0)
|
, mY(0)
|
||||||
, mZ(0)
|
, mZ(0)
|
||||||
|
, mSaidGreeting(false)
|
||||||
{
|
{
|
||||||
for(unsigned short counter = 0; counter < mIdle.size(); counter++)
|
for(unsigned short counter = 0; counter < mIdle.size(); counter++)
|
||||||
{
|
{
|
||||||
|
@ -66,8 +67,7 @@ namespace MWMechanics
|
||||||
|
|
||||||
bool AiWander::execute (const MWWorld::Ptr& actor,float duration)
|
bool AiWander::execute (const MWWorld::Ptr& actor,float duration)
|
||||||
{
|
{
|
||||||
if (actor.getClass().isNpc())
|
actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing);
|
||||||
actor.getClass().getNpcStats(actor).setDrawState(DrawState_Nothing);
|
|
||||||
MWBase::World *world = MWBase::Environment::get().getWorld();
|
MWBase::World *world = MWBase::Environment::get().getWorld();
|
||||||
if(mDuration)
|
if(mDuration)
|
||||||
{
|
{
|
||||||
|
@ -191,14 +191,49 @@ namespace MWMechanics
|
||||||
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
|
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
|
||||||
float chance = store.get<ESM::GameSetting>().find("fVoiceIdleOdds")->getFloat();
|
float chance = store.get<ESM::GameSetting>().find("fVoiceIdleOdds")->getFloat();
|
||||||
int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
|
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");
|
MWBase::Environment::get().getDialogueManager()->say(actor, "idle");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(mIdleNow)
|
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))
|
if(!checkIdle(actor, mPlayedIdle))
|
||||||
{
|
{
|
||||||
mPlayedIdle = 0;
|
mPlayedIdle = 0;
|
||||||
|
|
|
@ -32,6 +32,8 @@ namespace MWMechanics
|
||||||
std::vector<int> mIdle;
|
std::vector<int> mIdle;
|
||||||
bool mRepeat;
|
bool mRepeat;
|
||||||
|
|
||||||
|
bool mSaidGreeting;
|
||||||
|
|
||||||
float mX;
|
float mX;
|
||||||
float mY;
|
float mY;
|
||||||
float mZ;
|
float mZ;
|
||||||
|
|
|
@ -62,26 +62,6 @@ struct StateInfo {
|
||||||
const char groupname[32];
|
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[] = {
|
static const StateInfo sMovementList[] = {
|
||||||
{ CharState_WalkForward, "walkforward" },
|
{ CharState_WalkForward, "walkforward" },
|
||||||
{ CharState_WalkBack, "walkback" },
|
{ CharState_WalkBack, "walkback" },
|
||||||
|
@ -154,6 +134,17 @@ public:
|
||||||
{ return weap.type == type; }
|
{ 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)
|
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 recovery = mPtr.getClass().getCreatureStats(mPtr).getHitRecovery();
|
||||||
bool knockdown = mPtr.getClass().getCreatureStats(mPtr).getKnockedDown();
|
bool knockdown = mPtr.getClass().getCreatureStats(mPtr).getKnockedDown();
|
||||||
|
bool block = mPtr.getClass().getCreatureStats(mPtr).getBlock();
|
||||||
if(mHitState == CharState_None)
|
if(mHitState == CharState_None)
|
||||||
{
|
{
|
||||||
if(knockdown)
|
if(knockdown)
|
||||||
{
|
{
|
||||||
mHitState = CharState_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);
|
mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::Group_All, true, 1, "start", "stop", 0.0f, 0);
|
||||||
}
|
}
|
||||||
else if (recovery)
|
else if (recovery)
|
||||||
{
|
{
|
||||||
mHitState = CharState_Hit;
|
mHitState = CharState_Hit;
|
||||||
int iHit = rand() % (sHitListSize-1);
|
mCurrentHit = chooseRandomGroup("hit");
|
||||||
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];
|
|
||||||
}
|
|
||||||
mAnimation->play(mCurrentHit, Priority_Hit, MWRender::Animation::Group_All, true, 1, "start", "stop", 0.0f, 0);
|
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))
|
else if(!mAnimation->isPlaying(mCurrentHit))
|
||||||
{
|
{
|
||||||
|
@ -191,6 +182,8 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
|
||||||
mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(false);
|
mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(false);
|
||||||
if (recovery)
|
if (recovery)
|
||||||
mPtr.getClass().getCreatureStats(mPtr).setHitRecovery(false);
|
mPtr.getClass().getCreatureStats(mPtr).setHitRecovery(false);
|
||||||
|
if (block)
|
||||||
|
mPtr.getClass().getCreatureStats(mPtr).setBlock(false);
|
||||||
mHitState = CharState_None;
|
mHitState = CharState_None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -250,14 +243,16 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
|
||||||
|
|
||||||
mAnimation->disable(mCurrentJump);
|
mAnimation->disable(mCurrentJump);
|
||||||
mCurrentJump = jump;
|
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);
|
1.0f, ((mode!=2)?"start":"loop start"), "stop", 0.0f, ~0ul);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
mAnimation->disable(mCurrentJump);
|
mAnimation->disable(mCurrentJump);
|
||||||
mCurrentJump.clear();
|
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);
|
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)
|
if(stats.getDrawState() == DrawState_Spell)
|
||||||
{
|
{
|
||||||
|
@ -385,28 +380,20 @@ MWWorld::ContainerStoreIterator getActiveWeapon(NpcStats &stats, MWWorld::Invent
|
||||||
|
|
||||||
void CharacterController::playRandomDeath(float startpoint)
|
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 = "swimdeath";
|
||||||
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];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
mDeathState = CharState_Death1;
|
int selected=0;
|
||||||
mCurrentDeath = "death1";
|
mCurrentDeath = chooseRandomGroup("death", &selected);
|
||||||
|
mDeathState = static_cast<CharacterState>(CharState_Death1 + (selected-1));
|
||||||
}
|
}
|
||||||
|
|
||||||
mAnimation->play(mCurrentDeath, Priority_Death, MWRender::Animation::Group_All,
|
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)
|
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. */
|
* handle knockout and death which moves the character down. */
|
||||||
mAnimation->setAccumulation(Ogre::Vector3(1.0f, 1.0f, 0.0f));
|
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)
|
if(mWeaponType != WeapType_None)
|
||||||
{
|
{
|
||||||
getWeaponGroup(mWeaponType, mCurrentWeapon);
|
getWeaponGroup(mWeaponType, mCurrentWeapon);
|
||||||
mUpperBodyState = UpperCharState_WeapEquiped;
|
mUpperBodyState = UpperCharState_WeapEquiped;
|
||||||
mAnimation->showWeapons(true);
|
mAnimation->showWeapons(true);
|
||||||
}
|
}
|
||||||
|
mAnimation->showCarriedLeft(mWeaponType != WeapType_Spell && mWeaponType != WeapType_HandToHand);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!cls.getCreatureStats(mPtr).isDead())
|
if(!cls.getCreatureStats(mPtr).isDead())
|
||||||
|
@ -506,14 +494,14 @@ bool CharacterController::updateCreatureState()
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CharacterController::updateNpcState()
|
bool CharacterController::updateWeaponState()
|
||||||
{
|
{
|
||||||
const MWWorld::Class &cls = MWWorld::Class::get(mPtr);
|
const MWWorld::Class &cls = MWWorld::Class::get(mPtr);
|
||||||
NpcStats &stats = cls.getNpcStats(mPtr);
|
CreatureStats &stats = cls.getCreatureStats(mPtr);
|
||||||
WeaponType weaptype = WeapType_None;
|
WeaponType weaptype = WeapType_None;
|
||||||
MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr);
|
MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr);
|
||||||
MWWorld::ContainerStoreIterator weapon = getActiveWeapon(stats, inv, &weaptype);
|
MWWorld::ContainerStoreIterator weapon = getActiveWeapon(stats, inv, &weaptype);
|
||||||
const bool isWerewolf = stats.isWerewolf();
|
const bool isWerewolf = cls.isNpc() && cls.getNpcStats(mPtr).isWerewolf();
|
||||||
|
|
||||||
bool forcestateupdate = false;
|
bool forcestateupdate = false;
|
||||||
if(weaptype != mWeaponType && mHitState != CharState_KnockDown)
|
if(weaptype != mWeaponType && mHitState != CharState_KnockDown)
|
||||||
|
@ -604,7 +592,7 @@ bool CharacterController::updateNpcState()
|
||||||
{
|
{
|
||||||
// Unset casting flag, otherwise pressing the mouse button down would
|
// Unset casting flag, otherwise pressing the mouse button down would
|
||||||
// continue casting every frame if there is no animation
|
// 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();
|
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
|
||||||
|
|
||||||
|
@ -1115,8 +1103,8 @@ void CharacterController::update(float duration)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(cls.isNpc())
|
if(cls.hasInventoryStore(mPtr))
|
||||||
forcestateupdate = updateNpcState() || forcestateupdate;
|
forcestateupdate = updateWeaponState() || forcestateupdate;
|
||||||
else
|
else
|
||||||
forcestateupdate = updateCreatureState() || forcestateupdate;
|
forcestateupdate = updateCreatureState() || forcestateupdate;
|
||||||
|
|
||||||
|
@ -1314,7 +1302,7 @@ void CharacterController::determineAttackType()
|
||||||
if (move[0] && !move[1]) //sideway
|
if (move[0] && !move[1]) //sideway
|
||||||
{
|
{
|
||||||
mPtr.getClass().getCreatureStats(mPtr).setAttackType(MWMechanics::CreatureStats::AT_Slash);
|
mPtr.getClass().getCreatureStats(mPtr).setAttackType(MWMechanics::CreatureStats::AT_Slash);
|
||||||
if(mPtr.getClass().isNpc())
|
if(mPtr.getClass().hasInventoryStore(mPtr))
|
||||||
mAttackType = "slash";
|
mAttackType = "slash";
|
||||||
else
|
else
|
||||||
mCurrentWeapon = "attack2";
|
mCurrentWeapon = "attack2";
|
||||||
|
@ -1322,7 +1310,7 @@ void CharacterController::determineAttackType()
|
||||||
else if (move[1]) //forward
|
else if (move[1]) //forward
|
||||||
{
|
{
|
||||||
mPtr.getClass().getCreatureStats(mPtr).setAttackType(MWMechanics::CreatureStats::AT_Thrust);
|
mPtr.getClass().getCreatureStats(mPtr).setAttackType(MWMechanics::CreatureStats::AT_Thrust);
|
||||||
if(mPtr.getClass().isNpc())
|
if(mPtr.getClass().hasInventoryStore(mPtr))
|
||||||
mAttackType = "thrust";
|
mAttackType = "thrust";
|
||||||
else
|
else
|
||||||
mCurrentWeapon = "attack3";
|
mCurrentWeapon = "attack3";
|
||||||
|
@ -1330,11 +1318,11 @@ void CharacterController::determineAttackType()
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
mPtr.getClass().getCreatureStats(mPtr).setAttackType(MWMechanics::CreatureStats::AT_Chop);
|
mPtr.getClass().getCreatureStats(mPtr).setAttackType(MWMechanics::CreatureStats::AT_Chop);
|
||||||
if(mPtr.getClass().isNpc())
|
if(mPtr.getClass().hasInventoryStore(mPtr))
|
||||||
mAttackType = "chop";
|
mAttackType = "chop";
|
||||||
else
|
else
|
||||||
mCurrentWeapon = "attack1";
|
mCurrentWeapon = "attack1";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ namespace MWMechanics
|
||||||
{
|
{
|
||||||
|
|
||||||
class Movement;
|
class Movement;
|
||||||
class NpcStats;
|
class CreatureStats;
|
||||||
|
|
||||||
enum Priority {
|
enum Priority {
|
||||||
Priority_Default,
|
Priority_Default,
|
||||||
|
@ -92,7 +92,8 @@ enum CharacterState {
|
||||||
CharState_SwimDeath,
|
CharState_SwimDeath,
|
||||||
|
|
||||||
CharState_Hit,
|
CharState_Hit,
|
||||||
CharState_KnockDown
|
CharState_KnockDown,
|
||||||
|
CharState_Block
|
||||||
};
|
};
|
||||||
|
|
||||||
enum WeaponType {
|
enum WeaponType {
|
||||||
|
@ -171,13 +172,17 @@ class CharacterController
|
||||||
|
|
||||||
void clearAnimQueue();
|
void clearAnimQueue();
|
||||||
|
|
||||||
bool updateNpcState();
|
bool updateWeaponState();
|
||||||
bool updateCreatureState();
|
bool updateCreatureState();
|
||||||
|
|
||||||
void updateVisibility();
|
void updateVisibility();
|
||||||
|
|
||||||
void playRandomDeath(float startpoint = 0.0f);
|
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:
|
public:
|
||||||
CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim);
|
CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim);
|
||||||
virtual ~CharacterController();
|
virtual ~CharacterController();
|
||||||
|
@ -202,9 +207,7 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
void getWeaponGroup(WeaponType weaptype, std::string &group);
|
void getWeaponGroup(WeaponType weaptype, std::string &group);
|
||||||
MWWorld::ContainerStoreIterator getActiveWeapon(NpcStats &stats,
|
MWWorld::ContainerStoreIterator getActiveWeapon(CreatureStats &stats, MWWorld::InventoryStore &inv, WeaponType *weaptype);
|
||||||
MWWorld::InventoryStore &inv,
|
|
||||||
WeaponType *weaptype);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* GAME_MWMECHANICS_CHARACTER_HPP */
|
#endif /* GAME_MWMECHANICS_CHARACTER_HPP */
|
||||||
|
|
137
apps/openmw/mwmechanics/combat.cpp
Normal file
137
apps/openmw/mwmechanics/combat.cpp
Normal 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}");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
16
apps/openmw/mwmechanics/combat.hpp
Normal file
16
apps/openmw/mwmechanics/combat.hpp
Normal 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
|
|
@ -15,8 +15,8 @@ namespace MWMechanics
|
||||||
mAttacked (false), mHostile (false),
|
mAttacked (false), mHostile (false),
|
||||||
mAttackingOrSpell(false), mAttackType(AT_Chop),
|
mAttackingOrSpell(false), mAttackType(AT_Chop),
|
||||||
mIsWerewolf(false),
|
mIsWerewolf(false),
|
||||||
mFallHeight(0), mRecalcDynamicStats(false), mKnockdown(false), mHitRecovery(false),
|
mFallHeight(0), mRecalcDynamicStats(false), mKnockdown(false), mHitRecovery(false), mBlock(false),
|
||||||
mMovementFlags(0)
|
mMovementFlags(0), mDrawState (DrawState_Nothing), mAttackStrength(0.f)
|
||||||
{
|
{
|
||||||
for (int i=0; i<4; ++i)
|
for (int i=0; i<4; ++i)
|
||||||
mAiSettings[i] = 0;
|
mAiSettings[i] = 0;
|
||||||
|
@ -348,6 +348,13 @@ namespace MWMechanics
|
||||||
|
|
||||||
bool CreatureStats::getCreatureTargetted() const
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -427,6 +434,16 @@ namespace MWMechanics
|
||||||
return mHitRecovery;
|
return mHitRecovery;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CreatureStats::setBlock(bool value)
|
||||||
|
{
|
||||||
|
mBlock = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CreatureStats::getBlock() const
|
||||||
|
{
|
||||||
|
return mBlock;
|
||||||
|
}
|
||||||
|
|
||||||
bool CreatureStats::getMovementFlag (Flag flag) const
|
bool CreatureStats::getMovementFlag (Flag flag) const
|
||||||
{
|
{
|
||||||
return mMovementFlags & flag;
|
return mMovementFlags & flag;
|
||||||
|
@ -452,4 +469,24 @@ namespace MWMechanics
|
||||||
return false; // shut up, compiler
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
#include "spells.hpp"
|
#include "spells.hpp"
|
||||||
#include "activespells.hpp"
|
#include "activespells.hpp"
|
||||||
#include "aisequence.hpp"
|
#include "aisequence.hpp"
|
||||||
|
#include "drawstate.hpp"
|
||||||
|
|
||||||
namespace MWMechanics
|
namespace MWMechanics
|
||||||
{
|
{
|
||||||
|
@ -18,6 +19,7 @@ namespace MWMechanics
|
||||||
///
|
///
|
||||||
class CreatureStats
|
class CreatureStats
|
||||||
{
|
{
|
||||||
|
DrawState_ mDrawState;
|
||||||
AttributeValue mAttributes[8];
|
AttributeValue mAttributes[8];
|
||||||
DynamicStat<float> mDynamic[3]; // health, magicka, fatigue
|
DynamicStat<float> mDynamic[3]; // health, magicka, fatigue
|
||||||
int mLevel;
|
int mLevel;
|
||||||
|
@ -37,7 +39,9 @@ namespace MWMechanics
|
||||||
bool mAttackingOrSpell;
|
bool mAttackingOrSpell;
|
||||||
bool mKnockdown;
|
bool mKnockdown;
|
||||||
bool mHitRecovery;
|
bool mHitRecovery;
|
||||||
|
bool mBlock;
|
||||||
unsigned int mMovementFlags;
|
unsigned int mMovementFlags;
|
||||||
|
float mAttackStrength; // Note only some creatures attack with weapons
|
||||||
|
|
||||||
float mFallHeight;
|
float mFallHeight;
|
||||||
|
|
||||||
|
@ -57,6 +61,13 @@ namespace MWMechanics
|
||||||
public:
|
public:
|
||||||
CreatureStats();
|
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();
|
bool needToRecalcDynamicStats();
|
||||||
|
|
||||||
void addToFallHeight(float height);
|
void addToFallHeight(float height);
|
||||||
|
@ -194,6 +205,8 @@ namespace MWMechanics
|
||||||
bool getKnockedDown() const;
|
bool getKnockedDown() const;
|
||||||
void setHitRecovery(bool value);
|
void setHitRecovery(bool value);
|
||||||
bool getHitRecovery() const;
|
bool getHitRecovery() const;
|
||||||
|
void setBlock(bool value);
|
||||||
|
bool getBlock() const;
|
||||||
|
|
||||||
enum Flag
|
enum Flag
|
||||||
{
|
{
|
||||||
|
|
|
@ -28,6 +28,8 @@ namespace MWMechanics
|
||||||
void setEnchanter(MWWorld::Ptr enchanter);
|
void setEnchanter(MWWorld::Ptr enchanter);
|
||||||
void setSelfEnchanting(bool selfEnchanting);
|
void setSelfEnchanting(bool selfEnchanting);
|
||||||
void setOldItem(MWWorld::Ptr oldItem);
|
void setOldItem(MWWorld::Ptr oldItem);
|
||||||
|
MWWorld::Ptr getOldItem() { return mOldItemPtr; }
|
||||||
|
MWWorld::Ptr getGem() { return mSoulGemPtr; }
|
||||||
void setNewItemName(const std::string& s);
|
void setNewItemName(const std::string& s);
|
||||||
void setEffect(ESM::EffectList effectList);
|
void setEffect(ESM::EffectList effectList);
|
||||||
void setSoulGem(MWWorld::Ptr soulGem);
|
void setSoulGem(MWWorld::Ptr soulGem);
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#include "../mwworld/class.hpp"
|
#include "../mwworld/class.hpp"
|
||||||
#include "../mwbase/world.hpp"
|
#include "../mwbase/world.hpp"
|
||||||
#include "../mwbase/environment.hpp"
|
#include "../mwbase/environment.hpp"
|
||||||
|
#include "../mwmechanics/creaturestats.hpp"
|
||||||
|
|
||||||
namespace MWMechanics
|
namespace MWMechanics
|
||||||
{
|
{
|
||||||
|
|
|
@ -196,8 +196,8 @@ namespace MWMechanics
|
||||||
creatureStats.setDynamic (i, stat);
|
creatureStats.setDynamic (i, stat);
|
||||||
}
|
}
|
||||||
|
|
||||||
// auto-equip again. we need this for when the race is changed to a beast race
|
// 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 = MWWorld::Class::get(ptr).getInventoryStore(ptr);
|
MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore(ptr);
|
||||||
for (int i=0; i<MWWorld::InventoryStore::Slots; ++i)
|
for (int i=0; i<MWWorld::InventoryStore::Slots; ++i)
|
||||||
invStore.unequipAll(ptr);
|
invStore.unequipAll(ptr);
|
||||||
invStore.autoEquip(ptr);
|
invStore.autoEquip(ptr);
|
||||||
|
@ -950,4 +950,10 @@ namespace MWMechanics
|
||||||
|
|
||||||
return (roll >= target);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -135,6 +135,8 @@ namespace MWMechanics
|
||||||
|
|
||||||
virtual void toggleAI();
|
virtual void toggleAI();
|
||||||
virtual bool isAIActive();
|
virtual bool isAIActive();
|
||||||
|
|
||||||
|
virtual void getObjectsInRange (const Ogre::Vector3& position, float radius, std::vector<MWWorld::Ptr>& objects);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,40 +22,18 @@
|
||||||
#include "../mwbase/soundmanager.hpp"
|
#include "../mwbase/soundmanager.hpp"
|
||||||
|
|
||||||
MWMechanics::NpcStats::NpcStats()
|
MWMechanics::NpcStats::NpcStats()
|
||||||
: mDrawState (DrawState_Nothing)
|
: mBounty (0)
|
||||||
, mBounty (0)
|
|
||||||
, mLevelProgress(0)
|
, mLevelProgress(0)
|
||||||
, mDisposition(0)
|
, mDisposition(0)
|
||||||
, mReputation(0)
|
, mReputation(0)
|
||||||
, mWerewolfKills (0)
|
, mWerewolfKills (0)
|
||||||
, mProfit(0)
|
, mProfit(0)
|
||||||
, mAttackStrength(0.0f)
|
|
||||||
, mTimeToStartDrowning(20.0)
|
, mTimeToStartDrowning(20.0)
|
||||||
, mLastDrowningHit(0)
|
, mLastDrowningHit(0)
|
||||||
{
|
{
|
||||||
mSkillIncreases.resize (ESM::Attribute::Length, 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
|
int MWMechanics::NpcStats::getBaseDisposition() const
|
||||||
{
|
{
|
||||||
return mDisposition;
|
return mDisposition;
|
||||||
|
@ -289,12 +267,16 @@ bool MWMechanics::NpcStats::hasBeenUsed (const std::string& id) const
|
||||||
|
|
||||||
int MWMechanics::NpcStats::getBounty() 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)
|
void MWMechanics::NpcStats::setBounty (int bounty)
|
||||||
{
|
{
|
||||||
mBounty = bounty;
|
if (!mIsWerewolf)
|
||||||
|
mBounty = bounty;
|
||||||
}
|
}
|
||||||
|
|
||||||
int MWMechanics::NpcStats::getFactionReputation (const std::string& faction) const
|
int MWMechanics::NpcStats::getFactionReputation (const std::string& faction) const
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "stat.hpp"
|
#include "stat.hpp"
|
||||||
#include "drawstate.hpp"
|
|
||||||
|
|
||||||
#include "creaturestats.hpp"
|
#include "creaturestats.hpp"
|
||||||
|
|
||||||
|
@ -30,7 +29,6 @@ namespace MWMechanics
|
||||||
/// \note the faction key must be in lowercase
|
/// \note the faction key must be in lowercase
|
||||||
std::map<std::string, int> mFactionRank;
|
std::map<std::string, int> mFactionRank;
|
||||||
|
|
||||||
DrawState_ mDrawState;
|
|
||||||
int mDisposition;
|
int mDisposition;
|
||||||
SkillValue mSkill[27];
|
SkillValue mSkill[27];
|
||||||
SkillValue mWerewolfSkill[27];
|
SkillValue mWerewolfSkill[27];
|
||||||
|
@ -61,13 +59,6 @@ namespace MWMechanics
|
||||||
int getProfit() const;
|
int getProfit() const;
|
||||||
void modifyProfit(int diff);
|
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;
|
int getBaseDisposition() const;
|
||||||
|
|
||||||
void setBaseDisposition(int disposition);
|
void setBaseDisposition(int disposition);
|
||||||
|
|
|
@ -92,4 +92,13 @@ void Objects::skipAnimation(const MWWorld::Ptr& ptr)
|
||||||
iter->second->skipAnim();
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,6 +40,8 @@ namespace MWMechanics
|
||||||
|
|
||||||
void playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number);
|
void playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number);
|
||||||
void skipAnimation(const MWWorld::Ptr& ptr);
|
void skipAnimation(const MWWorld::Ptr& ptr);
|
||||||
|
|
||||||
|
void getObjectsInRange (const Ogre::Vector3& position, float radius, std::vector<MWWorld::Ptr>& out);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#include "OgreMath.h"
|
#include "OgreMath.h"
|
||||||
#include "OgreVector3.h"
|
#include "OgreVector3.h"
|
||||||
|
|
||||||
|
|
||||||
#include <boost/graph/dijkstra_shortest_paths.hpp>
|
#include <boost/graph/dijkstra_shortest_paths.hpp>
|
||||||
#include <boost/graph/adjacency_list.hpp>
|
#include <boost/graph/adjacency_list.hpp>
|
||||||
|
|
||||||
|
@ -256,5 +257,6 @@ namespace MWMechanics
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,29 +1,192 @@
|
||||||
#include "spellcasting.hpp"
|
#include "spellcasting.hpp"
|
||||||
|
|
||||||
|
#include <cfloat>
|
||||||
|
|
||||||
#include <boost/format.hpp>
|
#include <boost/format.hpp>
|
||||||
|
|
||||||
#include "../mwbase/windowmanager.hpp"
|
#include "../mwbase/windowmanager.hpp"
|
||||||
#include "../mwbase/soundmanager.hpp"
|
#include "../mwbase/soundmanager.hpp"
|
||||||
#include "../mwbase/mechanicsmanager.hpp"
|
#include "../mwbase/mechanicsmanager.hpp"
|
||||||
|
#include "../mwbase/environment.hpp"
|
||||||
|
#include "../mwbase/world.hpp"
|
||||||
|
|
||||||
#include "../mwworld/containerstore.hpp"
|
#include "../mwworld/containerstore.hpp"
|
||||||
#include "../mwworld/actionteleport.hpp"
|
#include "../mwworld/actionteleport.hpp"
|
||||||
#include "../mwworld/player.hpp"
|
#include "../mwworld/player.hpp"
|
||||||
|
#include "../mwworld/class.hpp"
|
||||||
|
|
||||||
#include "../mwrender/animation.hpp"
|
#include "../mwrender/animation.hpp"
|
||||||
|
|
||||||
|
#include "magiceffects.hpp"
|
||||||
|
#include "npcstats.hpp"
|
||||||
|
|
||||||
namespace MWMechanics
|
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)
|
CastSpell::CastSpell(const MWWorld::Ptr &caster, const MWWorld::Ptr &target)
|
||||||
: mCaster(caster)
|
: mCaster(caster)
|
||||||
, mTarget(target)
|
, mTarget(target)
|
||||||
, mStack(false)
|
, mStack(false)
|
||||||
|
, mHitPosition(0,0,0)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void CastSpell::inflict(const MWWorld::Ptr &target, const MWWorld::Ptr &caster,
|
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
|
// If none of the effects need to apply, we can early-out
|
||||||
bool found = false;
|
bool found = false;
|
||||||
|
@ -213,11 +376,12 @@ namespace MWMechanics
|
||||||
if (anim)
|
if (anim)
|
||||||
anim->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, "");
|
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())
|
if (reflectedEffects.mList.size())
|
||||||
inflict(caster, target, reflectedEffects, range, true);
|
inflict(caster, target, reflectedEffects, range, true);
|
||||||
|
|
||||||
|
@ -230,7 +394,7 @@ namespace MWMechanics
|
||||||
MWBase::Environment::get().getMechanicsManager()->commitCrime(caster, target, MWBase::MechanicsManager::OT_Assault);
|
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;
|
short effectId = effect.mId;
|
||||||
if (!target.getClass().isActor())
|
if (!target.getClass().isActor())
|
||||||
|
@ -359,12 +523,11 @@ namespace MWMechanics
|
||||||
|
|
||||||
mStack = (enchantment->mData.mType == ESM::Enchantment::CastOnce);
|
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;
|
const float enchantCost = enchantment->mData.mCost;
|
||||||
MWMechanics::NpcStats &stats = MWWorld::Class::get(mCaster).getNpcStats(mCaster);
|
int eSkill = mCaster.getClass().getSkill(mCaster, ESM::Skill::Enchant);
|
||||||
int eSkill = stats.getSkill(ESM::Skill::Enchant).getModified();
|
|
||||||
const int castCost = std::max(1.f, enchantCost - (enchantCost / 100) * (eSkill - 10));
|
const int castCost = std::max(1.f, enchantCost - (enchantCost / 100) * (eSkill - 10));
|
||||||
|
|
||||||
if (item.getCellRef().mEnchantmentCharge == -1)
|
if (item.getCellRef().mEnchantmentCharge == -1)
|
||||||
|
@ -377,10 +540,15 @@ namespace MWMechanics
|
||||||
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientCharge}");
|
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientCharge}");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reduce charge
|
// Reduce charge
|
||||||
item.getCellRef().mEnchantmentCharge -= castCost;
|
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)
|
if (enchantment->mData.mType == ESM::Enchantment::CastOnce)
|
||||||
item.getContainerStore()->remove(item, 1, mCaster);
|
item.getContainerStore()->remove(item, 1, mCaster);
|
||||||
else if (enchantment->mData.mType != ESM::Enchantment::WhenStrikes)
|
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
|
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);
|
inflict(mCaster, mCaster, enchantment->mEffects, ESM::RT_Self);
|
||||||
|
|
||||||
if (!mTarget.isEmpty())
|
if (!mTarget.isEmpty())
|
||||||
|
|
|
@ -1,33 +1,15 @@
|
||||||
#ifndef MWMECHANICS_SPELLSUCCESS_H
|
#ifndef MWMECHANICS_SPELLSUCCESS_H
|
||||||
#define MWMECHANICS_SPELLSUCCESS_H
|
#define MWMECHANICS_SPELLSUCCESS_H
|
||||||
|
|
||||||
#include <cfloat>
|
|
||||||
|
|
||||||
#include "../mwbase/world.hpp"
|
|
||||||
#include "../mwbase/environment.hpp"
|
|
||||||
|
|
||||||
#include "../mwworld/ptr.hpp"
|
#include "../mwworld/ptr.hpp"
|
||||||
#include "../mwworld/class.hpp"
|
|
||||||
#include "../mwmechanics/creaturestats.hpp"
|
|
||||||
|
|
||||||
#include "../mwworld/esmstore.hpp"
|
#include <OgreVector3.h>
|
||||||
|
|
||||||
#include "npcstats.hpp"
|
|
||||||
|
|
||||||
namespace MWMechanics
|
namespace MWMechanics
|
||||||
{
|
{
|
||||||
inline ESM::Skill::SkillEnum spellSchoolToSkill(int school)
|
class EffectKey;
|
||||||
{
|
|
||||||
std::map<int, ESM::Skill::SkillEnum> schoolSkillMap; // maps spell school to skill id
|
ESM::Skill::SkillEnum spellSchoolToSkill(int school);
|
||||||
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];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param spell spell to cast
|
* @param spell spell to cast
|
||||||
|
@ -36,147 +18,16 @@ namespace MWMechanics
|
||||||
* @attention actor has to be an NPC and not a creature!
|
* @attention actor has to be an NPC and not a creature!
|
||||||
* @return success chance from 0 to 100 (in percent)
|
* @return success chance from 0 to 100 (in percent)
|
||||||
*/
|
*/
|
||||||
inline float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool = NULL)
|
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);
|
||||||
CreatureStats& stats = actor.getClass().getCreatureStats(actor);
|
|
||||||
|
|
||||||
if (stats.getMagicEffects().get(ESM::MagicEffect::Silence).mMagnitude)
|
int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor);
|
||||||
return 0;
|
int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor);
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @return >=100 for fully resisted. can also return negative value for damage amplification.
|
/// @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)
|
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 getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell = NULL);
|
||||||
|
|
||||||
class CastSpell
|
class CastSpell
|
||||||
{
|
{
|
||||||
|
@ -187,6 +38,7 @@ namespace MWMechanics
|
||||||
bool mStack;
|
bool mStack;
|
||||||
std::string mId; // ID of spell, potion, item etc
|
std::string mId; // ID of spell, potion, item etc
|
||||||
std::string mSourceName; // Display name for spell, potion, etc
|
std::string mSourceName; // Display name for spell, potion, etc
|
||||||
|
Ogre::Vector3 mHitPosition; // Used for spawning area orb
|
||||||
|
|
||||||
public:
|
public:
|
||||||
CastSpell(const MWWorld::Ptr& caster, const MWWorld::Ptr& target);
|
CastSpell(const MWWorld::Ptr& caster, const MWWorld::Ptr& target);
|
||||||
|
@ -200,9 +52,9 @@ namespace MWMechanics
|
||||||
bool cast (const std::string& id);
|
bool cast (const std::string& id);
|
||||||
|
|
||||||
void inflict (const MWWorld::Ptr& target, const MWWorld::Ptr& caster,
|
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);
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ namespace MWMechanics
|
||||||
/// \brief Spell list
|
/// \brief Spell list
|
||||||
///
|
///
|
||||||
/// This class manages known spells as well as abilities, powers and permanent negative effects like
|
/// This class manages known spells as well as abilities, powers and permanent negative effects like
|
||||||
/// diseaes.
|
/// diseases.
|
||||||
class Spells
|
class Spells
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -75,14 +75,15 @@ void Actors::insertNPC(const MWWorld::Ptr& ptr)
|
||||||
delete mAllActors[ptr];
|
delete mAllActors[ptr];
|
||||||
mAllActors[ptr] = anim;
|
mAllActors[ptr] = anim;
|
||||||
mRendering->addWaterRippleEmitter (ptr);
|
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);
|
insertBegin(ptr);
|
||||||
CreatureAnimation* anim = new CreatureAnimation(ptr);
|
Animation* anim = NULL;
|
||||||
|
if (weaponsShields)
|
||||||
|
anim = new CreatureWeaponAnimation(ptr);
|
||||||
|
else
|
||||||
|
anim = new CreatureAnimation(ptr);
|
||||||
delete mAllActors[ptr];
|
delete mAllActors[ptr];
|
||||||
mAllActors[ptr] = anim;
|
mAllActors[ptr] = anim;
|
||||||
mRendering->addWaterRippleEmitter (ptr);
|
mRendering->addWaterRippleEmitter (ptr);
|
||||||
|
|
|
@ -40,7 +40,7 @@ namespace MWRender
|
||||||
void setRootNode(Ogre::SceneNode* root);
|
void setRootNode(Ogre::SceneNode* root);
|
||||||
|
|
||||||
void insertNPC(const MWWorld::Ptr& ptr);
|
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);
|
void insertActivator (const MWWorld::Ptr& ptr);
|
||||||
bool deleteObject (const MWWorld::Ptr& ptr);
|
bool deleteObject (const MWWorld::Ptr& ptr);
|
||||||
///< \return found?
|
///< \return found?
|
||||||
|
|
|
@ -584,7 +584,11 @@ bool Animation::reset(AnimState &state, const NifOgre::TextKeyMap &keys, const s
|
||||||
|
|
||||||
const std::string stoptag = groupname+": "+stop;
|
const std::string stoptag = groupname+": "+stop;
|
||||||
NifOgre::TextKeyMap::const_iterator stopkey(groupstart);
|
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++;
|
stopkey++;
|
||||||
if(stopkey == keys.end())
|
if(stopkey == keys.end())
|
||||||
return false;
|
return false;
|
||||||
|
@ -616,6 +620,13 @@ bool Animation::reset(AnimState &state, const NifOgre::TextKeyMap &keys, const s
|
||||||
return true;
|
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)
|
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)
|
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())
|
if(!sound.empty())
|
||||||
{
|
{
|
||||||
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
|
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
|
||||||
MWBase::SoundManager::PlayType type = MWBase::SoundManager::Play_TypeSfx;
|
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)
|
if(evt.compare(10, evt.size()-10, "left") == 0 || evt.compare(10, evt.size()-10, "right") == 0)
|
||||||
type = MWBase::SoundManager::Play_TypeFoot;
|
type = MWBase::SoundManager::Play_TypeFoot;
|
||||||
sndMgr->playSound3D(mPtr, sound, 1.0f, 1.0f, type);
|
sndMgr->playSound3D(mPtr, sound, volume, pitch, type);
|
||||||
}
|
}
|
||||||
return;
|
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")
|
else if (groupname == "spellcast" && evt.substr(evt.size()-7, 7) == "release")
|
||||||
MWBase::Environment::get().getWorld()->castSpell(mPtr);
|
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)
|
void Animation::changeGroups(const std::string &groupname, int groups)
|
||||||
|
|
|
@ -142,7 +142,7 @@ namespace MWRender
|
||||||
{
|
{
|
||||||
mAnimation->updateParts();
|
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);
|
MWWorld::ContainerStoreIterator iter = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
|
||||||
std::string groupname;
|
std::string groupname;
|
||||||
if(iter == inv.end())
|
if(iter == inv.end())
|
||||||
|
|
|
@ -1,26 +1,27 @@
|
||||||
#include "creatureanimation.hpp"
|
#include "creatureanimation.hpp"
|
||||||
|
|
||||||
|
#include <OgreEntity.h>
|
||||||
|
#include <OgreSkeletonInstance.h>
|
||||||
|
#include <OgreBone.h>
|
||||||
|
|
||||||
#include "renderconst.hpp"
|
#include "renderconst.hpp"
|
||||||
|
|
||||||
#include "../mwbase/world.hpp"
|
#include "../mwbase/world.hpp"
|
||||||
|
|
||||||
|
#include "../mwworld/class.hpp"
|
||||||
|
|
||||||
namespace MWRender
|
namespace MWRender
|
||||||
{
|
{
|
||||||
|
|
||||||
CreatureAnimation::~CreatureAnimation()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
CreatureAnimation::CreatureAnimation(const MWWorld::Ptr &ptr)
|
CreatureAnimation::CreatureAnimation(const MWWorld::Ptr &ptr)
|
||||||
: Animation(ptr, ptr.getRefData().getBaseNode())
|
: Animation(ptr, ptr.getRefData().getBaseNode())
|
||||||
{
|
{
|
||||||
MWWorld::LiveCellRef<ESM::Creature> *ref = mPtr.get<ESM::Creature>();
|
MWWorld::LiveCellRef<ESM::Creature> *ref = mPtr.get<ESM::Creature>();
|
||||||
|
|
||||||
assert (ref->mBase != NULL);
|
std::string model = ptr.getClass().getModel(ptr);
|
||||||
if(!ref->mBase->mModel.empty())
|
if(!model.empty())
|
||||||
{
|
{
|
||||||
std::string model = "meshes\\"+ref->mBase->mModel;
|
|
||||||
|
|
||||||
setObjectRoot(model, false);
|
setObjectRoot(model, false);
|
||||||
setRenderProperties(mObjectRoot, RV_Actors, RQG_Main, RQG_Alpha);
|
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()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
#define GAME_RENDER_CREATUREANIMATION_H
|
#define GAME_RENDER_CREATUREANIMATION_H
|
||||||
|
|
||||||
#include "animation.hpp"
|
#include "animation.hpp"
|
||||||
|
#include "../mwworld/inventorystore.hpp"
|
||||||
|
|
||||||
namespace MWWorld
|
namespace MWWorld
|
||||||
{
|
{
|
||||||
|
@ -14,7 +15,32 @@ namespace MWRender
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CreatureAnimation(const MWWorld::Ptr& ptr);
|
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;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
Ogre::SceneNode* sceneNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(worldPosition);
|
||||||
|
sceneNode->setScale(scale,scale,scale);
|
||||||
|
|
||||||
// fix texture extension to .dds
|
// fix texture extension to .dds
|
||||||
if (textureOverride.size() > 4)
|
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));
|
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(); )
|
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->mControllers[i].update();
|
||||||
}
|
}
|
||||||
|
objects->rotateBillboardNodes(camera);
|
||||||
|
|
||||||
// Finished playing?
|
// Finished playing?
|
||||||
if (objects->mControllers[0].getSource()->getValue() >= objects->mMaxControllerLength)
|
if (objects->mControllers[0].getSource()->getValue() >= objects->mMaxControllerLength)
|
||||||
|
|
|
@ -14,9 +14,9 @@ namespace MWRender
|
||||||
~EffectManager() { clear(); }
|
~EffectManager() { clear(); }
|
||||||
|
|
||||||
/// Add an effect. When it's finished playing, it will be removed automatically.
|
/// 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
|
/// Remove all effects
|
||||||
void clear();
|
void clear();
|
||||||
|
|
|
@ -378,7 +378,7 @@ void RenderingManager::update (float duration, bool paused)
|
||||||
if(paused)
|
if(paused)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
mEffectManager->update(duration);
|
mEffectManager->update(duration, mRendering.getCamera());
|
||||||
|
|
||||||
mActors->update (mRendering.getCamera());
|
mActors->update (mRendering.getCamera());
|
||||||
mPlayerAnimation->preRender(mRendering.getCamera());
|
mPlayerAnimation->preRender(mRendering.getCamera());
|
||||||
|
@ -892,8 +892,6 @@ void RenderingManager::renderPlayer(const MWWorld::Ptr &ptr)
|
||||||
mPlayerAnimation->~NpcAnimation();
|
mPlayerAnimation->~NpcAnimation();
|
||||||
new(mPlayerAnimation) NpcAnimation(ptr, ptr.getRefData().getBaseNode(), RV_Actors);
|
new(mPlayerAnimation) NpcAnimation(ptr, ptr.getRefData().getBaseNode(), RV_Actors);
|
||||||
}
|
}
|
||||||
// Ensure CustomData -> autoEquip -> animation update
|
|
||||||
ptr.getClass().getInventoryStore(ptr);
|
|
||||||
|
|
||||||
mCamera->setAnimation(mPlayerAnimation);
|
mCamera->setAnimation(mPlayerAnimation);
|
||||||
mWater->removeEmitter(ptr);
|
mWater->removeEmitter(ptr);
|
||||||
|
@ -1026,9 +1024,9 @@ float RenderingManager::getCameraDistance() const
|
||||||
return mCamera->getCameraDistance();
|
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
|
} // namespace
|
||||||
|
|
|
@ -207,7 +207,7 @@ public:
|
||||||
void stopVideo();
|
void stopVideo();
|
||||||
void frameStarted(float dt, bool paused);
|
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:
|
protected:
|
||||||
virtual void windowResized(int x, int y);
|
virtual void windowResized(int x, int y);
|
||||||
|
|
|
@ -437,6 +437,7 @@ namespace MWScript
|
||||||
|
|
||||||
MWMechanics::CreatureStats& creatureStats = actor.getClass().getCreatureStats(actor);
|
MWMechanics::CreatureStats& creatureStats = actor.getClass().getCreatureStats(actor);
|
||||||
|
|
||||||
|
|
||||||
creatureStats.setHostile(true);
|
creatureStats.setHostile(true);
|
||||||
creatureStats.getAiSequence().stack(
|
creatureStats.getAiSequence().stack(
|
||||||
MWMechanics::AiCombat(MWBase::Environment::get().getWorld()->getPtr(targetID, true) ));
|
MWMechanics::AiCombat(MWBase::Environment::get().getWorld()->getPtr(targetID, true) ));
|
||||||
|
|
|
@ -115,6 +115,7 @@ namespace MWScript
|
||||||
|
|
||||||
current = region->mName;
|
current = region->mName;
|
||||||
}
|
}
|
||||||
|
Misc::StringUtils::toLower(current);
|
||||||
|
|
||||||
bool match = current.length()>=name.length() &&
|
bool match = current.length()>=name.length() &&
|
||||||
current.substr (0, name.length())==name;
|
current.substr (0, name.length())==name;
|
||||||
|
|
|
@ -159,7 +159,7 @@ namespace MWScript
|
||||||
std::string item = runtime.getStringLiteral (runtime[0].mInteger);
|
std::string item = runtime.getStringLiteral (runtime[0].mInteger);
|
||||||
runtime.pop();
|
runtime.pop();
|
||||||
|
|
||||||
MWWorld::InventoryStore& invStore = MWWorld::Class::get(ptr).getInventoryStore (ptr);
|
MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore (ptr);
|
||||||
MWWorld::ContainerStoreIterator it = invStore.begin();
|
MWWorld::ContainerStoreIterator it = invStore.begin();
|
||||||
for (; it != invStore.end(); ++it)
|
for (; it != invStore.end(); ++it)
|
||||||
{
|
{
|
||||||
|
@ -171,6 +171,9 @@ namespace MWScript
|
||||||
|
|
||||||
MWWorld::ActionEquip action (*it);
|
MWWorld::ActionEquip action (*it);
|
||||||
action.execute(ptr);
|
action.execute(ptr);
|
||||||
|
|
||||||
|
if (ptr.getRefData().getHandle() == "player" && !ptr.getClass().getScript(ptr).empty())
|
||||||
|
ptr.getRefData().getLocals().setVarByInt(ptr.getClass().getScript(ptr), "onpcequip", 1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -745,6 +745,7 @@ namespace MWScript
|
||||||
MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr (targetId, false);
|
MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr (targetId, false);
|
||||||
|
|
||||||
MWMechanics::CastSpell cast(ptr, target);
|
MWMechanics::CastSpell cast(ptr, target);
|
||||||
|
cast.mHitPosition = Ogre::Vector3(target.getRefData().getPosition().pos);
|
||||||
cast.cast(spell);
|
cast.cast(spell);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -761,6 +762,7 @@ namespace MWScript
|
||||||
runtime.pop();
|
runtime.pop();
|
||||||
|
|
||||||
MWMechanics::CastSpell cast(ptr, ptr);
|
MWMechanics::CastSpell cast(ptr, ptr);
|
||||||
|
cast.mHitPosition = Ogre::Vector3(ptr.getRefData().getPosition().pos);
|
||||||
cast.cast(spell);
|
cast.cast(spell);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -640,6 +640,15 @@ namespace MWSound
|
||||||
mListenerUp = up;
|
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
|
// Default readAll implementation, for decoders that can't do anything
|
||||||
// better
|
// better
|
||||||
void Sound_Decoder::readAll(std::vector<char> &output)
|
void Sound_Decoder::readAll(std::vector<char> &output)
|
||||||
|
|
|
@ -148,6 +148,8 @@ namespace MWSound
|
||||||
virtual void update(float duration);
|
virtual void update(float duration);
|
||||||
|
|
||||||
virtual void setListenerPosDir(const Ogre::Vector3 &pos, const Ogre::Vector3 &dir, const Ogre::Vector3 &up);
|
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);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,9 +19,9 @@ namespace MWWorld
|
||||||
void ActionEquip::executeImp (const Ptr& actor)
|
void ActionEquip::executeImp (const Ptr& actor)
|
||||||
{
|
{
|
||||||
MWWorld::Ptr object = getTarget();
|
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
|
// display error message if the player tried to equip something
|
||||||
if (!result.second.empty() && actor == MWBase::Environment::get().getWorld()->getPlayerPtr())
|
if (!result.second.empty() && actor == MWBase::Environment::get().getWorld()->getPlayerPtr())
|
||||||
|
|
|
@ -8,6 +8,7 @@ namespace MWWorld
|
||||||
void ActionTrap::executeImp(const Ptr &actor)
|
void ActionTrap::executeImp(const Ptr &actor)
|
||||||
{
|
{
|
||||||
MWMechanics::CastSpell cast(mTrapSource, actor);
|
MWMechanics::CastSpell cast(mTrapSource, actor);
|
||||||
|
cast.mHitPosition = Ogre::Vector3(actor.getRefData().getPosition().pos);
|
||||||
cast.cast(mSpellId);
|
cast.cast(mSpellId);
|
||||||
|
|
||||||
mTrapSource.getCellRef().mTrap = "";
|
mTrapSource.getCellRef().mTrap = "";
|
||||||
|
|
|
@ -92,6 +92,11 @@ namespace MWWorld
|
||||||
throw std::runtime_error("class cannot hit");
|
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
|
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");
|
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");
|
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
|
void Class::lock (const Ptr& ptr, int lockLevel) const
|
||||||
{
|
{
|
||||||
throw std::runtime_error ("class does not support locking");
|
throw std::runtime_error ("class does not support locking");
|
||||||
|
|
|
@ -128,6 +128,10 @@ namespace MWWorld
|
||||||
/// actor responsible for the attack, and \a successful specifies if the hit is
|
/// actor responsible for the attack, and \a successful specifies if the hit is
|
||||||
/// successful or not.
|
/// 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;
|
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
|
///< 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
|
/// 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
|
///< Return inventory store or throw an exception, if class does not have a
|
||||||
/// inventory store (default implementation: throw an exceoption)
|
/// 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;
|
virtual void lock (const Ptr& ptr, int lockLevel) const;
|
||||||
///< Lock object (default implementation: throw an exception)
|
///< Lock object (default implementation: throw an exception)
|
||||||
|
|
||||||
|
|
|
@ -62,6 +62,8 @@ namespace MWWorld
|
||||||
|
|
||||||
virtual ~ContainerStore();
|
virtual ~ContainerStore();
|
||||||
|
|
||||||
|
virtual ContainerStore* clone() { return new ContainerStore(*this); }
|
||||||
|
|
||||||
ContainerStoreIterator begin (int mask = Type_All);
|
ContainerStoreIterator begin (int mask = Type_All);
|
||||||
|
|
||||||
ContainerStoreIterator end();
|
ContainerStoreIterator end();
|
||||||
|
|
|
@ -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
|
// Auto-equip items if an armor/clothing or weapon item is added, but not for the player nor werewolves
|
||||||
if ((actorPtr.getRefData().getHandle() != "player")
|
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())
|
&& !actorPtr.getClass().getCreatureStats(actorPtr).isDead())
|
||||||
{
|
{
|
||||||
std::string type = itemPtr.getTypeName();
|
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,
|
// If an armor/clothing item is removed, try to find a replacement,
|
||||||
// but not for the player nor werewolves.
|
// but not for the player nor werewolves.
|
||||||
if ((actor.getRefData().getHandle() != "player")
|
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();
|
std::string type = item.getTypeName();
|
||||||
if (((type == typeid(ESM::Armor).name()) || (type == typeid(ESM::Clothing).name()))
|
if (((type == typeid(ESM::Armor).name()) || (type == typeid(ESM::Clothing).name()))
|
||||||
|
|
|
@ -113,6 +113,8 @@ namespace MWWorld
|
||||||
|
|
||||||
InventoryStore& operator= (const InventoryStore& store);
|
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);
|
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)
|
///< 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).
|
/// Auto-equip items if specific conditions are fulfilled (see the implementation).
|
||||||
|
|
|
@ -487,7 +487,6 @@ namespace MWWorld
|
||||||
insertCellRefList(mRendering, cell.mContainers, cell, *mPhysics, rescale, loadingListener);
|
insertCellRefList(mRendering, cell.mContainers, cell, *mPhysics, rescale, loadingListener);
|
||||||
insertCellRefList(mRendering, cell.mDoors, cell, *mPhysics, rescale, loadingListener);
|
insertCellRefList(mRendering, cell.mDoors, cell, *mPhysics, rescale, loadingListener);
|
||||||
insertCellRefList(mRendering, cell.mIngreds, 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.mItemLists, cell, *mPhysics, rescale, loadingListener);
|
||||||
insertCellRefList(mRendering, cell.mLights, cell, *mPhysics, rescale, loadingListener);
|
insertCellRefList(mRendering, cell.mLights, cell, *mPhysics, rescale, loadingListener);
|
||||||
insertCellRefList(mRendering, cell.mLockpicks, 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)
|
// Load NPCs and creatures _after_ everything else (important for adjustPosition to work correctly)
|
||||||
insertCellRefList(mRendering, cell.mCreatures, cell, *mPhysics, rescale, loadingListener);
|
insertCellRefList(mRendering, cell.mCreatures, cell, *mPhysics, rescale, loadingListener);
|
||||||
insertCellRefList(mRendering, cell.mNpcs, 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)
|
void Scene::addObjectToScene (const Ptr& ptr)
|
||||||
|
|
|
@ -56,7 +56,7 @@ void Store<ESM::Cell>::load(ESM::ESMReader &esm, const std::string &id)
|
||||||
// copy list into new cell
|
// copy list into new cell
|
||||||
cell->mContextList = oldcell->mContextList;
|
cell->mContextList = oldcell->mContextList;
|
||||||
// have new cell replace old cell
|
// have new cell replace old cell
|
||||||
*oldcell = *cell;
|
ESM::Cell::merge(oldcell, cell);
|
||||||
} else
|
} else
|
||||||
mInt[idLower] = *cell;
|
mInt[idLower] = *cell;
|
||||||
}
|
}
|
||||||
|
@ -83,7 +83,7 @@ void Store<ESM::Cell>::load(ESM::ESMReader &esm, const std::string &id)
|
||||||
}
|
}
|
||||||
cell->mMovedRefs = oldcell->mMovedRefs;
|
cell->mMovedRefs = oldcell->mMovedRefs;
|
||||||
// have new cell replace old cell
|
// have new cell replace old cell
|
||||||
*oldcell = *cell;
|
ESM::Cell::merge(oldcell, cell);
|
||||||
} else
|
} else
|
||||||
mExt[std::make_pair(cell->mData.mX, cell->mData.mY)] = *cell;
|
mExt[std::make_pair(cell->mData.mX, cell->mData.mY)] = *cell;
|
||||||
}
|
}
|
||||||
|
|
|
@ -821,6 +821,8 @@ namespace MWWorld
|
||||||
if(anim != NULL)
|
if(anim != NULL)
|
||||||
{
|
{
|
||||||
Ogre::Node *node = anim->getNode("Head");
|
Ogre::Node *node = anim->getNode("Head");
|
||||||
|
if (node == NULL)
|
||||||
|
node = anim->getNode("Bip01 Head");
|
||||||
if(node != NULL)
|
if(node != NULL)
|
||||||
pos += node->_getDerivedPosition();
|
pos += node->_getDerivedPosition();
|
||||||
}
|
}
|
||||||
|
@ -903,6 +905,7 @@ namespace MWWorld
|
||||||
MWWorld::Class::get(ptr).copyToCell(ptr, newCell, pos);
|
MWWorld::Class::get(ptr).copyToCell(ptr, newCell, pos);
|
||||||
|
|
||||||
mRendering->updateObjectCell(ptr, copy);
|
mRendering->updateObjectCell(ptr, copy);
|
||||||
|
MWBase::Environment::get().getSoundManager()->updatePtr (ptr, copy);
|
||||||
|
|
||||||
MWBase::MechanicsManager *mechMgr = MWBase::Environment::get().getMechanicsManager();
|
MWBase::MechanicsManager *mechMgr = MWBase::Environment::get().getMechanicsManager();
|
||||||
mechMgr->updateCell(ptr, copy);
|
mechMgr->updateCell(ptr, copy);
|
||||||
|
@ -2097,13 +2100,14 @@ namespace MWWorld
|
||||||
void World::castSpell(const Ptr &actor)
|
void World::castSpell(const Ptr &actor)
|
||||||
{
|
{
|
||||||
MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor);
|
MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor);
|
||||||
InventoryStore& inv = actor.getClass().getInventoryStore(actor);
|
|
||||||
|
|
||||||
MWWorld::Ptr target = getFacedObject();
|
MWWorld::Ptr target = getFacedObject();
|
||||||
|
|
||||||
std::string selectedSpell = stats.getSpells().getSelectedSpell();
|
std::string selectedSpell = stats.getSpells().getSelectedSpell();
|
||||||
|
|
||||||
MWMechanics::CastSpell cast(actor, target);
|
MWMechanics::CastSpell cast(actor, target);
|
||||||
|
if (!target.isEmpty())
|
||||||
|
cast.mHitPosition = Ogre::Vector3(target.getRefData().getPosition().pos);
|
||||||
|
|
||||||
if (!selectedSpell.empty())
|
if (!selectedSpell.empty())
|
||||||
{
|
{
|
||||||
|
@ -2111,9 +2115,11 @@ namespace MWWorld
|
||||||
|
|
||||||
cast.cast(spell);
|
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.mId = id;
|
||||||
state.mActorHandle = actor.getRefData().getHandle();
|
state.mActorHandle = actor.getRefData().getHandle();
|
||||||
state.mSpeed = speed;
|
state.mSpeed = speed;
|
||||||
state.mEffects = effects;
|
|
||||||
state.mStack = stack;
|
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();
|
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;
|
mProjectiles[ptr] = state;
|
||||||
}
|
}
|
||||||
|
@ -2186,6 +2199,7 @@ namespace MWWorld
|
||||||
{
|
{
|
||||||
if (!mWorldScene->isCellActive(*it->first.getCell()))
|
if (!mWorldScene->isCellActive(*it->first.getCell()))
|
||||||
{
|
{
|
||||||
|
deleteObject(it->first);
|
||||||
mProjectiles.erase(it++);
|
mProjectiles.erase(it++);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -2218,11 +2232,10 @@ namespace MWWorld
|
||||||
if (obstacle == ptr)
|
if (obstacle == ptr)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
explode = true;
|
|
||||||
|
|
||||||
MWWorld::Ptr caster = searchPtrViaHandle(it->second.mActorHandle);
|
MWWorld::Ptr caster = searchPtrViaHandle(it->second.mActorHandle);
|
||||||
if (caster.isEmpty())
|
if (caster.isEmpty())
|
||||||
caster = obstacle;
|
caster = obstacle;
|
||||||
|
|
||||||
if (obstacle.isEmpty())
|
if (obstacle.isEmpty())
|
||||||
{
|
{
|
||||||
// Terrain
|
// Terrain
|
||||||
|
@ -2230,19 +2243,23 @@ namespace MWWorld
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
MWMechanics::CastSpell cast(caster, obstacle);
|
MWMechanics::CastSpell cast(caster, obstacle);
|
||||||
cast.mStack = it->second.mStack;
|
cast.mHitPosition = pos;
|
||||||
cast.mId = it->second.mId;
|
cast.mId = it->second.mId;
|
||||||
cast.mSourceName = it->second.mSourceName;
|
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);
|
explode = true;
|
||||||
mProjectiles.erase(it++);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (explode)
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2282,7 +2299,7 @@ namespace MWWorld
|
||||||
void World::breakInvisibility(const Ptr &actor)
|
void World::breakInvisibility(const Ptr &actor)
|
||||||
{
|
{
|
||||||
actor.getClass().getCreatureStats(actor).getActiveSpells().purgeEffect(ESM::MagicEffect::Invisibility);
|
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);
|
actor.getClass().getInventoryStore(actor).purgeEffect(ESM::MagicEffect::Invisibility);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2638,4 +2655,67 @@ namespace MWWorld
|
||||||
|
|
||||||
mRendering->spawnEffect(model, texture, worldPosition);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -552,6 +552,9 @@ namespace MWWorld
|
||||||
|
|
||||||
/// Spawn a blood effect for \a ptr at \a worldPosition
|
/// Spawn a blood effect for \a ptr at \a worldPosition
|
||||||
virtual void spawnBloodEffect (const MWWorld::Ptr& ptr, const Ogre::Vector3& 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);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -47,9 +47,13 @@ void Cell::load(ESMReader &esm, bool saveContext)
|
||||||
esm.getHT(waterl);
|
esm.getHT(waterl);
|
||||||
mWater = (float) waterl;
|
mWater = (float) waterl;
|
||||||
mWaterInt = true;
|
mWaterInt = true;
|
||||||
|
mHasWaterLevelRecord = true;
|
||||||
}
|
}
|
||||||
else if (esm.isNextSub("WHGT"))
|
else if (esm.isNextSub("WHGT"))
|
||||||
|
{
|
||||||
esm.getHT(mWater);
|
esm.getHT(mWater);
|
||||||
|
mHasWaterLevelRecord = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Quasi-exterior cells have a region (which determines the
|
// Quasi-exterior cells have a region (which determines the
|
||||||
// weather), pure interior cells have ambient lighting
|
// weather), pure interior cells have ambient lighting
|
||||||
|
@ -94,7 +98,7 @@ void Cell::save(ESMWriter &esm) const
|
||||||
esm.writeHNT("DATA", mData, 12);
|
esm.writeHNT("DATA", mData, 12);
|
||||||
if (mData.mFlags & Interior)
|
if (mData.mFlags & Interior)
|
||||||
{
|
{
|
||||||
if (mWater != -1) {
|
if (mHasWaterLevelRecord) {
|
||||||
if (mWaterInt) {
|
if (mWaterInt) {
|
||||||
int water =
|
int water =
|
||||||
(mWater >= 0) ? (int) (mWater + 0.5) : (int) (mWater - 0.5);
|
(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.mFog = 0;
|
||||||
mAmbi.mFogDensity = 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,7 +77,10 @@ struct Cell
|
||||||
float mFogDensity;
|
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
|
// Interior cells are indexed by this (it's the 'id'), for exterior
|
||||||
// cells it is optional.
|
// cells it is optional.
|
||||||
|
@ -90,6 +93,7 @@ struct Cell
|
||||||
DATAstruct mData;
|
DATAstruct mData;
|
||||||
AMBIstruct mAmbi;
|
AMBIstruct mAmbi;
|
||||||
float mWater; // Water level
|
float mWater; // Water level
|
||||||
|
bool mHasWaterLevelRecord;
|
||||||
bool mWaterInt;
|
bool mWaterInt;
|
||||||
int mMapColor;
|
int mMapColor;
|
||||||
int mNAM0;
|
int mNAM0;
|
||||||
|
|
|
@ -70,7 +70,7 @@ struct DialInfo
|
||||||
// Sound and text associated with this item
|
// Sound and text associated with this item
|
||||||
std::string mSound, mResponse;
|
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
|
// selected
|
||||||
std::string mResultScript;
|
std::string mResultScript;
|
||||||
|
|
||||||
|
|
|
@ -917,6 +917,8 @@ class NIFObjectLoader
|
||||||
{
|
{
|
||||||
int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, ctrl->target->recIndex);
|
int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, ctrl->target->recIndex);
|
||||||
Ogre::Bone *trgtbone = scene->mSkelBase->getSkeleton()->getBone(trgtid);
|
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::ControllerValueRealPtr srcval((animflags&Nif::NiNode::AnimFlag_AutoPlay) ?
|
||||||
Ogre::ControllerManager::getSingleton().getFrameTimeSource() :
|
Ogre::ControllerManager::getSingleton().getFrameTimeSource() :
|
||||||
Ogre::ControllerValueRealPtr());
|
Ogre::ControllerValueRealPtr());
|
||||||
|
|
|
@ -433,6 +433,7 @@ void QuadTreeNode::prepareForCompositeMap(Ogre::TRect<float> area)
|
||||||
LayerInfo info;
|
LayerInfo info;
|
||||||
info.mDiffuseMap = "textures\\_land_default.dds";
|
info.mDiffuseMap = "textures\\_land_default.dds";
|
||||||
info.mParallax = false;
|
info.mParallax = false;
|
||||||
|
info.mSpecular = false;
|
||||||
layer.push_back(info);
|
layer.push_back(info);
|
||||||
matGen.setLayerList(layer);
|
matGen.setLayerList(layer);
|
||||||
makeQuad(sceneMgr, area.left, area.top, area.right, area.bottom, matGen.generateForCompositeMapRTT(Ogre::MaterialPtr()));
|
makeQuad(sceneMgr, area.left, area.top, area.right, area.bottom, matGen.generateForCompositeMapRTT(Ogre::MaterialPtr()));
|
||||||
|
|
Loading…
Reference in a new issue