forked from teamnwah/openmw-tes3coop
Rework manual spellcasting (e.g. via scripts)
This commit is contained in:
parent
99e4d49e7c
commit
3d1daaebab
18 changed files with 302 additions and 65 deletions
|
@ -81,9 +81,9 @@ add_openmw_dir (mwclass
|
|||
add_openmw_dir (mwmechanics
|
||||
mechanicsmanagerimp stat creaturestats magiceffects movement actorutil
|
||||
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
|
||||
character actors objects aistate coordinateconverter trading aiface weaponpriority spellpriority
|
||||
character actors objects aistate coordinateconverter trading weaponpriority spellpriority
|
||||
)
|
||||
|
||||
add_openmw_dir (mwstate
|
||||
|
|
|
@ -225,9 +225,12 @@ namespace MWBase
|
|||
/// Resurrects the player if necessary
|
||||
virtual void keepPlayerAlive() = 0;
|
||||
|
||||
virtual bool isCastingSpell (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 void castSpell(const MWWorld::Ptr& ptr, const std::string spellId, bool manualSpell) = 0;
|
||||
|
||||
/// 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
|
||||
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 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 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)
|
||||
{
|
||||
if (!actor.getClass().isActor())
|
||||
|
@ -1968,6 +1975,15 @@ namespace MWMechanics
|
|||
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
|
||||
{
|
||||
PtrActorMap::const_iterator it = mActors.find(ptr);
|
||||
|
|
|
@ -77,6 +77,8 @@ namespace MWMechanics
|
|||
///
|
||||
/// \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);
|
||||
///< Updates an actor with a new Ptr
|
||||
|
||||
|
@ -161,6 +163,7 @@ namespace MWMechanics
|
|||
|
||||
void clear(); // Clear death counter
|
||||
|
||||
bool isCastingSpell(const MWWorld::Ptr& ptr) const;
|
||||
bool isReadyToBlock(const MWWorld::Ptr& ptr) const;
|
||||
bool isAttackingOrSpell(const MWWorld::Ptr& ptr) const;
|
||||
|
||||
|
|
84
apps/openmw/mwmechanics/aicast.cpp
Normal file
84
apps/openmw/mwmechanics/aicast.cpp
Normal file
|
@ -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;
|
||||
}
|
37
apps/openmw/mwmechanics/aicast.hpp
Normal file
37
apps/openmw/mwmechanics/aicast.hpp
Normal file
|
@ -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,
|
||||
TypeIdFace = 8,
|
||||
TypeIdBreathe = 9,
|
||||
TypeIdInternalTravel = 10
|
||||
TypeIdInternalTravel = 10,
|
||||
TypeIdCast = 11
|
||||
};
|
||||
|
||||
///Default constructor
|
||||
|
|
|
@ -180,12 +180,8 @@ bool AiSequence::isPackageDone() const
|
|||
|
||||
bool isActualAiPackage(int packageTypeId)
|
||||
{
|
||||
return (packageTypeId != AiPackage::TypeIdCombat
|
||||
&& packageTypeId != AiPackage::TypeIdPursue
|
||||
&& packageTypeId != AiPackage::TypeIdAvoidDoor
|
||||
&& packageTypeId != AiPackage::TypeIdFace
|
||||
&& packageTypeId != AiPackage::TypeIdBreathe
|
||||
&& packageTypeId != AiPackage::TypeIdInternalTravel);
|
||||
return (packageTypeId >= AiPackage::TypeIdWander &&
|
||||
packageTypeId <= AiPackage::TypeIdActivate);
|
||||
}
|
||||
|
||||
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()))
|
||||
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.
|
||||
// Also there is no point to stack return packages.
|
||||
int currentTypeId = getTypeId();
|
||||
|
@ -316,7 +312,8 @@ void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor, boo
|
|||
if (currentTypeId <= MWMechanics::AiPackage::TypeIdWander
|
||||
&& !hasPackage(MWMechanics::AiPackage::TypeIdInternalTravel)
|
||||
&& (newTypeId <= MWMechanics::AiPackage::TypeIdCombat
|
||||
|| newTypeId == MWMechanics::AiPackage::TypeIdPursue))
|
||||
|| newTypeId == MWMechanics::AiPackage::TypeIdPursue
|
||||
|| newTypeId == MWMechanics::AiPackage::TypeIdCast))
|
||||
{
|
||||
osg::Vec3f dest;
|
||||
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
|
||||
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())
|
||||
{
|
||||
mPackages.insert(it,package.clone());
|
||||
|
|
|
@ -39,6 +39,7 @@
|
|||
#include "../mwworld/esmstore.hpp"
|
||||
#include "../mwworld/player.hpp"
|
||||
|
||||
#include "aicombataction.hpp"
|
||||
#include "movement.hpp"
|
||||
#include "npcstats.hpp"
|
||||
#include "creaturestats.hpp"
|
||||
|
@ -794,6 +795,7 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim
|
|||
, mSecondsOfRunning(0)
|
||||
, mTurnAnimationThreshold(0)
|
||||
, mAttackingOrSpell(false)
|
||||
, mCastingManualSpell(false)
|
||||
, mTimeUntilWake(0.f)
|
||||
{
|
||||
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.
|
||||
&& 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)
|
||||
|
@ -1088,13 +1091,18 @@ bool CharacterController::updateCreatureState()
|
|||
if (weapType == WeapType_Spell)
|
||||
{
|
||||
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);
|
||||
|
||||
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
|
||||
{
|
||||
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);
|
||||
}
|
||||
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);
|
||||
|
||||
const ESM::Spell *spell = store.get<ESM::Spell>().find(spellid);
|
||||
|
@ -2377,6 +2386,11 @@ bool CharacterController::isAttackPrepairing() const
|
|||
mUpperBodyState == UpperCharState_MinAttackToMaxAttack;
|
||||
}
|
||||
|
||||
bool CharacterController::isCastingSpell() const
|
||||
{
|
||||
return mCastingManualSpell || mUpperBodyState == UpperCharState_CastingSpell;
|
||||
}
|
||||
|
||||
bool CharacterController::isReadyToBlock() const
|
||||
{
|
||||
return updateCarriedLeftVisible(mWeaponType);
|
||||
|
@ -2440,6 +2454,14 @@ void CharacterController::setAttackingOrSpell(bool 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)
|
||||
{
|
||||
mAttackType = attackType;
|
||||
|
|
|
@ -204,6 +204,7 @@ class CharacterController : public MWRender::Animation::TextKeyListener
|
|||
std::string mAttackType; // slash, chop or thrust
|
||||
|
||||
bool mAttackingOrSpell;
|
||||
bool mCastingManualSpell;
|
||||
|
||||
float mTimeUntilWake;
|
||||
|
||||
|
@ -276,6 +277,7 @@ public:
|
|||
void forceStateUpdate();
|
||||
|
||||
bool isAttackPrepairing() const;
|
||||
bool isCastingSpell() const;
|
||||
bool isReadyToBlock() const;
|
||||
bool isKnockedDown() const;
|
||||
bool isKnockedOut() const;
|
||||
|
@ -286,6 +288,7 @@ public:
|
|||
bool isAttackingOrSpell() const;
|
||||
|
||||
void setAttackingOrSpell(bool attackingOrSpell);
|
||||
void castSpell(const std::string spellId, bool manualSpell=false);
|
||||
void setAIAttackType(const std::string& attackType);
|
||||
static void setAttackTypeRandomly(std::string& attackType);
|
||||
|
||||
|
|
|
@ -253,6 +253,12 @@ namespace MWMechanics
|
|||
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)
|
||||
{
|
||||
if(ptr == mWatched)
|
||||
|
@ -1758,6 +1764,11 @@ namespace MWMechanics
|
|||
stats.resurrect();
|
||||
}
|
||||
|
||||
bool MechanicsManager::isCastingSpell(const MWWorld::Ptr &ptr) const
|
||||
{
|
||||
return mActors.isCastingSpell(ptr);
|
||||
}
|
||||
|
||||
bool MechanicsManager::isReadyToBlock(const MWWorld::Ptr &ptr) const
|
||||
{
|
||||
return mActors.isReadyToBlock(ptr);
|
||||
|
|
|
@ -190,10 +190,14 @@ namespace MWMechanics
|
|||
|
||||
virtual void keepPlayerAlive();
|
||||
|
||||
virtual bool isCastingSpell (const MWWorld::Ptr& ptr) const;
|
||||
|
||||
virtual bool isReadyToBlock (const MWWorld::Ptr& ptr) const;
|
||||
/// Is \a ptr casting spell or using weapon now?
|
||||
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
|
||||
/// 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);
|
||||
|
|
|
@ -315,13 +315,14 @@ namespace MWMechanics
|
|||
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)
|
||||
, mTarget(target)
|
||||
, mStack(false)
|
||||
, mHitPosition(0,0,0)
|
||||
, mAlwaysSucceed(false)
|
||||
, mFromProjectile(fromProjectile)
|
||||
, mIsScripted(isScripted)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -863,7 +864,7 @@ namespace MWMechanics
|
|||
|
||||
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);
|
||||
|
||||
|
@ -910,7 +911,7 @@ namespace MWMechanics
|
|||
stats.getSpells().usePower(spell);
|
||||
}
|
||||
|
||||
if (mCaster == getPlayer() && spellIncreasesSkill(spell))
|
||||
if (mCaster == getPlayer() && spellIncreasesSkill())
|
||||
mCaster.getClass().skillUsageSucceeded(mCaster,
|
||||
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)
|
||||
{
|
||||
/*
|
||||
|
|
|
@ -88,9 +88,10 @@ namespace MWMechanics
|
|||
osg::Vec3f mHitPosition; // Used for spawning area orb
|
||||
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 mIsScripted; // True if spell is casted from script and ignores some checks (mana level, success chance, etc.)
|
||||
|
||||
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);
|
||||
|
||||
|
@ -108,6 +109,8 @@ namespace MWMechanics
|
|||
|
||||
void playSpellCastingEffects(const std::string &spellid);
|
||||
|
||||
bool spellIncreasesSkill();
|
||||
|
||||
/// Launch a bolt with the given effects.
|
||||
void launchMagicBolt ();
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#include "../mwworld/esmstore.hpp"
|
||||
#include "../mwworld/cellstore.hpp"
|
||||
|
||||
#include "../mwmechanics/aicast.hpp"
|
||||
#include "../mwmechanics/npcstats.hpp"
|
||||
#include "../mwmechanics/creaturestats.hpp"
|
||||
#include "../mwmechanics/spellcasting.hpp"
|
||||
|
@ -1056,15 +1057,31 @@ namespace MWScript
|
|||
{
|
||||
MWWorld::Ptr ptr = R()(runtime);
|
||||
|
||||
std::string spell = runtime.getStringLiteral (runtime[0].mInteger);
|
||||
std::string spellId = runtime.getStringLiteral (runtime[0].mInteger);
|
||||
runtime.pop();
|
||||
|
||||
std::string targetId = ::Misc::StringUtils::lowerCase(runtime.getStringLiteral (runtime[0].mInteger));
|
||||
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);
|
||||
|
||||
MWMechanics::CastSpell cast(ptr, target);
|
||||
MWMechanics::CastSpell cast(ptr, target, false, true);
|
||||
cast.mHitPosition = target.getRefData().getPosition().asVec3();
|
||||
cast.mAlwaysSucceed = true;
|
||||
cast.cast(spell);
|
||||
|
@ -1082,7 +1099,7 @@ namespace MWScript
|
|||
std::string spell = runtime.getStringLiteral (runtime[0].mInteger);
|
||||
runtime.pop();
|
||||
|
||||
MWMechanics::CastSpell cast(ptr, ptr);
|
||||
MWMechanics::CastSpell cast(ptr, ptr, false, true);
|
||||
cast.mHitPosition = ptr.getRefData().getPosition().asVec3();
|
||||
cast.mAlwaysSucceed = true;
|
||||
cast.cast(spell);
|
||||
|
|
|
@ -2771,13 +2771,13 @@ namespace MWWorld
|
|||
return !fail;
|
||||
}
|
||||
|
||||
void World::castSpell(const Ptr &actor)
|
||||
void World::castSpell(const Ptr &actor, bool manualSpell)
|
||||
{
|
||||
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.
|
||||
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);
|
||||
|
||||
const float fCombatDistance = getStore().get<ESM::GameSetting>().find("fCombatDistance")->getFloat();
|
||||
|
@ -2795,51 +2795,71 @@ namespace MWWorld
|
|||
|
||||
if (target.isEmpty())
|
||||
{
|
||||
// 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
|
||||
osg::Vec3f origin = getActorHeadTransform(actor).getTrans();
|
||||
|
||||
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::Vec3f direction = orient * osg::Vec3f(0,1,0);
|
||||
float distance = getMaxActivationDistance();
|
||||
osg::Vec3f dest = origin + direction * distance;
|
||||
|
||||
MWRender::RenderingManager::RayResult result2 = mRendering->castRay(origin, dest, true, true);
|
||||
|
||||
float dist1 = std::numeric_limits<float>::max();
|
||||
float dist2 = std::numeric_limits<float>::max();
|
||||
|
||||
if (!result1.first.isEmpty() && result1.first.getClass().isActor())
|
||||
dist1 = (origin - result1.second).length();
|
||||
if (result2.mHit)
|
||||
dist2 = (origin - result2.mHitPointWorld).length();
|
||||
|
||||
if (!result1.first.isEmpty() && result1.first.getClass().isActor())
|
||||
// For scripted spells we should not use hit contact
|
||||
if (manualSpell)
|
||||
{
|
||||
target = result1.first;
|
||||
hitPosition = result1.second;
|
||||
if (dist1 > getMaxActivationDistance())
|
||||
target = NULL;
|
||||
// Actors that are targeted by this actor's Follow or Escort packages also side with them
|
||||
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 if (result2.mHit)
|
||||
else
|
||||
{
|
||||
target = result2.mHitObject;
|
||||
hitPosition = result2.mHitPointWorld;
|
||||
if (dist2 > getMaxActivationDistance() && !target.isEmpty() && !target.getClass().canBeActivated(target))
|
||||
target = NULL;
|
||||
// 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
|
||||
osg::Vec3f origin = getActorHeadTransform(actor).getTrans();
|
||||
|
||||
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::Vec3f direction = orient * osg::Vec3f(0,1,0);
|
||||
float distance = getMaxActivationDistance();
|
||||
osg::Vec3f dest = origin + direction * distance;
|
||||
|
||||
MWRender::RenderingManager::RayResult result2 = mRendering->castRay(origin, dest, true, true);
|
||||
|
||||
float dist1 = std::numeric_limits<float>::max();
|
||||
float dist2 = std::numeric_limits<float>::max();
|
||||
|
||||
if (!result1.first.isEmpty() && result1.first.getClass().isActor())
|
||||
dist1 = (origin - result1.second).length();
|
||||
if (result2.mHit)
|
||||
dist2 = (origin - result2.mHitPointWorld).length();
|
||||
|
||||
if (!result1.first.isEmpty() && result1.first.getClass().isActor())
|
||||
{
|
||||
target = result1.first;
|
||||
hitPosition = result1.second;
|
||||
if (dist1 > getMaxActivationDistance())
|
||||
target = NULL;
|
||||
}
|
||||
else if (result2.mHit)
|
||||
{
|
||||
target = result2.mHitObject;
|
||||
hitPosition = result2.mHitPointWorld;
|
||||
if (dist2 > getMaxActivationDistance() && !target.isEmpty() && !target.getClass().canBeActivated(target))
|
||||
target = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string selectedSpell = stats.getSpells().getSelectedSpell();
|
||||
|
||||
MWMechanics::CastSpell cast(actor, target);
|
||||
MWMechanics::CastSpell cast(actor, target, false, manualSpell);
|
||||
cast.mHitPosition = hitPosition;
|
||||
|
||||
if (!selectedSpell.empty())
|
||||
|
|
|
@ -602,7 +602,7 @@ namespace MWWorld
|
|||
* @brief Cast the actual spell, should be called mid-animation
|
||||
* @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 launchProjectile (MWWorld::Ptr actor, MWWorld::ConstPtr projectile,
|
||||
|
|
Loading…
Reference in a new issue