Merged pull request #1789

fix/skillcap
Marc Zinnschlag 7 years ago
commit 0bd6078826

@ -2,6 +2,7 @@
------
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 #2326: After a bound item expires the last equipped item of that type is not automatically re-equipped
Bug #2455: Creatures attacks degrade armor
@ -65,7 +66,9 @@
Bug #4495: Crossbow animations blending is buggy
Bug #4496: SpellTurnLeft and SpellTurnRight animation groups are unused
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 #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 #3641: Editor: Limit FPS in 3d preview window
Feature #4222: 360° screenshots

@ -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;

@ -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,
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();
@ -2794,6 +2794,25 @@ namespace MWWorld
target = NULL;
if (target.isEmpty())
{
// For scripted spells we should not use hit contact
if (manualSpell)
{
// 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
{
// 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.
@ -2836,10 +2855,11 @@ namespace MWWorld
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…
Cancel
Save