mirror of
https://github.com/OpenMW/openmw.git
synced 2025-03-27 04:10:24 +00:00
Merge remote-tracking branch 'refs/remotes/upstream/master'
This commit is contained in:
commit
25e92481f4
33 changed files with 506 additions and 175 deletions
|
@ -173,6 +173,7 @@ Programmers
|
||||||
vocollapse
|
vocollapse
|
||||||
Yohaulticetl
|
Yohaulticetl
|
||||||
zelurker
|
zelurker
|
||||||
|
James Carty (MrTopCat)
|
||||||
|
|
||||||
Documentation
|
Documentation
|
||||||
-------------
|
-------------
|
||||||
|
@ -181,9 +182,11 @@ Documentation
|
||||||
Alejandro Sanchez (HiPhish)
|
Alejandro Sanchez (HiPhish)
|
||||||
Bodillium
|
Bodillium
|
||||||
Bret Curtis (psi29a)
|
Bret Curtis (psi29a)
|
||||||
|
David Walley (Loriel)
|
||||||
Cramal
|
Cramal
|
||||||
Ryan Tucker (Ravenwing)
|
Ryan Tucker (Ravenwing)
|
||||||
sir_herrbatka
|
sir_herrbatka
|
||||||
|
Diego Crespo
|
||||||
|
|
||||||
Packagers
|
Packagers
|
||||||
---------
|
---------
|
||||||
|
|
|
@ -39,6 +39,7 @@
|
||||||
Bug #4230: AiTravel package issues break some Tribunal quests
|
Bug #4230: AiTravel package issues break some Tribunal quests
|
||||||
Bug #4231: Infected rats from the "Crimson Plague" quest rendered unconscious by change in Drain Fatigue functionality
|
Bug #4231: Infected rats from the "Crimson Plague" quest rendered unconscious by change in Drain Fatigue functionality
|
||||||
Bug #4251: Stationary NPCs do not return to their position after combat
|
Bug #4251: Stationary NPCs do not return to their position after combat
|
||||||
|
Bug #4271: Scamp flickers when attacking
|
||||||
Bug #4274: Pre-0.43 death animations are not forward-compatible with 0.43+
|
Bug #4274: Pre-0.43 death animations are not forward-compatible with 0.43+
|
||||||
Bug #4286: Scripted animations can be interrupted
|
Bug #4286: Scripted animations can be interrupted
|
||||||
Bug #4291: Non-persistent actors that started the game as dead do not play death animations
|
Bug #4291: Non-persistent actors that started the game as dead do not play death animations
|
||||||
|
@ -83,6 +84,7 @@
|
||||||
Bug #4503: Cast and ExplodeSpell commands increase alteration skill
|
Bug #4503: Cast and ExplodeSpell commands increase alteration skill
|
||||||
Bug #4510: Division by zero in MWMechanics::CreatureStats::setAttribute
|
Bug #4510: Division by zero in MWMechanics::CreatureStats::setAttribute
|
||||||
Bug #4519: Knockdown does not discard movement in the 1st-person mode
|
Bug #4519: Knockdown does not discard movement in the 1st-person mode
|
||||||
|
Bug #4531: Movement does not reset idle animations
|
||||||
Bug #4539: Paper Doll is affected by GUI scaling
|
Bug #4539: Paper Doll is affected by GUI scaling
|
||||||
Bug #4545: Creatures flee from werewolves
|
Bug #4545: Creatures flee from werewolves
|
||||||
Bug #4551: Replace 0 sound range with default range separately
|
Bug #4551: Replace 0 sound range with default range separately
|
||||||
|
@ -95,6 +97,8 @@
|
||||||
Bug #4574: Player turning animations are twitchy
|
Bug #4574: Player turning animations are twitchy
|
||||||
Bug #4575: Weird result of attack animation blending with movement animations
|
Bug #4575: Weird result of attack animation blending with movement animations
|
||||||
Bug #4576: Reset of idle animations when attack can not be started
|
Bug #4576: Reset of idle animations when attack can not be started
|
||||||
|
Bug #4591: Attack strength should be 0 if player did not hold the attack button
|
||||||
|
Feature #1645: Casting effects from objects
|
||||||
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 #3083: Play animation when NPC is casting spell via script
|
||||||
Feature #3103: Provide option for disposition to get increased by successful trade
|
Feature #3103: Provide option for disposition to get increased by successful trade
|
||||||
|
|
|
@ -257,7 +257,7 @@ namespace MWBase
|
||||||
virtual void cleanupSummonedCreature(const MWWorld::Ptr& caster, int creatureActorId) = 0;
|
virtual void cleanupSummonedCreature(const MWWorld::Ptr& caster, int creatureActorId) = 0;
|
||||||
|
|
||||||
virtual void confiscateStolenItemToOwner(const MWWorld::Ptr &player, const MWWorld::Ptr &item, const MWWorld::Ptr& victim, int count) = 0;
|
virtual void confiscateStolenItemToOwner(const MWWorld::Ptr &player, const MWWorld::Ptr &item, const MWWorld::Ptr& victim, int count) = 0;
|
||||||
virtual bool isAttackPrepairing(const MWWorld::Ptr& ptr) = 0;
|
virtual bool isAttackPreparing(const MWWorld::Ptr& ptr) = 0;
|
||||||
virtual bool isRunning(const MWWorld::Ptr& ptr) = 0;
|
virtual bool isRunning(const MWWorld::Ptr& ptr) = 0;
|
||||||
virtual bool isSneaking(const MWWorld::Ptr& ptr) = 0;
|
virtual bool isSneaking(const MWWorld::Ptr& ptr) = 0;
|
||||||
};
|
};
|
||||||
|
|
|
@ -536,7 +536,7 @@ namespace MWBase
|
||||||
/// Spawn a blood effect for \a ptr at \a worldPosition
|
/// Spawn a blood effect for \a ptr at \a worldPosition
|
||||||
virtual void spawnBloodEffect (const MWWorld::Ptr& ptr, const osg::Vec3f& worldPosition) = 0;
|
virtual void spawnBloodEffect (const MWWorld::Ptr& ptr, const osg::Vec3f& worldPosition) = 0;
|
||||||
|
|
||||||
virtual void spawnEffect (const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPos) = 0;
|
virtual void spawnEffect (const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPos, float scale = 1.f, bool isMagicVFX = true) = 0;
|
||||||
|
|
||||||
virtual void explodeSpell(const osg::Vec3f& origin, const ESM::EffectList& effects, const MWWorld::Ptr& caster,
|
virtual void explodeSpell(const osg::Vec3f& origin, const ESM::EffectList& effects, const MWWorld::Ptr& caster,
|
||||||
const MWWorld::Ptr& ignore, ESM::RangeType rangeType, const std::string& id,
|
const MWWorld::Ptr& ignore, ESM::RangeType rangeType, const std::string& id,
|
||||||
|
|
|
@ -1004,9 +1004,9 @@ namespace MWInput
|
||||||
if (!mControlSwitch["playerfighting"] || !mControlSwitch["playercontrols"])
|
if (!mControlSwitch["playerfighting"] || !mControlSwitch["playercontrols"])
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// We want to interrupt animation only if attack is prepairing, but still is not triggered
|
// We want to interrupt animation only if attack is preparing, but still is not triggered
|
||||||
// Otherwise we will get a "speedshooting" exploit, when player can skip reload animation by hitting "Toggle Weapon" key twice
|
// Otherwise we will get a "speedshooting" exploit, when player can skip reload animation by hitting "Toggle Weapon" key twice
|
||||||
if (MWBase::Environment::get().getMechanicsManager()->isAttackPrepairing(mPlayer->getPlayer()))
|
if (MWBase::Environment::get().getMechanicsManager()->isAttackPreparing(mPlayer->getPlayer()))
|
||||||
mPlayer->setAttackingOrSpell(false);
|
mPlayer->setAttackingOrSpell(false);
|
||||||
else if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(mPlayer->getPlayer()))
|
else if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(mPlayer->getPlayer()))
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -896,14 +896,14 @@ namespace MWMechanics
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Actors::isAttackPrepairing(const MWWorld::Ptr& ptr)
|
bool Actors::isAttackPreparing(const MWWorld::Ptr& ptr)
|
||||||
{
|
{
|
||||||
PtrActorMap::iterator it = mActors.find(ptr);
|
PtrActorMap::iterator it = mActors.find(ptr);
|
||||||
if (it == mActors.end())
|
if (it == mActors.end())
|
||||||
return false;
|
return false;
|
||||||
CharacterController* ctrl = it->second->getCharacterController();
|
CharacterController* ctrl = it->second->getCharacterController();
|
||||||
|
|
||||||
return ctrl->isAttackPrepairing();
|
return ctrl->isAttackPreparing();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Actors::isRunning(const MWWorld::Ptr& ptr)
|
bool Actors::isRunning(const MWWorld::Ptr& ptr)
|
||||||
|
|
|
@ -118,7 +118,7 @@ namespace MWMechanics
|
||||||
int countDeaths (const std::string& id) const;
|
int countDeaths (const std::string& id) const;
|
||||||
///< Return the number of deaths for actors with the given ID.
|
///< Return the number of deaths for actors with the given ID.
|
||||||
|
|
||||||
bool isAttackPrepairing(const MWWorld::Ptr& ptr);
|
bool isAttackPreparing(const MWWorld::Ptr& ptr);
|
||||||
bool isRunning(const MWWorld::Ptr& ptr);
|
bool isRunning(const MWWorld::Ptr& ptr);
|
||||||
bool isSneaking(const MWWorld::Ptr& ptr);
|
bool isSneaking(const MWWorld::Ptr& ptr);
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,19 @@ bool MWMechanics::AiCast::execute(const MWWorld::Ptr& actor, MWMechanics::Charac
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
osg::Vec3f dir = target.getRefData().getPosition().asVec3() - actor.getRefData().getPosition().asVec3();
|
osg::Vec3f targetPos = target.getRefData().getPosition().asVec3();
|
||||||
|
if (target.getClass().isActor())
|
||||||
|
{
|
||||||
|
osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(target);
|
||||||
|
targetPos.z() += halfExtents.z() * 2 * 0.75f;
|
||||||
|
}
|
||||||
|
|
||||||
|
osg::Vec3f actorPos = actor.getRefData().getPosition().asVec3();
|
||||||
|
osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(actor);
|
||||||
|
actorPos.z() += halfExtents.z() * 2 * 0.75f;
|
||||||
|
|
||||||
|
osg::Vec3f dir = targetPos - actorPos;
|
||||||
|
|
||||||
bool turned = smoothTurn(actor, getZAngleToDir(dir), 2, osg::DegreesToRadians(3.f));
|
bool turned = smoothTurn(actor, getZAngleToDir(dir), 2, osg::DegreesToRadians(3.f));
|
||||||
turned &= smoothTurn(actor, getXAngleToDir(dir), 0, osg::DegreesToRadians(3.f));
|
turned &= smoothTurn(actor, getXAngleToDir(dir), 0, osg::DegreesToRadians(3.f));
|
||||||
|
|
||||||
|
|
|
@ -372,20 +372,31 @@ namespace MWMechanics
|
||||||
actorMovementSettings.mPosition[1] = storage.mMovement.mPosition[1];
|
actorMovementSettings.mPosition[1] = storage.mMovement.mPosition[1];
|
||||||
actorMovementSettings.mPosition[2] = storage.mMovement.mPosition[2];
|
actorMovementSettings.mPosition[2] = storage.mMovement.mPosition[2];
|
||||||
|
|
||||||
rotateActorOnAxis(actor, 2, actorMovementSettings, storage.mMovement);
|
rotateActorOnAxis(actor, 2, actorMovementSettings, storage);
|
||||||
rotateActorOnAxis(actor, 0, actorMovementSettings, storage.mMovement);
|
rotateActorOnAxis(actor, 0, actorMovementSettings, storage);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AiCombat::rotateActorOnAxis(const MWWorld::Ptr& actor, int axis,
|
void AiCombat::rotateActorOnAxis(const MWWorld::Ptr& actor, int axis,
|
||||||
MWMechanics::Movement& actorMovementSettings, MWMechanics::Movement& desiredMovement)
|
MWMechanics::Movement& actorMovementSettings, AiCombatStorage& storage)
|
||||||
{
|
{
|
||||||
actorMovementSettings.mRotation[axis] = 0;
|
actorMovementSettings.mRotation[axis] = 0;
|
||||||
float& targetAngleRadians = desiredMovement.mRotation[axis];
|
float& targetAngleRadians = storage.mMovement.mRotation[axis];
|
||||||
if (targetAngleRadians != 0)
|
if (targetAngleRadians != 0)
|
||||||
{
|
{
|
||||||
if (smoothTurn(actor, targetAngleRadians, axis))
|
// Some attack animations contain small amount of movement.
|
||||||
|
// Since we use cone shapes for melee, we can use a threshold to avoid jittering
|
||||||
|
std::shared_ptr<Action>& currentAction = storage.mCurrentAction;
|
||||||
|
bool isRangedCombat = false;
|
||||||
|
currentAction->getCombatRange(isRangedCombat);
|
||||||
|
// Check if the actor now facing desired direction, no need to turn any more
|
||||||
|
if (isRangedCombat)
|
||||||
{
|
{
|
||||||
// actor now facing desired direction, no need to turn any more
|
if (smoothTurn(actor, targetAngleRadians, axis))
|
||||||
|
targetAngleRadians = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (smoothTurn(actor, targetAngleRadians, axis, osg::DegreesToRadians(3.f)))
|
||||||
targetAngleRadians = 0;
|
targetAngleRadians = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -453,7 +464,7 @@ namespace MWMechanics
|
||||||
if (distToTarget <= rangeAttackOfTarget && Misc::Rng::rollClosedProbability() < 0.25)
|
if (distToTarget <= rangeAttackOfTarget && Misc::Rng::rollClosedProbability() < 0.25)
|
||||||
{
|
{
|
||||||
mMovement.mPosition[0] = Misc::Rng::rollProbability() < 0.5 ? 1.0f : -1.0f; // to the left/right
|
mMovement.mPosition[0] = Misc::Rng::rollProbability() < 0.5 ? 1.0f : -1.0f; // to the left/right
|
||||||
mTimerCombatMove = 0.05f + 0.15f * Misc::Rng::rollClosedProbability();
|
mTimerCombatMove = 0.1f + 0.1f * Misc::Rng::rollClosedProbability();
|
||||||
mCombatMove = true;
|
mCombatMove = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -129,7 +129,7 @@ namespace MWMechanics
|
||||||
/// Transfer desired movement (from AiCombatStorage) to Actor
|
/// Transfer desired movement (from AiCombatStorage) to Actor
|
||||||
void updateActorsMovement(const MWWorld::Ptr& actor, float duration, AiCombatStorage& storage);
|
void updateActorsMovement(const MWWorld::Ptr& actor, float duration, AiCombatStorage& storage);
|
||||||
void rotateActorOnAxis(const MWWorld::Ptr& actor, int axis,
|
void rotateActorOnAxis(const MWWorld::Ptr& actor, int axis,
|
||||||
MWMechanics::Movement& actorMovementSettings, MWMechanics::Movement& desiredMovement);
|
MWMechanics::Movement& actorMovementSettings, AiCombatStorage& storage);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
#include "aicombataction.hpp"
|
#include "aicombataction.hpp"
|
||||||
#include "aipursue.hpp"
|
#include "aipursue.hpp"
|
||||||
#include "actorutil.hpp"
|
#include "actorutil.hpp"
|
||||||
|
#include "../mwworld/class.hpp"
|
||||||
|
|
||||||
namespace MWMechanics
|
namespace MWMechanics
|
||||||
{
|
{
|
||||||
|
@ -122,6 +123,20 @@ bool AiSequence::isInCombat() const
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool AiSequence::isEngagedWithActor() const
|
||||||
|
{
|
||||||
|
for (std::list<AiPackage *>::const_iterator it = mPackages.begin(); it != mPackages.end(); ++it)
|
||||||
|
{
|
||||||
|
if ((*it)->getTypeId() == AiPackage::TypeIdCombat)
|
||||||
|
{
|
||||||
|
MWWorld::Ptr target2 = (*it)->getTarget();
|
||||||
|
if (!target2.isEmpty() && target2.getClass().isNpc())
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool AiSequence::hasPackage(int typeId) const
|
bool AiSequence::hasPackage(int typeId) const
|
||||||
{
|
{
|
||||||
for (std::list<AiPackage*>::const_iterator it = mPackages.begin(); it != mPackages.end(); ++it)
|
for (std::list<AiPackage*>::const_iterator it = mPackages.begin(); it != mPackages.end(); ++it)
|
||||||
|
|
|
@ -88,6 +88,9 @@ namespace MWMechanics
|
||||||
/// Is there any combat package?
|
/// Is there any combat package?
|
||||||
bool isInCombat () const;
|
bool isInCombat () const;
|
||||||
|
|
||||||
|
/// Are we in combat with any other actor, who's also engaging us?
|
||||||
|
bool isEngagedWithActor () const;
|
||||||
|
|
||||||
/// Does this AI sequence have the given package type?
|
/// Does this AI sequence have the given package type?
|
||||||
bool hasPackage(int typeId) const;
|
bool hasPackage(int typeId) const;
|
||||||
|
|
||||||
|
|
|
@ -411,10 +411,6 @@ void CharacterController::refreshMovementAnims(const WeaponInfo* weap, Character
|
||||||
if(force || movement != mMovementState)
|
if(force || movement != mMovementState)
|
||||||
{
|
{
|
||||||
mMovementState = movement;
|
mMovementState = movement;
|
||||||
|
|
||||||
if (movement != CharState_None)
|
|
||||||
mIdleState = CharState_None;
|
|
||||||
|
|
||||||
std::string movementAnimName;
|
std::string movementAnimName;
|
||||||
MWRender::Animation::BlendMask movemask = MWRender::Animation::BlendMask_All;
|
MWRender::Animation::BlendMask movemask = MWRender::Animation::BlendMask_All;
|
||||||
const StateInfo *movestate = std::find_if(sMovementList, sMovementListEnd, FindCharState(mMovementState));
|
const StateInfo *movestate = std::find_if(sMovementList, sMovementListEnd, FindCharState(mMovementState));
|
||||||
|
@ -531,7 +527,7 @@ void CharacterController::refreshMovementAnims(const WeaponInfo* weap, Character
|
||||||
|
|
||||||
void CharacterController::refreshIdleAnims(const WeaponInfo* weap, CharacterState idle, bool force)
|
void CharacterController::refreshIdleAnims(const WeaponInfo* weap, CharacterState idle, bool force)
|
||||||
{
|
{
|
||||||
if(force || idle != mIdleState || (!mAnimation->isPlaying(mCurrentIdle) && mAnimQueue.empty()))
|
if(force || idle != mIdleState || mIdleState == CharState_None || (!mAnimation->isPlaying(mCurrentIdle) && mAnimQueue.empty()))
|
||||||
{
|
{
|
||||||
mIdleState = idle;
|
mIdleState = idle;
|
||||||
size_t numLoops = ~0ul;
|
size_t numLoops = ~0ul;
|
||||||
|
@ -565,11 +561,21 @@ void CharacterController::refreshIdleAnims(const WeaponInfo* weap, CharacterStat
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// There is no need to restart anim if the new and old anims are the same.
|
||||||
|
// Just update a number of loops.
|
||||||
|
float startPoint = 0;
|
||||||
|
if (!mCurrentIdle.empty() && mCurrentIdle == idleGroup)
|
||||||
|
{
|
||||||
|
mAnimation->getInfo(mCurrentIdle, &startPoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!mCurrentIdle.empty())
|
||||||
mAnimation->disable(mCurrentIdle);
|
mAnimation->disable(mCurrentIdle);
|
||||||
|
|
||||||
mCurrentIdle = idleGroup;
|
mCurrentIdle = idleGroup;
|
||||||
if(!mCurrentIdle.empty())
|
if(!mCurrentIdle.empty())
|
||||||
mAnimation->play(mCurrentIdle, idlePriority, MWRender::Animation::BlendMask_All, false,
|
mAnimation->play(mCurrentIdle, idlePriority, MWRender::Animation::BlendMask_All, false,
|
||||||
1.0f, "start", "stop", 0.0f, numLoops, true);
|
1.0f, "start", "stop", startPoint, numLoops, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1385,14 +1391,6 @@ bool CharacterController::updateWeaponState()
|
||||||
MWBase::Environment::get().getWorld()->breakInvisibility(mPtr);
|
MWBase::Environment::get().getWorld()->breakInvisibility(mPtr);
|
||||||
mAttackStrength = 0;
|
mAttackStrength = 0;
|
||||||
|
|
||||||
// Randomize attacks for non-bipedal creatures with Weapon flag
|
|
||||||
if (mPtr.getClass().getTypeName() == typeid(ESM::Creature).name() &&
|
|
||||||
!mPtr.getClass().isBipedal(mPtr) &&
|
|
||||||
(!mAnimation->hasAnimation(mCurrentWeapon) || isRandomAttackAnimation(mCurrentWeapon)))
|
|
||||||
{
|
|
||||||
mCurrentWeapon = chooseRandomAttackAnimation();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(mWeaponType == WeapType_Spell)
|
if(mWeaponType == WeapType_Spell)
|
||||||
{
|
{
|
||||||
// Unset casting flag, otherwise pressing the mouse button down would
|
// Unset casting flag, otherwise pressing the mouse button down would
|
||||||
|
@ -1401,15 +1399,10 @@ bool CharacterController::updateWeaponState()
|
||||||
if (mPtr == player)
|
if (mPtr == player)
|
||||||
{
|
{
|
||||||
MWBase::Environment::get().getWorld()->getPlayer().setAttackingOrSpell(false);
|
MWBase::Environment::get().getWorld()->getPlayer().setAttackingOrSpell(false);
|
||||||
}
|
|
||||||
|
|
||||||
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
|
|
||||||
|
|
||||||
// For the player, set the spell we want to cast
|
// For the player, set the spell we want to cast
|
||||||
// This has to be done at the start of the casting animation,
|
// This has to be done at the start of the casting animation,
|
||||||
// *not* when selecting a spell in the GUI (otherwise you could change the spell mid-animation)
|
// *not* when selecting a spell in the GUI (otherwise you could change the spell mid-animation)
|
||||||
if (mPtr == player)
|
|
||||||
{
|
|
||||||
std::string selectedSpell = MWBase::Environment::get().getWindowManager()->getSelectedSpell();
|
std::string selectedSpell = MWBase::Environment::get().getWindowManager()->getSelectedSpell();
|
||||||
stats.getSpells().setSelectedSpell(selectedSpell);
|
stats.getSpells().setSelectedSpell(selectedSpell);
|
||||||
}
|
}
|
||||||
|
@ -1421,6 +1414,7 @@ bool CharacterController::updateWeaponState()
|
||||||
MWMechanics::CastSpell cast(mPtr, NULL, false, mCastingManualSpell);
|
MWMechanics::CastSpell cast(mPtr, NULL, false, mCastingManualSpell);
|
||||||
cast.playSpellCastingEffects(spellid);
|
cast.playSpellCastingEffects(spellid);
|
||||||
|
|
||||||
|
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
|
||||||
const ESM::Spell *spell = store.get<ESM::Spell>().find(spellid);
|
const ESM::Spell *spell = store.get<ESM::Spell>().find(spellid);
|
||||||
const ESM::ENAMstruct &lastEffect = spell->mEffects.mList.back();
|
const ESM::ENAMstruct &lastEffect = spell->mEffects.mList.back();
|
||||||
const ESM::MagicEffect *effect;
|
const ESM::MagicEffect *effect;
|
||||||
|
@ -1520,13 +1514,12 @@ bool CharacterController::updateWeaponState()
|
||||||
startKey = mAttackType+" start";
|
startKey = mAttackType+" start";
|
||||||
stopKey = mAttackType+" min attack";
|
stopKey = mAttackType+" min attack";
|
||||||
}
|
}
|
||||||
|
else if (isRandomAttackAnimation(mCurrentWeapon))
|
||||||
if (isRandomAttackAnimation(mCurrentWeapon))
|
|
||||||
{
|
{
|
||||||
startKey = "start";
|
startKey = "start";
|
||||||
stopKey = "stop";
|
stopKey = "stop";
|
||||||
}
|
}
|
||||||
else if (mAttackType != "shoot")
|
else
|
||||||
{
|
{
|
||||||
if(mPtr == getPlayer())
|
if(mPtr == getPlayer())
|
||||||
{
|
{
|
||||||
|
@ -1683,11 +1676,6 @@ bool CharacterController::updateWeaponState()
|
||||||
std::string start, stop;
|
std::string start, stop;
|
||||||
switch(mUpperBodyState)
|
switch(mUpperBodyState)
|
||||||
{
|
{
|
||||||
case UpperCharState_StartToMinAttack:
|
|
||||||
start = mAttackType+" min attack";
|
|
||||||
stop = mAttackType+" max attack";
|
|
||||||
mUpperBodyState = UpperCharState_MinAttackToMaxAttack;
|
|
||||||
break;
|
|
||||||
case UpperCharState_MinAttackToMaxAttack:
|
case UpperCharState_MinAttackToMaxAttack:
|
||||||
//hack to avoid body pos desync when jumping/sneaking in 'max attack' state
|
//hack to avoid body pos desync when jumping/sneaking in 'max attack' state
|
||||||
if(!mAnimation->isPlaying(mCurrentWeapon))
|
if(!mAnimation->isPlaying(mCurrentWeapon))
|
||||||
|
@ -1695,6 +1683,23 @@ bool CharacterController::updateWeaponState()
|
||||||
MWRender::Animation::BlendMask_All, false,
|
MWRender::Animation::BlendMask_All, false,
|
||||||
0, mAttackType+" min attack", mAttackType+" max attack", 0.999f, 0);
|
0, mAttackType+" min attack", mAttackType+" max attack", 0.999f, 0);
|
||||||
break;
|
break;
|
||||||
|
case UpperCharState_StartToMinAttack:
|
||||||
|
{
|
||||||
|
// If actor is already stopped preparing attack, do not play the "min attack -> max attack" part.
|
||||||
|
// Happens if the player did not hold the attack button.
|
||||||
|
// Note: if the "min attack"->"max attack" is a stub, "play" it anyway. Attack strength will be 1.
|
||||||
|
float minAttackTime = mAnimation->getTextKeyTime(mCurrentWeapon+": "+mAttackType+" "+"min attack");
|
||||||
|
float maxAttackTime = mAnimation->getTextKeyTime(mCurrentWeapon+": "+mAttackType+" "+"max attack");
|
||||||
|
if (mAttackingOrSpell || minAttackTime == maxAttackTime)
|
||||||
|
{
|
||||||
|
start = mAttackType+" min attack";
|
||||||
|
stop = mAttackType+" max attack";
|
||||||
|
mUpperBodyState = UpperCharState_MinAttackToMaxAttack;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
playSwishSound(0.0f);
|
||||||
|
}
|
||||||
|
// Fall-through
|
||||||
case UpperCharState_MaxAttackToMinHit:
|
case UpperCharState_MaxAttackToMinHit:
|
||||||
if(mAttackType == "shoot")
|
if(mAttackType == "shoot")
|
||||||
{
|
{
|
||||||
|
@ -2094,7 +2099,16 @@ void CharacterController::update(float duration)
|
||||||
|
|
||||||
if(mAnimQueue.empty() || inwater || sneak)
|
if(mAnimQueue.empty() || inwater || sneak)
|
||||||
{
|
{
|
||||||
idlestate = (inwater ? CharState_IdleSwim : (sneak && !inJump ? CharState_IdleSneak : CharState_Idle));
|
// Note: turning animations should not interrupt idle ones.
|
||||||
|
// Also movement should not stop idle animation for spellcasting stance.
|
||||||
|
if (inwater)
|
||||||
|
idlestate = CharState_IdleSwim;
|
||||||
|
else if (sneak && !inJump)
|
||||||
|
idlestate = CharState_IdleSneak;
|
||||||
|
else if (movestate != CharState_None && !isTurning() && mWeaponType != WeapType_Spell)
|
||||||
|
idlestate = CharState_None;
|
||||||
|
else
|
||||||
|
idlestate = CharState_Idle;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
updateAnimQueue();
|
updateAnimQueue();
|
||||||
|
@ -2493,7 +2507,7 @@ bool CharacterController::isRandomAttackAnimation(const std::string& group) cons
|
||||||
group == "attack3" || group == "swimattack3");
|
group == "attack3" || group == "swimattack3");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CharacterController::isAttackPrepairing() const
|
bool CharacterController::isAttackPreparing() const
|
||||||
{
|
{
|
||||||
return mUpperBodyState == UpperCharState_StartToMinAttack ||
|
return mUpperBodyState == UpperCharState_StartToMinAttack ||
|
||||||
mUpperBodyState == UpperCharState_MinAttackToMaxAttack;
|
mUpperBodyState == UpperCharState_MinAttackToMaxAttack;
|
||||||
|
|
|
@ -281,7 +281,7 @@ public:
|
||||||
|
|
||||||
void forceStateUpdate();
|
void forceStateUpdate();
|
||||||
|
|
||||||
bool isAttackPrepairing() const;
|
bool isAttackPreparing() const;
|
||||||
bool isCastingSpell() const;
|
bool isCastingSpell() const;
|
||||||
bool isReadyToBlock() const;
|
bool isReadyToBlock() const;
|
||||||
bool isKnockedDown() const;
|
bool isKnockedDown() const;
|
||||||
|
|
|
@ -436,9 +436,9 @@ namespace MWMechanics
|
||||||
return mActors.isActorDetected(actor, observer);
|
return mActors.isActorDetected(actor, observer);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MechanicsManager::isAttackPrepairing(const MWWorld::Ptr& ptr)
|
bool MechanicsManager::isAttackPreparing(const MWWorld::Ptr& ptr)
|
||||||
{
|
{
|
||||||
return mActors.isAttackPrepairing(ptr);
|
return mActors.isAttackPreparing(ptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MechanicsManager::isRunning(const MWWorld::Ptr& ptr)
|
bool MechanicsManager::isRunning(const MWWorld::Ptr& ptr)
|
||||||
|
@ -1462,24 +1462,11 @@ namespace MWMechanics
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attacking an NPC that is already in combat with any other NPC is not a crime
|
if (canCommitCrimeAgainst(target, attacker))
|
||||||
AiSequence& seq = statsTarget.getAiSequence();
|
|
||||||
bool isFightingNpc = false;
|
|
||||||
for (std::list<AiPackage*>::const_iterator it = seq.begin(); it != seq.end(); ++it)
|
|
||||||
{
|
|
||||||
if ((*it)->getTypeId() == AiPackage::TypeIdCombat)
|
|
||||||
{
|
|
||||||
MWWorld::Ptr target2 = (*it)->getTarget();
|
|
||||||
if (!target2.isEmpty() && target2.getClass().isNpc())
|
|
||||||
isFightingNpc = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (target.getClass().isNpc() && !attacker.isEmpty() && !seq.isInCombat(attacker)
|
|
||||||
&& !isAggressive(target, attacker) && !isFightingNpc
|
|
||||||
&& !target.getClass().getCreatureStats(target).getAiSequence().hasPackage(AiPackage::TypeIdPursue))
|
|
||||||
commitCrime(attacker, target, MWBase::MechanicsManager::OT_Assault);
|
commitCrime(attacker, target, MWBase::MechanicsManager::OT_Assault);
|
||||||
|
|
||||||
|
AiSequence& seq = statsTarget.getAiSequence();
|
||||||
|
|
||||||
if (!attacker.isEmpty() && (attacker.getClass().getCreatureStats(attacker).getAiSequence().isInCombat(target)
|
if (!attacker.isEmpty() && (attacker.getClass().getCreatureStats(attacker).getAiSequence().isInCombat(target)
|
||||||
|| attacker == getPlayer())
|
|| attacker == getPlayer())
|
||||||
&& !seq.isInCombat(attacker))
|
&& !seq.isInCombat(attacker))
|
||||||
|
@ -1506,6 +1493,14 @@ namespace MWMechanics
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool MechanicsManager::canCommitCrimeAgainst(const MWWorld::Ptr &target, const MWWorld::Ptr &attacker)
|
||||||
|
{
|
||||||
|
MWMechanics::AiSequence seq = target.getClass().getCreatureStats(target).getAiSequence();
|
||||||
|
return target.getClass().isNpc() && !attacker.isEmpty() && !seq.isInCombat(attacker)
|
||||||
|
&& !isAggressive(target, attacker) && !seq.isEngagedWithActor()
|
||||||
|
&& !target.getClass().getCreatureStats(target).getAiSequence().hasPackage(AiPackage::TypeIdPursue);
|
||||||
|
}
|
||||||
|
|
||||||
void MechanicsManager::actorKilled(const MWWorld::Ptr &victim, const MWWorld::Ptr &attacker)
|
void MechanicsManager::actorKilled(const MWWorld::Ptr &victim, const MWWorld::Ptr &attacker)
|
||||||
{
|
{
|
||||||
if (attacker.isEmpty() || victim.isEmpty())
|
if (attacker.isEmpty() || victim.isEmpty())
|
||||||
|
@ -1518,11 +1513,10 @@ namespace MWMechanics
|
||||||
return; // TODO: implement animal rights
|
return; // TODO: implement animal rights
|
||||||
|
|
||||||
const MWMechanics::NpcStats& victimStats = victim.getClass().getNpcStats(victim);
|
const MWMechanics::NpcStats& victimStats = victim.getClass().getNpcStats(victim);
|
||||||
if (victimStats.getCrimeId() == -1)
|
const MWWorld::Ptr &player = getPlayer();
|
||||||
return;
|
bool canCommit = attacker == player && canCommitCrimeAgainst(victim, attacker);
|
||||||
|
|
||||||
// For now we report only about crimes of player and player's followers
|
// For now we report only about crimes of player and player's followers
|
||||||
const MWWorld::Ptr &player = getPlayer();
|
|
||||||
if (attacker != player)
|
if (attacker != player)
|
||||||
{
|
{
|
||||||
std::set<MWWorld::Ptr> playerFollowers;
|
std::set<MWWorld::Ptr> playerFollowers;
|
||||||
|
@ -1531,6 +1525,9 @@ namespace MWMechanics
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!canCommit && victimStats.getCrimeId() == -1)
|
||||||
|
return;
|
||||||
|
|
||||||
// Simple check for who attacked first: if the player attacked first, a crimeId should be set
|
// Simple check for who attacked first: if the player attacked first, a crimeId should be set
|
||||||
// Doesn't handle possible edge case where no one reported the assault, but in such a case,
|
// Doesn't handle possible edge case where no one reported the assault, but in such a case,
|
||||||
// for bystanders it is not possible to tell who attacked first, anyway.
|
// for bystanders it is not possible to tell who attacked first, anyway.
|
||||||
|
|
|
@ -132,6 +132,12 @@ namespace MWMechanics
|
||||||
/// @note No-op for non-player attackers
|
/// @note No-op for non-player attackers
|
||||||
virtual void actorKilled (const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker);
|
virtual void actorKilled (const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker);
|
||||||
|
|
||||||
|
/// Checks if commiting a crime is currently valid
|
||||||
|
/// @param victim The actor being attacked
|
||||||
|
/// @param attacker The actor commiting the crime
|
||||||
|
/// @return true if the victim is a valid target for crime
|
||||||
|
virtual bool canCommitCrimeAgainst(const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker);
|
||||||
|
|
||||||
/// Utility to check if taking this item is illegal and calling commitCrime if so
|
/// Utility to check if taking this item is illegal and calling commitCrime if so
|
||||||
/// @param container The container the item is in; may be empty for an item in the world
|
/// @param container The container the item is in; may be empty for an item in the world
|
||||||
virtual void itemTaken (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item, const MWWorld::Ptr& container,
|
virtual void itemTaken (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item, const MWWorld::Ptr& container,
|
||||||
|
@ -223,7 +229,7 @@ namespace MWMechanics
|
||||||
|
|
||||||
virtual void confiscateStolenItemToOwner(const MWWorld::Ptr &player, const MWWorld::Ptr &item, const MWWorld::Ptr& victim, int count);
|
virtual void confiscateStolenItemToOwner(const MWWorld::Ptr &player, const MWWorld::Ptr &item, const MWWorld::Ptr& victim, int count);
|
||||||
|
|
||||||
virtual bool isAttackPrepairing(const MWWorld::Ptr& ptr);
|
virtual bool isAttackPreparing(const MWWorld::Ptr& ptr);
|
||||||
virtual bool isRunning(const MWWorld::Ptr& ptr);
|
virtual bool isRunning(const MWWorld::Ptr& ptr);
|
||||||
virtual bool isSneaking(const MWWorld::Ptr& ptr);
|
virtual bool isSneaking(const MWWorld::Ptr& ptr);
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
#include "../mwworld/inventorystore.hpp"
|
#include "../mwworld/inventorystore.hpp"
|
||||||
|
|
||||||
#include "../mwrender/animation.hpp"
|
#include "../mwrender/animation.hpp"
|
||||||
|
#include "../mwrender/vismask.hpp"
|
||||||
|
|
||||||
#include "npcstats.hpp"
|
#include "npcstats.hpp"
|
||||||
#include "actorutil.hpp"
|
#include "actorutil.hpp"
|
||||||
|
@ -328,14 +329,17 @@ namespace MWMechanics
|
||||||
|
|
||||||
void CastSpell::launchMagicBolt ()
|
void CastSpell::launchMagicBolt ()
|
||||||
{
|
{
|
||||||
osg::Vec3f fallbackDirection (0,1,0);
|
osg::Vec3f fallbackDirection(0, 1, 0);
|
||||||
|
osg::Vec3f offset(0, 0, 0);
|
||||||
|
if (!mTarget.isEmpty() && mTarget.getClass().isActor())
|
||||||
|
offset.z() = MWBase::Environment::get().getWorld()->getHalfExtents(mTarget).z();
|
||||||
|
|
||||||
// Fall back to a "caster to target" direction if we have no other means of determining it
|
// Fall back to a "caster to target" direction if we have no other means of determining it
|
||||||
// (e.g. when cast by a non-actor)
|
// (e.g. when cast by a non-actor)
|
||||||
if (!mTarget.isEmpty())
|
if (!mTarget.isEmpty())
|
||||||
fallbackDirection =
|
fallbackDirection =
|
||||||
osg::Vec3f(mTarget.getRefData().getPosition().asVec3())-
|
(mTarget.getRefData().getPosition().asVec3() + offset) -
|
||||||
osg::Vec3f(mCaster.getRefData().getPosition().asVec3());
|
(mCaster.getRefData().getPosition().asVec3());
|
||||||
|
|
||||||
MWBase::Environment::get().getWorld()->launchMagicBolt(mId, mCaster, fallbackDirection);
|
MWBase::Environment::get().getWorld()->launchMagicBolt(mId, mCaster, fallbackDirection);
|
||||||
}
|
}
|
||||||
|
@ -999,11 +1003,13 @@ namespace MWMechanics
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CastSpell::playSpellCastingEffects(const std::string &spellid){
|
void CastSpell::playSpellCastingEffects(const std::string &spellid)
|
||||||
|
{
|
||||||
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
|
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
|
||||||
const ESM::Spell *spell = store.get<ESM::Spell>().find(spellid);
|
const ESM::Spell *spell = store.get<ESM::Spell>().find(spellid);
|
||||||
|
|
||||||
|
std::vector<std::string> addedEffects;
|
||||||
|
|
||||||
for (std::vector<ESM::ENAMstruct>::const_iterator iter = spell->mEffects.mList.begin();
|
for (std::vector<ESM::ENAMstruct>::const_iterator iter = spell->mEffects.mList.begin();
|
||||||
iter != spell->mEffects.mList.end(); ++iter)
|
iter != spell->mEffects.mList.end(); ++iter)
|
||||||
{
|
{
|
||||||
|
@ -1012,8 +1018,6 @@ namespace MWMechanics
|
||||||
|
|
||||||
MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(mCaster);
|
MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(mCaster);
|
||||||
|
|
||||||
if (animation && mCaster.getClass().isActor()) // TODO: Non-actors should also create a spell cast vfx even if they are disabled (animation == NULL)
|
|
||||||
{
|
|
||||||
const ESM::Static* castStatic;
|
const ESM::Static* castStatic;
|
||||||
|
|
||||||
if (!effect->mCasting.empty())
|
if (!effect->mCasting.empty())
|
||||||
|
@ -1021,9 +1025,49 @@ namespace MWMechanics
|
||||||
else
|
else
|
||||||
castStatic = store.get<ESM::Static>().find ("VFX_DefaultCast");
|
castStatic = store.get<ESM::Static>().find ("VFX_DefaultCast");
|
||||||
|
|
||||||
|
// check if the effect was already added
|
||||||
|
if (std::find(addedEffects.begin(), addedEffects.end(), "meshes\\" + castStatic->mModel) != addedEffects.end())
|
||||||
|
continue;
|
||||||
|
|
||||||
std::string texture = effect->mParticle;
|
std::string texture = effect->mParticle;
|
||||||
|
|
||||||
animation->addEffect("meshes\\" + castStatic->mModel, effect->mIndex, false, "", texture);
|
float scale = 1.0f;
|
||||||
|
osg::Vec3f pos (mCaster.getRefData().getPosition().asVec3());
|
||||||
|
|
||||||
|
if (animation && mCaster.getClass().isNpc())
|
||||||
|
{
|
||||||
|
// For NPC we should take race height as scaling factor
|
||||||
|
const ESM::NPC *npc = mCaster.get<ESM::NPC>()->mBase;
|
||||||
|
const MWWorld::ESMStore &esmStore =
|
||||||
|
MWBase::Environment::get().getWorld()->getStore();
|
||||||
|
|
||||||
|
const ESM::Race *race =
|
||||||
|
esmStore.get<ESM::Race>().find(npc->mRace);
|
||||||
|
|
||||||
|
scale = npc->isMale() ? race->mData.mHeight.mMale : race->mData.mHeight.mFemale;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(mCaster);
|
||||||
|
|
||||||
|
// TODO: take a size of particle or NPC with height and weight = 1.0 as scale = 1.0
|
||||||
|
float scaleX = halfExtents.x() * 2 / 60.f;
|
||||||
|
float scaleY = halfExtents.y() * 2 / 60.f;
|
||||||
|
float scaleZ = halfExtents.z() * 2 / 120.f;
|
||||||
|
|
||||||
|
scale = std::max({ scaleX, scaleY, scaleZ });
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the caster has no animation, add the effect directly to the effectManager
|
||||||
|
if (animation)
|
||||||
|
{
|
||||||
|
animation->addEffect("meshes\\" + castStatic->mModel, effect->mIndex, false, "", texture, scale);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// We should set scale for effect manager manually
|
||||||
|
float meshScale = !mCaster.getClass().isActor() ? mCaster.getCellRef().getScale() : 1.0f;
|
||||||
|
MWBase::Environment::get().getWorld()->spawnEffect("meshes\\" + castStatic->mModel, effect->mParticle, pos, scale * meshScale);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (animation && !mCaster.getClass().isActor())
|
if (animation && !mCaster.getClass().isActor())
|
||||||
|
@ -1033,6 +1077,8 @@ namespace MWMechanics
|
||||||
"alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration"
|
"alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
addedEffects.push_back("meshes\\" + castStatic->mModel);
|
||||||
|
|
||||||
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
|
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
|
||||||
if(!effect->mCastSound.empty())
|
if(!effect->mCastSound.empty())
|
||||||
sndMgr->playSound3D(mCaster, effect->mCastSound, 1.0f, 1.0f);
|
sndMgr->playSound3D(mCaster, effect->mCastSound, 1.0f, 1.0f);
|
||||||
|
|
|
@ -108,12 +108,20 @@ namespace MWMechanics
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (actor.getClass().isNpc())
|
||||||
|
{
|
||||||
int skill = item.getClass().getEquipmentSkill(item);
|
int skill = item.getClass().getEquipmentSkill(item);
|
||||||
if (skill != -1)
|
if (skill != -1)
|
||||||
{
|
{
|
||||||
int value = actor.getClass().getSkill(actor, skill);
|
int value = actor.getClass().getSkill(actor, skill);
|
||||||
rating *= MWMechanics::getHitChance(actor, enemy, value) / 100.f;
|
rating *= MWMechanics::getHitChance(actor, enemy, value) / 100.f;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MWWorld::LiveCellRef<ESM::Creature> *ref = actor.get<ESM::Creature>();
|
||||||
|
rating *= MWMechanics::getHitChance(actor, enemy, ref->mBase->mData.mCombat) / 100.f;
|
||||||
|
}
|
||||||
|
|
||||||
return rating * rangedMult;
|
return rating * rangedMult;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include <osg/MatrixTransform>
|
#include <osg/MatrixTransform>
|
||||||
#include <osg/BlendFunc>
|
#include <osg/BlendFunc>
|
||||||
#include <osg/Material>
|
#include <osg/Material>
|
||||||
|
#include <osg/PositionAttitudeTransform>
|
||||||
|
|
||||||
#include <osgParticle/ParticleSystem>
|
#include <osgParticle/ParticleSystem>
|
||||||
#include <osgParticle/ParticleProcessor>
|
#include <osgParticle/ParticleProcessor>
|
||||||
|
@ -204,6 +205,110 @@ namespace
|
||||||
std::vector<std::pair<osg::Node*, osg::Group*> > mToRemove;
|
std::vector<std::pair<osg::Node*, osg::Group*> > mToRemove;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class RemoveFinishedCallbackVisitor : public RemoveVisitor
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
RemoveFinishedCallbackVisitor()
|
||||||
|
: RemoveVisitor()
|
||||||
|
, mEffectId(-1)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoveFinishedCallbackVisitor(int effectId)
|
||||||
|
: RemoveVisitor()
|
||||||
|
, mEffectId(effectId)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void apply(osg::Node &node)
|
||||||
|
{
|
||||||
|
traverse(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void apply(osg::Group &group)
|
||||||
|
{
|
||||||
|
traverse(group);
|
||||||
|
|
||||||
|
osg::Callback* callback = group.getUpdateCallback();
|
||||||
|
if (callback)
|
||||||
|
{
|
||||||
|
// We should remove empty transformation nodes and finished callbacks here
|
||||||
|
MWRender::UpdateVfxCallback* vfxCallback = dynamic_cast<MWRender::UpdateVfxCallback*>(callback);
|
||||||
|
bool finished = vfxCallback && vfxCallback->mFinished;
|
||||||
|
bool toRemove = vfxCallback && mEffectId >= 0 && vfxCallback->mParams.mEffectId == mEffectId;
|
||||||
|
if (finished || toRemove)
|
||||||
|
{
|
||||||
|
mToRemove.push_back(std::make_pair(group.asNode(), group.getParent(0)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void apply(osg::MatrixTransform &node)
|
||||||
|
{
|
||||||
|
traverse(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void apply(osg::Geometry&)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
int mEffectId;
|
||||||
|
};
|
||||||
|
|
||||||
|
class FindVfxCallbacksVisitor : public osg::NodeVisitor
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
std::vector<MWRender::UpdateVfxCallback*> mCallbacks;
|
||||||
|
|
||||||
|
FindVfxCallbacksVisitor()
|
||||||
|
: osg::NodeVisitor(TRAVERSE_ALL_CHILDREN)
|
||||||
|
, mEffectId(-1)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
FindVfxCallbacksVisitor(int effectId)
|
||||||
|
: osg::NodeVisitor(TRAVERSE_ALL_CHILDREN)
|
||||||
|
, mEffectId(effectId)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void apply(osg::Node &node)
|
||||||
|
{
|
||||||
|
traverse(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void apply(osg::Group &group)
|
||||||
|
{
|
||||||
|
osg::Callback* callback = group.getUpdateCallback();
|
||||||
|
if (callback)
|
||||||
|
{
|
||||||
|
MWRender::UpdateVfxCallback* vfxCallback = dynamic_cast<MWRender::UpdateVfxCallback*>(callback);
|
||||||
|
if (vfxCallback)
|
||||||
|
{
|
||||||
|
if (mEffectId < 0 || vfxCallback->mParams.mEffectId == mEffectId)
|
||||||
|
{
|
||||||
|
mCallbacks.push_back(vfxCallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
traverse(group);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void apply(osg::MatrixTransform &node)
|
||||||
|
{
|
||||||
|
traverse(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void apply(osg::Geometry&)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
int mEffectId;
|
||||||
|
};
|
||||||
|
|
||||||
// Removes all drawables from a graph.
|
// Removes all drawables from a graph.
|
||||||
class CleanObjectRootVisitor : public RemoveVisitor
|
class CleanObjectRootVisitor : public RemoveVisitor
|
||||||
{
|
{
|
||||||
|
@ -287,7 +392,6 @@ namespace
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace MWRender
|
namespace MWRender
|
||||||
|
@ -432,6 +536,42 @@ namespace MWRender
|
||||||
const std::multimap<float, std::string>& getTextKeys() const;
|
const std::multimap<float, std::string>& getTextKeys() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void UpdateVfxCallback::operator()(osg::Node* node, osg::NodeVisitor* nv)
|
||||||
|
{
|
||||||
|
traverse(node, nv);
|
||||||
|
|
||||||
|
if (mFinished)
|
||||||
|
return;
|
||||||
|
|
||||||
|
double newTime = nv->getFrameStamp()->getSimulationTime();
|
||||||
|
if (mStartingTime == 0)
|
||||||
|
{
|
||||||
|
mStartingTime = newTime;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
double duration = newTime - mStartingTime;
|
||||||
|
mStartingTime = newTime;
|
||||||
|
|
||||||
|
mParams.mAnimTime->addTime(duration);
|
||||||
|
if (mParams.mAnimTime->getTime() >= mParams.mMaxControllerLength)
|
||||||
|
{
|
||||||
|
if (mParams.mLoop)
|
||||||
|
{
|
||||||
|
// Start from the beginning again; carry over the remainder
|
||||||
|
// Not sure if this is actually needed, the controller function might already handle loops
|
||||||
|
float remainder = mParams.mAnimTime->getTime() - mParams.mMaxControllerLength;
|
||||||
|
mParams.mAnimTime->resetTime(remainder);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Remove effect immediately
|
||||||
|
mParams.mObjects.reset();
|
||||||
|
mFinished = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class ResetAccumRootCallback : public osg::NodeCallback
|
class ResetAccumRootCallback : public osg::NodeCallback
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -1436,15 +1576,22 @@ namespace MWRender
|
||||||
useQuadratic, quadraticValue, quadraticRadiusMult, useLinear, linearRadiusMult, linearValue);
|
useQuadratic, quadraticValue, quadraticRadiusMult, useLinear, linearRadiusMult, linearValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Animation::addEffect (const std::string& model, int effectId, bool loop, const std::string& bonename, const std::string& texture)
|
void Animation::addEffect (const std::string& model, int effectId, bool loop, const std::string& bonename, const std::string& texture, float scale)
|
||||||
{
|
{
|
||||||
if (!mObjectRoot.get())
|
if (!mObjectRoot.get())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Early out if we already have this effect
|
// Early out if we already have this effect
|
||||||
for (std::vector<EffectParams>::iterator it = mEffects.begin(); it != mEffects.end(); ++it)
|
FindVfxCallbacksVisitor visitor(effectId);
|
||||||
if (it->mLoop && loop && it->mEffectId == effectId && it->mBoneName == bonename)
|
mInsert->accept(visitor);
|
||||||
|
|
||||||
|
for (std::vector<UpdateVfxCallback*>::iterator it = visitor.mCallbacks.begin(); it != visitor.mCallbacks.end(); ++it)
|
||||||
|
{
|
||||||
|
UpdateVfxCallback* callback = *it;
|
||||||
|
|
||||||
|
if (loop && !callback->mFinished && callback->mParams.mLoop && callback->mParams.mBoneName == bonename)
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
EffectParams params;
|
EffectParams params;
|
||||||
params.mModelName = model;
|
params.mModelName = model;
|
||||||
|
@ -1459,83 +1606,64 @@ namespace MWRender
|
||||||
|
|
||||||
parentNode = found->second;
|
parentNode = found->second;
|
||||||
}
|
}
|
||||||
osg::ref_ptr<osg::Node> node = mResourceSystem->getSceneManager()->getInstance(model, parentNode);
|
|
||||||
|
|
||||||
|
osg::ref_ptr<osg::PositionAttitudeTransform> trans = new osg::PositionAttitudeTransform;
|
||||||
|
trans->setScale(osg::Vec3f(scale, scale, scale));
|
||||||
|
parentNode->addChild(trans);
|
||||||
|
|
||||||
|
osg::ref_ptr<osg::Node> node = mResourceSystem->getSceneManager()->getInstance(model, trans);
|
||||||
node->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
|
node->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
|
||||||
|
|
||||||
params.mObjects = PartHolderPtr(new PartHolder(node));
|
|
||||||
|
|
||||||
SceneUtil::FindMaxControllerLengthVisitor findMaxLengthVisitor;
|
SceneUtil::FindMaxControllerLengthVisitor findMaxLengthVisitor;
|
||||||
node->accept(findMaxLengthVisitor);
|
node->accept(findMaxLengthVisitor);
|
||||||
|
|
||||||
// FreezeOnCull doesn't work so well with effect particles, that tend to have moving emitters
|
// FreezeOnCull doesn't work so well with effect particles, that tend to have moving emitters
|
||||||
SceneUtil::DisableFreezeOnCullVisitor disableFreezeOnCullVisitor;
|
SceneUtil::DisableFreezeOnCullVisitor disableFreezeOnCullVisitor;
|
||||||
node->accept(disableFreezeOnCullVisitor);
|
node->accept(disableFreezeOnCullVisitor);
|
||||||
|
|
||||||
params.mMaxControllerLength = findMaxLengthVisitor.getMaxLength();
|
|
||||||
|
|
||||||
node->setNodeMask(Mask_Effect);
|
node->setNodeMask(Mask_Effect);
|
||||||
|
|
||||||
|
params.mMaxControllerLength = findMaxLengthVisitor.getMaxLength();
|
||||||
params.mLoop = loop;
|
params.mLoop = loop;
|
||||||
params.mEffectId = effectId;
|
params.mEffectId = effectId;
|
||||||
params.mBoneName = bonename;
|
params.mBoneName = bonename;
|
||||||
|
params.mObjects = PartHolderPtr(new PartHolder(node));
|
||||||
params.mAnimTime = std::shared_ptr<EffectAnimationTime>(new EffectAnimationTime);
|
params.mAnimTime = std::shared_ptr<EffectAnimationTime>(new EffectAnimationTime);
|
||||||
|
trans->addUpdateCallback(new UpdateVfxCallback(params));
|
||||||
|
|
||||||
SceneUtil::AssignControllerSourcesVisitor assignVisitor(std::shared_ptr<SceneUtil::ControllerSource>(params.mAnimTime));
|
SceneUtil::AssignControllerSourcesVisitor assignVisitor(std::shared_ptr<SceneUtil::ControllerSource>(params.mAnimTime));
|
||||||
node->accept(assignVisitor);
|
node->accept(assignVisitor);
|
||||||
|
|
||||||
overrideFirstRootTexture(texture, mResourceSystem, node);
|
overrideFirstRootTexture(texture, mResourceSystem, node);
|
||||||
|
|
||||||
// TODO: in vanilla morrowind the effect is scaled based on the host object's bounding box.
|
|
||||||
|
|
||||||
mEffects.push_back(params);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Animation::removeEffect(int effectId)
|
void Animation::removeEffect(int effectId)
|
||||||
{
|
{
|
||||||
for (std::vector<EffectParams>::iterator it = mEffects.begin(); it != mEffects.end(); ++it)
|
RemoveFinishedCallbackVisitor visitor(effectId);
|
||||||
{
|
mInsert->accept(visitor);
|
||||||
if (it->mEffectId == effectId)
|
visitor.remove();
|
||||||
{
|
|
||||||
mEffects.erase(it);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Animation::getLoopingEffects(std::vector<int> &out) const
|
void Animation::getLoopingEffects(std::vector<int> &out) const
|
||||||
{
|
{
|
||||||
for (std::vector<EffectParams>::const_iterator it = mEffects.begin(); it != mEffects.end(); ++it)
|
FindVfxCallbacksVisitor visitor;
|
||||||
|
mInsert->accept(visitor);
|
||||||
|
|
||||||
|
for (std::vector<UpdateVfxCallback*>::iterator it = visitor.mCallbacks.begin(); it != visitor.mCallbacks.end(); ++it)
|
||||||
{
|
{
|
||||||
if (it->mLoop)
|
UpdateVfxCallback* callback = *it;
|
||||||
out.push_back(it->mEffectId);
|
|
||||||
|
if (callback->mParams.mLoop && !callback->mFinished)
|
||||||
|
out.push_back(callback->mParams.mEffectId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Animation::updateEffects(float duration)
|
void Animation::updateEffects(float duration)
|
||||||
{
|
{
|
||||||
for (std::vector<EffectParams>::iterator it = mEffects.begin(); it != mEffects.end(); )
|
// TODO: objects without animation still will have
|
||||||
{
|
// transformation nodes with finished callbacks
|
||||||
it->mAnimTime->addTime(duration);
|
RemoveFinishedCallbackVisitor visitor;
|
||||||
|
mInsert->accept(visitor);
|
||||||
if (it->mAnimTime->getTime() >= it->mMaxControllerLength)
|
visitor.remove();
|
||||||
{
|
|
||||||
if (it->mLoop)
|
|
||||||
{
|
|
||||||
// Start from the beginning again; carry over the remainder
|
|
||||||
// Not sure if this is actually needed, the controller function might already handle loops
|
|
||||||
float remainder = it->mAnimTime->getTime() - it->mMaxControllerLength;
|
|
||||||
it->mAnimTime->resetTime(remainder);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
it = mEffects.erase(it);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
++it;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Animation::upperBodyReady() const
|
bool Animation::upperBodyReady() const
|
||||||
|
@ -1778,5 +1906,4 @@ namespace MWRender
|
||||||
mNode->getParent(0)->removeChild(mNode);
|
mNode->getParent(0)->removeChild(mNode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,6 +71,17 @@ private:
|
||||||
};
|
};
|
||||||
typedef std::shared_ptr<PartHolder> PartHolderPtr;
|
typedef std::shared_ptr<PartHolder> PartHolderPtr;
|
||||||
|
|
||||||
|
struct EffectParams
|
||||||
|
{
|
||||||
|
std::string mModelName; // Just here so we don't add the same effect twice
|
||||||
|
PartHolderPtr mObjects;
|
||||||
|
std::shared_ptr<EffectAnimationTime> mAnimTime;
|
||||||
|
float mMaxControllerLength;
|
||||||
|
int mEffectId;
|
||||||
|
bool mLoop;
|
||||||
|
std::string mBoneName;
|
||||||
|
};
|
||||||
|
|
||||||
class Animation : public osg::Referenced
|
class Animation : public osg::Referenced
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -247,19 +258,6 @@ protected:
|
||||||
|
|
||||||
osg::Vec3f mAccumulate;
|
osg::Vec3f mAccumulate;
|
||||||
|
|
||||||
struct EffectParams
|
|
||||||
{
|
|
||||||
std::string mModelName; // Just here so we don't add the same effect twice
|
|
||||||
PartHolderPtr mObjects;
|
|
||||||
std::shared_ptr<EffectAnimationTime> mAnimTime;
|
|
||||||
float mMaxControllerLength;
|
|
||||||
int mEffectId;
|
|
||||||
bool mLoop;
|
|
||||||
std::string mBoneName;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::vector<EffectParams> mEffects;
|
|
||||||
|
|
||||||
TextKeyListener* mTextKeyListener;
|
TextKeyListener* mTextKeyListener;
|
||||||
|
|
||||||
osg::ref_ptr<RotateController> mHeadController;
|
osg::ref_ptr<RotateController> mHeadController;
|
||||||
|
@ -369,7 +367,7 @@ public:
|
||||||
* @param texture override the texture specified in the model's materials - if empty, do not override
|
* @param texture override the texture specified in the model's materials - if empty, do not override
|
||||||
* @note Will not add an effect twice.
|
* @note Will not add an effect twice.
|
||||||
*/
|
*/
|
||||||
void addEffect (const std::string& model, int effectId, bool loop = false, const std::string& bonename = "", const std::string& texture = "");
|
void addEffect (const std::string& model, int effectId, bool loop = false, const std::string& bonename = "", const std::string& texture = "", float scale = 1.0f);
|
||||||
void removeEffect (int effectId);
|
void removeEffect (int effectId);
|
||||||
void getLoopingEffects (std::vector<int>& out) const;
|
void getLoopingEffects (std::vector<int>& out) const;
|
||||||
|
|
||||||
|
@ -489,5 +487,24 @@ public:
|
||||||
ObjectAnimation(const MWWorld::Ptr& ptr, const std::string &model, Resource::ResourceSystem* resourceSystem, bool animated, bool allowLight);
|
ObjectAnimation(const MWWorld::Ptr& ptr, const std::string &model, Resource::ResourceSystem* resourceSystem, bool animated, bool allowLight);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class UpdateVfxCallback : public osg::NodeCallback
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
UpdateVfxCallback(EffectParams& params)
|
||||||
|
: mFinished(false)
|
||||||
|
, mParams(params)
|
||||||
|
, mStartingTime(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool mFinished;
|
||||||
|
EffectParams mParams;
|
||||||
|
|
||||||
|
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv);
|
||||||
|
|
||||||
|
private:
|
||||||
|
double mStartingTime;
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -50,6 +50,7 @@
|
||||||
#include <boost/algorithm/string.hpp>
|
#include <boost/algorithm/string.hpp>
|
||||||
|
|
||||||
#include "../mwworld/cellstore.hpp"
|
#include "../mwworld/cellstore.hpp"
|
||||||
|
#include "../mwworld/class.hpp"
|
||||||
#include "../mwgui/loadingscreen.hpp"
|
#include "../mwgui/loadingscreen.hpp"
|
||||||
#include "../mwbase/environment.hpp"
|
#include "../mwbase/environment.hpp"
|
||||||
#include "../mwbase/windowmanager.hpp"
|
#include "../mwbase/windowmanager.hpp"
|
||||||
|
@ -1347,6 +1348,29 @@ namespace MWRender
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
osg::Vec3f RenderingManager::getHalfExtents(const MWWorld::ConstPtr& object) const
|
||||||
|
{
|
||||||
|
osg::Vec3f halfExtents(0, 0, 0);
|
||||||
|
std::string modelName = object.getClass().getModel(object);
|
||||||
|
if (modelName.empty())
|
||||||
|
return halfExtents;
|
||||||
|
|
||||||
|
osg::ref_ptr<const osg::Node> node = mResourceSystem->getSceneManager()->getTemplate(modelName);
|
||||||
|
osg::ComputeBoundsVisitor computeBoundsVisitor;
|
||||||
|
computeBoundsVisitor.setTraversalMask(~(MWRender::Mask_ParticleSystem|MWRender::Mask_Effect));
|
||||||
|
const_cast<osg::Node*>(node.get())->accept(computeBoundsVisitor);
|
||||||
|
osg::BoundingBox bounds = computeBoundsVisitor.getBoundingBox();
|
||||||
|
|
||||||
|
if (bounds.valid())
|
||||||
|
{
|
||||||
|
halfExtents[0] = std::abs(bounds.xMax() - bounds.xMin()) / 2.f;
|
||||||
|
halfExtents[1] = std::abs(bounds.yMax() - bounds.yMin()) / 2.f;
|
||||||
|
halfExtents[2] = std::abs(bounds.zMax() - bounds.zMin()) / 2.f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return halfExtents;
|
||||||
|
}
|
||||||
|
|
||||||
void RenderingManager::resetFieldOfView()
|
void RenderingManager::resetFieldOfView()
|
||||||
{
|
{
|
||||||
if (mFieldOfViewOverridden == true)
|
if (mFieldOfViewOverridden == true)
|
||||||
|
|
|
@ -204,6 +204,8 @@ namespace MWRender
|
||||||
/// reset a previous overrideFieldOfView() call, i.e. revert to field of view specified in the settings file.
|
/// reset a previous overrideFieldOfView() call, i.e. revert to field of view specified in the settings file.
|
||||||
void resetFieldOfView();
|
void resetFieldOfView();
|
||||||
|
|
||||||
|
osg::Vec3f getHalfExtents(const MWWorld::ConstPtr& object) const;
|
||||||
|
|
||||||
void exportSceneGraph(const MWWorld::Ptr& ptr, const std::string& filename, const std::string& format);
|
void exportSceneGraph(const MWWorld::Ptr& ptr, const std::string& filename, const std::string& format);
|
||||||
|
|
||||||
LandManager* getLandManager() const;
|
LandManager* getLandManager() const;
|
||||||
|
|
|
@ -1082,6 +1082,7 @@ namespace MWScript
|
||||||
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, false, true);
|
MWMechanics::CastSpell cast(ptr, target, false, true);
|
||||||
|
cast.playSpellCastingEffects(spell->mId);
|
||||||
cast.mHitPosition = target.getRefData().getPosition().asVec3();
|
cast.mHitPosition = target.getRefData().getPosition().asVec3();
|
||||||
cast.mAlwaysSucceed = true;
|
cast.mAlwaysSucceed = true;
|
||||||
cast.cast(spell);
|
cast.cast(spell);
|
||||||
|
|
|
@ -3339,12 +3339,16 @@ namespace MWWorld
|
||||||
return mRendering->getTerrainHeightAt(worldPos);
|
return mRendering->getTerrainHeightAt(worldPos);
|
||||||
}
|
}
|
||||||
|
|
||||||
osg::Vec3f World::getHalfExtents(const ConstPtr& actor, bool rendering) const
|
osg::Vec3f World::getHalfExtents(const ConstPtr& object, bool rendering) const
|
||||||
{
|
{
|
||||||
|
if (!object.getClass().isActor())
|
||||||
|
return mRendering->getHalfExtents(object);
|
||||||
|
|
||||||
|
// Handle actors separately because of bodyparts
|
||||||
if (rendering)
|
if (rendering)
|
||||||
return mPhysics->getRenderingHalfExtents(actor);
|
return mPhysics->getRenderingHalfExtents(object);
|
||||||
else
|
else
|
||||||
return mPhysics->getHalfExtents(actor);
|
return mPhysics->getHalfExtents(object);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string World::exportSceneGraph(const Ptr &ptr)
|
std::string World::exportSceneGraph(const Ptr &ptr)
|
||||||
|
@ -3403,9 +3407,9 @@ namespace MWWorld
|
||||||
mRendering->spawnEffect(model, texture, worldPosition, 1.0f, false);
|
mRendering->spawnEffect(model, texture, worldPosition, 1.0f, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void World::spawnEffect(const std::string &model, const std::string &textureOverride, const osg::Vec3f &worldPos)
|
void World::spawnEffect(const std::string &model, const std::string &textureOverride, const osg::Vec3f &worldPos, float scale, bool isMagicVFX)
|
||||||
{
|
{
|
||||||
mRendering->spawnEffect(model, textureOverride, worldPos);
|
mRendering->spawnEffect(model, textureOverride, worldPos, scale, isMagicVFX);
|
||||||
}
|
}
|
||||||
|
|
||||||
void World::explodeSpell(const osg::Vec3f& origin, const ESM::EffectList& effects, const Ptr& caster, const Ptr& ignore, ESM::RangeType rangeType,
|
void World::explodeSpell(const osg::Vec3f& origin, const ESM::EffectList& effects, const Ptr& caster, const Ptr& ignore, ESM::RangeType rangeType,
|
||||||
|
|
|
@ -646,7 +646,7 @@ namespace MWWorld
|
||||||
/// Spawn a blood effect for \a ptr at \a worldPosition
|
/// Spawn a blood effect for \a ptr at \a worldPosition
|
||||||
void spawnBloodEffect (const MWWorld::Ptr& ptr, const osg::Vec3f& worldPosition) override;
|
void spawnBloodEffect (const MWWorld::Ptr& ptr, const osg::Vec3f& worldPosition) override;
|
||||||
|
|
||||||
void spawnEffect (const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPos) override;
|
void spawnEffect (const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPos, float scale = 1.f, bool isMagicVFX = true) override;
|
||||||
|
|
||||||
void explodeSpell(const osg::Vec3f& origin, const ESM::EffectList& effects, const MWWorld::Ptr& caster, const MWWorld::Ptr& ignore,
|
void explodeSpell(const osg::Vec3f& origin, const ESM::EffectList& effects, const MWWorld::Ptr& caster, const MWWorld::Ptr& ignore,
|
||||||
ESM::RangeType rangeType, const std::string& id, const std::string& sourceName,
|
ESM::RangeType rangeType, const std::string& id, const std::string& sourceName,
|
||||||
|
|
|
@ -557,7 +557,7 @@ namespace Compiler
|
||||||
mExplicit.clear();
|
mExplicit.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GetArgumentsFromMessageFormat::visitedPlaceholder(Placeholder placeholder, char /*padding*/, int /*width*/, int /*precision*/)
|
void GetArgumentsFromMessageFormat::visitedPlaceholder(Placeholder placeholder, char /*padding*/, int /*width*/, int /*precision*/, Notation /*notation*/)
|
||||||
{
|
{
|
||||||
switch (placeholder)
|
switch (placeholder)
|
||||||
{
|
{
|
||||||
|
|
|
@ -83,7 +83,7 @@ namespace Compiler
|
||||||
std::string mArguments;
|
std::string mArguments;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void visitedPlaceholder(Placeholder placeholder, char padding, int width, int precision);
|
virtual void visitedPlaceholder(Placeholder placeholder, char padding, int width, int precision, Notation notation);
|
||||||
virtual void visitedCharacter(char c) {}
|
virtual void visitedCharacter(char c) {}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -24,7 +24,7 @@ namespace Interpreter
|
||||||
Runtime& mRuntime;
|
Runtime& mRuntime;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void visitedPlaceholder(Placeholder placeholder, char padding, int width, int precision)
|
virtual void visitedPlaceholder(Placeholder placeholder, char padding, int width, int precision, Notation notation)
|
||||||
{
|
{
|
||||||
std::ostringstream out;
|
std::ostringstream out;
|
||||||
out.fill(padding);
|
out.fill(padding);
|
||||||
|
@ -58,9 +58,35 @@ namespace Interpreter
|
||||||
float value = mRuntime[0].mFloat;
|
float value = mRuntime[0].mFloat;
|
||||||
mRuntime.pop();
|
mRuntime.pop();
|
||||||
|
|
||||||
|
if (notation == FixedNotation)
|
||||||
|
{
|
||||||
out << std::fixed << value;
|
out << std::fixed << value;
|
||||||
mFormattedMessage += out.str();
|
mFormattedMessage += out.str();
|
||||||
}
|
}
|
||||||
|
else if (notation == ShortestNotation)
|
||||||
|
{
|
||||||
|
std::string scientific;
|
||||||
|
std::string fixed;
|
||||||
|
|
||||||
|
out << std::scientific << value;
|
||||||
|
|
||||||
|
scientific = out.str();
|
||||||
|
|
||||||
|
out.str(std::string());
|
||||||
|
out.clear();
|
||||||
|
|
||||||
|
out << std::fixed << value;
|
||||||
|
|
||||||
|
fixed = out.str();
|
||||||
|
|
||||||
|
mFormattedMessage += fixed.length() < scientific.length() ? fixed : scientific;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
out << std::scientific << value;
|
||||||
|
mFormattedMessage += out.str();
|
||||||
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -49,11 +49,15 @@ namespace Misc
|
||||||
width = (widthSet) ? width : -1;
|
width = (widthSet) ? width : -1;
|
||||||
|
|
||||||
if (m[i] == 'S' || m[i] == 's')
|
if (m[i] == 'S' || m[i] == 's')
|
||||||
visitedPlaceholder(StringPlaceholder, pad, width, precision);
|
visitedPlaceholder(StringPlaceholder, pad, width, precision, FixedNotation);
|
||||||
else if (m[i] == 'g' || m[i] == 'G')
|
else if (m[i] == 'd' || m[i] == 'i')
|
||||||
visitedPlaceholder(IntegerPlaceholder, pad, width, precision);
|
visitedPlaceholder(IntegerPlaceholder, pad, width, precision, FixedNotation);
|
||||||
else if (m[i] == 'f' || m[i] == 'F')
|
else if (m[i] == 'f' || m[i] == 'F')
|
||||||
visitedPlaceholder(FloatPlaceholder, pad, width, precision);
|
visitedPlaceholder(FloatPlaceholder, pad, width, precision, FixedNotation);
|
||||||
|
else if (m[i] == 'e' || m[i] == 'E')
|
||||||
|
visitedPlaceholder(FloatPlaceholder, pad, width, precision, ScientificNotation);
|
||||||
|
else if (m[i] == 'g' || m[i] == 'G')
|
||||||
|
visitedPlaceholder(FloatPlaceholder, pad, width, precision, ShortestNotation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,14 @@ namespace Misc
|
||||||
FloatPlaceholder
|
FloatPlaceholder
|
||||||
};
|
};
|
||||||
|
|
||||||
virtual void visitedPlaceholder(Placeholder placeholder, char padding, int width, int precision) = 0;
|
enum Notation
|
||||||
|
{
|
||||||
|
FixedNotation,
|
||||||
|
ScientificNotation,
|
||||||
|
ShortestNotation
|
||||||
|
};
|
||||||
|
|
||||||
|
virtual void visitedPlaceholder(Placeholder placeholder, char padding, int width, int precision, Notation notation) = 0;
|
||||||
virtual void visitedCharacter(char c) = 0;
|
virtual void visitedCharacter(char c) = 0;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -9,7 +9,7 @@ few chapters to familiarise yourself with the new interface.
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
OpenMW CS is still software in development. The manual does not cover any of
|
OpenMW CS is still software in development. The manual does not cover any of
|
||||||
its shortcomings, it is written as if everything was working as inteded.
|
its shortcomings, it is written as if everything was working as intended.
|
||||||
Please report any software problems as bugs in the software, not errors in
|
Please report any software problems as bugs in the software, not errors in
|
||||||
the manual.
|
the manual.
|
||||||
|
|
||||||
|
|
|
@ -121,7 +121,7 @@ existing filters into more complex ones.
|
||||||
Scopes
|
Scopes
|
||||||
======
|
======
|
||||||
|
|
||||||
Every default filter has the prefix ``project``. This is a *scpoe*, a mechanism
|
Every default filter has the prefix ``project``. This is a *scope*, a mechanism
|
||||||
that determines the lifetime of the filter. These are the supported scopes:
|
that determines the lifetime of the filter. These are the supported scopes:
|
||||||
|
|
||||||
``project::``
|
``project::``
|
||||||
|
|
|
@ -236,7 +236,7 @@ a negative number indicating that he will restock again to maintain that level.
|
||||||
However, it's an attractive item, so he will probably wear it rather than sell it.
|
However, it's an attractive item, so he will probably wear it rather than sell it.
|
||||||
So set his stock level too high for him to wear them all (3 works, 2 might do).
|
So set his stock level too high for him to wear them all (3 works, 2 might do).
|
||||||
|
|
||||||
Another possibilty, again in Seyda Neen making it easy to access, would be for
|
Another possibility, again in Seyda Neen making it easy to access, would be for
|
||||||
Fargoth to give it to the player in exchange for his healing ring.
|
Fargoth to give it to the player in exchange for his healing ring.
|
||||||
|
|
||||||
.. figure:: https://gitlab.com/OpenMW/openmw-docs/raw/master/docs/source/manuals/openmw-cs/_static/images/chapter-1/Ring_to_Fargoth_1.png
|
.. figure:: https://gitlab.com/OpenMW/openmw-docs/raw/master/docs/source/manuals/openmw-cs/_static/images/chapter-1/Ring_to_Fargoth_1.png
|
||||||
|
@ -360,7 +360,7 @@ the base game.
|
||||||
"Modified" status will cover items from the base game which have been modified in this addon.
|
"Modified" status will cover items from the base game which have been modified in this addon.
|
||||||
|
|
||||||
Click on the top of the column to toggle between ascending and descending order - thus between "Added"
|
Click on the top of the column to toggle between ascending and descending order - thus between "Added"
|
||||||
and "Modified" at the top. Or put your desired modified status into a filter then sort alpabetically
|
and "Modified" at the top. Or put your desired modified status into a filter then sort alphabetically
|
||||||
on a different column.
|
on a different column.
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue