mirror of
https://github.com/OpenMW/openmw.git
synced 2025-02-21 07:09:42 +00:00
Merge branch 'master' into combat_anims
This commit is contained in:
commit
3c827da702
28 changed files with 519 additions and 184 deletions
|
@ -173,6 +173,7 @@ Programmers
|
|||
vocollapse
|
||||
Yohaulticetl
|
||||
zelurker
|
||||
James Carty (MrTopCat)
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
@ -181,9 +182,11 @@ Documentation
|
|||
Alejandro Sanchez (HiPhish)
|
||||
Bodillium
|
||||
Bret Curtis (psi29a)
|
||||
David Walley (Loriel)
|
||||
Cramal
|
||||
Ryan Tucker (Ravenwing)
|
||||
sir_herrbatka
|
||||
Diego Crespo
|
||||
|
||||
Packagers
|
||||
---------
|
||||
|
|
|
@ -15,12 +15,14 @@
|
|||
Bug #2872: Tab completion in console doesn't work with explicit reference
|
||||
Bug #2971: Compiler did not reject lines with naked expressions beginning with x.y
|
||||
Bug #3049: Drain and Fortify effects are not properly applied on health, magicka and fatigue
|
||||
Bug #3072: Fatal error on AddItem <item> that has a script containing Equip <item>
|
||||
Bug #3249: Fixed revert function not updating views properly
|
||||
Bug #3374: Touch spells not hitting kwama foragers
|
||||
Bug #3486: [Mod] NPC Commands does not work
|
||||
Bug #3533: GetSpellEffects should detect effects with zero duration
|
||||
Bug #3591: Angled hit distance too low
|
||||
Bug #3629: DB assassin attack never triggers creature spawning
|
||||
Bug #3788: GetPCInJail and GetPCTraveling do not work as in vanilla
|
||||
Bug #3876: Landscape texture painting is misaligned
|
||||
Bug #3897: Have Goodbye give all choices the effects of Goodbye
|
||||
Bug #3911: [macOS] Typing in the "Content List name" dialog box produces double characters
|
||||
|
@ -42,6 +44,7 @@
|
|||
Bug #4286: Scripted animations can be interrupted
|
||||
Bug #4291: Non-persistent actors that started the game as dead do not play death animations
|
||||
Bug #4293: Faction members are not aware of faction ownerships in barter
|
||||
Bug #4304: "Follow" not working as a second AI package
|
||||
Bug #4307: World cleanup should remove dead bodies only if death animation is finished
|
||||
Bug #4311: OpenMW does not handle RootCollisionNode correctly
|
||||
Bug #4327: Missing animations during spell/weapon stance switching
|
||||
|
@ -95,6 +98,7 @@
|
|||
Bug #4575: Weird result of attack animation blending with movement animations
|
||||
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 #3083: Play animation when NPC is casting spell via script
|
||||
Feature #3103: Provide option for disposition to get increased by successful trade
|
||||
|
|
|
@ -536,7 +536,7 @@ namespace MWBase
|
|||
/// Spawn a blood effect for \a ptr at \a worldPosition
|
||||
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,
|
||||
const MWWorld::Ptr& ignore, ESM::RangeType rangeType, const std::string& id,
|
||||
|
@ -566,6 +566,9 @@ namespace MWBase
|
|||
|
||||
virtual bool isPlayerInJail() const = 0;
|
||||
|
||||
virtual void setPlayerTraveling(bool traveling) = 0;
|
||||
virtual bool isPlayerTraveling() const = 0;
|
||||
|
||||
virtual void rotateWorldObject (const MWWorld::Ptr& ptr, osg::Quat rotate) = 0;
|
||||
|
||||
/// Return terrain height at \a worldPos position.
|
||||
|
|
|
@ -517,7 +517,7 @@ namespace MWGui
|
|||
|
||||
// Give the script a chance to run once before we do anything else
|
||||
// this is important when setting pcskipequip as a reaction to onpcequip being set (bk_treasuryreport does this)
|
||||
if (!script.empty() && MWBase::Environment::get().getWorld()->getScriptsEnabled())
|
||||
if (!force && !script.empty() && MWBase::Environment::get().getWorld()->getScriptsEnabled())
|
||||
{
|
||||
MWScript::InterpreterContext interpreterContext (&ptr.getRefData().getLocals(), ptr);
|
||||
MWBase::Environment::get().getScriptManager()->run (script, interpreterContext);
|
||||
|
|
|
@ -154,6 +154,10 @@ namespace MWGui
|
|||
if (playerGold<price)
|
||||
return;
|
||||
|
||||
// Set "traveling" flag, so GetPCTraveling can detect teleportation.
|
||||
// We will reset this flag during next world update.
|
||||
MWBase::Environment::get().getWorld()->setPlayerTraveling(true);
|
||||
|
||||
if (!mPtr.getCell()->isExterior())
|
||||
// Interior cell -> mages guild transport
|
||||
MWBase::Environment::get().getWindowManager()->playSound("mysticism cast");
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
#include <typeinfo>
|
||||
#include <iostream>
|
||||
|
||||
#include <components/esm/esmreader.hpp>
|
||||
#include <components/esm/esmwriter.hpp>
|
||||
#include <components/esm/loadnpc.hpp>
|
||||
|
@ -404,7 +403,7 @@ namespace MWMechanics
|
|||
std::set<MWWorld::Ptr> playerAllies;
|
||||
getActorsSidingWith(MWMechanics::getPlayer(), playerAllies, cachedAllies);
|
||||
|
||||
bool isPlayerFollowerOrEscorter = std::find(playerAllies.begin(), playerAllies.end(), actor1) != playerAllies.end();
|
||||
bool isPlayerFollowerOrEscorter = playerAllies.find(actor1) != playerAllies.end();
|
||||
|
||||
// If actor2 and at least one actor2 are in combat with actor1, actor1 and its allies start combat with them
|
||||
// Doesn't apply for player followers/escorters
|
||||
|
@ -458,7 +457,7 @@ namespace MWMechanics
|
|||
// Do aggression check if actor2 is the player or a player follower or escorter
|
||||
if (!aggressive)
|
||||
{
|
||||
if (againstPlayer || std::find(playerAllies.begin(), playerAllies.end(), actor2) != playerAllies.end())
|
||||
if (againstPlayer || playerAllies.find(actor2) != playerAllies.end())
|
||||
{
|
||||
// Player followers and escorters with high fight should not initiate combat with the player or with
|
||||
// other player followers or escorters
|
||||
|
@ -1777,38 +1776,35 @@ namespace MWMechanics
|
|||
std::list<MWWorld::Ptr> Actors::getActorsSidingWith(const MWWorld::Ptr& actor)
|
||||
{
|
||||
std::list<MWWorld::Ptr> list;
|
||||
for(PtrActorMap::iterator iter(mActors.begin());iter != mActors.end();++iter)
|
||||
for(PtrActorMap::iterator iter = mActors.begin(); iter != mActors.end(); ++iter)
|
||||
{
|
||||
const MWWorld::Class &cls = iter->first.getClass();
|
||||
const CreatureStats &stats = cls.getCreatureStats(iter->first);
|
||||
const MWWorld::Ptr &iteratedActor = iter->first;
|
||||
if (iteratedActor == getPlayer())
|
||||
continue;
|
||||
|
||||
const CreatureStats &stats = iteratedActor.getClass().getCreatureStats(iteratedActor);
|
||||
if (stats.isDead())
|
||||
continue;
|
||||
|
||||
// An actor counts as siding with this actor if Follow or Escort is the current AI package, or there are only Combat packages before the Follow/Escort package
|
||||
for (std::list<MWMechanics::AiPackage*>::const_iterator it = stats.getAiSequence().begin(); it != stats.getAiSequence().end(); ++it)
|
||||
{
|
||||
if ((*it)->sideWithTarget() && (*it)->getTarget() == actor)
|
||||
{
|
||||
list.push_back(iter->first);
|
||||
break;
|
||||
}
|
||||
else if ((*it)->getTypeId() != MWMechanics::AiPackage::TypeIdCombat)
|
||||
break;
|
||||
}
|
||||
// An actor counts as siding with this actor if Follow or Escort is the current AI package, or there are only Combat and Wander packages before the Follow/Escort package
|
||||
// Actors that are targeted by this actor's Follow or Escort packages also side with them
|
||||
if (actor != getPlayer())
|
||||
for (auto package = stats.getAiSequence().begin(); package != stats.getAiSequence().end(); ++package)
|
||||
{
|
||||
const CreatureStats &stats2 = actor.getClass().getCreatureStats(actor);
|
||||
for (std::list<MWMechanics::AiPackage*>::const_iterator it2 = stats2.getAiSequence().begin(); it2 != stats2.getAiSequence().end(); ++it2)
|
||||
const MWWorld::Ptr &target = (*package)->getTarget();
|
||||
if ((*package)->sideWithTarget() && !target.isEmpty())
|
||||
{
|
||||
if ((*it2)->sideWithTarget() && !(*it2)->getTarget().isEmpty())
|
||||
if (iteratedActor == actor)
|
||||
{
|
||||
list.push_back((*it2)->getTarget());
|
||||
break;
|
||||
list.push_back(target);
|
||||
}
|
||||
else if ((*it2)->getTypeId() != MWMechanics::AiPackage::TypeIdCombat)
|
||||
break;
|
||||
else if (target == actor)
|
||||
{
|
||||
list.push_back(iteratedActor);
|
||||
}
|
||||
break;
|
||||
}
|
||||
else if ((*package)->getTypeId() != AiPackage::TypeIdCombat && (*package)->getTypeId() != AiPackage::TypeIdWander)
|
||||
break;
|
||||
}
|
||||
}
|
||||
return list;
|
||||
|
@ -1819,17 +1815,21 @@ namespace MWMechanics
|
|||
std::list<MWWorld::Ptr> list;
|
||||
for(PtrActorMap::iterator iter(mActors.begin());iter != mActors.end();++iter)
|
||||
{
|
||||
const MWWorld::Class &cls = iter->first.getClass();
|
||||
CreatureStats &stats = cls.getCreatureStats(iter->first);
|
||||
const MWWorld::Ptr &iteratedActor = iter->first;
|
||||
if (iteratedActor == getPlayer())
|
||||
continue;
|
||||
|
||||
const CreatureStats &stats = iteratedActor.getClass().getCreatureStats(iteratedActor);
|
||||
if (stats.isDead())
|
||||
continue;
|
||||
|
||||
// An actor counts as following if AiFollow is the current AiPackage, or there are only Combat packages before the AiFollow package
|
||||
for (std::list<MWMechanics::AiPackage*>::const_iterator it = stats.getAiSequence().begin(); it != stats.getAiSequence().end(); ++it)
|
||||
// An actor counts as following if AiFollow is the current AiPackage,
|
||||
// or there are only Combat and Wander packages before the AiFollow package
|
||||
for (auto package = stats.getAiSequence().begin(); package != stats.getAiSequence().end(); ++package)
|
||||
{
|
||||
if ((*it)->followTargetThroughDoors() && (*it)->getTarget() == actor)
|
||||
list.push_back(iter->first);
|
||||
else if ((*it)->getTypeId() != MWMechanics::AiPackage::TypeIdCombat)
|
||||
if ((*package)->followTargetThroughDoors() && (*package)->getTarget() == actor)
|
||||
list.push_back(iteratedActor);
|
||||
else if ((*package)->getTypeId() != AiPackage::TypeIdCombat && (*package)->getTypeId() != AiPackage::TypeIdWander)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -1878,24 +1878,24 @@ namespace MWMechanics
|
|||
std::list<int> list;
|
||||
for(PtrActorMap::iterator iter(mActors.begin());iter != mActors.end();++iter)
|
||||
{
|
||||
const MWWorld::Class &cls = iter->first.getClass();
|
||||
CreatureStats &stats = cls.getCreatureStats(iter->first);
|
||||
const MWWorld::Ptr &iteratedActor = iter->first;
|
||||
if (iteratedActor == getPlayer())
|
||||
continue;
|
||||
|
||||
const CreatureStats &stats = iteratedActor.getClass().getCreatureStats(iteratedActor);
|
||||
if (stats.isDead())
|
||||
continue;
|
||||
|
||||
// An actor counts as following if AiFollow is the current AiPackage, or there are only Combat packages before the AiFollow package
|
||||
for (std::list<MWMechanics::AiPackage*>::const_iterator it = stats.getAiSequence().begin(); it != stats.getAiSequence().end(); ++it)
|
||||
// An actor counts as following if AiFollow is the current AiPackage,
|
||||
// or there are only Combat and Wander packages before the AiFollow package
|
||||
for (auto package = stats.getAiSequence().begin(); package != stats.getAiSequence().end(); ++package)
|
||||
{
|
||||
if ((*it)->getTypeId() == MWMechanics::AiPackage::TypeIdFollow)
|
||||
if ((*package)->followTargetThroughDoors() && (*package)->getTarget() == actor)
|
||||
{
|
||||
MWWorld::Ptr followTarget = (*it)->getTarget();
|
||||
if (followTarget.isEmpty())
|
||||
continue;
|
||||
if (followTarget == actor)
|
||||
list.push_back(static_cast<MWMechanics::AiFollow*>(*it)->getFollowIndex());
|
||||
list.push_back(static_cast<AiFollow*>(*package)->getFollowIndex());
|
||||
break;
|
||||
}
|
||||
else if ((*it)->getTypeId() != MWMechanics::AiPackage::TypeIdCombat)
|
||||
else if ((*package)->getTypeId() != AiPackage::TypeIdCombat && (*package)->getTypeId() != AiPackage::TypeIdWander)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -1907,14 +1907,14 @@ namespace MWMechanics
|
|||
std::vector<MWWorld::Ptr> neighbors;
|
||||
osg::Vec3f position (actor.getRefData().getPosition().asVec3());
|
||||
getObjectsInRange(position, aiProcessingDistance, neighbors);
|
||||
for(std::vector<MWWorld::Ptr>::const_iterator iter(neighbors.begin());iter != neighbors.end();++iter)
|
||||
for(auto neighbor = neighbors.begin(); neighbor != neighbors.end(); ++neighbor)
|
||||
{
|
||||
const MWWorld::Class &cls = iter->getClass();
|
||||
const CreatureStats &stats = cls.getCreatureStats(*iter);
|
||||
if (stats.isDead() || *iter == actor)
|
||||
const CreatureStats &stats = neighbor->getClass().getCreatureStats(*neighbor);
|
||||
if (stats.isDead() || *neighbor == actor)
|
||||
continue;
|
||||
|
||||
if (stats.getAiSequence().isInCombat(actor))
|
||||
list.push_front(*iter);
|
||||
list.push_front(*neighbor);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
@ -1926,15 +1926,18 @@ namespace MWMechanics
|
|||
osg::Vec3f position (actor.getRefData().getPosition().asVec3());
|
||||
getObjectsInRange(position, aiProcessingDistance, neighbors);
|
||||
|
||||
std::list<MWWorld::Ptr> followers = getActorsFollowing(actor);
|
||||
for(std::vector<MWWorld::Ptr>::const_iterator iter(neighbors.begin());iter != neighbors.end();++iter)
|
||||
std::set<MWWorld::Ptr> followers;
|
||||
getActorsFollowing(actor, followers);
|
||||
for (auto neighbor = neighbors.begin(); neighbor != neighbors.end(); ++neighbor)
|
||||
{
|
||||
const CreatureStats &stats = iter->getClass().getCreatureStats(*iter);
|
||||
if (stats.isDead() || *iter == actor || iter->getClass().isPureWaterCreature(*iter))
|
||||
const CreatureStats &stats = neighbor->getClass().getCreatureStats(*neighbor);
|
||||
if (stats.isDead() || *neighbor == actor || neighbor->getClass().isPureWaterCreature(*neighbor))
|
||||
continue;
|
||||
const bool isFollower = std::find(followers.begin(), followers.end(), *iter) != followers.end();
|
||||
if (stats.getAiSequence().isInCombat(actor) || (MWBase::Environment::get().getMechanicsManager()->isAggressive(*iter, actor) && !isFollower))
|
||||
list.push_back(*iter);
|
||||
|
||||
const bool isFollower = followers.find(*neighbor) != followers.end();
|
||||
|
||||
if (stats.getAiSequence().isInCombat(actor) || (MWBase::Environment::get().getMechanicsManager()->isAggressive(*neighbor, actor) && !isFollower))
|
||||
list.push_back(*neighbor);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
|
|
@ -42,7 +42,19 @@ bool MWMechanics::AiCast::execute(const MWWorld::Ptr& actor, MWMechanics::Charac
|
|||
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));
|
||||
turned &= smoothTurn(actor, getXAngleToDir(dir), 0, osg::DegreesToRadians(3.f));
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include "aicombataction.hpp"
|
||||
#include "aipursue.hpp"
|
||||
#include "actorutil.hpp"
|
||||
#include "../mwworld/class.hpp"
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
|
@ -122,6 +123,20 @@ bool AiSequence::isInCombat() const
|
|||
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
|
||||
{
|
||||
for (std::list<AiPackage*>::const_iterator it = mPackages.begin(); it != mPackages.end(); ++it)
|
||||
|
|
|
@ -88,6 +88,9 @@ namespace MWMechanics
|
|||
/// Is there any combat package?
|
||||
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?
|
||||
bool hasPackage(int typeId) const;
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include "mechanicsmanagerimp.hpp"
|
||||
|
||||
#include <limits.h>
|
||||
#include <set>
|
||||
|
||||
#include <components/misc/rng.hpp>
|
||||
|
||||
|
@ -1445,11 +1446,12 @@ namespace MWMechanics
|
|||
if (target == getPlayer() || !attacker.getClass().isActor())
|
||||
return false;
|
||||
|
||||
std::list<MWWorld::Ptr> followersAttacker = getActorsSidingWith(attacker);
|
||||
std::set<MWWorld::Ptr> followersAttacker;
|
||||
getActorsSidingWith(attacker, followersAttacker);
|
||||
|
||||
MWMechanics::CreatureStats& statsTarget = target.getClass().getCreatureStats(target);
|
||||
|
||||
if (std::find(followersAttacker.begin(), followersAttacker.end(), target) != followersAttacker.end())
|
||||
if (followersAttacker.find(target) != followersAttacker.end())
|
||||
{
|
||||
statsTarget.friendlyHit();
|
||||
|
||||
|
@ -1460,24 +1462,11 @@ namespace MWMechanics
|
|||
}
|
||||
}
|
||||
|
||||
// Attacking an NPC that is already in combat with any other NPC is not a crime
|
||||
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))
|
||||
if (canCommitCrimeAgainst(target, attacker))
|
||||
commitCrime(attacker, target, MWBase::MechanicsManager::OT_Assault);
|
||||
|
||||
AiSequence& seq = statsTarget.getAiSequence();
|
||||
|
||||
if (!attacker.isEmpty() && (attacker.getClass().getCreatureStats(attacker).getAiSequence().isInCombat(target)
|
||||
|| attacker == getPlayer())
|
||||
&& !seq.isInCombat(attacker))
|
||||
|
@ -1504,6 +1493,14 @@ namespace MWMechanics
|
|||
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)
|
||||
{
|
||||
if (attacker.isEmpty() || victim.isEmpty())
|
||||
|
@ -1516,11 +1513,10 @@ namespace MWMechanics
|
|||
return; // TODO: implement animal rights
|
||||
|
||||
const MWMechanics::NpcStats& victimStats = victim.getClass().getNpcStats(victim);
|
||||
if (victimStats.getCrimeId() == -1)
|
||||
return;
|
||||
const MWWorld::Ptr &player = getPlayer();
|
||||
bool canCommit = attacker == player && canCommitCrimeAgainst(victim, attacker);
|
||||
|
||||
// For now we report only about crimes of player and player's followers
|
||||
const MWWorld::Ptr &player = getPlayer();
|
||||
if (attacker != player)
|
||||
{
|
||||
std::set<MWWorld::Ptr> playerFollowers;
|
||||
|
@ -1529,6 +1525,9 @@ namespace MWMechanics
|
|||
return;
|
||||
}
|
||||
|
||||
if (!canCommit && victimStats.getCrimeId() == -1)
|
||||
return;
|
||||
|
||||
// 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,
|
||||
// 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
|
||||
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
|
||||
/// @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,
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include "../mwworld/inventorystore.hpp"
|
||||
|
||||
#include "../mwrender/animation.hpp"
|
||||
#include "../mwrender/vismask.hpp"
|
||||
|
||||
#include "npcstats.hpp"
|
||||
#include "actorutil.hpp"
|
||||
|
@ -327,16 +328,19 @@ namespace MWMechanics
|
|||
}
|
||||
|
||||
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
|
||||
// (e.g. when cast by a non-actor)
|
||||
if (!mTarget.isEmpty())
|
||||
fallbackDirection =
|
||||
osg::Vec3f(mTarget.getRefData().getPosition().asVec3())-
|
||||
osg::Vec3f(mCaster.getRefData().getPosition().asVec3());
|
||||
|
||||
(mTarget.getRefData().getPosition().asVec3() + offset) -
|
||||
(mCaster.getRefData().getPosition().asVec3());
|
||||
|
||||
MWBase::Environment::get().getWorld()->launchMagicBolt(mId, mCaster, fallbackDirection);
|
||||
}
|
||||
|
||||
|
@ -999,11 +1003,13 @@ namespace MWMechanics
|
|||
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 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();
|
||||
iter != spell->mEffects.mList.end(); ++iter)
|
||||
{
|
||||
|
@ -1012,18 +1018,56 @@ namespace MWMechanics
|
|||
|
||||
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;
|
||||
|
||||
if (!effect->mCasting.empty())
|
||||
castStatic = store.get<ESM::Static>().find (effect->mCasting);
|
||||
else
|
||||
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;
|
||||
|
||||
float scale = 1.0f;
|
||||
osg::Vec3f pos (mCaster.getRefData().getPosition().asVec3());
|
||||
|
||||
if (animation && mCaster.getClass().isNpc())
|
||||
{
|
||||
const ESM::Static* castStatic;
|
||||
// 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();
|
||||
|
||||
if (!effect->mCasting.empty())
|
||||
castStatic = store.get<ESM::Static>().find (effect->mCasting);
|
||||
else
|
||||
castStatic = store.get<ESM::Static>().find ("VFX_DefaultCast");
|
||||
const ESM::Race *race =
|
||||
esmStore.get<ESM::Race>().find(npc->mRace);
|
||||
|
||||
std::string texture = effect->mParticle;
|
||||
scale = npc->isMale() ? race->mData.mHeight.mMale : race->mData.mHeight.mFemale;
|
||||
}
|
||||
else
|
||||
{
|
||||
osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(mCaster);
|
||||
|
||||
animation->addEffect("meshes\\" + castStatic->mModel, effect->mIndex, false, "", texture);
|
||||
// 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())
|
||||
|
@ -1033,6 +1077,8 @@ namespace MWMechanics
|
|||
"alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration"
|
||||
};
|
||||
|
||||
addedEffects.push_back("meshes\\" + castStatic->mModel);
|
||||
|
||||
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
|
||||
if(!effect->mCastSound.empty())
|
||||
sndMgr->playSound3D(mCaster, effect->mCastSound, 1.0f, 1.0f);
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include <osg/MatrixTransform>
|
||||
#include <osg/BlendFunc>
|
||||
#include <osg/Material>
|
||||
#include <osg/PositionAttitudeTransform>
|
||||
|
||||
#include <osgParticle/ParticleSystem>
|
||||
#include <osgParticle/ParticleProcessor>
|
||||
|
@ -204,6 +205,110 @@ namespace
|
|||
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.
|
||||
class CleanObjectRootVisitor : public RemoveVisitor
|
||||
{
|
||||
|
@ -287,7 +392,6 @@ namespace
|
|||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace MWRender
|
||||
|
@ -432,6 +536,42 @@ namespace MWRender
|
|||
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
|
||||
{
|
||||
public:
|
||||
|
@ -1436,15 +1576,22 @@ namespace MWRender
|
|||
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())
|
||||
return;
|
||||
|
||||
// Early out if we already have this effect
|
||||
for (std::vector<EffectParams>::iterator it = mEffects.begin(); it != mEffects.end(); ++it)
|
||||
if (it->mLoop && loop && it->mEffectId == effectId && it->mBoneName == bonename)
|
||||
FindVfxCallbacksVisitor visitor(effectId);
|
||||
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;
|
||||
}
|
||||
|
||||
EffectParams params;
|
||||
params.mModelName = model;
|
||||
|
@ -1459,83 +1606,64 @@ namespace MWRender
|
|||
|
||||
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);
|
||||
|
||||
params.mObjects = PartHolderPtr(new PartHolder(node));
|
||||
|
||||
SceneUtil::FindMaxControllerLengthVisitor findMaxLengthVisitor;
|
||||
node->accept(findMaxLengthVisitor);
|
||||
|
||||
// FreezeOnCull doesn't work so well with effect particles, that tend to have moving emitters
|
||||
SceneUtil::DisableFreezeOnCullVisitor disableFreezeOnCullVisitor;
|
||||
node->accept(disableFreezeOnCullVisitor);
|
||||
|
||||
params.mMaxControllerLength = findMaxLengthVisitor.getMaxLength();
|
||||
|
||||
node->setNodeMask(Mask_Effect);
|
||||
|
||||
params.mMaxControllerLength = findMaxLengthVisitor.getMaxLength();
|
||||
params.mLoop = loop;
|
||||
params.mEffectId = effectId;
|
||||
params.mBoneName = bonename;
|
||||
|
||||
params.mObjects = PartHolderPtr(new PartHolder(node));
|
||||
params.mAnimTime = std::shared_ptr<EffectAnimationTime>(new EffectAnimationTime);
|
||||
trans->addUpdateCallback(new UpdateVfxCallback(params));
|
||||
|
||||
SceneUtil::AssignControllerSourcesVisitor assignVisitor(std::shared_ptr<SceneUtil::ControllerSource>(params.mAnimTime));
|
||||
node->accept(assignVisitor);
|
||||
|
||||
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)
|
||||
{
|
||||
for (std::vector<EffectParams>::iterator it = mEffects.begin(); it != mEffects.end(); ++it)
|
||||
{
|
||||
if (it->mEffectId == effectId)
|
||||
{
|
||||
mEffects.erase(it);
|
||||
return;
|
||||
}
|
||||
}
|
||||
RemoveFinishedCallbackVisitor visitor(effectId);
|
||||
mInsert->accept(visitor);
|
||||
visitor.remove();
|
||||
}
|
||||
|
||||
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)
|
||||
out.push_back(it->mEffectId);
|
||||
UpdateVfxCallback* callback = *it;
|
||||
|
||||
if (callback->mParams.mLoop && !callback->mFinished)
|
||||
out.push_back(callback->mParams.mEffectId);
|
||||
}
|
||||
}
|
||||
|
||||
void Animation::updateEffects(float duration)
|
||||
{
|
||||
for (std::vector<EffectParams>::iterator it = mEffects.begin(); it != mEffects.end(); )
|
||||
{
|
||||
it->mAnimTime->addTime(duration);
|
||||
|
||||
if (it->mAnimTime->getTime() >= it->mMaxControllerLength)
|
||||
{
|
||||
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;
|
||||
}
|
||||
// TODO: objects without animation still will have
|
||||
// transformation nodes with finished callbacks
|
||||
RemoveFinishedCallbackVisitor visitor;
|
||||
mInsert->accept(visitor);
|
||||
visitor.remove();
|
||||
}
|
||||
|
||||
bool Animation::upperBodyReady() const
|
||||
|
@ -1778,5 +1906,4 @@ namespace MWRender
|
|||
mNode->getParent(0)->removeChild(mNode);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -71,6 +71,17 @@ private:
|
|||
};
|
||||
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
|
||||
{
|
||||
public:
|
||||
|
@ -247,19 +258,6 @@ protected:
|
|||
|
||||
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;
|
||||
|
||||
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
|
||||
* @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 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);
|
||||
};
|
||||
|
||||
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
|
||||
|
|
|
@ -49,6 +49,7 @@
|
|||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include "../mwworld/cellstore.hpp"
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwgui/loadingscreen.hpp"
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/windowmanager.hpp"
|
||||
|
@ -1321,6 +1322,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()
|
||||
{
|
||||
if (mFieldOfViewOverridden == true)
|
||||
|
|
|
@ -203,6 +203,8 @@ namespace MWRender
|
|||
/// reset a previous overrideFieldOfView() call, i.e. revert to field of view specified in the settings file.
|
||||
void resetFieldOfView();
|
||||
|
||||
osg::Vec3f getHalfExtents(const MWWorld::ConstPtr& object) const;
|
||||
|
||||
void exportSceneGraph(const MWWorld::Ptr& ptr, const std::string& filename, const std::string& format);
|
||||
|
||||
LandManager* getLandManager() const;
|
||||
|
|
|
@ -192,8 +192,9 @@ namespace MWScript
|
|||
if (it == invStore.end())
|
||||
{
|
||||
it = ptr.getClass().getContainerStore (ptr).add (item, 1, ptr);
|
||||
Log(Debug::Warning) << "Implicitly adding one " << item << " to container "
|
||||
"to fulfil requirements of Equip instruction";
|
||||
Log(Debug::Warning) << "Implicitly adding one " << item <<
|
||||
" to the inventory store of " << ptr.getCellRef().getRefId() <<
|
||||
" to fulfill the requirements of Equip instruction";
|
||||
}
|
||||
|
||||
if (ptr == MWMechanics::getPlayer())
|
||||
|
|
|
@ -1082,6 +1082,7 @@ namespace MWScript
|
|||
MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr (targetId, false);
|
||||
|
||||
MWMechanics::CastSpell cast(ptr, target, false, true);
|
||||
cast.playSpellCastingEffects(spell->mId);
|
||||
cast.mHitPosition = target.getRefData().getPosition().asVec3();
|
||||
cast.mAlwaysSucceed = true;
|
||||
cast.cast(spell);
|
||||
|
@ -1155,8 +1156,7 @@ namespace MWScript
|
|||
|
||||
virtual void execute (Interpreter::Runtime &runtime)
|
||||
{
|
||||
/// \todo implement traveling check
|
||||
runtime.push (0);
|
||||
runtime.push (MWBase::Environment::get().getWorld()->isPlayerTraveling());
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -155,7 +155,8 @@ namespace MWWorld
|
|||
mGodMode(false), mScriptsEnabled(true), mContentFiles (contentFiles), mUserDataPath(userDataPath),
|
||||
mActivationDistanceOverride (activationDistanceOverride), mStartupScript(startupScript),
|
||||
mStartCell (startCell), mDistanceToFacedObject(-1), mTeleportEnabled(true),
|
||||
mLevitationEnabled(true), mGoToJail(false), mDaysInPrison(0), mSpellPreloadTimer(0.f)
|
||||
mLevitationEnabled(true), mGoToJail(false), mDaysInPrison(0),
|
||||
mPlayerTraveling(false), mPlayerInJail(false), mSpellPreloadTimer(0.f)
|
||||
{
|
||||
mPhysics.reset(new MWPhysics::PhysicsSystem(resourceSystem, rootNode));
|
||||
mRendering.reset(new MWRender::RenderingManager(viewer, rootNode, resourceSystem, workQueue, &mFallback, resourcePath));
|
||||
|
@ -313,6 +314,8 @@ namespace MWWorld
|
|||
mGoToJail = false;
|
||||
mTeleportEnabled = true;
|
||||
mLevitationEnabled = true;
|
||||
mPlayerTraveling = false;
|
||||
mPlayerInJail = false;
|
||||
|
||||
fillGlobalVariables();
|
||||
}
|
||||
|
@ -1644,6 +1647,15 @@ namespace MWWorld
|
|||
if (mGoToJail && !paused)
|
||||
goToJail();
|
||||
|
||||
// Reset "traveling" flag - there was a frame to detect traveling.
|
||||
mPlayerTraveling = false;
|
||||
|
||||
// The same thing for "in jail" flag: reset it if:
|
||||
// 1. Player was in jail
|
||||
// 2. Jailing window was closed
|
||||
if (mPlayerInJail && !mGoToJail && !MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_Jail))
|
||||
mPlayerInJail = false;
|
||||
|
||||
updateWeather(duration, paused);
|
||||
|
||||
if (!paused)
|
||||
|
@ -3283,6 +3295,7 @@ namespace MWWorld
|
|||
{
|
||||
// Reset bounty and forget the crime now, but don't change cell yet (the player should be able to read the dialog text first)
|
||||
mGoToJail = true;
|
||||
mPlayerInJail = true;
|
||||
|
||||
MWWorld::Ptr player = getPlayerPtr();
|
||||
|
||||
|
@ -3308,10 +3321,17 @@ namespace MWWorld
|
|||
|
||||
bool World::isPlayerInJail() const
|
||||
{
|
||||
if (mGoToJail)
|
||||
return true;
|
||||
return mPlayerInJail;
|
||||
}
|
||||
|
||||
return MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_Jail);
|
||||
void World::setPlayerTraveling(bool traveling)
|
||||
{
|
||||
mPlayerTraveling = traveling;
|
||||
}
|
||||
|
||||
bool World::isPlayerTraveling() const
|
||||
{
|
||||
return mPlayerTraveling;
|
||||
}
|
||||
|
||||
float World::getTerrainHeightAt(const osg::Vec3f& worldPos) const
|
||||
|
@ -3319,12 +3339,16 @@ namespace MWWorld
|
|||
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)
|
||||
return mPhysics->getRenderingHalfExtents(actor);
|
||||
return mPhysics->getRenderingHalfExtents(object);
|
||||
else
|
||||
return mPhysics->getHalfExtents(actor);
|
||||
return mPhysics->getHalfExtents(object);
|
||||
}
|
||||
|
||||
std::string World::exportSceneGraph(const Ptr &ptr)
|
||||
|
@ -3383,9 +3407,9 @@ namespace MWWorld
|
|||
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,
|
||||
|
|
|
@ -168,6 +168,8 @@ namespace MWWorld
|
|||
bool mLevitationEnabled;
|
||||
bool mGoToJail;
|
||||
int mDaysInPrison;
|
||||
bool mPlayerTraveling;
|
||||
bool mPlayerInJail;
|
||||
|
||||
float mSpellPreloadTimer;
|
||||
|
||||
|
@ -644,7 +646,7 @@ namespace MWWorld
|
|||
/// Spawn a blood effect for \a ptr at \a worldPosition
|
||||
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,
|
||||
ESM::RangeType rangeType, const std::string& id, const std::string& sourceName,
|
||||
|
@ -672,6 +674,9 @@ namespace MWWorld
|
|||
|
||||
bool isPlayerInJail() const override;
|
||||
|
||||
void setPlayerTraveling(bool traveling) override;
|
||||
bool isPlayerTraveling() const override;
|
||||
|
||||
/// Return terrain height at \a worldPos position.
|
||||
float getTerrainHeightAt(const osg::Vec3f& worldPos) const override;
|
||||
|
||||
|
|
|
@ -557,7 +557,7 @@ namespace Compiler
|
|||
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)
|
||||
{
|
||||
|
|
|
@ -83,7 +83,7 @@ namespace Compiler
|
|||
std::string mArguments;
|
||||
|
||||
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) {}
|
||||
|
||||
public:
|
||||
|
|
|
@ -24,7 +24,7 @@ namespace Interpreter
|
|||
Runtime& mRuntime;
|
||||
|
||||
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;
|
||||
out.fill(padding);
|
||||
|
@ -58,8 +58,34 @@ namespace Interpreter
|
|||
float value = mRuntime[0].mFloat;
|
||||
mRuntime.pop();
|
||||
|
||||
out << std::fixed << value;
|
||||
mFormattedMessage += out.str();
|
||||
if (notation == FixedNotation)
|
||||
{
|
||||
out << std::fixed << value;
|
||||
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;
|
||||
default:
|
||||
|
|
|
@ -49,11 +49,15 @@ namespace Misc
|
|||
width = (widthSet) ? width : -1;
|
||||
|
||||
if (m[i] == 'S' || m[i] == 's')
|
||||
visitedPlaceholder(StringPlaceholder, pad, width, precision);
|
||||
else if (m[i] == 'g' || m[i] == 'G')
|
||||
visitedPlaceholder(IntegerPlaceholder, pad, width, precision);
|
||||
visitedPlaceholder(StringPlaceholder, pad, width, precision, FixedNotation);
|
||||
else if (m[i] == 'd' || m[i] == 'i')
|
||||
visitedPlaceholder(IntegerPlaceholder, pad, width, precision, FixedNotation);
|
||||
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
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
public:
|
||||
|
|
|
@ -9,7 +9,7 @@ few chapters to familiarise yourself with the new interface.
|
|||
|
||||
.. warning::
|
||||
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
|
||||
the manual.
|
||||
|
||||
|
|
|
@ -121,7 +121,7 @@ existing filters into more complex ones.
|
|||
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:
|
||||
|
||||
``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.
|
||||
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.
|
||||
|
||||
.. 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.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue