mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-01-30 16:15:31 +00:00
Add OpenMW commits up to 9 May 2020
# Conflicts: # apps/openmw/mwmechanics/spellcasting.cpp # apps/openmw/mwscript/aiextensions.cpp
This commit is contained in:
commit
053a5a6258
101 changed files with 2293 additions and 1279 deletions
|
@ -9,6 +9,7 @@
|
|||
Bug #5367: Selecting a spell on an enchanted item per hotkey always plays the equip sound
|
||||
Bug #5369: Spawnpoint in the Grazelands doesn't produce oversized creatures
|
||||
Bug #5370: Opening an unlocked but trapped door uses the key
|
||||
Bug #5400: Editor: Verifier checks race of non-skin bodyparts
|
||||
Feature #5362: Show the soul gems' trapped soul in count dialog
|
||||
|
||||
0.46.0
|
||||
|
@ -217,7 +218,6 @@
|
|||
Bug #5264: "Damage Fatigue" Magic Effect Can Bring Fatigue below 0
|
||||
Bug #5269: Editor: Cell lighting in resaved cleaned content files is corrupted
|
||||
Bug #5278: Console command Show doesn't fall back to global variable after local var not found
|
||||
Bug #5300: NPCs don't switch from torch to shield when starting combat
|
||||
Bug #5308: World map copying makes save loading much slower
|
||||
Bug #5313: Node properties of identical type are not applied in the correct order
|
||||
Bug #5326: Formatting issues in the settings.cfg
|
||||
|
|
|
@ -759,9 +759,7 @@ echo
|
|||
cd $DEPS_INSTALL/..
|
||||
echo
|
||||
echo "Setting up OpenMW build..."
|
||||
add_cmake_opts -DBUILD_BSATOOL=no \
|
||||
-DBUILD_ESMTOOL=no \
|
||||
-DBUILD_MYGUI_PLUGIN=no \
|
||||
add_cmake_opts -DBUILD_MYGUI_PLUGIN=no \
|
||||
-DOPENMW_MP_BUILD=on
|
||||
if [ ! -z $CI ]; then
|
||||
case $STEP in
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#!/bin/sh -e
|
||||
|
||||
git clone -b release-1.8.1 https://github.com/google/googletest.git
|
||||
git clone -b release-1.10.0 https://github.com/google/googletest.git
|
||||
cd googletest
|
||||
mkdir build
|
||||
cd build
|
||||
|
|
|
@ -33,13 +33,10 @@ void CSMTools::BodyPartCheckStage::perform (int stage, CSMDoc::Messages &message
|
|||
CSMWorld::UniversalId id( CSMWorld::UniversalId::Type_BodyPart, bodyPart.mId );
|
||||
|
||||
// Check BYDT
|
||||
if (bodyPart.mData.mPart > 14 )
|
||||
if (bodyPart.mData.mPart >= ESM::BodyPart::MP_Count )
|
||||
messages.add(id, "Invalid part", "", CSMDoc::Message::Severity_Error);
|
||||
|
||||
if (bodyPart.mData.mFlags > 3 )
|
||||
messages.add(id, "Invalid flags", "", CSMDoc::Message::Severity_Error);
|
||||
|
||||
if (bodyPart.mData.mType > 2 )
|
||||
if (bodyPart.mData.mType > ESM::BodyPart::MT_Armor )
|
||||
messages.add(id, "Invalid type", "", CSMDoc::Message::Severity_Error);
|
||||
|
||||
// Check MODL
|
||||
|
@ -48,9 +45,12 @@ void CSMTools::BodyPartCheckStage::perform (int stage, CSMDoc::Messages &message
|
|||
else if ( mMeshes.searchId( bodyPart.mModel ) == -1 )
|
||||
messages.add(id, "Model '" + bodyPart.mModel + "' does not exist", "", CSMDoc::Message::Severity_Error);
|
||||
|
||||
// Check FNAM
|
||||
if ( bodyPart.mRace.empty() )
|
||||
messages.add(id, "Race is missing", "", CSMDoc::Message::Severity_Error);
|
||||
else if ( mRaces.searchId( bodyPart.mRace ) == -1 )
|
||||
messages.add(id, "Race '" + bodyPart.mRace + "' does not exist", "", CSMDoc::Message::Severity_Error);
|
||||
// Check FNAM for skin body parts (for non-skin body parts it's meaningless)
|
||||
if ( bodyPart.mData.mType == ESM::BodyPart::MT_Skin )
|
||||
{
|
||||
if ( bodyPart.mRace.empty() )
|
||||
messages.add(id, "Race is missing", "", CSMDoc::Message::Severity_Error);
|
||||
else if ( mRaces.searchId( bodyPart.mRace ) == -1 )
|
||||
messages.add(id, "Race '" + bodyPart.mRace + "' does not exist", "", CSMDoc::Message::Severity_Error);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -83,9 +83,10 @@ add_openmw_dir (mwclass
|
|||
add_openmw_dir (mwmechanics
|
||||
mechanicsmanagerimp stat creaturestats magiceffects movement actorutil
|
||||
drawstate spells activespells npcstats aipackage aisequence aipursue alchemy aiwander aitravel aifollow aiavoiddoor aibreathe
|
||||
aicast aiescort aiface aiactivate aicombat recharge repair enchanting pathfinding pathgrid security spellsuccess spellcasting
|
||||
aicast aiescort aiface aiactivate aicombat recharge repair enchanting pathfinding pathgrid security spellcasting spellresistance
|
||||
disease pickpocket levelledlist combat steering obstacle autocalcspell difficultyscaling aicombataction actor summoning
|
||||
character actors objects aistate coordinateconverter trading weaponpriority spellpriority weapontype
|
||||
character actors objects aistate coordinateconverter trading weaponpriority spellpriority weapontype spellutil tickableeffects
|
||||
spellabsorption linkedeffects
|
||||
)
|
||||
|
||||
add_openmw_dir (mwstate
|
||||
|
|
|
@ -780,7 +780,6 @@ private:
|
|||
};
|
||||
|
||||
// Initialise and enter main loop.
|
||||
|
||||
void OMW::Engine::go()
|
||||
{
|
||||
assert (!mContentFiles.empty());
|
||||
|
@ -820,7 +819,8 @@ void OMW::Engine::go()
|
|||
mViewer->setUseConfigureAffinity(false);
|
||||
#endif
|
||||
|
||||
mScreenCaptureOperation = new WriteScreenshotToFileOperation(mCfgMgr.getUserDataPath().string(),
|
||||
mScreenCaptureOperation = new WriteScreenshotToFileOperation(
|
||||
mCfgMgr.getScreenshotPath().string(),
|
||||
Settings::Manager::getString("screenshot format", "General"));
|
||||
|
||||
mScreenCaptureHandler = new osgViewer::ScreenCaptureHandler(mScreenCaptureOperation);
|
||||
|
|
|
@ -477,6 +477,8 @@ namespace MWGui
|
|||
|
||||
void DialogueWindow::onClose()
|
||||
{
|
||||
if (MWBase::Environment::get().getWindowManager()->containsMode(GM_Dialogue))
|
||||
return;
|
||||
// Reset history
|
||||
for (DialogueText* text : mHistoryContents)
|
||||
delete text;
|
||||
|
|
|
@ -32,26 +32,6 @@ namespace MWGui
|
|||
{
|
||||
}
|
||||
|
||||
bool ItemStack::stacks(const ItemStack &other)
|
||||
{
|
||||
if(mBase == other.mBase)
|
||||
return true;
|
||||
|
||||
// If one of the items is in an inventory and currently equipped, we need to check stacking both ways to be sure
|
||||
if (mBase.getContainerStore() && other.mBase.getContainerStore())
|
||||
return mBase.getContainerStore()->stacks(mBase, other.mBase)
|
||||
&& other.mBase.getContainerStore()->stacks(mBase, other.mBase);
|
||||
|
||||
if (mBase.getContainerStore())
|
||||
return mBase.getContainerStore()->stacks(mBase, other.mBase);
|
||||
if (other.mBase.getContainerStore())
|
||||
return other.mBase.getContainerStore()->stacks(mBase, other.mBase);
|
||||
|
||||
MWWorld::ContainerStore store;
|
||||
return store.stacks(mBase, other.mBase);
|
||||
|
||||
}
|
||||
|
||||
bool operator == (const ItemStack& left, const ItemStack& right)
|
||||
{
|
||||
if (left.mType != right.mType)
|
||||
|
|
|
@ -13,7 +13,6 @@ namespace MWGui
|
|||
{
|
||||
ItemStack (const MWWorld::Ptr& base, ItemModel* creator, size_t count);
|
||||
ItemStack();
|
||||
bool stacks (const ItemStack& other);
|
||||
///< like operator==, only without checking mType
|
||||
|
||||
enum Type
|
||||
|
|
|
@ -334,12 +334,6 @@ namespace MWGui
|
|||
setupCopyFramebufferToTextureCallback();
|
||||
}
|
||||
|
||||
// Turn off rendering except the GUI
|
||||
int oldUpdateMask = mViewer->getUpdateVisitor()->getTraversalMask();
|
||||
int oldCullMask = mViewer->getCamera()->getCullMask();
|
||||
mViewer->getUpdateVisitor()->setTraversalMask(MWRender::Mask_GUI|MWRender::Mask_PreCompile);
|
||||
mViewer->getCamera()->setCullMask(MWRender::Mask_GUI|MWRender::Mask_PreCompile);
|
||||
|
||||
MWBase::Environment::get().getInputManager()->update(0, true, true);
|
||||
|
||||
//osg::Timer timer;
|
||||
|
@ -355,10 +349,6 @@ namespace MWGui
|
|||
//if (mViewer->getIncrementalCompileOperation())
|
||||
//std::cout << "num to compile " << mViewer->getIncrementalCompileOperation()->getToCompile().size() << std::endl;
|
||||
|
||||
// resume 3d rendering
|
||||
mViewer->getUpdateVisitor()->setTraversalMask(oldUpdateMask);
|
||||
mViewer->getCamera()->setCullMask(oldCullMask);
|
||||
|
||||
mLastRenderTime = mTimer.time_m();
|
||||
}
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
#include "../mwbase/world.hpp"
|
||||
#include "../mwbase/windowmanager.hpp"
|
||||
|
||||
#include "../mwmechanics/spellcasting.hpp"
|
||||
#include "../mwmechanics/spellutil.hpp"
|
||||
#include "../mwmechanics/creaturestats.hpp"
|
||||
#include "../mwmechanics/actorutil.hpp"
|
||||
|
||||
|
|
|
@ -27,10 +27,10 @@
|
|||
#include "../mwworld/class.hpp"
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
|
||||
#include "../mwmechanics/spellcasting.hpp"
|
||||
#include "../mwmechanics/spells.hpp"
|
||||
#include "../mwmechanics/creaturestats.hpp"
|
||||
#include "../mwmechanics/actorutil.hpp"
|
||||
#include "../mwmechanics/spellutil.hpp"
|
||||
|
||||
#include "tooltips.hpp"
|
||||
#include "class.hpp"
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
#include "../mwbase/windowmanager.hpp"
|
||||
|
||||
#include "../mwmechanics/creaturestats.hpp"
|
||||
#include "../mwmechanics/spellcasting.hpp"
|
||||
#include "../mwmechanics/spellutil.hpp"
|
||||
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
#include "../mwworld/inventorystore.hpp"
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
#include "../mwworld/esmstore.hpp"
|
||||
#include "../mwworld/player.hpp"
|
||||
|
||||
#include "../mwmechanics/spellcasting.hpp"
|
||||
#include "../mwmechanics/spellutil.hpp"
|
||||
#include "../mwmechanics/spells.hpp"
|
||||
#include "../mwmechanics/creaturestats.hpp"
|
||||
#include "../mwmechanics/actorutil.hpp"
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
#include "../mwmechanics/spellcasting.hpp"
|
||||
#include "../mwmechanics/spellutil.hpp"
|
||||
#include "../mwmechanics/actorutil.hpp"
|
||||
|
||||
#include "mapwindow.hpp"
|
||||
|
|
|
@ -142,7 +142,9 @@ namespace MWGui
|
|||
osgViewer::Viewer* viewer, osg::Group* guiRoot, Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue,
|
||||
const std::string& logpath, const std::string& resourcePath, bool consoleOnlyScripts, Translation::Storage& translationDataStorage,
|
||||
ToUTF8::FromType encoding, bool exportFonts, const std::string& versionDescription, const std::string& userDataPath)
|
||||
: mStore(nullptr)
|
||||
: mOldUpdateMask(0)
|
||||
, mOldCullMask(0)
|
||||
, mStore(nullptr)
|
||||
, mResourceSystem(resourceSystem)
|
||||
, mWorkQueue(workQueue)
|
||||
, mViewer(viewer)
|
||||
|
@ -697,13 +699,34 @@ namespace MWGui
|
|||
}
|
||||
}
|
||||
|
||||
void WindowManager::enableScene(bool enable)
|
||||
{
|
||||
unsigned int disablemask = MWRender::Mask_GUI|MWRender::Mask_PreCompile;
|
||||
if (!enable && mViewer->getCamera()->getCullMask() != disablemask)
|
||||
{
|
||||
mOldUpdateMask = mViewer->getUpdateVisitor()->getTraversalMask();
|
||||
mOldCullMask = mViewer->getCamera()->getCullMask();
|
||||
mViewer->getUpdateVisitor()->setTraversalMask(disablemask);
|
||||
mViewer->getCamera()->setCullMask(disablemask);
|
||||
}
|
||||
else if (enable && mViewer->getCamera()->getCullMask() == disablemask)
|
||||
{
|
||||
mViewer->getUpdateVisitor()->setTraversalMask(mOldUpdateMask);
|
||||
mViewer->getCamera()->setCullMask(mOldCullMask);
|
||||
}
|
||||
}
|
||||
|
||||
void WindowManager::updateVisible()
|
||||
{
|
||||
bool loading = (getMode() == GM_Loading || getMode() == GM_LoadingWallpaper);
|
||||
|
||||
bool mainmenucover = containsMode(GM_MainMenu) && MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_NoGame;
|
||||
|
||||
enableScene(!loading && !mainmenucover);
|
||||
|
||||
if (!mMap)
|
||||
return; // UI not created yet
|
||||
|
||||
bool loading = (getMode() == GM_Loading || getMode() == GM_LoadingWallpaper);
|
||||
|
||||
mHud->setVisible(mHudEnabled && !loading);
|
||||
mToolTips->setVisible(mHudEnabled && !loading);
|
||||
|
||||
|
@ -2005,11 +2028,7 @@ namespace MWGui
|
|||
mVideoBackground->eventKeyButtonPressed += MyGUI::newDelegate(this, &WindowManager::onVideoKeyPressed);
|
||||
}
|
||||
|
||||
// Turn off all rendering except for the GUI
|
||||
int oldUpdateMask = mViewer->getUpdateVisitor()->getTraversalMask();
|
||||
int oldCullMask = mViewer->getCamera()->getCullMask();
|
||||
mViewer->getUpdateVisitor()->setTraversalMask(MWRender::Mask_GUI);
|
||||
mViewer->getCamera()->setCullMask(MWRender::Mask_GUI);
|
||||
enableScene(false);
|
||||
|
||||
MyGUI::IntSize screenSize = MyGUI::RenderManager::getInstance().getViewSize();
|
||||
sizeVideo(screenSize.width, screenSize.height);
|
||||
|
@ -2065,8 +2084,7 @@ namespace MWGui
|
|||
setCursorVisible(cursorWasVisible);
|
||||
|
||||
// Restore normal rendering
|
||||
mViewer->getUpdateVisitor()->setTraversalMask(oldUpdateMask);
|
||||
mViewer->getCamera()->setCullMask(oldCullMask);
|
||||
updateVisible();
|
||||
|
||||
mVideoBackground->setVisible(false);
|
||||
}
|
||||
|
|
|
@ -476,6 +476,8 @@ namespace MWGui
|
|||
virtual bool injectKeyRelease(MyGUI::KeyCode key);
|
||||
|
||||
private:
|
||||
unsigned int mOldUpdateMask; unsigned int mOldCullMask;
|
||||
|
||||
const MWWorld::ESMStore* mStore;
|
||||
Resource::ResourceSystem* mResourceSystem;
|
||||
osg::ref_ptr<SceneUtil::WorkQueue> mWorkQueue;
|
||||
|
@ -657,6 +659,8 @@ namespace MWGui
|
|||
void setMenuTransparency(float value);
|
||||
|
||||
void updatePinnedWindows();
|
||||
|
||||
void enableScene(bool enable);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -59,6 +59,7 @@
|
|||
#include "summoning.hpp"
|
||||
#include "combat.hpp"
|
||||
#include "actorutil.hpp"
|
||||
#include "tickableeffects.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
@ -1310,11 +1311,6 @@ namespace MWMechanics
|
|||
if (heldIter != inventoryStore.end() && heldIter->getTypeName() != typeid(ESM::Light).name())
|
||||
inventoryStore.unequipItem(*heldIter, ptr);
|
||||
}
|
||||
else if (heldIter == inventoryStore.end() || heldIter->getTypeName() == typeid(ESM::Light).name())
|
||||
{
|
||||
// For hostile NPCs, see if they have anything better to equip first
|
||||
inventoryStore.autoEquip(ptr);
|
||||
}
|
||||
|
||||
heldIter = inventoryStore.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
|
||||
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
#include "../mwworld/cellstore.hpp"
|
||||
|
||||
#include "npcstats.hpp"
|
||||
#include "spellcasting.hpp"
|
||||
#include "combat.hpp"
|
||||
#include "weaponpriority.hpp"
|
||||
#include "spellpriority.hpp"
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#include "autocalcspell.hpp"
|
||||
#include "spellcasting.hpp"
|
||||
|
||||
#include <limits>
|
||||
|
||||
|
@ -8,6 +7,7 @@
|
|||
#include "../mwbase/world.hpp"
|
||||
#include "../mwbase/environment.hpp"
|
||||
|
||||
#include "spellutil.hpp"
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
#include "npcstats.hpp"
|
||||
#include "movement.hpp"
|
||||
#include "spellcasting.hpp"
|
||||
#include "spellresistance.hpp"
|
||||
#include "difficultyscaling.hpp"
|
||||
#include "actorutil.hpp"
|
||||
#include "pathfinding.hpp"
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
Include additional headers for multiplayer purposes
|
||||
*/
|
||||
#include <components/openmw-mp/TimedLog.hpp>
|
||||
#include "spellcasting.hpp"
|
||||
#include "summoning.hpp"
|
||||
/*
|
||||
End of tes3mp addition
|
||||
*/
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
#include "../mwbase/mechanicsmanager.hpp"
|
||||
|
||||
#include "creaturestats.hpp"
|
||||
#include "spellcasting.hpp"
|
||||
#include "spellutil.hpp"
|
||||
#include "actorutil.hpp"
|
||||
#include "weapontype.hpp"
|
||||
|
||||
|
|
74
apps/openmw/mwmechanics/linkedeffects.cpp
Normal file
74
apps/openmw/mwmechanics/linkedeffects.cpp
Normal file
|
@ -0,0 +1,74 @@
|
|||
#include "linkedeffects.hpp"
|
||||
|
||||
#include <components/misc/rng.hpp>
|
||||
#include <components/settings/settings.hpp>
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
|
||||
#include "../mwrender/animation.hpp"
|
||||
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
|
||||
#include "creaturestats.hpp"
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
|
||||
bool reflectEffect(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect,
|
||||
const MWWorld::Ptr& caster, const MWWorld::Ptr& target, ESM::EffectList& reflectedEffects)
|
||||
{
|
||||
if (caster.isEmpty() || caster == target || !target.getClass().isActor())
|
||||
return false;
|
||||
|
||||
bool isHarmful = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful;
|
||||
bool isUnreflectable = magicEffect->mData.mFlags & ESM::MagicEffect::Unreflectable;
|
||||
if (!isHarmful || isUnreflectable)
|
||||
return false;
|
||||
|
||||
float reflect = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Reflect).getMagnitude();
|
||||
if (Misc::Rng::roll0to99() >= reflect)
|
||||
return false;
|
||||
|
||||
const ESM::Static* reflectStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find ("VFX_Reflect");
|
||||
MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(target);
|
||||
if (animation && !reflectStatic->mModel.empty())
|
||||
animation->addEffect("meshes\\" + reflectStatic->mModel, ESM::MagicEffect::Reflect, false, std::string());
|
||||
reflectedEffects.mList.emplace_back(effect);
|
||||
return true;
|
||||
}
|
||||
|
||||
void absorbStat(const ESM::ENAMstruct& effect, const ESM::ActiveEffect& appliedEffect,
|
||||
const MWWorld::Ptr& caster, const MWWorld::Ptr& target, bool reflected, const std::string& source)
|
||||
{
|
||||
if (caster.isEmpty() || caster == target)
|
||||
return;
|
||||
|
||||
if (!target.getClass().isActor() || !caster.getClass().isActor())
|
||||
return;
|
||||
|
||||
// Make sure callers don't do something weird
|
||||
if (effect.mEffectID < ESM::MagicEffect::AbsorbAttribute || effect.mEffectID > ESM::MagicEffect::AbsorbSkill)
|
||||
throw std::runtime_error("invalid absorb stat effect");
|
||||
|
||||
if (appliedEffect.mMagnitude == 0)
|
||||
return;
|
||||
|
||||
std::vector<ActiveSpells::ActiveEffect> absorbEffects;
|
||||
ActiveSpells::ActiveEffect absorbEffect = appliedEffect;
|
||||
absorbEffect.mMagnitude *= -1;
|
||||
absorbEffects.emplace_back(absorbEffect);
|
||||
|
||||
// Morrowind negates reflected Absorb spells so the original caster won't be harmed.
|
||||
if (reflected && Settings::Manager::getBool("classic reflected absorb spells behavior", "Game"))
|
||||
{
|
||||
target.getClass().getCreatureStats(target).getActiveSpells().addSpell(std::string(), true,
|
||||
absorbEffects, source, caster.getClass().getCreatureStats(caster).getActorId());
|
||||
return;
|
||||
}
|
||||
|
||||
caster.getClass().getCreatureStats(caster).getActiveSpells().addSpell(std::string(), true,
|
||||
absorbEffects, source, target.getClass().getCreatureStats(target).getActorId());
|
||||
}
|
||||
}
|
32
apps/openmw/mwmechanics/linkedeffects.hpp
Normal file
32
apps/openmw/mwmechanics/linkedeffects.hpp
Normal file
|
@ -0,0 +1,32 @@
|
|||
#ifndef MWMECHANICS_LINKEDEFFECTS_H
|
||||
#define MWMECHANICS_LINKEDEFFECTS_H
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
struct ActiveEffect;
|
||||
struct EffectList;
|
||||
struct ENAMstruct;
|
||||
struct MagicEffect;
|
||||
struct Spell;
|
||||
}
|
||||
|
||||
namespace MWWorld
|
||||
{
|
||||
class Ptr;
|
||||
}
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
|
||||
// Try to reflect a spell effect. If it's reflected, it's also put into the passed reflected effects list.
|
||||
bool reflectEffect(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect,
|
||||
const MWWorld::Ptr& caster, const MWWorld::Ptr& target, ESM::EffectList& reflectedEffects);
|
||||
|
||||
// Try to absorb a stat (skill, attribute, etc.) from the target and transfer it to the caster.
|
||||
void absorbStat(const ESM::ENAMstruct& effect, const ESM::ActiveEffect& appliedEffect,
|
||||
const MWWorld::Ptr& caster, const MWWorld::Ptr& target, bool reflected, const std::string& source);
|
||||
}
|
||||
|
||||
#endif
|
|
@ -35,7 +35,7 @@
|
|||
|
||||
#include "aicombat.hpp"
|
||||
#include "aipursue.hpp"
|
||||
#include "spellcasting.hpp"
|
||||
#include "spellutil.hpp"
|
||||
#include "autocalcspell.hpp"
|
||||
#include "npcstats.hpp"
|
||||
#include "actorutil.hpp"
|
||||
|
@ -390,7 +390,7 @@ namespace MWMechanics
|
|||
{
|
||||
const std::string& spell = winMgr->getSelectedSpell();
|
||||
if (!spell.empty())
|
||||
winMgr->setSelectedSpell(spell, int(MWMechanics::getSpellSuccessChance(spell, mWatched)));
|
||||
winMgr->setSelectedSpell(spell, int(getSpellSuccessChance(spell, mWatched)));
|
||||
else
|
||||
winMgr->unsetSelectedSpell();
|
||||
}
|
||||
|
|
76
apps/openmw/mwmechanics/spellabsorption.cpp
Normal file
76
apps/openmw/mwmechanics/spellabsorption.cpp
Normal file
|
@ -0,0 +1,76 @@
|
|||
#include "spellabsorption.hpp"
|
||||
|
||||
#include <components/misc/rng.hpp>
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
|
||||
#include "../mwrender/animation.hpp"
|
||||
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
#include "../mwworld/inventorystore.hpp"
|
||||
|
||||
#include "creaturestats.hpp"
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
|
||||
class GetAbsorptionProbability : public MWMechanics::EffectSourceVisitor
|
||||
{
|
||||
public:
|
||||
float mProbability{0.f};
|
||||
|
||||
GetAbsorptionProbability() = default;
|
||||
|
||||
virtual void visit (MWMechanics::EffectKey key,
|
||||
const std::string& /*sourceName*/, const std::string& /*sourceId*/, int /*casterActorId*/,
|
||||
float magnitude, float /*remainingTime*/, float /*totalTime*/)
|
||||
{
|
||||
if (key.mId == ESM::MagicEffect::SpellAbsorption)
|
||||
{
|
||||
if (mProbability == 0.f)
|
||||
mProbability = magnitude / 100;
|
||||
else
|
||||
{
|
||||
// If there are different sources of SpellAbsorption effect, multiply failing probability for all effects.
|
||||
// Real absorption probability will be the (1 - total fail chance) in this case.
|
||||
float failProbability = 1.f - mProbability;
|
||||
failProbability *= 1.f - magnitude / 100;
|
||||
mProbability = 1.f - failProbability;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
bool absorbSpell (const ESM::Spell* spell, const MWWorld::Ptr& caster, const MWWorld::Ptr& target)
|
||||
{
|
||||
if (!spell || caster == target || !target.getClass().isActor())
|
||||
return false;
|
||||
|
||||
CreatureStats& stats = target.getClass().getCreatureStats(target);
|
||||
if (stats.getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).getMagnitude() <= 0.f)
|
||||
return false;
|
||||
|
||||
GetAbsorptionProbability check;
|
||||
stats.getActiveSpells().visitEffectSources(check);
|
||||
stats.getSpells().visitEffectSources(check);
|
||||
if (target.getClass().hasInventoryStore(target))
|
||||
target.getClass().getInventoryStore(target).visitEffectSources(check);
|
||||
|
||||
int chance = check.mProbability * 100;
|
||||
if (Misc::Rng::roll0to99() >= chance)
|
||||
return false;
|
||||
|
||||
const ESM::Static* absorbStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find("VFX_Absorb");
|
||||
MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(target);
|
||||
if (animation && !absorbStatic->mModel.empty())
|
||||
animation->addEffect( "meshes\\" + absorbStatic->mModel, ESM::MagicEffect::SpellAbsorption, false, std::string());
|
||||
// Magicka is increased by the cost of the spell
|
||||
DynamicStat<float> magicka = stats.getMagicka();
|
||||
magicka.setCurrent(magicka.getCurrent() + spell->mData.mCost);
|
||||
stats.setMagicka(magicka);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
20
apps/openmw/mwmechanics/spellabsorption.hpp
Normal file
20
apps/openmw/mwmechanics/spellabsorption.hpp
Normal file
|
@ -0,0 +1,20 @@
|
|||
#ifndef MWMECHANICS_SPELLABSORPTION_H
|
||||
#define MWMECHANICS_SPELLABSORPTION_H
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
struct Spell;
|
||||
}
|
||||
|
||||
namespace MWWorld
|
||||
{
|
||||
class Ptr;
|
||||
}
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
// Try to absorb a spell based on the magnitude of every Spell Absorption effect source on the target.
|
||||
bool absorbSpell(const ESM::Spell* spell, const MWWorld::Ptr& caster, const MWWorld::Ptr& target);
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,11 +1,7 @@
|
|||
#include "spellcasting.hpp"
|
||||
|
||||
#include <limits>
|
||||
#include <iomanip>
|
||||
|
||||
#include <components/misc/constants.hpp>
|
||||
#include <components/misc/rng.hpp>
|
||||
#include <components/settings/settings.hpp>
|
||||
|
||||
/*
|
||||
Start of tes3mp addition
|
||||
|
@ -40,328 +36,22 @@
|
|||
|
||||
#include "../mwrender/animation.hpp"
|
||||
|
||||
#include "npcstats.hpp"
|
||||
#include "actorutil.hpp"
|
||||
#include "aifollow.hpp"
|
||||
#include "creaturestats.hpp"
|
||||
#include "linkedeffects.hpp"
|
||||
#include "spellabsorption.hpp"
|
||||
#include "spellresistance.hpp"
|
||||
#include "spellutil.hpp"
|
||||
#include "summoning.hpp"
|
||||
#include "tickableeffects.hpp"
|
||||
#include "weapontype.hpp"
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
ESM::Skill::SkillEnum spellSchoolToSkill(int school)
|
||||
{
|
||||
static std::map<int, ESM::Skill::SkillEnum> schoolSkillMap; // maps spell school to skill id
|
||||
if (schoolSkillMap.empty())
|
||||
{
|
||||
schoolSkillMap[0] = ESM::Skill::Alteration;
|
||||
schoolSkillMap[1] = ESM::Skill::Conjuration;
|
||||
schoolSkillMap[3] = ESM::Skill::Illusion;
|
||||
schoolSkillMap[2] = ESM::Skill::Destruction;
|
||||
schoolSkillMap[4] = ESM::Skill::Mysticism;
|
||||
schoolSkillMap[5] = ESM::Skill::Restoration;
|
||||
}
|
||||
|
||||
assert(schoolSkillMap.find(school) != schoolSkillMap.end());
|
||||
return schoolSkillMap[school];
|
||||
}
|
||||
|
||||
float calcEffectCost(const ESM::ENAMstruct& effect)
|
||||
{
|
||||
const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find(effect.mEffectID);
|
||||
return calcEffectCost(effect, magicEffect);
|
||||
}
|
||||
|
||||
float calcEffectCost(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect)
|
||||
{
|
||||
int minMagn = 1;
|
||||
int maxMagn = 1;
|
||||
if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude))
|
||||
{
|
||||
minMagn = effect.mMagnMin;
|
||||
maxMagn = effect.mMagnMax;
|
||||
}
|
||||
|
||||
int duration = 1;
|
||||
if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration))
|
||||
duration = effect.mDuration;
|
||||
|
||||
static const float fEffectCostMult = MWBase::Environment::get().getWorld()->getStore()
|
||||
.get<ESM::GameSetting>().find("fEffectCostMult")->mValue.getFloat();
|
||||
|
||||
float x = 0.5 * (std::max(1, minMagn) + std::max(1, maxMagn));
|
||||
x *= 0.1 * magicEffect->mData.mBaseCost;
|
||||
x *= 1 + duration;
|
||||
x += 0.05 * std::max(1, effect.mArea) * magicEffect->mData.mBaseCost;
|
||||
|
||||
return x * fEffectCostMult;
|
||||
}
|
||||
|
||||
float calcSpellBaseSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool)
|
||||
{
|
||||
// Morrowind for some reason uses a formula slightly different from magicka cost calculation
|
||||
float y = std::numeric_limits<float>::max();
|
||||
float lowestSkill = 0;
|
||||
|
||||
for (std::vector<ESM::ENAMstruct>::const_iterator it = spell->mEffects.mList.begin(); it != spell->mEffects.mList.end(); ++it)
|
||||
{
|
||||
float x = static_cast<float>(it->mDuration);
|
||||
const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find(
|
||||
it->mEffectID);
|
||||
|
||||
if (!(magicEffect->mData.mFlags & ESM::MagicEffect::AppliedOnce))
|
||||
x = std::max(1.f, x);
|
||||
|
||||
x *= 0.1f * magicEffect->mData.mBaseCost;
|
||||
x *= 0.5f * (it->mMagnMin + it->mMagnMax);
|
||||
x *= it->mArea * 0.05f * magicEffect->mData.mBaseCost;
|
||||
if (it->mRange == ESM::RT_Target)
|
||||
x *= 1.5f;
|
||||
static const float fEffectCostMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
|
||||
"fEffectCostMult")->mValue.getFloat();
|
||||
x *= fEffectCostMult;
|
||||
|
||||
float s = 2.0f * actor.getClass().getSkill(actor, spellSchoolToSkill(magicEffect->mData.mSchool));
|
||||
if (s - x < y)
|
||||
{
|
||||
y = s - x;
|
||||
if (effectiveSchool)
|
||||
*effectiveSchool = magicEffect->mData.mSchool;
|
||||
lowestSkill = s;
|
||||
}
|
||||
}
|
||||
|
||||
CreatureStats& stats = actor.getClass().getCreatureStats(actor);
|
||||
|
||||
int actorWillpower = stats.getAttribute(ESM::Attribute::Willpower).getModified();
|
||||
int actorLuck = stats.getAttribute(ESM::Attribute::Luck).getModified();
|
||||
|
||||
float castChance = (lowestSkill - spell->mData.mCost + 0.2f * actorWillpower + 0.1f * actorLuck);
|
||||
|
||||
return castChance;
|
||||
}
|
||||
|
||||
float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool, bool cap, bool checkMagicka)
|
||||
{
|
||||
bool godmode = actor == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState();
|
||||
|
||||
CreatureStats& stats = actor.getClass().getCreatureStats(actor);
|
||||
|
||||
float castBonus = -stats.getMagicEffects().get(ESM::MagicEffect::Sound).getMagnitude();
|
||||
|
||||
float castChance = calcSpellBaseSuccessChance(spell, actor, effectiveSchool) + castBonus;
|
||||
castChance *= stats.getFatigueTerm();
|
||||
|
||||
if (stats.getMagicEffects().get(ESM::MagicEffect::Silence).getMagnitude()&& !godmode)
|
||||
return 0;
|
||||
|
||||
if (spell->mData.mType == ESM::Spell::ST_Power)
|
||||
return stats.getSpells().canUsePower(spell) ? 100 : 0;
|
||||
|
||||
if (spell->mData.mType != ESM::Spell::ST_Spell)
|
||||
return 100;
|
||||
|
||||
if (checkMagicka && stats.getMagicka().getCurrent() < spell->mData.mCost && !godmode)
|
||||
return 0;
|
||||
|
||||
if (spell->mData.mFlags & ESM::Spell::F_Always)
|
||||
return 100;
|
||||
|
||||
if (godmode)
|
||||
{
|
||||
return 100;
|
||||
}
|
||||
|
||||
if (!cap)
|
||||
return std::max(0.f, castChance);
|
||||
else
|
||||
return std::max(0.f, std::min(100.f, castChance));
|
||||
}
|
||||
|
||||
float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool, bool cap, bool checkMagicka)
|
||||
{
|
||||
const ESM::Spell* spell =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find(spellId);
|
||||
return getSpellSuccessChance(spell, actor, effectiveSchool, cap, checkMagicka);
|
||||
}
|
||||
|
||||
|
||||
int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor)
|
||||
{
|
||||
int school = 0;
|
||||
getSpellSuccessChance(spellId, actor, &school);
|
||||
return school;
|
||||
}
|
||||
|
||||
int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor)
|
||||
{
|
||||
int school = 0;
|
||||
getSpellSuccessChance(spell, actor, &school);
|
||||
return school;
|
||||
}
|
||||
|
||||
bool spellIncreasesSkill(const ESM::Spell *spell)
|
||||
{
|
||||
if (spell->mData.mType == ESM::Spell::ST_Spell && !(spell->mData.mFlags & ESM::Spell::F_Always))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool spellIncreasesSkill(const std::string &spellId)
|
||||
{
|
||||
const ESM::Spell* spell =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find(spellId);
|
||||
return spellIncreasesSkill(spell);
|
||||
}
|
||||
|
||||
float getEffectResistanceAttribute (short effectId, const MagicEffects* actorEffects)
|
||||
{
|
||||
short resistanceEffect = ESM::MagicEffect::getResistanceEffect(effectId);
|
||||
short weaknessEffect = ESM::MagicEffect::getWeaknessEffect(effectId);
|
||||
|
||||
float resistance = 0;
|
||||
if (resistanceEffect != -1)
|
||||
resistance += actorEffects->get(resistanceEffect).getMagnitude();
|
||||
if (weaknessEffect != -1)
|
||||
resistance -= actorEffects->get(weaknessEffect).getMagnitude();
|
||||
|
||||
if (effectId == ESM::MagicEffect::FireDamage)
|
||||
resistance += actorEffects->get(ESM::MagicEffect::FireShield).getMagnitude();
|
||||
if (effectId == ESM::MagicEffect::ShockDamage)
|
||||
resistance += actorEffects->get(ESM::MagicEffect::LightningShield).getMagnitude();
|
||||
if (effectId == ESM::MagicEffect::FrostDamage)
|
||||
resistance += actorEffects->get(ESM::MagicEffect::FrostShield).getMagnitude();
|
||||
|
||||
return resistance;
|
||||
}
|
||||
|
||||
float getEffectResistance (short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster,
|
||||
const ESM::Spell* spell, const MagicEffects* effects)
|
||||
{
|
||||
const ESM::MagicEffect *magicEffect =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
|
||||
effectId);
|
||||
|
||||
const MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor);
|
||||
const MWMechanics::MagicEffects* magicEffects = &stats.getMagicEffects();
|
||||
if (effects)
|
||||
magicEffects = effects;
|
||||
|
||||
// Effects with no resistance attribute belonging to them can not be resisted
|
||||
if (ESM::MagicEffect::getResistanceEffect(effectId) == -1)
|
||||
return 0.f;
|
||||
|
||||
float resistance = getEffectResistanceAttribute(effectId, magicEffects);
|
||||
|
||||
int willpower = stats.getAttribute(ESM::Attribute::Willpower).getModified();
|
||||
float luck = static_cast<float>(stats.getAttribute(ESM::Attribute::Luck).getModified());
|
||||
float x = (willpower + 0.1f * luck) * stats.getFatigueTerm();
|
||||
|
||||
// This makes spells that are easy to cast harder to resist and vice versa
|
||||
float castChance = 100.f;
|
||||
if (spell != nullptr && !caster.isEmpty() && caster.getClass().isActor())
|
||||
{
|
||||
castChance = getSpellSuccessChance(spell, caster, nullptr, false, false); // Uncapped casting chance
|
||||
}
|
||||
if (castChance > 0)
|
||||
x *= 50 / castChance;
|
||||
|
||||
float roll = Misc::Rng::rollClosedProbability() * 100;
|
||||
if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)
|
||||
roll -= resistance;
|
||||
|
||||
if (x <= roll)
|
||||
x = 0;
|
||||
else
|
||||
{
|
||||
if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)
|
||||
x = 100;
|
||||
else
|
||||
x = roll / std::min(x, 100.f);
|
||||
}
|
||||
|
||||
x = std::min(x + resistance, 100.f);
|
||||
return x;
|
||||
}
|
||||
|
||||
float getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster,
|
||||
const ESM::Spell* spell, const MagicEffects* effects)
|
||||
{
|
||||
float resistance = getEffectResistance(effectId, actor, caster, spell, effects);
|
||||
return 1 - resistance / 100.f;
|
||||
}
|
||||
|
||||
/// Check if the given effect can be applied to the target. If \a castByPlayer, emits a message box on failure.
|
||||
bool checkEffectTarget (int effectId, const MWWorld::Ptr& target, const MWWorld::Ptr& caster, bool castByPlayer)
|
||||
{
|
||||
switch (effectId)
|
||||
{
|
||||
case ESM::MagicEffect::Levitate:
|
||||
if (!MWBase::Environment::get().getWorld()->isLevitationEnabled())
|
||||
{
|
||||
if (castByPlayer)
|
||||
MWBase::Environment::get().getWindowManager()->messageBox("#{sLevitateDisabled}");
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case ESM::MagicEffect::Soultrap:
|
||||
if (!target.getClass().isNpc() // no messagebox for NPCs
|
||||
&& (target.getTypeName() == typeid(ESM::Creature).name() && target.get<ESM::Creature>()->mBase->mData.mSoul == 0))
|
||||
{
|
||||
if (castByPlayer)
|
||||
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInvalidTarget}");
|
||||
return true; // must still apply to get visual effect and have target regard it as attack
|
||||
}
|
||||
break;
|
||||
case ESM::MagicEffect::WaterWalking:
|
||||
if (target.getClass().isPureWaterCreature(target) && MWBase::Environment::get().getWorld()->isSwimming(target))
|
||||
return false;
|
||||
|
||||
MWBase::World *world = MWBase::Environment::get().getWorld();
|
||||
|
||||
if (!world->isWaterWalkingCastableOnTarget(target))
|
||||
{
|
||||
if (castByPlayer && caster == target)
|
||||
MWBase::Environment::get().getWindowManager()->messageBox ("#{sMagicInvalidEffect}");
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
class GetAbsorptionProbability : public MWMechanics::EffectSourceVisitor
|
||||
{
|
||||
public:
|
||||
float mProbability;
|
||||
|
||||
GetAbsorptionProbability(const MWWorld::Ptr& actor)
|
||||
: mProbability(0.f){}
|
||||
|
||||
virtual void visit (MWMechanics::EffectKey key,
|
||||
const std::string& sourceName, const std::string& sourceId, int casterActorId,
|
||||
float magnitude, float remainingTime = -1, float totalTime = -1)
|
||||
{
|
||||
if (key.mId == ESM::MagicEffect::SpellAbsorption)
|
||||
{
|
||||
if (mProbability == 0.f)
|
||||
mProbability = magnitude / 100;
|
||||
else
|
||||
{
|
||||
// If there are different sources of SpellAbsorption effect, multiply failing probability for all effects.
|
||||
// Real absorption probability will be the (1 - total fail chance) in this case.
|
||||
float failProbability = 1.f - mProbability;
|
||||
failProbability *= 1.f - magnitude / 100;
|
||||
mProbability = 1.f - failProbability;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
CastSpell::CastSpell(const MWWorld::Ptr &caster, const MWWorld::Ptr &target, const bool fromProjectile, const bool manualSpell)
|
||||
: mCaster(caster)
|
||||
, mTarget(target)
|
||||
, mStack(false)
|
||||
, mHitPosition(0,0,0)
|
||||
, mAlwaysSucceed(false)
|
||||
, mFromProjectile(fromProjectile)
|
||||
, mManualSpell(manualSpell)
|
||||
{
|
||||
|
@ -392,10 +82,9 @@ namespace MWMechanics
|
|||
|
||||
// If none of the effects need to apply, we can early-out
|
||||
bool found = false;
|
||||
for (std::vector<ESM::ENAMstruct>::const_iterator iter (effects.mList.begin());
|
||||
iter!=effects.mList.end(); ++iter)
|
||||
for (const ESM::ENAMstruct& effect : effects.mList)
|
||||
{
|
||||
if (iter->mRange == range)
|
||||
if (effect.mRange == range)
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
|
@ -441,6 +130,9 @@ namespace MWMechanics
|
|||
// throughout the iteration of this spell's
|
||||
// effects, we display a "can't re-cast" message
|
||||
|
||||
// Try absorbing the spell. Some handling must still happen for absorbed effects.
|
||||
bool absorbed = absorbSpell(spell, caster, target);
|
||||
|
||||
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt (effects.mList.begin());
|
||||
!target.isEmpty() && effectIt != effects.mList.end(); ++effectIt)
|
||||
{
|
||||
|
@ -458,8 +150,7 @@ namespace MWMechanics
|
|||
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicCannotRecast}");
|
||||
continue;
|
||||
}
|
||||
else
|
||||
canCastAnEffect = true;
|
||||
canCastAnEffect = true;
|
||||
|
||||
if (!checkEffectTarget(effectIt->mEffectID, target, caster, castByPlayer))
|
||||
continue;
|
||||
|
@ -469,89 +160,30 @@ namespace MWMechanics
|
|||
&& (caster.isEmpty() || !caster.getClass().isActor()))
|
||||
continue;
|
||||
|
||||
// If player is healing someone, show the target's HP bar
|
||||
if (castByPlayer && target != caster
|
||||
&& effectIt->mEffectID == ESM::MagicEffect::RestoreHealth
|
||||
&& target.getClass().isActor())
|
||||
MWBase::Environment::get().getWindowManager()->setEnemy(target);
|
||||
// Notify the target actor they've been hit
|
||||
bool isHarmful = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful;
|
||||
if (target.getClass().isActor() && target != caster && !caster.isEmpty() && isHarmful)
|
||||
target.getClass().onHit(target, 0.0f, true, MWWorld::Ptr(), caster, osg::Vec3f(), true);
|
||||
|
||||
// Try absorbing if it's a spell
|
||||
// Unlike Reflect, this is done once per spell absorption effect source
|
||||
bool absorbed = false;
|
||||
if (spell && caster != target && target.getClass().isActor())
|
||||
// Avoid proceeding further for absorbed spells.
|
||||
if (absorbed)
|
||||
continue;
|
||||
|
||||
// Reflect harmful effects
|
||||
if (!reflected && reflectEffect(*effectIt, magicEffect, caster, target, reflectedEffects))
|
||||
continue;
|
||||
|
||||
// Try resisting.
|
||||
float magnitudeMult = getEffectMultiplier(effectIt->mEffectID, target, caster, spell, &targetEffects);
|
||||
if (magnitudeMult == 0)
|
||||
{
|
||||
CreatureStats& stats = target.getClass().getCreatureStats(target);
|
||||
if (stats.getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).getMagnitude() > 0.f)
|
||||
{
|
||||
GetAbsorptionProbability check(target);
|
||||
stats.getActiveSpells().visitEffectSources(check);
|
||||
stats.getSpells().visitEffectSources(check);
|
||||
if (target.getClass().hasInventoryStore(target))
|
||||
target.getClass().getInventoryStore(target).visitEffectSources(check);
|
||||
|
||||
int absorb = check.mProbability * 100;
|
||||
absorbed = (Misc::Rng::roll0to99() < absorb);
|
||||
if (absorbed)
|
||||
{
|
||||
const ESM::Static* absorbStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find ("VFX_Absorb");
|
||||
MWBase::Environment::get().getWorld()->getAnimation(target)->addEffect(
|
||||
"meshes\\" + absorbStatic->mModel, ESM::MagicEffect::SpellAbsorption, false, "");
|
||||
// Magicka is increased by cost of spell
|
||||
DynamicStat<float> magicka = stats.getMagicka();
|
||||
magicka.setCurrent(magicka.getCurrent() + spell->mData.mCost);
|
||||
stats.setMagicka(magicka);
|
||||
}
|
||||
}
|
||||
// Fully resisted, show message
|
||||
if (target == getPlayer())
|
||||
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}");
|
||||
else if (castByPlayer)
|
||||
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}");
|
||||
}
|
||||
|
||||
float magnitudeMult = 1;
|
||||
|
||||
if (target.getClass().isActor())
|
||||
{
|
||||
if (absorbed)
|
||||
continue;
|
||||
|
||||
bool isHarmful = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful;
|
||||
// Reflect harmful effects
|
||||
if (isHarmful && !reflected && !caster.isEmpty() && caster != target && !(magicEffect->mData.mFlags & ESM::MagicEffect::Unreflectable))
|
||||
{
|
||||
float reflect = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Reflect).getMagnitude();
|
||||
bool isReflected = (Misc::Rng::roll0to99() < reflect);
|
||||
if (isReflected)
|
||||
{
|
||||
const ESM::Static* reflectStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find ("VFX_Reflect");
|
||||
MWBase::Environment::get().getWorld()->getAnimation(target)->addEffect(
|
||||
"meshes\\" + reflectStatic->mModel, ESM::MagicEffect::Reflect, false, "");
|
||||
reflectedEffects.mList.push_back(*effectIt);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Try resisting
|
||||
magnitudeMult = MWMechanics::getEffectMultiplier(effectIt->mEffectID, target, caster, spell, &targetEffects);
|
||||
if (magnitudeMult == 0)
|
||||
{
|
||||
// Fully resisted, show message
|
||||
if (target == getPlayer())
|
||||
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}");
|
||||
else if (castByPlayer)
|
||||
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}");
|
||||
}
|
||||
else if (isHarmful && castByPlayer && target != caster)
|
||||
{
|
||||
// If player is attempting to cast a harmful spell and it wasn't fully resisted, show the target's HP bar
|
||||
MWBase::Environment::get().getWindowManager()->setEnemy(target);
|
||||
}
|
||||
|
||||
if (target == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState() && isHarmful)
|
||||
magnitudeMult = 0;
|
||||
|
||||
// Notify the target actor they've been hit
|
||||
if (target != caster && !caster.isEmpty() && isHarmful)
|
||||
target.getClass().onHit(target, 0.0f, true, MWWorld::Ptr(), caster, osg::Vec3f(), true);
|
||||
}
|
||||
|
||||
if (magnitudeMult > 0 && !absorbed)
|
||||
else
|
||||
{
|
||||
float magnitude = effectIt->mMagnMin + Misc::Rng::rollDice(effectIt->mMagnMax - effectIt->mMagnMin + 1);
|
||||
magnitude *= magnitudeMult;
|
||||
|
@ -578,6 +210,19 @@ namespace MWMechanics
|
|||
effect.mMagnitude = 0;
|
||||
}
|
||||
|
||||
// Avoid applying harmful effects to the player in god mode
|
||||
if (target == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState() && isHarmful)
|
||||
{
|
||||
effect.mMagnitude = 0;
|
||||
}
|
||||
|
||||
bool effectAffectsHealth = isHarmful || effectIt->mEffectID == ESM::MagicEffect::RestoreHealth;
|
||||
if (castByPlayer && target != caster && effectAffectsHealth)
|
||||
{
|
||||
// If player is attempting to cast a harmful spell or is healing someone, show the target's HP bar.
|
||||
MWBase::Environment::get().getWindowManager()->setEnemy(target);
|
||||
}
|
||||
|
||||
bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration);
|
||||
if (hasDuration && effectIt->mDuration == 0)
|
||||
{
|
||||
|
@ -587,7 +232,7 @@ namespace MWMechanics
|
|||
|
||||
// duration 0 means apply full magnitude instantly
|
||||
bool wasDead = target.getClass().getCreatureStats(target).isDead();
|
||||
effectTick(target.getClass().getCreatureStats(target), target, EffectKey(*effectIt), magnitude);
|
||||
effectTick(target.getClass().getCreatureStats(target), target, EffectKey(*effectIt), effect.mMagnitude);
|
||||
bool isDead = target.getClass().getCreatureStats(target).isDead();
|
||||
|
||||
/*
|
||||
|
@ -619,11 +264,7 @@ namespace MWMechanics
|
|||
}
|
||||
else
|
||||
{
|
||||
|
||||
if (!hasDuration)
|
||||
effect.mDuration = 1.0f;
|
||||
else
|
||||
effect.mDuration = static_cast<float>(effectIt->mDuration);
|
||||
effect.mDuration = hasDuration ? static_cast<float>(effectIt->mDuration) : 1.f;
|
||||
|
||||
targetEffects.add(MWMechanics::EffectKey(*effectIt), MWMechanics::EffectParam(effect.mMagnitude));
|
||||
|
||||
|
@ -640,7 +281,7 @@ namespace MWMechanics
|
|||
// Command spells should have their effect, including taking the target out of combat, each time the spell successfully affects the target
|
||||
if (((effectIt->mEffectID == ESM::MagicEffect::CommandHumanoid && target.getClass().isNpc())
|
||||
|| (effectIt->mEffectID == ESM::MagicEffect::CommandCreature && target.getTypeName() == typeid(ESM::Creature).name()))
|
||||
&& !caster.isEmpty() && caster.getClass().isActor() && target != getPlayer() && magnitude >= target.getClass().getCreatureStats(target).getLevel())
|
||||
&& !caster.isEmpty() && caster.getClass().isActor() && target != getPlayer() && effect.mMagnitude >= target.getClass().getCreatureStats(target).getLevel())
|
||||
{
|
||||
MWMechanics::AiFollow package(caster, true);
|
||||
target.getClass().getCreatureStats(target).getAiSequence().stack(package, target);
|
||||
|
@ -648,23 +289,8 @@ namespace MWMechanics
|
|||
|
||||
// For absorb effects, also apply the effect to the caster - but with a negative
|
||||
// magnitude, since we're transferring stats from the target to the caster
|
||||
if (!caster.isEmpty() && caster != target && caster.getClass().isActor())
|
||||
{
|
||||
if (effectIt->mEffectID >= ESM::MagicEffect::AbsorbAttribute &&
|
||||
effectIt->mEffectID <= ESM::MagicEffect::AbsorbSkill)
|
||||
{
|
||||
std::vector<ActiveSpells::ActiveEffect> absorbEffects;
|
||||
ActiveSpells::ActiveEffect effect_ = effect;
|
||||
effect_.mMagnitude *= -1;
|
||||
absorbEffects.push_back(effect_);
|
||||
if (reflected && Settings::Manager::getBool("classic reflected absorb spells behavior", "Game"))
|
||||
target.getClass().getCreatureStats(target).getActiveSpells().addSpell("", true,
|
||||
absorbEffects, mSourceName, caster.getClass().getCreatureStats(caster).getActorId());
|
||||
else
|
||||
caster.getClass().getCreatureStats(caster).getActiveSpells().addSpell("", true,
|
||||
absorbEffects, mSourceName, target.getClass().getCreatureStats(target).getActorId());
|
||||
}
|
||||
}
|
||||
if (effectIt->mEffectID >= ESM::MagicEffect::AbsorbAttribute && effectIt->mEffectID <= ESM::MagicEffect::AbsorbSkill)
|
||||
absorbStat(*effectIt, effect, caster, target, reflected, mSourceName);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -712,13 +338,11 @@ namespace MWMechanics
|
|||
else
|
||||
castStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find ("VFX_DefaultHit");
|
||||
|
||||
std::string texture = magicEffect->mParticle;
|
||||
|
||||
bool loop = (magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx) != 0;
|
||||
// Note: in case of non actor, a free effect should be fine as well
|
||||
MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(target);
|
||||
if (anim)
|
||||
anim->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, "", texture);
|
||||
if (anim && !castStatic->mModel.empty())
|
||||
anim->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, "", magicEffect->mParticle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -920,16 +544,14 @@ namespace MWMechanics
|
|||
|
||||
bool CastSpell::cast(const std::string &id)
|
||||
{
|
||||
if (const ESM::Spell *spell =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search (id))
|
||||
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
|
||||
if (const auto spell = store.get<ESM::Spell>().search(id))
|
||||
return cast(spell);
|
||||
|
||||
if (const ESM::Potion *potion =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Potion>().search (id))
|
||||
if (const auto potion = store.get<ESM::Potion>().search(id))
|
||||
return cast(potion);
|
||||
|
||||
if (const ESM::Ingredient *ingredient =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Ingredient>().search (id))
|
||||
if (const auto ingredient = store.get<ESM::Ingredient>().search(id))
|
||||
return cast(ingredient);
|
||||
|
||||
throw std::runtime_error("ID type cannot be casted");
|
||||
|
@ -1114,13 +736,12 @@ namespace MWMechanics
|
|||
stats.getSpells().usePower(spell);
|
||||
}
|
||||
|
||||
if (mCaster == getPlayer() && spellIncreasesSkill())
|
||||
mCaster.getClass().skillUsageSucceeded(mCaster,
|
||||
spellSchoolToSkill(school), 0);
|
||||
|
||||
if (!mManualSpell && mCaster == getPlayer() && spellIncreasesSkill(spell))
|
||||
mCaster.getClass().skillUsageSucceeded(mCaster, spellSchoolToSkill(school), 0);
|
||||
|
||||
// A non-actor doesn't play its spell cast effects from a character controller, so play them here
|
||||
if (!mCaster.getClass().isActor())
|
||||
playSpellCastingEffects(mId, false);
|
||||
playSpellCastingEffects(spell->mEffects.mList);
|
||||
|
||||
inflict(mCaster, mCaster, spell->mEffects, ESM::RT_Self);
|
||||
|
||||
|
@ -1145,10 +766,8 @@ namespace MWMechanics
|
|||
effect.mRange = ESM::RT_Self;
|
||||
effect.mArea = 0;
|
||||
|
||||
const ESM::MagicEffect *magicEffect =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
|
||||
effect.mEffectID);
|
||||
|
||||
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
|
||||
const auto magicEffect = store.get<ESM::MagicEffect>().find(effect.mEffectID);
|
||||
const MWMechanics::CreatureStats& creatureStats = mCaster.getClass().getCreatureStats(mCaster);
|
||||
|
||||
float x = (mCaster.getClass().getSkill(mCaster, ESM::Skill::Alchemy) +
|
||||
|
@ -1160,7 +779,7 @@ namespace MWMechanics
|
|||
if (roll > x)
|
||||
{
|
||||
// "X has no effect on you"
|
||||
std::string message = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("sNotifyMessage50")->mValue.getString();
|
||||
std::string message = store.get<ESM::GameSetting>().find("sNotifyMessage50")->mValue.getString();
|
||||
message = Misc::StringUtils::format(message, ingredient->mName);
|
||||
MWBase::Environment::get().getWindowManager()->messageBox(message);
|
||||
return false;
|
||||
|
@ -1200,14 +819,13 @@ namespace MWMechanics
|
|||
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
|
||||
if (enchantment)
|
||||
{
|
||||
const ESM::Enchantment *spell = store.get<ESM::Enchantment>().find(spellid);
|
||||
playSpellCastingEffects(spell->mEffects.mList);
|
||||
|
||||
if (const auto spell = store.get<ESM::Enchantment>().search(spellid))
|
||||
playSpellCastingEffects(spell->mEffects.mList);
|
||||
}
|
||||
else
|
||||
{
|
||||
const ESM::Spell *spell = store.get<ESM::Spell>().find(spellid);
|
||||
playSpellCastingEffects(spell->mEffects.mList);
|
||||
if (const auto spell = store.get<ESM::Spell>().search(spellid))
|
||||
playSpellCastingEffects(spell->mEffects.mList);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1215,12 +833,9 @@ namespace MWMechanics
|
|||
{
|
||||
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
|
||||
std::vector<std::string> addedEffects;
|
||||
for (std::vector<ESM::ENAMstruct>::const_iterator iter = effects.begin(); iter != effects.end(); ++iter)
|
||||
for (const ESM::ENAMstruct& effectData : effects)
|
||||
{
|
||||
const ESM::MagicEffect *effect;
|
||||
effect = store.get<ESM::MagicEffect>().find(iter->mEffectID);
|
||||
|
||||
MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(mCaster);
|
||||
const auto effect = store.get<ESM::MagicEffect>().find(effectData.mEffectID);
|
||||
|
||||
const ESM::Static* castStatic;
|
||||
|
||||
|
@ -1233,13 +848,10 @@ namespace MWMechanics
|
|||
if (std::find(addedEffects.begin(), addedEffects.end(), "meshes\\" + castStatic->mModel) != addedEffects.end())
|
||||
continue;
|
||||
|
||||
std::string texture = effect->mParticle;
|
||||
|
||||
osg::Vec3f pos (mCaster.getRefData().getPosition().asVec3());
|
||||
|
||||
MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(mCaster);
|
||||
if (animation)
|
||||
{
|
||||
animation->addEffect("meshes\\" + castStatic->mModel, effect->mIndex, false, "", texture);
|
||||
animation->addEffect("meshes\\" + castStatic->mModel, effect->mIndex, false, "", effect->mParticle);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1248,6 +860,7 @@ namespace MWMechanics
|
|||
osg::Vec3f bounds (MWBase::Environment::get().getWorld()->getHalfExtents(mCaster) * 2.f / Constants::UnitsPerFoot);
|
||||
float scale = std::max({ bounds.x()/3.f, bounds.y()/3.f, bounds.z()/6.f });
|
||||
float meshScale = !mCaster.getClass().isActor() ? mCaster.getCellRef().getScale() : 1.0f;
|
||||
osg::Vec3f pos (mCaster.getRefData().getPosition().asVec3());
|
||||
MWBase::Environment::get().getWorld()->spawnEffect("meshes\\" + castStatic->mModel, effect->mParticle, pos, scale * meshScale);
|
||||
}
|
||||
|
||||
|
@ -1267,308 +880,4 @@ namespace MWMechanics
|
|||
sndMgr->playSound3D(mCaster, schools[effect->mData.mSchool]+" cast", 1.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
bool CastSpell::spellIncreasesSkill()
|
||||
{
|
||||
if (mManualSpell)
|
||||
return false;
|
||||
|
||||
return MWMechanics::spellIncreasesSkill(mId);
|
||||
}
|
||||
|
||||
int getEffectiveEnchantmentCastCost(float castCost, const MWWorld::Ptr &actor)
|
||||
{
|
||||
/*
|
||||
* Each point of enchant skill above/under 10 subtracts/adds
|
||||
* one percent of enchantment cost while minimum is 1.
|
||||
*/
|
||||
int eSkill = actor.getClass().getSkill(actor, ESM::Skill::Enchant);
|
||||
const float result = castCost - (castCost / 100) * (eSkill - 10);
|
||||
|
||||
return static_cast<int>((result < 1) ? 1 : result);
|
||||
}
|
||||
|
||||
bool isSummoningEffect(int effectId)
|
||||
{
|
||||
return ((effectId >= ESM::MagicEffect::SummonScamp
|
||||
&& effectId <= ESM::MagicEffect::SummonStormAtronach)
|
||||
|| effectId == ESM::MagicEffect::SummonCenturionSphere
|
||||
|| (effectId >= ESM::MagicEffect::SummonFabricant
|
||||
&& effectId <= ESM::MagicEffect::SummonCreature05));
|
||||
}
|
||||
|
||||
bool disintegrateSlot (MWWorld::Ptr ptr, int slot, float disintegrate)
|
||||
{
|
||||
if (ptr.getClass().hasInventoryStore(ptr))
|
||||
{
|
||||
MWWorld::InventoryStore& inv = ptr.getClass().getInventoryStore(ptr);
|
||||
MWWorld::ContainerStoreIterator item =
|
||||
inv.getSlot(slot);
|
||||
|
||||
if (item != inv.end() && (item.getType() == MWWorld::ContainerStore::Type_Armor || item.getType() == MWWorld::ContainerStore::Type_Weapon))
|
||||
{
|
||||
if (!item->getClass().hasItemHealth(*item))
|
||||
return false;
|
||||
int charge = item->getClass().getItemHealth(*item);
|
||||
|
||||
if (charge == 0)
|
||||
return false;
|
||||
|
||||
// Store remainder of disintegrate amount (automatically subtracted if > 1)
|
||||
item->getCellRef().applyChargeRemainderToBeSubtracted(disintegrate - std::floor(disintegrate));
|
||||
|
||||
charge = item->getClass().getItemHealth(*item);
|
||||
charge -=
|
||||
std::min(static_cast<int>(disintegrate),
|
||||
charge);
|
||||
item->getCellRef().setCharge(charge);
|
||||
|
||||
if (charge == 0)
|
||||
{
|
||||
// Will unequip the broken item and try to find a replacement
|
||||
if (ptr != getPlayer())
|
||||
inv.autoEquip(ptr);
|
||||
else
|
||||
inv.unequipItem(*item, ptr);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void adjustDynamicStat(CreatureStats& creatureStats, int index, float magnitude, bool allowDecreaseBelowZero = false)
|
||||
{
|
||||
DynamicStat<float> stat = creatureStats.getDynamic(index);
|
||||
stat.setCurrent(stat.getCurrent() + magnitude, allowDecreaseBelowZero);
|
||||
creatureStats.setDynamic(index, stat);
|
||||
}
|
||||
|
||||
bool effectTick(CreatureStats& creatureStats, const MWWorld::Ptr& actor, const EffectKey &effectKey, float magnitude)
|
||||
{
|
||||
if (magnitude == 0.f)
|
||||
return false;
|
||||
|
||||
bool receivedMagicDamage = false;
|
||||
|
||||
switch (effectKey.mId)
|
||||
{
|
||||
case ESM::MagicEffect::DamageAttribute:
|
||||
{
|
||||
AttributeValue attr = creatureStats.getAttribute(effectKey.mArg);
|
||||
attr.damage(magnitude);
|
||||
creatureStats.setAttribute(effectKey.mArg, attr);
|
||||
break;
|
||||
}
|
||||
case ESM::MagicEffect::RestoreAttribute:
|
||||
{
|
||||
AttributeValue attr = creatureStats.getAttribute(effectKey.mArg);
|
||||
attr.restore(magnitude);
|
||||
creatureStats.setAttribute(effectKey.mArg, attr);
|
||||
break;
|
||||
}
|
||||
case ESM::MagicEffect::RestoreHealth:
|
||||
case ESM::MagicEffect::RestoreMagicka:
|
||||
case ESM::MagicEffect::RestoreFatigue:
|
||||
adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::RestoreHealth, magnitude);
|
||||
break;
|
||||
case ESM::MagicEffect::DamageHealth:
|
||||
receivedMagicDamage = true;
|
||||
adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::DamageHealth, -magnitude);
|
||||
break;
|
||||
|
||||
case ESM::MagicEffect::DamageMagicka:
|
||||
case ESM::MagicEffect::DamageFatigue:
|
||||
{
|
||||
int index = effectKey.mId-ESM::MagicEffect::DamageHealth;
|
||||
static const bool uncappedDamageFatigue = Settings::Manager::getBool("uncapped damage fatigue", "Game");
|
||||
adjustDynamicStat(creatureStats, index, -magnitude, index == 2 && uncappedDamageFatigue);
|
||||
break;
|
||||
}
|
||||
case ESM::MagicEffect::AbsorbHealth:
|
||||
if (magnitude > 0.f)
|
||||
receivedMagicDamage = true;
|
||||
adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::AbsorbHealth, -magnitude);
|
||||
|
||||
break;
|
||||
|
||||
case ESM::MagicEffect::AbsorbMagicka:
|
||||
case ESM::MagicEffect::AbsorbFatigue:
|
||||
adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::AbsorbHealth, -magnitude);
|
||||
break;
|
||||
|
||||
case ESM::MagicEffect::DisintegrateArmor:
|
||||
{
|
||||
// According to UESP
|
||||
int priorities[] = {
|
||||
MWWorld::InventoryStore::Slot_CarriedLeft,
|
||||
MWWorld::InventoryStore::Slot_Cuirass,
|
||||
MWWorld::InventoryStore::Slot_LeftPauldron,
|
||||
MWWorld::InventoryStore::Slot_RightPauldron,
|
||||
MWWorld::InventoryStore::Slot_LeftGauntlet,
|
||||
MWWorld::InventoryStore::Slot_RightGauntlet,
|
||||
MWWorld::InventoryStore::Slot_Helmet,
|
||||
MWWorld::InventoryStore::Slot_Greaves,
|
||||
MWWorld::InventoryStore::Slot_Boots
|
||||
};
|
||||
|
||||
for (unsigned int i=0; i<sizeof(priorities)/sizeof(int); ++i)
|
||||
{
|
||||
if (disintegrateSlot(actor, priorities[i], magnitude))
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case ESM::MagicEffect::DisintegrateWeapon:
|
||||
disintegrateSlot(actor, MWWorld::InventoryStore::Slot_CarriedRight, magnitude);
|
||||
break;
|
||||
|
||||
case ESM::MagicEffect::SunDamage:
|
||||
{
|
||||
// isInCell shouldn't be needed, but updateActor called during game start
|
||||
if (!actor.isInCell() || !actor.getCell()->isExterior())
|
||||
break;
|
||||
float time = MWBase::Environment::get().getWorld()->getTimeStamp().getHour();
|
||||
float timeDiff = std::min(7.f, std::max(0.f, std::abs(time - 13)));
|
||||
float damageScale = 1.f - timeDiff / 7.f;
|
||||
// When cloudy, the sun damage effect is halved
|
||||
static float fMagicSunBlockedMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
|
||||
"fMagicSunBlockedMult")->mValue.getFloat();
|
||||
|
||||
int weather = MWBase::Environment::get().getWorld()->getCurrentWeather();
|
||||
if (weather > 1)
|
||||
damageScale *= fMagicSunBlockedMult;
|
||||
|
||||
adjustDynamicStat(creatureStats, 0, -magnitude * damageScale);
|
||||
if (magnitude * damageScale > 0.f)
|
||||
receivedMagicDamage = true;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case ESM::MagicEffect::FireDamage:
|
||||
case ESM::MagicEffect::ShockDamage:
|
||||
case ESM::MagicEffect::FrostDamage:
|
||||
case ESM::MagicEffect::Poison:
|
||||
{
|
||||
adjustDynamicStat(creatureStats, 0, -magnitude);
|
||||
receivedMagicDamage = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case ESM::MagicEffect::DamageSkill:
|
||||
case ESM::MagicEffect::RestoreSkill:
|
||||
{
|
||||
if (!actor.getClass().isNpc())
|
||||
break;
|
||||
NpcStats &npcStats = actor.getClass().getNpcStats(actor);
|
||||
SkillValue& skill = npcStats.getSkill(effectKey.mArg);
|
||||
if (effectKey.mId == ESM::MagicEffect::RestoreSkill)
|
||||
skill.restore(magnitude);
|
||||
else
|
||||
skill.damage(magnitude);
|
||||
break;
|
||||
}
|
||||
|
||||
case ESM::MagicEffect::CurePoison:
|
||||
actor.getClass().getCreatureStats(actor).getActiveSpells().purgeEffect(ESM::MagicEffect::Poison);
|
||||
break;
|
||||
case ESM::MagicEffect::CureParalyzation:
|
||||
actor.getClass().getCreatureStats(actor).getActiveSpells().purgeEffect(ESM::MagicEffect::Paralyze);
|
||||
break;
|
||||
case ESM::MagicEffect::CureCommonDisease:
|
||||
actor.getClass().getCreatureStats(actor).getSpells().purgeCommonDisease();
|
||||
break;
|
||||
case ESM::MagicEffect::CureBlightDisease:
|
||||
actor.getClass().getCreatureStats(actor).getSpells().purgeBlightDisease();
|
||||
break;
|
||||
case ESM::MagicEffect::CureCorprusDisease:
|
||||
actor.getClass().getCreatureStats(actor).getSpells().purgeCorprusDisease();
|
||||
break;
|
||||
case ESM::MagicEffect::RemoveCurse:
|
||||
actor.getClass().getCreatureStats(actor).getSpells().purgeCurses();
|
||||
break;
|
||||
/*
|
||||
Start of tes3mp addition
|
||||
|
||||
Don't apply paralysis to DedicatedPlayers and DedicatedActors unilterally on this client
|
||||
*/
|
||||
case ESM::MagicEffect::Paralyze:
|
||||
{
|
||||
if (mwmp::PlayerList::isDedicatedPlayer(actor) || mwmp::Main::get().getCellController()->isDedicatedActor(actor))
|
||||
{
|
||||
actor.getClass().getCreatureStats(actor).getActiveSpells().purgeEffect(ESM::MagicEffect::Paralyze);
|
||||
break;
|
||||
}
|
||||
}
|
||||
/*
|
||||
End of tes3mp addition
|
||||
*/
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
if (receivedMagicDamage && actor == getPlayer())
|
||||
MWBase::Environment::get().getWindowManager()->activateHitOverlay(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string getSummonedCreature(int effectId)
|
||||
{
|
||||
static std::map<int, std::string> summonMap;
|
||||
if (summonMap.empty())
|
||||
{
|
||||
summonMap[ESM::MagicEffect::SummonAncestralGhost] = "sMagicAncestralGhostID";
|
||||
summonMap[ESM::MagicEffect::SummonBonelord] = "sMagicBonelordID";
|
||||
summonMap[ESM::MagicEffect::SummonBonewalker] = "sMagicLeastBonewalkerID";
|
||||
summonMap[ESM::MagicEffect::SummonCenturionSphere] = "sMagicCenturionSphereID";
|
||||
summonMap[ESM::MagicEffect::SummonClannfear] = "sMagicClannfearID";
|
||||
summonMap[ESM::MagicEffect::SummonDaedroth] = "sMagicDaedrothID";
|
||||
summonMap[ESM::MagicEffect::SummonDremora] = "sMagicDremoraID";
|
||||
summonMap[ESM::MagicEffect::SummonFabricant] = "sMagicFabricantID";
|
||||
summonMap[ESM::MagicEffect::SummonFlameAtronach] = "sMagicFlameAtronachID";
|
||||
summonMap[ESM::MagicEffect::SummonFrostAtronach] = "sMagicFrostAtronachID";
|
||||
summonMap[ESM::MagicEffect::SummonGoldenSaint] = "sMagicGoldenSaintID";
|
||||
summonMap[ESM::MagicEffect::SummonGreaterBonewalker] = "sMagicGreaterBonewalkerID";
|
||||
summonMap[ESM::MagicEffect::SummonHunger] = "sMagicHungerID";
|
||||
summonMap[ESM::MagicEffect::SummonScamp] = "sMagicScampID";
|
||||
summonMap[ESM::MagicEffect::SummonSkeletalMinion] = "sMagicSkeletalMinionID";
|
||||
summonMap[ESM::MagicEffect::SummonStormAtronach] = "sMagicStormAtronachID";
|
||||
summonMap[ESM::MagicEffect::SummonWingedTwilight] = "sMagicWingedTwilightID";
|
||||
summonMap[ESM::MagicEffect::SummonWolf] = "sMagicCreature01ID";
|
||||
summonMap[ESM::MagicEffect::SummonBear] = "sMagicCreature02ID";
|
||||
summonMap[ESM::MagicEffect::SummonBonewolf] = "sMagicCreature03ID";
|
||||
summonMap[ESM::MagicEffect::SummonCreature04] = "sMagicCreature04ID";
|
||||
summonMap[ESM::MagicEffect::SummonCreature05] = "sMagicCreature05ID";
|
||||
}
|
||||
|
||||
std::map<int, std::string>::const_iterator it = summonMap.find(effectId);
|
||||
if (it == summonMap.end())
|
||||
return std::string();
|
||||
else
|
||||
return MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(it->second)->mValue.getString();
|
||||
}
|
||||
|
||||
void ApplyLoopingParticlesVisitor::visit (MWMechanics::EffectKey key,
|
||||
const std::string& /*sourceName*/, const std::string& /*sourceId*/, int /*casterActorId*/,
|
||||
float /*magnitude*/, float /*remainingTime*/, float /*totalTime*/)
|
||||
{
|
||||
const ESM::MagicEffect *magicEffect =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find(key.mId);
|
||||
|
||||
const ESM::Static* castStatic;
|
||||
if (!magicEffect->mHit.empty())
|
||||
castStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find (magicEffect->mHit);
|
||||
else
|
||||
castStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find ("VFX_DefaultHit");
|
||||
|
||||
std::string texture = magicEffect->mParticle;
|
||||
|
||||
bool loop = (magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx) != 0;
|
||||
MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(mActor);
|
||||
if (anim && loop)
|
||||
anim->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, "", texture);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
#ifndef MWMECHANICS_SPELLSUCCESS_H
|
||||
#define MWMECHANICS_SPELLSUCCESS_H
|
||||
#ifndef MWMECHANICS_SPELLCASTING_H
|
||||
#define MWMECHANICS_SPELLCASTING_H
|
||||
|
||||
#include <components/esm/effectlist.hpp>
|
||||
#include <components/esm/loadskil.hpp>
|
||||
#include <components/esm/loadmgef.hpp>
|
||||
|
||||
#include "../mwworld/ptr.hpp"
|
||||
|
||||
#include "magiceffects.hpp"
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
struct Spell;
|
||||
|
@ -20,63 +17,6 @@ namespace ESM
|
|||
namespace MWMechanics
|
||||
{
|
||||
struct EffectKey;
|
||||
class MagicEffects;
|
||||
class CreatureStats;
|
||||
|
||||
ESM::Skill::SkillEnum spellSchoolToSkill(int school);
|
||||
|
||||
float calcEffectCost(const ESM::ENAMstruct& effect);
|
||||
float calcEffectCost(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect);
|
||||
|
||||
bool isSummoningEffect(int effectId);
|
||||
|
||||
/**
|
||||
* @param spell spell to cast
|
||||
* @param actor calculate spell success chance for this actor (depends on actor's skills)
|
||||
* @param effectiveSchool the spell's effective school (relevant for skill progress) will be written here
|
||||
* @param cap cap the result to 100%?
|
||||
* @param checkMagicka check magicka?
|
||||
* @note actor can be an NPC or a creature
|
||||
* @return success chance from 0 to 100 (in percent), if cap=false then chance above 100 may be returned.
|
||||
*/
|
||||
float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool = nullptr, bool cap=true, bool checkMagicka=true);
|
||||
float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool = nullptr, bool cap=true, bool checkMagicka=true);
|
||||
|
||||
int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor);
|
||||
int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor);
|
||||
|
||||
/// Get whether or not the given spell contributes to skill progress.
|
||||
bool spellIncreasesSkill(const ESM::Spell* spell);
|
||||
bool spellIncreasesSkill(const std::string& spellId);
|
||||
|
||||
/// Get the resistance attribute against an effect for a given actor. This will add together
|
||||
/// ResistX and Weakness to X effects relevant against the given effect.
|
||||
float getEffectResistanceAttribute (short effectId, const MagicEffects* actorEffects);
|
||||
|
||||
/// Get the effective resistance against an effect casted by the given actor in the given spell (optional).
|
||||
/// @return >=100 for fully resisted. can also return negative value for damage amplification.
|
||||
/// @param effects Override the actor's current magicEffects. Useful if there are effects currently
|
||||
/// being applied (but not applied yet) that should also be considered.
|
||||
float getEffectResistance (short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster,
|
||||
const ESM::Spell* spell = nullptr, const MagicEffects* effects = nullptr);
|
||||
|
||||
/// Get an effect multiplier for applying an effect cast by the given actor in the given spell (optional).
|
||||
/// @return effect multiplier from 0 to 2. (100% net resistance to 100% net weakness)
|
||||
/// @param effects Override the actor's current magicEffects. Useful if there are effects currently
|
||||
/// being applied (but not applied yet) that should also be considered.
|
||||
float getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster,
|
||||
const ESM::Spell* spell = nullptr, const MagicEffects* effects = nullptr);
|
||||
|
||||
bool checkEffectTarget (int effectId, const MWWorld::Ptr& target, const MWWorld::Ptr& caster, bool castByPlayer);
|
||||
|
||||
int getEffectiveEnchantmentCastCost (float castCost, const MWWorld::Ptr& actor);
|
||||
float calcSpellBaseSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool);
|
||||
|
||||
/// Apply a magic effect that is applied in tick intervals until its remaining time ends or it is removed
|
||||
/// @return Was the effect a tickable effect with a magnitude?
|
||||
bool effectTick(CreatureStats& creatureStats, const MWWorld::Ptr& actor, const MWMechanics::EffectKey& effectKey, float magnitude);
|
||||
|
||||
std::string getSummonedCreature(int effectId);
|
||||
|
||||
class CastSpell
|
||||
{
|
||||
|
@ -87,11 +27,11 @@ namespace MWMechanics
|
|||
void playSpellCastingEffects(const std::vector<ESM::ENAMstruct>& effects);
|
||||
|
||||
public:
|
||||
bool mStack;
|
||||
bool mStack{false};
|
||||
std::string mId; // ID of spell, potion, item etc
|
||||
std::string mSourceName; // Display name for spell, potion, etc
|
||||
osg::Vec3f mHitPosition; // Used for spawning area orb
|
||||
bool mAlwaysSucceed; // Always succeed spells casted by NPCs/creatures regardless of their chance (default: false)
|
||||
osg::Vec3f mHitPosition{0,0,0}; // Used for spawning area orb
|
||||
bool mAlwaysSucceed{false}; // Always succeed spells casted by NPCs/creatures regardless of their chance (default: false)
|
||||
bool mFromProjectile; // True if spell is cast by enchantment of some projectile (arrow, bolt or thrown weapon)
|
||||
bool mManualSpell; // True if spell is casted from script and ignores some checks (mana level, success chance, etc.)
|
||||
|
||||
|
@ -114,8 +54,6 @@ namespace MWMechanics
|
|||
|
||||
void playSpellCastingEffects(const std::string &spellid, bool enchantment);
|
||||
|
||||
bool spellIncreasesSkill();
|
||||
|
||||
/// Launch a bolt with the given effects.
|
||||
void launchMagicBolt ();
|
||||
|
||||
|
@ -128,22 +66,6 @@ namespace MWMechanics
|
|||
/// @return was the target suitable for the effect?
|
||||
bool applyInstantEffect (const MWWorld::Ptr& target, const MWWorld::Ptr& caster, const MWMechanics::EffectKey& effect, float magnitude);
|
||||
};
|
||||
|
||||
class ApplyLoopingParticlesVisitor : public EffectSourceVisitor
|
||||
{
|
||||
private:
|
||||
MWWorld::Ptr mActor;
|
||||
|
||||
public:
|
||||
ApplyLoopingParticlesVisitor(const MWWorld::Ptr& actor)
|
||||
: mActor(actor)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void visit (MWMechanics::EffectKey key,
|
||||
const std::string& /*sourceName*/, const std::string& /*sourceId*/, int /*casterActorId*/,
|
||||
float /*magnitude*/, float /*remainingTime*/ = -1, float /*totalTime*/ = -1);
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -15,9 +15,11 @@
|
|||
#include "../mwworld/cellstore.hpp"
|
||||
|
||||
#include "creaturestats.hpp"
|
||||
#include "spellcasting.hpp"
|
||||
#include "spellresistance.hpp"
|
||||
#include "weapontype.hpp"
|
||||
#include "combat.hpp"
|
||||
#include "summoning.hpp"
|
||||
#include "spellutil.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
|
93
apps/openmw/mwmechanics/spellresistance.cpp
Normal file
93
apps/openmw/mwmechanics/spellresistance.cpp
Normal file
|
@ -0,0 +1,93 @@
|
|||
#include "spellresistance.hpp"
|
||||
|
||||
#include <components/misc/rng.hpp>
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
|
||||
#include "creaturestats.hpp"
|
||||
#include "spellutil.hpp"
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
|
||||
float getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster,
|
||||
const ESM::Spell* spell, const MagicEffects* effects)
|
||||
{
|
||||
if (!actor.getClass().isActor())
|
||||
return 1;
|
||||
|
||||
float resistance = getEffectResistance(effectId, actor, caster, spell, effects);
|
||||
return 1 - resistance / 100.f;
|
||||
}
|
||||
|
||||
float getEffectResistance (short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster,
|
||||
const ESM::Spell* spell, const MagicEffects* effects)
|
||||
{
|
||||
// Effects with no resistance attribute belonging to them can not be resisted
|
||||
if (ESM::MagicEffect::getResistanceEffect(effectId) == -1)
|
||||
return 0.f;
|
||||
|
||||
const auto magicEffect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find(effectId);
|
||||
|
||||
const MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor);
|
||||
const MWMechanics::MagicEffects* magicEffects = &stats.getMagicEffects();
|
||||
if (effects)
|
||||
magicEffects = effects;
|
||||
|
||||
float resistance = getEffectResistanceAttribute(effectId, magicEffects);
|
||||
|
||||
int willpower = stats.getAttribute(ESM::Attribute::Willpower).getModified();
|
||||
float luck = static_cast<float>(stats.getAttribute(ESM::Attribute::Luck).getModified());
|
||||
float x = (willpower + 0.1f * luck) * stats.getFatigueTerm();
|
||||
|
||||
// This makes spells that are easy to cast harder to resist and vice versa
|
||||
float castChance = 100.f;
|
||||
if (spell != nullptr && !caster.isEmpty() && caster.getClass().isActor())
|
||||
castChance = getSpellSuccessChance(spell, caster, nullptr, false, false); // Uncapped casting chance
|
||||
if (castChance > 0)
|
||||
x *= 50 / castChance;
|
||||
|
||||
float roll = Misc::Rng::rollClosedProbability() * 100;
|
||||
if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)
|
||||
roll -= resistance;
|
||||
|
||||
if (x <= roll)
|
||||
x = 0;
|
||||
else
|
||||
{
|
||||
if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)
|
||||
x = 100;
|
||||
else
|
||||
x = roll / std::min(x, 100.f);
|
||||
}
|
||||
|
||||
x = std::min(x + resistance, 100.f);
|
||||
return x;
|
||||
}
|
||||
|
||||
float getEffectResistanceAttribute (short effectId, const MagicEffects* actorEffects)
|
||||
{
|
||||
short resistanceEffect = ESM::MagicEffect::getResistanceEffect(effectId);
|
||||
short weaknessEffect = ESM::MagicEffect::getWeaknessEffect(effectId);
|
||||
|
||||
float resistance = 0;
|
||||
if (resistanceEffect != -1)
|
||||
resistance += actorEffects->get(resistanceEffect).getMagnitude();
|
||||
if (weaknessEffect != -1)
|
||||
resistance -= actorEffects->get(weaknessEffect).getMagnitude();
|
||||
|
||||
if (effectId == ESM::MagicEffect::FireDamage)
|
||||
resistance += actorEffects->get(ESM::MagicEffect::FireShield).getMagnitude();
|
||||
if (effectId == ESM::MagicEffect::ShockDamage)
|
||||
resistance += actorEffects->get(ESM::MagicEffect::LightningShield).getMagnitude();
|
||||
if (effectId == ESM::MagicEffect::FrostDamage)
|
||||
resistance += actorEffects->get(ESM::MagicEffect::FrostShield).getMagnitude();
|
||||
|
||||
return resistance;
|
||||
}
|
||||
|
||||
}
|
37
apps/openmw/mwmechanics/spellresistance.hpp
Normal file
37
apps/openmw/mwmechanics/spellresistance.hpp
Normal file
|
@ -0,0 +1,37 @@
|
|||
#ifndef MWMECHANICS_SPELLRESISTANCE_H
|
||||
#define MWMECHANICS_SPELLRESISTANCE_H
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
struct Spell;
|
||||
}
|
||||
|
||||
namespace MWWorld
|
||||
{
|
||||
class Ptr;
|
||||
}
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
class MagicEffects;
|
||||
|
||||
/// Get an effect multiplier for applying an effect cast by the given actor in the given spell (optional).
|
||||
/// @return effect multiplier from 0 to 2. (100% net resistance to 100% net weakness)
|
||||
/// @param effects Override the actor's current magicEffects. Useful if there are effects currently
|
||||
/// being applied (but not applied yet) that should also be considered.
|
||||
float getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster,
|
||||
const ESM::Spell* spell = nullptr, const MagicEffects* effects = nullptr);
|
||||
|
||||
/// Get the effective resistance against an effect casted by the given actor in the given spell (optional).
|
||||
/// @return >=100 for fully resisted. can also return negative value for damage amplification.
|
||||
/// @param effects Override the actor's current magicEffects. Useful if there are effects currently
|
||||
/// being applied (but not applied yet) that should also be considered.
|
||||
float getEffectResistance (short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster,
|
||||
const ESM::Spell* spell = nullptr, const MagicEffects* effects = nullptr);
|
||||
|
||||
/// Get the resistance attribute against an effect for a given actor. This will add together
|
||||
/// ResistX and Weakness to X effects relevant against the given effect.
|
||||
float getEffectResistanceAttribute (short effectId, const MagicEffects* actorEffects);
|
||||
}
|
||||
|
||||
#endif
|
208
apps/openmw/mwmechanics/spellutil.cpp
Normal file
208
apps/openmw/mwmechanics/spellutil.cpp
Normal file
|
@ -0,0 +1,208 @@
|
|||
#include "spellutil.hpp"
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/windowmanager.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
|
||||
#include "actorutil.hpp"
|
||||
#include "creaturestats.hpp"
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
ESM::Skill::SkillEnum spellSchoolToSkill(int school)
|
||||
{
|
||||
static const std::array<ESM::Skill::SkillEnum, 6> schoolSkillArray
|
||||
{
|
||||
ESM::Skill::Alteration, ESM::Skill::Conjuration, ESM::Skill::Destruction,
|
||||
ESM::Skill::Illusion, ESM::Skill::Mysticism, ESM::Skill::Restoration
|
||||
};
|
||||
return schoolSkillArray.at(school);
|
||||
}
|
||||
|
||||
float calcEffectCost(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect)
|
||||
{
|
||||
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
|
||||
if (!magicEffect)
|
||||
magicEffect = store.get<ESM::MagicEffect>().find(effect.mEffectID);
|
||||
bool hasMagnitude = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude);
|
||||
bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration);
|
||||
int minMagn = hasMagnitude ? effect.mMagnMin : 1;
|
||||
int maxMagn = hasMagnitude ? effect.mMagnMax : 1;
|
||||
int duration = hasDuration ? effect.mDuration : 1;
|
||||
static const float fEffectCostMult = store.get<ESM::GameSetting>().find("fEffectCostMult")->mValue.getFloat();
|
||||
|
||||
float x = 0.5 * (std::max(1, minMagn) + std::max(1, maxMagn));
|
||||
x *= 0.1 * magicEffect->mData.mBaseCost;
|
||||
x *= 1 + duration;
|
||||
x += 0.05 * std::max(1, effect.mArea) * magicEffect->mData.mBaseCost;
|
||||
|
||||
return x * fEffectCostMult;
|
||||
}
|
||||
|
||||
int getEffectiveEnchantmentCastCost(float castCost, const MWWorld::Ptr &actor)
|
||||
{
|
||||
/*
|
||||
* Each point of enchant skill above/under 10 subtracts/adds
|
||||
* one percent of enchantment cost while minimum is 1.
|
||||
*/
|
||||
int eSkill = actor.getClass().getSkill(actor, ESM::Skill::Enchant);
|
||||
const float result = castCost - (castCost / 100) * (eSkill - 10);
|
||||
|
||||
return static_cast<int>((result < 1) ? 1 : result);
|
||||
}
|
||||
|
||||
float calcSpellBaseSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool)
|
||||
{
|
||||
// Morrowind for some reason uses a formula slightly different from magicka cost calculation
|
||||
float y = std::numeric_limits<float>::max();
|
||||
float lowestSkill = 0;
|
||||
|
||||
for (const ESM::ENAMstruct& effect : spell->mEffects.mList)
|
||||
{
|
||||
float x = static_cast<float>(effect.mDuration);
|
||||
const auto magicEffect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find(effect.mEffectID);
|
||||
|
||||
if (!(magicEffect->mData.mFlags & ESM::MagicEffect::AppliedOnce))
|
||||
x = std::max(1.f, x);
|
||||
|
||||
x *= 0.1f * magicEffect->mData.mBaseCost;
|
||||
x *= 0.5f * (effect.mMagnMin + effect.mMagnMax);
|
||||
x += effect.mArea * 0.05f * magicEffect->mData.mBaseCost;
|
||||
if (effect.mRange == ESM::RT_Target)
|
||||
x *= 1.5f;
|
||||
static const float fEffectCostMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
|
||||
"fEffectCostMult")->mValue.getFloat();
|
||||
x *= fEffectCostMult;
|
||||
|
||||
float s = 2.0f * actor.getClass().getSkill(actor, spellSchoolToSkill(magicEffect->mData.mSchool));
|
||||
if (s - x < y)
|
||||
{
|
||||
y = s - x;
|
||||
if (effectiveSchool)
|
||||
*effectiveSchool = magicEffect->mData.mSchool;
|
||||
lowestSkill = s;
|
||||
}
|
||||
}
|
||||
|
||||
CreatureStats& stats = actor.getClass().getCreatureStats(actor);
|
||||
|
||||
int actorWillpower = stats.getAttribute(ESM::Attribute::Willpower).getModified();
|
||||
int actorLuck = stats.getAttribute(ESM::Attribute::Luck).getModified();
|
||||
|
||||
float castChance = (lowestSkill - spell->mData.mCost + 0.2f * actorWillpower + 0.1f * actorLuck);
|
||||
|
||||
return castChance;
|
||||
}
|
||||
|
||||
float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool, bool cap, bool checkMagicka)
|
||||
{
|
||||
bool godmode = actor == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState();
|
||||
|
||||
CreatureStats& stats = actor.getClass().getCreatureStats(actor);
|
||||
|
||||
if (stats.getMagicEffects().get(ESM::MagicEffect::Silence).getMagnitude() && !godmode)
|
||||
return 0;
|
||||
|
||||
if (spell->mData.mType == ESM::Spell::ST_Power)
|
||||
return stats.getSpells().canUsePower(spell) ? 100 : 0;
|
||||
|
||||
if (godmode)
|
||||
return 100;
|
||||
|
||||
if (spell->mData.mType != ESM::Spell::ST_Spell)
|
||||
return 100;
|
||||
|
||||
if (checkMagicka && stats.getMagicka().getCurrent() < spell->mData.mCost)
|
||||
return 0;
|
||||
|
||||
if (spell->mData.mFlags & ESM::Spell::F_Always)
|
||||
return 100;
|
||||
|
||||
float castBonus = -stats.getMagicEffects().get(ESM::MagicEffect::Sound).getMagnitude();
|
||||
float castChance = calcSpellBaseSuccessChance(spell, actor, effectiveSchool) + castBonus;
|
||||
castChance *= stats.getFatigueTerm();
|
||||
|
||||
return std::max(0.f, cap ? std::min(100.f, castChance) : castChance);
|
||||
}
|
||||
|
||||
float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool, bool cap, bool checkMagicka)
|
||||
{
|
||||
if (const auto spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search(spellId))
|
||||
return getSpellSuccessChance(spell, actor, effectiveSchool, cap, checkMagicka);
|
||||
return 0.f;
|
||||
}
|
||||
|
||||
int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor)
|
||||
{
|
||||
int school = 0;
|
||||
getSpellSuccessChance(spellId, actor, &school);
|
||||
return school;
|
||||
}
|
||||
|
||||
int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor)
|
||||
{
|
||||
int school = 0;
|
||||
getSpellSuccessChance(spell, actor, &school);
|
||||
return school;
|
||||
}
|
||||
|
||||
bool spellIncreasesSkill(const ESM::Spell *spell)
|
||||
{
|
||||
return spell->mData.mType == ESM::Spell::ST_Spell && !(spell->mData.mFlags & ESM::Spell::F_Always);
|
||||
}
|
||||
|
||||
bool spellIncreasesSkill(const std::string &spellId)
|
||||
{
|
||||
const auto spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search(spellId);
|
||||
return spell && spellIncreasesSkill(spell);
|
||||
}
|
||||
|
||||
bool checkEffectTarget (int effectId, const MWWorld::Ptr& target, const MWWorld::Ptr& caster, bool castByPlayer)
|
||||
{
|
||||
switch (effectId)
|
||||
{
|
||||
case ESM::MagicEffect::Levitate:
|
||||
{
|
||||
if (!MWBase::Environment::get().getWorld()->isLevitationEnabled())
|
||||
{
|
||||
if (castByPlayer)
|
||||
MWBase::Environment::get().getWindowManager()->messageBox("#{sLevitateDisabled}");
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESM::MagicEffect::Soultrap:
|
||||
{
|
||||
if (!target.getClass().isNpc() // no messagebox for NPCs
|
||||
&& (target.getTypeName() == typeid(ESM::Creature).name() && target.get<ESM::Creature>()->mBase->mData.mSoul == 0))
|
||||
{
|
||||
if (castByPlayer)
|
||||
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInvalidTarget}");
|
||||
return true; // must still apply to get visual effect and have target regard it as attack
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESM::MagicEffect::WaterWalking:
|
||||
{
|
||||
if (target.getClass().isPureWaterCreature(target) && MWBase::Environment::get().getWorld()->isSwimming(target))
|
||||
return false;
|
||||
|
||||
MWBase::World *world = MWBase::Environment::get().getWorld();
|
||||
|
||||
if (!world->isWaterWalkingCastableOnTarget(target))
|
||||
{
|
||||
if (castByPlayer && caster == target)
|
||||
MWBase::Environment::get().getWindowManager()->messageBox ("#{sMagicInvalidEffect}");
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
50
apps/openmw/mwmechanics/spellutil.hpp
Normal file
50
apps/openmw/mwmechanics/spellutil.hpp
Normal file
|
@ -0,0 +1,50 @@
|
|||
#ifndef MWMECHANICS_SPELLUTIL_H
|
||||
#define MWMECHANICS_SPELLUTIL_H
|
||||
|
||||
#include <components/esm/loadskil.hpp>
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
struct ENAMstruct;
|
||||
struct MagicEffect;
|
||||
struct Spell;
|
||||
}
|
||||
|
||||
namespace MWWorld
|
||||
{
|
||||
class Ptr;
|
||||
}
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
ESM::Skill::SkillEnum spellSchoolToSkill(int school);
|
||||
|
||||
float calcEffectCost(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect = nullptr);
|
||||
|
||||
int getEffectiveEnchantmentCastCost (float castCost, const MWWorld::Ptr& actor);
|
||||
|
||||
/**
|
||||
* @param spell spell to cast
|
||||
* @param actor calculate spell success chance for this actor (depends on actor's skills)
|
||||
* @param effectiveSchool the spell's effective school (relevant for skill progress) will be written here
|
||||
* @param cap cap the result to 100%?
|
||||
* @param checkMagicka check magicka?
|
||||
* @note actor can be an NPC or a creature
|
||||
* @return success chance from 0 to 100 (in percent), if cap=false then chance above 100 may be returned.
|
||||
*/
|
||||
float calcSpellBaseSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool);
|
||||
float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool = nullptr, bool cap=true, bool checkMagicka=true);
|
||||
float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool = nullptr, bool cap=true, bool checkMagicka=true);
|
||||
|
||||
int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor);
|
||||
int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor);
|
||||
|
||||
/// Get whether or not the given spell contributes to skill progress.
|
||||
bool spellIncreasesSkill(const ESM::Spell* spell);
|
||||
bool spellIncreasesSkill(const std::string& spellId);
|
||||
|
||||
/// Check if the given effect can be applied to the target. If \a castByPlayer, emits a message box on failure.
|
||||
bool checkEffectTarget (int effectId, const MWWorld::Ptr& target, const MWWorld::Ptr& caster, bool castByPlayer);
|
||||
}
|
||||
|
||||
#endif
|
|
@ -28,20 +28,55 @@
|
|||
|
||||
#include "../mwrender/animation.hpp"
|
||||
|
||||
#include "spellcasting.hpp"
|
||||
#include "creaturestats.hpp"
|
||||
#include "aifollow.hpp"
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
|
||||
UpdateSummonedCreatures::UpdateSummonedCreatures(const MWWorld::Ptr &actor)
|
||||
: mActor(actor)
|
||||
bool isSummoningEffect(int effectId)
|
||||
{
|
||||
|
||||
return ((effectId >= ESM::MagicEffect::SummonScamp && effectId <= ESM::MagicEffect::SummonStormAtronach)
|
||||
|| (effectId == ESM::MagicEffect::SummonCenturionSphere)
|
||||
|| (effectId >= ESM::MagicEffect::SummonFabricant && effectId <= ESM::MagicEffect::SummonCreature05));
|
||||
}
|
||||
|
||||
UpdateSummonedCreatures::~UpdateSummonedCreatures()
|
||||
std::string getSummonedCreature(int effectId)
|
||||
{
|
||||
static const std::map<int, std::string> summonMap
|
||||
{
|
||||
{ESM::MagicEffect::SummonAncestralGhost, "sMagicAncestralGhostID"},
|
||||
{ESM::MagicEffect::SummonBonelord, "sMagicBonelordID"},
|
||||
{ESM::MagicEffect::SummonBonewalker, "sMagicLeastBonewalkerID"},
|
||||
{ESM::MagicEffect::SummonCenturionSphere, "sMagicCenturionSphereID"},
|
||||
{ESM::MagicEffect::SummonClannfear, "sMagicClannfearID"},
|
||||
{ESM::MagicEffect::SummonDaedroth, "sMagicDaedrothID"},
|
||||
{ESM::MagicEffect::SummonDremora, "sMagicDremoraID"},
|
||||
{ESM::MagicEffect::SummonFabricant, "sMagicFabricantID"},
|
||||
{ESM::MagicEffect::SummonFlameAtronach, "sMagicFlameAtronachID"},
|
||||
{ESM::MagicEffect::SummonFrostAtronach, "sMagicFrostAtronachID"},
|
||||
{ESM::MagicEffect::SummonGoldenSaint, "sMagicGoldenSaintID"},
|
||||
{ESM::MagicEffect::SummonGreaterBonewalker, "sMagicGreaterBonewalkerID"},
|
||||
{ESM::MagicEffect::SummonHunger, "sMagicHungerID"},
|
||||
{ESM::MagicEffect::SummonScamp, "sMagicScampID"},
|
||||
{ESM::MagicEffect::SummonSkeletalMinion, "sMagicSkeletalMinionID"},
|
||||
{ESM::MagicEffect::SummonStormAtronach, "sMagicStormAtronachID"},
|
||||
{ESM::MagicEffect::SummonWingedTwilight, "sMagicWingedTwilightID"},
|
||||
{ESM::MagicEffect::SummonWolf, "sMagicCreature01ID"},
|
||||
{ESM::MagicEffect::SummonBear, "sMagicCreature02ID"},
|
||||
{ESM::MagicEffect::SummonBonewolf, "sMagicCreature03ID"},
|
||||
{ESM::MagicEffect::SummonCreature04, "sMagicCreature04ID"},
|
||||
{ESM::MagicEffect::SummonCreature05, "sMagicCreature05ID"}
|
||||
};
|
||||
|
||||
auto it = summonMap.find(effectId);
|
||||
if (it != summonMap.end())
|
||||
return MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(it->second)->mValue.getString();
|
||||
return std::string();
|
||||
}
|
||||
|
||||
UpdateSummonedCreatures::UpdateSummonedCreatures(const MWWorld::Ptr &actor)
|
||||
: mActor(actor)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -9,13 +9,16 @@
|
|||
|
||||
namespace MWMechanics
|
||||
{
|
||||
|
||||
class CreatureStats;
|
||||
|
||||
bool isSummoningEffect(int effectId);
|
||||
|
||||
std::string getSummonedCreature(int effectId);
|
||||
|
||||
struct UpdateSummonedCreatures : public EffectSourceVisitor
|
||||
{
|
||||
UpdateSummonedCreatures(const MWWorld::Ptr& actor);
|
||||
virtual ~UpdateSummonedCreatures();
|
||||
virtual ~UpdateSummonedCreatures() = default;
|
||||
|
||||
virtual void visit (MWMechanics::EffectKey key,
|
||||
const std::string& sourceName, const std::string& sourceId, int casterActorId,
|
||||
|
|
245
apps/openmw/mwmechanics/tickableeffects.cpp
Normal file
245
apps/openmw/mwmechanics/tickableeffects.cpp
Normal file
|
@ -0,0 +1,245 @@
|
|||
#include "tickableeffects.hpp"
|
||||
|
||||
#include <components/settings/settings.hpp>
|
||||
|
||||
/*
|
||||
Start of tes3mp addition
|
||||
|
||||
Include additional headers for multiplayer purposes
|
||||
*/
|
||||
#include "../mwmp/Main.hpp"
|
||||
#include "../mwmp/PlayerList.hpp"
|
||||
#include "../mwmp/CellController.hpp"
|
||||
/*
|
||||
End of tes3mp addition
|
||||
*/
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/windowmanager.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
|
||||
#include "../mwworld/cellstore.hpp"
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwworld/containerstore.hpp"
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
#include "../mwworld/inventorystore.hpp"
|
||||
|
||||
#include "actorutil.hpp"
|
||||
#include "npcstats.hpp"
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
void adjustDynamicStat(CreatureStats& creatureStats, int index, float magnitude, bool allowDecreaseBelowZero = false)
|
||||
{
|
||||
DynamicStat<float> stat = creatureStats.getDynamic(index);
|
||||
stat.setCurrent(stat.getCurrent() + magnitude, allowDecreaseBelowZero);
|
||||
creatureStats.setDynamic(index, stat);
|
||||
}
|
||||
|
||||
bool disintegrateSlot (const MWWorld::Ptr& ptr, int slot, float disintegrate)
|
||||
{
|
||||
if (!ptr.getClass().hasInventoryStore(ptr))
|
||||
return false;
|
||||
|
||||
MWWorld::InventoryStore& inv = ptr.getClass().getInventoryStore(ptr);
|
||||
MWWorld::ContainerStoreIterator item = inv.getSlot(slot);
|
||||
|
||||
if (item != inv.end() && (item.getType() == MWWorld::ContainerStore::Type_Armor || item.getType() == MWWorld::ContainerStore::Type_Weapon))
|
||||
{
|
||||
if (!item->getClass().hasItemHealth(*item))
|
||||
return false;
|
||||
int charge = item->getClass().getItemHealth(*item);
|
||||
if (charge == 0)
|
||||
return false;
|
||||
|
||||
// Store remainder of disintegrate amount (automatically subtracted if > 1)
|
||||
item->getCellRef().applyChargeRemainderToBeSubtracted(disintegrate - std::floor(disintegrate));
|
||||
|
||||
charge = item->getClass().getItemHealth(*item);
|
||||
charge -= std::min(static_cast<int>(disintegrate), charge);
|
||||
item->getCellRef().setCharge(charge);
|
||||
|
||||
if (charge == 0)
|
||||
{
|
||||
// Will unequip the broken item and try to find a replacement
|
||||
if (ptr != getPlayer())
|
||||
inv.autoEquip(ptr);
|
||||
else
|
||||
inv.unequipItem(*item, ptr);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool effectTick(CreatureStats& creatureStats, const MWWorld::Ptr& actor, const EffectKey &effectKey, float magnitude)
|
||||
{
|
||||
if (magnitude == 0.f)
|
||||
return false;
|
||||
|
||||
bool receivedMagicDamage = false;
|
||||
|
||||
switch (effectKey.mId)
|
||||
{
|
||||
case ESM::MagicEffect::DamageAttribute:
|
||||
{
|
||||
AttributeValue attr = creatureStats.getAttribute(effectKey.mArg);
|
||||
attr.damage(magnitude);
|
||||
creatureStats.setAttribute(effectKey.mArg, attr);
|
||||
break;
|
||||
}
|
||||
case ESM::MagicEffect::RestoreAttribute:
|
||||
{
|
||||
AttributeValue attr = creatureStats.getAttribute(effectKey.mArg);
|
||||
attr.restore(magnitude);
|
||||
creatureStats.setAttribute(effectKey.mArg, attr);
|
||||
break;
|
||||
}
|
||||
case ESM::MagicEffect::RestoreHealth:
|
||||
case ESM::MagicEffect::RestoreMagicka:
|
||||
case ESM::MagicEffect::RestoreFatigue:
|
||||
adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::RestoreHealth, magnitude);
|
||||
break;
|
||||
case ESM::MagicEffect::DamageHealth:
|
||||
receivedMagicDamage = true;
|
||||
adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::DamageHealth, -magnitude);
|
||||
break;
|
||||
|
||||
case ESM::MagicEffect::DamageMagicka:
|
||||
case ESM::MagicEffect::DamageFatigue:
|
||||
{
|
||||
int index = effectKey.mId-ESM::MagicEffect::DamageHealth;
|
||||
static const bool uncappedDamageFatigue = Settings::Manager::getBool("uncapped damage fatigue", "Game");
|
||||
adjustDynamicStat(creatureStats, index, -magnitude, index == 2 && uncappedDamageFatigue);
|
||||
break;
|
||||
}
|
||||
case ESM::MagicEffect::AbsorbHealth:
|
||||
if (magnitude > 0.f)
|
||||
receivedMagicDamage = true;
|
||||
adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::AbsorbHealth, -magnitude);
|
||||
|
||||
break;
|
||||
|
||||
case ESM::MagicEffect::AbsorbMagicka:
|
||||
case ESM::MagicEffect::AbsorbFatigue:
|
||||
adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::AbsorbHealth, -magnitude);
|
||||
break;
|
||||
|
||||
case ESM::MagicEffect::DisintegrateArmor:
|
||||
{
|
||||
static const std::array<int, 9> priorities
|
||||
{
|
||||
MWWorld::InventoryStore::Slot_CarriedLeft,
|
||||
MWWorld::InventoryStore::Slot_Cuirass,
|
||||
MWWorld::InventoryStore::Slot_LeftPauldron,
|
||||
MWWorld::InventoryStore::Slot_RightPauldron,
|
||||
MWWorld::InventoryStore::Slot_LeftGauntlet,
|
||||
MWWorld::InventoryStore::Slot_RightGauntlet,
|
||||
MWWorld::InventoryStore::Slot_Helmet,
|
||||
MWWorld::InventoryStore::Slot_Greaves,
|
||||
MWWorld::InventoryStore::Slot_Boots
|
||||
};
|
||||
for (const int priority : priorities)
|
||||
{
|
||||
if (disintegrateSlot(actor, priority, magnitude))
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case ESM::MagicEffect::DisintegrateWeapon:
|
||||
disintegrateSlot(actor, MWWorld::InventoryStore::Slot_CarriedRight, magnitude);
|
||||
break;
|
||||
|
||||
case ESM::MagicEffect::SunDamage:
|
||||
{
|
||||
// isInCell shouldn't be needed, but updateActor called during game start
|
||||
if (!actor.isInCell() || !actor.getCell()->isExterior())
|
||||
break;
|
||||
float time = MWBase::Environment::get().getWorld()->getTimeStamp().getHour();
|
||||
float timeDiff = std::min(7.f, std::max(0.f, std::abs(time - 13)));
|
||||
float damageScale = 1.f - timeDiff / 7.f;
|
||||
// When cloudy, the sun damage effect is halved
|
||||
static float fMagicSunBlockedMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
|
||||
"fMagicSunBlockedMult")->mValue.getFloat();
|
||||
|
||||
int weather = MWBase::Environment::get().getWorld()->getCurrentWeather();
|
||||
if (weather > 1)
|
||||
damageScale *= fMagicSunBlockedMult;
|
||||
|
||||
adjustDynamicStat(creatureStats, 0, -magnitude * damageScale);
|
||||
if (magnitude * damageScale > 0.f)
|
||||
receivedMagicDamage = true;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case ESM::MagicEffect::FireDamage:
|
||||
case ESM::MagicEffect::ShockDamage:
|
||||
case ESM::MagicEffect::FrostDamage:
|
||||
case ESM::MagicEffect::Poison:
|
||||
{
|
||||
adjustDynamicStat(creatureStats, 0, -magnitude);
|
||||
receivedMagicDamage = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case ESM::MagicEffect::DamageSkill:
|
||||
case ESM::MagicEffect::RestoreSkill:
|
||||
{
|
||||
if (!actor.getClass().isNpc())
|
||||
break;
|
||||
NpcStats &npcStats = actor.getClass().getNpcStats(actor);
|
||||
SkillValue& skill = npcStats.getSkill(effectKey.mArg);
|
||||
if (effectKey.mId == ESM::MagicEffect::RestoreSkill)
|
||||
skill.restore(magnitude);
|
||||
else
|
||||
skill.damage(magnitude);
|
||||
break;
|
||||
}
|
||||
|
||||
case ESM::MagicEffect::CurePoison:
|
||||
actor.getClass().getCreatureStats(actor).getActiveSpells().purgeEffect(ESM::MagicEffect::Poison);
|
||||
break;
|
||||
case ESM::MagicEffect::CureParalyzation:
|
||||
actor.getClass().getCreatureStats(actor).getActiveSpells().purgeEffect(ESM::MagicEffect::Paralyze);
|
||||
break;
|
||||
case ESM::MagicEffect::CureCommonDisease:
|
||||
actor.getClass().getCreatureStats(actor).getSpells().purgeCommonDisease();
|
||||
break;
|
||||
case ESM::MagicEffect::CureBlightDisease:
|
||||
actor.getClass().getCreatureStats(actor).getSpells().purgeBlightDisease();
|
||||
break;
|
||||
case ESM::MagicEffect::CureCorprusDisease:
|
||||
actor.getClass().getCreatureStats(actor).getSpells().purgeCorprusDisease();
|
||||
break;
|
||||
case ESM::MagicEffect::RemoveCurse:
|
||||
actor.getClass().getCreatureStats(actor).getSpells().purgeCurses();
|
||||
break;
|
||||
/*
|
||||
Start of tes3mp addition
|
||||
|
||||
Don't apply paralysis to DedicatedPlayers and DedicatedActors unilterally on this client
|
||||
*/
|
||||
case ESM::MagicEffect::Paralyze:
|
||||
{
|
||||
if (mwmp::PlayerList::isDedicatedPlayer(actor) || mwmp::Main::get().getCellController()->isDedicatedActor(actor))
|
||||
{
|
||||
actor.getClass().getCreatureStats(actor).getActiveSpells().purgeEffect(ESM::MagicEffect::Paralyze);
|
||||
break;
|
||||
}
|
||||
}
|
||||
/*
|
||||
End of tes3mp addition
|
||||
*/
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
if (receivedMagicDamage && actor == getPlayer())
|
||||
MWBase::Environment::get().getWindowManager()->activateHitOverlay(false);
|
||||
return true;
|
||||
}
|
||||
}
|
19
apps/openmw/mwmechanics/tickableeffects.hpp
Normal file
19
apps/openmw/mwmechanics/tickableeffects.hpp
Normal file
|
@ -0,0 +1,19 @@
|
|||
#ifndef MWMECHANICS_TICKABLEEFFECTS_H
|
||||
#define MWMECHANICS_TICKABLEEFFECTS_H
|
||||
|
||||
namespace MWWorld
|
||||
{
|
||||
class Ptr;
|
||||
}
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
class CreatureStats;
|
||||
struct EffectKey;
|
||||
|
||||
/// Apply a magic effect that is applied in tick intervals until its remaining time ends or it is removed
|
||||
/// @return Was the effect a tickable effect with a magnitude?
|
||||
bool effectTick(CreatureStats& creatureStats, const MWWorld::Ptr& actor, const EffectKey& effectKey, float magnitude);
|
||||
}
|
||||
|
||||
#endif
|
|
@ -13,7 +13,7 @@
|
|||
#include "combat.hpp"
|
||||
#include "aicombataction.hpp"
|
||||
#include "spellpriority.hpp"
|
||||
#include "spellcasting.hpp"
|
||||
#include "spellutil.hpp"
|
||||
#include "weapontype.hpp"
|
||||
|
||||
namespace MWMechanics
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include "../mwmechanics/creaturestats.hpp"
|
||||
#include "../mwmechanics/mechanicsmanagerimp.hpp"
|
||||
#include "../mwmechanics/spellcasting.hpp"
|
||||
#include "../mwmechanics/spellutil.hpp"
|
||||
|
||||
#include "../mwscript/scriptmanagerimp.hpp"
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include "../mwmechanics/combat.hpp"
|
||||
#include "../mwmechanics/levelledlist.hpp"
|
||||
#include "../mwmechanics/spellcasting.hpp"
|
||||
#include "../mwmechanics/spellutil.hpp"
|
||||
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwworld/inventorystore.hpp"
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
#include <components/resource/scenemanager.hpp>
|
||||
#include <components/resource/keyframemanager.hpp>
|
||||
#include <components/resource/resourcesystem.hpp>
|
||||
|
||||
#include <components/misc/constants.hpp>
|
||||
#include <components/misc/resourcehelpers.hpp>
|
||||
|
@ -36,6 +37,8 @@
|
|||
|
||||
#include <components/settings/settings.hpp>
|
||||
|
||||
#include <components/shader/shadermanager.hpp>
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
|
@ -502,8 +505,9 @@ namespace MWRender
|
|||
class TransparencyUpdater : public SceneUtil::StateSetUpdater
|
||||
{
|
||||
public:
|
||||
TransparencyUpdater(const float alpha)
|
||||
TransparencyUpdater(const float alpha, osg::ref_ptr<osg::Uniform> shadowUniform)
|
||||
: mAlpha(alpha)
|
||||
, mShadowUniform(shadowUniform)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -517,6 +521,9 @@ namespace MWRender
|
|||
{
|
||||
osg::BlendFunc* blendfunc (new osg::BlendFunc);
|
||||
stateset->setAttributeAndModes(blendfunc, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE);
|
||||
// TODO: don't do this anymore once custom shadow renderbin is handling it
|
||||
if (mShadowUniform)
|
||||
stateset->addUniform(mShadowUniform);
|
||||
|
||||
// FIXME: overriding diffuse/ambient/emissive colors
|
||||
osg::Material* material = new osg::Material;
|
||||
|
@ -535,6 +542,7 @@ namespace MWRender
|
|||
|
||||
private:
|
||||
float mAlpha;
|
||||
osg::ref_ptr<osg::Uniform> mShadowUniform;
|
||||
};
|
||||
|
||||
struct Animation::AnimSource
|
||||
|
@ -1744,7 +1752,7 @@ namespace MWRender
|
|||
{
|
||||
if (mTransparencyUpdater == nullptr)
|
||||
{
|
||||
mTransparencyUpdater = new TransparencyUpdater(alpha);
|
||||
mTransparencyUpdater = new TransparencyUpdater(alpha, mResourceSystem->getSceneManager()->getShaderManager().getShadowMapAlphaTestEnableUniform());
|
||||
mObjectRoot->addUpdateCallback(mTransparencyUpdater);
|
||||
}
|
||||
else
|
||||
|
|
|
@ -266,16 +266,22 @@ void HeadAnimationTime::setBlinkStop(float value)
|
|||
|
||||
// ----------------------------------------------------
|
||||
|
||||
NpcAnimation::NpcType NpcAnimation::getNpcType()
|
||||
NpcAnimation::NpcType NpcAnimation::getNpcType() const
|
||||
{
|
||||
const MWWorld::Class &cls = mPtr.getClass();
|
||||
// Dead vampires should typically stay vampires.
|
||||
if (mNpcType == Type_Vampire && cls.getNpcStats(mPtr).isDead() && !cls.getNpcStats(mPtr).isWerewolf())
|
||||
return mNpcType;
|
||||
return getNpcType(mPtr);
|
||||
}
|
||||
|
||||
NpcAnimation::NpcType NpcAnimation::getNpcType(const MWWorld::Ptr& ptr)
|
||||
{
|
||||
const MWWorld::Class &cls = ptr.getClass();
|
||||
NpcAnimation::NpcType curType = Type_Normal;
|
||||
if (cls.getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Vampirism).getMagnitude() > 0)
|
||||
if (cls.getCreatureStats(ptr).getMagicEffects().get(ESM::MagicEffect::Vampirism).getMagnitude() > 0)
|
||||
curType = Type_Vampire;
|
||||
if (cls.getNpcStats(mPtr).isWerewolf())
|
||||
if (cls.getNpcStats(ptr).isWerewolf())
|
||||
curType = Type_Werewolf;
|
||||
|
||||
return curType;
|
||||
|
@ -326,7 +332,7 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, osg::ref_ptr<osg::Group> par
|
|||
mViewMode(viewMode),
|
||||
mShowWeapons(false),
|
||||
mShowCarriedLeft(true),
|
||||
mNpcType(getNpcType()),
|
||||
mNpcType(getNpcType(ptr)),
|
||||
mFirstPersonFieldOfView(firstPersonFieldOfView),
|
||||
mSoundsDisabled(disableSounds),
|
||||
mAccurateAiming(false),
|
||||
|
@ -1127,7 +1133,7 @@ void NpcAnimation::equipmentChanged()
|
|||
static const bool shieldSheathing = Settings::Manager::getBool("shield sheathing", "Game");
|
||||
if (shieldSheathing)
|
||||
{
|
||||
int weaptype;
|
||||
int weaptype = ESM::Weapon::None;
|
||||
MWMechanics::getActiveWeapon(mPtr, &weaptype);
|
||||
showCarriedLeft(updateCarriedLeftVisible(weaptype));
|
||||
}
|
||||
|
|
|
@ -74,7 +74,7 @@ private:
|
|||
|
||||
void updateNpcBase();
|
||||
|
||||
NpcType getNpcType();
|
||||
NpcType getNpcType() const;
|
||||
|
||||
PartHolderPtr insertBoundedPart(const std::string &model, const std::string &bonename,
|
||||
const std::string &bonefilter, bool enchantedGlow, osg::Vec4f* glowColor=nullptr);
|
||||
|
@ -94,6 +94,7 @@ private:
|
|||
|
||||
static bool isFirstPersonPart(const ESM::BodyPart* bodypart);
|
||||
static bool isFemalePart(const ESM::BodyPart* bodypart);
|
||||
static NpcType getNpcType(const MWWorld::Ptr& ptr);
|
||||
|
||||
protected:
|
||||
virtual void addControllers();
|
||||
|
|
|
@ -235,6 +235,8 @@ namespace MWRender
|
|||
sceneRoot->setLightingMask(Mask_Lighting);
|
||||
mSceneRoot = sceneRoot;
|
||||
sceneRoot->setStartLight(1);
|
||||
sceneRoot->setNodeMask(Mask_Scene);
|
||||
sceneRoot->setName("Scene Root");
|
||||
|
||||
int shadowCastingTraversalMask = Mask_Scene;
|
||||
if (Settings::Manager::getBool("actor shadows", "Shadows"))
|
||||
|
@ -347,9 +349,6 @@ namespace MWRender
|
|||
defaultMat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f));
|
||||
sceneRoot->getOrCreateStateSet()->setAttribute(defaultMat);
|
||||
|
||||
sceneRoot->setNodeMask(Mask_Scene);
|
||||
sceneRoot->setName("Scene Root");
|
||||
|
||||
mSky.reset(new SkyManager(sceneRoot, resourceSystem->getSceneManager()));
|
||||
|
||||
mSky->setCamera(mViewer->getCamera());
|
||||
|
@ -377,6 +376,7 @@ namespace MWRender
|
|||
|
||||
mViewer->getCamera()->setCullMask(~(Mask_UpdateVisitor|Mask_SimpleWater));
|
||||
NifOsg::Loader::setHiddenNodeMask(Mask_UpdateVisitor);
|
||||
NifOsg::Loader::setIntersectionDisabledNodeMask(Mask_Effect);
|
||||
|
||||
mNearClip = Settings::Manager::getFloat("near clip", "Camera");
|
||||
mViewDistance = Settings::Manager::getFloat("viewing distance", "Camera");
|
||||
|
@ -768,11 +768,10 @@ namespace MWRender
|
|||
|
||||
void waitTillDone()
|
||||
{
|
||||
mMutex.lock();
|
||||
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mMutex);
|
||||
if (mDone)
|
||||
return;
|
||||
mCondition.wait(&mMutex);
|
||||
mMutex.unlock();
|
||||
}
|
||||
|
||||
mutable OpenThreads::Condition mCondition;
|
||||
|
|
|
@ -247,24 +247,23 @@ namespace MWScript
|
|||
template<class R>
|
||||
class OpGetAiSetting : public Interpreter::Opcode0
|
||||
{
|
||||
int mIndex;
|
||||
MWMechanics::CreatureStats::AiSetting mIndex;
|
||||
public:
|
||||
OpGetAiSetting(int index) : mIndex(index) {}
|
||||
OpGetAiSetting(MWMechanics::CreatureStats::AiSetting index) : mIndex(index) {}
|
||||
|
||||
virtual void execute (Interpreter::Runtime& runtime)
|
||||
{
|
||||
MWWorld::Ptr ptr = R()(runtime);
|
||||
|
||||
runtime.push(ptr.getClass().getCreatureStats (ptr).getAiSetting (
|
||||
(MWMechanics::CreatureStats::AiSetting)mIndex).getModified());
|
||||
runtime.push(ptr.getClass().getCreatureStats (ptr).getAiSetting (mIndex).getModified());
|
||||
}
|
||||
};
|
||||
template<class R>
|
||||
class OpModAiSetting : public Interpreter::Opcode0
|
||||
{
|
||||
int mIndex;
|
||||
MWMechanics::CreatureStats::AiSetting mIndex;
|
||||
public:
|
||||
OpModAiSetting(int index) : mIndex(index) {}
|
||||
OpModAiSetting(MWMechanics::CreatureStats::AiSetting index) : mIndex(index) {}
|
||||
|
||||
virtual void execute (Interpreter::Runtime& runtime)
|
||||
{
|
||||
|
@ -272,19 +271,16 @@ namespace MWScript
|
|||
Interpreter::Type_Integer value = runtime[0].mInteger;
|
||||
runtime.pop();
|
||||
|
||||
MWMechanics::CreatureStats::AiSetting setting
|
||||
= MWMechanics::CreatureStats::AiSetting(mIndex);
|
||||
|
||||
ptr.getClass().getCreatureStats (ptr).setAiSetting (setting,
|
||||
ptr.getClass().getCreatureStats (ptr).getAiSetting (setting).getBase() + value);
|
||||
ptr.getClass().getCreatureStats (ptr).setAiSetting (mIndex,
|
||||
ptr.getClass().getCreatureStats (ptr).getAiSetting (mIndex).getBase() + value);
|
||||
}
|
||||
};
|
||||
template<class R>
|
||||
class OpSetAiSetting : public Interpreter::Opcode0
|
||||
{
|
||||
int mIndex;
|
||||
MWMechanics::CreatureStats::AiSetting mIndex;
|
||||
public:
|
||||
OpSetAiSetting(int index) : mIndex(index) {}
|
||||
OpSetAiSetting(MWMechanics::CreatureStats::AiSetting index) : mIndex(index) {}
|
||||
|
||||
virtual void execute (Interpreter::Runtime& runtime)
|
||||
{
|
||||
|
@ -292,9 +288,7 @@ namespace MWScript
|
|||
Interpreter::Type_Integer value = runtime[0].mInteger;
|
||||
runtime.pop();
|
||||
|
||||
MWMechanics::CreatureStats::AiSetting setting = (MWMechanics::CreatureStats::AiSetting)mIndex;
|
||||
|
||||
MWMechanics::Stat<int> stat = ptr.getClass().getCreatureStats(ptr).getAiSetting(setting);
|
||||
MWMechanics::Stat<int> stat = ptr.getClass().getCreatureStats(ptr).getAiSetting(mIndex);
|
||||
|
||||
/*
|
||||
Start of tes3mp addition
|
||||
|
@ -308,7 +302,7 @@ namespace MWScript
|
|||
*/
|
||||
|
||||
stat.setModified(value, 0);
|
||||
ptr.getClass().getCreatureStats(ptr).setAiSetting(setting, stat);
|
||||
ptr.getClass().getCreatureStats(ptr).setAiSetting(mIndex, stat);
|
||||
|
||||
/*
|
||||
Start of tes3mp addition
|
||||
|
@ -317,7 +311,7 @@ namespace MWScript
|
|||
so send a combat packet regardless of whether we're the cell authority or not; the server
|
||||
can decide if it wants to comply with them by forwarding them to the cell authority
|
||||
*/
|
||||
if (stat.getBase() != initialValue && setting == MWMechanics::CreatureStats::AI_Fight && value == 100)
|
||||
if (stat.getBase() != initialValue && mIndex == MWMechanics::CreatureStats::AI_Fight && value == 100)
|
||||
{
|
||||
mwmp::ActorList *actorList = mwmp::Main::get().getNetworking()->getActorList();
|
||||
actorList->reset();
|
||||
|
@ -638,32 +632,32 @@ namespace MWScript
|
|||
interpreter.installSegment5 (Compiler::Ai::opcodeStopCombatExplicit, new OpStopCombat<ExplicitRef>);
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeToggleAI, new OpToggleAI);
|
||||
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeSetHello, new OpSetAiSetting<ImplicitRef>(0));
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeSetHelloExplicit, new OpSetAiSetting<ExplicitRef>(0));
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeSetFight, new OpSetAiSetting<ImplicitRef>(1));
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeSetFightExplicit, new OpSetAiSetting<ExplicitRef>(1));
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeSetFlee, new OpSetAiSetting<ImplicitRef>(2));
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeSetFleeExplicit, new OpSetAiSetting<ExplicitRef>(2));
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeSetAlarm, new OpSetAiSetting<ImplicitRef>(3));
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeSetAlarmExplicit, new OpSetAiSetting<ExplicitRef>(3));
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeSetHello, new OpSetAiSetting<ImplicitRef>(MWMechanics::CreatureStats::AiSetting::AI_Hello));
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeSetHelloExplicit, new OpSetAiSetting<ExplicitRef>(MWMechanics::CreatureStats::AiSetting::AI_Hello));
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeSetFight, new OpSetAiSetting<ImplicitRef>(MWMechanics::CreatureStats::AiSetting::AI_Fight));
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeSetFightExplicit, new OpSetAiSetting<ExplicitRef>(MWMechanics::CreatureStats::AiSetting::AI_Fight));
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeSetFlee, new OpSetAiSetting<ImplicitRef>(MWMechanics::CreatureStats::AiSetting::AI_Flee));
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeSetFleeExplicit, new OpSetAiSetting<ExplicitRef>(MWMechanics::CreatureStats::AiSetting::AI_Flee));
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeSetAlarm, new OpSetAiSetting<ImplicitRef>(MWMechanics::CreatureStats::AiSetting::AI_Alarm));
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeSetAlarmExplicit, new OpSetAiSetting<ExplicitRef>(MWMechanics::CreatureStats::AiSetting::AI_Alarm));
|
||||
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeModHello, new OpModAiSetting<ImplicitRef>(0));
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeModHelloExplicit, new OpModAiSetting<ExplicitRef>(0));
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeModFight, new OpModAiSetting<ImplicitRef>(1));
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeModFightExplicit, new OpModAiSetting<ExplicitRef>(1));
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeModFlee, new OpModAiSetting<ImplicitRef>(2));
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeModFleeExplicit, new OpModAiSetting<ExplicitRef>(2));
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeModAlarm, new OpModAiSetting<ImplicitRef>(3));
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeModAlarmExplicit, new OpModAiSetting<ExplicitRef>(3));
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeModHello, new OpModAiSetting<ImplicitRef>(MWMechanics::CreatureStats::AiSetting::AI_Hello));
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeModHelloExplicit, new OpModAiSetting<ExplicitRef>(MWMechanics::CreatureStats::AiSetting::AI_Hello));
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeModFight, new OpModAiSetting<ImplicitRef>(MWMechanics::CreatureStats::AiSetting::AI_Fight));
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeModFightExplicit, new OpModAiSetting<ExplicitRef>(MWMechanics::CreatureStats::AiSetting::AI_Fight));
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeModFlee, new OpModAiSetting<ImplicitRef>(MWMechanics::CreatureStats::AiSetting::AI_Flee));
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeModFleeExplicit, new OpModAiSetting<ExplicitRef>(MWMechanics::CreatureStats::AiSetting::AI_Flee));
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeModAlarm, new OpModAiSetting<ImplicitRef>(MWMechanics::CreatureStats::AiSetting::AI_Alarm));
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeModAlarmExplicit, new OpModAiSetting<ExplicitRef>(MWMechanics::CreatureStats::AiSetting::AI_Alarm));
|
||||
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeGetHello, new OpGetAiSetting<ImplicitRef>(0));
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeGetHelloExplicit, new OpGetAiSetting<ExplicitRef>(0));
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeGetFight, new OpGetAiSetting<ImplicitRef>(1));
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeGetFightExplicit, new OpGetAiSetting<ExplicitRef>(1));
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeGetFlee, new OpGetAiSetting<ImplicitRef>(2));
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeGetFleeExplicit, new OpGetAiSetting<ExplicitRef>(2));
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeGetAlarm, new OpGetAiSetting<ImplicitRef>(3));
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeGetAlarmExplicit, new OpGetAiSetting<ExplicitRef>(3));
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeGetHello, new OpGetAiSetting<ImplicitRef>(MWMechanics::CreatureStats::AiSetting::AI_Hello));
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeGetHelloExplicit, new OpGetAiSetting<ExplicitRef>(MWMechanics::CreatureStats::AiSetting::AI_Hello));
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeGetFight, new OpGetAiSetting<ImplicitRef>(MWMechanics::CreatureStats::AiSetting::AI_Fight));
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeGetFightExplicit, new OpGetAiSetting<ExplicitRef>(MWMechanics::CreatureStats::AiSetting::AI_Fight));
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeGetFlee, new OpGetAiSetting<ImplicitRef>(MWMechanics::CreatureStats::AiSetting::AI_Flee));
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeGetFleeExplicit, new OpGetAiSetting<ExplicitRef>(MWMechanics::CreatureStats::AiSetting::AI_Flee));
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeGetAlarm, new OpGetAiSetting<ImplicitRef>(MWMechanics::CreatureStats::AiSetting::AI_Alarm));
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeGetAlarmExplicit, new OpGetAiSetting<ExplicitRef>(MWMechanics::CreatureStats::AiSetting::AI_Alarm));
|
||||
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeFace, new OpFace<ImplicitRef>);
|
||||
interpreter.installSegment5 (Compiler::Ai::opcodeFaceExplicit, new OpFace<ExplicitRef>);
|
||||
|
|
|
@ -455,7 +455,6 @@ namespace MWScript
|
|||
}
|
||||
};
|
||||
|
||||
template<class R>
|
||||
class OpPlaceItemCell : public Interpreter::Opcode0
|
||||
{
|
||||
public:
|
||||
|
@ -547,7 +546,6 @@ namespace MWScript
|
|||
}
|
||||
};
|
||||
|
||||
template<class R>
|
||||
class OpPlaceItem : public Interpreter::Opcode0
|
||||
{
|
||||
public:
|
||||
|
@ -922,8 +920,8 @@ namespace MWScript
|
|||
interpreter.installSegment5(Compiler::Transformation::opcodePositionExplicit,new OpPosition<ExplicitRef>);
|
||||
interpreter.installSegment5(Compiler::Transformation::opcodePositionCell,new OpPositionCell<ImplicitRef>);
|
||||
interpreter.installSegment5(Compiler::Transformation::opcodePositionCellExplicit,new OpPositionCell<ExplicitRef>);
|
||||
interpreter.installSegment5(Compiler::Transformation::opcodePlaceItemCell,new OpPlaceItemCell<ImplicitRef>);
|
||||
interpreter.installSegment5(Compiler::Transformation::opcodePlaceItem,new OpPlaceItem<ImplicitRef>);
|
||||
interpreter.installSegment5(Compiler::Transformation::opcodePlaceItemCell,new OpPlaceItemCell);
|
||||
interpreter.installSegment5(Compiler::Transformation::opcodePlaceItem,new OpPlaceItem);
|
||||
interpreter.installSegment5(Compiler::Transformation::opcodePlaceAtPc,new OpPlaceAt<ImplicitRef, true>);
|
||||
interpreter.installSegment5(Compiler::Transformation::opcodePlaceAtMe,new OpPlaceAt<ImplicitRef, false>);
|
||||
interpreter.installSegment5(Compiler::Transformation::opcodePlaceAtMeExplicit,new OpPlaceAt<ExplicitRef, false>);
|
||||
|
|
|
@ -26,7 +26,8 @@
|
|||
#include "../mwbase/mechanicsmanager.hpp"
|
||||
|
||||
#include "../mwmechanics/npcstats.hpp"
|
||||
#include "../mwmechanics/spellcasting.hpp"
|
||||
#include "../mwmechanics/spellresistance.hpp"
|
||||
#include "../mwmechanics/spellutil.hpp"
|
||||
#include "../mwmechanics/actorutil.hpp"
|
||||
#include "../mwmechanics/weapontype.hpp"
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
|
||||
#include "../mwmechanics/movement.hpp"
|
||||
#include "../mwmechanics/npcstats.hpp"
|
||||
#include "../mwmechanics/spellcasting.hpp"
|
||||
#include "../mwmechanics/spellutil.hpp"
|
||||
|
||||
#include "class.hpp"
|
||||
#include "ptr.hpp"
|
||||
|
|
|
@ -63,6 +63,7 @@
|
|||
#include "../mwmechanics/levelledlist.hpp"
|
||||
#include "../mwmechanics/combat.hpp"
|
||||
#include "../mwmechanics/aiavoiddoor.hpp" //Used to tell actors to avoid doors
|
||||
#include "../mwmechanics/summoning.hpp"
|
||||
|
||||
#include "../mwrender/animation.hpp"
|
||||
#include "../mwrender/npcanimation.hpp"
|
||||
|
@ -3705,12 +3706,42 @@ namespace MWWorld
|
|||
mProjectileManager->launchMagicBolt(spellId, caster, fallbackDirection);
|
||||
}
|
||||
|
||||
class ApplyLoopingParticlesVisitor : public MWMechanics::EffectSourceVisitor
|
||||
{
|
||||
private:
|
||||
MWWorld::Ptr mActor;
|
||||
|
||||
public:
|
||||
ApplyLoopingParticlesVisitor(const MWWorld::Ptr& actor)
|
||||
: mActor(actor)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void visit (MWMechanics::EffectKey key,
|
||||
const std::string& /*sourceName*/, const std::string& /*sourceId*/, int /*casterActorId*/,
|
||||
float /*magnitude*/, float /*remainingTime*/ = -1, float /*totalTime*/ = -1)
|
||||
{
|
||||
const ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
|
||||
const auto magicEffect = store.get<ESM::MagicEffect>().find(key.mId);
|
||||
if ((magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx) == 0)
|
||||
return;
|
||||
const ESM::Static* castStatic;
|
||||
if (!magicEffect->mHit.empty())
|
||||
castStatic = store.get<ESM::Static>().find (magicEffect->mHit);
|
||||
else
|
||||
castStatic = store.get<ESM::Static>().find ("VFX_DefaultHit");
|
||||
MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(mActor);
|
||||
if (anim && !castStatic->mModel.empty())
|
||||
anim->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, /*loop*/true, "", magicEffect->mParticle);
|
||||
}
|
||||
};
|
||||
|
||||
void World::applyLoopingParticles(const MWWorld::Ptr& ptr)
|
||||
{
|
||||
const MWWorld::Class &cls = ptr.getClass();
|
||||
if (cls.isActor())
|
||||
{
|
||||
MWMechanics::ApplyLoopingParticlesVisitor visitor(ptr);
|
||||
ApplyLoopingParticlesVisitor visitor(ptr);
|
||||
cls.getCreatureStats(ptr).getActiveSpells().visitEffectSources(visitor);
|
||||
cls.getCreatureStats(ptr).getSpells().visitEffectSources(visitor);
|
||||
if (cls.hasInventoryStore(ptr))
|
||||
|
|
|
@ -27,6 +27,10 @@ if (GTEST_FOUND AND GMOCK_FOUND)
|
|||
detournavigator/tilecachedrecastmeshmanager.cpp
|
||||
|
||||
settings/parser.cpp
|
||||
|
||||
shader/parsedefines.cpp
|
||||
shader/parsefors.cpp
|
||||
shader/shadermanager.cpp
|
||||
)
|
||||
|
||||
source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES})
|
||||
|
|
|
@ -73,6 +73,7 @@ namespace
|
|||
mSettings.mTrianglesPerChunk = 256;
|
||||
mSettings.mMaxPolys = 4096;
|
||||
mSettings.mMaxTilesNumber = 512;
|
||||
mSettings.mMinUpdateInterval = std::chrono::milliseconds(50);
|
||||
mNavigator.reset(new NavigatorImpl(mSettings));
|
||||
}
|
||||
};
|
||||
|
@ -699,4 +700,108 @@ namespace
|
|||
|
||||
EXPECT_FLOAT_EQ(distance, 85.260780334472656);
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorNavigatorTest, multiple_threads_should_lock_tiles)
|
||||
{
|
||||
mSettings.mAsyncNavMeshUpdaterThreads = 2;
|
||||
mNavigator.reset(new NavigatorImpl(mSettings));
|
||||
|
||||
const std::array<btScalar, 5 * 5> heightfieldData {{
|
||||
0, 0, 0, 0, 0,
|
||||
0, -25, -25, -25, -25,
|
||||
0, -25, -100, -100, -100,
|
||||
0, -25, -100, -100, -100,
|
||||
0, -25, -100, -100, -100,
|
||||
}};
|
||||
btHeightfieldTerrainShape heightfieldShape(5, 5, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false);
|
||||
heightfieldShape.setLocalScaling(btVector3(128, 128, 1));
|
||||
|
||||
const std::vector<btBoxShape> boxShapes(100, btVector3(20, 20, 100));
|
||||
|
||||
mNavigator->addAgent(mAgentHalfExtents);
|
||||
|
||||
mNavigator->addObject(ObjectId(&heightfieldShape), heightfieldShape, btTransform::getIdentity());
|
||||
|
||||
for (std::size_t i = 0; i < boxShapes.size(); ++i)
|
||||
{
|
||||
const btTransform transform(btMatrix3x3::getIdentity(), btVector3(i * 10, i * 10, i * 10));
|
||||
mNavigator->addObject(ObjectId(&boxShapes[i]), boxShapes[i], transform);
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(1));
|
||||
|
||||
for (std::size_t i = 0; i < boxShapes.size(); ++i)
|
||||
{
|
||||
const btTransform transform(btMatrix3x3::getIdentity(), btVector3(i * 10 + 1, i * 10 + 1, i * 10 + 1));
|
||||
mNavigator->updateObject(ObjectId(&boxShapes[i]), boxShapes[i], transform);
|
||||
}
|
||||
|
||||
mNavigator->update(mPlayerPosition);
|
||||
mNavigator->wait();
|
||||
|
||||
EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mOut), Status::Success);
|
||||
|
||||
EXPECT_THAT(mPath, ElementsAre(
|
||||
Vec3fEq(-215, 215, 1.8782780170440673828125),
|
||||
Vec3fEq(-199.7968292236328125, 191.09100341796875, -3.54875946044921875),
|
||||
Vec3fEq(-184.5936431884765625, 167.1819915771484375, -8.97846889495849609375),
|
||||
Vec3fEq(-169.3904571533203125, 143.2729949951171875, -14.40818119049072265625),
|
||||
Vec3fEq(-154.1872711181640625, 119.363983154296875, -19.837886810302734375),
|
||||
Vec3fEq(-138.9840850830078125, 95.4549713134765625, -25.2675952911376953125),
|
||||
Vec3fEq(-123.78090667724609375, 71.54595947265625, -30.6973056793212890625),
|
||||
Vec3fEq(-108.57772064208984375, 47.63695526123046875, -36.12701416015625),
|
||||
Vec3fEq(-93.3745269775390625, 23.72794342041015625, -40.754695892333984375),
|
||||
Vec3fEq(-78.17134857177734375, -0.18106450140476226806640625, -37.128795623779296875),
|
||||
Vec3fEq(-62.968158721923828125, -24.0900726318359375, -33.50289154052734375),
|
||||
Vec3fEq(-47.764972686767578125, -47.99908447265625, -30.797946929931640625),
|
||||
Vec3fEq(-23.8524494171142578125, -63.196746826171875, -33.97112274169921875),
|
||||
Vec3fEq(0.0600722394883632659912109375, -78.3944091796875, -37.14543914794921875),
|
||||
Vec3fEq(23.97259521484375, -93.592071533203125, -40.774089813232421875),
|
||||
Vec3fEq(47.885120391845703125, -108.78974151611328125, -36.051296234130859375),
|
||||
Vec3fEq(71.797637939453125, -123.98740386962890625, -30.62355804443359375),
|
||||
Vec3fEq(95.71016693115234375, -139.18505859375, -25.195819854736328125),
|
||||
Vec3fEq(119.6226806640625, -154.382720947265625, -19.768085479736328125),
|
||||
Vec3fEq(143.5352020263671875, -169.5803680419921875, -14.34035015106201171875),
|
||||
Vec3fEq(167.447723388671875, -184.7780303955078125, -8.912616729736328125),
|
||||
Vec3fEq(191.3602294921875, -199.9756927490234375, -3.48488140106201171875),
|
||||
Vec3fEq(215, -215, 1.8782813549041748046875)
|
||||
)) << mPath;
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorNavigatorTest, update_changed_multiple_times_object_should_delay_navmesh_change)
|
||||
{
|
||||
const std::vector<btBoxShape> shapes(100, btVector3(64, 64, 64));
|
||||
|
||||
mNavigator->addAgent(mAgentHalfExtents);
|
||||
|
||||
for (std::size_t i = 0; i < shapes.size(); ++i)
|
||||
{
|
||||
const btTransform transform(btMatrix3x3::getIdentity(), btVector3(i * 32, i * 32, i * 32));
|
||||
mNavigator->addObject(ObjectId(&shapes[i]), shapes[i], transform);
|
||||
}
|
||||
mNavigator->update(mPlayerPosition);
|
||||
mNavigator->wait();
|
||||
|
||||
const auto start = std::chrono::steady_clock::now();
|
||||
for (std::size_t i = 0; i < shapes.size(); ++i)
|
||||
{
|
||||
const btTransform transform(btMatrix3x3::getIdentity(), btVector3(i * 32 + 1, i * 32 + 1, i * 32 + 1));
|
||||
mNavigator->updateObject(ObjectId(&shapes[i]), shapes[i], transform);
|
||||
}
|
||||
mNavigator->update(mPlayerPosition);
|
||||
mNavigator->wait();
|
||||
|
||||
for (std::size_t i = 0; i < shapes.size(); ++i)
|
||||
{
|
||||
const btTransform transform(btMatrix3x3::getIdentity(), btVector3(i * 32 + 2, i * 32 + 2, i * 32 + 2));
|
||||
mNavigator->updateObject(ObjectId(&shapes[i]), shapes[i], transform);
|
||||
}
|
||||
mNavigator->update(mPlayerPosition);
|
||||
mNavigator->wait();
|
||||
|
||||
const auto duration = std::chrono::steady_clock::now() - start;
|
||||
|
||||
EXPECT_GT(duration, mSettings.mMinUpdateInterval)
|
||||
<< std::chrono::duration_cast<std::chrono::duration<float, std::milli>>(duration).count() << " ms";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ namespace
|
|||
struct DetourNavigatorTileCachedRecastMeshManagerTest : Test
|
||||
{
|
||||
Settings mSettings;
|
||||
std::vector<TilePosition> mChangedTiles;
|
||||
|
||||
DetourNavigatorTileCachedRecastMeshManagerTest()
|
||||
{
|
||||
|
@ -29,6 +30,11 @@ namespace
|
|||
mSettings.mTileSize = 64;
|
||||
mSettings.mTrianglesPerChunk = 256;
|
||||
}
|
||||
|
||||
void onChangedTile(const TilePosition& tilePosition)
|
||||
{
|
||||
mChangedTiles.push_back(tilePosition);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_empty_should_return_nullptr)
|
||||
|
@ -78,8 +84,10 @@ namespace
|
|||
const btBoxShape boxShape(btVector3(20, 20, 100));
|
||||
const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0));
|
||||
manager.addObject(ObjectId(&boxShape), boxShape, transform, AreaType::AreaType_ground);
|
||||
EXPECT_TRUE(manager.updateObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground,
|
||||
[&] (const auto& v) { onChangedTile(v); }));
|
||||
EXPECT_THAT(
|
||||
manager.updateObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground),
|
||||
mChangedTiles,
|
||||
ElementsAre(TilePosition(-1, -1), TilePosition(-1, 0), TilePosition(0, -1), TilePosition(0, 0),
|
||||
TilePosition(1, -1), TilePosition(1, 0))
|
||||
);
|
||||
|
@ -90,10 +98,9 @@ namespace
|
|||
TileCachedRecastMeshManager manager(mSettings);
|
||||
const btBoxShape boxShape(btVector3(20, 20, 100));
|
||||
manager.addObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground);
|
||||
EXPECT_EQ(
|
||||
manager.updateObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground),
|
||||
std::vector<TilePosition>()
|
||||
);
|
||||
EXPECT_FALSE(manager.updateObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground,
|
||||
[&] (const auto& v) { onChangedTile(v); }));
|
||||
EXPECT_EQ(mChangedTiles, std::vector<TilePosition>());
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_after_add_object_should_return_recast_mesh_for_each_used_tile)
|
||||
|
@ -127,7 +134,7 @@ namespace
|
|||
EXPECT_NE(manager.getMesh(TilePosition(1, 0)), nullptr);
|
||||
EXPECT_NE(manager.getMesh(TilePosition(1, -1)), nullptr);
|
||||
|
||||
manager.updateObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground);
|
||||
manager.updateObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {});
|
||||
EXPECT_NE(manager.getMesh(TilePosition(-1, -1)), nullptr);
|
||||
EXPECT_NE(manager.getMesh(TilePosition(-1, 0)), nullptr);
|
||||
EXPECT_NE(manager.getMesh(TilePosition(0, -1)), nullptr);
|
||||
|
@ -144,7 +151,7 @@ namespace
|
|||
EXPECT_EQ(manager.getMesh(TilePosition(-1, -1)), nullptr);
|
||||
EXPECT_EQ(manager.getMesh(TilePosition(-1, 0)), nullptr);
|
||||
|
||||
manager.updateObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground);
|
||||
manager.updateObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {});
|
||||
EXPECT_EQ(manager.getMesh(TilePosition(1, 0)), nullptr);
|
||||
EXPECT_EQ(manager.getMesh(TilePosition(1, -1)), nullptr);
|
||||
}
|
||||
|
@ -172,7 +179,7 @@ namespace
|
|||
EXPECT_NE(manager.getMesh(TilePosition(0, -1)), nullptr);
|
||||
EXPECT_NE(manager.getMesh(TilePosition(0, 0)), nullptr);
|
||||
|
||||
manager.updateObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground);
|
||||
manager.updateObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {});
|
||||
EXPECT_NE(manager.getMesh(TilePosition(-1, -1)), nullptr);
|
||||
EXPECT_NE(manager.getMesh(TilePosition(-1, 0)), nullptr);
|
||||
EXPECT_NE(manager.getMesh(TilePosition(0, -1)), nullptr);
|
||||
|
@ -205,7 +212,7 @@ namespace
|
|||
const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0));
|
||||
manager.addObject(ObjectId(&boxShape), boxShape, transform, AreaType::AreaType_ground);
|
||||
const auto beforeUpdateRevision = manager.getRevision();
|
||||
manager.updateObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground);
|
||||
manager.updateObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {});
|
||||
EXPECT_EQ(manager.getRevision(), beforeUpdateRevision + 1);
|
||||
}
|
||||
|
||||
|
@ -215,7 +222,7 @@ namespace
|
|||
const btBoxShape boxShape(btVector3(20, 20, 100));
|
||||
manager.addObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground);
|
||||
const auto beforeUpdateRevision = manager.getRevision();
|
||||
manager.updateObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground);
|
||||
manager.updateObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {});
|
||||
EXPECT_EQ(manager.getRevision(), beforeUpdateRevision);
|
||||
}
|
||||
|
||||
|
|
191
apps/openmw_test_suite/shader/parsedefines.cpp
Normal file
191
apps/openmw_test_suite/shader/parsedefines.cpp
Normal file
|
@ -0,0 +1,191 @@
|
|||
#include <components/shader/shadermanager.hpp>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
using namespace testing;
|
||||
using namespace Shader;
|
||||
|
||||
using DefineMap = ShaderManager::DefineMap;
|
||||
|
||||
struct ShaderParseDefinesTest : Test
|
||||
{
|
||||
std::string mSource;
|
||||
const std::string mName = "shader";
|
||||
DefineMap mDefines;
|
||||
DefineMap mGlobalDefines;
|
||||
};
|
||||
|
||||
TEST_F(ShaderParseDefinesTest, empty_should_succeed)
|
||||
{
|
||||
ASSERT_TRUE(parseDefines(mSource, mDefines, mGlobalDefines, mName));
|
||||
EXPECT_EQ(mSource, "");
|
||||
}
|
||||
|
||||
TEST_F(ShaderParseDefinesTest, should_fail_for_absent_define)
|
||||
{
|
||||
mSource = "@foo\n";
|
||||
ASSERT_FALSE(parseDefines(mSource, mDefines, mGlobalDefines, mName));
|
||||
EXPECT_EQ(mSource, "@foo\n");
|
||||
}
|
||||
|
||||
TEST_F(ShaderParseDefinesTest, should_replace_by_existing_define)
|
||||
{
|
||||
mDefines["foo"] = "42";
|
||||
mSource = "@foo\n";
|
||||
ASSERT_TRUE(parseDefines(mSource, mDefines, mGlobalDefines, mName));
|
||||
EXPECT_EQ(mSource, "42\n");
|
||||
}
|
||||
|
||||
TEST_F(ShaderParseDefinesTest, should_replace_by_existing_global_define)
|
||||
{
|
||||
mGlobalDefines["foo"] = "42";
|
||||
mSource = "@foo\n";
|
||||
ASSERT_TRUE(parseDefines(mSource, mDefines, mGlobalDefines, mName));
|
||||
EXPECT_EQ(mSource, "42\n");
|
||||
}
|
||||
|
||||
TEST_F(ShaderParseDefinesTest, should_prefer_define_over_global_define)
|
||||
{
|
||||
mDefines["foo"] = "13";
|
||||
mGlobalDefines["foo"] = "42";
|
||||
mSource = "@foo\n";
|
||||
ASSERT_TRUE(parseDefines(mSource, mDefines, mGlobalDefines, mName));
|
||||
EXPECT_EQ(mSource, "13\n");
|
||||
}
|
||||
|
||||
namespace SupportedTerminals
|
||||
{
|
||||
struct ShaderParseDefinesTest : ::ShaderParseDefinesTest, WithParamInterface<char> {};
|
||||
|
||||
TEST_P(ShaderParseDefinesTest, support_defines_terminated_by)
|
||||
{
|
||||
mDefines["foo"] = "13";
|
||||
mSource = "@foo" + std::string(1, GetParam());
|
||||
ASSERT_TRUE(parseDefines(mSource, mDefines, mGlobalDefines, mName));
|
||||
EXPECT_EQ(mSource, "13" + std::string(1, GetParam()));
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
SupportedTerminals,
|
||||
ShaderParseDefinesTest,
|
||||
Values(' ', '\n', '\r', '(', ')', '[', ']', '.', ';', ',')
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(ShaderParseDefinesTest, should_not_support_define_ending_with_source)
|
||||
{
|
||||
mDefines["foo"] = "42";
|
||||
mSource = "@foo";
|
||||
ASSERT_FALSE(parseDefines(mSource, mDefines, mGlobalDefines, mName));
|
||||
EXPECT_EQ(mSource, "@foo");
|
||||
}
|
||||
|
||||
TEST_F(ShaderParseDefinesTest, should_replace_all_matched_values)
|
||||
{
|
||||
mDefines["foo"] = "42";
|
||||
mSource = "@foo @foo ";
|
||||
ASSERT_TRUE(parseDefines(mSource, mDefines, mGlobalDefines, mName));
|
||||
EXPECT_EQ(mSource, "42 42 ");
|
||||
}
|
||||
|
||||
TEST_F(ShaderParseDefinesTest, should_support_define_with_empty_name)
|
||||
{
|
||||
mDefines[""] = "42";
|
||||
mSource = "@ ";
|
||||
ASSERT_TRUE(parseDefines(mSource, mDefines, mGlobalDefines, mName));
|
||||
EXPECT_EQ(mSource, "42 ");
|
||||
}
|
||||
|
||||
TEST_F(ShaderParseDefinesTest, should_replace_all_found_defines)
|
||||
{
|
||||
mDefines["foo"] = "42";
|
||||
mDefines["bar"] = "13";
|
||||
mDefines["baz"] = "55";
|
||||
mSource = "@foo @bar ";
|
||||
ASSERT_TRUE(parseDefines(mSource, mDefines, mGlobalDefines, mName));
|
||||
EXPECT_EQ(mSource, "42 13 ");
|
||||
}
|
||||
|
||||
TEST_F(ShaderParseDefinesTest, should_fail_on_foreach_without_endforeach)
|
||||
{
|
||||
mSource = "@foreach ";
|
||||
ASSERT_FALSE(parseDefines(mSource, mDefines, mGlobalDefines, mName));
|
||||
EXPECT_EQ(mSource, "$foreach ");
|
||||
}
|
||||
|
||||
TEST_F(ShaderParseDefinesTest, should_fail_on_endforeach_without_foreach)
|
||||
{
|
||||
mSource = "@endforeach ";
|
||||
ASSERT_FALSE(parseDefines(mSource, mDefines, mGlobalDefines, mName));
|
||||
EXPECT_EQ(mSource, "$endforeach ");
|
||||
}
|
||||
|
||||
TEST_F(ShaderParseDefinesTest, should_replace_at_sign_by_dollar_for_foreach_endforeach)
|
||||
{
|
||||
mSource = "@foreach @endforeach ";
|
||||
ASSERT_TRUE(parseDefines(mSource, mDefines, mGlobalDefines, mName));
|
||||
EXPECT_EQ(mSource, "$foreach $endforeach ");
|
||||
}
|
||||
|
||||
TEST_F(ShaderParseDefinesTest, should_succeed_on_unmatched_nested_foreach)
|
||||
{
|
||||
mSource = "@foreach @foreach @endforeach ";
|
||||
ASSERT_TRUE(parseDefines(mSource, mDefines, mGlobalDefines, mName));
|
||||
EXPECT_EQ(mSource, "$foreach $foreach $endforeach ");
|
||||
}
|
||||
|
||||
TEST_F(ShaderParseDefinesTest, should_fail_on_unmatched_nested_endforeach)
|
||||
{
|
||||
mSource = "@foreach @endforeach @endforeach ";
|
||||
ASSERT_FALSE(parseDefines(mSource, mDefines, mGlobalDefines, mName));
|
||||
EXPECT_EQ(mSource, "$foreach $endforeach $endforeach ");
|
||||
}
|
||||
|
||||
TEST_F(ShaderParseDefinesTest, should_support_nested_foreach)
|
||||
{
|
||||
mSource = "@foreach @foreach @endforeach @endforeach ";
|
||||
ASSERT_TRUE(parseDefines(mSource, mDefines, mGlobalDefines, mName));
|
||||
EXPECT_EQ(mSource, "$foreach $foreach $endforeach $endforeach ");
|
||||
}
|
||||
|
||||
TEST_F(ShaderParseDefinesTest, should_support_foreach_variable)
|
||||
{
|
||||
mSource = "@foreach foo @foo @endforeach ";
|
||||
ASSERT_TRUE(parseDefines(mSource, mDefines, mGlobalDefines, mName));
|
||||
EXPECT_EQ(mSource, "$foreach foo $foo $endforeach ");
|
||||
}
|
||||
|
||||
TEST_F(ShaderParseDefinesTest, should_not_replace_foreach_variable_by_define)
|
||||
{
|
||||
mDefines["foo"] = "42";
|
||||
mSource = "@foreach foo @foo @endforeach ";
|
||||
ASSERT_TRUE(parseDefines(mSource, mDefines, mGlobalDefines, mName));
|
||||
EXPECT_EQ(mSource, "$foreach foo $foo $endforeach ");
|
||||
}
|
||||
|
||||
TEST_F(ShaderParseDefinesTest, should_support_nested_foreach_with_variable)
|
||||
{
|
||||
mSource = "@foreach foo @foo @foreach bar @bar @endforeach @endforeach ";
|
||||
ASSERT_TRUE(parseDefines(mSource, mDefines, mGlobalDefines, mName));
|
||||
EXPECT_EQ(mSource, "$foreach foo $foo $foreach bar $bar $endforeach $endforeach ");
|
||||
}
|
||||
|
||||
TEST_F(ShaderParseDefinesTest, should_not_support_single_line_comments_for_defines)
|
||||
{
|
||||
mDefines["foo"] = "42";
|
||||
mSource = "@foo // @foo\n";
|
||||
ASSERT_TRUE(parseDefines(mSource, mDefines, mGlobalDefines, mName));
|
||||
EXPECT_EQ(mSource, "42 // 42\n");
|
||||
}
|
||||
|
||||
TEST_F(ShaderParseDefinesTest, should_not_support_multiline_comments_for_defines)
|
||||
{
|
||||
mDefines["foo"] = "42";
|
||||
mSource = "/* @foo */ @foo ";
|
||||
ASSERT_TRUE(parseDefines(mSource, mDefines, mGlobalDefines, mName));
|
||||
EXPECT_EQ(mSource, "/* 42 */ 42 ");
|
||||
}
|
||||
}
|
94
apps/openmw_test_suite/shader/parsefors.cpp
Normal file
94
apps/openmw_test_suite/shader/parsefors.cpp
Normal file
|
@ -0,0 +1,94 @@
|
|||
#include <components/shader/shadermanager.hpp>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
using namespace testing;
|
||||
using namespace Shader;
|
||||
|
||||
using DefineMap = ShaderManager::DefineMap;
|
||||
|
||||
struct ShaderParseForsTest : Test
|
||||
{
|
||||
std::string mSource;
|
||||
const std::string mName = "shader";
|
||||
};
|
||||
|
||||
TEST_F(ShaderParseForsTest, empty_should_succeed)
|
||||
{
|
||||
ASSERT_TRUE(parseFors(mSource, mName));
|
||||
EXPECT_EQ(mSource, "");
|
||||
}
|
||||
|
||||
TEST_F(ShaderParseForsTest, should_fail_for_single_escape_symbol)
|
||||
{
|
||||
mSource = "$";
|
||||
ASSERT_FALSE(parseFors(mSource, mName));
|
||||
EXPECT_EQ(mSource, "$");
|
||||
}
|
||||
|
||||
TEST_F(ShaderParseForsTest, should_fail_on_first_found_escaped_not_foreach)
|
||||
{
|
||||
mSource = "$foo ";
|
||||
ASSERT_FALSE(parseFors(mSource, mName));
|
||||
EXPECT_EQ(mSource, "$foo ");
|
||||
}
|
||||
|
||||
TEST_F(ShaderParseForsTest, should_fail_on_absent_foreach_variable)
|
||||
{
|
||||
mSource = "$foreach ";
|
||||
ASSERT_FALSE(parseFors(mSource, mName));
|
||||
EXPECT_EQ(mSource, "$foreach ");
|
||||
}
|
||||
|
||||
TEST_F(ShaderParseForsTest, should_fail_on_unmatched_after_variable)
|
||||
{
|
||||
mSource = "$foreach foo ";
|
||||
ASSERT_FALSE(parseFors(mSource, mName));
|
||||
EXPECT_EQ(mSource, "$foreach foo ");
|
||||
}
|
||||
|
||||
TEST_F(ShaderParseForsTest, should_fail_on_absent_newline_after_foreach_list)
|
||||
{
|
||||
mSource = "$foreach foo 1,2,3 ";
|
||||
ASSERT_FALSE(parseFors(mSource, mName));
|
||||
EXPECT_EQ(mSource, "$foreach foo 1,2,3 ");
|
||||
}
|
||||
|
||||
TEST_F(ShaderParseForsTest, should_fail_on_absent_endforeach_after_newline)
|
||||
{
|
||||
mSource = "$foreach foo 1,2,3\n";
|
||||
ASSERT_FALSE(parseFors(mSource, mName));
|
||||
EXPECT_EQ(mSource, "$foreach foo 1,2,3\n");
|
||||
}
|
||||
|
||||
TEST_F(ShaderParseForsTest, should_replace_complete_foreach_by_line_number)
|
||||
{
|
||||
mSource = "$foreach foo 1,2,3\n$endforeach";
|
||||
ASSERT_TRUE(parseFors(mSource, mName));
|
||||
EXPECT_EQ(mSource, "\n#line 3");
|
||||
}
|
||||
|
||||
TEST_F(ShaderParseForsTest, should_replace_loop_variable)
|
||||
{
|
||||
mSource = "$foreach foo 1,2,3\n$foo\n$endforeach";
|
||||
ASSERT_TRUE(parseFors(mSource, mName));
|
||||
EXPECT_EQ(mSource, "1\n2\n3\n\n#line 4");
|
||||
}
|
||||
|
||||
TEST_F(ShaderParseForsTest, should_count_line_number_from_existing)
|
||||
{
|
||||
mSource = "$foreach foo 1,2,3\n#line 10\n$foo\n$endforeach";
|
||||
ASSERT_TRUE(parseFors(mSource, mName));
|
||||
EXPECT_EQ(mSource, "#line 10\n1\n#line 10\n2\n#line 10\n3\n\n#line 12");
|
||||
}
|
||||
|
||||
TEST_F(ShaderParseForsTest, should_not_support_nested_loops)
|
||||
{
|
||||
mSource = "$foreach foo 1,2\n$foo\n$foreach bar 1,2\n$bar\n$endforeach\n$endforeach";
|
||||
ASSERT_FALSE(parseFors(mSource, mName));
|
||||
EXPECT_EQ(mSource, "1\n1\n2\n$foreach bar 1,2\n1\n\n#line 6\n2\n2\n$foreach bar 1,2\n2\n\n#line 6\n\n#line 7");
|
||||
}
|
||||
}
|
240
apps/openmw_test_suite/shader/shadermanager.cpp
Normal file
240
apps/openmw_test_suite/shader/shadermanager.cpp
Normal file
|
@ -0,0 +1,240 @@
|
|||
#include <components/shader/shadermanager.hpp>
|
||||
|
||||
#include <boost/filesystem/fstream.hpp>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
using namespace testing;
|
||||
using namespace Shader;
|
||||
|
||||
struct ShaderManagerTest : Test
|
||||
{
|
||||
ShaderManager mManager;
|
||||
ShaderManager::DefineMap mDefines;
|
||||
|
||||
ShaderManagerTest()
|
||||
{
|
||||
mManager.setShaderPath(".");
|
||||
}
|
||||
|
||||
template <class F>
|
||||
void withShaderFile(const std::string& content, F&& f)
|
||||
{
|
||||
withShaderFile("", content, std::forward<F>(f));
|
||||
}
|
||||
|
||||
template <class F>
|
||||
void withShaderFile(const std::string& suffix, const std::string& content, F&& f)
|
||||
{
|
||||
const auto path = UnitTest::GetInstance()->current_test_info()->name() + suffix + ".glsl";
|
||||
|
||||
{
|
||||
boost::filesystem::ofstream stream;
|
||||
stream.open(path);
|
||||
stream << content;
|
||||
stream.close();
|
||||
}
|
||||
|
||||
f(path);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(ShaderManagerTest, get_shader_with_empty_content_should_succeed)
|
||||
{
|
||||
const std::string content;
|
||||
|
||||
withShaderFile(content, [this] (const std::string& templateName) {
|
||||
EXPECT_TRUE(mManager.getShader(templateName, {}, osg::Shader::VERTEX));
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ShaderManagerTest, get_shader_should_not_change_source_without_template_parameters)
|
||||
{
|
||||
const std::string content =
|
||||
"#version 120\n"
|
||||
"void main() {}\n";
|
||||
|
||||
withShaderFile(content, [&] (const std::string& templateName) {
|
||||
const auto shader = mManager.getShader(templateName, mDefines, osg::Shader::VERTEX);
|
||||
ASSERT_TRUE(shader);
|
||||
EXPECT_EQ(shader->getShaderSource(), content);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ShaderManagerTest, get_shader_should_replace_includes_with_content)
|
||||
{
|
||||
const std::string content0 =
|
||||
"void foo() {}\n";
|
||||
|
||||
withShaderFile("_0", content0, [&] (const std::string& templateName0) {
|
||||
const std::string content1 =
|
||||
"#include \"" + templateName0 + "\"\n"
|
||||
"void bar() { foo() }\n";
|
||||
|
||||
withShaderFile("_1", content1, [&] (const std::string& templateName1) {
|
||||
const std::string content2 =
|
||||
"#version 120\n"
|
||||
"#include \"" + templateName1 + "\"\n"
|
||||
"void main() { bar() }\n";
|
||||
|
||||
withShaderFile(content2, [&] (const std::string& templateName2) {
|
||||
const auto shader = mManager.getShader(templateName2, mDefines, osg::Shader::VERTEX);
|
||||
ASSERT_TRUE(shader);
|
||||
const std::string expected =
|
||||
"#version 120\n"
|
||||
"#line 0 1\n"
|
||||
"#line 0 2\n"
|
||||
"void foo() {}\n"
|
||||
"\n"
|
||||
"#line 0 0\n"
|
||||
"\n"
|
||||
"void bar() { foo() }\n"
|
||||
"\n"
|
||||
"#line 2 0\n"
|
||||
"\n"
|
||||
"void main() { bar() }\n";
|
||||
EXPECT_EQ(shader->getShaderSource(), expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ShaderManagerTest, get_shader_should_replace_defines)
|
||||
{
|
||||
const std::string content =
|
||||
"#version 120\n"
|
||||
"#define FLAG @flag\n"
|
||||
"void main() {}\n"
|
||||
;
|
||||
|
||||
withShaderFile(content, [&] (const std::string& templateName) {
|
||||
mDefines["flag"] = "1";
|
||||
const auto shader = mManager.getShader(templateName, mDefines, osg::Shader::VERTEX);
|
||||
ASSERT_TRUE(shader);
|
||||
const std::string expected =
|
||||
"#version 120\n"
|
||||
"#define FLAG 1\n"
|
||||
"void main() {}\n";
|
||||
EXPECT_EQ(shader->getShaderSource(), expected);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ShaderManagerTest, get_shader_should_expand_loop)
|
||||
{
|
||||
const std::string content =
|
||||
"#version 120\n"
|
||||
"@foreach index @list\n"
|
||||
" varying vec4 foo@index;\n"
|
||||
"@endforeach\n"
|
||||
"void main() {}\n"
|
||||
;
|
||||
|
||||
withShaderFile(content, [&] (const std::string& templateName) {
|
||||
mDefines["list"] = "1,2,3";
|
||||
const auto shader = mManager.getShader(templateName, mDefines, osg::Shader::VERTEX);
|
||||
ASSERT_TRUE(shader);
|
||||
const std::string expected =
|
||||
"#version 120\n"
|
||||
" varying vec4 foo1;\n"
|
||||
" varying vec4 foo2;\n"
|
||||
" varying vec4 foo3;\n"
|
||||
"\n"
|
||||
"#line 5\n"
|
||||
"void main() {}\n";
|
||||
EXPECT_EQ(shader->getShaderSource(), expected);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ShaderManagerTest, get_shader_should_replace_loops_with_conditions)
|
||||
{
|
||||
const std::string content =
|
||||
"#version 120\n"
|
||||
"@foreach index @list\n"
|
||||
" varying vec4 foo@index;\n"
|
||||
"@endforeach\n"
|
||||
"void main()\n"
|
||||
"{\n"
|
||||
"#ifdef BAR\n"
|
||||
"@foreach index @list\n"
|
||||
" foo@index = vec4(1.0);\n"
|
||||
"@endforeach\n"
|
||||
"#elif BAZ\n"
|
||||
"@foreach index @list\n"
|
||||
" foo@index = vec4(2.0);\n"
|
||||
"@endforeach\n"
|
||||
"#else\n"
|
||||
"@foreach index @list\n"
|
||||
" foo@index = vec4(3.0);\n"
|
||||
"@endforeach\n"
|
||||
"#endif\n"
|
||||
"}\n"
|
||||
;
|
||||
|
||||
withShaderFile(content, [&] (const std::string& templateName) {
|
||||
mDefines["list"] = "1,2,3";
|
||||
const auto shader = mManager.getShader(templateName, mDefines, osg::Shader::VERTEX);
|
||||
ASSERT_TRUE(shader);
|
||||
const std::string expected =
|
||||
"#version 120\n"
|
||||
" varying vec4 foo1;\n"
|
||||
" varying vec4 foo2;\n"
|
||||
" varying vec4 foo3;\n"
|
||||
"\n"
|
||||
"#line 5\n"
|
||||
"void main()\n"
|
||||
"{\n"
|
||||
"#ifdef BAR\n"
|
||||
" foo1 = vec4(1.0);\n"
|
||||
" foo2 = vec4(1.0);\n"
|
||||
" foo3 = vec4(1.0);\n"
|
||||
"\n"
|
||||
"#line 11\n"
|
||||
"#elif BAZ\n"
|
||||
"#line 12\n"
|
||||
" foo1 = vec4(2.0);\n"
|
||||
" foo2 = vec4(2.0);\n"
|
||||
" foo3 = vec4(2.0);\n"
|
||||
"\n"
|
||||
"#line 15\n"
|
||||
"#else\n"
|
||||
"#line 16\n"
|
||||
" foo1 = vec4(3.0);\n"
|
||||
" foo2 = vec4(3.0);\n"
|
||||
" foo3 = vec4(3.0);\n"
|
||||
"\n"
|
||||
"#line 19\n"
|
||||
"#endif\n"
|
||||
"#line 20\n"
|
||||
"}\n";
|
||||
EXPECT_EQ(shader->getShaderSource(), expected);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ShaderManagerTest, get_shader_should_fail_on_absent_template_parameters_in_single_line_comments)
|
||||
{
|
||||
const std::string content =
|
||||
"#version 120\n"
|
||||
"// #define FLAG @flag\n"
|
||||
"void main() {}\n"
|
||||
;
|
||||
|
||||
withShaderFile(content, [&] (const std::string& templateName) {
|
||||
EXPECT_FALSE(mManager.getShader(templateName, mDefines, osg::Shader::VERTEX));
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ShaderManagerTest, get_shader_should_fail_on_absent_template_parameter_in_multi_line_comments)
|
||||
{
|
||||
const std::string content =
|
||||
"#version 120\n"
|
||||
"/* #define FLAG @flag */\n"
|
||||
"void main() {}\n"
|
||||
;
|
||||
|
||||
withShaderFile(content, [&] (const std::string& templateName) {
|
||||
EXPECT_FALSE(mManager.getShader(templateName, mDefines, osg::Shader::VERTEX));
|
||||
});
|
||||
}
|
||||
}
|
|
@ -555,6 +555,9 @@ static bool is_debugger_present()
|
|||
|
||||
void crashCatcherInstall(int argc, char **argv, const std::string &crashLogPath)
|
||||
{
|
||||
if (const auto env = std::getenv("OPENMW_DISABLE_CRASH_CATCHER"))
|
||||
if (std::atol(env) != 0)
|
||||
return;
|
||||
if ((argc == 2 && strcmp(argv[1], crash_switch) == 0) || !is_debugger_present())
|
||||
{
|
||||
int s[5] = { SIGSEGV, SIGILL, SIGFPE, SIGBUS, SIGABRT };
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
#include <osg/Stats>
|
||||
|
||||
#include <numeric>
|
||||
|
||||
namespace
|
||||
{
|
||||
using DetourNavigator::ChangeType;
|
||||
|
@ -87,6 +89,9 @@ namespace DetourNavigator
|
|||
job.mChangeType = changedTile.second;
|
||||
job.mDistanceToPlayer = getManhattanDistance(changedTile.first, playerTile);
|
||||
job.mDistanceToOrigin = getManhattanDistance(changedTile.first, TilePosition {0, 0});
|
||||
job.mProcessTime = job.mChangeType == ChangeType::update
|
||||
? mLastUpdates[job.mAgentHalfExtents][job.mChangedTile] + mSettings.get().mMinUpdateInterval
|
||||
: std::chrono::steady_clock::time_point();
|
||||
|
||||
mJobs.push(std::move(job));
|
||||
}
|
||||
|
@ -100,8 +105,11 @@ namespace DetourNavigator
|
|||
|
||||
void AsyncNavMeshUpdater::wait()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mMutex);
|
||||
mDone.wait(lock, [&] { return mJobs.empty(); });
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mMutex);
|
||||
mDone.wait(lock, [&] { return mJobs.empty() && getTotalThreadJobsUnsafe() == 0; });
|
||||
}
|
||||
mProcessingTiles.wait(mProcessed, [] (const auto& v) { return v.empty(); });
|
||||
}
|
||||
|
||||
void AsyncNavMeshUpdater::reportStats(unsigned int frameNumber, osg::Stats& stats) const
|
||||
|
@ -110,7 +118,7 @@ namespace DetourNavigator
|
|||
|
||||
{
|
||||
const std::lock_guard<std::mutex> lock(mMutex);
|
||||
jobs = mJobs.size();
|
||||
jobs = mJobs.size() + getTotalThreadJobsUnsafe();
|
||||
}
|
||||
|
||||
stats.setAttribute(frameNumber, "NavMesh UpdateJobs", jobs);
|
||||
|
@ -120,7 +128,7 @@ namespace DetourNavigator
|
|||
|
||||
void AsyncNavMeshUpdater::process() throw()
|
||||
{
|
||||
Log(Debug::Debug) << "Start process navigator jobs";
|
||||
Log(Debug::Debug) << "Start process navigator jobs by thread=" << std::this_thread::get_id();
|
||||
while (!mShouldStop)
|
||||
{
|
||||
try
|
||||
|
@ -132,18 +140,21 @@ namespace DetourNavigator
|
|||
if (!processed)
|
||||
repost(std::move(*job));
|
||||
}
|
||||
else
|
||||
cleanupLastUpdates();
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
Log(Debug::Error) << "AsyncNavMeshUpdater::process exception: " << e.what();
|
||||
}
|
||||
}
|
||||
Log(Debug::Debug) << "Stop navigator jobs processing";
|
||||
Log(Debug::Debug) << "Stop navigator jobs processing by thread=" << std::this_thread::get_id();
|
||||
}
|
||||
|
||||
bool AsyncNavMeshUpdater::processJob(const Job& job)
|
||||
{
|
||||
Log(Debug::Debug) << "Process job for agent=(" << std::fixed << std::setprecision(2) << job.mAgentHalfExtents << ")";
|
||||
Log(Debug::Debug) << "Process job for agent=(" << std::fixed << std::setprecision(2) << job.mAgentHalfExtents << ")"
|
||||
" by thread=" << std::this_thread::get_id();
|
||||
|
||||
const auto start = std::chrono::steady_clock::now();
|
||||
|
||||
|
@ -170,11 +181,13 @@ namespace DetourNavigator
|
|||
const auto locked = navMeshCacheItem->lockConst();
|
||||
Log(Debug::Debug) << std::fixed << std::setprecision(2) <<
|
||||
"Cache updated for agent=(" << job.mAgentHalfExtents << ")" <<
|
||||
" tile=" << job.mChangedTile <<
|
||||
" status=" << status <<
|
||||
" generation=" << locked->getGeneration() <<
|
||||
" revision=" << locked->getNavMeshRevision() <<
|
||||
" time=" << std::chrono::duration_cast<FloatMs>(finish - start).count() << "ms" <<
|
||||
" total_time=" << std::chrono::duration_cast<FloatMs>(finish - firstStart).count() << "ms";
|
||||
" total_time=" << std::chrono::duration_cast<FloatMs>(finish - firstStart).count() << "ms"
|
||||
" thread=" << std::this_thread::get_id();
|
||||
|
||||
return isSuccess(status);
|
||||
}
|
||||
|
@ -188,42 +201,57 @@ namespace DetourNavigator
|
|||
|
||||
while (true)
|
||||
{
|
||||
const auto hasJob = [&] { return !mJobs.empty() || !threadQueue.mPushed.empty(); };
|
||||
const auto hasJob = [&] {
|
||||
return (!mJobs.empty() && mJobs.top().mProcessTime <= std::chrono::steady_clock::now())
|
||||
|| !threadQueue.mJobs.empty();
|
||||
};
|
||||
|
||||
if (!mHasJob.wait_for(lock, std::chrono::milliseconds(10), hasJob))
|
||||
{
|
||||
mFirstStart.lock()->reset();
|
||||
mDone.notify_all();
|
||||
if (mJobs.empty() && getTotalThreadJobsUnsafe() == 0)
|
||||
mDone.notify_all();
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
Log(Debug::Debug) << "Got " << mJobs.size() << " navigator jobs and "
|
||||
<< threadQueue.mJobs.size() << " thread jobs";
|
||||
<< threadQueue.mJobs.size() << " thread jobs by thread=" << std::this_thread::get_id();
|
||||
|
||||
auto job = threadQueue.mJobs.empty()
|
||||
? getJob(mJobs, mPushed)
|
||||
: getJob(threadQueue.mJobs, threadQueue.mPushed);
|
||||
? getJob(mJobs, mPushed, true)
|
||||
: getJob(threadQueue.mJobs, threadQueue.mPushed, false);
|
||||
|
||||
const auto owner = lockTile(job.mAgentHalfExtents, job.mChangedTile);
|
||||
if (!job)
|
||||
continue;
|
||||
|
||||
const auto owner = lockTile(job->mAgentHalfExtents, job->mChangedTile);
|
||||
|
||||
if (owner == threadId)
|
||||
return job;
|
||||
|
||||
postThreadJob(std::move(job), mThreadsQueues[owner]);
|
||||
postThreadJob(std::move(*job), mThreadsQueues[owner]);
|
||||
}
|
||||
}
|
||||
|
||||
AsyncNavMeshUpdater::Job AsyncNavMeshUpdater::getJob(Jobs& jobs, Pushed& pushed)
|
||||
boost::optional<AsyncNavMeshUpdater::Job> AsyncNavMeshUpdater::getJob(Jobs& jobs, Pushed& pushed, bool changeLastUpdate)
|
||||
{
|
||||
auto job = jobs.top();
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
|
||||
if (jobs.top().mProcessTime > now)
|
||||
return {};
|
||||
|
||||
Job job = std::move(jobs.top());
|
||||
jobs.pop();
|
||||
|
||||
if (changeLastUpdate && job.mChangeType == ChangeType::update)
|
||||
mLastUpdates[job.mAgentHalfExtents][job.mChangedTile] = now;
|
||||
|
||||
const auto it = pushed.find(job.mAgentHalfExtents);
|
||||
it->second.erase(job.mChangedTile);
|
||||
if (it->second.empty())
|
||||
pushed.erase(it);
|
||||
|
||||
return job;
|
||||
return {std::move(job)};
|
||||
}
|
||||
|
||||
void AsyncNavMeshUpdater::writeDebugFiles(const Job& job, const RecastMesh* recastMesh) const
|
||||
|
@ -326,5 +354,37 @@ namespace DetourNavigator
|
|||
|
||||
if (agent->second.empty())
|
||||
locked->erase(agent);
|
||||
|
||||
if (locked->empty())
|
||||
mProcessed.notify_all();
|
||||
}
|
||||
|
||||
std::size_t AsyncNavMeshUpdater::getTotalThreadJobsUnsafe() const
|
||||
{
|
||||
return std::accumulate(mThreadsQueues.begin(), mThreadsQueues.end(), std::size_t(0),
|
||||
[] (auto r, const auto& v) { return r + v.second.mJobs.size(); });
|
||||
}
|
||||
|
||||
void AsyncNavMeshUpdater::cleanupLastUpdates()
|
||||
{
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
|
||||
const std::lock_guard<std::mutex> lock(mMutex);
|
||||
|
||||
for (auto agent = mLastUpdates.begin(); agent != mLastUpdates.end();)
|
||||
{
|
||||
for (auto tile = agent->second.begin(); tile != agent->second.end();)
|
||||
{
|
||||
if (now - tile->second > mSettings.get().mMinUpdateInterval)
|
||||
tile = agent->second.erase(tile);
|
||||
else
|
||||
++tile;
|
||||
}
|
||||
|
||||
if (agent->second.empty())
|
||||
agent = mLastUpdates.erase(agent);
|
||||
else
|
||||
++agent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,6 +32,21 @@ namespace DetourNavigator
|
|||
update = 3,
|
||||
};
|
||||
|
||||
inline std::ostream& operator <<(std::ostream& stream, ChangeType value)
|
||||
{
|
||||
switch (value) {
|
||||
case ChangeType::remove:
|
||||
return stream << "ChangeType::remove";
|
||||
case ChangeType::mixed:
|
||||
return stream << "ChangeType::mixed";
|
||||
case ChangeType::add:
|
||||
return stream << "ChangeType::add";
|
||||
case ChangeType::update:
|
||||
return stream << "ChangeType::update";
|
||||
}
|
||||
return stream << "ChangeType::" << static_cast<int>(value);
|
||||
}
|
||||
|
||||
class AsyncNavMeshUpdater
|
||||
{
|
||||
public:
|
||||
|
@ -56,10 +71,11 @@ namespace DetourNavigator
|
|||
ChangeType mChangeType;
|
||||
int mDistanceToPlayer;
|
||||
int mDistanceToOrigin;
|
||||
std::chrono::steady_clock::time_point mProcessTime;
|
||||
|
||||
std::tuple<unsigned, ChangeType, int, int> getPriority() const
|
||||
std::tuple<std::chrono::steady_clock::time_point, unsigned, ChangeType, int, int> getPriority() const
|
||||
{
|
||||
return std::make_tuple(mTryNumber, mChangeType, mDistanceToPlayer, mDistanceToOrigin);
|
||||
return std::make_tuple(mProcessTime, mTryNumber, mChangeType, mDistanceToPlayer, mDistanceToOrigin);
|
||||
}
|
||||
|
||||
friend inline bool operator <(const Job& lhs, const Job& rhs)
|
||||
|
@ -86,12 +102,14 @@ namespace DetourNavigator
|
|||
mutable std::mutex mMutex;
|
||||
std::condition_variable mHasJob;
|
||||
std::condition_variable mDone;
|
||||
std::condition_variable mProcessed;
|
||||
Jobs mJobs;
|
||||
std::map<osg::Vec3f, std::set<TilePosition>> mPushed;
|
||||
Misc::ScopeGuarded<TilePosition> mPlayerTile;
|
||||
Misc::ScopeGuarded<boost::optional<std::chrono::steady_clock::time_point>> mFirstStart;
|
||||
NavMeshTilesCache mNavMeshTilesCache;
|
||||
Misc::ScopeGuarded<std::map<osg::Vec3f, std::map<TilePosition, std::thread::id>>> mProcessingTiles;
|
||||
std::map<osg::Vec3f, std::map<TilePosition, std::chrono::steady_clock::time_point>> mLastUpdates;
|
||||
std::map<std::thread::id, Queue> mThreadsQueues;
|
||||
std::vector<std::thread> mThreads;
|
||||
|
||||
|
@ -101,7 +119,7 @@ namespace DetourNavigator
|
|||
|
||||
boost::optional<Job> getNextJob();
|
||||
|
||||
static Job getJob(Jobs& jobs, Pushed& pushed);
|
||||
boost::optional<Job> getJob(Jobs& jobs, Pushed& pushed, bool changeLastUpdate);
|
||||
|
||||
void postThreadJob(Job&& job, Queue& queue);
|
||||
|
||||
|
@ -114,6 +132,10 @@ namespace DetourNavigator
|
|||
std::thread::id lockTile(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile);
|
||||
|
||||
void unlockTile(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile);
|
||||
|
||||
inline std::size_t getTotalThreadJobsUnsafe() const;
|
||||
|
||||
void cleanupLastUpdates();
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -56,12 +56,8 @@ namespace DetourNavigator
|
|||
bool NavMeshManager::updateObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform,
|
||||
const AreaType areaType)
|
||||
{
|
||||
const auto changedTiles = mRecastMeshManager.updateObject(id, shape, transform, areaType);
|
||||
if (changedTiles.empty())
|
||||
return false;
|
||||
for (const auto& tile : changedTiles)
|
||||
addChangedTile(tile, ChangeType::update);
|
||||
return true;
|
||||
return mRecastMeshManager.updateObject(id, shape, transform, areaType,
|
||||
[&] (const TilePosition& tile) { addChangedTile(tile, ChangeType::update); });
|
||||
}
|
||||
|
||||
bool NavMeshManager::removeObject(const ObjectId id)
|
||||
|
@ -195,7 +191,7 @@ namespace DetourNavigator
|
|||
mAsyncNavMeshUpdater.post(agentHalfExtents, cached, playerTile, tilesToPost);
|
||||
if (changedTiles != mChangedTiles.end())
|
||||
changedTiles->second.clear();
|
||||
Log(Debug::Debug) << "cache update posted for agent=" << agentHalfExtents <<
|
||||
Log(Debug::Debug) << "Cache update posted for agent=" << agentHalfExtents <<
|
||||
" playerTile=" << lastPlayerTile->second <<
|
||||
" recastMeshManagerRevision=" << lastRevision;
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
|
|
|
@ -40,6 +40,7 @@ namespace DetourNavigator
|
|||
navigatorSettings.mNavMeshPathPrefix = ::Settings::Manager::getString("nav mesh path prefix", "Navigator");
|
||||
navigatorSettings.mEnableRecastMeshFileNameRevision = ::Settings::Manager::getBool("enable recast mesh file name revision", "Navigator");
|
||||
navigatorSettings.mEnableNavMeshFileNameRevision = ::Settings::Manager::getBool("enable nav mesh file name revision", "Navigator");
|
||||
navigatorSettings.mMinUpdateInterval = std::chrono::milliseconds(::Settings::Manager::getInt("min update interval ms", "Navigator"));
|
||||
|
||||
return navigatorSettings;
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include <boost/optional.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <chrono>
|
||||
|
||||
namespace DetourNavigator
|
||||
{
|
||||
|
@ -38,6 +39,7 @@ namespace DetourNavigator
|
|||
std::size_t mTrianglesPerChunk = 0;
|
||||
std::string mRecastMeshPathPrefix;
|
||||
std::string mNavMeshPathPrefix;
|
||||
std::chrono::milliseconds mMinUpdateInterval;
|
||||
};
|
||||
|
||||
boost::optional<Settings> makeSettingsFromSettingsManager();
|
||||
|
|
|
@ -31,43 +31,6 @@ namespace DetourNavigator
|
|||
return result;
|
||||
}
|
||||
|
||||
std::vector<TilePosition> TileCachedRecastMeshManager::updateObject(const ObjectId id, const btCollisionShape& shape,
|
||||
const btTransform& transform, const AreaType areaType)
|
||||
{
|
||||
const auto object = mObjectsTilesPositions.find(id);
|
||||
if (object == mObjectsTilesPositions.end())
|
||||
return std::vector<TilePosition>();
|
||||
auto& currentTiles = object->second;
|
||||
const auto border = getBorderSize(mSettings);
|
||||
std::vector<TilePosition> changedTiles;
|
||||
std::set<TilePosition> newTiles;
|
||||
{
|
||||
auto tiles = mTiles.lock();
|
||||
const auto onTilePosition = [&] (const TilePosition& tilePosition)
|
||||
{
|
||||
if (currentTiles.count(tilePosition))
|
||||
{
|
||||
newTiles.insert(tilePosition);
|
||||
if (updateTile(id, transform, areaType, tilePosition, tiles.get()))
|
||||
changedTiles.push_back(tilePosition);
|
||||
}
|
||||
else if (addTile(id, shape, transform, areaType, tilePosition, border, tiles.get()))
|
||||
{
|
||||
newTiles.insert(tilePosition);
|
||||
changedTiles.push_back(tilePosition);
|
||||
}
|
||||
};
|
||||
getTilesPositions(shape, transform, mSettings, onTilePosition);
|
||||
for (const auto& tile : currentTiles)
|
||||
if (!newTiles.count(tile) && removeTile(id, tile, tiles.get()))
|
||||
changedTiles.push_back(tile);
|
||||
}
|
||||
std::swap(currentTiles, newTiles);
|
||||
if (!changedTiles.empty())
|
||||
++mRevision;
|
||||
return changedTiles;
|
||||
}
|
||||
|
||||
boost::optional<RemovedRecastMeshObject> TileCachedRecastMeshManager::removeObject(const ObjectId id)
|
||||
{
|
||||
const auto object = mObjectsTilesPositions.find(id);
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
#include "cachedrecastmeshmanager.hpp"
|
||||
#include "tileposition.hpp"
|
||||
#include "settingsutils.hpp"
|
||||
#include "gettilespositions.hpp"
|
||||
|
||||
#include <components/misc/guarded.hpp>
|
||||
|
||||
|
@ -20,8 +22,52 @@ namespace DetourNavigator
|
|||
bool addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform,
|
||||
const AreaType areaType);
|
||||
|
||||
std::vector<TilePosition> updateObject(const ObjectId id, const btCollisionShape& shape,
|
||||
const btTransform& transform, const AreaType areaType);
|
||||
template <class OnChangedTile>
|
||||
bool updateObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform,
|
||||
const AreaType areaType, OnChangedTile&& onChangedTile)
|
||||
{
|
||||
const auto object = mObjectsTilesPositions.find(id);
|
||||
if (object == mObjectsTilesPositions.end())
|
||||
return false;
|
||||
auto& currentTiles = object->second;
|
||||
const auto border = getBorderSize(mSettings);
|
||||
bool changed = false;
|
||||
std::set<TilePosition> newTiles;
|
||||
{
|
||||
auto tiles = mTiles.lock();
|
||||
const auto onTilePosition = [&] (const TilePosition& tilePosition)
|
||||
{
|
||||
if (currentTiles.count(tilePosition))
|
||||
{
|
||||
newTiles.insert(tilePosition);
|
||||
if (updateTile(id, transform, areaType, tilePosition, tiles.get()))
|
||||
{
|
||||
onChangedTile(tilePosition);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
else if (addTile(id, shape, transform, areaType, tilePosition, border, tiles.get()))
|
||||
{
|
||||
newTiles.insert(tilePosition);
|
||||
onChangedTile(tilePosition);
|
||||
changed = true;
|
||||
}
|
||||
};
|
||||
getTilesPositions(shape, transform, mSettings, onTilePosition);
|
||||
for (const auto& tile : currentTiles)
|
||||
{
|
||||
if (!newTiles.count(tile) && removeTile(id, tile, tiles.get()))
|
||||
{
|
||||
onChangedTile(tile);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
std::swap(currentTiles, newTiles);
|
||||
if (changed)
|
||||
++mRevision;
|
||||
return changed;
|
||||
}
|
||||
|
||||
boost::optional<RemovedRecastMeshObject> removeObject(const ObjectId id);
|
||||
|
||||
|
|
|
@ -32,6 +32,14 @@ ConfigurationManager::ConfigurationManager(bool silent)
|
|||
boost::filesystem::create_directories(mFixedPath.getUserDataPath());
|
||||
|
||||
mLogPath = mFixedPath.getUserConfigPath();
|
||||
|
||||
mScreenshotPath = mFixedPath.getUserDataPath() / "screenshots";
|
||||
|
||||
// probably not necessary but validate the creation of the screenshots directory and fallback to the original behavior if it fails
|
||||
boost::system::error_code dirErr;
|
||||
if (!boost::filesystem::create_directories(mScreenshotPath, dirErr) && !boost::filesystem::is_directory(mScreenshotPath)) {
|
||||
mScreenshotPath = mFixedPath.getUserDataPath();
|
||||
}
|
||||
}
|
||||
|
||||
ConfigurationManager::~ConfigurationManager()
|
||||
|
@ -196,4 +204,9 @@ const boost::filesystem::path& ConfigurationManager::getLogPath() const
|
|||
return mLogPath;
|
||||
}
|
||||
|
||||
const boost::filesystem::path& ConfigurationManager::getScreenshotPath() const
|
||||
{
|
||||
return mScreenshotPath;
|
||||
}
|
||||
|
||||
} /* namespace Cfg */
|
||||
|
|
|
@ -41,6 +41,7 @@ struct ConfigurationManager
|
|||
const boost::filesystem::path& getCachePath() const;
|
||||
|
||||
const boost::filesystem::path& getLogPath() const;
|
||||
const boost::filesystem::path& getScreenshotPath() const;
|
||||
|
||||
private:
|
||||
typedef Files::FixedPath<> FixedPathType;
|
||||
|
@ -57,6 +58,7 @@ struct ConfigurationManager
|
|||
FixedPathType mFixedPath;
|
||||
|
||||
boost::filesystem::path mLogPath;
|
||||
boost::filesystem::path mScreenshotPath;
|
||||
|
||||
TokensMappingContainer mTokensMapping;
|
||||
|
||||
|
|
|
@ -28,22 +28,6 @@ namespace Interpreter
|
|||
return;
|
||||
}
|
||||
|
||||
case 1:
|
||||
{
|
||||
int opcode = (code>>24) & 0x3f;
|
||||
unsigned int arg0 = (code>>16) & 0xfff;
|
||||
unsigned int arg1 = code & 0xfff;
|
||||
|
||||
std::map<int, Opcode2 *>::iterator iter = mSegment1.find (opcode);
|
||||
|
||||
if (iter==mSegment1.end())
|
||||
abortUnknownCode (1, opcode);
|
||||
|
||||
iter->second->execute (mRuntime, arg0, arg1);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
case 2:
|
||||
{
|
||||
int opcode = (code>>20) & 0x3ff;
|
||||
|
@ -79,22 +63,6 @@ namespace Interpreter
|
|||
return;
|
||||
}
|
||||
|
||||
case 0x31:
|
||||
{
|
||||
int opcode = (code>>16) & 0x3ff;
|
||||
unsigned int arg0 = (code>>8) & 0xff;
|
||||
unsigned int arg1 = code & 0xff;
|
||||
|
||||
std::map<int, Opcode2 *>::iterator iter = mSegment4.find (opcode);
|
||||
|
||||
if (iter==mSegment4.end())
|
||||
abortUnknownCode (4, opcode);
|
||||
|
||||
iter->second->execute (mRuntime, arg0, arg1);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x32:
|
||||
{
|
||||
int opcode = code & 0x3ffffff;
|
||||
|
@ -161,10 +129,6 @@ namespace Interpreter
|
|||
iter!=mSegment0.end(); ++iter)
|
||||
delete iter->second;
|
||||
|
||||
for (std::map<int, Opcode2 *>::iterator iter (mSegment1.begin());
|
||||
iter!=mSegment1.end(); ++iter)
|
||||
delete iter->second;
|
||||
|
||||
for (std::map<int, Opcode1 *>::iterator iter (mSegment2.begin());
|
||||
iter!=mSegment2.end(); ++iter)
|
||||
delete iter->second;
|
||||
|
@ -173,10 +137,6 @@ namespace Interpreter
|
|||
iter!=mSegment3.end(); ++iter)
|
||||
delete iter->second;
|
||||
|
||||
for (std::map<int, Opcode2 *>::iterator iter (mSegment4.begin());
|
||||
iter!=mSegment4.end(); ++iter)
|
||||
delete iter->second;
|
||||
|
||||
for (std::map<int, Opcode0 *>::iterator iter (mSegment5.begin());
|
||||
iter!=mSegment5.end(); ++iter)
|
||||
delete iter->second;
|
||||
|
@ -188,12 +148,6 @@ namespace Interpreter
|
|||
mSegment0.insert (std::make_pair (code, opcode));
|
||||
}
|
||||
|
||||
void Interpreter::installSegment1 (int code, Opcode2 *opcode)
|
||||
{
|
||||
assert(mSegment1.find(code) == mSegment1.end());
|
||||
mSegment1.insert (std::make_pair (code, opcode));
|
||||
}
|
||||
|
||||
void Interpreter::installSegment2 (int code, Opcode1 *opcode)
|
||||
{
|
||||
assert(mSegment2.find(code) == mSegment2.end());
|
||||
|
@ -206,12 +160,6 @@ namespace Interpreter
|
|||
mSegment3.insert (std::make_pair (code, opcode));
|
||||
}
|
||||
|
||||
void Interpreter::installSegment4 (int code, Opcode2 *opcode)
|
||||
{
|
||||
assert(mSegment4.find(code) == mSegment4.end());
|
||||
mSegment4.insert (std::make_pair (code, opcode));
|
||||
}
|
||||
|
||||
void Interpreter::installSegment5 (int code, Opcode0 *opcode)
|
||||
{
|
||||
assert(mSegment5.find(code) == mSegment5.end());
|
||||
|
|
|
@ -11,7 +11,6 @@ namespace Interpreter
|
|||
{
|
||||
class Opcode0;
|
||||
class Opcode1;
|
||||
class Opcode2;
|
||||
|
||||
class Interpreter
|
||||
{
|
||||
|
@ -19,10 +18,8 @@ namespace Interpreter
|
|||
bool mRunning;
|
||||
Runtime mRuntime;
|
||||
std::map<int, Opcode1 *> mSegment0;
|
||||
std::map<int, Opcode2 *> mSegment1;
|
||||
std::map<int, Opcode1 *> mSegment2;
|
||||
std::map<int, Opcode1 *> mSegment3;
|
||||
std::map<int, Opcode2 *> mSegment4;
|
||||
std::map<int, Opcode0 *> mSegment5;
|
||||
|
||||
// not implemented
|
||||
|
@ -48,18 +45,12 @@ namespace Interpreter
|
|||
void installSegment0 (int code, Opcode1 *opcode);
|
||||
///< ownership of \a opcode is transferred to *this.
|
||||
|
||||
void installSegment1 (int code, Opcode2 *opcode);
|
||||
///< ownership of \a opcode is transferred to *this.
|
||||
|
||||
void installSegment2 (int code, Opcode1 *opcode);
|
||||
///< ownership of \a opcode is transferred to *this.
|
||||
|
||||
void installSegment3 (int code, Opcode1 *opcode);
|
||||
///< ownership of \a opcode is transferred to *this.
|
||||
|
||||
void installSegment4 (int code, Opcode2 *opcode);
|
||||
///< ownership of \a opcode is transferred to *this.
|
||||
|
||||
void installSegment5 (int code, Opcode0 *opcode);
|
||||
///< ownership of \a opcode is transferred to *this.
|
||||
|
||||
|
|
|
@ -25,16 +25,6 @@ namespace Interpreter
|
|||
virtual ~Opcode1() {}
|
||||
};
|
||||
|
||||
/// opcode for 2 arguments
|
||||
class Opcode2
|
||||
{
|
||||
public:
|
||||
|
||||
virtual void execute (Runtime& runtime, unsigned int arg1, unsigned int arg2) = 0;
|
||||
|
||||
virtual ~Opcode2() {}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
#include <condition_variable>
|
||||
|
||||
namespace Misc
|
||||
{
|
||||
|
@ -79,6 +80,13 @@ namespace Misc
|
|||
return Locked<const T>(mMutex, mValue);
|
||||
}
|
||||
|
||||
template <class Predicate>
|
||||
void wait(std::condition_variable& cv, Predicate&& predicate)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mMutex);
|
||||
cv.wait(lock, [&] { return predicate(mValue); });
|
||||
}
|
||||
|
||||
private:
|
||||
std::mutex mMutex;
|
||||
T mValue;
|
||||
|
|
|
@ -170,6 +170,17 @@ namespace NifOsg
|
|||
class CollisionSwitch : public osg::MatrixTransform
|
||||
{
|
||||
public:
|
||||
CollisionSwitch() : osg::MatrixTransform()
|
||||
{
|
||||
}
|
||||
|
||||
CollisionSwitch(const CollisionSwitch& copy, const osg::CopyOp& copyop)
|
||||
: osg::MatrixTransform(copy, copyop)
|
||||
{
|
||||
}
|
||||
|
||||
META_Node(NifOsg, CollisionSwitch)
|
||||
|
||||
CollisionSwitch(const osg::Matrixf& transformations, bool enabled) : osg::MatrixTransform(transformations)
|
||||
{
|
||||
setEnabled(enabled);
|
||||
|
@ -177,7 +188,7 @@ namespace NifOsg
|
|||
|
||||
void setEnabled(bool enabled)
|
||||
{
|
||||
setNodeMask(enabled ? ~0 : 0);
|
||||
setNodeMask(enabled ? ~0 : Loader::getIntersectionDisabledNodeMask());
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -204,6 +215,18 @@ namespace NifOsg
|
|||
return sHiddenNodeMask;
|
||||
}
|
||||
|
||||
unsigned int Loader::sIntersectionDisabledNodeMask = ~0;
|
||||
|
||||
void Loader::setIntersectionDisabledNodeMask(unsigned int mask)
|
||||
{
|
||||
sIntersectionDisabledNodeMask = mask;
|
||||
}
|
||||
|
||||
unsigned int Loader::getIntersectionDisabledNodeMask()
|
||||
{
|
||||
return sIntersectionDisabledNodeMask;
|
||||
}
|
||||
|
||||
class LoaderImpl
|
||||
{
|
||||
public:
|
||||
|
@ -219,6 +242,9 @@ namespace NifOsg
|
|||
size_t mFirstRootTextureIndex = -1;
|
||||
bool mFoundFirstRootTexturingProperty = false;
|
||||
|
||||
// This is used to queue emitters that weren't attached to their node yet.
|
||||
std::vector<std::pair<size_t, osg::ref_ptr<Emitter>>> mEmitterQueue;
|
||||
|
||||
static void loadKf(Nif::NIFFilePtr nif, KeyframeHolder& target)
|
||||
{
|
||||
if(nif->numRoots() < 1)
|
||||
|
@ -290,6 +316,9 @@ namespace NifOsg
|
|||
|
||||
osg::ref_ptr<osg::Node> created = handleNode(nifNode, nullptr, imageManager, std::vector<unsigned int>(), 0, false, false, false, &textkeys->mTextKeys);
|
||||
|
||||
// Attach particle emitters to their nodes which should all be loaded by now.
|
||||
handleQueuedParticleEmitters(created, nif);
|
||||
|
||||
if (nif->getUseSkinning())
|
||||
{
|
||||
osg::ref_ptr<SceneUtil::Skeleton> skel = new SceneUtil::Skeleton;
|
||||
|
@ -460,17 +489,8 @@ namespace NifOsg
|
|||
osg::ref_ptr<osg::Group> node;
|
||||
osg::Object::DataVariance dataVariance = osg::Object::UNSPECIFIED;
|
||||
|
||||
// TODO: it is unclear how to handle transformations of LOD nodes and controllers for them.
|
||||
switch (nifNode->recType)
|
||||
{
|
||||
case Nif::RC_NiLODNode:
|
||||
{
|
||||
const Nif::NiLODNode* niLodNode = static_cast<const Nif::NiLODNode*>(nifNode);
|
||||
node = handleLodNode(niLodNode);
|
||||
dataVariance = osg::Object::DYNAMIC;
|
||||
break;
|
||||
}
|
||||
case Nif::RC_NiSwitchNode:
|
||||
case Nif::RC_NiAutoNormalParticles:
|
||||
case Nif::RC_NiRotatingParticles:
|
||||
// Leaf nodes in the NIF hierarchy, so won't be able to dynamically attach children.
|
||||
|
@ -591,7 +611,10 @@ namespace NifOsg
|
|||
{
|
||||
bool hasVisController = false;
|
||||
for (Nif::ControllerPtr ctrl = nifNode->controller; !ctrl.empty(); ctrl = ctrl->next)
|
||||
hasVisController = (ctrl->recType == Nif::RC_NiVisController);
|
||||
{
|
||||
if (hasVisController |= (ctrl->recType == Nif::RC_NiVisController))
|
||||
break;
|
||||
}
|
||||
|
||||
if (!hasVisController)
|
||||
skipMeshes = true; // skip child meshes, but still create the child node hierarchy for animating collision shapes
|
||||
|
@ -648,6 +671,11 @@ namespace NifOsg
|
|||
&& !nifNode->controller.empty() && node->getDataVariance() == osg::Object::DYNAMIC)
|
||||
handleNodeControllers(nifNode, static_cast<osg::MatrixTransform*>(node.get()), animflags);
|
||||
|
||||
// LOD and Switch nodes must be wrapped by a transform (the current node) to support transformations properly
|
||||
// and we need to attach their children to the osg::LOD/osg::Switch nodes
|
||||
// but we must return that transform to the caller of handleNode instead of the actual LOD/Switch nodes.
|
||||
osg::ref_ptr<osg::Group> currentNode = node;
|
||||
|
||||
if (nifNode->recType == Nif::RC_NiSwitchNode)
|
||||
{
|
||||
const Nif::NiSwitchNode* niSwitchNode = static_cast<const Nif::NiSwitchNode*>(nifNode);
|
||||
|
@ -658,7 +686,14 @@ namespace NifOsg
|
|||
else if (niSwitchNode->name == Constants::HerbalismLabel && !SceneUtil::hasUserDescription(rootNode, Constants::HerbalismLabel))
|
||||
rootNode->getOrCreateUserDataContainer()->addDescription(Constants::HerbalismLabel);
|
||||
|
||||
node = switchNode;
|
||||
currentNode = switchNode;
|
||||
}
|
||||
else if (nifNode->recType == Nif::RC_NiLODNode)
|
||||
{
|
||||
const Nif::NiLODNode* niLodNode = static_cast<const Nif::NiLODNode*>(nifNode);
|
||||
osg::ref_ptr<osg::LOD> lodNode = handleLodNode(niLodNode);
|
||||
node->addChild(lodNode);
|
||||
currentNode = lodNode;
|
||||
}
|
||||
|
||||
const Nif::NiNode *ninode = dynamic_cast<const Nif::NiNode*>(nifNode);
|
||||
|
@ -668,14 +703,14 @@ namespace NifOsg
|
|||
for (size_t i = 0; i < effects.length(); ++i)
|
||||
{
|
||||
if (!effects[i].empty())
|
||||
handleEffect(effects[i].getPtr(), node, imageManager);
|
||||
handleEffect(effects[i].getPtr(), currentNode, imageManager);
|
||||
}
|
||||
|
||||
const Nif::NodeList &children = ninode->children;
|
||||
for(size_t i = 0;i < children.length();++i)
|
||||
{
|
||||
if(!children[i].empty())
|
||||
handleNode(children[i].getPtr(), node, imageManager, boundTextures, animflags, skipMeshes, hasMarkers, isAnimated, textKeys, rootNode);
|
||||
handleNode(children[i].getPtr(), currentNode, imageManager, boundTextures, animflags, skipMeshes, hasMarkers, isAnimated, textKeys, rootNode);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -979,6 +1014,27 @@ namespace NifOsg
|
|||
return emitter;
|
||||
}
|
||||
|
||||
void handleQueuedParticleEmitters(osg::Node* rootNode, Nif::NIFFilePtr nif)
|
||||
{
|
||||
for (const auto& emitterPair : mEmitterQueue)
|
||||
{
|
||||
size_t recIndex = emitterPair.first;
|
||||
FindGroupByRecIndex findEmitterNode(recIndex);
|
||||
rootNode->accept(findEmitterNode);
|
||||
osg::Group* emitterNode = findEmitterNode.mFound;
|
||||
if (!emitterNode)
|
||||
{
|
||||
nif->warn("Failed to find particle emitter emitter node (node record index " + std::to_string(recIndex) + ")");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Emitter attached to the emitter node. Note one side effect of the emitter using the CullVisitor is that hiding its node
|
||||
// actually causes the emitter to stop firing. Convenient, because MW behaves this way too!
|
||||
emitterNode->addChild(emitterPair.second);
|
||||
}
|
||||
mEmitterQueue.clear();
|
||||
}
|
||||
|
||||
void handleParticleSystem(const Nif::Node *nifNode, osg::Group *parentNode, SceneUtil::CompositeStateSetUpdater* composite, int animflags, osg::Node* rootNode)
|
||||
{
|
||||
osg::ref_ptr<ParticleSystem> partsys (new ParticleSystem);
|
||||
|
@ -1028,22 +1084,10 @@ namespace NifOsg
|
|||
emitter->setParticleSystem(partsys);
|
||||
emitter->setReferenceFrame(osgParticle::ParticleProcessor::RELATIVE_RF);
|
||||
|
||||
// Note: we assume that the Emitter node is placed *before* the Particle node in the scene graph.
|
||||
// This seems to be true for all NIF files in the game that I've checked, suggesting that NIFs work similar to OSG with regards to update order.
|
||||
// If something ever violates this assumption, the worst that could happen is the culling being one frame late, which wouldn't be a disaster.
|
||||
|
||||
FindGroupByRecIndex find (partctrl->emitter->recIndex);
|
||||
rootNode->accept(find);
|
||||
if (!find.mFound)
|
||||
{
|
||||
Log(Debug::Info) << "can't find emitter node, wrong node order? in " << mFilename;
|
||||
return;
|
||||
}
|
||||
osg::Group* emitterNode = find.mFound;
|
||||
|
||||
// Emitter attached to the emitter node. Note one side effect of the emitter using the CullVisitor is that hiding its node
|
||||
// actually causes the emitter to stop firing. Convenient, because MW behaves this way too!
|
||||
emitterNode->addChild(emitter);
|
||||
// The emitter node may not actually be handled yet, so let's delay attaching the emitter to a later moment.
|
||||
// If the emitter node is placed later than the particle node, it'll have a single frame delay in particle processing.
|
||||
// But that shouldn't be a game-breaking issue.
|
||||
mEmitterQueue.emplace_back(partctrl->emitter->recIndex, emitter);
|
||||
|
||||
osg::ref_ptr<ParticleSystemController> callback(new ParticleSystemController(partctrl));
|
||||
setupController(partctrl, callback, animflags);
|
||||
|
@ -1060,14 +1104,14 @@ namespace NifOsg
|
|||
partsys->update(0.0, nv);
|
||||
}
|
||||
|
||||
// affectors must be attached *after* the emitter in the scene graph for correct update order
|
||||
// affectors should be attached *after* the emitter in the scene graph for correct update order
|
||||
// attach to same node as the ParticleSystem, we need osgParticle Operators to get the correct
|
||||
// localToWorldMatrix for transforming to particle space
|
||||
handleParticlePrograms(partctrl->affectors, partctrl->colliders, parentNode, partsys.get(), rf);
|
||||
|
||||
std::vector<const Nif::Property*> drawableProps;
|
||||
collectDrawableProperties(nifNode, drawableProps);
|
||||
applyDrawableProperties(parentNode, drawableProps, composite, true, animflags, true);
|
||||
applyDrawableProperties(parentNode, drawableProps, composite, true, animflags);
|
||||
|
||||
// particle system updater (after the emitters and affectors in the scene graph)
|
||||
// I think for correct culling needs to be *before* the ParticleSystem, though osg examples do it the other way
|
||||
|
@ -1159,7 +1203,7 @@ namespace NifOsg
|
|||
// above the actual renderable would be tedious.
|
||||
std::vector<const Nif::Property*> drawableProps;
|
||||
collectDrawableProperties(nifNode, drawableProps);
|
||||
applyDrawableProperties(parentNode, drawableProps, composite, vertexColorsPresent, animflags, false);
|
||||
applyDrawableProperties(parentNode, drawableProps, composite, vertexColorsPresent, animflags);
|
||||
}
|
||||
|
||||
void handleTriShape(const Nif::Node* nifNode, osg::Group* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector<unsigned int>& boundTextures, int animflags)
|
||||
|
@ -1711,7 +1755,7 @@ namespace NifOsg
|
|||
}
|
||||
|
||||
void applyDrawableProperties(osg::Node* node, const std::vector<const Nif::Property*>& properties, SceneUtil::CompositeStateSetUpdater* composite,
|
||||
bool hasVertexColors, int animflags, bool particleMaterial)
|
||||
bool hasVertexColors, int animflags)
|
||||
{
|
||||
osg::StateSet* stateset = node->getOrCreateStateSet();
|
||||
|
||||
|
|
|
@ -79,8 +79,14 @@ namespace NifOsg
|
|||
static void setHiddenNodeMask(unsigned int mask);
|
||||
static unsigned int getHiddenNodeMask();
|
||||
|
||||
// Set the mask to use for nodes that ignore the crosshair intersection. The default is the default node mask.
|
||||
// This is used for NiCollisionSwitch nodes with NiCollisionSwitch state set to disabled.
|
||||
static void setIntersectionDisabledNodeMask(unsigned int mask);
|
||||
static unsigned int getIntersectionDisabledNodeMask();
|
||||
|
||||
private:
|
||||
static unsigned int sHiddenNodeMask;
|
||||
static unsigned int sIntersectionDisabledNodeMask;
|
||||
static bool sShowMarkers;
|
||||
};
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ namespace Resource
|
|||
ImageManager::ImageManager(const VFS::Manager *vfs)
|
||||
: ResourceManager(vfs)
|
||||
, mWarningImage(createWarningImage())
|
||||
, mOptions(new osgDB::Options("dds_flip dds_dxt1_detect_rgba"))
|
||||
, mOptions(new osgDB::Options("dds_flip dds_dxt1_detect_rgba ignoreTga2Fields"))
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
|
||||
#include <osgText/Text>
|
||||
|
||||
#include <osgDB/Registry>
|
||||
|
||||
#include <osgViewer/Viewer>
|
||||
#include <osgViewer/Renderer>
|
||||
|
||||
|
@ -32,12 +34,16 @@ StatsHandler::StatsHandler():
|
|||
|
||||
_resourceStatsChildNum = 0;
|
||||
|
||||
_font = osgMyGUI::DataManager::getInstance().getDataPath("DejaVuLGCSansMono.ttf");
|
||||
if (osgDB::Registry::instance()->getReaderWriterForExtension("ttf"))
|
||||
_font = osgMyGUI::DataManager::getInstance().getDataPath("DejaVuLGCSansMono.ttf");
|
||||
}
|
||||
|
||||
Profiler::Profiler()
|
||||
{
|
||||
_font = osgMyGUI::DataManager::getInstance().getDataPath("DejaVuLGCSansMono.ttf");
|
||||
if (osgDB::Registry::instance()->getReaderWriterForExtension("ttf"))
|
||||
_font = osgMyGUI::DataManager::getInstance().getDataPath("DejaVuLGCSansMono.ttf");
|
||||
else
|
||||
_font = "";
|
||||
|
||||
setKeyEventTogglesOnScreenStats(osgGA::GUIEventAdapter::KEY_F3);
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ namespace SceneUtil
|
|||
if (const osgParticle::ParticleSystemUpdater* updater = dynamic_cast<const osgParticle::ParticleSystemUpdater*>(node))
|
||||
{
|
||||
osgParticle::ParticleSystemUpdater* cloned = new osgParticle::ParticleSystemUpdater(*updater, osg::CopyOp::SHALLOW_COPY);
|
||||
mMap2[cloned] = updater->getParticleSystem(0);
|
||||
mUpdaterToOldPs[cloned] = updater->getParticleSystem(0);
|
||||
return cloned;
|
||||
}
|
||||
return osg::CopyOp::operator()(node);
|
||||
|
@ -69,7 +69,16 @@ namespace SceneUtil
|
|||
osgParticle::ParticleProcessor* CopyOp::operator() (const osgParticle::ParticleProcessor* processor) const
|
||||
{
|
||||
osgParticle::ParticleProcessor* cloned = osg::clone(processor, osg::CopyOp::DEEP_COPY_CALLBACKS);
|
||||
mMap[cloned] = processor->getParticleSystem();
|
||||
for (const auto& oldPsNewPsPair : mOldPsToNewPs)
|
||||
{
|
||||
if (processor->getParticleSystem() == oldPsNewPsPair.first)
|
||||
{
|
||||
cloned->setParticleSystem(oldPsNewPsPair.second);
|
||||
return cloned;
|
||||
}
|
||||
}
|
||||
|
||||
mProcessorToOldPs[cloned] = processor->getParticleSystem();
|
||||
return cloned;
|
||||
}
|
||||
|
||||
|
@ -77,22 +86,25 @@ namespace SceneUtil
|
|||
{
|
||||
osgParticle::ParticleSystem* cloned = osg::clone(partsys, *this);
|
||||
|
||||
for (std::map<osgParticle::ParticleProcessor*, const osgParticle::ParticleSystem*>::const_iterator it = mMap.begin(); it != mMap.end(); ++it)
|
||||
for (const auto& processorPsPair : mProcessorToOldPs)
|
||||
{
|
||||
if (it->second == partsys)
|
||||
if (processorPsPair.second == partsys)
|
||||
{
|
||||
it->first->setParticleSystem(cloned);
|
||||
processorPsPair.first->setParticleSystem(cloned);
|
||||
}
|
||||
}
|
||||
for (std::map<osgParticle::ParticleSystemUpdater*, const osgParticle::ParticleSystem*>::const_iterator it = mMap2.begin(); it != mMap2.end(); ++it)
|
||||
for (const auto& updaterPsPair : mUpdaterToOldPs)
|
||||
{
|
||||
if (it->second == partsys)
|
||||
if (updaterPsPair.second == partsys)
|
||||
{
|
||||
osgParticle::ParticleSystemUpdater* updater = it->first;
|
||||
osgParticle::ParticleSystemUpdater* updater = updaterPsPair.first;
|
||||
updater->removeParticleSystem(updater->getParticleSystem(0));
|
||||
updater->addParticleSystem(cloned);
|
||||
}
|
||||
}
|
||||
// In rare situations a particle processor may be placed after the particle system in the scene graph.
|
||||
mOldPsToNewPs[partsys] = cloned;
|
||||
|
||||
return cloned;
|
||||
}
|
||||
|
||||
|
|
|
@ -35,10 +35,11 @@ namespace SceneUtil
|
|||
virtual osg::Object* operator ()(const osg::Object* node) const;
|
||||
|
||||
private:
|
||||
// maps new ParticleProcessor to their old ParticleSystem pointer
|
||||
// maps new pointers to their old pointers
|
||||
// a little messy, but I think this should be the most efficient way
|
||||
mutable std::map<osgParticle::ParticleProcessor*, const osgParticle::ParticleSystem*> mMap;
|
||||
mutable std::map<osgParticle::ParticleSystemUpdater*, const osgParticle::ParticleSystem*> mMap2;
|
||||
mutable std::map<osgParticle::ParticleProcessor*, const osgParticle::ParticleSystem*> mProcessorToOldPs;
|
||||
mutable std::map<osgParticle::ParticleSystemUpdater*, const osgParticle::ParticleSystem*> mUpdaterToOldPs;
|
||||
mutable std::map<const osgParticle::ParticleSystem*, osgParticle::ParticleSystem*> mOldPsToNewPs;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -7,9 +7,6 @@
|
|||
#include <osg/Group>
|
||||
#include <osg/LineWidth>
|
||||
|
||||
#define OPENMW_TO_STRING(X) #X
|
||||
#define OPENMW_LINE_STRING OPENMW_TO_STRING(__LINE__)
|
||||
|
||||
namespace
|
||||
{
|
||||
using DetourNavigator::operator<<;
|
||||
|
@ -121,6 +118,3 @@ namespace SceneUtil
|
|||
mColors->push_back(value);
|
||||
}
|
||||
}
|
||||
|
||||
#undef OPENMW_TO_STRING
|
||||
#undef OPENMW_LINE_STRING
|
||||
|
|
|
@ -872,6 +872,15 @@ void SceneUtil::MWShadowTechnique::setupCastingShader(Shader::ShaderManager & sh
|
|||
|
||||
_castingProgram->addShader(shaderManager.getShader("shadowcasting_vertex.glsl", Shader::ShaderManager::DefineMap(), osg::Shader::VERTEX));
|
||||
_castingProgram->addShader(shaderManager.getShader("shadowcasting_fragment.glsl", Shader::ShaderManager::DefineMap(), osg::Shader::FRAGMENT));
|
||||
|
||||
_shadowMapAlphaTestDisableUniform = shaderManager.getShadowMapAlphaTestDisableUniform();
|
||||
_shadowMapAlphaTestDisableUniform->setName("alphaTestShadows");
|
||||
_shadowMapAlphaTestDisableUniform->setType(osg::Uniform::BOOL);
|
||||
_shadowMapAlphaTestDisableUniform->set(false);
|
||||
|
||||
shaderManager.getShadowMapAlphaTestEnableUniform()->setName("alphaTestShadows");
|
||||
shaderManager.getShadowMapAlphaTestEnableUniform()->setType(osg::Uniform::BOOL);
|
||||
shaderManager.getShadowMapAlphaTestEnableUniform()->set(true);
|
||||
}
|
||||
|
||||
MWShadowTechnique::ViewDependentData* MWShadowTechnique::createViewDependentData(osgUtil::CullVisitor* /*cv*/)
|
||||
|
@ -1570,6 +1579,7 @@ void MWShadowTechnique::createShaders()
|
|||
// The casting program uses a sampler, so to avoid undefined behaviour, we must bind a dummy texture in case no other is supplied
|
||||
_shadowCastingStateSet->setTextureAttributeAndModes(0, _fallbackBaseTexture.get(), osg::StateAttribute::ON);
|
||||
_shadowCastingStateSet->addUniform(new osg::Uniform("useDiffuseMapForShadowAlpha", false));
|
||||
_shadowCastingStateSet->addUniform(_shadowMapAlphaTestDisableUniform);
|
||||
|
||||
_shadowCastingStateSet->setMode(GL_DEPTH_CLAMP, osg::StateAttribute::ON);
|
||||
|
||||
|
|
|
@ -286,6 +286,7 @@ namespace SceneUtil {
|
|||
|
||||
osg::ref_ptr<DebugHUD> _debugHud;
|
||||
osg::ref_ptr<osg::Program> _castingProgram;
|
||||
osg::ref_ptr<osg::Uniform> _shadowMapAlphaTestDisableUniform;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -735,20 +735,6 @@ bool Optimizer::CombineStaticTransformsVisitor::removeTransforms(osg::Node* node
|
|||
// RemoveEmptyNodes.
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Optimizer::RemoveEmptyNodesVisitor::apply(osg::Switch& switchNode)
|
||||
{
|
||||
// We should keep all switch child nodes since they reflect different switch states.
|
||||
for (unsigned int i=0; i<switchNode.getNumChildren(); ++i)
|
||||
traverse(*switchNode.getChild(i));
|
||||
}
|
||||
|
||||
void Optimizer::RemoveEmptyNodesVisitor::apply(osg::LOD& lod)
|
||||
{
|
||||
// don't remove any direct children of the LOD because they are used to define each LOD level.
|
||||
for (unsigned int i=0; i<lod.getNumChildren(); ++i)
|
||||
traverse(*lod.getChild(i));
|
||||
}
|
||||
|
||||
void Optimizer::RemoveEmptyNodesVisitor::apply(osg::Group& group)
|
||||
{
|
||||
if (group.getNumParents()>0)
|
||||
|
@ -787,8 +773,11 @@ void Optimizer::RemoveEmptyNodesVisitor::removeEmptyNodes()
|
|||
++pitr)
|
||||
{
|
||||
osg::Group* parent = *pitr;
|
||||
parent->removeChild(nodeToRemove.get());
|
||||
if (parent->getNumChildren()==0 && isOperationPermissibleForObject(parent)) newEmptyGroups.insert(parent);
|
||||
if (!parent->asSwitch() && !dynamic_cast<osg::LOD*>(parent))
|
||||
{
|
||||
parent->removeChild(nodeToRemove.get());
|
||||
if (parent->getNumChildren()==0 && isOperationPermissibleForObject(parent)) newEmptyGroups.insert(parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -321,8 +321,6 @@ class Optimizer
|
|||
BaseOptimizerVisitor(optimizer, REMOVE_REDUNDANT_NODES) {}
|
||||
|
||||
virtual void apply(osg::Group& group);
|
||||
virtual void apply(osg::LOD& lod);
|
||||
virtual void apply(osg::Switch& switchNode);
|
||||
|
||||
void removeEmptyNodes();
|
||||
|
||||
|
|
|
@ -109,23 +109,29 @@ void registerSerializers()
|
|||
const char* ignore[] = {
|
||||
"MWRender::PtrHolder",
|
||||
"Resource::TemplateRef",
|
||||
"SceneUtil::CompositeStateSetUpdater",
|
||||
"SceneUtil::LightListCallback",
|
||||
"SceneUtil::LightManagerUpdateCallback",
|
||||
"SceneUtil::UpdateRigBounds",
|
||||
"SceneUtil::UpdateRigGeometry",
|
||||
"SceneUtil::LightSource",
|
||||
"SceneUtil::StateSetUpdater",
|
||||
"SceneUtil::DisableLight",
|
||||
"SceneUtil::MWShadowTechnique",
|
||||
"NifOsg::NodeUserData",
|
||||
"NifOsg::FlipController",
|
||||
"NifOsg::KeyframeController",
|
||||
"NifOsg::TextKeyMapHolder",
|
||||
"NifOsg::Emitter",
|
||||
"NifOsg::ParticleColorAffector",
|
||||
"NifOsg::ParticleSystem",
|
||||
"NifOsg::GravityAffector",
|
||||
"NifOsg::GrowFadeAffector",
|
||||
"NifOsg::InverseWorldMatrix",
|
||||
"NifOsg::StaticBoundingBoxCallback",
|
||||
"NifOsg::GeomMorpherController",
|
||||
"NifOsg::UpdateMorphGeometry",
|
||||
"NifOsg::CollisionSwitch",
|
||||
"osgMyGUI::Drawable",
|
||||
"osg::DrawCallback",
|
||||
"osgOQ::ClearQueriesCallback",
|
||||
|
|
|
@ -40,6 +40,8 @@ namespace SceneUtil
|
|||
mShadowSettings->setMinimumShadowMapNearFarRatio(Settings::Manager::getFloat("minimum lispsm near far ratio", "Shadows"));
|
||||
if (Settings::Manager::getBool("compute tight scene bounds", "Shadows"))
|
||||
mShadowSettings->setComputeNearFarModeOverride(osg::CullSettings::COMPUTE_NEAR_FAR_USING_PRIMITIVES);
|
||||
else
|
||||
mShadowSettings->setComputeNearFarModeOverride(osg::CullSettings::COMPUTE_NEAR_FAR_USING_BOUNDING_VOLUMES);
|
||||
|
||||
int mapres = Settings::Manager::getInt("shadow map resolution", "Shadows");
|
||||
mShadowSettings->setTextureSize(osg::Vec2s(mapres, mapres));
|
||||
|
@ -95,6 +97,7 @@ namespace SceneUtil
|
|||
|
||||
mShadowedScene->addChild(sceneRoot);
|
||||
rootNode->addChild(mShadowedScene);
|
||||
mShadowedScene->setNodeMask(sceneRoot->getNodeMask());
|
||||
|
||||
mShadowSettings = mShadowedScene->getShadowSettings();
|
||||
setupShadowSettings();
|
||||
|
|
|
@ -7,6 +7,12 @@
|
|||
|
||||
namespace SceneUtil
|
||||
{
|
||||
// disable nonsense test against a worldsize bb what will always pass
|
||||
class WaterBoundCallback : public osg::Drawable::ComputeBoundingBoxCallback
|
||||
{
|
||||
virtual osg::BoundingBox computeBound(const osg::Drawable&) const { return osg::BoundingBox(); }
|
||||
};
|
||||
|
||||
osg::ref_ptr<osg::Geometry> createWaterGeometry(float size, int segments, float textureRepeats)
|
||||
{
|
||||
osg::ref_ptr<osg::Vec3Array> verts (new osg::Vec3Array);
|
||||
|
@ -51,6 +57,8 @@ namespace SceneUtil
|
|||
waterGeom->setNormalArray(normal, osg::Array::BIND_OVERALL);
|
||||
|
||||
waterGeom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS,0,verts->size()));
|
||||
waterGeom->setComputeBoundingBoxCallback(new WaterBoundCallback);
|
||||
waterGeom->setCullingActive(false);
|
||||
return waterGeom;
|
||||
}
|
||||
|
||||
|
@ -74,6 +82,9 @@ namespace SceneUtil
|
|||
|
||||
stateset->setRenderBinDetails(renderBin, "RenderBin");
|
||||
|
||||
// Let the shader know we're dealing with simple water here.
|
||||
stateset->addUniform(new osg::Uniform("simpleWater", true));
|
||||
|
||||
return stateset;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ void SceneUtil::writeScene(osg::Node *node, const std::string& filename, const s
|
|||
|
||||
osg::ref_ptr<osgDB::Options> options = new osgDB::Options;
|
||||
options->setPluginStringData("fileType", format);
|
||||
options->setPluginStringData("WriteImageHint", "UseExternal");
|
||||
|
||||
rw->writeNode(*node, stream, options);
|
||||
}
|
||||
|
|
|
@ -58,7 +58,7 @@ namespace Shader
|
|||
return true;
|
||||
}
|
||||
|
||||
bool parseIncludes(boost::filesystem::path shaderPath, std::string& source, const std::string& shaderTemplate)
|
||||
bool parseIncludes(boost::filesystem::path shaderPath, std::string& source, const std::string& templateName)
|
||||
{
|
||||
Misc::StringUtils::replaceAll(source, "\r\n", "\n");
|
||||
|
||||
|
@ -70,13 +70,13 @@ namespace Shader
|
|||
size_t start = source.find('"', foundPos);
|
||||
if (start == std::string::npos || start == source.size()-1)
|
||||
{
|
||||
Log(Debug::Error) << "Shader " << shaderTemplate << " error: Invalid #include";
|
||||
Log(Debug::Error) << "Shader " << templateName << " error: Invalid #include";
|
||||
return false;
|
||||
}
|
||||
size_t end = source.find('"', start+1);
|
||||
if (end == std::string::npos)
|
||||
{
|
||||
Log(Debug::Error) << "Shader " << shaderTemplate << " error: Invalid #include";
|
||||
Log(Debug::Error) << "Shader " << templateName << " error: Invalid #include";
|
||||
return false;
|
||||
}
|
||||
std::string includeFilename = source.substr(start+1, end-(start+1));
|
||||
|
@ -85,7 +85,7 @@ namespace Shader
|
|||
includeFstream.open(includePath);
|
||||
if (includeFstream.fail())
|
||||
{
|
||||
Log(Debug::Error) << "Shader " << shaderTemplate << " error: Failed to open include " << includePath.string();
|
||||
Log(Debug::Error) << "Shader " << templateName << " error: Failed to open include " << includePath.string();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -120,14 +120,14 @@ namespace Shader
|
|||
|
||||
if (includedFiles.insert(includePath).second == false)
|
||||
{
|
||||
Log(Debug::Error) << "Shader " << shaderTemplate << " error: Detected cyclic #includes";
|
||||
Log(Debug::Error) << "Shader " << templateName << " error: Detected cyclic #includes";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool parseFors(std::string& source, const std::string& shaderTemplate)
|
||||
bool parseFors(std::string& source, const std::string& templateName)
|
||||
{
|
||||
const char escapeCharacter = '$';
|
||||
size_t foundPos = 0;
|
||||
|
@ -136,13 +136,13 @@ namespace Shader
|
|||
size_t endPos = source.find_first_of(" \n\r()[].;,", foundPos);
|
||||
if (endPos == std::string::npos)
|
||||
{
|
||||
Log(Debug::Error) << "Shader " << shaderTemplate << " error: Unexpected EOF";
|
||||
Log(Debug::Error) << "Shader " << templateName << " error: Unexpected EOF";
|
||||
return false;
|
||||
}
|
||||
std::string command = source.substr(foundPos + 1, endPos - (foundPos + 1));
|
||||
if (command != "foreach")
|
||||
{
|
||||
Log(Debug::Error) << "Shader " << shaderTemplate << " error: Unknown shader directive: $" << command;
|
||||
Log(Debug::Error) << "Shader " << templateName << " error: Unknown shader directive: $" << command;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -150,7 +150,7 @@ namespace Shader
|
|||
size_t iterNameEnd = source.find_first_of(" \n\r()[].;,", iterNameStart);
|
||||
if (iterNameEnd == std::string::npos)
|
||||
{
|
||||
Log(Debug::Error) << "Shader " << shaderTemplate << " error: Unexpected EOF";
|
||||
Log(Debug::Error) << "Shader " << templateName << " error: Unexpected EOF";
|
||||
return false;
|
||||
}
|
||||
std::string iteratorName = "$" + source.substr(iterNameStart, iterNameEnd - iterNameStart);
|
||||
|
@ -159,7 +159,7 @@ namespace Shader
|
|||
size_t listEnd = source.find_first_of("\n\r", listStart);
|
||||
if (listEnd == std::string::npos)
|
||||
{
|
||||
Log(Debug::Error) << "Shader " << shaderTemplate << " error: Unexpected EOF";
|
||||
Log(Debug::Error) << "Shader " << templateName << " error: Unexpected EOF";
|
||||
return false;
|
||||
}
|
||||
std::string list = source.substr(listStart, listEnd - listStart);
|
||||
|
@ -171,7 +171,7 @@ namespace Shader
|
|||
size_t contentEnd = source.find("$endforeach", contentStart);
|
||||
if (contentEnd == std::string::npos)
|
||||
{
|
||||
Log(Debug::Error) << "Shader " << shaderTemplate << " error: Unexpected EOF";
|
||||
Log(Debug::Error) << "Shader " << templateName << " error: Unexpected EOF";
|
||||
return false;
|
||||
}
|
||||
std::string content = source.substr(contentStart, contentEnd - contentStart);
|
||||
|
@ -211,7 +211,7 @@ namespace Shader
|
|||
}
|
||||
|
||||
bool parseDefines(std::string& source, const ShaderManager::DefineMap& defines,
|
||||
const ShaderManager::DefineMap& globalDefines, const std::string& shaderTemplate)
|
||||
const ShaderManager::DefineMap& globalDefines, const std::string& templateName)
|
||||
{
|
||||
const char escapeCharacter = '@';
|
||||
size_t foundPos = 0;
|
||||
|
@ -221,7 +221,7 @@ namespace Shader
|
|||
size_t endPos = source.find_first_of(" \n\r()[].;,", foundPos);
|
||||
if (endPos == std::string::npos)
|
||||
{
|
||||
Log(Debug::Error) << "Shader " << shaderTemplate << " error: Unexpected EOF";
|
||||
Log(Debug::Error) << "Shader " << templateName << " error: Unexpected EOF";
|
||||
return false;
|
||||
}
|
||||
std::string define = source.substr(foundPos+1, endPos - (foundPos+1));
|
||||
|
@ -234,7 +234,7 @@ namespace Shader
|
|||
size_t iterNameEnd = source.find_first_of(" \n\r()[].;,", iterNameStart);
|
||||
if (iterNameEnd == std::string::npos)
|
||||
{
|
||||
Log(Debug::Error) << "Shader " << shaderTemplate << " error: Unexpected EOF";
|
||||
Log(Debug::Error) << "Shader " << templateName << " error: Unexpected EOF";
|
||||
return false;
|
||||
}
|
||||
forIterators.push_back(source.substr(iterNameStart, iterNameEnd - iterNameStart));
|
||||
|
@ -244,7 +244,7 @@ namespace Shader
|
|||
source.replace(foundPos, 1, "$");
|
||||
if (forIterators.empty())
|
||||
{
|
||||
Log(Debug::Error) << "Shader " << shaderTemplate << " error: endforeach without foreach";
|
||||
Log(Debug::Error) << "Shader " << templateName << " error: endforeach without foreach";
|
||||
return false;
|
||||
}
|
||||
else
|
||||
|
@ -264,22 +264,22 @@ namespace Shader
|
|||
}
|
||||
else
|
||||
{
|
||||
Log(Debug::Error) << "Shader " << shaderTemplate << " error: Undefined " << define;
|
||||
Log(Debug::Error) << "Shader " << templateName << " error: Undefined " << define;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
osg::ref_ptr<osg::Shader> ShaderManager::getShader(const std::string &shaderTemplate, const ShaderManager::DefineMap &defines, osg::Shader::Type shaderType)
|
||||
osg::ref_ptr<osg::Shader> ShaderManager::getShader(const std::string &templateName, const ShaderManager::DefineMap &defines, osg::Shader::Type shaderType)
|
||||
{
|
||||
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mMutex);
|
||||
|
||||
// read the template if we haven't already
|
||||
TemplateMap::iterator templateIt = mShaderTemplates.find(shaderTemplate);
|
||||
TemplateMap::iterator templateIt = mShaderTemplates.find(templateName);
|
||||
if (templateIt == mShaderTemplates.end())
|
||||
{
|
||||
boost::filesystem::path p = (boost::filesystem::path(mPath) / shaderTemplate);
|
||||
boost::filesystem::path p = (boost::filesystem::path(mPath) / templateName);
|
||||
boost::filesystem::ifstream stream;
|
||||
stream.open(p);
|
||||
if (stream.fail())
|
||||
|
@ -293,20 +293,20 @@ namespace Shader
|
|||
// parse includes
|
||||
std::string source = buffer.str();
|
||||
if (!addLineDirectivesAfterConditionalBlocks(source)
|
||||
|| !parseIncludes(boost::filesystem::path(mPath), source, shaderTemplate))
|
||||
|| !parseIncludes(boost::filesystem::path(mPath), source, templateName))
|
||||
return nullptr;
|
||||
|
||||
templateIt = mShaderTemplates.insert(std::make_pair(shaderTemplate, source)).first;
|
||||
templateIt = mShaderTemplates.insert(std::make_pair(templateName, source)).first;
|
||||
}
|
||||
|
||||
ShaderMap::iterator shaderIt = mShaders.find(std::make_pair(shaderTemplate, defines));
|
||||
ShaderMap::iterator shaderIt = mShaders.find(std::make_pair(templateName, defines));
|
||||
if (shaderIt == mShaders.end())
|
||||
{
|
||||
std::string shaderSource = templateIt->second;
|
||||
if (!parseDefines(shaderSource, defines, mGlobalDefines, shaderTemplate) || !parseFors(shaderSource, shaderTemplate))
|
||||
if (!parseDefines(shaderSource, defines, mGlobalDefines, templateName) || !parseFors(shaderSource, templateName))
|
||||
{
|
||||
// Add to the cache anyway to avoid logging the same error over and over.
|
||||
mShaders.insert(std::make_pair(std::make_pair(shaderTemplate, defines), nullptr));
|
||||
mShaders.insert(std::make_pair(std::make_pair(templateName, defines), nullptr));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -316,7 +316,7 @@ namespace Shader
|
|||
static unsigned int counter = 0;
|
||||
shader->setName(std::to_string(counter++));
|
||||
|
||||
shaderIt = mShaders.insert(std::make_pair(std::make_pair(shaderTemplate, defines), shader)).first;
|
||||
shaderIt = mShaders.insert(std::make_pair(std::make_pair(templateName, defines), shader)).first;
|
||||
}
|
||||
return shaderIt->second;
|
||||
}
|
||||
|
@ -372,4 +372,14 @@ namespace Shader
|
|||
program.second->releaseGLObjects(state);
|
||||
}
|
||||
|
||||
const osg::ref_ptr<osg::Uniform> ShaderManager::getShadowMapAlphaTestEnableUniform()
|
||||
{
|
||||
return mShadowMapAlphaTestEnableUniform;
|
||||
}
|
||||
|
||||
const osg::ref_ptr<osg::Uniform> ShaderManager::getShadowMapAlphaTestDisableUniform()
|
||||
{
|
||||
return mShadowMapAlphaTestDisableUniform;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ namespace Shader
|
|||
/// @param shaderType The type of shader (usually vertex or fragment shader).
|
||||
/// @note May return nullptr on failure.
|
||||
/// @note Thread safe.
|
||||
osg::ref_ptr<osg::Shader> getShader(const std::string& shaderTemplate, const DefineMap& defines, osg::Shader::Type shaderType);
|
||||
osg::ref_ptr<osg::Shader> getShader(const std::string& templateName, const DefineMap& defines, osg::Shader::Type shaderType);
|
||||
|
||||
osg::ref_ptr<osg::Program> getProgram(osg::ref_ptr<osg::Shader> vertexShader, osg::ref_ptr<osg::Shader> fragmentShader);
|
||||
|
||||
|
@ -44,6 +44,9 @@ namespace Shader
|
|||
|
||||
void releaseGLObjects(osg::State* state);
|
||||
|
||||
const osg::ref_ptr<osg::Uniform> getShadowMapAlphaTestEnableUniform();
|
||||
const osg::ref_ptr<osg::Uniform> getShadowMapAlphaTestDisableUniform();
|
||||
|
||||
private:
|
||||
std::string mPath;
|
||||
|
||||
|
@ -61,8 +64,15 @@ namespace Shader
|
|||
ProgramMap mPrograms;
|
||||
|
||||
OpenThreads::Mutex mMutex;
|
||||
|
||||
const osg::ref_ptr<osg::Uniform> mShadowMapAlphaTestEnableUniform = new osg::Uniform();
|
||||
const osg::ref_ptr<osg::Uniform> mShadowMapAlphaTestDisableUniform = new osg::Uniform();
|
||||
};
|
||||
|
||||
bool parseFors(std::string& source, const std::string& templateName);
|
||||
|
||||
bool parseDefines(std::string& source, const ShaderManager::DefineMap& defines,
|
||||
const ShaderManager::DefineMap& globalDefines, const std::string& templateName);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
#include "shadervisitor.hpp"
|
||||
|
||||
#include <osg/Texture>
|
||||
#include <osg/Material>
|
||||
#include <osg/AlphaFunc>
|
||||
#include <osg/BlendFunc>
|
||||
#include <osg/Geometry>
|
||||
#include <osg/Material>
|
||||
#include <osg/Texture>
|
||||
|
||||
#include <osgUtil/TangentSpaceGenerator>
|
||||
|
||||
|
@ -23,6 +25,7 @@ namespace Shader
|
|||
: mShaderRequired(false)
|
||||
, mColorMode(0)
|
||||
, mMaterialOverridden(false)
|
||||
, mBlendFuncOverridden(false)
|
||||
, mNormalHeight(false)
|
||||
, mTexStageRequiringTangents(-1)
|
||||
, mNode(nullptr)
|
||||
|
@ -229,15 +232,21 @@ namespace Shader
|
|||
{
|
||||
if (!writableStateSet)
|
||||
writableStateSet = getWritableStateSet(node);
|
||||
// We probably shouldn't construct a new version of this each time as Uniforms use pointer comparison for early-out.
|
||||
// Also it should probably belong to the shader manager
|
||||
writableStateSet->addUniform(new osg::Uniform("useDiffuseMapForShadowAlpha", true));
|
||||
}
|
||||
}
|
||||
|
||||
bool alphaSettingsChanged = false;
|
||||
bool alphaTestShadows = false;
|
||||
|
||||
const osg::StateSet::AttributeList& attributes = stateset->getAttributeList();
|
||||
for (osg::StateSet::AttributeList::const_iterator it = attributes.begin(); it != attributes.end(); ++it)
|
||||
{
|
||||
if (it->first.first == osg::StateAttribute::MATERIAL)
|
||||
{
|
||||
// This should probably be moved out of ShaderRequirements and be applied directly now it's a uniform instead of a define
|
||||
if (!mRequirements.back().mMaterialOverridden || it->second.second & osg::StateAttribute::PROTECTED)
|
||||
{
|
||||
if (it->second.second & osg::StateAttribute::OVERRIDE)
|
||||
|
@ -269,6 +278,27 @@ namespace Shader
|
|||
mRequirements.back().mColorMode = colorMode;
|
||||
}
|
||||
}
|
||||
else if (it->first.first == osg::StateAttribute::BLENDFUNC)
|
||||
{
|
||||
if (!mRequirements.back().mBlendFuncOverridden || it->second.second & osg::StateAttribute::PROTECTED)
|
||||
{
|
||||
if (it->second.second & osg::StateAttribute::OVERRIDE)
|
||||
mRequirements.back().mBlendFuncOverridden = true;
|
||||
|
||||
const osg::BlendFunc* blend = static_cast<const osg::BlendFunc*>(it->second.first.get());
|
||||
if (blend->getSource() == osg::BlendFunc::SRC_ALPHA || blend->getSource() == osg::BlendFunc::SRC_COLOR)
|
||||
alphaTestShadows = true;
|
||||
alphaSettingsChanged = true;
|
||||
}
|
||||
}
|
||||
// Eventually, move alpha testing to discard in shader adn remove deprecated state here
|
||||
}
|
||||
// we don't need to check for glEnable/glDisable of blending as we always set it at the same time
|
||||
if (alphaSettingsChanged)
|
||||
{
|
||||
if (!writableStateSet)
|
||||
writableStateSet = getWritableStateSet(node);
|
||||
writableStateSet->addUniform(alphaTestShadows ? mShaderManager.getShadowMapAlphaTestEnableUniform() : mShaderManager.getShadowMapAlphaTestDisableUniform());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -75,6 +75,8 @@ namespace Shader
|
|||
int mColorMode;
|
||||
|
||||
bool mMaterialOverridden;
|
||||
bool mBlendFuncOverridden;
|
||||
|
||||
bool mNormalHeight; // true if normal map has height info in alpha channel
|
||||
|
||||
// -1 == no tangents required
|
||||
|
|
|
@ -323,6 +323,7 @@ void updateWaterCullingView(HeightCullCallback* callback, ViewData* vd, osgUtil:
|
|||
return;
|
||||
}
|
||||
cv->pushCurrentMask();
|
||||
static bool debug = getenv("OPENMW_WATER_CULLING_DEBUG") != nullptr;
|
||||
for (unsigned int i=0; i<vd->getNumEntries(); ++i)
|
||||
{
|
||||
ViewData::Entry& entry = vd->getEntry(i);
|
||||
|
@ -337,7 +338,6 @@ void updateWaterCullingView(HeightCullCallback* callback, ViewData* vd, osgUtil:
|
|||
continue;
|
||||
lowZ = bb._min.z();
|
||||
|
||||
static bool debug = getenv("OPENMW_WATER_CULLING_DEBUG") != nullptr;
|
||||
if (!debug)
|
||||
break;
|
||||
osg::Box* b = new osg::Box;
|
||||
|
|
|
@ -40,36 +40,46 @@ namespace Terrain
|
|||
class ChunkManager;
|
||||
class CompositeMapRenderer;
|
||||
|
||||
class HeightCullCallback : public osg::NodeCallback
|
||||
{
|
||||
public:
|
||||
HeightCullCallback() : mLowZ(-std::numeric_limits<float>::max()), mHighZ(std::numeric_limits<float>::max()), mMask(~0) {}
|
||||
|
||||
void setLowZ(float z)
|
||||
class HeightCullCallback : public osg::NodeCallback
|
||||
{
|
||||
mLowZ = z;
|
||||
}
|
||||
float getLowZ() const { return mLowZ; }
|
||||
public:
|
||||
void setLowZ(float z)
|
||||
{
|
||||
mLowZ = z;
|
||||
}
|
||||
float getLowZ() const
|
||||
{
|
||||
return mLowZ;
|
||||
}
|
||||
|
||||
void setHighZ(float highZ)
|
||||
{
|
||||
mHighZ = highZ;
|
||||
}
|
||||
float getHighZ() const { return mHighZ; }
|
||||
void setHighZ(float highZ)
|
||||
{
|
||||
mHighZ = highZ;
|
||||
}
|
||||
float getHighZ() const
|
||||
{
|
||||
return mHighZ;
|
||||
}
|
||||
|
||||
void setCullMask(unsigned int mask) { mMask = mask; }
|
||||
unsigned int getCullMask() const { return mMask; }
|
||||
void setCullMask(unsigned int mask)
|
||||
{
|
||||
mMask = mask;
|
||||
}
|
||||
unsigned int getCullMask() const
|
||||
{
|
||||
return mMask;
|
||||
}
|
||||
|
||||
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
|
||||
{
|
||||
if (mLowZ <= mHighZ)
|
||||
traverse(node, nv);
|
||||
}
|
||||
private:
|
||||
float mLowZ;
|
||||
float mHighZ;
|
||||
unsigned int mMask;
|
||||
};
|
||||
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
|
||||
{
|
||||
if (mLowZ <= mHighZ)
|
||||
traverse(node, nv);
|
||||
}
|
||||
private:
|
||||
float mLowZ{-std::numeric_limits<float>::max()};
|
||||
float mHighZ{std::numeric_limits<float>::max()};
|
||||
unsigned int mMask{~0u};
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A View is a collection of rendering objects that are visible from a given camera/intersection.
|
||||
|
|
|
@ -74,6 +74,20 @@ Game will not eat all memory at once.
|
|||
Memory will be consumed in approximately linear dependency from number of nav mesh updates.
|
||||
But only for new locations or already dropped from cache.
|
||||
|
||||
min update interval ms
|
||||
----------------
|
||||
|
||||
:Type: integer
|
||||
:Range: >= 0
|
||||
:Default: 250
|
||||
|
||||
Minimum time duration required to pass before next navmesh update for the same tile in milliseconds.
|
||||
Only tiles affected where objects are transformed.
|
||||
Next update for tile with added or removed object will not be delayed.
|
||||
Visible ingame effect is navmesh update around opening or closing door.
|
||||
Primary usage is for rotating signs like in Seyda Neen at Arrille's Tradehouse entrance.
|
||||
Decreasing this value may increase CPU usage by background threads.
|
||||
|
||||
Developer's settings
|
||||
********************
|
||||
|
||||
|
|
|
@ -88,7 +88,6 @@ compute tight scene bounds
|
|||
:Default: True
|
||||
|
||||
With this setting enabled, attempt to better use the shadow map(s) by making them cover a smaller area.
|
||||
This can be especially helpful when looking downwards with a high viewing distance but will be less useful with the default value.
|
||||
May have a minor to major performance impact.
|
||||
|
||||
shadow map resolution
|
||||
|
|
7
extern/osgQt/GraphicsWindowQt.cpp
vendored
7
extern/osgQt/GraphicsWindowQt.cpp
vendored
|
@ -119,6 +119,13 @@ bool GLWidget::event( QEvent* event )
|
|||
enqueueDeferredEvent(QEvent::ParentChange);
|
||||
return true;
|
||||
}
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0)
|
||||
else if (event->type() == QEvent::PlatformSurface && static_cast<QPlatformSurfaceEvent*>(event)->surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed)
|
||||
{
|
||||
if (_gw)
|
||||
_gw->close();
|
||||
}
|
||||
#endif
|
||||
|
||||
// perform regular event handling
|
||||
return QGLWidget::event( event );
|
||||
|
|
|
@ -776,6 +776,9 @@ enable recast mesh render = false
|
|||
# Max number of navmesh tiles (value >= 0)
|
||||
max tiles number = 512
|
||||
|
||||
# Min time duration for the same tile update in milliseconds (value >= 0)
|
||||
min update interval ms = 250
|
||||
|
||||
[Shadows]
|
||||
|
||||
# Enable or disable shadows. Bear in mind that this will force OpenMW to use shaders as if "[Shaders]/force shaders" was set to true.
|
||||
|
@ -805,7 +808,7 @@ enable debug hud = false
|
|||
# Enable the debug overlay to see where each shadow map affects.
|
||||
enable debug overlay = false
|
||||
|
||||
# Attempt to better use the shadow map by making them cover a smaller area. Especially helpful when looking downwards. May have a minor to major performance impact.
|
||||
# Attempt to better use the shadow map by making them cover a smaller area. May have a minor to major performance impact.
|
||||
compute tight scene bounds = true
|
||||
|
||||
# How large to make the shadow map(s). Higher values increase GPU load, but can produce better-looking results. Power-of-two values may turn out to be faster on some GPU/driver combinations.
|
||||
|
|
|
@ -49,6 +49,8 @@ uniform vec2 envMapLumaBias;
|
|||
uniform mat2 bumpMapMatrix;
|
||||
#endif
|
||||
|
||||
uniform bool simpleWater = false;
|
||||
|
||||
varying float euclideanDepth;
|
||||
varying float linearDepth;
|
||||
|
||||
|
@ -90,7 +92,7 @@ void main()
|
|||
vec3 cameraPos = (gl_ModelViewMatrixInverse * vec4(0,0,0,1)).xyz;
|
||||
vec3 objectPos = (gl_ModelViewMatrixInverse * vec4(passViewPos, 1)).xyz;
|
||||
vec3 eyeDir = normalize(cameraPos - objectPos);
|
||||
vec2 offset = getParallaxOffset(eyeDir, tbnTranspose, normalTex.a, (passTangent.w > 0) ? -1.f : 1.f);
|
||||
vec2 offset = getParallaxOffset(eyeDir, tbnTranspose, normalTex.a, (passTangent.w > 0.0) ? -1.f : 1.f);
|
||||
adjustedDiffuseUV += offset; // only offset diffuse for now, other textures are more likely to be using a completely different UV set
|
||||
|
||||
// TODO: check not working as the same UV buffer is being bound to different targets
|
||||
|
@ -171,7 +173,7 @@ void main()
|
|||
|
||||
#if @specularMap
|
||||
vec4 specTex = texture2D(specularMap, specularMapUV);
|
||||
float shininess = specTex.a * 255;
|
||||
float shininess = specTex.a * 255.0;
|
||||
vec3 matSpec = specTex.xyz;
|
||||
#else
|
||||
float shininess = gl_FrontMaterial.shininess;
|
||||
|
@ -180,7 +182,11 @@ void main()
|
|||
|
||||
gl_FragData[0].xyz += getSpecular(normalize(viewNormal), normalize(passViewPos.xyz), shininess, matSpec) * shadowing;
|
||||
#if @radialFog
|
||||
float fogValue = clamp((euclideanDepth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0);
|
||||
float depth = euclideanDepth;
|
||||
// For the less detailed mesh of simple water we need to recalculate depth on per-pixel basis
|
||||
if (simpleWater)
|
||||
depth = length(passViewPos);
|
||||
float fogValue = clamp((depth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0);
|
||||
#else
|
||||
float fogValue = clamp((linearDepth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0);
|
||||
#endif
|
||||
|
|
|
@ -6,6 +6,7 @@ varying vec2 diffuseMapUV;
|
|||
varying float alphaPassthrough;
|
||||
|
||||
uniform bool useDiffuseMapForShadowAlpha;
|
||||
uniform bool alphaTestShadows;
|
||||
|
||||
void main()
|
||||
{
|
||||
|
@ -15,7 +16,7 @@ void main()
|
|||
else
|
||||
gl_FragData[0].a = alphaPassthrough;
|
||||
|
||||
// Prevent translucent things casting shadow (including the player using an invisibility effect)
|
||||
if (gl_FragData[0].a <= 0.5)
|
||||
// Prevent translucent things casting shadow (including the player using an invisibility effect). For now, rely on the deprecated FF test for non-blended stuff.
|
||||
if (alphaTestShadows && gl_FragData[0].a <= 0.5)
|
||||
discard;
|
||||
}
|
||||
|
|
|
@ -5,7 +5,8 @@ varying vec2 diffuseMapUV;
|
|||
varying float alphaPassthrough;
|
||||
|
||||
uniform int colorMode;
|
||||
uniform bool useDiffuseMapForShadowAlpha;
|
||||
uniform bool useDiffuseMapForShadowAlpha = true;
|
||||
uniform bool alphaTestShadows = true;
|
||||
|
||||
void main(void)
|
||||
{
|
||||
|
@ -18,7 +19,6 @@ void main(void)
|
|||
diffuseMapUV = (gl_TextureMatrix[0] * gl_MultiTexCoord0).xy;
|
||||
else
|
||||
diffuseMapUV = vec2(0.0); // Avoid undefined behaviour if running on hardware predating the concept of dynamically uniform expressions
|
||||
|
||||
if (colorMode == 2)
|
||||
alphaPassthrough = gl_Color.a;
|
||||
else
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue