forked from teamnwah/openmw-tes3coop
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
|
||||
drawstate spells activespells npcstats aipackage aisequence alchemy aiwander aitravel aifollow
|
||||
aiescort aiactivate aicombat repair enchanting pathfinding security spellsuccess spellcasting
|
||||
disease pickpocket levelledlist
|
||||
disease pickpocket levelledlist combat
|
||||
)
|
||||
|
||||
add_openmw_dir (mwbase
|
||||
|
|
|
@ -509,6 +509,9 @@ void OMW::Engine::activate()
|
|||
if (ptr.isEmpty())
|
||||
return;
|
||||
|
||||
if (ptr.getClass().getName(ptr) == "") // objects without name presented to user can never be activated
|
||||
return;
|
||||
|
||||
MWScript::InterpreterContext interpreterContext (&ptr.getRefData().getLocals(), ptr);
|
||||
|
||||
boost::shared_ptr<MWWorld::Action> action =
|
||||
|
|
|
@ -157,6 +157,8 @@ namespace MWBase
|
|||
|
||||
virtual void toggleAI() = 0;
|
||||
virtual bool isAIActive() = 0;
|
||||
|
||||
virtual void getObjectsInRange (const Ogre::Vector3& position, float radius, std::vector<MWWorld::Ptr>& objects) = 0;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -147,6 +147,8 @@ namespace MWBase
|
|||
virtual void update(float duration) = 0;
|
||||
|
||||
virtual void setListenerPosDir(const Ogre::Vector3 &pos, const Ogre::Vector3 &dir, const Ogre::Vector3 &up) = 0;
|
||||
|
||||
virtual void updatePtr (const MWWorld::Ptr& old, const MWWorld::Ptr& updated) = 0;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -225,7 +225,7 @@ namespace MWBase
|
|||
|
||||
/// Returns a pointer to the object the provided object would hit (if within the
|
||||
/// specified distance), and the point where the hit occurs. This will attempt to
|
||||
/// use the "Head" node as a basis.
|
||||
/// use the "Head" node, or alternatively the "Bip01 Head" node as a basis.
|
||||
virtual std::pair<MWWorld::Ptr,Ogre::Vector3> getHitContact(const MWWorld::Ptr &ptr, float distance) = 0;
|
||||
|
||||
virtual void adjustPosition (const MWWorld::Ptr& ptr) = 0;
|
||||
|
@ -466,6 +466,9 @@ namespace MWBase
|
|||
|
||||
/// Spawn a blood effect for \a ptr at \a worldPosition
|
||||
virtual void spawnBloodEffect (const MWWorld::Ptr& ptr, const Ogre::Vector3& worldPosition) = 0;
|
||||
|
||||
virtual void explodeSpell (const Ogre::Vector3& origin, const MWWorld::Ptr& object, const ESM::EffectList& effects,
|
||||
const MWWorld::Ptr& caster, const std::string& id, const std::string& sourceName) = 0;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -289,7 +289,7 @@ namespace MWClass
|
|||
|
||||
std::pair<int, std::string> Armor::canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const
|
||||
{
|
||||
MWWorld::InventoryStore& invStore = MWWorld::Class::get(npc).getInventoryStore(npc);
|
||||
MWWorld::InventoryStore& invStore = npc.getClass().getInventoryStore(npc);
|
||||
|
||||
if (ptr.getCellRef().mCharge == 0)
|
||||
return std::make_pair(0, "#{sInventoryMessage1}");
|
||||
|
@ -300,20 +300,23 @@ namespace MWClass
|
|||
if (slots_.first.empty())
|
||||
return std::make_pair(0, "");
|
||||
|
||||
std::string npcRace = npc.get<ESM::NPC>()->mBase->mRace;
|
||||
|
||||
// Beast races cannot equip shoes / boots, or full helms (head part vs hair part)
|
||||
const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(npcRace);
|
||||
if(race->mData.mFlags & ESM::Race::Beast)
|
||||
if (npc.getClass().isNpc())
|
||||
{
|
||||
std::vector<ESM::PartReference> parts = ptr.get<ESM::Armor>()->mBase->mParts.mParts;
|
||||
std::string npcRace = npc.get<ESM::NPC>()->mBase->mRace;
|
||||
|
||||
for(std::vector<ESM::PartReference>::iterator itr = parts.begin(); itr != parts.end(); ++itr)
|
||||
// Beast races cannot equip shoes / boots, or full helms (head part vs hair part)
|
||||
const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(npcRace);
|
||||
if(race->mData.mFlags & ESM::Race::Beast)
|
||||
{
|
||||
if((*itr).mPart == ESM::PRT_Head)
|
||||
return std::make_pair(0, "#{sNotifyMessage13}");
|
||||
if((*itr).mPart == ESM::PRT_LFoot || (*itr).mPart == ESM::PRT_RFoot)
|
||||
return std::make_pair(0, "#{sNotifyMessage14}");
|
||||
std::vector<ESM::PartReference> parts = ptr.get<ESM::Armor>()->mBase->mParts.mParts;
|
||||
|
||||
for(std::vector<ESM::PartReference>::iterator itr = parts.begin(); itr != parts.end(); ++itr)
|
||||
{
|
||||
if((*itr).mPart == ESM::PRT_Head)
|
||||
return std::make_pair(0, "#{sNotifyMessage13}");
|
||||
if((*itr).mPart == ESM::PRT_LFoot || (*itr).mPart == ESM::PRT_RFoot)
|
||||
return std::make_pair(0, "#{sNotifyMessage14}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -238,20 +238,23 @@ namespace MWClass
|
|||
if (slots_.first.empty())
|
||||
return std::make_pair(0, "");
|
||||
|
||||
std::string npcRace = npc.get<ESM::NPC>()->mBase->mRace;
|
||||
|
||||
// Beast races cannot equip shoes / boots, or full helms (head part vs hair part)
|
||||
const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(npcRace);
|
||||
if(race->mData.mFlags & ESM::Race::Beast)
|
||||
if (npc.getClass().isNpc())
|
||||
{
|
||||
std::vector<ESM::PartReference> parts = ptr.get<ESM::Clothing>()->mBase->mParts.mParts;
|
||||
std::string npcRace = npc.get<ESM::NPC>()->mBase->mRace;
|
||||
|
||||
for(std::vector<ESM::PartReference>::iterator itr = parts.begin(); itr != parts.end(); ++itr)
|
||||
// Beast races cannot equip shoes / boots, or full helms (head part vs hair part)
|
||||
const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(npcRace);
|
||||
if(race->mData.mFlags & ESM::Race::Beast)
|
||||
{
|
||||
if((*itr).mPart == ESM::PRT_Head)
|
||||
return std::make_pair(0, "#{sNotifyMessage13}");
|
||||
if((*itr).mPart == ESM::PRT_LFoot || (*itr).mPart == ESM::PRT_RFoot)
|
||||
return std::make_pair(0, "#{sNotifyMessage15}");
|
||||
std::vector<ESM::PartReference> parts = ptr.get<ESM::Clothing>()->mBase->mParts.mParts;
|
||||
|
||||
for(std::vector<ESM::PartReference>::iterator itr = parts.begin(); itr != parts.end(); ++itr)
|
||||
{
|
||||
if((*itr).mPart == ESM::PRT_Head)
|
||||
return std::make_pair(0, "#{sNotifyMessage13}");
|
||||
if((*itr).mPart == ESM::PRT_LFoot || (*itr).mPart == ESM::PRT_RFoot)
|
||||
return std::make_pair(0, "#{sNotifyMessage15}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include "../mwmechanics/creaturestats.hpp"
|
||||
#include "../mwmechanics/magiceffects.hpp"
|
||||
#include "../mwmechanics/movement.hpp"
|
||||
#include "../mwmechanics/spellcasting.hpp"
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/mechanicsmanager.hpp"
|
||||
|
@ -26,22 +27,30 @@
|
|||
|
||||
#include "../mwgui/tooltips.hpp"
|
||||
|
||||
#include "../mwworld/inventorystore.hpp"
|
||||
|
||||
#include "../mwmechanics/npcstats.hpp"
|
||||
#include "../mwmechanics/combat.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
struct CustomData : public MWWorld::CustomData
|
||||
{
|
||||
MWMechanics::CreatureStats mCreatureStats;
|
||||
MWWorld::ContainerStore mContainerStore;
|
||||
MWWorld::ContainerStore* mContainerStore; // may be InventoryStore for some creatures
|
||||
MWMechanics::Movement mMovement;
|
||||
|
||||
virtual MWWorld::CustomData *clone() const;
|
||||
|
||||
CustomData() : mContainerStore(0) {}
|
||||
virtual ~CustomData() { delete mContainerStore; }
|
||||
};
|
||||
|
||||
MWWorld::CustomData *CustomData::clone() const
|
||||
{
|
||||
return new CustomData (*this);
|
||||
CustomData* cloned = new CustomData (*this);
|
||||
cloned->mContainerStore = mContainerStore->clone();
|
||||
return cloned;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -106,15 +115,23 @@ namespace MWClass
|
|||
data->mCreatureStats.getSpells().add (*iter);
|
||||
|
||||
// inventory
|
||||
data->mContainerStore.fill(ref->mBase->mInventory, getId(ptr), "",
|
||||
if (ref->mBase->mFlags & ESM::Creature::Weapon)
|
||||
data->mContainerStore = new MWWorld::InventoryStore();
|
||||
else
|
||||
data->mContainerStore = new MWWorld::ContainerStore();
|
||||
|
||||
// store
|
||||
ptr.getRefData().setCustomData (data.release());
|
||||
|
||||
getContainerStore(ptr).fill(ref->mBase->mInventory, getId(ptr), "",
|
||||
MWBase::Environment::get().getWorld()->getStore());
|
||||
|
||||
// TODO: this is not quite correct, in vanilla the merchant's gold pool is not available in his inventory.
|
||||
// (except for gold you gave him)
|
||||
data->mContainerStore.add(MWWorld::ContainerStore::sGoldId, ref->mBase->mData.mGold, ptr);
|
||||
getContainerStore(ptr).add(MWWorld::ContainerStore::sGoldId, ref->mBase->mData.mGold, ptr);
|
||||
|
||||
// store
|
||||
ptr.getRefData().setCustomData (data.release());
|
||||
if (ref->mBase->mFlags & ESM::Creature::Weapon)
|
||||
getInventoryStore(ptr).autoEquip(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -133,8 +150,10 @@ namespace MWClass
|
|||
|
||||
void Creature::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const
|
||||
{
|
||||
MWWorld::LiveCellRef<ESM::Creature> *ref = ptr.get<ESM::Creature>();
|
||||
|
||||
MWRender::Actors& actors = renderingInterface.getActors();
|
||||
actors.insertCreature(ptr);
|
||||
actors.insertCreature(ptr, ref->mBase->mFlags & ESM::Creature::Weapon);
|
||||
}
|
||||
|
||||
void Creature::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const
|
||||
|
@ -178,9 +197,40 @@ namespace MWClass
|
|||
{
|
||||
MWWorld::LiveCellRef<ESM::Creature> *ref =
|
||||
ptr.get<ESM::Creature>();
|
||||
const MWWorld::Store<ESM::GameSetting> &gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
|
||||
MWMechanics::CreatureStats &stats = getCreatureStats(ptr);
|
||||
|
||||
// Get the weapon used (if hand-to-hand, weapon = inv.end())
|
||||
MWWorld::Ptr weapon;
|
||||
if (ptr.getClass().hasInventoryStore(ptr))
|
||||
{
|
||||
MWWorld::InventoryStore &inv = getInventoryStore(ptr);
|
||||
MWWorld::ContainerStoreIterator weaponslot = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
|
||||
if (weaponslot != inv.end() && weaponslot->getTypeName() == typeid(ESM::Weapon).name())
|
||||
weapon = *weaponslot;
|
||||
}
|
||||
|
||||
// Reduce fatigue
|
||||
// somewhat of a guess, but using the weapon weight makes sense
|
||||
const float fFatigueAttackBase = gmst.find("fFatigueAttackBase")->getFloat();
|
||||
const float fFatigueAttackMult = gmst.find("fFatigueAttackMult")->getFloat();
|
||||
const float fWeaponFatigueMult = gmst.find("fWeaponFatigueMult")->getFloat();
|
||||
MWMechanics::DynamicStat<float> fatigue = stats.getFatigue();
|
||||
const float normalizedEncumbrance = getEncumbrance(ptr) / getCapacity(ptr);
|
||||
float fatigueLoss = fFatigueAttackBase + normalizedEncumbrance * fFatigueAttackMult;
|
||||
if (!weapon.isEmpty())
|
||||
fatigueLoss += weapon.getClass().getWeight(weapon) * stats.getAttackStrength() * fWeaponFatigueMult;
|
||||
fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss);
|
||||
stats.setFatigue(fatigue);
|
||||
|
||||
// TODO: where is the distance defined?
|
||||
std::pair<MWWorld::Ptr, Ogre::Vector3> result = MWBase::Environment::get().getWorld()->getHitContact(ptr, 100);
|
||||
float dist = 200.f;
|
||||
if (!weapon.isEmpty())
|
||||
{
|
||||
const float fCombatDistance = gmst.find("fCombatDistance")->getFloat();
|
||||
dist = fCombatDistance * weapon.get<ESM::Weapon>()->mBase->mData.mReach;
|
||||
}
|
||||
std::pair<MWWorld::Ptr, Ogre::Vector3> result = MWBase::Environment::get().getWorld()->getHitContact(ptr, dist);
|
||||
if (result.first.isEmpty())
|
||||
return; // Didn't hit anything
|
||||
|
||||
|
@ -191,7 +241,6 @@ namespace MWClass
|
|||
|
||||
Ogre::Vector3 hitPosition = result.second;
|
||||
|
||||
MWMechanics::CreatureStats &stats = getCreatureStats(ptr);
|
||||
MWMechanics::CreatureStats &otherstats = victim.getClass().getCreatureStats(victim);
|
||||
const MWMechanics::MagicEffects &mageffects = stats.getMagicEffects();
|
||||
float hitchance = ref->mBase->mData.mCombat +
|
||||
|
@ -226,10 +275,62 @@ namespace MWClass
|
|||
break;
|
||||
}
|
||||
|
||||
// I think this should be random, since attack1-3 animations don't have an attack strength like NPCs do
|
||||
float damage = min + (max - min) * ::rand()/(RAND_MAX+1.0);
|
||||
|
||||
// TODO: do not do this if the attack is blocked
|
||||
MWBase::Environment::get().getWorld()->spawnBloodEffect(victim, hitPosition);
|
||||
if (!weapon.isEmpty())
|
||||
{
|
||||
const bool weaphashealth = get(weapon).hasItemHealth(weapon);
|
||||
const unsigned char *attack = NULL;
|
||||
if(type == MWMechanics::CreatureStats::AT_Chop)
|
||||
attack = weapon.get<ESM::Weapon>()->mBase->mData.mChop;
|
||||
else if(type == MWMechanics::CreatureStats::AT_Slash)
|
||||
attack = weapon.get<ESM::Weapon>()->mBase->mData.mSlash;
|
||||
else if(type == MWMechanics::CreatureStats::AT_Thrust)
|
||||
attack = weapon.get<ESM::Weapon>()->mBase->mData.mThrust;
|
||||
if(attack)
|
||||
{
|
||||
float weaponDamage = attack[0] + ((attack[1]-attack[0])*stats.getAttackStrength());
|
||||
weaponDamage *= 0.5f + (stats.getAttribute(ESM::Attribute::Luck).getModified() / 100.0f);
|
||||
if(weaphashealth)
|
||||
{
|
||||
int weapmaxhealth = weapon.get<ESM::Weapon>()->mBase->mData.mHealth;
|
||||
if(weapon.getCellRef().mCharge == -1)
|
||||
weapon.getCellRef().mCharge = weapmaxhealth;
|
||||
weaponDamage *= float(weapon.getCellRef().mCharge) / weapmaxhealth;
|
||||
}
|
||||
|
||||
if (!MWBase::Environment::get().getWorld()->getGodModeState())
|
||||
weapon.getCellRef().mCharge -= std::min(std::max(1,
|
||||
(int)(damage * gmst.find("fWeaponDamageMult")->getFloat())), weapon.getCellRef().mCharge);
|
||||
|
||||
// Weapon broken? unequip it
|
||||
if (weapon.getCellRef().mCharge == 0)
|
||||
weapon = *getInventoryStore(ptr).unequipItem(weapon, ptr);
|
||||
|
||||
damage += weaponDamage;
|
||||
}
|
||||
|
||||
// Apply "On hit" enchanted weapons
|
||||
std::string enchantmentName = !weapon.isEmpty() ? weapon.getClass().getEnchantment(weapon) : "";
|
||||
if (!enchantmentName.empty())
|
||||
{
|
||||
const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().find(
|
||||
enchantmentName);
|
||||
if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes)
|
||||
{
|
||||
MWMechanics::CastSpell cast(ptr, victim);
|
||||
cast.mHitPosition = hitPosition;
|
||||
cast.cast(weapon);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!weapon.isEmpty() && MWMechanics::blockMeleeAttack(ptr, victim, weapon, damage))
|
||||
damage = 0;
|
||||
|
||||
if (damage > 0)
|
||||
MWBase::Environment::get().getWorld()->spawnBloodEffect(victim, hitPosition);
|
||||
|
||||
victim.getClass().onHit(victim, damage, true, MWWorld::Ptr(), ptr, true);
|
||||
}
|
||||
|
@ -258,31 +359,60 @@ namespace MWClass
|
|||
ptr.getRefData().getLocals().setVarByInt(script, "onpchitme", 1);
|
||||
}
|
||||
|
||||
// Check for knockdown
|
||||
float agilityTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() * fKnockDownMult->getFloat();
|
||||
float knockdownTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified()
|
||||
* iKnockDownOddsMult->getInt() * 0.01 + iKnockDownOddsBase->getInt();
|
||||
int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
|
||||
if (ishealth && agilityTerm <= damage && knockdownTerm <= roll)
|
||||
{
|
||||
getCreatureStats(ptr).setKnockedDown(true);
|
||||
if (damage > 0.0f && !object.isEmpty())
|
||||
MWMechanics::resistNormalWeapon(ptr, attacker, object, damage);
|
||||
|
||||
}
|
||||
else
|
||||
getCreatureStats(ptr).setHitRecovery(true); // Is this supposed to always occur?
|
||||
|
||||
if(ishealth)
|
||||
if (damage > 0.f)
|
||||
{
|
||||
if(damage > 0.0f)
|
||||
// Check for knockdown
|
||||
float agilityTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() * fKnockDownMult->getFloat();
|
||||
float knockdownTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified()
|
||||
* iKnockDownOddsMult->getInt() * 0.01 + iKnockDownOddsBase->getInt();
|
||||
int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
|
||||
if (ishealth && agilityTerm <= damage && knockdownTerm <= roll)
|
||||
{
|
||||
getCreatureStats(ptr).setKnockedDown(true);
|
||||
|
||||
}
|
||||
else
|
||||
getCreatureStats(ptr).setHitRecovery(true); // Is this supposed to always occur?
|
||||
|
||||
if(ishealth)
|
||||
{
|
||||
MWBase::Environment::get().getSoundManager()->playSound3D(ptr, "Health Damage", 1.0f, 1.0f);
|
||||
float health = getCreatureStats(ptr).getHealth().getCurrent() - damage;
|
||||
setActorHealth(ptr, health, attacker);
|
||||
float health = getCreatureStats(ptr).getHealth().getCurrent() - damage;
|
||||
setActorHealth(ptr, health, attacker);
|
||||
}
|
||||
else
|
||||
{
|
||||
MWMechanics::DynamicStat<float> fatigue(getCreatureStats(ptr).getFatigue());
|
||||
fatigue.setCurrent(fatigue.getCurrent() - damage, true);
|
||||
getCreatureStats(ptr).setFatigue(fatigue);
|
||||
}
|
||||
}
|
||||
else
|
||||
}
|
||||
|
||||
void Creature::block(const MWWorld::Ptr &ptr) const
|
||||
{
|
||||
MWWorld::InventoryStore& inv = getInventoryStore(ptr);
|
||||
MWWorld::ContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
|
||||
if (shield == inv.end())
|
||||
return;
|
||||
|
||||
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
|
||||
switch(shield->getClass().getEquipmentSkill(*shield))
|
||||
{
|
||||
MWMechanics::DynamicStat<float> fatigue(getCreatureStats(ptr).getFatigue());
|
||||
fatigue.setCurrent(fatigue.getCurrent() - damage, true);
|
||||
getCreatureStats(ptr).setFatigue(fatigue);
|
||||
case ESM::Skill::LightArmor:
|
||||
sndMgr->playSound3D(ptr, "Light Armor Hit", 1.0f, 1.0f);
|
||||
break;
|
||||
case ESM::Skill::MediumArmor:
|
||||
sndMgr->playSound3D(ptr, "Medium Armor Hit", 1.0f, 1.0f);
|
||||
break;
|
||||
case ESM::Skill::HeavyArmor:
|
||||
sndMgr->playSound3D(ptr, "Heavy Armor Hit", 1.0f, 1.0f);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -325,18 +455,33 @@ namespace MWClass
|
|||
return boost::shared_ptr<MWWorld::Action>(new MWWorld::ActionTalk(ptr));
|
||||
}
|
||||
|
||||
MWWorld::ContainerStore& Creature::getContainerStore (const MWWorld::Ptr& ptr)
|
||||
const
|
||||
MWWorld::ContainerStore& Creature::getContainerStore (const MWWorld::Ptr& ptr) const
|
||||
{
|
||||
ensureCustomData (ptr);
|
||||
|
||||
return dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData()).mContainerStore;
|
||||
return *dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData()).mContainerStore;
|
||||
}
|
||||
|
||||
MWWorld::InventoryStore& Creature::getInventoryStore(const MWWorld::Ptr &ptr) const
|
||||
{
|
||||
MWWorld::LiveCellRef<ESM::Creature> *ref = ptr.get<ESM::Creature>();
|
||||
|
||||
if (ref->mBase->mFlags & ESM::Creature::Weapon)
|
||||
return dynamic_cast<MWWorld::InventoryStore&>(getContainerStore(ptr));
|
||||
else
|
||||
throw std::runtime_error("this creature has no inventory store");
|
||||
}
|
||||
|
||||
bool Creature::hasInventoryStore(const MWWorld::Ptr &ptr) const
|
||||
{
|
||||
MWWorld::LiveCellRef<ESM::Creature> *ref = ptr.get<ESM::Creature>();
|
||||
|
||||
return (ref->mBase->mFlags & ESM::Creature::Weapon);
|
||||
}
|
||||
|
||||
std::string Creature::getScript (const MWWorld::Ptr& ptr) const
|
||||
{
|
||||
MWWorld::LiveCellRef<ESM::Creature> *ref =
|
||||
ptr.get<ESM::Creature>();
|
||||
MWWorld::LiveCellRef<ESM::Creature> *ref = ptr.get<ESM::Creature>();
|
||||
|
||||
return ref->mBase->mScript;
|
||||
}
|
||||
|
|
|
@ -56,6 +56,8 @@ namespace MWClass
|
|||
|
||||
virtual void hit(const MWWorld::Ptr& ptr, int type) const;
|
||||
|
||||
virtual void block(const MWWorld::Ptr &ptr) const;
|
||||
|
||||
virtual void onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, bool successful) const;
|
||||
|
||||
virtual void setActorHealth(const MWWorld::Ptr& ptr, float health, const MWWorld::Ptr& attacker) const;
|
||||
|
@ -68,6 +70,11 @@ namespace MWClass
|
|||
const MWWorld::Ptr& ptr) const;
|
||||
///< Return container store
|
||||
|
||||
virtual MWWorld::InventoryStore& getInventoryStore (const MWWorld::Ptr& ptr) const;
|
||||
///< Return inventory store
|
||||
|
||||
virtual bool hasInventoryStore (const MWWorld::Ptr &ptr) const;
|
||||
|
||||
virtual std::string getScript (const MWWorld::Ptr& ptr) const;
|
||||
///< Return name of the script attached to ptr
|
||||
|
||||
|
|
|
@ -3,6 +3,24 @@
|
|||
|
||||
#include <components/esm/loadlevlist.hpp>
|
||||
|
||||
#include "../mwmechanics/levelledlist.hpp"
|
||||
|
||||
#include "../mwworld/customdata.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
struct CustomData : public MWWorld::CustomData
|
||||
{
|
||||
// TODO: save the creature we spawned here
|
||||
virtual MWWorld::CustomData *clone() const;
|
||||
};
|
||||
|
||||
MWWorld::CustomData *CustomData::clone() const
|
||||
{
|
||||
return new CustomData (*this);
|
||||
}
|
||||
}
|
||||
|
||||
namespace MWClass
|
||||
{
|
||||
std::string CreatureLevList::getName (const MWWorld::Ptr& ptr) const
|
||||
|
@ -16,4 +34,33 @@ namespace MWClass
|
|||
|
||||
registerClass (typeid (ESM::CreatureLevList).name(), instance);
|
||||
}
|
||||
|
||||
void CreatureLevList::insertObjectRendering(const MWWorld::Ptr &ptr, MWRender::RenderingInterface &renderingInterface) const
|
||||
{
|
||||
ensureCustomData(ptr);
|
||||
}
|
||||
|
||||
void CreatureLevList::ensureCustomData(const MWWorld::Ptr &ptr) const
|
||||
{
|
||||
if (!ptr.getRefData().getCustomData())
|
||||
{
|
||||
std::auto_ptr<CustomData> data (new CustomData);
|
||||
|
||||
MWWorld::LiveCellRef<ESM::CreatureLevList> *ref =
|
||||
ptr.get<ESM::CreatureLevList>();
|
||||
|
||||
std::string id = MWMechanics::getLevelledItem(ref->mBase, true);
|
||||
|
||||
if (!id.empty())
|
||||
{
|
||||
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
|
||||
MWWorld::ManualRef ref(store, id);
|
||||
ref.getPtr().getCellRef().mPos = ptr.getCellRef().mPos;
|
||||
// TODO: hold on to this for respawn purposes later
|
||||
MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(), *ptr.getCell() , ptr.getCellRef().mPos);
|
||||
}
|
||||
|
||||
ptr.getRefData().setCustomData(data.release());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,8 @@ namespace MWClass
|
|||
{
|
||||
class CreatureLevList : public MWWorld::Class
|
||||
{
|
||||
void ensureCustomData (const MWWorld::Ptr& ptr) const;
|
||||
|
||||
public:
|
||||
|
||||
virtual std::string getName (const MWWorld::Ptr& ptr) const;
|
||||
|
@ -14,6 +16,9 @@ namespace MWClass
|
|||
/// can return an empty string.
|
||||
|
||||
static void registerSelf();
|
||||
|
||||
virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const;
|
||||
///< Add reference into a cell for rendering
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include "../mwmechanics/movement.hpp"
|
||||
#include "../mwmechanics/spellcasting.hpp"
|
||||
#include "../mwmechanics/disease.hpp"
|
||||
#include "../mwmechanics/combat.hpp"
|
||||
|
||||
#include "../mwworld/ptr.hpp"
|
||||
#include "../mwworld/actiontalk.hpp"
|
||||
|
@ -36,9 +37,6 @@
|
|||
|
||||
namespace
|
||||
{
|
||||
const Ogre::Radian kOgrePi (Ogre::Math::PI);
|
||||
const Ogre::Radian kOgrePiOverTwo (Ogre::Math::PI / Ogre::Real(2.0));
|
||||
|
||||
struct CustomData : public MWWorld::CustomData
|
||||
{
|
||||
MWMechanics::NpcStats mNpcStats;
|
||||
|
@ -144,7 +142,7 @@ namespace
|
|||
*
|
||||
* and by adding class, race, specialization bonus.
|
||||
*/
|
||||
void autoCalculateSkills(const ESM::NPC* npc, MWMechanics::NpcStats& npcStats)
|
||||
void autoCalculateSkills(const ESM::NPC* npc, MWMechanics::NpcStats& npcStats, const MWWorld::Ptr& ptr)
|
||||
{
|
||||
const ESM::Class *class_ =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Class>().find(npc->mClass);
|
||||
|
@ -193,6 +191,18 @@ namespace
|
|||
majorMultiplier = 1.0f;
|
||||
break;
|
||||
}
|
||||
if (class_->mData.mSkills[k][1] == skillIndex)
|
||||
{
|
||||
// Major skill -> add starting spells for this skill if existing
|
||||
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
|
||||
MWWorld::Store<ESM::Spell>::iterator it = store.get<ESM::Spell>().begin();
|
||||
for (; it != store.get<ESM::Spell>().end(); ++it)
|
||||
{
|
||||
if (it->mData.mFlags & ESM::Spell::F_Autocalc
|
||||
&& MWMechanics::spellSchoolToSkill(MWMechanics::getSpellSchool(&*it, ptr)) == skillIndex)
|
||||
npcStats.getSpells().add(it->mId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// is this skill in the same Specialization as the class?
|
||||
|
@ -243,6 +253,8 @@ namespace MWClass
|
|||
fKnockDownMult = gmst.find("fKnockDownMult");
|
||||
iKnockDownOddsMult = gmst.find("iKnockDownOddsMult");
|
||||
iKnockDownOddsBase = gmst.find("iKnockDownOddsBase");
|
||||
fDamageStrengthBase = gmst.find("fDamageStrengthBase");
|
||||
fDamageStrengthMult = gmst.find("fDamageStrengthMult");
|
||||
|
||||
inited = true;
|
||||
}
|
||||
|
@ -305,7 +317,15 @@ namespace MWClass
|
|||
data->mNpcStats.setReputation(ref->mBase->mNpdt12.mReputation);
|
||||
|
||||
autoCalculateAttributes(ref->mBase, data->mNpcStats);
|
||||
autoCalculateSkills(ref->mBase, data->mNpcStats);
|
||||
autoCalculateSkills(ref->mBase, data->mNpcStats, ptr);
|
||||
}
|
||||
|
||||
// race powers
|
||||
const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(ref->mBase->mRace);
|
||||
for (std::vector<std::string>::const_iterator iter (race->mPowers.mList.begin());
|
||||
iter!=race->mPowers.mList.end(); ++iter)
|
||||
{
|
||||
data->mNpcStats.getSpells().add (*iter);
|
||||
}
|
||||
|
||||
if (data->mNpcStats.getFactionRanks().size())
|
||||
|
@ -450,10 +470,11 @@ namespace MWClass
|
|||
fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss);
|
||||
getCreatureStats(ptr).setFatigue(fatigue);
|
||||
|
||||
|
||||
float dist = 100.0f * (!weapon.isEmpty() ?
|
||||
const float fCombatDistance = gmst.find("fCombatDistance")->getFloat();
|
||||
float dist = fCombatDistance * (!weapon.isEmpty() ?
|
||||
weapon.get<ESM::Weapon>()->mBase->mData.mReach :
|
||||
gmst.find("fHandToHandReach")->getFloat());
|
||||
|
||||
// TODO: Use second to work out the hit angle
|
||||
std::pair<MWWorld::Ptr, Ogre::Vector3> result = world->getHitContact(ptr, dist);
|
||||
MWWorld::Ptr victim = result.first;
|
||||
|
@ -506,7 +527,8 @@ namespace MWClass
|
|||
if(attack)
|
||||
{
|
||||
damage = attack[0] + ((attack[1]-attack[0])*stats.getAttackStrength());
|
||||
damage *= 0.5f + (stats.getAttribute(ESM::Attribute::Luck).getModified() / 100.0f);
|
||||
damage *= fDamageStrengthBase->getFloat() +
|
||||
(stats.getAttribute(ESM::Attribute::Strength).getModified() * fDamageStrengthMult->getFloat() * 0.1);
|
||||
if(weaphashealth)
|
||||
{
|
||||
int weapmaxhealth = weapon.get<ESM::Weapon>()->mBase->mData.mHealth;
|
||||
|
@ -579,33 +601,19 @@ namespace MWClass
|
|||
enchantmentName);
|
||||
if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes)
|
||||
{
|
||||
// Check if we have enough charges
|
||||
const float enchantCost = enchantment->mData.mCost;
|
||||
int eSkill = stats.getSkill(ESM::Skill::Enchant).getModified();
|
||||
const int castCost = std::max(1.f, enchantCost - (enchantCost / 100) * (eSkill - 10));
|
||||
MWMechanics::CastSpell cast(ptr, victim);
|
||||
cast.mHitPosition = hitPosition;
|
||||
bool success = cast.cast(weapon);
|
||||
|
||||
if (weapon.getCellRef().mEnchantmentCharge == -1)
|
||||
weapon.getCellRef().mEnchantmentCharge = enchantment->mData.mCharge;
|
||||
if (weapon.getCellRef().mEnchantmentCharge < castCost)
|
||||
{
|
||||
if (ptr.getRefData().getHandle() == "player")
|
||||
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientCharge}");
|
||||
}
|
||||
else
|
||||
{
|
||||
weapon.getCellRef().mEnchantmentCharge -= castCost;
|
||||
|
||||
MWMechanics::CastSpell cast(ptr, victim);
|
||||
cast.cast(weapon);
|
||||
|
||||
if (ptr.getRefData().getHandle() == "player")
|
||||
skillUsageSucceeded (ptr, ESM::Skill::Enchant, 3);
|
||||
}
|
||||
if (ptr.getRefData().getHandle() == "player" && success)
|
||||
skillUsageSucceeded (ptr, ESM::Skill::Enchant, 3);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: do not do this if the attack is blocked
|
||||
if (healthdmg)
|
||||
if (!weapon.isEmpty() && MWMechanics::blockMeleeAttack(ptr, victim, weapon, damage))
|
||||
damage = 0;
|
||||
|
||||
if (healthdmg && damage > 0)
|
||||
MWBase::Environment::get().getWorld()->spawnBloodEffect(victim, hitPosition);
|
||||
|
||||
othercls.onHit(victim, damage, healthdmg, weapon, ptr, true);
|
||||
|
@ -644,6 +652,9 @@ namespace MWClass
|
|||
if (!attacker.isEmpty())
|
||||
MWMechanics::diseaseContact(ptr, attacker);
|
||||
|
||||
if (damage > 0.0f && !object.isEmpty())
|
||||
MWMechanics::resistNormalWeapon(ptr, attacker, object, damage);
|
||||
|
||||
if(damage > 0.0f)
|
||||
{
|
||||
// 'ptr' is losing health. Play a 'hit' voiced dialog entry if not already saying
|
||||
|
@ -746,6 +757,30 @@ namespace MWClass
|
|||
}
|
||||
}
|
||||
|
||||
void Npc::block(const MWWorld::Ptr &ptr) const
|
||||
{
|
||||
MWWorld::InventoryStore& inv = getInventoryStore(ptr);
|
||||
MWWorld::ContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
|
||||
if (shield == inv.end())
|
||||
return;
|
||||
|
||||
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
|
||||
switch(shield->getClass().getEquipmentSkill(*shield))
|
||||
{
|
||||
case ESM::Skill::LightArmor:
|
||||
sndMgr->playSound3D(ptr, "Light Armor Hit", 1.0f, 1.0f);
|
||||
break;
|
||||
case ESM::Skill::MediumArmor:
|
||||
sndMgr->playSound3D(ptr, "Medium Armor Hit", 1.0f, 1.0f);
|
||||
break;
|
||||
case ESM::Skill::HeavyArmor:
|
||||
sndMgr->playSound3D(ptr, "Heavy Armor Hit", 1.0f, 1.0f);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void Npc::setActorHealth(const MWWorld::Ptr& ptr, float health, const MWWorld::Ptr& attacker) const
|
||||
{
|
||||
MWMechanics::CreatureStats &crstats = getCreatureStats(ptr);
|
||||
|
@ -1252,4 +1287,7 @@ namespace MWClass
|
|||
const ESM::GameSetting *Npc::fKnockDownMult;
|
||||
const ESM::GameSetting *Npc::iKnockDownOddsMult;
|
||||
const ESM::GameSetting *Npc::iKnockDownOddsBase;
|
||||
const ESM::GameSetting *Npc::fDamageStrengthBase;
|
||||
const ESM::GameSetting *Npc::fDamageStrengthMult;
|
||||
|
||||
}
|
||||
|
|
|
@ -36,6 +36,8 @@ namespace MWClass
|
|||
static const ESM::GameSetting *fKnockDownMult;
|
||||
static const ESM::GameSetting *iKnockDownOddsMult;
|
||||
static const ESM::GameSetting *iKnockDownOddsBase;
|
||||
static const ESM::GameSetting *fDamageStrengthBase;
|
||||
static const ESM::GameSetting *fDamageStrengthMult;
|
||||
|
||||
public:
|
||||
|
||||
|
@ -71,10 +73,14 @@ namespace MWClass
|
|||
virtual MWWorld::InventoryStore& getInventoryStore (const MWWorld::Ptr& ptr) const;
|
||||
///< Return inventory store
|
||||
|
||||
virtual bool hasInventoryStore(const MWWorld::Ptr &ptr) const { return true; }
|
||||
|
||||
virtual void hit(const MWWorld::Ptr& ptr, int type) const;
|
||||
|
||||
virtual void onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, bool successful) const;
|
||||
|
||||
virtual void block(const MWWorld::Ptr &ptr) const;
|
||||
|
||||
virtual void setActorHealth(const MWWorld::Ptr& ptr, float health, const MWWorld::Ptr& attacker) const;
|
||||
|
||||
virtual boost::shared_ptr<MWWorld::Action> activate (const MWWorld::Ptr& ptr,
|
||||
|
|
|
@ -420,7 +420,7 @@ namespace MWDialogue
|
|||
MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Dialogue);
|
||||
|
||||
// Apply disposition change to NPC's base disposition
|
||||
if (mActor.getTypeName() == typeid(ESM::NPC).name())
|
||||
if (mActor.getClass().isNpc())
|
||||
{
|
||||
MWMechanics::NpcStats& npcStats = MWWorld::Class::get(mActor).getNpcStats(mActor);
|
||||
npcStats.setBaseDisposition(npcStats.getBaseDisposition() + mPermanentDispositionChange);
|
||||
|
@ -586,7 +586,8 @@ namespace MWDialogue
|
|||
MWBase::WindowManager *winMgr = MWBase::Environment::get().getWindowManager();
|
||||
if(winMgr->getSubtitlesEnabled())
|
||||
winMgr->messageBox(info->mResponse);
|
||||
sndMgr->say(actor, info->mSound);
|
||||
if (!info->mSound.empty())
|
||||
sndMgr->say(actor, info->mSound);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -216,7 +216,7 @@ bool MWDialogue::Filter::testSelectStructNumeric (const SelectWrapper& select) c
|
|||
float ratio = MWWorld::Class::get (player).getCreatureStats (player).getHealth().getCurrent() /
|
||||
MWWorld::Class::get (player).getCreatureStats (player).getHealth().getModified();
|
||||
|
||||
return select.selectCompare (ratio);
|
||||
return select.selectCompare (static_cast<int>(ratio*100));
|
||||
}
|
||||
|
||||
case SelectWrapper::Function_PcDynamicStat:
|
||||
|
@ -536,7 +536,7 @@ bool MWDialogue::Filter::getSelectStructBoolean (const SelectWrapper& select) co
|
|||
|
||||
case SelectWrapper::Function_CreatureTargetted:
|
||||
|
||||
return MWWorld::Class::get (mActor).getCreatureStats (mActor).getCreatureTargetted();
|
||||
return mActor.getClass().getCreatureStats (mActor).getCreatureTargetted();
|
||||
|
||||
case SelectWrapper::Function_Werewolf:
|
||||
|
||||
|
|
|
@ -39,21 +39,24 @@ namespace MWDialogue
|
|||
const ESM::Dialogue *dialogue =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Dialogue>().find (mTopic);
|
||||
|
||||
bool found=false;
|
||||
for (std::vector<ESM::DialInfo>::const_iterator iter (dialogue->mInfo.begin());
|
||||
iter!=dialogue->mInfo.end(); ++iter)
|
||||
if (iter->mData.mDisposition==index && iter->mQuestStatus!=ESM::DialInfo::QS_Name)
|
||||
{
|
||||
mIndex = index;
|
||||
|
||||
if (iter->mQuestStatus==ESM::DialInfo::QS_Finished)
|
||||
mFinished = true;
|
||||
else if (iter->mQuestStatus==ESM::DialInfo::QS_Restart)
|
||||
mFinished = false;
|
||||
|
||||
return;
|
||||
found = true;
|
||||
// Don't return here. Quest status may actually be in a different info record, since we don't merge these (yet?)
|
||||
}
|
||||
|
||||
throw std::runtime_error ("unknown journal index for topic " + mTopic);
|
||||
if (found)
|
||||
mIndex = index;
|
||||
else
|
||||
throw std::runtime_error ("unknown journal index for topic " + mTopic);
|
||||
}
|
||||
|
||||
bool Quest::isFinished() const
|
||||
|
|
|
@ -12,7 +12,7 @@ namespace MWGui
|
|||
|
||||
void CompanionItemModel::copyItem (const ItemStack& item, size_t count)
|
||||
{
|
||||
if (mActor.getTypeName() == typeid(ESM::NPC).name())
|
||||
if (mActor.getClass().isNpc())
|
||||
{
|
||||
MWMechanics::NpcStats& stats = MWWorld::Class::get(mActor).getNpcStats(mActor);
|
||||
stats.modifyProfit(MWWorld::Class::get(item.mBase).getValue(item.mBase) * count);
|
||||
|
@ -23,7 +23,7 @@ namespace MWGui
|
|||
|
||||
void CompanionItemModel::removeItem (const ItemStack& item, size_t count)
|
||||
{
|
||||
if (mActor.getTypeName() == typeid(ESM::NPC).name())
|
||||
if (mActor.getClass().isNpc())
|
||||
{
|
||||
MWMechanics::NpcStats& stats = MWWorld::Class::get(mActor).getNpcStats(mActor);
|
||||
stats.modifyProfit(-MWWorld::Class::get(item.mBase).getValue(item.mBase) * count);
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
#include "../mwbase/soundmanager.hpp"
|
||||
#include "../mwbase/dialoguemanager.hpp"
|
||||
#include "../mwbase/mechanicsmanager.hpp"
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwworld/containerstore.hpp"
|
||||
|
||||
|
@ -296,6 +298,28 @@ namespace MWGui
|
|||
return;
|
||||
}
|
||||
|
||||
// check if the player is attempting to use a soulstone or item that was stolen from this actor
|
||||
if (mPtr != player)
|
||||
{
|
||||
for (int i=0; i<2; ++i)
|
||||
{
|
||||
MWWorld::Ptr item = (i == 0) ? mEnchanting.getOldItem() : mEnchanting.getGem();
|
||||
if (Misc::StringUtils::ciEqual(item.getCellRef().mOwner, mPtr.getCellRef().mRefID))
|
||||
{
|
||||
std::string msg = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("sNotifyMessage49")->getString();
|
||||
if (msg.find("%s") != std::string::npos)
|
||||
msg.replace(msg.find("%s"), 2, item.getClass().getName(item));
|
||||
MWBase::Environment::get().getWindowManager()->messageBox(msg);
|
||||
MWBase::Environment::get().getDialogueManager()->say(mPtr, "Thief");
|
||||
MWBase::Environment::get().getMechanicsManager()->reportCrime(player, mPtr, MWBase::MechanicsManager::OT_Theft,
|
||||
item.getClass().getValue(item));
|
||||
MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Enchanting);
|
||||
MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Dialogue);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int result = mEnchanting.create();
|
||||
|
||||
if(result==1)
|
||||
|
|
|
@ -74,9 +74,9 @@ void InventoryItemModel::update()
|
|||
|
||||
ItemStack newItem (item, this, item.getRefData().getCount());
|
||||
|
||||
if (mActor.getTypeName() == typeid(ESM::NPC).name())
|
||||
if (mActor.getClass().hasInventoryStore(mActor))
|
||||
{
|
||||
MWWorld::InventoryStore& store = MWWorld::Class::get(mActor).getInventoryStore(mActor);
|
||||
MWWorld::InventoryStore& store = mActor.getClass().getInventoryStore(mActor);
|
||||
for (int slot=0; slot<MWWorld::InventoryStore::Slots; ++slot)
|
||||
{
|
||||
MWWorld::ContainerStoreIterator equipped = store.getSlot(slot);
|
||||
|
|
|
@ -206,10 +206,12 @@ struct JournalViewModelImpl : JournalViewModel
|
|||
if (active_only && i->second.isFinished ())
|
||||
continue;
|
||||
|
||||
/// \todo quest.getName() is broken? returns empty string
|
||||
//const MWDialogue::Quest& quest = i->second;
|
||||
|
||||
visitor (reinterpret_cast <QuestId> (&i->second), toUtf8Span (i->first));
|
||||
const MWDialogue::Quest& quest = i->second;
|
||||
// Unfortunately Morrowind.esm has no quest names, since the quest book was added with tribunal.
|
||||
if (quest.getName().empty())
|
||||
visitor (reinterpret_cast <QuestId> (&i->second), toUtf8Span (i->first));
|
||||
else
|
||||
visitor (reinterpret_cast <QuestId> (&i->second), toUtf8Span (quest.getName()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -287,24 +287,6 @@ namespace MWGui
|
|||
else {
|
||||
mainWidgetSize.width = textSize.width + 3*textPadding;
|
||||
}
|
||||
mainWidgetSize.height = textSize.height + 2*textPadding + textButtonPadding + buttonHeight * buttons.size() + buttonMainPadding;
|
||||
|
||||
mMainWidget->setSize(mainWidgetSize);
|
||||
|
||||
MyGUI::IntCoord absCoord;
|
||||
absCoord.left = (gameWindowSize.width - mainWidgetSize.width)/2;
|
||||
absCoord.top = (gameWindowSize.height - mainWidgetSize.height)/2;
|
||||
|
||||
mMainWidget->setCoord(absCoord);
|
||||
mMainWidget->setSize(mainWidgetSize);
|
||||
|
||||
|
||||
MyGUI::IntCoord messageWidgetCoord;
|
||||
messageWidgetCoord.left = (mainWidgetSize.width - textSize.width)/2;
|
||||
messageWidgetCoord.top = textPadding;
|
||||
mMessageWidget->setCoord(messageWidgetCoord);
|
||||
|
||||
mMessageWidget->setSize(textSize);
|
||||
|
||||
MyGUI::IntCoord buttonCord;
|
||||
MyGUI::IntSize buttonSize(0, buttonHeight);
|
||||
|
@ -326,6 +308,21 @@ namespace MWGui
|
|||
top += buttonSize.height + 2*buttonTopPadding;
|
||||
}
|
||||
|
||||
mainWidgetSize.height = top + buttonMainPadding;
|
||||
mMainWidget->setSize(mainWidgetSize);
|
||||
|
||||
MyGUI::IntPoint absPos;
|
||||
absPos.left = (gameWindowSize.width - mainWidgetSize.width)/2;
|
||||
absPos.top = (gameWindowSize.height - mainWidgetSize.height)/2;
|
||||
|
||||
mMainWidget->setPosition(absPos);
|
||||
|
||||
MyGUI::IntCoord messageWidgetCoord;
|
||||
messageWidgetCoord.left = (mainWidgetSize.width - textSize.width)/2;
|
||||
messageWidgetCoord.top = textPadding;
|
||||
messageWidgetCoord.width = textSize.width;
|
||||
messageWidgetCoord.height = textSize.height;
|
||||
mMessageWidget->setCoord(messageWidgetCoord);
|
||||
}
|
||||
|
||||
// Set key focus to "Ok" button
|
||||
|
|
|
@ -3,8 +3,15 @@
|
|||
#include <boost/lexical_cast.hpp>
|
||||
|
||||
#include "../mwworld/inventorystore.hpp"
|
||||
#include "../mwworld/actionequip.hpp"
|
||||
#include "../mwworld/class.hpp"
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
|
||||
#include "../mwmechanics/spellcasting.hpp"
|
||||
#include "../mwmechanics/spells.hpp"
|
||||
#include "../mwmechanics/creaturestats.hpp"
|
||||
|
||||
#include "../mwgui/inventorywindow.hpp"
|
||||
#include "../mwgui/bookwindow.hpp"
|
||||
#include "../mwgui/scrollwindow.hpp"
|
||||
|
@ -17,8 +24,8 @@ namespace
|
|||
{
|
||||
bool sortItems(const MWWorld::Ptr& left, const MWWorld::Ptr& right)
|
||||
{
|
||||
int cmp = MWWorld::Class::get(left).getName(left).compare(
|
||||
MWWorld::Class::get(right).getName(right));
|
||||
int cmp = left.getClass().getName(left).compare(
|
||||
right.getClass().getName(right));
|
||||
return cmp < 0;
|
||||
}
|
||||
|
||||
|
@ -334,10 +341,7 @@ namespace MWGui
|
|||
// equip, if it can be equipped
|
||||
if (!MWWorld::Class::get(item).getEquipmentSlots(item).first.empty())
|
||||
{
|
||||
// Note: can't use Class::use here because enchanted scrolls for example would then open the scroll window instead of equipping
|
||||
|
||||
MWWorld::ActionEquip action(item);
|
||||
action.execute (MWBase::Environment::get().getWorld ()->getPlayerPtr());
|
||||
MWBase::Environment::get().getWindowManager()->getInventoryWindow()->useItem(item);
|
||||
}
|
||||
|
||||
store.setSelectedEnchantItem(it);
|
||||
|
|
|
@ -3,13 +3,17 @@
|
|||
#include <boost/lexical_cast.hpp>
|
||||
|
||||
#include "../mwbase/windowmanager.hpp"
|
||||
|
||||
#include "../mwbase/soundmanager.hpp"
|
||||
#include "../mwbase/mechanicsmanager.hpp"
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
|
||||
#include "../mwworld/containerstore.hpp"
|
||||
#include "../mwworld/class.hpp"
|
||||
|
||||
#include "../mwmechanics/spellcasting.hpp"
|
||||
#include "../mwmechanics/spells.hpp"
|
||||
#include "../mwmechanics/creaturestats.hpp"
|
||||
|
||||
#include "tooltips.hpp"
|
||||
#include "class.hpp"
|
||||
|
|
|
@ -4,11 +4,15 @@
|
|||
#include <boost/format.hpp>
|
||||
|
||||
#include "../mwbase/windowmanager.hpp"
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
|
||||
#include "../mwworld/inventorystore.hpp"
|
||||
#include "../mwworld/actionequip.hpp"
|
||||
#include "../mwworld/class.hpp"
|
||||
|
||||
#include "../mwmechanics/spellcasting.hpp"
|
||||
#include "../mwmechanics/spells.hpp"
|
||||
#include "../mwmechanics/creaturestats.hpp"
|
||||
|
||||
#include "spellicons.hpp"
|
||||
#include "inventorywindow.hpp"
|
||||
|
@ -231,8 +235,7 @@ namespace MWGui
|
|||
MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top);
|
||||
|
||||
float enchantCost = enchant->mData.mCost;
|
||||
MWMechanics::NpcStats &stats = player.getClass().getNpcStats(player);
|
||||
int eSkill = stats.getSkill(ESM::Skill::Enchant).getModified();
|
||||
int eSkill = player.getClass().getSkill(player, ESM::Skill::Enchant);
|
||||
int castCost = std::max(1.f, enchantCost - (enchantCost / 100) * (eSkill - 10));
|
||||
|
||||
std::string cost = boost::lexical_cast<std::string>(castCost);
|
||||
|
@ -316,13 +319,7 @@ namespace MWGui
|
|||
if (_sender->getUserString("Equipped") == "false"
|
||||
&& !MWWorld::Class::get(item).getEquipmentSlots(item).first.empty())
|
||||
{
|
||||
// Note: can't use Class::use here because enchanted scrolls for example would then open the scroll window instead of equipping
|
||||
|
||||
MWWorld::ActionEquip action(item);
|
||||
action.execute (MWBase::Environment::get().getWorld ()->getPlayerPtr());
|
||||
|
||||
// since we changed equipping status, update the inventory window
|
||||
MWBase::Environment::get().getWindowManager()->getInventoryWindow()->updateItemView();
|
||||
MWBase::Environment::get().getWindowManager()->getInventoryWindow()->useItem(item);
|
||||
}
|
||||
|
||||
store.setSelectedEnchantItem(it);
|
||||
|
|
|
@ -211,6 +211,7 @@ namespace MWGui
|
|||
params.mMagnMin = it->mMagnMin;
|
||||
params.mMagnMax = it->mMagnMax;
|
||||
params.mRange = it->mRange;
|
||||
params.mArea = it->mArea;
|
||||
params.mIsConstant = (spell->mData.mType == ESM::Spell::ST_Ability);
|
||||
params.mNoTarget = false;
|
||||
effects.push_back(params);
|
||||
|
|
|
@ -162,7 +162,7 @@ namespace MWGui
|
|||
}
|
||||
|
||||
// don't show equipped items
|
||||
if(mMerchant.getTypeName() == typeid(ESM::NPC).name())
|
||||
if(mMerchant.getClass().hasInventoryStore(mMerchant))
|
||||
{
|
||||
bool isEquipped = false;
|
||||
MWWorld::InventoryStore& store = MWWorld::Class::get(mMerchant).getInventoryStore(mMerchant);
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
#include "../mwworld/containerstore.hpp"
|
||||
|
||||
#include "../mwmechanics/creaturestats.hpp"
|
||||
#include "../mwmechanics/npcstats.hpp"
|
||||
|
||||
#include "inventorywindow.hpp"
|
||||
#include "itemview.hpp"
|
||||
|
@ -272,6 +271,25 @@ namespace MWGui
|
|||
return;
|
||||
}
|
||||
|
||||
// check if the player is attempting to sell back an item stolen from this actor
|
||||
for (std::vector<ItemStack>::iterator it = merchantBought.begin(); it != merchantBought.end(); ++it)
|
||||
{
|
||||
if (Misc::StringUtils::ciEqual(it->mBase.getCellRef().mOwner, mPtr.getCellRef().mRefID))
|
||||
{
|
||||
std::string msg = gmst.find("sNotifyMessage49")->getString();
|
||||
if (msg.find("%s") != std::string::npos)
|
||||
msg.replace(msg.find("%s"), 2, it->mBase.getClass().getName(it->mBase));
|
||||
MWBase::Environment::get().getWindowManager()->messageBox(msg);
|
||||
MWBase::Environment::get().getDialogueManager()->say(mPtr, "Thief");
|
||||
MWBase::Environment::get().getMechanicsManager()->reportCrime(player, mPtr, MWBase::MechanicsManager::OT_Theft,
|
||||
it->mBase.getClass().getValue(it->mBase)
|
||||
* it->mCount);
|
||||
onCancelButtonClicked(mCancelButton);
|
||||
MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Dialogue);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(mCurrentBalance > mCurrentMerchantOffer)
|
||||
{
|
||||
//if npc is a creature: reject (no haggle)
|
||||
|
@ -293,8 +311,8 @@ namespace MWGui
|
|||
float clampedDisposition = std::max<int>(0,std::min<int>(int(MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mPtr)
|
||||
+ MWBase::Environment::get().getDialogueManager()->getTemporaryDispositionChange()),100));
|
||||
|
||||
const MWMechanics::NpcStats &sellerStats = mPtr.getClass().getNpcStats(mPtr);
|
||||
const MWMechanics::NpcStats &playerStats = player.getClass().getNpcStats(player);
|
||||
const MWMechanics::CreatureStats &sellerStats = mPtr.getClass().getCreatureStats(mPtr);
|
||||
const MWMechanics::CreatureStats &playerStats = player.getClass().getCreatureStats(player);
|
||||
|
||||
float a1 = std::min(player.getClass().getSkill(player, ESM::Skill::Mercantile), 100);
|
||||
float b1 = std::min(0.1f * playerStats.getAttribute(ESM::Attribute::Luck).getModified(), 10.f);
|
||||
|
|
|
@ -48,8 +48,7 @@ void adjustBoundItem (const std::string& item, bool bound, const MWWorld::Ptr& a
|
|||
|
||||
bool disintegrateSlot (MWWorld::Ptr ptr, int slot, float disintegrate)
|
||||
{
|
||||
// TODO: remove this check once creatures support inventory store
|
||||
if (ptr.getTypeName() == typeid(ESM::NPC).name())
|
||||
if (ptr.getClass().hasInventoryStore(ptr))
|
||||
{
|
||||
MWWorld::InventoryStore& inv = ptr.getClass().getInventoryStore(ptr);
|
||||
MWWorld::ContainerStoreIterator item =
|
||||
|
@ -132,6 +131,8 @@ namespace MWMechanics
|
|||
static const float fSoulgemMult = world->getStore().get<ESM::GameSetting>().find("fSoulgemMult")->getFloat();
|
||||
|
||||
float creatureSoulValue = mCreature.get<ESM::Creature>()->mBase->mData.mSoul;
|
||||
if (creatureSoulValue == 0)
|
||||
return;
|
||||
|
||||
// Use the smallest soulgem that is large enough to hold the soul
|
||||
MWWorld::ContainerStore& container = caster.getClass().getContainerStore(caster);
|
||||
|
@ -918,4 +919,13 @@ namespace MWMechanics
|
|||
return iter->second->isAnimPlaying(groupName);
|
||||
return false;
|
||||
}
|
||||
|
||||
void Actors::getObjectsInRange(const Ogre::Vector3& position, float radius, std::vector<MWWorld::Ptr>& out)
|
||||
{
|
||||
for (PtrControllerMap::iterator iter = mActors.begin(); iter != mActors.end(); ++iter)
|
||||
{
|
||||
if (Ogre::Vector3(iter->first.getRefData().getPosition().pos).squaredDistance(position) <= radius*radius)
|
||||
out.push_back(iter->first);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -94,6 +94,8 @@ namespace MWMechanics
|
|||
void skipAnimation(const MWWorld::Ptr& ptr);
|
||||
bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName);
|
||||
|
||||
void getObjectsInRange(const Ogre::Vector3& position, float radius, std::vector<MWWorld::Ptr>& out);
|
||||
|
||||
private:
|
||||
PtrControllerMap mActors;
|
||||
|
||||
|
|
|
@ -50,9 +50,11 @@ namespace MWMechanics
|
|||
bool AiCombat::execute (const MWWorld::Ptr& actor,float duration)
|
||||
{
|
||||
//General description
|
||||
if(!actor.getClass().getCreatureStats(actor).isHostile())
|
||||
if(!actor.getClass().getCreatureStats(actor).isHostile()
|
||||
|| actor.getClass().getCreatureStats(actor).getHealth().getCurrent() <= 0)
|
||||
return true;
|
||||
if(actor.getClass().getCreatureStats(actor).getHealth().getCurrent() <= 0)
|
||||
|
||||
if(mTarget.getClass().getCreatureStats(mTarget).isDead())
|
||||
return true;
|
||||
|
||||
//Update every frame
|
||||
|
@ -125,9 +127,9 @@ namespace MWMechanics
|
|||
|
||||
actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true);
|
||||
|
||||
if(actor.getTypeName() == typeid(ESM::NPC).name())
|
||||
if (actor.getClass().hasInventoryStore(actor))
|
||||
{
|
||||
MWMechanics::DrawState_ state = actor.getClass().getNpcStats(actor).getDrawState();
|
||||
MWMechanics::DrawState_ state = actor.getClass().getCreatureStats(actor).getDrawState();
|
||||
if (state == MWMechanics::DrawState_Spell || state == MWMechanics::DrawState_Nothing)
|
||||
actor.getClass().getNpcStats(actor).setDrawState(MWMechanics::DrawState_Weapon);
|
||||
|
||||
|
@ -160,6 +162,7 @@ namespace MWMechanics
|
|||
|
||||
float zAngle;
|
||||
|
||||
|
||||
float rangeMelee;
|
||||
float rangeCloseUp;
|
||||
bool distantCombat = false;
|
||||
|
@ -367,12 +370,14 @@ namespace MWMechanics
|
|||
return mTarget.getRefData().getHandle();
|
||||
}
|
||||
|
||||
|
||||
AiCombat *MWMechanics::AiCombat::clone() const
|
||||
{
|
||||
return new AiCombat(*this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/mechanicsmanager.hpp"
|
||||
#include "../mwbase/dialoguemanager.hpp"
|
||||
#include "../mwmechanics/npcstats.hpp"
|
||||
|
||||
#include "creaturestats.hpp"
|
||||
#include <OgreVector3.h>
|
||||
|
||||
namespace
|
||||
|
@ -32,6 +32,7 @@ namespace MWMechanics
|
|||
, mX(0)
|
||||
, mY(0)
|
||||
, mZ(0)
|
||||
, mSaidGreeting(false)
|
||||
{
|
||||
for(unsigned short counter = 0; counter < mIdle.size(); counter++)
|
||||
{
|
||||
|
@ -66,8 +67,7 @@ namespace MWMechanics
|
|||
|
||||
bool AiWander::execute (const MWWorld::Ptr& actor,float duration)
|
||||
{
|
||||
if (actor.getClass().isNpc())
|
||||
actor.getClass().getNpcStats(actor).setDrawState(DrawState_Nothing);
|
||||
actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing);
|
||||
MWBase::World *world = MWBase::Environment::get().getWorld();
|
||||
if(mDuration)
|
||||
{
|
||||
|
@ -191,14 +191,49 @@ namespace MWMechanics
|
|||
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
|
||||
float chance = store.get<ESM::GameSetting>().find("fVoiceIdleOdds")->getFloat();
|
||||
int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
|
||||
// TODO: do not show subtitle messagebox if player is too far away? or do not say at all?
|
||||
if (roll < chance)
|
||||
|
||||
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
|
||||
|
||||
// Don't bother if the player is out of hearing range
|
||||
if (roll < chance && Ogre::Vector3(player.getRefData().getPosition().pos).distance(Ogre::Vector3(actor.getRefData().getPosition().pos)) < 1500)
|
||||
MWBase::Environment::get().getDialogueManager()->say(actor, "idle");
|
||||
}
|
||||
}
|
||||
|
||||
if(mIdleNow)
|
||||
{
|
||||
// Play a random voice greeting if the player gets too close
|
||||
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
|
||||
|
||||
float hello = actor.getClass().getCreatureStats(actor).getAiSetting(CreatureStats::AI_Hello).getModified();
|
||||
float helloDistance = hello;
|
||||
int iGreetDistanceMultiplier = store.get<ESM::GameSetting>().find("iGreetDistanceMultiplier")->getInt();
|
||||
helloDistance *= iGreetDistanceMultiplier;
|
||||
|
||||
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
|
||||
float playerDist = Ogre::Vector3(player.getRefData().getPosition().pos).distance(
|
||||
Ogre::Vector3(actor.getRefData().getPosition().pos));
|
||||
|
||||
if (!mSaidGreeting)
|
||||
{
|
||||
// TODO: check if actor is aware / has line of sight
|
||||
if (playerDist <= helloDistance
|
||||
// Only play a greeting if the player is not moving
|
||||
&& Ogre::Vector3(player.getClass().getMovementSettings(player).mPosition).squaredLength() == 0)
|
||||
{
|
||||
mSaidGreeting = true;
|
||||
MWBase::Environment::get().getDialogueManager()->say(actor, "hello");
|
||||
// TODO: turn to face player and interrupt the idle animation?
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
float fGreetDistanceReset = store.get<ESM::GameSetting>().find("fGreetDistanceReset")->getFloat();
|
||||
if (playerDist >= fGreetDistanceReset * iGreetDistanceMultiplier)
|
||||
mSaidGreeting = false;
|
||||
}
|
||||
|
||||
// Check if idle animation finished
|
||||
if(!checkIdle(actor, mPlayedIdle))
|
||||
{
|
||||
mPlayedIdle = 0;
|
||||
|
|
|
@ -32,6 +32,8 @@ namespace MWMechanics
|
|||
std::vector<int> mIdle;
|
||||
bool mRepeat;
|
||||
|
||||
bool mSaidGreeting;
|
||||
|
||||
float mX;
|
||||
float mY;
|
||||
float mZ;
|
||||
|
|
|
@ -62,26 +62,6 @@ struct StateInfo {
|
|||
const char groupname[32];
|
||||
};
|
||||
|
||||
static const std::string sDeathList[] = {
|
||||
"death1" ,
|
||||
"death2" ,
|
||||
"death3" ,
|
||||
"death4" ,
|
||||
"death5" ,
|
||||
"swimdeath",
|
||||
};
|
||||
static const int sDeathListSize = sizeof(sDeathList)/sizeof(sDeathList[0]);
|
||||
|
||||
static const std::string sHitList[] = {
|
||||
"hit1" ,
|
||||
"hit2" ,
|
||||
"hit3" ,
|
||||
"hit4" ,
|
||||
"hit5" ,
|
||||
"knockdown" ,
|
||||
};
|
||||
static const int sHitListSize = sizeof(sHitList)/sizeof(sHitList[0]);
|
||||
|
||||
static const StateInfo sMovementList[] = {
|
||||
{ CharState_WalkForward, "walkforward" },
|
||||
{ CharState_WalkBack, "walkback" },
|
||||
|
@ -154,6 +134,17 @@ public:
|
|||
{ return weap.type == type; }
|
||||
};
|
||||
|
||||
std::string CharacterController::chooseRandomGroup (const std::string& prefix, int* num)
|
||||
{
|
||||
int numAnims=0;
|
||||
while (mAnimation->hasAnimation(prefix + Ogre::StringConverter::toString(numAnims+1)))
|
||||
++numAnims;
|
||||
|
||||
int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * numAnims + 1; // [1, numAnims]
|
||||
if (num)
|
||||
*num = roll;
|
||||
return prefix + Ogre::StringConverter::toString(roll);
|
||||
}
|
||||
|
||||
void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterState movement, bool force)
|
||||
{
|
||||
|
@ -162,27 +153,27 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
|
|||
{
|
||||
bool recovery = mPtr.getClass().getCreatureStats(mPtr).getHitRecovery();
|
||||
bool knockdown = mPtr.getClass().getCreatureStats(mPtr).getKnockedDown();
|
||||
bool block = mPtr.getClass().getCreatureStats(mPtr).getBlock();
|
||||
if(mHitState == CharState_None)
|
||||
{
|
||||
if(knockdown)
|
||||
{
|
||||
mHitState = CharState_KnockDown;
|
||||
mCurrentHit = sHitList[sHitListSize-1];
|
||||
mCurrentHit = "knockdown";
|
||||
mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::Group_All, true, 1, "start", "stop", 0.0f, 0);
|
||||
}
|
||||
else if (recovery)
|
||||
{
|
||||
mHitState = CharState_Hit;
|
||||
int iHit = rand() % (sHitListSize-1);
|
||||
mCurrentHit = sHitList[iHit];
|
||||
if(mPtr.getRefData().getHandle()=="player" && !mAnimation->hasAnimation(mCurrentHit))
|
||||
{
|
||||
//only 3 different hit animations if player is in 1st person
|
||||
int iHit = rand() % (sHitListSize-3);
|
||||
mCurrentHit = sHitList[iHit];
|
||||
}
|
||||
mCurrentHit = chooseRandomGroup("hit");
|
||||
mAnimation->play(mCurrentHit, Priority_Hit, MWRender::Animation::Group_All, true, 1, "start", "stop", 0.0f, 0);
|
||||
}
|
||||
else if (block)
|
||||
{
|
||||
mHitState = CharState_Block;
|
||||
mCurrentHit = "shield";
|
||||
mAnimation->play(mCurrentHit, Priority_Hit, MWRender::Animation::Group_All, true, 1, "block start", "block stop", 0.0f, 0);
|
||||
}
|
||||
}
|
||||
else if(!mAnimation->isPlaying(mCurrentHit))
|
||||
{
|
||||
|
@ -191,6 +182,8 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
|
|||
mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(false);
|
||||
if (recovery)
|
||||
mPtr.getClass().getCreatureStats(mPtr).setHitRecovery(false);
|
||||
if (block)
|
||||
mPtr.getClass().getCreatureStats(mPtr).setBlock(false);
|
||||
mHitState = CharState_None;
|
||||
}
|
||||
}
|
||||
|
@ -250,14 +243,16 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
|
|||
|
||||
mAnimation->disable(mCurrentJump);
|
||||
mCurrentJump = jump;
|
||||
mAnimation->play(mCurrentJump, Priority_Jump, jumpgroup, false,
|
||||
if (mAnimation->hasAnimation("jump"))
|
||||
mAnimation->play(mCurrentJump, Priority_Jump, jumpgroup, false,
|
||||
1.0f, ((mode!=2)?"start":"loop start"), "stop", 0.0f, ~0ul);
|
||||
}
|
||||
else
|
||||
{
|
||||
mAnimation->disable(mCurrentJump);
|
||||
mCurrentJump.clear();
|
||||
mAnimation->play(jump, Priority_Jump, jumpgroup, true,
|
||||
if (mAnimation->hasAnimation("jump"))
|
||||
mAnimation->play(jump, Priority_Jump, jumpgroup, true,
|
||||
1.0f, "loop stop", "stop", 0.0f, 0);
|
||||
}
|
||||
}
|
||||
|
@ -323,7 +318,7 @@ void getWeaponGroup(WeaponType weaptype, std::string &group)
|
|||
}
|
||||
|
||||
|
||||
MWWorld::ContainerStoreIterator getActiveWeapon(NpcStats &stats, MWWorld::InventoryStore &inv, WeaponType *weaptype)
|
||||
MWWorld::ContainerStoreIterator getActiveWeapon(CreatureStats &stats, MWWorld::InventoryStore &inv, WeaponType *weaptype)
|
||||
{
|
||||
if(stats.getDrawState() == DrawState_Spell)
|
||||
{
|
||||
|
@ -385,28 +380,20 @@ MWWorld::ContainerStoreIterator getActiveWeapon(NpcStats &stats, MWWorld::Invent
|
|||
|
||||
void CharacterController::playRandomDeath(float startpoint)
|
||||
{
|
||||
if(MWWorld::Class::get(mPtr).isNpc())
|
||||
if(MWBase::Environment::get().getWorld()->isSwimming(mPtr) && mAnimation->hasAnimation("swimdeath"))
|
||||
{
|
||||
if(MWBase::Environment::get().getWorld()->isSwimming(mPtr))
|
||||
{
|
||||
mDeathState = CharState_SwimDeath;
|
||||
mCurrentDeath = sDeathList[sDeathListSize-1]; //last in the list is 'swimdeath'
|
||||
}
|
||||
else
|
||||
{
|
||||
int num = rand() % (sDeathListSize-1);
|
||||
mDeathState = static_cast<CharacterState>(CharState_Death1 + num);
|
||||
mCurrentDeath = sDeathList[num];
|
||||
}
|
||||
mDeathState = CharState_SwimDeath;
|
||||
mCurrentDeath = "swimdeath";
|
||||
}
|
||||
else
|
||||
{
|
||||
mDeathState = CharState_Death1;
|
||||
mCurrentDeath = "death1";
|
||||
int selected=0;
|
||||
mCurrentDeath = chooseRandomGroup("death", &selected);
|
||||
mDeathState = static_cast<CharacterState>(CharState_Death1 + (selected-1));
|
||||
}
|
||||
|
||||
mAnimation->play(mCurrentDeath, Priority_Death, MWRender::Animation::Group_All,
|
||||
false, 1.0f, "start", "stop", 0.0f, 0);
|
||||
false, 1.0f, "start", "stop", startpoint, 0);
|
||||
}
|
||||
|
||||
CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim)
|
||||
|
@ -434,15 +421,16 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim
|
|||
* handle knockout and death which moves the character down. */
|
||||
mAnimation->setAccumulation(Ogre::Vector3(1.0f, 1.0f, 0.0f));
|
||||
|
||||
if(mPtr.getTypeName() == typeid(ESM::NPC).name())
|
||||
if (cls.hasInventoryStore(mPtr))
|
||||
{
|
||||
getActiveWeapon(cls.getNpcStats(mPtr), cls.getInventoryStore(mPtr), &mWeaponType);
|
||||
getActiveWeapon(cls.getCreatureStats(mPtr), cls.getInventoryStore(mPtr), &mWeaponType);
|
||||
if(mWeaponType != WeapType_None)
|
||||
{
|
||||
getWeaponGroup(mWeaponType, mCurrentWeapon);
|
||||
mUpperBodyState = UpperCharState_WeapEquiped;
|
||||
mAnimation->showWeapons(true);
|
||||
}
|
||||
mAnimation->showCarriedLeft(mWeaponType != WeapType_Spell && mWeaponType != WeapType_HandToHand);
|
||||
}
|
||||
|
||||
if(!cls.getCreatureStats(mPtr).isDead())
|
||||
|
@ -506,14 +494,14 @@ bool CharacterController::updateCreatureState()
|
|||
return false;
|
||||
}
|
||||
|
||||
bool CharacterController::updateNpcState()
|
||||
bool CharacterController::updateWeaponState()
|
||||
{
|
||||
const MWWorld::Class &cls = MWWorld::Class::get(mPtr);
|
||||
NpcStats &stats = cls.getNpcStats(mPtr);
|
||||
CreatureStats &stats = cls.getCreatureStats(mPtr);
|
||||
WeaponType weaptype = WeapType_None;
|
||||
MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr);
|
||||
MWWorld::ContainerStoreIterator weapon = getActiveWeapon(stats, inv, &weaptype);
|
||||
const bool isWerewolf = stats.isWerewolf();
|
||||
const bool isWerewolf = cls.isNpc() && cls.getNpcStats(mPtr).isWerewolf();
|
||||
|
||||
bool forcestateupdate = false;
|
||||
if(weaptype != mWeaponType && mHitState != CharState_KnockDown)
|
||||
|
@ -604,7 +592,7 @@ bool CharacterController::updateNpcState()
|
|||
{
|
||||
// Unset casting flag, otherwise pressing the mouse button down would
|
||||
// continue casting every frame if there is no animation
|
||||
mPtr.getClass().getCreatureStats(mPtr).setAttackingOrSpell(false);
|
||||
stats.setAttackingOrSpell(false);
|
||||
|
||||
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
|
||||
|
||||
|
@ -1115,8 +1103,8 @@ void CharacterController::update(float duration)
|
|||
}
|
||||
}
|
||||
|
||||
if(cls.isNpc())
|
||||
forcestateupdate = updateNpcState() || forcestateupdate;
|
||||
if(cls.hasInventoryStore(mPtr))
|
||||
forcestateupdate = updateWeaponState() || forcestateupdate;
|
||||
else
|
||||
forcestateupdate = updateCreatureState() || forcestateupdate;
|
||||
|
||||
|
@ -1314,7 +1302,7 @@ void CharacterController::determineAttackType()
|
|||
if (move[0] && !move[1]) //sideway
|
||||
{
|
||||
mPtr.getClass().getCreatureStats(mPtr).setAttackType(MWMechanics::CreatureStats::AT_Slash);
|
||||
if(mPtr.getClass().isNpc())
|
||||
if(mPtr.getClass().hasInventoryStore(mPtr))
|
||||
mAttackType = "slash";
|
||||
else
|
||||
mCurrentWeapon = "attack2";
|
||||
|
@ -1322,7 +1310,7 @@ void CharacterController::determineAttackType()
|
|||
else if (move[1]) //forward
|
||||
{
|
||||
mPtr.getClass().getCreatureStats(mPtr).setAttackType(MWMechanics::CreatureStats::AT_Thrust);
|
||||
if(mPtr.getClass().isNpc())
|
||||
if(mPtr.getClass().hasInventoryStore(mPtr))
|
||||
mAttackType = "thrust";
|
||||
else
|
||||
mCurrentWeapon = "attack3";
|
||||
|
@ -1330,11 +1318,11 @@ void CharacterController::determineAttackType()
|
|||
else
|
||||
{
|
||||
mPtr.getClass().getCreatureStats(mPtr).setAttackType(MWMechanics::CreatureStats::AT_Chop);
|
||||
if(mPtr.getClass().isNpc())
|
||||
if(mPtr.getClass().hasInventoryStore(mPtr))
|
||||
mAttackType = "chop";
|
||||
else
|
||||
mCurrentWeapon = "attack1";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ namespace MWMechanics
|
|||
{
|
||||
|
||||
class Movement;
|
||||
class NpcStats;
|
||||
class CreatureStats;
|
||||
|
||||
enum Priority {
|
||||
Priority_Default,
|
||||
|
@ -92,7 +92,8 @@ enum CharacterState {
|
|||
CharState_SwimDeath,
|
||||
|
||||
CharState_Hit,
|
||||
CharState_KnockDown
|
||||
CharState_KnockDown,
|
||||
CharState_Block
|
||||
};
|
||||
|
||||
enum WeaponType {
|
||||
|
@ -171,13 +172,17 @@ class CharacterController
|
|||
|
||||
void clearAnimQueue();
|
||||
|
||||
bool updateNpcState();
|
||||
bool updateWeaponState();
|
||||
bool updateCreatureState();
|
||||
|
||||
void updateVisibility();
|
||||
|
||||
void playRandomDeath(float startpoint = 0.0f);
|
||||
|
||||
/// choose a random animation group with \a prefix and numeric suffix
|
||||
/// @param num if non-NULL, the chosen animation number will be written here
|
||||
std::string chooseRandomGroup (const std::string& prefix, int* num = NULL);
|
||||
|
||||
public:
|
||||
CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim);
|
||||
virtual ~CharacterController();
|
||||
|
@ -202,9 +207,7 @@ public:
|
|||
};
|
||||
|
||||
void getWeaponGroup(WeaponType weaptype, std::string &group);
|
||||
MWWorld::ContainerStoreIterator getActiveWeapon(NpcStats &stats,
|
||||
MWWorld::InventoryStore &inv,
|
||||
WeaponType *weaptype);
|
||||
MWWorld::ContainerStoreIterator getActiveWeapon(CreatureStats &stats, MWWorld::InventoryStore &inv, WeaponType *weaptype);
|
||||
}
|
||||
|
||||
#endif /* GAME_MWMECHANICS_CHARACTER_HPP */
|
||||
|
|
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),
|
||||
mAttackingOrSpell(false), mAttackType(AT_Chop),
|
||||
mIsWerewolf(false),
|
||||
mFallHeight(0), mRecalcDynamicStats(false), mKnockdown(false), mHitRecovery(false),
|
||||
mMovementFlags(0)
|
||||
mFallHeight(0), mRecalcDynamicStats(false), mKnockdown(false), mHitRecovery(false), mBlock(false),
|
||||
mMovementFlags(0), mDrawState (DrawState_Nothing), mAttackStrength(0.f)
|
||||
{
|
||||
for (int i=0; i<4; ++i)
|
||||
mAiSettings[i] = 0;
|
||||
|
@ -348,6 +348,13 @@ namespace MWMechanics
|
|||
|
||||
bool CreatureStats::getCreatureTargetted() const
|
||||
{
|
||||
std::string target;
|
||||
if (mAiSequence.getCombatTarget(target))
|
||||
{
|
||||
MWWorld::Ptr targetPtr;
|
||||
targetPtr = MWBase::Environment::get().getWorld()->getPtr(target, true);
|
||||
return targetPtr.getTypeName() == typeid(ESM::Creature).name();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -427,6 +434,16 @@ namespace MWMechanics
|
|||
return mHitRecovery;
|
||||
}
|
||||
|
||||
void CreatureStats::setBlock(bool value)
|
||||
{
|
||||
mBlock = value;
|
||||
}
|
||||
|
||||
bool CreatureStats::getBlock() const
|
||||
{
|
||||
return mBlock;
|
||||
}
|
||||
|
||||
bool CreatureStats::getMovementFlag (Flag flag) const
|
||||
{
|
||||
return mMovementFlags & flag;
|
||||
|
@ -452,4 +469,24 @@ namespace MWMechanics
|
|||
return false; // shut up, compiler
|
||||
}
|
||||
|
||||
DrawState_ CreatureStats::getDrawState() const
|
||||
{
|
||||
return mDrawState;
|
||||
}
|
||||
|
||||
void CreatureStats::setDrawState(DrawState_ state)
|
||||
{
|
||||
mDrawState = state;
|
||||
}
|
||||
|
||||
float CreatureStats::getAttackStrength() const
|
||||
{
|
||||
return mAttackStrength;
|
||||
}
|
||||
|
||||
void CreatureStats::setAttackStrength(float value)
|
||||
{
|
||||
mAttackStrength = value;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include "spells.hpp"
|
||||
#include "activespells.hpp"
|
||||
#include "aisequence.hpp"
|
||||
#include "drawstate.hpp"
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
|
@ -18,6 +19,7 @@ namespace MWMechanics
|
|||
///
|
||||
class CreatureStats
|
||||
{
|
||||
DrawState_ mDrawState;
|
||||
AttributeValue mAttributes[8];
|
||||
DynamicStat<float> mDynamic[3]; // health, magicka, fatigue
|
||||
int mLevel;
|
||||
|
@ -37,7 +39,9 @@ namespace MWMechanics
|
|||
bool mAttackingOrSpell;
|
||||
bool mKnockdown;
|
||||
bool mHitRecovery;
|
||||
bool mBlock;
|
||||
unsigned int mMovementFlags;
|
||||
float mAttackStrength; // Note only some creatures attack with weapons
|
||||
|
||||
float mFallHeight;
|
||||
|
||||
|
@ -57,6 +61,13 @@ namespace MWMechanics
|
|||
public:
|
||||
CreatureStats();
|
||||
|
||||
DrawState_ getDrawState() const;
|
||||
void setDrawState(DrawState_ state);
|
||||
|
||||
/// When attacking, stores how strong the attack should be (0 = weakest, 1 = strongest)
|
||||
float getAttackStrength() const;
|
||||
void setAttackStrength(float value);
|
||||
|
||||
bool needToRecalcDynamicStats();
|
||||
|
||||
void addToFallHeight(float height);
|
||||
|
@ -194,6 +205,8 @@ namespace MWMechanics
|
|||
bool getKnockedDown() const;
|
||||
void setHitRecovery(bool value);
|
||||
bool getHitRecovery() const;
|
||||
void setBlock(bool value);
|
||||
bool getBlock() const;
|
||||
|
||||
enum Flag
|
||||
{
|
||||
|
|
|
@ -28,6 +28,8 @@ namespace MWMechanics
|
|||
void setEnchanter(MWWorld::Ptr enchanter);
|
||||
void setSelfEnchanting(bool selfEnchanting);
|
||||
void setOldItem(MWWorld::Ptr oldItem);
|
||||
MWWorld::Ptr getOldItem() { return mOldItemPtr; }
|
||||
MWWorld::Ptr getGem() { return mSoulGemPtr; }
|
||||
void setNewItemName(const std::string& s);
|
||||
void setEffect(ESM::EffectList effectList);
|
||||
void setSoulGem(MWWorld::Ptr soulGem);
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include "../mwworld/class.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwmechanics/creaturestats.hpp"
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
|
|
|
@ -196,8 +196,8 @@ namespace MWMechanics
|
|||
creatureStats.setDynamic (i, stat);
|
||||
}
|
||||
|
||||
// auto-equip again. we need this for when the race is changed to a beast race
|
||||
MWWorld::InventoryStore& invStore = MWWorld::Class::get(ptr).getInventoryStore(ptr);
|
||||
// auto-equip again. we need this for when the race is changed to a beast race and shoes are no longer equippable
|
||||
MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore(ptr);
|
||||
for (int i=0; i<MWWorld::InventoryStore::Slots; ++i)
|
||||
invStore.unequipAll(ptr);
|
||||
invStore.autoEquip(ptr);
|
||||
|
@ -950,4 +950,10 @@ namespace MWMechanics
|
|||
|
||||
return (roll >= target);
|
||||
}
|
||||
|
||||
void MechanicsManager::getObjectsInRange(const Ogre::Vector3 &position, float radius, std::vector<MWWorld::Ptr> &objects)
|
||||
{
|
||||
mActors.getObjectsInRange(position, radius, objects);
|
||||
mObjects.getObjectsInRange(position, radius, objects);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -135,6 +135,8 @@ namespace MWMechanics
|
|||
|
||||
virtual void toggleAI();
|
||||
virtual bool isAIActive();
|
||||
|
||||
virtual void getObjectsInRange (const Ogre::Vector3& position, float radius, std::vector<MWWorld::Ptr>& objects);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -22,40 +22,18 @@
|
|||
#include "../mwbase/soundmanager.hpp"
|
||||
|
||||
MWMechanics::NpcStats::NpcStats()
|
||||
: mDrawState (DrawState_Nothing)
|
||||
, mBounty (0)
|
||||
: mBounty (0)
|
||||
, mLevelProgress(0)
|
||||
, mDisposition(0)
|
||||
, mReputation(0)
|
||||
, mWerewolfKills (0)
|
||||
, mProfit(0)
|
||||
, mAttackStrength(0.0f)
|
||||
, mTimeToStartDrowning(20.0)
|
||||
, mLastDrowningHit(0)
|
||||
{
|
||||
mSkillIncreases.resize (ESM::Attribute::Length, 0);
|
||||
}
|
||||
|
||||
MWMechanics::DrawState_ MWMechanics::NpcStats::getDrawState() const
|
||||
{
|
||||
return mDrawState;
|
||||
}
|
||||
|
||||
void MWMechanics::NpcStats::setDrawState (DrawState_ state)
|
||||
{
|
||||
mDrawState = state;
|
||||
}
|
||||
|
||||
float MWMechanics::NpcStats::getAttackStrength() const
|
||||
{
|
||||
return mAttackStrength;
|
||||
}
|
||||
|
||||
void MWMechanics::NpcStats::setAttackStrength(float value)
|
||||
{
|
||||
mAttackStrength = value;
|
||||
}
|
||||
|
||||
int MWMechanics::NpcStats::getBaseDisposition() const
|
||||
{
|
||||
return mDisposition;
|
||||
|
@ -289,12 +267,16 @@ bool MWMechanics::NpcStats::hasBeenUsed (const std::string& id) const
|
|||
|
||||
int MWMechanics::NpcStats::getBounty() const
|
||||
{
|
||||
return mBounty;
|
||||
if (mIsWerewolf)
|
||||
return MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("iWereWolfBounty")->getInt();
|
||||
else
|
||||
return mBounty;
|
||||
}
|
||||
|
||||
void MWMechanics::NpcStats::setBounty (int bounty)
|
||||
{
|
||||
mBounty = bounty;
|
||||
if (!mIsWerewolf)
|
||||
mBounty = bounty;
|
||||
}
|
||||
|
||||
int MWMechanics::NpcStats::getFactionReputation (const std::string& faction) const
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
#include <vector>
|
||||
|
||||
#include "stat.hpp"
|
||||
#include "drawstate.hpp"
|
||||
|
||||
#include "creaturestats.hpp"
|
||||
|
||||
|
@ -30,7 +29,6 @@ namespace MWMechanics
|
|||
/// \note the faction key must be in lowercase
|
||||
std::map<std::string, int> mFactionRank;
|
||||
|
||||
DrawState_ mDrawState;
|
||||
int mDisposition;
|
||||
SkillValue mSkill[27];
|
||||
SkillValue mWerewolfSkill[27];
|
||||
|
@ -61,13 +59,6 @@ namespace MWMechanics
|
|||
int getProfit() const;
|
||||
void modifyProfit(int diff);
|
||||
|
||||
DrawState_ getDrawState() const;
|
||||
void setDrawState (DrawState_ state);
|
||||
|
||||
/// When attacking, stores how strong the attack should be (0 = weakest, 1 = strongest)
|
||||
float getAttackStrength() const;
|
||||
void setAttackStrength(float value);
|
||||
|
||||
int getBaseDisposition() const;
|
||||
|
||||
void setBaseDisposition(int disposition);
|
||||
|
|
|
@ -92,4 +92,13 @@ void Objects::skipAnimation(const MWWorld::Ptr& ptr)
|
|||
iter->second->skipAnim();
|
||||
}
|
||||
|
||||
void Objects::getObjectsInRange(const Ogre::Vector3& position, float radius, std::vector<MWWorld::Ptr>& out)
|
||||
{
|
||||
for (PtrControllerMap::iterator iter = mObjects.begin(); iter != mObjects.end(); ++iter)
|
||||
{
|
||||
if (Ogre::Vector3(iter->first.getRefData().getPosition().pos).squaredDistance(position) <= radius*radius)
|
||||
out.push_back(iter->first);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -40,6 +40,8 @@ namespace MWMechanics
|
|||
|
||||
void playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number);
|
||||
void skipAnimation(const MWWorld::Ptr& ptr);
|
||||
|
||||
void getObjectsInRange (const Ogre::Vector3& position, float radius, std::vector<MWWorld::Ptr>& out);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include "OgreMath.h"
|
||||
#include "OgreVector3.h"
|
||||
|
||||
|
||||
#include <boost/graph/dijkstra_shortest_paths.hpp>
|
||||
#include <boost/graph/adjacency_list.hpp>
|
||||
|
||||
|
@ -256,5 +257,6 @@ namespace MWMechanics
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -1,29 +1,192 @@
|
|||
#include "spellcasting.hpp"
|
||||
|
||||
#include <cfloat>
|
||||
|
||||
#include <boost/format.hpp>
|
||||
|
||||
#include "../mwbase/windowmanager.hpp"
|
||||
#include "../mwbase/soundmanager.hpp"
|
||||
#include "../mwbase/mechanicsmanager.hpp"
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
|
||||
#include "../mwworld/containerstore.hpp"
|
||||
#include "../mwworld/actionteleport.hpp"
|
||||
#include "../mwworld/player.hpp"
|
||||
#include "../mwworld/class.hpp"
|
||||
|
||||
#include "../mwrender/animation.hpp"
|
||||
|
||||
#include "magiceffects.hpp"
|
||||
#include "npcstats.hpp"
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
|
||||
ESM::Skill::SkillEnum spellSchoolToSkill(int school)
|
||||
{
|
||||
std::map<int, ESM::Skill::SkillEnum> schoolSkillMap; // maps spell school to skill id
|
||||
schoolSkillMap[0] = ESM::Skill::Alteration;
|
||||
schoolSkillMap[1] = ESM::Skill::Conjuration;
|
||||
schoolSkillMap[3] = ESM::Skill::Illusion;
|
||||
schoolSkillMap[2] = ESM::Skill::Destruction;
|
||||
schoolSkillMap[4] = ESM::Skill::Mysticism;
|
||||
schoolSkillMap[5] = ESM::Skill::Restoration;
|
||||
assert(schoolSkillMap.find(school) != schoolSkillMap.end());
|
||||
return schoolSkillMap[school];
|
||||
}
|
||||
|
||||
float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool)
|
||||
{
|
||||
CreatureStats& stats = actor.getClass().getCreatureStats(actor);
|
||||
|
||||
if (stats.getMagicEffects().get(ESM::MagicEffect::Silence).mMagnitude)
|
||||
return 0;
|
||||
|
||||
float y = FLT_MAX;
|
||||
float lowestSkill = 0;
|
||||
|
||||
for (std::vector<ESM::ENAMstruct>::const_iterator it = spell->mEffects.mList.begin(); it != spell->mEffects.mList.end(); ++it)
|
||||
{
|
||||
float x = it->mDuration;
|
||||
const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find(
|
||||
it->mEffectID);
|
||||
if (!(magicEffect->mData.mFlags & ESM::MagicEffect::UncappedDamage))
|
||||
x = std::max(1.f, x);
|
||||
x *= 0.1 * magicEffect->mData.mBaseCost;
|
||||
x *= 0.5 * (it->mMagnMin + it->mMagnMax);
|
||||
x *= it->mArea * 0.05 * magicEffect->mData.mBaseCost;
|
||||
if (magicEffect->mData.mFlags & ESM::MagicEffect::CastTarget)
|
||||
x *= 1.5;
|
||||
static const float fEffectCostMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
|
||||
"fEffectCostMult")->getFloat();
|
||||
x *= fEffectCostMult;
|
||||
|
||||
float s = 2 * actor.getClass().getSkill(actor, spellSchoolToSkill(magicEffect->mData.mSchool));
|
||||
if (s - x < y)
|
||||
{
|
||||
y = s - x;
|
||||
if (effectiveSchool)
|
||||
*effectiveSchool = magicEffect->mData.mSchool;
|
||||
lowestSkill = s;
|
||||
}
|
||||
}
|
||||
|
||||
if (spell->mData.mType != ESM::Spell::ST_Spell)
|
||||
return 100;
|
||||
|
||||
if (spell->mData.mFlags & ESM::Spell::F_Always)
|
||||
return 100;
|
||||
|
||||
int castBonus = -stats.getMagicEffects().get(ESM::MagicEffect::Sound).mMagnitude;
|
||||
|
||||
int actorWillpower = stats.getAttribute(ESM::Attribute::Willpower).getModified();
|
||||
int actorLuck = stats.getAttribute(ESM::Attribute::Luck).getModified();
|
||||
|
||||
float castChance = (lowestSkill - spell->mData.mCost + castBonus + 0.2 * actorWillpower + 0.1 * actorLuck) * stats.getFatigueTerm();
|
||||
if (MWBase::Environment::get().getWorld()->getGodModeState() && actor.getRefData().getHandle() == "player")
|
||||
castChance = 100;
|
||||
|
||||
return std::max(0.f, std::min(100.f, castChance));
|
||||
}
|
||||
|
||||
float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool)
|
||||
{
|
||||
const ESM::Spell* spell =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find(spellId);
|
||||
return getSpellSuccessChance(spell, actor, effectiveSchool);
|
||||
}
|
||||
|
||||
|
||||
int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor)
|
||||
{
|
||||
int school = 0;
|
||||
getSpellSuccessChance(spellId, actor, &school);
|
||||
return school;
|
||||
}
|
||||
|
||||
int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor)
|
||||
{
|
||||
int school = 0;
|
||||
getSpellSuccessChance(spell, actor, &school);
|
||||
return school;
|
||||
}
|
||||
|
||||
float getEffectResistance (short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell)
|
||||
{
|
||||
const ESM::MagicEffect *magicEffect =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
|
||||
effectId);
|
||||
|
||||
const MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor);
|
||||
|
||||
float resisted = 0;
|
||||
if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful)
|
||||
{
|
||||
|
||||
short resistanceEffect = ESM::MagicEffect::getResistanceEffect(effectId);
|
||||
short weaknessEffect = ESM::MagicEffect::getWeaknessEffect(effectId);
|
||||
|
||||
float resistance = 0;
|
||||
if (resistanceEffect != -1)
|
||||
resistance += stats.getMagicEffects().get(resistanceEffect).mMagnitude;
|
||||
if (weaknessEffect != -1)
|
||||
resistance -= stats.getMagicEffects().get(weaknessEffect).mMagnitude;
|
||||
|
||||
|
||||
float willpower = stats.getAttribute(ESM::Attribute::Willpower).getModified();
|
||||
float luck = stats.getAttribute(ESM::Attribute::Luck).getModified();
|
||||
float x = (willpower + 0.1 * luck) * stats.getFatigueTerm();
|
||||
|
||||
// This makes spells that are easy to cast harder to resist and vice versa
|
||||
if (spell != NULL && caster.getClass().isActor())
|
||||
{
|
||||
float castChance = getSpellSuccessChance(spell, caster);
|
||||
if (castChance > 0)
|
||||
x *= 50 / castChance;
|
||||
}
|
||||
|
||||
float roll = static_cast<float>(std::rand()) / RAND_MAX * 100;
|
||||
if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)
|
||||
roll -= resistance;
|
||||
|
||||
if (x <= roll)
|
||||
x = 0;
|
||||
else
|
||||
{
|
||||
if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)
|
||||
x = 100;
|
||||
else
|
||||
x = roll / std::min(x, 100.f);
|
||||
}
|
||||
|
||||
x = std::min(x + resistance, 100.f);
|
||||
|
||||
resisted = x;
|
||||
}
|
||||
|
||||
return resisted;
|
||||
}
|
||||
|
||||
float getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell)
|
||||
{
|
||||
float resistance = getEffectResistance(effectId, actor, caster, spell);
|
||||
if (resistance >= 0)
|
||||
return 1 - resistance / 100.f;
|
||||
else
|
||||
return -(resistance-100) / 100.f;
|
||||
}
|
||||
|
||||
CastSpell::CastSpell(const MWWorld::Ptr &caster, const MWWorld::Ptr &target)
|
||||
: mCaster(caster)
|
||||
, mTarget(target)
|
||||
, mStack(false)
|
||||
, mHitPosition(0,0,0)
|
||||
{
|
||||
}
|
||||
|
||||
void CastSpell::inflict(const MWWorld::Ptr &target, const MWWorld::Ptr &caster,
|
||||
const ESM::EffectList &effects, ESM::RangeType range, bool reflected)
|
||||
const ESM::EffectList &effects, ESM::RangeType range, bool reflected, bool exploded)
|
||||
{
|
||||
// If none of the effects need to apply, we can early-out
|
||||
bool found = false;
|
||||
|
@ -213,11 +376,12 @@ namespace MWMechanics
|
|||
if (anim)
|
||||
anim->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, "");
|
||||
}
|
||||
|
||||
// TODO: For Area effects, launch a growing particle effect that applies the effect to more actors as it hits them. Best managed in World.
|
||||
}
|
||||
}
|
||||
|
||||
if (!exploded)
|
||||
MWBase::Environment::get().getWorld()->explodeSpell(mHitPosition, mTarget, effects, caster, mId, mSourceName);
|
||||
|
||||
if (reflectedEffects.mList.size())
|
||||
inflict(caster, target, reflectedEffects, range, true);
|
||||
|
||||
|
@ -230,7 +394,7 @@ namespace MWMechanics
|
|||
MWBase::Environment::get().getMechanicsManager()->commitCrime(caster, target, MWBase::MechanicsManager::OT_Assault);
|
||||
}
|
||||
|
||||
void CastSpell::applyInstantEffect(const MWWorld::Ptr &target, const MWWorld::Ptr &caster, MWMechanics::EffectKey effect, float magnitude)
|
||||
void CastSpell::applyInstantEffect(const MWWorld::Ptr &target, const MWWorld::Ptr &caster, const MWMechanics::EffectKey& effect, float magnitude)
|
||||
{
|
||||
short effectId = effect.mId;
|
||||
if (!target.getClass().isActor())
|
||||
|
@ -359,12 +523,11 @@ namespace MWMechanics
|
|||
|
||||
mStack = (enchantment->mData.mType == ESM::Enchantment::CastOnce);
|
||||
|
||||
if (enchantment->mData.mType == ESM::Enchantment::WhenUsed)
|
||||
// Check if there's enough charge left
|
||||
if (enchantment->mData.mType == ESM::Enchantment::WhenUsed || enchantment->mData.mType == ESM::Enchantment::WhenStrikes)
|
||||
{
|
||||
// Check if there's enough charge left
|
||||
const float enchantCost = enchantment->mData.mCost;
|
||||
MWMechanics::NpcStats &stats = MWWorld::Class::get(mCaster).getNpcStats(mCaster);
|
||||
int eSkill = stats.getSkill(ESM::Skill::Enchant).getModified();
|
||||
int eSkill = mCaster.getClass().getSkill(mCaster, ESM::Skill::Enchant);
|
||||
const int castCost = std::max(1.f, enchantCost - (enchantCost / 100) * (eSkill - 10));
|
||||
|
||||
if (item.getCellRef().mEnchantmentCharge == -1)
|
||||
|
@ -377,10 +540,15 @@ namespace MWMechanics
|
|||
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientCharge}");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reduce charge
|
||||
item.getCellRef().mEnchantmentCharge -= castCost;
|
||||
}
|
||||
|
||||
if (enchantment->mData.mType == ESM::Enchantment::WhenUsed)
|
||||
{
|
||||
if (mCaster.getRefData().getHandle() == "player")
|
||||
mCaster.getClass().skillUsageSucceeded (mCaster, ESM::Skill::Enchant, 1);
|
||||
}
|
||||
if (enchantment->mData.mType == ESM::Enchantment::CastOnce)
|
||||
item.getContainerStore()->remove(item, 1, mCaster);
|
||||
else if (enchantment->mData.mType != ESM::Enchantment::WhenStrikes)
|
||||
|
@ -389,9 +557,6 @@ namespace MWMechanics
|
|||
MWBase::Environment::get().getWindowManager()->setSelectedEnchantItem(item); // Set again to show the modified charge
|
||||
}
|
||||
|
||||
if (mCaster.getRefData().getHandle() == "player")
|
||||
mCaster.getClass().skillUsageSucceeded (mCaster, ESM::Skill::Enchant, 1);
|
||||
|
||||
inflict(mCaster, mCaster, enchantment->mEffects, ESM::RT_Self);
|
||||
|
||||
if (!mTarget.isEmpty())
|
||||
|
|
|
@ -1,33 +1,15 @@
|
|||
#ifndef MWMECHANICS_SPELLSUCCESS_H
|
||||
#define MWMECHANICS_SPELLSUCCESS_H
|
||||
|
||||
#include <cfloat>
|
||||
|
||||
#include "../mwbase/world.hpp"
|
||||
#include "../mwbase/environment.hpp"
|
||||
|
||||
#include "../mwworld/ptr.hpp"
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwmechanics/creaturestats.hpp"
|
||||
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
|
||||
#include "npcstats.hpp"
|
||||
#include <OgreVector3.h>
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
inline ESM::Skill::SkillEnum spellSchoolToSkill(int school)
|
||||
{
|
||||
std::map<int, ESM::Skill::SkillEnum> schoolSkillMap; // maps spell school to skill id
|
||||
schoolSkillMap[0] = ESM::Skill::Alteration;
|
||||
schoolSkillMap[1] = ESM::Skill::Conjuration;
|
||||
schoolSkillMap[3] = ESM::Skill::Illusion;
|
||||
schoolSkillMap[2] = ESM::Skill::Destruction;
|
||||
schoolSkillMap[4] = ESM::Skill::Mysticism;
|
||||
schoolSkillMap[5] = ESM::Skill::Restoration;
|
||||
assert(schoolSkillMap.find(school) != schoolSkillMap.end());
|
||||
return schoolSkillMap[school];
|
||||
}
|
||||
class EffectKey;
|
||||
|
||||
ESM::Skill::SkillEnum spellSchoolToSkill(int school);
|
||||
|
||||
/**
|
||||
* @param spell spell to cast
|
||||
|
@ -36,147 +18,16 @@ namespace MWMechanics
|
|||
* @attention actor has to be an NPC and not a creature!
|
||||
* @return success chance from 0 to 100 (in percent)
|
||||
*/
|
||||
inline float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool = NULL)
|
||||
{
|
||||
CreatureStats& stats = actor.getClass().getCreatureStats(actor);
|
||||
float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool = NULL);
|
||||
float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool = NULL);
|
||||
|
||||
if (stats.getMagicEffects().get(ESM::MagicEffect::Silence).mMagnitude)
|
||||
return 0;
|
||||
|
||||
float y = FLT_MAX;
|
||||
float lowestSkill = 0;
|
||||
|
||||
for (std::vector<ESM::ENAMstruct>::const_iterator it = spell->mEffects.mList.begin(); it != spell->mEffects.mList.end(); ++it)
|
||||
{
|
||||
float x = it->mDuration;
|
||||
const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find(
|
||||
it->mEffectID);
|
||||
if (!(magicEffect->mData.mFlags & ESM::MagicEffect::UncappedDamage))
|
||||
x = std::max(1.f, x);
|
||||
x *= 0.1 * magicEffect->mData.mBaseCost;
|
||||
x *= 0.5 * (it->mMagnMin + it->mMagnMax);
|
||||
x *= it->mArea * 0.05 * magicEffect->mData.mBaseCost;
|
||||
if (magicEffect->mData.mFlags & ESM::MagicEffect::CastTarget)
|
||||
x *= 1.5;
|
||||
static const float fEffectCostMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
|
||||
"fEffectCostMult")->getFloat();
|
||||
x *= fEffectCostMult;
|
||||
|
||||
float s = 2 * actor.getClass().getSkill(actor, spellSchoolToSkill(magicEffect->mData.mSchool));
|
||||
if (s - x < y)
|
||||
{
|
||||
y = s - x;
|
||||
if (effectiveSchool)
|
||||
*effectiveSchool = magicEffect->mData.mSchool;
|
||||
lowestSkill = s;
|
||||
}
|
||||
}
|
||||
|
||||
if (spell->mData.mType != ESM::Spell::ST_Spell)
|
||||
return 100;
|
||||
|
||||
if (spell->mData.mFlags & ESM::Spell::F_Always)
|
||||
return 100;
|
||||
|
||||
int castBonus = -stats.getMagicEffects().get(ESM::MagicEffect::Sound).mMagnitude;
|
||||
|
||||
int actorWillpower = stats.getAttribute(ESM::Attribute::Willpower).getModified();
|
||||
int actorLuck = stats.getAttribute(ESM::Attribute::Luck).getModified();
|
||||
|
||||
float castChance = (lowestSkill - spell->mData.mCost + castBonus + 0.2 * actorWillpower + 0.1 * actorLuck) * stats.getFatigueTerm();
|
||||
if (MWBase::Environment::get().getWorld()->getGodModeState() && actor.getRefData().getHandle() == "player")
|
||||
castChance = 100;
|
||||
|
||||
return std::max(0.f, std::min(100.f, castChance));
|
||||
}
|
||||
|
||||
inline float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool = NULL)
|
||||
{
|
||||
const ESM::Spell* spell =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find(spellId);
|
||||
return getSpellSuccessChance(spell, actor, effectiveSchool);
|
||||
}
|
||||
|
||||
inline int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor)
|
||||
{
|
||||
int school = 0;
|
||||
getSpellSuccessChance(spellId, actor, &school);
|
||||
return school;
|
||||
}
|
||||
|
||||
inline int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor)
|
||||
{
|
||||
int school = 0;
|
||||
getSpellSuccessChance(spell, actor, &school);
|
||||
return school;
|
||||
}
|
||||
int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor);
|
||||
int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor);
|
||||
|
||||
/// @return >=100 for fully resisted. can also return negative value for damage amplification.
|
||||
inline float getEffectResistance (short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell = NULL)
|
||||
{
|
||||
const ESM::MagicEffect *magicEffect =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
|
||||
effectId);
|
||||
|
||||
const MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor);
|
||||
|
||||
float resisted = 0;
|
||||
if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful)
|
||||
{
|
||||
|
||||
short resistanceEffect = ESM::MagicEffect::getResistanceEffect(effectId);
|
||||
short weaknessEffect = ESM::MagicEffect::getWeaknessEffect(effectId);
|
||||
|
||||
float resistance = 0;
|
||||
if (resistanceEffect != -1)
|
||||
resistance += stats.getMagicEffects().get(resistanceEffect).mMagnitude;
|
||||
if (weaknessEffect != -1)
|
||||
resistance -= stats.getMagicEffects().get(weaknessEffect).mMagnitude;
|
||||
|
||||
|
||||
float willpower = stats.getAttribute(ESM::Attribute::Willpower).getModified();
|
||||
float luck = stats.getAttribute(ESM::Attribute::Luck).getModified();
|
||||
float x = (willpower + 0.1 * luck) * stats.getFatigueTerm();
|
||||
|
||||
// This makes spells that are easy to cast harder to resist and vice versa
|
||||
if (spell != NULL && caster.getClass().isActor())
|
||||
{
|
||||
float castChance = getSpellSuccessChance(spell, caster);
|
||||
if (castChance > 0)
|
||||
x *= 50 / castChance;
|
||||
}
|
||||
|
||||
float roll = static_cast<float>(std::rand()) / RAND_MAX * 100;
|
||||
if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)
|
||||
roll -= resistance;
|
||||
|
||||
if (x <= roll)
|
||||
x = 0;
|
||||
else
|
||||
{
|
||||
if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)
|
||||
x = 100;
|
||||
else
|
||||
x = roll / std::min(x, 100.f);
|
||||
}
|
||||
|
||||
x = std::min(x + resistance, 100.f);
|
||||
|
||||
resisted = x;
|
||||
}
|
||||
|
||||
return resisted;
|
||||
}
|
||||
|
||||
inline float getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell = NULL)
|
||||
{
|
||||
float resistance = getEffectResistance(effectId, actor, caster, spell);
|
||||
if (resistance >= 0)
|
||||
return 1 - resistance / 100.f;
|
||||
else
|
||||
return -(resistance-100) / 100.f;
|
||||
}
|
||||
float getEffectResistance (short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell = NULL);
|
||||
|
||||
float getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell = NULL);
|
||||
|
||||
class CastSpell
|
||||
{
|
||||
|
@ -187,6 +38,7 @@ namespace MWMechanics
|
|||
bool mStack;
|
||||
std::string mId; // ID of spell, potion, item etc
|
||||
std::string mSourceName; // Display name for spell, potion, etc
|
||||
Ogre::Vector3 mHitPosition; // Used for spawning area orb
|
||||
|
||||
public:
|
||||
CastSpell(const MWWorld::Ptr& caster, const MWWorld::Ptr& target);
|
||||
|
@ -200,9 +52,9 @@ namespace MWMechanics
|
|||
bool cast (const std::string& id);
|
||||
|
||||
void inflict (const MWWorld::Ptr& target, const MWWorld::Ptr& caster,
|
||||
const ESM::EffectList& effects, ESM::RangeType range, bool reflected=false);
|
||||
const ESM::EffectList& effects, ESM::RangeType range, bool reflected=false, bool exploded=false);
|
||||
|
||||
void applyInstantEffect (const MWWorld::Ptr& target, const MWWorld::Ptr& caster, MWMechanics::EffectKey effect, float magnitude);
|
||||
void applyInstantEffect (const MWWorld::Ptr& target, const MWWorld::Ptr& caster, const MWMechanics::EffectKey& effect, float magnitude);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ namespace MWMechanics
|
|||
/// \brief Spell list
|
||||
///
|
||||
/// This class manages known spells as well as abilities, powers and permanent negative effects like
|
||||
/// diseaes.
|
||||
/// diseases.
|
||||
class Spells
|
||||
{
|
||||
public:
|
||||
|
|
|
@ -75,14 +75,15 @@ void Actors::insertNPC(const MWWorld::Ptr& ptr)
|
|||
delete mAllActors[ptr];
|
||||
mAllActors[ptr] = anim;
|
||||
mRendering->addWaterRippleEmitter (ptr);
|
||||
|
||||
// Create CustomData, will do autoEquip and trigger animation parts update
|
||||
ptr.getClass().getInventoryStore(ptr);
|
||||
}
|
||||
void Actors::insertCreature (const MWWorld::Ptr& ptr)
|
||||
void Actors::insertCreature (const MWWorld::Ptr& ptr, bool weaponsShields)
|
||||
{
|
||||
insertBegin(ptr);
|
||||
CreatureAnimation* anim = new CreatureAnimation(ptr);
|
||||
Animation* anim = NULL;
|
||||
if (weaponsShields)
|
||||
anim = new CreatureWeaponAnimation(ptr);
|
||||
else
|
||||
anim = new CreatureAnimation(ptr);
|
||||
delete mAllActors[ptr];
|
||||
mAllActors[ptr] = anim;
|
||||
mRendering->addWaterRippleEmitter (ptr);
|
||||
|
|
|
@ -40,7 +40,7 @@ namespace MWRender
|
|||
void setRootNode(Ogre::SceneNode* root);
|
||||
|
||||
void insertNPC(const MWWorld::Ptr& ptr);
|
||||
void insertCreature (const MWWorld::Ptr& ptr);
|
||||
void insertCreature (const MWWorld::Ptr& ptr, bool weaponsShields);
|
||||
void insertActivator (const MWWorld::Ptr& ptr);
|
||||
bool deleteObject (const MWWorld::Ptr& ptr);
|
||||
///< \return found?
|
||||
|
|
|
@ -584,7 +584,11 @@ bool Animation::reset(AnimState &state, const NifOgre::TextKeyMap &keys, const s
|
|||
|
||||
const std::string stoptag = groupname+": "+stop;
|
||||
NifOgre::TextKeyMap::const_iterator stopkey(groupstart);
|
||||
while(stopkey != keys.end() && stopkey->second != stoptag)
|
||||
while(stopkey != keys.end()
|
||||
// We have to ignore extra garbage at the end.
|
||||
// The Scrib's idle3 animation has "Idle3: Stop." instead of "Idle3: Stop".
|
||||
// Why, just why? :(
|
||||
&& (stopkey->second.size() < stoptag.size() || stopkey->second.substr(0,stoptag.size()) != stoptag))
|
||||
stopkey++;
|
||||
if(stopkey == keys.end())
|
||||
return false;
|
||||
|
@ -616,6 +620,13 @@ bool Animation::reset(AnimState &state, const NifOgre::TextKeyMap &keys, const s
|
|||
return true;
|
||||
}
|
||||
|
||||
void split(const std::string &s, char delim, std::vector<std::string> &elems) {
|
||||
std::stringstream ss(s);
|
||||
std::string item;
|
||||
while (std::getline(ss, item, delim)) {
|
||||
elems.push_back(item);
|
||||
}
|
||||
}
|
||||
|
||||
void Animation::handleTextKey(AnimState &state, const std::string &groupname, const NifOgre::TextKeyMap::const_iterator &key)
|
||||
{
|
||||
|
@ -630,14 +641,29 @@ void Animation::handleTextKey(AnimState &state, const std::string &groupname, co
|
|||
}
|
||||
if(evt.compare(0, 10, "soundgen: ") == 0)
|
||||
{
|
||||
std::string sound = MWWorld::Class::get(mPtr).getSoundIdFromSndGen(mPtr, evt.substr(10));
|
||||
std::string soundgen = evt.substr(10);
|
||||
|
||||
// The event can optionally contain volume and pitch modifiers
|
||||
float volume=1.f, pitch=1.f;
|
||||
if (soundgen.find(" ") != std::string::npos)
|
||||
{
|
||||
std::vector<std::string> tokens;
|
||||
split(soundgen, ' ', tokens);
|
||||
soundgen = tokens[0];
|
||||
if (tokens.size() >= 2)
|
||||
volume = Ogre::StringConverter::parseReal(tokens[1]);
|
||||
if (tokens.size() >= 3)
|
||||
pitch = Ogre::StringConverter::parseReal(tokens[2]);
|
||||
}
|
||||
|
||||
std::string sound = mPtr.getClass().getSoundIdFromSndGen(mPtr, soundgen);
|
||||
if(!sound.empty())
|
||||
{
|
||||
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
|
||||
MWBase::SoundManager::PlayType type = MWBase::SoundManager::Play_TypeSfx;
|
||||
if(evt.compare(10, evt.size()-10, "left") == 0 || evt.compare(10, evt.size()-10, "right") == 0)
|
||||
type = MWBase::SoundManager::Play_TypeFoot;
|
||||
sndMgr->playSound3D(mPtr, sound, 1.0f, 1.0f, type);
|
||||
sndMgr->playSound3D(mPtr, sound, volume, pitch, type);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -679,6 +705,9 @@ void Animation::handleTextKey(AnimState &state, const std::string &groupname, co
|
|||
|
||||
else if (groupname == "spellcast" && evt.substr(evt.size()-7, 7) == "release")
|
||||
MWBase::Environment::get().getWorld()->castSpell(mPtr);
|
||||
|
||||
else if (groupname == "shield" && evt.compare(off, len, "block hit") == 0)
|
||||
mPtr.getClass().block(mPtr);
|
||||
}
|
||||
|
||||
void Animation::changeGroups(const std::string &groupname, int groups)
|
||||
|
|
|
@ -142,7 +142,7 @@ namespace MWRender
|
|||
{
|
||||
mAnimation->updateParts();
|
||||
|
||||
MWWorld::InventoryStore &inv = MWWorld::Class::get(mCharacter).getInventoryStore(mCharacter);
|
||||
MWWorld::InventoryStore &inv = mCharacter.getClass().getInventoryStore(mCharacter);
|
||||
MWWorld::ContainerStoreIterator iter = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
|
||||
std::string groupname;
|
||||
if(iter == inv.end())
|
||||
|
|
|
@ -1,26 +1,27 @@
|
|||
#include "creatureanimation.hpp"
|
||||
|
||||
#include <OgreEntity.h>
|
||||
#include <OgreSkeletonInstance.h>
|
||||
#include <OgreBone.h>
|
||||
|
||||
#include "renderconst.hpp"
|
||||
|
||||
#include "../mwbase/world.hpp"
|
||||
|
||||
#include "../mwworld/class.hpp"
|
||||
|
||||
namespace MWRender
|
||||
{
|
||||
|
||||
CreatureAnimation::~CreatureAnimation()
|
||||
{
|
||||
}
|
||||
|
||||
CreatureAnimation::CreatureAnimation(const MWWorld::Ptr &ptr)
|
||||
: Animation(ptr, ptr.getRefData().getBaseNode())
|
||||
{
|
||||
MWWorld::LiveCellRef<ESM::Creature> *ref = mPtr.get<ESM::Creature>();
|
||||
|
||||
assert (ref->mBase != NULL);
|
||||
if(!ref->mBase->mModel.empty())
|
||||
std::string model = ptr.getClass().getModel(ptr);
|
||||
if(!model.empty())
|
||||
{
|
||||
std::string model = "meshes\\"+ref->mBase->mModel;
|
||||
|
||||
setObjectRoot(model, false);
|
||||
setRenderProperties(mObjectRoot, RV_Actors, RQG_Main, RQG_Alpha);
|
||||
|
||||
|
@ -30,4 +31,115 @@ CreatureAnimation::CreatureAnimation(const MWWorld::Ptr &ptr)
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
CreatureWeaponAnimation::CreatureWeaponAnimation(const MWWorld::Ptr &ptr)
|
||||
: Animation(ptr, ptr.getRefData().getBaseNode())
|
||||
, mShowWeapons(false)
|
||||
, mShowCarriedLeft(false)
|
||||
{
|
||||
MWWorld::LiveCellRef<ESM::Creature> *ref = mPtr.get<ESM::Creature>();
|
||||
|
||||
std::string model = ptr.getClass().getModel(ptr);
|
||||
if(!model.empty())
|
||||
{
|
||||
setObjectRoot(model, false);
|
||||
setRenderProperties(mObjectRoot, RV_Actors, RQG_Main, RQG_Alpha);
|
||||
|
||||
if((ref->mBase->mFlags&ESM::Creature::Bipedal))
|
||||
addAnimSource("meshes\\base_anim.nif");
|
||||
addAnimSource(model);
|
||||
|
||||
mPtr.getClass().getInventoryStore(mPtr).setListener(this, mPtr);
|
||||
|
||||
updateParts();
|
||||
}
|
||||
}
|
||||
|
||||
void CreatureWeaponAnimation::showWeapons(bool showWeapon)
|
||||
{
|
||||
if (showWeapon != mShowWeapons)
|
||||
{
|
||||
mShowWeapons = showWeapon;
|
||||
updateParts();
|
||||
}
|
||||
}
|
||||
|
||||
void CreatureWeaponAnimation::showCarriedLeft(bool show)
|
||||
{
|
||||
if (show != mShowCarriedLeft)
|
||||
{
|
||||
mShowCarriedLeft = show;
|
||||
updateParts();
|
||||
}
|
||||
}
|
||||
|
||||
void CreatureWeaponAnimation::updateParts()
|
||||
{
|
||||
mWeapon.setNull();
|
||||
mShield.setNull();
|
||||
|
||||
if (mShowWeapons)
|
||||
updatePart(mWeapon, MWWorld::InventoryStore::Slot_CarriedRight);
|
||||
if (mShowCarriedLeft)
|
||||
updatePart(mShield, MWWorld::InventoryStore::Slot_CarriedLeft);
|
||||
}
|
||||
|
||||
void CreatureWeaponAnimation::updatePart(NifOgre::ObjectScenePtr& scene, int slot)
|
||||
{
|
||||
MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr);
|
||||
MWWorld::ContainerStoreIterator it = inv.getSlot(slot);
|
||||
|
||||
if (it == inv.end())
|
||||
{
|
||||
scene.setNull();
|
||||
return;
|
||||
}
|
||||
MWWorld::Ptr item = *it;
|
||||
|
||||
std::string bonename;
|
||||
if (slot == MWWorld::InventoryStore::Slot_CarriedRight)
|
||||
bonename = "Weapon Bone";
|
||||
else
|
||||
bonename = "Shield Bone";
|
||||
|
||||
scene = NifOgre::Loader::createObjects(mSkelBase, bonename, mInsert, item.getClass().getModel(item));
|
||||
Ogre::Vector3 glowColor = getEnchantmentColor(item);
|
||||
|
||||
setRenderProperties(scene, RV_Actors, RQG_Main, RQG_Alpha, 0,
|
||||
!item.getClass().getEnchantment(item).empty(), &glowColor);
|
||||
|
||||
if(scene->mSkelBase)
|
||||
{
|
||||
Ogre::SkeletonInstance *skel = scene->mSkelBase->getSkeleton();
|
||||
if(scene->mSkelBase->isParentTagPoint())
|
||||
{
|
||||
Ogre::Node *root = scene->mSkelBase->getParentNode();
|
||||
if(skel->hasBone("BoneOffset"))
|
||||
{
|
||||
Ogre::Bone *offset = skel->getBone("BoneOffset");
|
||||
|
||||
root->translate(offset->getPosition());
|
||||
|
||||
// It appears that the BoneOffset rotation is completely bogus, at least for light models.
|
||||
//root->rotate(offset->getOrientation());
|
||||
root->pitch(Ogre::Degree(-90.0f));
|
||||
|
||||
root->scale(offset->getScale());
|
||||
root->setInitialState();
|
||||
}
|
||||
}
|
||||
updateSkeletonInstance(mSkelBase->getSkeleton(), skel);
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// type == ESM::PRT_Weapon should get an animation source based on the current offset
|
||||
// of the weapon attack animation (from its beginning, or start marker?)
|
||||
std::vector<Ogre::Controller<Ogre::Real> >::iterator ctrl(scene->mControllers.begin());
|
||||
for(;ctrl != scene->mControllers.end();ctrl++)
|
||||
{
|
||||
if(ctrl->getSource().isNull())
|
||||
ctrl->setSource(Ogre::SharedPtr<NullAnimationTime>(new NullAnimationTime()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#define GAME_RENDER_CREATUREANIMATION_H
|
||||
|
||||
#include "animation.hpp"
|
||||
#include "../mwworld/inventorystore.hpp"
|
||||
|
||||
namespace MWWorld
|
||||
{
|
||||
|
@ -14,7 +15,32 @@ namespace MWRender
|
|||
{
|
||||
public:
|
||||
CreatureAnimation(const MWWorld::Ptr& ptr);
|
||||
virtual ~CreatureAnimation();
|
||||
virtual ~CreatureAnimation() {}
|
||||
};
|
||||
|
||||
// For creatures with weapons and shields
|
||||
// Animation is already virtual anyway, so might as well make a separate class.
|
||||
// Most creatures don't need weapons/shields, so this will save some memory.
|
||||
class CreatureWeaponAnimation : public Animation, public MWWorld::InventoryStoreListener
|
||||
{
|
||||
public:
|
||||
CreatureWeaponAnimation(const MWWorld::Ptr& ptr);
|
||||
virtual ~CreatureWeaponAnimation() {}
|
||||
|
||||
virtual void equipmentChanged() { updateParts(); }
|
||||
|
||||
virtual void showWeapons(bool showWeapon);
|
||||
virtual void showCarriedLeft(bool show);
|
||||
|
||||
void updateParts();
|
||||
|
||||
void updatePart(NifOgre::ObjectScenePtr& scene, int slot);
|
||||
|
||||
private:
|
||||
NifOgre::ObjectScenePtr mWeapon;
|
||||
NifOgre::ObjectScenePtr mShield;
|
||||
bool mShowWeapons;
|
||||
bool mShowCarriedLeft;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -26,9 +26,10 @@ EffectManager::EffectManager(Ogre::SceneManager *sceneMgr)
|
|||
{
|
||||
}
|
||||
|
||||
void EffectManager::addEffect(const std::string &model, std::string textureOverride, const Ogre::Vector3 &worldPosition)
|
||||
void EffectManager::addEffect(const std::string &model, std::string textureOverride, const Ogre::Vector3 &worldPosition, float scale)
|
||||
{
|
||||
Ogre::SceneNode* sceneNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(worldPosition);
|
||||
sceneNode->setScale(scale,scale,scale);
|
||||
|
||||
// fix texture extension to .dds
|
||||
if (textureOverride.size() > 4)
|
||||
|
@ -78,7 +79,7 @@ void EffectManager::addEffect(const std::string &model, std::string textureOverr
|
|||
mEffects.push_back(std::make_pair(sceneNode, scene));
|
||||
}
|
||||
|
||||
void EffectManager::update(float dt)
|
||||
void EffectManager::update(float dt, Ogre::Camera* camera)
|
||||
{
|
||||
for (std::vector<std::pair<Ogre::SceneNode*, NifOgre::ObjectScenePtr> >::iterator it = mEffects.begin(); it != mEffects.end(); )
|
||||
{
|
||||
|
@ -91,6 +92,7 @@ void EffectManager::update(float dt)
|
|||
|
||||
objects->mControllers[i].update();
|
||||
}
|
||||
objects->rotateBillboardNodes(camera);
|
||||
|
||||
// Finished playing?
|
||||
if (objects->mControllers[0].getSource()->getValue() >= objects->mMaxControllerLength)
|
||||
|
|
|
@ -14,9 +14,9 @@ namespace MWRender
|
|||
~EffectManager() { clear(); }
|
||||
|
||||
/// Add an effect. When it's finished playing, it will be removed automatically.
|
||||
void addEffect (const std::string& model, std::string textureOverride, const Ogre::Vector3& worldPosition);
|
||||
void addEffect (const std::string& model, std::string textureOverride, const Ogre::Vector3& worldPosition, float scale);
|
||||
|
||||
void update(float dt);
|
||||
void update(float dt, Ogre::Camera* camera);
|
||||
|
||||
/// Remove all effects
|
||||
void clear();
|
||||
|
|
|
@ -378,7 +378,7 @@ void RenderingManager::update (float duration, bool paused)
|
|||
if(paused)
|
||||
return;
|
||||
|
||||
mEffectManager->update(duration);
|
||||
mEffectManager->update(duration, mRendering.getCamera());
|
||||
|
||||
mActors->update (mRendering.getCamera());
|
||||
mPlayerAnimation->preRender(mRendering.getCamera());
|
||||
|
@ -892,8 +892,6 @@ void RenderingManager::renderPlayer(const MWWorld::Ptr &ptr)
|
|||
mPlayerAnimation->~NpcAnimation();
|
||||
new(mPlayerAnimation) NpcAnimation(ptr, ptr.getRefData().getBaseNode(), RV_Actors);
|
||||
}
|
||||
// Ensure CustomData -> autoEquip -> animation update
|
||||
ptr.getClass().getInventoryStore(ptr);
|
||||
|
||||
mCamera->setAnimation(mPlayerAnimation);
|
||||
mWater->removeEmitter(ptr);
|
||||
|
@ -1026,9 +1024,9 @@ float RenderingManager::getCameraDistance() const
|
|||
return mCamera->getCameraDistance();
|
||||
}
|
||||
|
||||
void RenderingManager::spawnEffect(const std::string &model, const std::string &texture, const Vector3 &worldPosition)
|
||||
void RenderingManager::spawnEffect(const std::string &model, const std::string &texture, const Vector3 &worldPosition, float scale)
|
||||
{
|
||||
mEffectManager->addEffect(model, texture, worldPosition);
|
||||
mEffectManager->addEffect(model, "", worldPosition, scale);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
|
|
@ -207,7 +207,7 @@ public:
|
|||
void stopVideo();
|
||||
void frameStarted(float dt, bool paused);
|
||||
|
||||
void spawnEffect (const std::string& model, const std::string& texture, const Ogre::Vector3& worldPosition);
|
||||
void spawnEffect (const std::string& model, const std::string& texture, const Ogre::Vector3& worldPosition, float scale=1.f);
|
||||
|
||||
protected:
|
||||
virtual void windowResized(int x, int y);
|
||||
|
|
|
@ -437,6 +437,7 @@ namespace MWScript
|
|||
|
||||
MWMechanics::CreatureStats& creatureStats = actor.getClass().getCreatureStats(actor);
|
||||
|
||||
|
||||
creatureStats.setHostile(true);
|
||||
creatureStats.getAiSequence().stack(
|
||||
MWMechanics::AiCombat(MWBase::Environment::get().getWorld()->getPtr(targetID, true) ));
|
||||
|
|
|
@ -115,6 +115,7 @@ namespace MWScript
|
|||
|
||||
current = region->mName;
|
||||
}
|
||||
Misc::StringUtils::toLower(current);
|
||||
|
||||
bool match = current.length()>=name.length() &&
|
||||
current.substr (0, name.length())==name;
|
||||
|
|
|
@ -159,7 +159,7 @@ namespace MWScript
|
|||
std::string item = runtime.getStringLiteral (runtime[0].mInteger);
|
||||
runtime.pop();
|
||||
|
||||
MWWorld::InventoryStore& invStore = MWWorld::Class::get(ptr).getInventoryStore (ptr);
|
||||
MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore (ptr);
|
||||
MWWorld::ContainerStoreIterator it = invStore.begin();
|
||||
for (; it != invStore.end(); ++it)
|
||||
{
|
||||
|
@ -171,6 +171,9 @@ namespace MWScript
|
|||
|
||||
MWWorld::ActionEquip action (*it);
|
||||
action.execute(ptr);
|
||||
|
||||
if (ptr.getRefData().getHandle() == "player" && !ptr.getClass().getScript(ptr).empty())
|
||||
ptr.getRefData().getLocals().setVarByInt(ptr.getClass().getScript(ptr), "onpcequip", 1);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -745,6 +745,7 @@ namespace MWScript
|
|||
MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr (targetId, false);
|
||||
|
||||
MWMechanics::CastSpell cast(ptr, target);
|
||||
cast.mHitPosition = Ogre::Vector3(target.getRefData().getPosition().pos);
|
||||
cast.cast(spell);
|
||||
}
|
||||
};
|
||||
|
@ -761,6 +762,7 @@ namespace MWScript
|
|||
runtime.pop();
|
||||
|
||||
MWMechanics::CastSpell cast(ptr, ptr);
|
||||
cast.mHitPosition = Ogre::Vector3(ptr.getRefData().getPosition().pos);
|
||||
cast.cast(spell);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -640,6 +640,15 @@ namespace MWSound
|
|||
mListenerUp = up;
|
||||
}
|
||||
|
||||
void SoundManager::updatePtr(const MWWorld::Ptr &old, const MWWorld::Ptr &updated)
|
||||
{
|
||||
for (SoundMap::iterator snditer = mActiveSounds.begin(); snditer != mActiveSounds.end(); ++snditer)
|
||||
{
|
||||
if (snditer->second.first == old)
|
||||
snditer->second.first = updated;
|
||||
}
|
||||
}
|
||||
|
||||
// Default readAll implementation, for decoders that can't do anything
|
||||
// better
|
||||
void Sound_Decoder::readAll(std::vector<char> &output)
|
||||
|
|
|
@ -148,6 +148,8 @@ namespace MWSound
|
|||
virtual void update(float duration);
|
||||
|
||||
virtual void setListenerPosDir(const Ogre::Vector3 &pos, const Ogre::Vector3 &dir, const Ogre::Vector3 &up);
|
||||
|
||||
virtual void updatePtr (const MWWorld::Ptr& old, const MWWorld::Ptr& updated);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -19,9 +19,9 @@ namespace MWWorld
|
|||
void ActionEquip::executeImp (const Ptr& actor)
|
||||
{
|
||||
MWWorld::Ptr object = getTarget();
|
||||
MWWorld::InventoryStore& invStore = MWWorld::Class::get(actor).getInventoryStore(actor);
|
||||
MWWorld::InventoryStore& invStore = actor.getClass().getInventoryStore(actor);
|
||||
|
||||
std::pair <int, std::string> result = MWWorld::Class::get (object).canBeEquipped (object, actor);
|
||||
std::pair <int, std::string> result = object.getClass().canBeEquipped (object, actor);
|
||||
|
||||
// display error message if the player tried to equip something
|
||||
if (!result.second.empty() && actor == MWBase::Environment::get().getWorld()->getPlayerPtr())
|
||||
|
|
|
@ -8,6 +8,7 @@ namespace MWWorld
|
|||
void ActionTrap::executeImp(const Ptr &actor)
|
||||
{
|
||||
MWMechanics::CastSpell cast(mTrapSource, actor);
|
||||
cast.mHitPosition = Ogre::Vector3(actor.getRefData().getPosition().pos);
|
||||
cast.cast(mSpellId);
|
||||
|
||||
mTrapSource.getCellRef().mTrap = "";
|
||||
|
|
|
@ -92,6 +92,11 @@ namespace MWWorld
|
|||
throw std::runtime_error("class cannot hit");
|
||||
}
|
||||
|
||||
void Class::block(const Ptr &ptr) const
|
||||
{
|
||||
throw std::runtime_error("class cannot block");
|
||||
}
|
||||
|
||||
void Class::onHit(const Ptr& ptr, float damage, bool ishealth, const Ptr& object, const Ptr& attacker, bool successful) const
|
||||
{
|
||||
throw std::runtime_error("class cannot be hit");
|
||||
|
@ -122,6 +127,11 @@ namespace MWWorld
|
|||
throw std::runtime_error ("class does not have an inventory store");
|
||||
}
|
||||
|
||||
bool Class::hasInventoryStore(const Ptr &ptr) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void Class::lock (const Ptr& ptr, int lockLevel) const
|
||||
{
|
||||
throw std::runtime_error ("class does not support locking");
|
||||
|
|
|
@ -128,6 +128,10 @@ namespace MWWorld
|
|||
/// actor responsible for the attack, and \a successful specifies if the hit is
|
||||
/// successful or not.
|
||||
|
||||
virtual void block (const Ptr& ptr) const;
|
||||
///< Play the appropriate sound for a blocked attack, depending on the currently equipped shield
|
||||
/// (default implementation: throw an exception)
|
||||
|
||||
virtual void setActorHealth(const Ptr& ptr, float health, const Ptr& attacker=Ptr()) const;
|
||||
///< Sets a new current health value for the actor, optionally specifying the object causing
|
||||
/// the change. Use this instead of using CreatureStats directly as this will make sure the
|
||||
|
@ -150,6 +154,9 @@ namespace MWWorld
|
|||
///< Return inventory store or throw an exception, if class does not have a
|
||||
/// inventory store (default implementation: throw an exceoption)
|
||||
|
||||
virtual bool hasInventoryStore (const Ptr& ptr) const;
|
||||
///< Does this object have an inventory store, i.e. equipment slots? (default implementation: false)
|
||||
|
||||
virtual void lock (const Ptr& ptr, int lockLevel) const;
|
||||
///< Lock object (default implementation: throw an exception)
|
||||
|
||||
|
|
|
@ -62,6 +62,8 @@ namespace MWWorld
|
|||
|
||||
virtual ~ContainerStore();
|
||||
|
||||
virtual ContainerStore* clone() { return new ContainerStore(*this); }
|
||||
|
||||
ContainerStoreIterator begin (int mask = Type_All);
|
||||
|
||||
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
|
||||
if ((actorPtr.getRefData().getHandle() != "player")
|
||||
&& !(MWWorld::Class::get(actorPtr).getNpcStats(actorPtr).isWerewolf())
|
||||
&& !(actorPtr.getClass().isNpc() && actorPtr.getClass().getNpcStats(actorPtr).isWerewolf())
|
||||
&& !actorPtr.getClass().getCreatureStats(actorPtr).isDead())
|
||||
{
|
||||
std::string type = itemPtr.getTypeName();
|
||||
|
@ -457,7 +457,7 @@ int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor
|
|||
// If an armor/clothing item is removed, try to find a replacement,
|
||||
// but not for the player nor werewolves.
|
||||
if ((actor.getRefData().getHandle() != "player")
|
||||
&& !(MWWorld::Class::get(actor).getNpcStats(actor).isWerewolf()))
|
||||
&& !(actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()))
|
||||
{
|
||||
std::string type = item.getTypeName();
|
||||
if (((type == typeid(ESM::Armor).name()) || (type == typeid(ESM::Clothing).name()))
|
||||
|
|
|
@ -113,6 +113,8 @@ namespace MWWorld
|
|||
|
||||
InventoryStore& operator= (const InventoryStore& store);
|
||||
|
||||
virtual InventoryStore* clone() { return new InventoryStore(*this); }
|
||||
|
||||
virtual ContainerStoreIterator add (const Ptr& itemPtr, int count, const Ptr& actorPtr, bool setOwner=false);
|
||||
///< Add the item pointed to by \a ptr to this container. (Stacks automatically if needed)
|
||||
/// Auto-equip items if specific conditions are fulfilled (see the implementation).
|
||||
|
|
|
@ -487,7 +487,6 @@ namespace MWWorld
|
|||
insertCellRefList(mRendering, cell.mContainers, cell, *mPhysics, rescale, loadingListener);
|
||||
insertCellRefList(mRendering, cell.mDoors, cell, *mPhysics, rescale, loadingListener);
|
||||
insertCellRefList(mRendering, cell.mIngreds, cell, *mPhysics, rescale, loadingListener);
|
||||
insertCellRefList(mRendering, cell.mCreatureLists, cell, *mPhysics, rescale, loadingListener);
|
||||
insertCellRefList(mRendering, cell.mItemLists, cell, *mPhysics, rescale, loadingListener);
|
||||
insertCellRefList(mRendering, cell.mLights, cell, *mPhysics, rescale, loadingListener);
|
||||
insertCellRefList(mRendering, cell.mLockpicks, cell, *mPhysics, rescale, loadingListener);
|
||||
|
@ -499,6 +498,8 @@ namespace MWWorld
|
|||
// Load NPCs and creatures _after_ everything else (important for adjustPosition to work correctly)
|
||||
insertCellRefList(mRendering, cell.mCreatures, cell, *mPhysics, rescale, loadingListener);
|
||||
insertCellRefList(mRendering, cell.mNpcs, cell, *mPhysics, rescale, loadingListener);
|
||||
// Since this adds additional creatures, load afterwards, or they would be loaded twice
|
||||
insertCellRefList(mRendering, cell.mCreatureLists, cell, *mPhysics, rescale, loadingListener);
|
||||
}
|
||||
|
||||
void Scene::addObjectToScene (const Ptr& ptr)
|
||||
|
|
|
@ -56,7 +56,7 @@ void Store<ESM::Cell>::load(ESM::ESMReader &esm, const std::string &id)
|
|||
// copy list into new cell
|
||||
cell->mContextList = oldcell->mContextList;
|
||||
// have new cell replace old cell
|
||||
*oldcell = *cell;
|
||||
ESM::Cell::merge(oldcell, cell);
|
||||
} else
|
||||
mInt[idLower] = *cell;
|
||||
}
|
||||
|
@ -83,7 +83,7 @@ void Store<ESM::Cell>::load(ESM::ESMReader &esm, const std::string &id)
|
|||
}
|
||||
cell->mMovedRefs = oldcell->mMovedRefs;
|
||||
// have new cell replace old cell
|
||||
*oldcell = *cell;
|
||||
ESM::Cell::merge(oldcell, cell);
|
||||
} else
|
||||
mExt[std::make_pair(cell->mData.mX, cell->mData.mY)] = *cell;
|
||||
}
|
||||
|
|
|
@ -821,6 +821,8 @@ namespace MWWorld
|
|||
if(anim != NULL)
|
||||
{
|
||||
Ogre::Node *node = anim->getNode("Head");
|
||||
if (node == NULL)
|
||||
node = anim->getNode("Bip01 Head");
|
||||
if(node != NULL)
|
||||
pos += node->_getDerivedPosition();
|
||||
}
|
||||
|
@ -903,6 +905,7 @@ namespace MWWorld
|
|||
MWWorld::Class::get(ptr).copyToCell(ptr, newCell, pos);
|
||||
|
||||
mRendering->updateObjectCell(ptr, copy);
|
||||
MWBase::Environment::get().getSoundManager()->updatePtr (ptr, copy);
|
||||
|
||||
MWBase::MechanicsManager *mechMgr = MWBase::Environment::get().getMechanicsManager();
|
||||
mechMgr->updateCell(ptr, copy);
|
||||
|
@ -2097,13 +2100,14 @@ namespace MWWorld
|
|||
void World::castSpell(const Ptr &actor)
|
||||
{
|
||||
MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor);
|
||||
InventoryStore& inv = actor.getClass().getInventoryStore(actor);
|
||||
|
||||
MWWorld::Ptr target = getFacedObject();
|
||||
|
||||
std::string selectedSpell = stats.getSpells().getSelectedSpell();
|
||||
|
||||
MWMechanics::CastSpell cast(actor, target);
|
||||
if (!target.isEmpty())
|
||||
cast.mHitPosition = Ogre::Vector3(target.getRefData().getPosition().pos);
|
||||
|
||||
if (!selectedSpell.empty())
|
||||
{
|
||||
|
@ -2111,9 +2115,11 @@ namespace MWWorld
|
|||
|
||||
cast.cast(spell);
|
||||
}
|
||||
else if (inv.getSelectedEnchantItem() != inv.end())
|
||||
else if (actor.getClass().hasInventoryStore(actor))
|
||||
{
|
||||
cast.cast(*inv.getSelectedEnchantItem());
|
||||
MWWorld::InventoryStore& inv = actor.getClass().getInventoryStore(actor);
|
||||
if (inv.getSelectedEnchantItem() != inv.end())
|
||||
cast.cast(*inv.getSelectedEnchantItem());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2170,11 +2176,18 @@ namespace MWWorld
|
|||
state.mId = id;
|
||||
state.mActorHandle = actor.getRefData().getHandle();
|
||||
state.mSpeed = speed;
|
||||
state.mEffects = effects;
|
||||
state.mStack = stack;
|
||||
|
||||
// Only interested in "on target" effects
|
||||
for (std::vector<ESM::ENAMstruct>::const_iterator iter (effects.mList.begin());
|
||||
iter!=effects.mList.end(); ++iter)
|
||||
{
|
||||
if (iter->mRange == ESM::RT_Target)
|
||||
state.mEffects.mList.push_back(*iter);
|
||||
}
|
||||
|
||||
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
|
||||
sndMgr->playSound3D(ptr, sound, 1.0f, 1.0f);
|
||||
sndMgr->playSound3D(ptr, sound, 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop);
|
||||
|
||||
mProjectiles[ptr] = state;
|
||||
}
|
||||
|
@ -2186,6 +2199,7 @@ namespace MWWorld
|
|||
{
|
||||
if (!mWorldScene->isCellActive(*it->first.getCell()))
|
||||
{
|
||||
deleteObject(it->first);
|
||||
mProjectiles.erase(it++);
|
||||
continue;
|
||||
}
|
||||
|
@ -2218,11 +2232,10 @@ namespace MWWorld
|
|||
if (obstacle == ptr)
|
||||
continue;
|
||||
|
||||
explode = true;
|
||||
|
||||
MWWorld::Ptr caster = searchPtrViaHandle(it->second.mActorHandle);
|
||||
if (caster.isEmpty())
|
||||
caster = obstacle;
|
||||
|
||||
if (obstacle.isEmpty())
|
||||
{
|
||||
// Terrain
|
||||
|
@ -2230,19 +2243,23 @@ namespace MWWorld
|
|||
else
|
||||
{
|
||||
MWMechanics::CastSpell cast(caster, obstacle);
|
||||
cast.mStack = it->second.mStack;
|
||||
cast.mHitPosition = pos;
|
||||
cast.mId = it->second.mId;
|
||||
cast.mSourceName = it->second.mSourceName;
|
||||
cast.inflict(obstacle, caster, it->second.mEffects, ESM::RT_Target, false);
|
||||
cast.mStack = it->second.mStack;
|
||||
cast.inflict(obstacle, caster, it->second.mEffects, ESM::RT_Target, false, true);
|
||||
}
|
||||
|
||||
deleteObject(ptr);
|
||||
mProjectiles.erase(it++);
|
||||
explode = true;
|
||||
}
|
||||
|
||||
if (explode)
|
||||
{
|
||||
// TODO: Explode
|
||||
MWWorld::Ptr caster = searchPtrViaHandle(it->second.mActorHandle);
|
||||
explodeSpell(Ogre::Vector3(ptr.getRefData().getPosition().pos), ptr, it->second.mEffects, caster, it->second.mId, it->second.mSourceName);
|
||||
|
||||
deleteObject(ptr);
|
||||
mProjectiles.erase(it++);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -2282,7 +2299,7 @@ namespace MWWorld
|
|||
void World::breakInvisibility(const Ptr &actor)
|
||||
{
|
||||
actor.getClass().getCreatureStats(actor).getActiveSpells().purgeEffect(ESM::MagicEffect::Invisibility);
|
||||
if (actor.getClass().isNpc())
|
||||
if (actor.getClass().hasInventoryStore(actor))
|
||||
actor.getClass().getInventoryStore(actor).purgeEffect(ESM::MagicEffect::Invisibility);
|
||||
}
|
||||
|
||||
|
@ -2638,4 +2655,67 @@ namespace MWWorld
|
|||
|
||||
mRendering->spawnEffect(model, texture, worldPosition);
|
||||
}
|
||||
|
||||
void World::explodeSpell(const Vector3 &origin, const MWWorld::Ptr& object, const ESM::EffectList &effects, const Ptr &caster,
|
||||
const std::string& id, const std::string& sourceName)
|
||||
{
|
||||
std::map<MWWorld::Ptr, std::vector<ESM::ENAMstruct> > toApply;
|
||||
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt = effects.mList.begin();
|
||||
effectIt != effects.mList.end(); ++effectIt)
|
||||
{
|
||||
const ESM::MagicEffect* effect = getStore().get<ESM::MagicEffect>().find(effectIt->mEffectID);
|
||||
|
||||
if (effectIt->mArea <= 0)
|
||||
continue; // Not an area effect
|
||||
|
||||
// Spawn the explosion orb effect
|
||||
const ESM::Static* areaStatic;
|
||||
if (!effect->mCasting.empty())
|
||||
areaStatic = getStore().get<ESM::Static>().find (effect->mArea);
|
||||
else
|
||||
areaStatic = getStore().get<ESM::Static>().find ("VFX_DefaultArea");
|
||||
|
||||
mRendering->spawnEffect("meshes\\" + areaStatic->mModel, "", origin, effectIt->mArea);
|
||||
|
||||
// Play explosion sound (make sure to use NoTrack, since we will delete the projectile now)
|
||||
static const std::string schools[] = {
|
||||
"alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration"
|
||||
};
|
||||
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
|
||||
if(!effect->mAreaSound.empty())
|
||||
sndMgr->playSound3D(object, effect->mAreaSound, 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_NoTrack);
|
||||
else
|
||||
sndMgr->playSound3D(object, schools[effect->mData.mSchool]+" area", 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_NoTrack);
|
||||
|
||||
// Get the actors in range of the effect
|
||||
std::vector<MWWorld::Ptr> objects;
|
||||
MWBase::Environment::get().getMechanicsManager()->getObjectsInRange(
|
||||
origin, feetToGameUnits(effectIt->mArea), objects);
|
||||
|
||||
for (std::vector<MWWorld::Ptr>::iterator affected = objects.begin(); affected != objects.end(); ++affected)
|
||||
toApply[*affected].push_back(*effectIt);
|
||||
}
|
||||
|
||||
// Now apply the appropriate effects to each actor in range
|
||||
for (std::map<MWWorld::Ptr, std::vector<ESM::ENAMstruct> >::iterator apply = toApply.begin(); apply != toApply.end(); ++apply)
|
||||
{
|
||||
MWWorld::Ptr source = caster;
|
||||
// Vanilla-compatible behaviour of never applying the spell to the caster
|
||||
// (could be changed by mods later)
|
||||
if (apply->first == caster)
|
||||
continue;
|
||||
|
||||
if (source.isEmpty())
|
||||
source = apply->first;
|
||||
|
||||
MWMechanics::CastSpell cast(source, apply->first);
|
||||
cast.mHitPosition = origin;
|
||||
cast.mId = id;
|
||||
cast.mSourceName = sourceName;
|
||||
cast.mStack = false;
|
||||
ESM::EffectList effects;
|
||||
effects.mList = apply->second;
|
||||
cast.inflict(apply->first, caster, effects, ESM::RT_Target, false, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -552,6 +552,9 @@ namespace MWWorld
|
|||
|
||||
/// Spawn a blood effect for \a ptr at \a worldPosition
|
||||
virtual void spawnBloodEffect (const MWWorld::Ptr& ptr, const Ogre::Vector3& worldPosition);
|
||||
|
||||
virtual void explodeSpell (const Ogre::Vector3& origin, const MWWorld::Ptr& object, const ESM::EffectList& effects,
|
||||
const MWWorld::Ptr& caster, const std::string& id, const std::string& sourceName);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -47,9 +47,13 @@ void Cell::load(ESMReader &esm, bool saveContext)
|
|||
esm.getHT(waterl);
|
||||
mWater = (float) waterl;
|
||||
mWaterInt = true;
|
||||
mHasWaterLevelRecord = true;
|
||||
}
|
||||
else if (esm.isNextSub("WHGT"))
|
||||
{
|
||||
esm.getHT(mWater);
|
||||
mHasWaterLevelRecord = true;
|
||||
}
|
||||
|
||||
// Quasi-exterior cells have a region (which determines the
|
||||
// weather), pure interior cells have ambient lighting
|
||||
|
@ -94,7 +98,7 @@ void Cell::save(ESMWriter &esm) const
|
|||
esm.writeHNT("DATA", mData, 12);
|
||||
if (mData.mFlags & Interior)
|
||||
{
|
||||
if (mWater != -1) {
|
||||
if (mHasWaterLevelRecord) {
|
||||
if (mWaterInt) {
|
||||
int water =
|
||||
(mWater >= 0) ? (int) (mWater + 0.5) : (int) (mWater - 0.5);
|
||||
|
@ -301,4 +305,17 @@ bool Cell::getNextMVRF(ESMReader &esm, MovedCellRef &mref)
|
|||
mAmbi.mFog = 0;
|
||||
mAmbi.mFogDensity = 0;
|
||||
}
|
||||
|
||||
void Cell::merge(Cell *original, Cell *modified)
|
||||
{
|
||||
float waterLevel = original->mWater;
|
||||
if (modified->mHasWaterLevelRecord)
|
||||
{
|
||||
waterLevel = modified->mWater;
|
||||
}
|
||||
// else: keep original water level, instead of resetting to 0
|
||||
|
||||
*original = *modified;
|
||||
original->mWater = waterLevel;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -77,7 +77,10 @@ struct Cell
|
|||
float mFogDensity;
|
||||
};
|
||||
|
||||
Cell() : mWater(-1) {}
|
||||
Cell() : mWater(0), mHasWaterLevelRecord(false) {}
|
||||
|
||||
/// Merge \a modified into \a original
|
||||
static void merge (Cell* original, Cell* modified);
|
||||
|
||||
// Interior cells are indexed by this (it's the 'id'), for exterior
|
||||
// cells it is optional.
|
||||
|
@ -90,6 +93,7 @@ struct Cell
|
|||
DATAstruct mData;
|
||||
AMBIstruct mAmbi;
|
||||
float mWater; // Water level
|
||||
bool mHasWaterLevelRecord;
|
||||
bool mWaterInt;
|
||||
int mMapColor;
|
||||
int mNAM0;
|
||||
|
|
|
@ -70,7 +70,7 @@ struct DialInfo
|
|||
// Sound and text associated with this item
|
||||
std::string mSound, mResponse;
|
||||
|
||||
// Result script (uncomiled) to run whenever this dialog item is
|
||||
// Result script (uncompiled) to run whenever this dialog item is
|
||||
// selected
|
||||
std::string mResultScript;
|
||||
|
||||
|
|
|
@ -917,6 +917,8 @@ class NIFObjectLoader
|
|||
{
|
||||
int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, ctrl->target->recIndex);
|
||||
Ogre::Bone *trgtbone = scene->mSkelBase->getSkeleton()->getBone(trgtid);
|
||||
// The keyframe controller will control this bone manually
|
||||
trgtbone->setManuallyControlled(true);
|
||||
Ogre::ControllerValueRealPtr srcval((animflags&Nif::NiNode::AnimFlag_AutoPlay) ?
|
||||
Ogre::ControllerManager::getSingleton().getFrameTimeSource() :
|
||||
Ogre::ControllerValueRealPtr());
|
||||
|
|
|
@ -433,6 +433,7 @@ void QuadTreeNode::prepareForCompositeMap(Ogre::TRect<float> area)
|
|||
LayerInfo info;
|
||||
info.mDiffuseMap = "textures\\_land_default.dds";
|
||||
info.mParallax = false;
|
||||
info.mSpecular = false;
|
||||
layer.push_back(info);
|
||||
matGen.setLayerList(layer);
|
||||
makeQuad(sceneMgr, area.left, area.top, area.right, area.bottom, matGen.generateForCompositeMapRTT(Ogre::MaterialPtr()));
|
||||
|
|
Loading…
Reference in a new issue