Merged pull request #1789

pull/1/head
Marc Zinnschlag 6 years ago
commit 0bd6078826

@ -2,6 +2,7 @@
------ ------
Bug #1990: Sunrise/sunset not set correct Bug #1990: Sunrise/sunset not set correct
Bug #2131: Lustidrike's spell misses the player every time
Bug #2222: Fatigue's effect on selling price is backwards Bug #2222: Fatigue's effect on selling price is backwards
Bug #2326: After a bound item expires the last equipped item of that type is not automatically re-equipped Bug #2326: After a bound item expires the last equipped item of that type is not automatically re-equipped
Bug #2455: Creatures attacks degrade armor Bug #2455: Creatures attacks degrade armor
@ -65,7 +66,9 @@
Bug #4495: Crossbow animations blending is buggy Bug #4495: Crossbow animations blending is buggy
Bug #4496: SpellTurnLeft and SpellTurnRight animation groups are unused Bug #4496: SpellTurnLeft and SpellTurnRight animation groups are unused
Bug #4497: File names starting with x or X are not classified as animation Bug #4497: File names starting with x or X are not classified as animation
Bug #4503: Cast and ExplodeSpell commands increase alteration skill
Feature #2606: Editor: Implemented (optional) case sensitive global search Feature #2606: Editor: Implemented (optional) case sensitive global search
Feature #3083: Play animation when NPC is casting spell via script
Feature #3276: Editor: Search- Show number of (remaining) search results and indicate a search without any results Feature #3276: Editor: Search- Show number of (remaining) search results and indicate a search without any results
Feature #3641: Editor: Limit FPS in 3d preview window Feature #3641: Editor: Limit FPS in 3d preview window
Feature #4222: 360° screenshots Feature #4222: 360° screenshots

@ -81,9 +81,9 @@ add_openmw_dir (mwclass
add_openmw_dir (mwmechanics add_openmw_dir (mwmechanics
mechanicsmanagerimp stat creaturestats magiceffects movement actorutil mechanicsmanagerimp stat creaturestats magiceffects movement actorutil
drawstate spells activespells npcstats aipackage aisequence aipursue alchemy aiwander aitravel aifollow aiavoiddoor aibreathe drawstate spells activespells npcstats aipackage aisequence aipursue alchemy aiwander aitravel aifollow aiavoiddoor aibreathe
aiescort aiactivate aicombat repair enchanting pathfinding pathgrid security spellsuccess spellcasting aicast aiescort aiface aiactivate aicombat repair enchanting pathfinding pathgrid security spellsuccess spellcasting
disease pickpocket levelledlist combat steering obstacle autocalcspell difficultyscaling aicombataction actor summoning disease pickpocket levelledlist combat steering obstacle autocalcspell difficultyscaling aicombataction actor summoning
character actors objects aistate coordinateconverter trading aiface weaponpriority spellpriority character actors objects aistate coordinateconverter trading weaponpriority spellpriority
) )
add_openmw_dir (mwstate add_openmw_dir (mwstate

@ -225,9 +225,12 @@ namespace MWBase
/// Resurrects the player if necessary /// Resurrects the player if necessary
virtual void keepPlayerAlive() = 0; virtual void keepPlayerAlive() = 0;
virtual bool isCastingSpell (const MWWorld::Ptr& ptr) const = 0;
virtual bool isReadyToBlock (const MWWorld::Ptr& ptr) const = 0; virtual bool isReadyToBlock (const MWWorld::Ptr& ptr) const = 0;
virtual bool isAttackingOrSpell(const MWWorld::Ptr &ptr) const = 0; virtual bool isAttackingOrSpell(const MWWorld::Ptr &ptr) const = 0;
virtual void castSpell(const MWWorld::Ptr& ptr, const std::string spellId, bool manualSpell) = 0;
/// Check if the target actor was detected by an observer /// Check if the target actor was detected by an observer
/// If the observer is a non-NPC, check all actors in AI processing distance as observers /// If the observer is a non-NPC, check all actors in AI processing distance as observers
virtual bool isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer) = 0; virtual bool isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer) = 0;

@ -487,7 +487,7 @@ namespace MWBase
*/ */
virtual bool startSpellCast (const MWWorld::Ptr& actor) = 0; virtual bool startSpellCast (const MWWorld::Ptr& actor) = 0;
virtual void castSpell (const MWWorld::Ptr& actor) = 0; virtual void castSpell (const MWWorld::Ptr& actor, bool manualSpell=false) = 0;
virtual void launchMagicBolt (const std::string& spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection) = 0; virtual void launchMagicBolt (const std::string& spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection) = 0;
virtual void launchProjectile (MWWorld::Ptr actor, MWWorld::ConstPtr projectile, virtual void launchProjectile (MWWorld::Ptr actor, MWWorld::ConstPtr projectile,

@ -1157,6 +1157,13 @@ namespace MWMechanics
} }
} }
void Actors::castSpell(const MWWorld::Ptr& ptr, const std::string spellId, bool manualSpell)
{
PtrActorMap::iterator iter = mActors.find(ptr);
if(iter != mActors.end())
iter->second->getCharacterController()->castSpell(spellId, manualSpell);
}
bool Actors::isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer) bool Actors::isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer)
{ {
if (!actor.getClass().isActor()) if (!actor.getClass().isActor())
@ -1968,6 +1975,15 @@ namespace MWMechanics
return it->second->getCharacterController()->isReadyToBlock(); return it->second->getCharacterController()->isReadyToBlock();
} }
bool Actors::isCastingSpell(const MWWorld::Ptr &ptr) const
{
PtrActorMap::const_iterator it = mActors.find(ptr);
if (it == mActors.end())
return false;
return it->second->getCharacterController()->isCastingSpell();
}
bool Actors::isAttackingOrSpell(const MWWorld::Ptr& ptr) const bool Actors::isAttackingOrSpell(const MWWorld::Ptr& ptr) const
{ {
PtrActorMap::const_iterator it = mActors.find(ptr); PtrActorMap::const_iterator it = mActors.find(ptr);

@ -77,6 +77,8 @@ namespace MWMechanics
/// ///
/// \note Ignored, if \a ptr is not a registered actor. /// \note Ignored, if \a ptr is not a registered actor.
void castSpell(const MWWorld::Ptr& ptr, const std::string spellId, bool manualSpell=false);
void updateActor(const MWWorld::Ptr &old, const MWWorld::Ptr& ptr); void updateActor(const MWWorld::Ptr &old, const MWWorld::Ptr& ptr);
///< Updates an actor with a new Ptr ///< Updates an actor with a new Ptr
@ -161,6 +163,7 @@ namespace MWMechanics
void clear(); // Clear death counter void clear(); // Clear death counter
bool isCastingSpell(const MWWorld::Ptr& ptr) const;
bool isReadyToBlock(const MWWorld::Ptr& ptr) const; bool isReadyToBlock(const MWWorld::Ptr& ptr) const;
bool isAttackingOrSpell(const MWWorld::Ptr& ptr) const; bool isAttackingOrSpell(const MWWorld::Ptr& ptr) const;

@ -0,0 +1,84 @@
#include "aicast.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwworld/class.hpp"
#include "aicombataction.hpp"
#include "creaturestats.hpp"
#include "spellcasting.hpp"
#include "steering.hpp"
MWMechanics::AiCast::AiCast(const std::string& targetId, const std::string& spellId, bool manualSpell)
: mTargetId(targetId), mSpellId(spellId), mCasting(false), mManual(manualSpell), mDistance(0)
{
ActionSpell action = ActionSpell(spellId);
bool isRanged;
mDistance = action.getCombatRange(isRanged);
}
MWMechanics::AiPackage *MWMechanics::AiCast::clone() const
{
return new AiCast(*this);
}
bool MWMechanics::AiCast::execute(const MWWorld::Ptr& actor, MWMechanics::CharacterController& characterController, MWMechanics::AiState& state, float duration)
{
MWWorld::Ptr target;
if (actor.getCellRef().getRefId() == mTargetId)
{
// If the target has the same ID as caster, consider that actor casts spell with Self range.
target = actor;
}
else
{
target = getTarget();
if (!target)
return true;
if (!mManual && !pathTo(actor, target.getRefData().getPosition().pos, duration, mDistance))
{
return false;
}
osg::Vec3f dir = target.getRefData().getPosition().asVec3() - actor.getRefData().getPosition().asVec3();
bool turned = smoothTurn(actor, getZAngleToDir(dir), 2, osg::DegreesToRadians(3.f));
turned &= smoothTurn(actor, getXAngleToDir(dir), 0, osg::DegreesToRadians(3.f));
if (!turned)
return false;
}
// Check if the actor is already casting another spell
bool isCasting = MWBase::Environment::get().getMechanicsManager()->isCastingSpell(actor);
if (isCasting && !mCasting)
return false;
if (!mCasting)
{
MWBase::Environment::get().getMechanicsManager()->castSpell(actor, mSpellId, mManual);
mCasting = true;
return false;
}
// Finish package, if actor finished spellcasting
return !isCasting;
}
MWWorld::Ptr MWMechanics::AiCast::getTarget() const
{
MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(mTargetId, false);
return target;
}
int MWMechanics::AiCast::getTypeId() const
{
return AiPackage::TypeIdCast;
}
unsigned int MWMechanics::AiCast::getPriority() const
{
return 3;
}

@ -0,0 +1,37 @@
#ifndef GAME_MWMECHANICS_AICAST_H
#define GAME_MWMECHANICS_AICAST_H
#include "../mwbase/world.hpp"
#include "aipackage.hpp"
namespace MWMechanics
{
/// AiPackage which makes an actor to cast given spell.
class AiCast : public AiPackage {
public:
AiCast(const std::string& targetId, const std::string& spellId, bool manualSpell=false);
virtual AiPackage *clone() const;
virtual bool execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration);
virtual int getTypeId() const;
virtual MWWorld::Ptr getTarget() const;
virtual unsigned int getPriority() const;
virtual bool canCancel() const { return false; }
virtual bool shouldCancelPreviousAi() const { return false; }
private:
std::string mTargetId;
std::string mSpellId;
bool mCasting;
bool mManual;
float mDistance;
};
}
#endif

@ -49,7 +49,8 @@ namespace MWMechanics
TypeIdAvoidDoor = 7, TypeIdAvoidDoor = 7,
TypeIdFace = 8, TypeIdFace = 8,
TypeIdBreathe = 9, TypeIdBreathe = 9,
TypeIdInternalTravel = 10 TypeIdInternalTravel = 10,
TypeIdCast = 11
}; };
///Default constructor ///Default constructor

@ -180,12 +180,8 @@ bool AiSequence::isPackageDone() const
bool isActualAiPackage(int packageTypeId) bool isActualAiPackage(int packageTypeId)
{ {
return (packageTypeId != AiPackage::TypeIdCombat return (packageTypeId >= AiPackage::TypeIdWander &&
&& packageTypeId != AiPackage::TypeIdPursue packageTypeId <= AiPackage::TypeIdActivate);
&& packageTypeId != AiPackage::TypeIdAvoidDoor
&& packageTypeId != AiPackage::TypeIdFace
&& packageTypeId != AiPackage::TypeIdBreathe
&& packageTypeId != AiPackage::TypeIdInternalTravel);
} }
void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& characterController, float duration) void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& characterController, float duration)
@ -308,7 +304,7 @@ void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor, boo
if (isActualAiPackage(package.getTypeId())) if (isActualAiPackage(package.getTypeId()))
stopCombat(); stopCombat();
// We should return a wandering actor back after combat or pursuit. // We should return a wandering actor back after combat, casting or pursuit.
// The same thing for actors without AI packages. // The same thing for actors without AI packages.
// Also there is no point to stack return packages. // Also there is no point to stack return packages.
int currentTypeId = getTypeId(); int currentTypeId = getTypeId();
@ -316,7 +312,8 @@ void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor, boo
if (currentTypeId <= MWMechanics::AiPackage::TypeIdWander if (currentTypeId <= MWMechanics::AiPackage::TypeIdWander
&& !hasPackage(MWMechanics::AiPackage::TypeIdInternalTravel) && !hasPackage(MWMechanics::AiPackage::TypeIdInternalTravel)
&& (newTypeId <= MWMechanics::AiPackage::TypeIdCombat && (newTypeId <= MWMechanics::AiPackage::TypeIdCombat
|| newTypeId == MWMechanics::AiPackage::TypeIdPursue)) || newTypeId == MWMechanics::AiPackage::TypeIdPursue
|| newTypeId == MWMechanics::AiPackage::TypeIdCast))
{ {
osg::Vec3f dest; osg::Vec3f dest;
if (currentTypeId == MWMechanics::AiPackage::TypeIdWander) if (currentTypeId == MWMechanics::AiPackage::TypeIdWander)
@ -352,6 +349,13 @@ void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor, boo
// insert new package in correct place depending on priority // insert new package in correct place depending on priority
for(std::list<AiPackage *>::iterator it = mPackages.begin(); it != mPackages.end(); ++it) for(std::list<AiPackage *>::iterator it = mPackages.begin(); it != mPackages.end(); ++it)
{ {
// We should keep current AiCast package, if we try to add a new one.
if ((*it)->getTypeId() == MWMechanics::AiPackage::TypeIdCast &&
package.getTypeId() == MWMechanics::AiPackage::TypeIdCast)
{
continue;
}
if((*it)->getPriority() <= package.getPriority()) if((*it)->getPriority() <= package.getPriority())
{ {
mPackages.insert(it,package.clone()); mPackages.insert(it,package.clone());

@ -39,6 +39,7 @@
#include "../mwworld/esmstore.hpp" #include "../mwworld/esmstore.hpp"
#include "../mwworld/player.hpp" #include "../mwworld/player.hpp"
#include "aicombataction.hpp"
#include "movement.hpp" #include "movement.hpp"
#include "npcstats.hpp" #include "npcstats.hpp"
#include "creaturestats.hpp" #include "creaturestats.hpp"
@ -794,6 +795,7 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim
, mSecondsOfRunning(0) , mSecondsOfRunning(0)
, mTurnAnimationThreshold(0) , mTurnAnimationThreshold(0)
, mAttackingOrSpell(false) , mAttackingOrSpell(false)
, mCastingManualSpell(false)
, mTimeUntilWake(0.f) , mTimeUntilWake(0.f)
{ {
if(!mAnimation) if(!mAnimation)
@ -1007,7 +1009,8 @@ void CharacterController::handleTextKey(const std::string &groupname, const std:
// the same animation for all range types, so there are 3 "release" keys on the same time, one for each range type. // the same animation for all range types, so there are 3 "release" keys on the same time, one for each range type.
&& evt.compare(off, len, mAttackType + " release") == 0) && evt.compare(off, len, mAttackType + " release") == 0)
{ {
MWBase::Environment::get().getWorld()->castSpell(mPtr); MWBase::Environment::get().getWorld()->castSpell(mPtr, mCastingManualSpell);
mCastingManualSpell = false;
} }
else if (groupname == "shield" && evt.compare(off, len, "block hit") == 0) else if (groupname == "shield" && evt.compare(off, len, "block hit") == 0)
@ -1088,13 +1091,18 @@ bool CharacterController::updateCreatureState()
if (weapType == WeapType_Spell) if (weapType == WeapType_Spell)
{ {
const std::string spellid = stats.getSpells().getSelectedSpell(); const std::string spellid = stats.getSpells().getSelectedSpell();
if (!spellid.empty() && MWBase::Environment::get().getWorld()->startSpellCast(mPtr)) bool canCast = mCastingManualSpell || MWBase::Environment::get().getWorld()->startSpellCast(mPtr);
if (!spellid.empty() && canCast)
{ {
MWMechanics::CastSpell cast(mPtr, NULL); MWMechanics::CastSpell cast(mPtr, NULL, false, mCastingManualSpell);
cast.playSpellCastingEffects(spellid); cast.playSpellCastingEffects(spellid);
if (!mAnimation->hasAnimation("spellcast")) if (!mAnimation->hasAnimation("spellcast"))
MWBase::Environment::get().getWorld()->castSpell(mPtr); // No "release" text key to use, so cast immediately {
MWBase::Environment::get().getWorld()->castSpell(mPtr, mCastingManualSpell); // No "release" text key to use, so cast immediately
mCastingManualSpell = false;
}
else else
{ {
const ESM::Spell *spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find(spellid); const ESM::Spell *spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find(spellid);
@ -1367,10 +1375,11 @@ bool CharacterController::updateWeaponState()
stats.getSpells().setSelectedSpell(selectedSpell); stats.getSpells().setSelectedSpell(selectedSpell);
} }
std::string spellid = stats.getSpells().getSelectedSpell(); std::string spellid = stats.getSpells().getSelectedSpell();
bool canCast = mCastingManualSpell || MWBase::Environment::get().getWorld()->startSpellCast(mPtr);
if(!spellid.empty() && MWBase::Environment::get().getWorld()->startSpellCast(mPtr)) if(!spellid.empty() && canCast)
{ {
MWMechanics::CastSpell cast(mPtr, NULL); MWMechanics::CastSpell cast(mPtr, NULL, false, mCastingManualSpell);
cast.playSpellCastingEffects(spellid); cast.playSpellCastingEffects(spellid);
const ESM::Spell *spell = store.get<ESM::Spell>().find(spellid); const ESM::Spell *spell = store.get<ESM::Spell>().find(spellid);
@ -2377,6 +2386,11 @@ bool CharacterController::isAttackPrepairing() const
mUpperBodyState == UpperCharState_MinAttackToMaxAttack; mUpperBodyState == UpperCharState_MinAttackToMaxAttack;
} }
bool CharacterController::isCastingSpell() const
{
return mCastingManualSpell || mUpperBodyState == UpperCharState_CastingSpell;
}
bool CharacterController::isReadyToBlock() const bool CharacterController::isReadyToBlock() const
{ {
return updateCarriedLeftVisible(mWeaponType); return updateCarriedLeftVisible(mWeaponType);
@ -2440,6 +2454,14 @@ void CharacterController::setAttackingOrSpell(bool attackingOrSpell)
mAttackingOrSpell = attackingOrSpell; mAttackingOrSpell = attackingOrSpell;
} }
void CharacterController::castSpell(const std::string spellId, bool manualSpell)
{
mAttackingOrSpell = true;
mCastingManualSpell = manualSpell;
ActionSpell action = ActionSpell(spellId);
action.prepare(mPtr);
}
void CharacterController::setAIAttackType(const std::string& attackType) void CharacterController::setAIAttackType(const std::string& attackType)
{ {
mAttackType = attackType; mAttackType = attackType;

@ -204,6 +204,7 @@ class CharacterController : public MWRender::Animation::TextKeyListener
std::string mAttackType; // slash, chop or thrust std::string mAttackType; // slash, chop or thrust
bool mAttackingOrSpell; bool mAttackingOrSpell;
bool mCastingManualSpell;
float mTimeUntilWake; float mTimeUntilWake;
@ -276,6 +277,7 @@ public:
void forceStateUpdate(); void forceStateUpdate();
bool isAttackPrepairing() const; bool isAttackPrepairing() const;
bool isCastingSpell() const;
bool isReadyToBlock() const; bool isReadyToBlock() const;
bool isKnockedDown() const; bool isKnockedDown() const;
bool isKnockedOut() const; bool isKnockedOut() const;
@ -286,6 +288,7 @@ public:
bool isAttackingOrSpell() const; bool isAttackingOrSpell() const;
void setAttackingOrSpell(bool attackingOrSpell); void setAttackingOrSpell(bool attackingOrSpell);
void castSpell(const std::string spellId, bool manualSpell=false);
void setAIAttackType(const std::string& attackType); void setAIAttackType(const std::string& attackType);
static void setAttackTypeRandomly(std::string& attackType); static void setAttackTypeRandomly(std::string& attackType);

@ -253,6 +253,12 @@ namespace MWMechanics
mObjects.addObject(ptr); mObjects.addObject(ptr);
} }
void MechanicsManager::castSpell(const MWWorld::Ptr& ptr, const std::string spellId, bool manualSpell)
{
if(ptr.getClass().isActor())
mActors.castSpell(ptr, spellId, manualSpell);
}
void MechanicsManager::remove(const MWWorld::Ptr& ptr) void MechanicsManager::remove(const MWWorld::Ptr& ptr)
{ {
if(ptr == mWatched) if(ptr == mWatched)
@ -1758,6 +1764,11 @@ namespace MWMechanics
stats.resurrect(); stats.resurrect();
} }
bool MechanicsManager::isCastingSpell(const MWWorld::Ptr &ptr) const
{
return mActors.isCastingSpell(ptr);
}
bool MechanicsManager::isReadyToBlock(const MWWorld::Ptr &ptr) const bool MechanicsManager::isReadyToBlock(const MWWorld::Ptr &ptr) const
{ {
return mActors.isReadyToBlock(ptr); return mActors.isReadyToBlock(ptr);

@ -190,10 +190,14 @@ namespace MWMechanics
virtual void keepPlayerAlive(); virtual void keepPlayerAlive();
virtual bool isCastingSpell (const MWWorld::Ptr& ptr) const;
virtual bool isReadyToBlock (const MWWorld::Ptr& ptr) const; virtual bool isReadyToBlock (const MWWorld::Ptr& ptr) const;
/// Is \a ptr casting spell or using weapon now? /// Is \a ptr casting spell or using weapon now?
virtual bool isAttackingOrSpell(const MWWorld::Ptr &ptr) const; virtual bool isAttackingOrSpell(const MWWorld::Ptr &ptr) const;
virtual void castSpell(const MWWorld::Ptr& ptr, const std::string spellId, bool manualSpell=false);
/// Check if the target actor was detected by an observer /// Check if the target actor was detected by an observer
/// If the observer is a non-NPC, check all actors in AI processing distance as observers /// If the observer is a non-NPC, check all actors in AI processing distance as observers
virtual bool isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer); virtual bool isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer);

@ -315,13 +315,14 @@ namespace MWMechanics
return true; return true;
} }
CastSpell::CastSpell(const MWWorld::Ptr &caster, const MWWorld::Ptr &target, const bool fromProjectile) CastSpell::CastSpell(const MWWorld::Ptr &caster, const MWWorld::Ptr &target, const bool fromProjectile, const bool isScripted)
: mCaster(caster) : mCaster(caster)
, mTarget(target) , mTarget(target)
, mStack(false) , mStack(false)
, mHitPosition(0,0,0) , mHitPosition(0,0,0)
, mAlwaysSucceed(false) , mAlwaysSucceed(false)
, mFromProjectile(fromProjectile) , mFromProjectile(fromProjectile)
, mIsScripted(isScripted)
{ {
} }
@ -863,7 +864,7 @@ namespace MWMechanics
bool godmode = mCaster == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); bool godmode = mCaster == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState();
if (mCaster.getClass().isActor() && !mAlwaysSucceed) if (mCaster.getClass().isActor() && !mAlwaysSucceed && !mIsScripted)
{ {
school = getSpellSchool(spell, mCaster); school = getSpellSchool(spell, mCaster);
@ -910,7 +911,7 @@ namespace MWMechanics
stats.getSpells().usePower(spell); stats.getSpells().usePower(spell);
} }
if (mCaster == getPlayer() && spellIncreasesSkill(spell)) if (mCaster == getPlayer() && spellIncreasesSkill())
mCaster.getClass().skillUsageSucceeded(mCaster, mCaster.getClass().skillUsageSucceeded(mCaster,
spellSchoolToSkill(school), 0); spellSchoolToSkill(school), 0);
@ -1034,6 +1035,14 @@ namespace MWMechanics
} }
} }
bool CastSpell::spellIncreasesSkill()
{
if (mIsScripted)
return false;
return MWMechanics::spellIncreasesSkill(mId);
}
int getEffectiveEnchantmentCastCost(float castCost, const MWWorld::Ptr &actor) int getEffectiveEnchantmentCastCost(float castCost, const MWWorld::Ptr &actor)
{ {
/* /*

@ -88,9 +88,10 @@ namespace MWMechanics
osg::Vec3f mHitPosition; // Used for spawning area orb osg::Vec3f mHitPosition; // Used for spawning area orb
bool mAlwaysSucceed; // Always succeed spells casted by NPCs/creatures regardless of their chance (default: false) bool mAlwaysSucceed; // Always succeed spells casted by NPCs/creatures regardless of their chance (default: false)
bool mFromProjectile; // True if spell is cast by enchantment of some projectile (arrow, bolt or thrown weapon) bool mFromProjectile; // True if spell is cast by enchantment of some projectile (arrow, bolt or thrown weapon)
bool mIsScripted; // True if spell is casted from script and ignores some checks (mana level, success chance, etc.)
public: public:
CastSpell(const MWWorld::Ptr& caster, const MWWorld::Ptr& target, const bool fromProjectile=false); CastSpell(const MWWorld::Ptr& caster, const MWWorld::Ptr& target, const bool fromProjectile=false, const bool isScripted=false);
bool cast (const ESM::Spell* spell); bool cast (const ESM::Spell* spell);
@ -108,6 +109,8 @@ namespace MWMechanics
void playSpellCastingEffects(const std::string &spellid); void playSpellCastingEffects(const std::string &spellid);
bool spellIncreasesSkill();
/// Launch a bolt with the given effects. /// Launch a bolt with the given effects.
void launchMagicBolt (); void launchMagicBolt ();

@ -25,6 +25,7 @@
#include "../mwworld/esmstore.hpp" #include "../mwworld/esmstore.hpp"
#include "../mwworld/cellstore.hpp" #include "../mwworld/cellstore.hpp"
#include "../mwmechanics/aicast.hpp"
#include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/npcstats.hpp"
#include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/creaturestats.hpp"
#include "../mwmechanics/spellcasting.hpp" #include "../mwmechanics/spellcasting.hpp"
@ -1056,15 +1057,31 @@ namespace MWScript
{ {
MWWorld::Ptr ptr = R()(runtime); MWWorld::Ptr ptr = R()(runtime);
std::string spell = runtime.getStringLiteral (runtime[0].mInteger); std::string spellId = runtime.getStringLiteral (runtime[0].mInteger);
runtime.pop(); runtime.pop();
std::string targetId = ::Misc::StringUtils::lowerCase(runtime.getStringLiteral (runtime[0].mInteger)); std::string targetId = ::Misc::StringUtils::lowerCase(runtime.getStringLiteral (runtime[0].mInteger));
runtime.pop(); runtime.pop();
const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find (spellId);
if (spell && spell->mData.mType != ESM::Spell::ST_Spell && spell->mData.mType != ESM::Spell::ST_Power)
{
runtime.getContext().report("spellcasting failed: you can cast only spells and powers.");
return;
}
// Obviously we can not use casting animation for player here
if (ptr.getClass().isActor() && ptr != MWMechanics::getPlayer())
{
MWMechanics::AiCast castPackage(targetId, spellId, true);
ptr.getClass().getCreatureStats (ptr).getAiSequence().stack(castPackage, ptr);
return;
}
MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr (targetId, false); MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr (targetId, false);
MWMechanics::CastSpell cast(ptr, target); MWMechanics::CastSpell cast(ptr, target, false, true);
cast.mHitPosition = target.getRefData().getPosition().asVec3(); cast.mHitPosition = target.getRefData().getPosition().asVec3();
cast.mAlwaysSucceed = true; cast.mAlwaysSucceed = true;
cast.cast(spell); cast.cast(spell);
@ -1082,7 +1099,7 @@ namespace MWScript
std::string spell = runtime.getStringLiteral (runtime[0].mInteger); std::string spell = runtime.getStringLiteral (runtime[0].mInteger);
runtime.pop(); runtime.pop();
MWMechanics::CastSpell cast(ptr, ptr); MWMechanics::CastSpell cast(ptr, ptr, false, true);
cast.mHitPosition = ptr.getRefData().getPosition().asVec3(); cast.mHitPosition = ptr.getRefData().getPosition().asVec3();
cast.mAlwaysSucceed = true; cast.mAlwaysSucceed = true;
cast.cast(spell); cast.cast(spell);

@ -2771,13 +2771,13 @@ namespace MWWorld
return !fail; return !fail;
} }
void World::castSpell(const Ptr &actor) void World::castSpell(const Ptr &actor, bool manualSpell)
{ {
MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor);
// For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result. // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result.
std::vector<MWWorld::Ptr> targetActors; std::vector<MWWorld::Ptr> targetActors;
if (!actor.isEmpty() && actor != MWMechanics::getPlayer()) if (!actor.isEmpty() && actor != MWMechanics::getPlayer() && !manualSpell)
actor.getClass().getCreatureStats(actor).getAiSequence().getCombatTargets(targetActors); actor.getClass().getCreatureStats(actor).getAiSequence().getCombatTargets(targetActors);
const float fCombatDistance = getStore().get<ESM::GameSetting>().find("fCombatDistance")->getFloat(); const float fCombatDistance = getStore().get<ESM::GameSetting>().find("fCombatDistance")->getFloat();
@ -2795,51 +2795,71 @@ namespace MWWorld
if (target.isEmpty()) if (target.isEmpty())
{ {
// For actor targets, we want to use hit contact with bounding boxes. // For scripted spells we should not use hit contact
// This is to give a slight tolerance for errors, especially with creatures like the Skeleton that would be very hard to aim at otherwise. if (manualSpell)
// For object targets, we want the detailed shapes (rendering raycast). {
// If we used the bounding boxes for static objects, then we would not be able to target e.g. objects lying on a shelf. // Actors that are targeted by this actor's Follow or Escort packages also side with them
std::pair<MWWorld::Ptr,osg::Vec3f> result1 = getHitContact(actor, fCombatDistance, targetActors); if (actor != MWMechanics::getPlayer())
{
const MWMechanics::CreatureStats &stats = actor.getClass().getCreatureStats(actor);
for (std::list<MWMechanics::AiPackage*>::const_iterator it = stats.getAiSequence().begin(); it != stats.getAiSequence().end(); ++it)
{
if ((*it)->getTypeId() == MWMechanics::AiPackage::TypeIdCast)
{
target = (*it)->getTarget();
break;
}
}
}
}
else
{
// For actor targets, we want to use hit contact with bounding boxes.
// This is to give a slight tolerance for errors, especially with creatures like the Skeleton that would be very hard to aim at otherwise.
// For object targets, we want the detailed shapes (rendering raycast).
// If we used the bounding boxes for static objects, then we would not be able to target e.g. objects lying on a shelf.
std::pair<MWWorld::Ptr,osg::Vec3f> result1 = getHitContact(actor, fCombatDistance, targetActors);
// Get the target to use for "on touch" effects, using the facing direction from Head node // Get the target to use for "on touch" effects, using the facing direction from Head node
osg::Vec3f origin = getActorHeadTransform(actor).getTrans(); osg::Vec3f origin = getActorHeadTransform(actor).getTrans();
osg::Quat orient = osg::Quat(actor.getRefData().getPosition().rot[0], osg::Vec3f(-1,0,0)) osg::Quat orient = osg::Quat(actor.getRefData().getPosition().rot[0], osg::Vec3f(-1,0,0))
* osg::Quat(actor.getRefData().getPosition().rot[2], osg::Vec3f(0,0,-1)); * osg::Quat(actor.getRefData().getPosition().rot[2], osg::Vec3f(0,0,-1));
osg::Vec3f direction = orient * osg::Vec3f(0,1,0); osg::Vec3f direction = orient * osg::Vec3f(0,1,0);
float distance = getMaxActivationDistance(); float distance = getMaxActivationDistance();
osg::Vec3f dest = origin + direction * distance; osg::Vec3f dest = origin + direction * distance;
MWRender::RenderingManager::RayResult result2 = mRendering->castRay(origin, dest, true, true); MWRender::RenderingManager::RayResult result2 = mRendering->castRay(origin, dest, true, true);
float dist1 = std::numeric_limits<float>::max(); float dist1 = std::numeric_limits<float>::max();
float dist2 = std::numeric_limits<float>::max(); float dist2 = std::numeric_limits<float>::max();
if (!result1.first.isEmpty() && result1.first.getClass().isActor()) if (!result1.first.isEmpty() && result1.first.getClass().isActor())
dist1 = (origin - result1.second).length(); dist1 = (origin - result1.second).length();
if (result2.mHit) if (result2.mHit)
dist2 = (origin - result2.mHitPointWorld).length(); dist2 = (origin - result2.mHitPointWorld).length();
if (!result1.first.isEmpty() && result1.first.getClass().isActor()) if (!result1.first.isEmpty() && result1.first.getClass().isActor())
{ {
target = result1.first; target = result1.first;
hitPosition = result1.second; hitPosition = result1.second;
if (dist1 > getMaxActivationDistance()) if (dist1 > getMaxActivationDistance())
target = NULL; target = NULL;
} }
else if (result2.mHit) else if (result2.mHit)
{ {
target = result2.mHitObject; target = result2.mHitObject;
hitPosition = result2.mHitPointWorld; hitPosition = result2.mHitPointWorld;
if (dist2 > getMaxActivationDistance() && !target.isEmpty() && !target.getClass().canBeActivated(target)) if (dist2 > getMaxActivationDistance() && !target.isEmpty() && !target.getClass().canBeActivated(target))
target = NULL; target = NULL;
}
} }
} }
std::string selectedSpell = stats.getSpells().getSelectedSpell(); std::string selectedSpell = stats.getSpells().getSelectedSpell();
MWMechanics::CastSpell cast(actor, target); MWMechanics::CastSpell cast(actor, target, false, manualSpell);
cast.mHitPosition = hitPosition; cast.mHitPosition = hitPosition;
if (!selectedSpell.empty()) if (!selectedSpell.empty())

@ -602,7 +602,7 @@ namespace MWWorld
* @brief Cast the actual spell, should be called mid-animation * @brief Cast the actual spell, should be called mid-animation
* @param actor * @param actor
*/ */
void castSpell (const MWWorld::Ptr& actor) override; void castSpell (const MWWorld::Ptr& actor, bool manualSpell=false) override;
void launchMagicBolt (const std::string& spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection) override; void launchMagicBolt (const std::string& spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection) override;
void launchProjectile (MWWorld::Ptr actor, MWWorld::ConstPtr projectile, void launchProjectile (MWWorld::Ptr actor, MWWorld::ConstPtr projectile,

Loading…
Cancel
Save