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
|
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;
|
||||||
|
|
||||||
|
|
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,
|
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.
|
|
||||||
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;
|
// Actors that are targeted by this actor's Follow or Escort packages also side with them
|
||||||
hitPosition = result1.second;
|
if (actor != MWMechanics::getPlayer())
|
||||||
if (dist1 > getMaxActivationDistance())
|
{
|
||||||
target = NULL;
|
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;
|
// For actor targets, we want to use hit contact with bounding boxes.
|
||||||
hitPosition = result2.mHitPointWorld;
|
// 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 (dist2 > getMaxActivationDistance() && !target.isEmpty() && !target.getClass().canBeActivated(target))
|
// For object targets, we want the detailed shapes (rendering raycast).
|
||||||
target = NULL;
|
// 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();
|
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…
Reference in a new issue