diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 3b533b416..41cc320ad 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -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 diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 5d04b985f..f18c8a8b8 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -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 action = diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index 726c8cf04..914fbded2 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -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& objects) = 0; }; } diff --git a/apps/openmw/mwbase/soundmanager.hpp b/apps/openmw/mwbase/soundmanager.hpp index 4d764597c..98b733c41 100644 --- a/apps/openmw/mwbase/soundmanager.hpp +++ b/apps/openmw/mwbase/soundmanager.hpp @@ -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; }; } diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 3ad716b72..92a1a4143 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -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 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; }; } diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index 5e37426c9..83bda25d1 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -289,7 +289,7 @@ namespace MWClass std::pair 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()->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().find(npcRace); - if(race->mData.mFlags & ESM::Race::Beast) + if (npc.getClass().isNpc()) { - std::vector parts = ptr.get()->mBase->mParts.mParts; + std::string npcRace = npc.get()->mBase->mRace; - for(std::vector::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().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 parts = ptr.get()->mBase->mParts.mParts; + + for(std::vector::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}"); + } } } diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index 62fc26a71..a135585eb 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -238,20 +238,23 @@ namespace MWClass if (slots_.first.empty()) return std::make_pair(0, ""); - std::string npcRace = npc.get()->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().find(npcRace); - if(race->mData.mFlags & ESM::Race::Beast) + if (npc.getClass().isNpc()) { - std::vector parts = ptr.get()->mBase->mParts.mParts; + std::string npcRace = npc.get()->mBase->mRace; - for(std::vector::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().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 parts = ptr.get()->mBase->mParts.mParts; + + for(std::vector::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}"); + } } } diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index a97268318..c23b9e23a 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -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 *ref = ptr.get(); + 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 *ref = ptr.get(); + const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); + 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 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 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()->mBase->mData.mReach; + } + std::pair 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()->mBase->mData.mChop; + else if(type == MWMechanics::CreatureStats::AT_Slash) + attack = weapon.get()->mBase->mData.mSlash; + else if(type == MWMechanics::CreatureStats::AT_Thrust) + attack = weapon.get()->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()->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().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 (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 (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 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 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(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 (*ptr.getRefData().getCustomData()).mContainerStore; + return *dynamic_cast (*ptr.getRefData().getCustomData()).mContainerStore; + } + + MWWorld::InventoryStore& Creature::getInventoryStore(const MWWorld::Ptr &ptr) const + { + MWWorld::LiveCellRef *ref = ptr.get(); + + if (ref->mBase->mFlags & ESM::Creature::Weapon) + return dynamic_cast(getContainerStore(ptr)); + else + throw std::runtime_error("this creature has no inventory store"); + } + + bool Creature::hasInventoryStore(const MWWorld::Ptr &ptr) const + { + MWWorld::LiveCellRef *ref = ptr.get(); + + return (ref->mBase->mFlags & ESM::Creature::Weapon); } std::string Creature::getScript (const MWWorld::Ptr& ptr) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mScript; } diff --git a/apps/openmw/mwclass/creature.hpp b/apps/openmw/mwclass/creature.hpp index d518d0056..ca8abc040 100644 --- a/apps/openmw/mwclass/creature.hpp +++ b/apps/openmw/mwclass/creature.hpp @@ -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 diff --git a/apps/openmw/mwclass/creaturelevlist.cpp b/apps/openmw/mwclass/creaturelevlist.cpp index 53dd34bb4..ea586e5b6 100644 --- a/apps/openmw/mwclass/creaturelevlist.cpp +++ b/apps/openmw/mwclass/creaturelevlist.cpp @@ -3,6 +3,24 @@ #include +#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 data (new CustomData); + + MWWorld::LiveCellRef *ref = + ptr.get(); + + 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()); + } + } } diff --git a/apps/openmw/mwclass/creaturelevlist.hpp b/apps/openmw/mwclass/creaturelevlist.hpp index 81965efd5..d2c02043e 100644 --- a/apps/openmw/mwclass/creaturelevlist.hpp +++ b/apps/openmw/mwclass/creaturelevlist.hpp @@ -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 }; } diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index f93a3e342..8c6e89544 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -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().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::iterator it = store.get().begin(); + for (; it != store.get().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().find(ref->mBase->mRace); + for (std::vector::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()->mBase->mData.mReach : gmst.find("fHandToHandReach")->getFloat()); + // TODO: Use second to work out the hit angle std::pair 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()->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; + } diff --git a/apps/openmw/mwclass/npc.hpp b/apps/openmw/mwclass/npc.hpp index 497d0ced8..2bd91d198 100644 --- a/apps/openmw/mwclass/npc.hpp +++ b/apps/openmw/mwclass/npc.hpp @@ -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 activate (const MWWorld::Ptr& ptr, diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index b2107c329..7fbebb9d7 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -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); } } diff --git a/apps/openmw/mwdialogue/filter.cpp b/apps/openmw/mwdialogue/filter.cpp index f132d13a3..731fafbf6 100644 --- a/apps/openmw/mwdialogue/filter.cpp +++ b/apps/openmw/mwdialogue/filter.cpp @@ -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(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: diff --git a/apps/openmw/mwdialogue/quest.cpp b/apps/openmw/mwdialogue/quest.cpp index 5e2739be1..520331bc1 100644 --- a/apps/openmw/mwdialogue/quest.cpp +++ b/apps/openmw/mwdialogue/quest.cpp @@ -39,21 +39,24 @@ namespace MWDialogue const ESM::Dialogue *dialogue = MWBase::Environment::get().getWorld()->getStore().get().find (mTopic); + bool found=false; for (std::vector::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 diff --git a/apps/openmw/mwgui/companionitemmodel.cpp b/apps/openmw/mwgui/companionitemmodel.cpp index 3212ed701..bb6cf2800 100644 --- a/apps/openmw/mwgui/companionitemmodel.cpp +++ b/apps/openmw/mwgui/companionitemmodel.cpp @@ -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); diff --git a/apps/openmw/mwgui/enchantingdialog.cpp b/apps/openmw/mwgui/enchantingdialog.cpp index d9d2a2ea8..92205c3e9 100644 --- a/apps/openmw/mwgui/enchantingdialog.cpp +++ b/apps/openmw/mwgui/enchantingdialog.cpp @@ -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().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) diff --git a/apps/openmw/mwgui/inventoryitemmodel.cpp b/apps/openmw/mwgui/inventoryitemmodel.cpp index d26feba88..97e1e9a2b 100644 --- a/apps/openmw/mwgui/inventoryitemmodel.cpp +++ b/apps/openmw/mwgui/inventoryitemmodel.cpp @@ -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; slotsecond.isFinished ()) continue; - /// \todo quest.getName() is broken? returns empty string - //const MWDialogue::Quest& quest = i->second; - - visitor (reinterpret_cast (&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 (&i->second), toUtf8Span (i->first)); + else + visitor (reinterpret_cast (&i->second), toUtf8Span (quest.getName())); } } diff --git a/apps/openmw/mwgui/messagebox.cpp b/apps/openmw/mwgui/messagebox.cpp index 644b8f66a..74231c008 100644 --- a/apps/openmw/mwgui/messagebox.cpp +++ b/apps/openmw/mwgui/messagebox.cpp @@ -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 diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index e1d430307..ff13ae1af 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -3,8 +3,15 @@ #include #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); diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp index 857dd76d5..469d7188e 100644 --- a/apps/openmw/mwgui/spellcreationdialog.cpp +++ b/apps/openmw/mwgui/spellcreationdialog.cpp @@ -3,13 +3,17 @@ #include #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" diff --git a/apps/openmw/mwgui/spellwindow.cpp b/apps/openmw/mwgui/spellwindow.cpp index 21257ef9c..07076159c 100644 --- a/apps/openmw/mwgui/spellwindow.cpp +++ b/apps/openmw/mwgui/spellwindow.cpp @@ -4,11 +4,15 @@ #include #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(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); diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index b52c8e3dd..0fe500879 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -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); diff --git a/apps/openmw/mwgui/tradeitemmodel.cpp b/apps/openmw/mwgui/tradeitemmodel.cpp index 28216ad14..88c14b791 100644 --- a/apps/openmw/mwgui/tradeitemmodel.cpp +++ b/apps/openmw/mwgui/tradeitemmodel.cpp @@ -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); diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index f86044841..92ba9470d 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -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::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(0,std::min(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); diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 84b388f97..35783bd9f 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -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().find("fSoulgemMult")->getFloat(); float creatureSoulValue = mCreature.get()->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& 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); + } + } } diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index b7544dad4..78aae6861 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -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& out); + private: PtrControllerMap mActors; diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 5ae2a0ca1..ba3d06c62 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -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 { diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 853d0ff7b..77b52f5be 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -7,8 +7,8 @@ #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/dialoguemanager.hpp" -#include "../mwmechanics/npcstats.hpp" +#include "creaturestats.hpp" #include 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().find("fVoiceIdleOdds")->getFloat(); int roll = std::rand()/ (static_cast (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().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().find("fGreetDistanceReset")->getFloat(); + if (playerDist >= fGreetDistanceReset * iGreetDistanceMultiplier) + mSaidGreeting = false; + } + + // Check if idle animation finished if(!checkIdle(actor, mPlayedIdle)) { mPlayedIdle = 0; diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index 48bc62c62..9a44aa065 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -32,6 +32,8 @@ namespace MWMechanics std::vector mIdle; bool mRepeat; + bool mSaidGreeting; + float mX; float mY; float mZ; diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 954eb18b3..4acdb4462 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -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 (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(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(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"; } } -} \ No newline at end of file +} diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 6950854c8..b89cb0c12 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -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 */ diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp new file mode 100644 index 000000000..204264106 --- /dev/null +++ b/apps/openmw/mwmechanics/combat.cpp @@ -0,0 +1,137 @@ +#include "combat.hpp" + +#include + +#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& gmst = MWBase::Environment::get().getWorld()->getStore().get(); + 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 (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 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()->mBase->mData.mFlags & ESM::Weapon::Silver + || weapon.get()->mBase->mData.mFlags & ESM::Weapon::Magical)) + damage *= multiplier; + + if (weapon.get()->mBase->mData.mFlags & ESM::Weapon::Silver + & actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) + damage *= MWBase::Environment::get().getWorld()->getStore().get().find("fWereWolfSilverWeaponDamageMult")->getFloat(); + + if (damage == 0 && attacker.getRefData().getHandle() == "player") + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResistsWeapons}"); + } + +} diff --git a/apps/openmw/mwmechanics/combat.hpp b/apps/openmw/mwmechanics/combat.hpp new file mode 100644 index 000000000..7f2415697 --- /dev/null +++ b/apps/openmw/mwmechanics/combat.hpp @@ -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 diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index 8d37e34c8..c6522f08f 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -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; + } + } diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index 308883fc5..0501eb286 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -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 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 { diff --git a/apps/openmw/mwmechanics/enchanting.hpp b/apps/openmw/mwmechanics/enchanting.hpp index 988ce41fc..ae0b25a4a 100644 --- a/apps/openmw/mwmechanics/enchanting.hpp +++ b/apps/openmw/mwmechanics/enchanting.hpp @@ -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); diff --git a/apps/openmw/mwmechanics/levelledlist.hpp b/apps/openmw/mwmechanics/levelledlist.hpp index d65503011..120616f9f 100644 --- a/apps/openmw/mwmechanics/levelledlist.hpp +++ b/apps/openmw/mwmechanics/levelledlist.hpp @@ -6,6 +6,7 @@ #include "../mwworld/class.hpp" #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" +#include "../mwmechanics/creaturestats.hpp" namespace MWMechanics { diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 0dee4706a..a0183e973 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -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= target); } + + void MechanicsManager::getObjectsInRange(const Ogre::Vector3 &position, float radius, std::vector &objects) + { + mActors.getObjectsInRange(position, radius, objects); + mObjects.getObjectsInRange(position, radius, objects); + } } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 469123df9..012de2e32 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -135,6 +135,8 @@ namespace MWMechanics virtual void toggleAI(); virtual bool isAIActive(); + + virtual void getObjectsInRange (const Ogre::Vector3& position, float radius, std::vector& objects); }; } diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index 293b078da..e41ce2078 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -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().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 diff --git a/apps/openmw/mwmechanics/npcstats.hpp b/apps/openmw/mwmechanics/npcstats.hpp index b89a2b4b3..8cdeeea5d 100644 --- a/apps/openmw/mwmechanics/npcstats.hpp +++ b/apps/openmw/mwmechanics/npcstats.hpp @@ -7,7 +7,6 @@ #include #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 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); diff --git a/apps/openmw/mwmechanics/objects.cpp b/apps/openmw/mwmechanics/objects.cpp index 41d6b4ffa..b09574923 100644 --- a/apps/openmw/mwmechanics/objects.cpp +++ b/apps/openmw/mwmechanics/objects.cpp @@ -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& 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); + } +} + } diff --git a/apps/openmw/mwmechanics/objects.hpp b/apps/openmw/mwmechanics/objects.hpp index 32432c130..373a2a105 100644 --- a/apps/openmw/mwmechanics/objects.hpp +++ b/apps/openmw/mwmechanics/objects.hpp @@ -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& out); }; } diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index b9c8c5c4f..9ae6263e0 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -6,6 +6,7 @@ #include "OgreMath.h" #include "OgreVector3.h" + #include #include @@ -256,5 +257,6 @@ namespace MWMechanics } } + } diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index a0e91791b..749a5d7b1 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -1,29 +1,192 @@ #include "spellcasting.hpp" +#include + #include #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 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::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().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().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().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().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(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()) diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp index 52af26ad1..74dc490ea 100644 --- a/apps/openmw/mwmechanics/spellcasting.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -1,33 +1,15 @@ #ifndef MWMECHANICS_SPELLSUCCESS_H #define MWMECHANICS_SPELLSUCCESS_H -#include - -#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 namespace MWMechanics { - inline ESM::Skill::SkillEnum spellSchoolToSkill(int school) - { - std::map 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::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().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().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().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().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(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); }; } diff --git a/apps/openmw/mwmechanics/spells.hpp b/apps/openmw/mwmechanics/spells.hpp index facf02da8..cc239a650 100644 --- a/apps/openmw/mwmechanics/spells.hpp +++ b/apps/openmw/mwmechanics/spells.hpp @@ -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: diff --git a/apps/openmw/mwrender/actors.cpp b/apps/openmw/mwrender/actors.cpp index 639045bbe..4955dd6cb 100644 --- a/apps/openmw/mwrender/actors.cpp +++ b/apps/openmw/mwrender/actors.cpp @@ -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); diff --git a/apps/openmw/mwrender/actors.hpp b/apps/openmw/mwrender/actors.hpp index af71525fa..d5d6c52bb 100644 --- a/apps/openmw/mwrender/actors.hpp +++ b/apps/openmw/mwrender/actors.hpp @@ -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? diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index a7623efea..c0e4eebda 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -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 &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 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) diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 2dbc72e26..32145928e 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -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()) diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index 20e5ff8ef..e2aa9a2b8 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -1,26 +1,27 @@ #include "creatureanimation.hpp" +#include +#include +#include + #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 *ref = mPtr.get(); - 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 *ref = mPtr.get(); + + 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 >::iterator ctrl(scene->mControllers.begin()); + for(;ctrl != scene->mControllers.end();ctrl++) + { + if(ctrl->getSource().isNull()) + ctrl->setSource(Ogre::SharedPtr(new NullAnimationTime())); + } +} + } diff --git a/apps/openmw/mwrender/creatureanimation.hpp b/apps/openmw/mwrender/creatureanimation.hpp index a902df5d8..37826673d 100644 --- a/apps/openmw/mwrender/creatureanimation.hpp +++ b/apps/openmw/mwrender/creatureanimation.hpp @@ -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; }; } diff --git a/apps/openmw/mwrender/effectmanager.cpp b/apps/openmw/mwrender/effectmanager.cpp index eb4525a4f..7d41525b7 100644 --- a/apps/openmw/mwrender/effectmanager.cpp +++ b/apps/openmw/mwrender/effectmanager.cpp @@ -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 >::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) diff --git a/apps/openmw/mwrender/effectmanager.hpp b/apps/openmw/mwrender/effectmanager.hpp index 0c8bc3857..bc9e68d26 100644 --- a/apps/openmw/mwrender/effectmanager.hpp +++ b/apps/openmw/mwrender/effectmanager.hpp @@ -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(); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 8a2ab1c7a..515112668 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -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 diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index b6379bee4..ea9a64120 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -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); diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp index 759d0ba94..05886c51c 100644 --- a/apps/openmw/mwscript/aiextensions.cpp +++ b/apps/openmw/mwscript/aiextensions.cpp @@ -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) )); diff --git a/apps/openmw/mwscript/cellextensions.cpp b/apps/openmw/mwscript/cellextensions.cpp index 5de95d1d4..1834c5651 100644 --- a/apps/openmw/mwscript/cellextensions.cpp +++ b/apps/openmw/mwscript/cellextensions.cpp @@ -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; diff --git a/apps/openmw/mwscript/containerextensions.cpp b/apps/openmw/mwscript/containerextensions.cpp index 7bac7cdbe..e06505e86 100644 --- a/apps/openmw/mwscript/containerextensions.cpp +++ b/apps/openmw/mwscript/containerextensions.cpp @@ -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); } }; diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 637159475..27730767f 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -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); } }; diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index bdd03a8b4..d39602516 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -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 &output) diff --git a/apps/openmw/mwsound/soundmanagerimp.hpp b/apps/openmw/mwsound/soundmanagerimp.hpp index f62e62d50..4fd1d3d48 100644 --- a/apps/openmw/mwsound/soundmanagerimp.hpp +++ b/apps/openmw/mwsound/soundmanagerimp.hpp @@ -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); }; } diff --git a/apps/openmw/mwworld/actionequip.cpp b/apps/openmw/mwworld/actionequip.cpp index d34773bd5..2a1b7a3aa 100644 --- a/apps/openmw/mwworld/actionequip.cpp +++ b/apps/openmw/mwworld/actionequip.cpp @@ -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 result = MWWorld::Class::get (object).canBeEquipped (object, actor); + std::pair 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()) diff --git a/apps/openmw/mwworld/actiontrap.cpp b/apps/openmw/mwworld/actiontrap.cpp index d723b9823..bcefb0181 100644 --- a/apps/openmw/mwworld/actiontrap.cpp +++ b/apps/openmw/mwworld/actiontrap.cpp @@ -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 = ""; diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index 6c00b949c..9771ffde3 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -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"); diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index ec22d0306..0dee8b292 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -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) diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp index 0a1728740..913ec544a 100644 --- a/apps/openmw/mwworld/containerstore.hpp +++ b/apps/openmw/mwworld/containerstore.hpp @@ -62,6 +62,8 @@ namespace MWWorld virtual ~ContainerStore(); + virtual ContainerStore* clone() { return new ContainerStore(*this); } + ContainerStoreIterator begin (int mask = Type_All); ContainerStoreIterator end(); diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 82b827e75..ea43314e6 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -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())) diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index 067c8261e..d97bdf365 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -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). diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 3607b8276..0d5fb0560 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -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) diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index ca37cc591..8e4c5ecef 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -56,7 +56,7 @@ void Store::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::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; } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 0d7802081..0394a2edd 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -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::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 > toApply; + for (std::vector::const_iterator effectIt = effects.mList.begin(); + effectIt != effects.mList.end(); ++effectIt) + { + const ESM::MagicEffect* effect = getStore().get().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().find (effect->mArea); + else + areaStatic = getStore().get().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 objects; + MWBase::Environment::get().getMechanicsManager()->getObjectsInRange( + origin, feetToGameUnits(effectIt->mArea), objects); + + for (std::vector::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 >::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); + } + } } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 38766e74f..8b1bd9538 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -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); }; } diff --git a/components/esm/loadcell.cpp b/components/esm/loadcell.cpp index f6bc29ae1..f4bba7f19 100644 --- a/components/esm/loadcell.cpp +++ b/components/esm/loadcell.cpp @@ -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; + } } diff --git a/components/esm/loadcell.hpp b/components/esm/loadcell.hpp index b066f497e..71478946f 100644 --- a/components/esm/loadcell.hpp +++ b/components/esm/loadcell.hpp @@ -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; diff --git a/components/esm/loadinfo.hpp b/components/esm/loadinfo.hpp index 737494f6c..0c0d662a8 100644 --- a/components/esm/loadinfo.hpp +++ b/components/esm/loadinfo.hpp @@ -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; diff --git a/components/nifogre/ogrenifloader.cpp b/components/nifogre/ogrenifloader.cpp index 5a76b702e..d036844fc 100644 --- a/components/nifogre/ogrenifloader.cpp +++ b/components/nifogre/ogrenifloader.cpp @@ -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()); diff --git a/components/terrain/quadtreenode.cpp b/components/terrain/quadtreenode.cpp index 02225cb02..7fc452fbf 100644 --- a/components/terrain/quadtreenode.cpp +++ b/components/terrain/quadtreenode.cpp @@ -433,6 +433,7 @@ void QuadTreeNode::prepareForCompositeMap(Ogre::TRect 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()));