1
0
Fork 1
mirror of https://github.com/TES3MP/openmw-tes3mp.git synced 2025-03-04 20:49:42 +00:00

Add OpenMW commits up to 4 Jan 2021

# Conflicts:
#   CMakeLists.txt
#   apps/openmw/main.cpp
#   apps/openmw/mwmechanics/npcstats.hpp
#   apps/openmw/mwphysics/actor.cpp
#   apps/openmw/mwphysics/mtphysics.hpp
#   components/CMakeLists.txt
This commit is contained in:
David Cernat 2021-01-05 07:25:31 +02:00
commit da3316daf8
147 changed files with 3230 additions and 1257 deletions

View file

@ -49,6 +49,7 @@ Programmers
Cédric Mocquillon
Chris Boyce (slothlife)
Chris Robinson (KittyCat)
Coleman Smith (olcoal)
Cory F. Cohen (cfcohen)
Cris Mihalache (Mirceam)
crussell187
@ -122,7 +123,6 @@ Programmers
Lordrea
Łukasz Gołębiewski (lukago)
Lukasz Gromanowski (lgro)
Manuel Edelmann (vorenon)
Marc Bouvier (CramitDeFrog)
Marcin Hulist (Gohan)
Mark Siewert (mark76)

View file

@ -1,21 +1,31 @@
0.47.0
------
Bug #832: OpenMW-CS: Handle deleted references
Bug #1662: Qt4 and Windows binaries crash if there's a non-ASCII character in a file path/config path
Bug #1901: Actors colliding behaviour is different from vanilla
Bug #1952: Incorrect particle lighting
Bug #2069: Fireflies in Fireflies invade Morrowind look wrong
Bug #2311: Targeted scripts are not properly supported on non-unique RefIDs
Bug #2473: Unable to overstock merchants
Bug #2798: Mutable ESM records
Bug #2976 [reopened]: Issues combining settings from the command line and both config files
Bug #3137: Walking into a wall prevents jumping
Bug #3372: Projectiles and magic bolts go through moving targets
Bug #3676: NiParticleColorModifier isn't applied properly
Bug #3714: Savegame fails to load due to conflict between SpellState and MagicEffects
Bug #3789: Crash in visitEffectSources while in battle
Bug #3862: Random container contents behave differently than vanilla
Bug #3929: Leveled list merchant containers respawn on barter
Bug #4021: Attributes and skills are not stored as floats
Bug #4039: Multiple followers should have the same following distance
Bug #4055: Local scripts don't inherit variables from their base record
Bug #4083: Door animation freezes when colliding with actors
Bug #4247: Cannot walk up stairs in Ebonheart docks
Bug #4447: Actor collision capsule shape allows looking through some walls
Bug #4465: Collision shape overlapping causes twitching
Bug #4476: Abot Gondoliers: player hangs in air during scenic travel
Bug #4568: Too many actors in one spot can push other actors out of bounds
Bug #4623: Corprus implementation is incorrect
Bug #4631: Setting MSAA level too high doesn't fall back to highest supported level
Bug #4764: Data race in osg ParticleSystem
@ -23,6 +33,7 @@
Bug #5101: Hostile followers travel with the player
Bug #5108: Savegame bloating due to inefficient fog textures format
Bug #5165: Active spells should use real time intead of timestamps
Bug #5300: NPCs don't switch from torch to shield when starting combat
Bug #5358: ForceGreeting always resets the dialogue window completely
Bug #5363: Enchantment autocalc not always 0/1
Bug #5364: Script fails/stops if trying to startscript an unknown script
@ -37,6 +48,7 @@
Bug #5415: Environment maps in ebony cuirass and HiRez Armors Indoril cuirass don't work
Bug #5416: Junk non-node records before the root node are not handled gracefully
Bug #5422: The player loses all spells when resurrected
Bug #5423: Guar follows actors too closely
Bug #5424: Creatures do not headtrack player
Bug #5425: Poison effect only appears for one frame
Bug #5427: GetDistance unknown ID error is misleading
@ -61,20 +73,33 @@
Bug #5604: Only one valid NIF root node is loaded from a single file
Bug #5611: Usable items with "0 Uses" should be used only once
Bug #5622: Can't properly interact with the console when in pause menu
Bug #5627: Bookart not shown if it isn't followed by <BR> statement
Bug #5633: Damage Spells in effect before god mode is enabled continue to hurt the player character and can kill them
Bug #5639: Tooltips cover Messageboxes
Bug #5644: Summon effects running on the player during game initialization cause crashes
Bug #5656: Sneaking characters block hits while standing
Bug #5661: Region sounds don't play at the right interval
Bug #5675: OpenMW-cs. FRMR subrecords are saved with the wrong MastIdx
Bug #5681: Player character can clip or pass through bridges instead of colliding against them
Bug #5688: Water shader broken indoors with enable indoor shadows = false
Bug #5695: ExplodeSpell for actors doesn't target the ground
Bug #5703: OpenMW-CS menu system crashing on XFCE
Bug #5706: AI sequences stop looping after the saved game is reloaded
Bug #5731: Editor: skirts are invisible on characters
Bug #5758: Paralyzed actors behavior is inconsistent with vanilla
Bug #5762: Movement solver is insufficiently robust
Feature #390: 3rd person look "over the shoulder"
Feature #1536: Show more information about level on menu
Feature #2386: Distant Statics in the form of Object Paging
Feature #2404: Levelled List can not be placed into a container
Feature #2686: Timestamps in openmw.log
Feature #4894: Consider actors as obstacles for pathfinding
Feature #5043: Head Bobbing
Feature #5199: Improve Scene Colors
Feature #5297: Add a search function to the "Datafiles" tab of the OpenMW launcher
Feature #5362: Show the soul gems' trapped soul in count dialog
Feature #5445: Handle NiLines
Feature #5456: Basic collada animation support
Feature #5457: Realistic diagonal movement
Feature #5486: Fixes trainers to choose their training skills based on their base skill points
Feature #5519: Code Patch tab in launcher
@ -89,6 +114,7 @@
Feature #5649: Skyrim SE compressed BSA format support
Feature #5672: Make stretch menu background configuration more accessible
Feature #5692: Improve spell/magic item search to factor in magic effect names
Feature #5730: Add graphic herbalism option to the launcher and documents
Task #5480: Drop Qt4 support
Task #5520: Improve cell name autocompleter implementation

View file

@ -18,6 +18,7 @@ Known Issues:
New Features:
- Dialogue to split item stacks now displays the name of the trapped soul for stacks of soul gems (#5362)
- Basics of Collada animations are now supported via osgAnimation plugin (#5456)
New Editor Features:
- ?
@ -33,8 +34,11 @@ Bug Fixes:
- Morrowind legacy madness: Using a key on a trapped door/container now only disarms the trap if the door/container is locked (#5370)
Editor Bug Fixes:
- Deleted and moved objects within a cell are now saved properly (#832)
- Verifier no longer checks for alleged 'race' entries in clothing body parts (#5400)
- Loading mods now keeps the master index (#5675)
- Flicker and crashing on XFCE4 fixed (#5703)
Miscellaneous:
- Prevent save-game bloating by using an appropriate fog texture format (#5108)
- Ensure that 'Enchantment autocalc" flag is treated as flag in OpenMW-CS and in our esm tools (#5363)
- Ensure that 'Enchantment autocalc" flag is treated as flag in OpenMW-CS and in our esm tools (#5363)

View file

@ -1,4 +1,4 @@
#!/bin/sh -ex
curl -fSL -R -J https://gitlab.com/OpenMW/openmw-deps/-/raw/main/android/openmw-android-deps-20201018.zip -o ~/openmw-android-deps.zip
curl -fSL -R -J https://gitlab.com/OpenMW/openmw-deps/-/raw/main/android/openmw-android-deps-20201129.zip -o ~/openmw-android-deps.zip
unzip -o ~/openmw-android-deps -d /usr/lib/android-sdk/ndk-bundle/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr > /dev/null

View file

@ -73,7 +73,7 @@ CONFIGURATIONS=()
TEST_FRAMEWORK=""
GOOGLE_INSTALL_ROOT=""
INSTALL_PREFIX="."
BULLET_DOUBLE=""
BULLET_DOUBLE=true
BULLET_DBL=""
BULLET_DBL_DISPLAY="Single precision"

View file

@ -3,6 +3,9 @@ cmake_minimum_required(VERSION 3.1.0)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Detect OS
include(cmake/OSIdentity.cmake)
# for link time optimization, remove if cmake version is >= 3.9
if(POLICY CMP0069)
cmake_policy(SET CMP0069 NEW)
@ -21,7 +24,7 @@ option(BUILD_NIFTEST "Build nif file tester" ON)
option(BUILD_DOCS "Build documentation." OFF )
option(BUILD_WITH_CODE_COVERAGE "Enable code coverage with gconv" OFF)
option(BUILD_UNITTESTS "Enable Unittests with Google C++ Unittest" OFF)
option(BULLET_USE_DOUBLES "Use double precision for Bullet" OFF)
option(BULLET_USE_DOUBLES "Use double precision for Bullet" ON)
option(BUILD_OPENMW_MP "Build OpenMW-MP" ON)
option(BUILD_BROWSER "Build tes3mp Server Browser" ON)
option(BUILD_MASTER "Build tes3mp Master Server" OFF)
@ -620,6 +623,12 @@ if (WIN32)
5031 # #pragma warning(pop): likely mismatch, popping warning state pushed in different file (config_begin.hpp, config_end.hpp)
)
endif()
if( "${MyGUI_VERSION}" VERSION_LESS_EQUAL "3.4.0" )
set(WARNINGS_DISABLE ${WARNINGS_DISABLE}
4866 # compiler may not enforce left-to-right evaluation order for call
)
endif()
foreach(d ${WARNINGS_DISABLE})
set(WARNINGS "${WARNINGS} /wd${d}")

View file

@ -10,11 +10,9 @@
#include <cmath>
Launcher::AdvancedPage::AdvancedPage(Files::ConfigurationManager &cfg,
Config::GameSettings &gameSettings,
Launcher::AdvancedPage::AdvancedPage(Config::GameSettings &gameSettings,
Settings::Manager &engineSettings, QWidget *parent)
: QWidget(parent)
, mCfgMgr(cfg)
, mGameSettings(gameSettings)
, mEngineSettings(engineSettings)
{
@ -153,6 +151,7 @@ bool Launcher::AdvancedPage::loadSettings()
if (showOwnedIndex >= 0 && showOwnedIndex <= 3)
showOwnedComboBox->setCurrentIndex(showOwnedIndex);
loadSettingBool(stretchBackgroundCheckBox, "stretch menu background", "GUI");
loadSettingBool(graphicHerbalismCheckBox, "graphic herbalism", "Game");
}
// Bug fixes
@ -279,6 +278,7 @@ void Launcher::AdvancedPage::saveSettings()
if (showOwnedCurrentIndex != mEngineSettings.getInt("show owned", "Game"))
mEngineSettings.setInt("show owned", "Game", showOwnedCurrentIndex);
saveSettingBool(stretchBackgroundCheckBox, "stretch menu background", "GUI");
saveSettingBool(graphicHerbalismCheckBox, "graphic herbalism", "Game");
}
// Bug fixes

View file

@ -9,7 +9,6 @@
#include <components/settings/settings.hpp>
namespace Files { struct ConfigurationManager; }
namespace Config { class GameSettings; }
namespace Launcher
@ -19,7 +18,7 @@ namespace Launcher
Q_OBJECT
public:
AdvancedPage(Files::ConfigurationManager &cfg, Config::GameSettings &gameSettings,
AdvancedPage(Config::GameSettings &gameSettings,
Settings::Manager &engineSettings, QWidget *parent = nullptr);
bool loadSettings();
@ -35,7 +34,6 @@ namespace Launcher
void slotViewOverShoulderToggled(bool checked);
private:
Files::ConfigurationManager &mCfgMgr;
Config::GameSettings &mGameSettings;
Settings::Manager &mEngineSettings;
QCompleter mCellNameCompleter;

View file

@ -88,11 +88,7 @@ namespace Launcher
QStringList previousSelectedFiles;
QString mDataLocal;
void setPluginsCheckstates(Qt::CheckState state);
void buildView();
void setupConfig();
void readConfig();
void setProfile (int index, bool savePrevious);
void setProfile (const QString &previous, const QString &current, bool savePrevious);
void removeProfile (const QString &profile);

View file

@ -29,9 +29,8 @@ QString getAspect(int x, int y)
return QString(QString::number(xaspect) + ":" + QString::number(yaspect));
}
Launcher::GraphicsPage::GraphicsPage(Files::ConfigurationManager &cfg, Settings::Manager &engineSettings, QWidget *parent)
Launcher::GraphicsPage::GraphicsPage(Settings::Manager &engineSettings, QWidget *parent)
: QWidget(parent)
, mCfgMgr(cfg)
, mEngineSettings(engineSettings)
{
setObjectName ("GraphicsPage");

View file

@ -20,7 +20,7 @@ namespace Launcher
Q_OBJECT
public:
GraphicsPage(Files::ConfigurationManager &cfg, Settings::Manager &engineSettings, QWidget *parent = nullptr);
GraphicsPage(Settings::Manager &engineSettings, QWidget *parent = nullptr);
void saveSettings();
bool loadSettings();
@ -35,7 +35,6 @@ namespace Launcher
void slotShadowDistLimitToggled(bool checked);
private:
Files::ConfigurationManager &mCfgMgr;
Settings::Manager &mEngineSettings;
QVector<QStringList> mResolutionsPerScreen;

View file

@ -126,9 +126,9 @@ void Launcher::MainDialog::createPages()
mPlayPage = new PlayPage(this);
mDataFilesPage = new DataFilesPage(mCfgMgr, mGameSettings, mLauncherSettings, this);
mGraphicsPage = new GraphicsPage(mCfgMgr, mEngineSettings, this);
mGraphicsPage = new GraphicsPage(mEngineSettings, this);
mSettingsPage = new SettingsPage(mCfgMgr, mGameSettings, mLauncherSettings, this);
mAdvancedPage = new AdvancedPage(mCfgMgr, mGameSettings, mEngineSettings, this);
mAdvancedPage = new AdvancedPage(mGameSettings, mEngineSettings, this);
// Set the combobox of the play page to imitate the combobox on the datafilespage
mPlayPage->setProfilesModel(mDataFilesPage->profilesModel());

View file

@ -45,4 +45,5 @@ QString CellNameLoader::getCellName(ESM::ESMReader &esmReader)
cell.loadNameAndData(esmReader, isDeleted);
return QString::fromStdString(cell.mName);
}
}

View file

@ -210,6 +210,19 @@ void CSMPrefs::State::declare()
setTooltip("Size of the orthographic frustum, greater value will allow the camera to see more of the world.").
setRange(10, 10000);
declareDouble ("object-marker-alpha", "Object Marker Transparency", 0.5).setPrecision(2).setRange(0,1);
declareBool("scene-use-gradient", "Use Gradient Background", true);
declareColour ("scene-day-background-colour", "Day Background Colour", QColor (110, 120, 128, 255));
declareColour ("scene-day-gradient-colour", "Day Gradient Colour", QColor (47, 51, 51, 255)).
setTooltip("Sets the gradient color to use in conjunction with the day background color. Ignored if "
"the gradient option is disabled.");
declareColour ("scene-bright-background-colour", "Scene Bright Background Colour", QColor (79, 87, 92, 255));
declareColour ("scene-bright-gradient-colour", "Scene Bright Gradient Colour", QColor (47, 51, 51, 255)).
setTooltip("Sets the gradient color to use in conjunction with the bright background color. Ignored if "
"the gradient option is disabled.");
declareColour ("scene-night-background-colour", "Scene Night Background Colour", QColor (64, 77, 79, 255));
declareColour ("scene-night-gradient-colour", "Scene Night Gradient Colour", QColor (47, 51, 51, 255)).
setTooltip("Sets the gradient color to use in conjunction with the night background color. Ignored if "
"the gradient option is disabled.");
declareCategory ("Tooltips");
declareBool ("scene", "Show Tooltips in 3D scenes", true);

View file

@ -593,56 +593,33 @@ namespace CSMWorld
}
else if (type == UniversalId::Type_Clothing)
{
int priority = 0;
// TODO: reserve bodyparts for robes and skirts
auto& clothing = dynamic_cast<const Record<ESM::Clothing>&>(record).get();
std::vector<ESM::PartReferenceType> parts;
if (clothing.mData.mType == ESM::Clothing::Robe)
{
auto reservedList = std::vector<ESM::PartReference>();
ESM::PartReference pr;
pr.mMale = "";
pr.mFemale = "";
ESM::PartReferenceType parts[] = {
parts = {
ESM::PRT_Groin, ESM::PRT_Skirt, ESM::PRT_RLeg, ESM::PRT_LLeg,
ESM::PRT_RUpperarm, ESM::PRT_LUpperarm, ESM::PRT_RKnee, ESM::PRT_LKnee,
ESM::PRT_RForearm, ESM::PRT_LForearm
ESM::PRT_RForearm, ESM::PRT_LForearm, ESM::PRT_Cuirass
};
size_t parts_size = sizeof(parts)/sizeof(parts[0]);
for(size_t p = 0;p < parts_size;++p)
{
pr.mPart = parts[p];
reservedList.push_back(pr);
}
priority = parts_size;
addParts(reservedList, priority);
}
else if (clothing.mData.mType == ESM::Clothing::Skirt)
{
auto reservedList = std::vector<ESM::PartReference>();
ESM::PartReference pr;
pr.mMale = "";
pr.mFemale = "";
ESM::PartReferenceType parts[] = {
ESM::PRT_Groin, ESM::PRT_RLeg, ESM::PRT_LLeg
};
size_t parts_size = sizeof(parts)/sizeof(parts[0]);
for(size_t p = 0;p < parts_size;++p)
{
pr.mPart = parts[p];
reservedList.push_back(pr);
}
priority = parts_size;
addParts(reservedList, priority);
parts = {ESM::PRT_Groin, ESM::PRT_RLeg, ESM::PRT_LLeg};
}
std::vector<ESM::PartReference> reservedList;
for (const auto& p : parts)
{
ESM::PartReference pr;
pr.mPart = p;
reservedList.emplace_back(pr);
}
int priority = parts.size();
addParts(clothing.mParts.mParts, priority);
addParts(reservedList, priority);
// Changing parts could affect what is picked for rendering
data->addOtherDependency(itemId);

View file

@ -64,10 +64,12 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool
// ignore content file number
std::map<ESM::RefNum, std::string>::iterator iter = cache.begin();
ref.mRefNum.mIndex = ref.mRefNum.mIndex & 0x00ffffff;
unsigned int thisIndex = ref.mRefNum.mIndex & 0x00ffffff;
if (ref.mRefNum.mContentFile != -1 && !base) ref.mRefNum.mContentFile = ref.mRefNum.mIndex >> 24;
for (; iter != cache.end(); ++iter)
{
if (ref.mRefNum.mIndex == iter->first.mIndex)
if (thisIndex == iter->first.mIndex)
break;
}

View file

@ -151,9 +151,9 @@ void CSVRender::CellArrow::buildShape()
osg::Vec4Array *colours = new osg::Vec4Array;
for (int i=0; i<6; ++i)
colours->push_back (osg::Vec4f (1.0f, 0.0f, 0.0f, 1.0f));
colours->push_back (osg::Vec4f (0.11, 0.6f, 0.95f, 1.0f));
for (int i=0; i<6; ++i)
colours->push_back (osg::Vec4f (0.8f, (i==2 || i==5) ? 0.6f : 0.4f, 0.0f, 1.0f));
colours->push_back (osg::Vec4f (0.08f, 0.44f, 0.7f, 1.0f));
geometry->setColorArray (colours, osg::Array::BIND_PER_VERTEX);

View file

@ -69,7 +69,6 @@ RenderWidget::RenderWidget(QWidget *parent, Qt::WindowFlags f)
setLayout(layout);
mView->getCamera()->setGraphicsContext(window);
mView->getCamera()->setClearColor( osg::Vec4(0.2, 0.2, 0.6, 1.0) );
mView->getCamera()->setViewport( new osg::Viewport(0, 0, traits->width, traits->height) );
SceneUtil::LightManager* lightMgr = new SceneUtil::LightManager;
@ -212,6 +211,25 @@ SceneWidget::SceneWidget(std::shared_ptr<Resource::ResourceSystem> resourceSyste
mOrbitCamControl->setConstRoll( CSMPrefs::get()["3D Scene Input"]["navi-orbit-const-roll"].isTrue() );
// set up gradient view or configured clear color
QColor bgColour = CSMPrefs::get()["Rendering"]["scene-day-background-colour"].toColor();
if (CSMPrefs::get()["Rendering"]["scene-use-gradient"].isTrue()) {
QColor gradientColour = CSMPrefs::get()["Rendering"]["scene-day-gradient-colour"].toColor();
mGradientCamera = createGradientCamera(bgColour, gradientColour);
mView->getCamera()->setClearMask(0);
mView->getCamera()->addChild(mGradientCamera.get());
}
else {
mView->getCamera()->setClearColor(osg::Vec4(
bgColour.redF(),
bgColour.greenF(),
bgColour.blueF(),
1.0f
));
}
// we handle lighting manually
mView->setLightingMode(osgViewer::View::NO_LIGHT);
@ -249,6 +267,79 @@ SceneWidget::~SceneWidget()
mResourceSystem->releaseGLObjects(mView->getCamera()->getGraphicsContext()->getState());
}
osg::ref_ptr<osg::Geometry> SceneWidget::createGradientRectangle(QColor bgColour, QColor gradientColour)
{
osg::ref_ptr<osg::Geometry> geometry = new osg::Geometry;
osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array;
vertices->push_back(osg::Vec3(0.0f, 0.0f, -1.0f));
vertices->push_back(osg::Vec3(1.0f, 0.0f, -1.0f));
vertices->push_back(osg::Vec3(0.0f, 1.0f, -1.0f));
vertices->push_back(osg::Vec3(1.0f, 1.0f, -1.0f));
geometry->setVertexArray(vertices);
osg::ref_ptr<osg::DrawElementsUShort> primitives = new osg::DrawElementsUShort (osg::PrimitiveSet::TRIANGLES, 0);
// triangle 1
primitives->push_back (0);
primitives->push_back (1);
primitives->push_back (2);
// triangle 2
primitives->push_back (2);
primitives->push_back (1);
primitives->push_back (3);
geometry->addPrimitiveSet(primitives);
osg::ref_ptr <osg::Vec4ubArray> colours = new osg::Vec4ubArray;
colours->push_back(osg::Vec4ub(gradientColour.red(), gradientColour.green(), gradientColour.blue(), 1.0f));
colours->push_back(osg::Vec4ub(gradientColour.red(), gradientColour.green(), gradientColour.blue(), 1.0f));
colours->push_back(osg::Vec4ub(bgColour.red(), bgColour.green(), bgColour.blue(), 1.0f));
colours->push_back(osg::Vec4ub(bgColour.red(), bgColour.green(), bgColour.blue(), 1.0f));
geometry->setColorArray(colours, osg::Array::BIND_PER_VERTEX);
geometry->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
geometry->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
return geometry;
}
osg::ref_ptr<osg::Camera> SceneWidget::createGradientCamera(QColor bgColour, QColor gradientColour)
{
osg::ref_ptr<osg::Camera> camera = new osg::Camera();
camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF);
camera->setProjectionMatrix(osg::Matrix::ortho2D(0, 1.0f, 0, 1.0f));
camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF);
camera->setViewMatrix(osg::Matrix::identity());
camera->setClearMask(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
camera->setAllowEventFocus(false);
// draw subgraph before main camera view.
camera->setRenderOrder(osg::Camera::PRE_RENDER);
camera->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
osg::ref_ptr<osg::Geometry> gradientQuad = createGradientRectangle(bgColour, gradientColour);
camera->addChild(gradientQuad);
return camera;
}
void SceneWidget::updateGradientCamera(QColor bgColour, QColor gradientColour)
{
osg::ref_ptr<osg::Geometry> gradientRect = createGradientRectangle(bgColour, gradientColour);
// Replaces previous rectangle
mGradientCamera->setChild(0, gradientRect.get());
}
void SceneWidget::setLighting(Lighting *lighting)
{
if (mLighting)
@ -276,12 +367,59 @@ void SceneWidget::setAmbient(const osg::Vec4f& ambient)
void SceneWidget::selectLightingMode (const std::string& mode)
{
if (mode=="day")
setLighting (&mLightingDay);
else if (mode=="night")
setLighting (&mLightingNight);
else if (mode=="bright")
setLighting (&mLightingBright);
QColor backgroundColour;
QColor gradientColour;
if (mode == "day")
{
backgroundColour = CSMPrefs::get()["Rendering"]["scene-day-background-colour"].toColor();
gradientColour = CSMPrefs::get()["Rendering"]["scene-day-gradient-colour"].toColor();
setLighting(&mLightingDay);
}
else if (mode == "night")
{
backgroundColour = CSMPrefs::get()["Rendering"]["scene-night-background-colour"].toColor();
gradientColour = CSMPrefs::get()["Rendering"]["scene-night-gradient-colour"].toColor();
setLighting(&mLightingNight);
}
else if (mode == "bright")
{
backgroundColour = CSMPrefs::get()["Rendering"]["scene-bright-background-colour"].toColor();
gradientColour = CSMPrefs::get()["Rendering"]["scene-bright-gradient-colour"].toColor();
setLighting(&mLightingBright);
}
if (CSMPrefs::get()["Rendering"]["scene-use-gradient"].isTrue()) {
if (mGradientCamera.get() != nullptr) {
// we can go ahead and update since this camera still exists
updateGradientCamera(backgroundColour, gradientColour);
if (!mView->getCamera()->containsNode(mGradientCamera.get()))
{
// need to re-attach the gradient camera
mView->getCamera()->setClearMask(0);
mView->getCamera()->addChild(mGradientCamera.get());
}
}
else {
// need to create the gradient camera
mGradientCamera = createGradientCamera(backgroundColour, gradientColour);
mView->getCamera()->setClearMask(0);
mView->getCamera()->addChild(mGradientCamera.get());
}
}
else {
// Fall back to using the clear color for the camera
mView->getCamera()->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
mView->getCamera()->setClearColor(osg::Vec4(
backgroundColour.redF(),
backgroundColour.greenF(),
backgroundColour.blueF(),
1.0f
));
if (mGradientCamera.get() != nullptr && mView->getCamera()->containsNode(mGradientCamera.get())) {
// Remove the child to prevent the gradient from rendering
mView->getCamera()->removeChild(mGradientCamera.get());
}
}
}
CSVWidget::SceneToolMode *SceneWidget::makeLightingSelector (CSVWidget::SceneToolbar *parent)

View file

@ -100,10 +100,15 @@ namespace CSVRender
void mouseMoveEvent (QMouseEvent *event) override;
void wheelEvent (QWheelEvent *event) override;
osg::ref_ptr<osg::Geometry> createGradientRectangle(QColor bgColour, QColor gradientColour);
osg::ref_ptr<osg::Camera> createGradientCamera(QColor bgColour, QColor gradientColour);
void updateGradientCamera(QColor bgColour, QColor gradientColour);
std::shared_ptr<Resource::ResourceSystem> mResourceSystem;
Lighting* mLighting;
osg::ref_ptr<osg::Camera> mGradientCamera;
osg::Vec4f mDefaultAmbient;
bool mHasDefaultAmbient;
bool mIsExterior;

View file

@ -66,14 +66,14 @@ add_openmw_dir (mwworld
cells localscripts customdata inventorystore ptr actionopen actionread actionharvest
actionequip timestamp actionalchemy cellstore actionapply actioneat
store esmstore recordcmp fallback actionrepair actionsoulgem livecellref actiondoor
contentloader esmloader actiontrap cellreflist cellref physicssystem weather projectilemanager
contentloader esmloader actiontrap cellreflist cellref weather projectilemanager
cellpreloader datetimemanager
)
add_openmw_dir (mwphysics
physicssystem trace collisiontype actor convert object heightfield closestnotmerayresultcallback
contacttestresultcallback deepestnotmecontacttestresultcallback stepper movementsolver
closestnotmeconvexresultcallback raycasting mtphysics
contacttestresultcallback deepestnotmecontacttestresultcallback stepper movementsolver projectile
closestnotmeconvexresultcallback raycasting mtphysics contacttestwrapper
)
add_openmw_dir (mwclass

View file

@ -477,8 +477,6 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager)
, mNewGame (false)
, mCfgMgr(configurationManager)
{
MWClass::registerClasses();
SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0"); // We use only gamepads
Uint32 flags = SDL_INIT_VIDEO|SDL_INIT_NOPARACHUTE|SDL_INIT_GAMECONTROLLER|SDL_INIT_JOYSTICK|SDL_INIT_SENSOR;
@ -972,6 +970,8 @@ void OMW::Engine::go()
std::string settingspath;
settingspath = loadSettings (settings);
MWClass::registerClasses();
// Create encoder
mEncoder = new ToUTF8::Utf8Encoder(mEncoding);

View file

@ -168,7 +168,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
if (variables.count ("help"))
{
std::cout << desc << std::endl;
getRawStdout() << desc << std::endl;
return false;
}
@ -177,7 +177,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
cfgMgr.readConfiguration(variables, desc, true);
Version::Version v = Version::getOpenmwVersion(variables["resources"].as<Files::EscapePath>().mPath.string());
std::cout << v.describe() << std::endl;
getRawStdout() << v.describe() << std::endl;
return false;
}
@ -202,7 +202,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
Because there is no need to print the commit hash again, only print OpenMW's version
*/
std::cout << "OpenMW version " << v.mVersion << std::endl;
Log(Debug::Info) << "OpenMW version " << v.mVersion;
/*
End of tes3mp change (minor)
*/
@ -240,7 +240,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
// Font encoding settings
std::string encoding(variables["encoding"].as<Files::EscapeHashString>().toStdString());
std::cout << ToUTF8::encodingUsingMessage(encoding) << std::endl;
Log(Debug::Info) << ToUTF8::encodingUsingMessage(encoding);
engine.setEncoding(ToUTF8::calculateEncoding(encoding));
// directory settings
@ -314,6 +314,42 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
return true;
}
namespace
{
class OSGLogHandler : public osg::NotifyHandler
{
void notify(osg::NotifySeverity severity, const char* msg) override
{
// Copy, because osg logging is not thread safe.
std::string msgCopy(msg);
if (msgCopy.empty())
return;
Debug::Level level;
switch (severity)
{
case osg::ALWAYS:
case osg::FATAL:
level = Debug::Error;
break;
case osg::WARN:
case osg::NOTICE:
level = Debug::Warning;
break;
case osg::INFO:
level = Debug::Info;
break;
case osg::DEBUG_INFO:
case osg::DEBUG_FP:
default:
level = Debug::Debug;
}
std::string_view s(msgCopy);
Log(level) << (s.back() == '\n' ? s.substr(0, s.size() - 1) : s);
}
};
}
int runApplication(int argc, char *argv[])
{
#ifdef __APPLE__
@ -322,6 +358,7 @@ int runApplication(int argc, char *argv[])
setenv("OSG_GL_TEXTURE_STORAGE", "OFF", 0);
#endif
osg::setNotifyHandler(new OSGLogHandler());
Files::ConfigurationManager cfgMgr;
std::unique_ptr<OMW::Engine> engine;
engine.reset(new OMW::Engine(cfgMgr));

View file

@ -209,6 +209,7 @@ namespace MWBase
virtual std::list<MWWorld::Ptr> getActorsSidingWith(const MWWorld::Ptr& actor) = 0;
virtual std::list<MWWorld::Ptr> getActorsFollowing(const MWWorld::Ptr& actor) = 0;
virtual std::list<int> getActorsFollowingIndices(const MWWorld::Ptr& actor) = 0;
virtual std::map<int, MWWorld::Ptr> getActorsFollowingByIndex(const MWWorld::Ptr& actor) = 0;
///Returns a list of actors who are fighting the given actor within the fAlarmDistance
/** ie AiCombat is active and the target is the actor **/

View file

@ -383,6 +383,9 @@ namespace MWBase
virtual MWWorld::Ptr moveObject(const MWWorld::Ptr &ptr, MWWorld::CellStore* newCell, float x, float y, float z, bool movePhysics=true) = 0;
///< @return an updated Ptr
virtual MWWorld::Ptr moveObjectBy(const MWWorld::Ptr &ptr, osg::Vec3f vec) = 0;
///< @return an updated Ptr
virtual void scaleObject (const MWWorld::Ptr& ptr, float scale) = 0;
virtual void rotateObject(const MWWorld::Ptr& ptr, float x, float y, float z,
@ -687,7 +690,7 @@ namespace MWBase
/// Returns true if levitation spell effect is allowed.
virtual bool isLevitationEnabled() const = 0;
virtual bool getGodModeState() = 0;
virtual bool getGodModeState() const = 0;
virtual bool toggleGodMode() = 0;

View file

@ -14,6 +14,7 @@
#include <components/esm/loadcont.hpp>
#include <components/esm/containerstate.hpp>
#include <components/settings/settings.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
@ -71,6 +72,11 @@ namespace MWClass
return *this;
}
Container::Container()
{
mHarvestEnabled = Settings::Manager::getBool("graphic herbalism", "Game");
}
void Container::ensureCustomData (const MWWorld::Ptr& ptr) const
{
if (!ptr.getRefData().getCustomData())
@ -84,8 +90,10 @@ namespace MWClass
}
}
bool canBeHarvested(const MWWorld::ConstPtr& ptr)
bool Container::canBeHarvested(const MWWorld::ConstPtr& ptr) const
{
if (!mHarvestEnabled)
return false;
const MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(ptr);
if (animation == nullptr)
return false;

View file

@ -30,11 +30,16 @@ namespace MWClass
class Container : public MWWorld::Class
{
bool mHarvestEnabled;
void ensureCustomData (const MWWorld::Ptr& ptr) const;
MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override;
bool canBeHarvested(const MWWorld::ConstPtr& ptr) const;
public:
Container();
void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override;
///< Add reference into a cell for rendering

View file

@ -551,6 +551,7 @@ namespace MWGui
setTitle(mPtr.getClass().getName(mPtr));
updateTopics();
updateTopicsPane(); // force update for new services
updateDisposition();
restock();
@ -603,12 +604,14 @@ namespace MWGui
mHistoryContents.clear();
}
void DialogueWindow::setKeywords(std::list<std::string> keyWords)
bool DialogueWindow::setKeywords(std::list<std::string> keyWords)
{
if (mKeywords == keyWords && isCompanion() == mIsCompanion)
return;
return false;
mIsCompanion = isCompanion();
mKeywords = keyWords;
updateTopicsPane();
return true;
}
void DialogueWindow::updateTopicsPane()
@ -683,6 +686,8 @@ namespace MWGui
mTopicsList->adjustSize();
updateHistory();
// The topics list has been regenerated so topic formatting needs to be updated
updateTopicFormat();
}
void DialogueWindow::updateHistory(bool scrollbar)
@ -885,9 +890,9 @@ namespace MWGui
void DialogueWindow::updateTopics()
{
setKeywords(MWBase::Environment::get().getDialogueManager()->getAvailableTopics());
updateTopicsPane();
updateTopicFormat();
// Topic formatting needs to be updated regardless of whether the topic list has changed
if (!setKeywords(MWBase::Environment::get().getDialogueManager()->getAvailableTopics()))
updateTopicFormat();
}
bool DialogueWindow::isCompanion()

View file

@ -142,7 +142,8 @@ namespace MWGui
void setPtr(const MWWorld::Ptr& actor) override;
void setKeywords(std::list<std::string> keyWord);
/// @return true if stale keywords were updated successfully
bool setKeywords(std::list<std::string> keyWord);
void addResponse (const std::string& title, const std::string& text, bool needMargin = true);

View file

@ -30,14 +30,18 @@ namespace MWGui
// vanilla game does not show any text after the last EOL tag.
const std::string lowerText = Misc::StringUtils::lowerCase(mText);
int brIndex = lowerText.rfind("<br>");
int pIndex = lowerText.rfind("<p>");
if (brIndex == pIndex)
mText = "";
else if (brIndex > pIndex)
mText = mText.substr(0, brIndex+4);
else
mText = mText.substr(0, pIndex+3);
size_t brIndex = lowerText.rfind("<br>");
size_t pIndex = lowerText.rfind("<p>");
mPlainTextEnd = 0;
if (brIndex != pIndex)
{
if (brIndex != std::string::npos && pIndex != std::string::npos)
mPlainTextEnd = std::max(brIndex, pIndex);
else if (brIndex != std::string::npos)
mPlainTextEnd = brIndex;
else
mPlainTextEnd = pIndex;
}
registerTag("br", Event_BrTag);
registerTag("p", Event_PTag);
@ -103,7 +107,8 @@ namespace MWGui
{
if (!mIgnoreLineEndings || ch != '\n')
{
mBuffer.push_back(ch);
if (mIndex < mPlainTextEnd)
mBuffer.push_back(ch);
mIgnoreLineEndings = false;
mIgnoreNewlineTags = false;
}

View file

@ -73,6 +73,8 @@ namespace MWGui
bool mClosingTag;
std::map<std::string, Events> mTagTypes;
std::string mBuffer;
size_t mPlainTextEnd;
};
class Paginator

View file

@ -78,7 +78,7 @@ void KeyboardNavigation::saveFocus(int mode)
{
mKeyFocus[mode] = focus;
}
else
else if(shouldAcceptKeyFocus(mCurrentFocus))
{
mKeyFocus[mode] = mCurrentFocus;
}
@ -104,6 +104,7 @@ void KeyboardNavigation::_unlinkWidget(MyGUI::Widget *widget)
mCurrentFocus = nullptr;
}
#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3)
void styleFocusedButton(MyGUI::Widget* w)
{
if (w)
@ -114,6 +115,7 @@ void styleFocusedButton(MyGUI::Widget* w)
}
}
}
#endif
bool isRootParent(MyGUI::Widget* widget, MyGUI::Widget* root)
{
@ -145,7 +147,9 @@ void KeyboardNavigation::onFrame()
if (focus == mCurrentFocus)
{
#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3)
styleFocusedButton(mCurrentFocus);
#endif
return;
}
@ -156,19 +160,21 @@ void KeyboardNavigation::onFrame()
focus = mCurrentFocus;
}
// style highlighted button (won't be needed for MyGUI 3.2.3)
if (focus != mCurrentFocus)
{
#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3)
if (mCurrentFocus)
{
if (MyGUI::Button* b = mCurrentFocus->castType<MyGUI::Button>(false))
b->_setWidgetState("normal");
}
#endif
mCurrentFocus = focus;
}
#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3)
styleFocusedButton(mCurrentFocus);
#endif
}
void KeyboardNavigation::setDefaultFocus(MyGUI::Widget *window, MyGUI::Widget *defaultFocus)

View file

@ -1,10 +1,12 @@
#include "loadingscreen.hpp"
#include <array>
#include <condition_variable>
#include <osgViewer/Viewer>
#include <osg/Texture2D>
#include <osg/Version>
#include <MyGUI_RenderManager.h>
#include <MyGUI_ScrollBar.h>
@ -43,6 +45,8 @@ namespace MWGui
, mNestedLoadingCount(0)
, mProgress(0)
, mShowWallpaper(true)
, mOldCallback(nullptr)
, mHasCallback(false)
{
mMainWidget->setSize(MyGUI::RenderManager::getInstance().getViewSize());
@ -136,24 +140,54 @@ namespace MWGui
{
public:
CopyFramebufferToTextureCallback(osg::Texture2D* texture)
: mTexture(texture)
, oneshot(true)
: mOneshot(true)
, mTexture(texture)
{
}
void operator () (osg::RenderInfo& renderInfo) const override
{
if (!oneshot)
return;
oneshot = false;
{
std::unique_lock<std::mutex> lock(mMutex);
mOneshot = false;
}
mSignal.notify_all();
int w = renderInfo.getCurrentCamera()->getViewport()->width();
int h = renderInfo.getCurrentCamera()->getViewport()->height();
mTexture->copyTexImage2D(*renderInfo.getState(), 0, 0, w, h);
{
std::unique_lock<std::mutex> lock(mMutex);
mOneshot = false;
}
mSignal.notify_all();
}
void wait()
{
std::unique_lock<std::mutex> lock(mMutex);
while (mOneshot)
mSignal.wait(lock);
}
void waitUntilInvoked()
{
std::unique_lock<std::mutex> lock(mMutex);
while (mOneshot)
mSignal.wait(lock);
}
void reset()
{
mOneshot = true;
}
private:
mutable bool mOneshot;
mutable std::mutex mMutex;
mutable std::condition_variable mSignal;
osg::ref_ptr<osg::Texture2D> mTexture;
mutable bool oneshot;
};
class DontComputeBoundCallback : public osg::Node::ComputeBoundingSphereCallback
@ -322,9 +356,20 @@ namespace MWGui
mGuiTexture.reset(new osgMyGUI::OSGTexture(mTexture));
}
// Notice that the next time this is called, the current CopyFramebufferToTextureCallback will be deleted
// so there's no memory leak as at most one object of type CopyFramebufferToTextureCallback is allocated at a time.
mViewer->getCamera()->setInitialDrawCallback(new CopyFramebufferToTextureCallback(mTexture));
if (!mCopyFramebufferToTextureCallback)
{
mCopyFramebufferToTextureCallback = new CopyFramebufferToTextureCallback(mTexture);
}
#if OSG_VERSION_GREATER_OR_EQUAL(3, 5, 10)
mViewer->getCamera()->addInitialDrawCallback(mCopyFramebufferToTextureCallback);
#else
// TODO: Remove once we officially end support for OSG versions pre 3.5.10
mOldCallback = mViewer->getCamera()->getInitialDrawCallback();
mViewer->getCamera()->setInitialDrawCallback(mCopyFramebufferToTextureCallback);
#endif
mCopyFramebufferToTextureCallback->reset();
mHasCallback = true;
mBackgroundImage->setBackgroundImage("");
mBackgroundImage->setVisible(false);
@ -367,6 +412,21 @@ namespace MWGui
mViewer->renderingTraversals();
mViewer->advance(mViewer->getFrameStamp()->getSimulationTime());
if (mHasCallback)
{
mCopyFramebufferToTextureCallback->waitUntilInvoked();
// Note that we are removing the callback before the draw thread has returned from it.
// This is OK as we are retaining the ref_ptr.
#if OSG_VERSION_GREATER_OR_EQUAL(3, 5, 10)
mViewer->getCamera()->removeInitialDrawCallback(mCopyFramebufferToTextureCallback);
#else
// TODO: Remove once we officially end support for OSG versions pre 3.5.10
mViewer->getCamera()->setInitialDrawCallback(mOldCallback);
#endif
mHasCallback = false;
}
mLastRenderTime = mTimer.time_m();
}

View file

@ -3,6 +3,7 @@
#include <memory>
#include <osg/Camera>
#include <osg/Timer>
#include <osg/ref_ptr>
@ -28,6 +29,7 @@ namespace Resource
namespace MWGui
{
class BackgroundImage;
class CopyFramebufferToTextureCallback;
class LoadingScreen : public WindowBase, public Loading::Listener
{
@ -84,6 +86,9 @@ namespace MWGui
std::vector<std::string> mSplashScreens;
osg::ref_ptr<osg::Texture2D> mTexture;
osg::ref_ptr<CopyFramebufferToTextureCallback> mCopyFramebufferToTextureCallback;
osg::ref_ptr<osg::Camera::DrawCallback> mOldCallback;
bool mHasCallback;
std::unique_ptr<MyGUI::ITexture> mGuiTexture;
void changeWallpaper();

View file

@ -48,9 +48,9 @@ namespace MWGui
const MWWorld::ESMStore &store =
MWBase::Environment::get().getWorld()->getStore();
for (unsigned int i = 0; i < effects.mList.size(); ++i)
for (const auto& effect : effects.mList)
{
short effectId = effects.mList[i].mEffectID;
short effectId = effect.mEffectID;
if (effectId != -1)
{
@ -59,14 +59,14 @@ namespace MWGui
std::string effectIDStr = ESM::MagicEffect::effectIdToString(effectId);
std::string fullEffectName = wm->getGameSettingString(effectIDStr, "");
if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill && effects.mList[i].mSkill != -1)
if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill && effect.mSkill != -1)
{
fullEffectName += " " + wm->getGameSettingString(ESM::Skill::sSkillNameIds[effects.mList[i].mSkill], "");
fullEffectName += " " + wm->getGameSettingString(ESM::Skill::sSkillNameIds[effect.mSkill], "");
}
if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute && effects.mList[i].mAttribute != -1)
if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute && effect.mAttribute != -1)
{
fullEffectName += " " + wm->getGameSettingString(ESM::Attribute::sGmstAttributeIds[effects.mList[i].mAttribute], "");
fullEffectName += " " + wm->getGameSettingString(ESM::Attribute::sGmstAttributeIds[effect.mAttribute], "");
}
std::string convert = Misc::StringUtils::lowerCaseUtf8(fullEffectName);

View file

@ -5,6 +5,7 @@
#include <MyGUI_ProgressBar.h>
#include <MyGUI_ImageBox.h>
#include <MyGUI_InputManager.h>
#include <MyGUI_LanguageManager.h>
#include <MyGUI_Gui.h>
#include <components/settings/settings.hpp>
@ -335,6 +336,17 @@ namespace MWGui
{
int max = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("iLevelUpTotal")->mValue.getInteger();
getWidget(levelWidget, i==0 ? "Level_str" : "LevelText");
std::stringstream detail;
for (int i = 0; i < ESM::Attribute::Length; ++i)
{
if (auto increase = PCstats.getLevelUpAttributeIncrease(i))
detail << (detail.str().empty() ? "" : "\n") << "#{"
<< MyGUI::TextIterator::toTagsString(ESM::Attribute::sGmstAttributeIds[i])
<< "} x" << MyGUI::utility::toString(increase);
}
if (!detail.str().empty())
levelWidget->setUserString("Caption_LevelDetailText", MyGUI::LanguageManager::getInstance().replaceTags(detail.str()));
levelWidget->setUserString("RangePosition_LevelProgress", MyGUI::utility::toString(PCstats.getLevelProgress()));
levelWidget->setUserString("Range_LevelProgress", MyGUI::utility::toString(max));
levelWidget->setUserString("Caption_LevelProgressText", MyGUI::utility::toString(PCstats.getLevelProgress()) + "/"

View file

@ -161,6 +161,29 @@ void getRestorationPerHourOfSleep (const MWWorld::Ptr& ptr, float& health, float
magicka = fRestMagicMult * stats.getAttribute(ESM::Attribute::Intelligence).getModified();
}
template<class T>
void forEachFollowingPackage(MWMechanics::Actors::PtrActorMap& actors, const MWWorld::Ptr& actor, const MWWorld::Ptr& player, T&& func)
{
for(auto& iter : actors)
{
const MWWorld::Ptr &iteratedActor = iter.first;
if (iteratedActor == player || iteratedActor == actor)
continue;
const MWMechanics::CreatureStats &stats = iteratedActor.getClass().getCreatureStats(iteratedActor);
if (stats.isDead())
continue;
// An actor counts as following if AiFollow is the current AiPackage,
// or there are only Combat and Wander packages before the AiFollow package
for (const auto& package : stats.getAiSequence())
{
if(!func(iter, package))
break;
}
}
}
}
namespace MWMechanics
@ -1482,6 +1505,13 @@ 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
auto shield = inventoryStore.getPreferredShield(ptr);
if(shield != inventoryStore.end())
inventoryStore.equip(MWWorld::InventoryStore::Slot_CarriedLeft, shield, ptr);
}
heldIter = inventoryStore.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
@ -2788,26 +2818,14 @@ namespace MWMechanics
std::list<MWWorld::Ptr> Actors::getActorsFollowing(const MWWorld::Ptr& actor)
{
std::list<MWWorld::Ptr> list;
for(PtrActorMap::iterator iter(mActors.begin());iter != mActors.end();++iter)
forEachFollowingPackage(mActors, actor, getPlayer(), [&] (auto& iter, const std::unique_ptr<AiPackage>& package)
{
const MWWorld::Ptr &iteratedActor = iter->first;
if (iteratedActor == getPlayer() || iteratedActor == actor)
continue;
const CreatureStats &stats = iteratedActor.getClass().getCreatureStats(iteratedActor);
if (stats.isDead())
continue;
// An actor counts as following if AiFollow is the current AiPackage,
// or there are only Combat and Wander packages before the AiFollow package
for (const auto& package : stats.getAiSequence())
{
if (package->followTargetThroughDoors() && package->getTarget() == actor)
list.push_back(iteratedActor);
else if (package->getTypeId() != AiPackageTypeId::Combat && package->getTypeId() != AiPackageTypeId::Wander)
break;
}
}
if (package->followTargetThroughDoors() && package->getTarget() == actor)
list.push_back(iter.first);
else if (package->getTypeId() != AiPackageTypeId::Combat && package->getTypeId() != AiPackageTypeId::Wander)
return false;
return true;
});
return list;
}
@ -2851,32 +2869,38 @@ namespace MWMechanics
std::list<int> Actors::getActorsFollowingIndices(const MWWorld::Ptr &actor)
{
std::list<int> list;
for(PtrActorMap::iterator iter(mActors.begin());iter != mActors.end();++iter)
forEachFollowingPackage(mActors, actor, getPlayer(), [&] (auto& iter, const std::unique_ptr<AiPackage>& package)
{
const MWWorld::Ptr &iteratedActor = iter->first;
if (iteratedActor == getPlayer() || iteratedActor == actor)
continue;
const CreatureStats &stats = iteratedActor.getClass().getCreatureStats(iteratedActor);
if (stats.isDead())
continue;
// An actor counts as following if AiFollow is the current AiPackage,
// or there are only Combat and Wander packages before the AiFollow package
for (const auto& package : stats.getAiSequence())
if (package->followTargetThroughDoors() && package->getTarget() == actor)
{
if (package->followTargetThroughDoors() && package->getTarget() == actor)
{
list.push_back(static_cast<const AiFollow*>(package.get())->getFollowIndex());
break;
}
else if (package->getTypeId() != AiPackageTypeId::Combat && package->getTypeId() != AiPackageTypeId::Wander)
break;
list.push_back(static_cast<const AiFollow*>(package.get())->getFollowIndex());
return false;
}
}
else if (package->getTypeId() != AiPackageTypeId::Combat && package->getTypeId() != AiPackageTypeId::Wander)
return false;
return true;
});
return list;
}
std::map<int, MWWorld::Ptr> Actors::getActorsFollowingByIndex(const MWWorld::Ptr &actor)
{
std::map<int, MWWorld::Ptr> map;
forEachFollowingPackage(mActors, actor, getPlayer(), [&] (auto& iter, const std::unique_ptr<AiPackage>& package)
{
if (package->followTargetThroughDoors() && package->getTarget() == actor)
{
int index = static_cast<const AiFollow*>(package.get())->getFollowIndex();
map[index] = iter.first;
return false;
}
else if (package->getTypeId() != AiPackageTypeId::Combat && package->getTypeId() != AiPackageTypeId::Wander)
return false;
return true;
});
return map;
}
std::list<MWWorld::Ptr> Actors::getActorsFighting(const MWWorld::Ptr& actor) {
std::list<MWWorld::Ptr> list;
std::vector<MWWorld::Ptr> neighbors;

View file

@ -191,6 +191,7 @@ namespace MWMechanics
/// Get the list of AiFollow::mFollowIndex for all actors following this target
std::list<int> getActorsFollowingIndices(const MWWorld::Ptr& actor);
std::map<int, MWWorld::Ptr> getActorsFollowingByIndex(const MWWorld::Ptr& actor);
///Returns the list of actors which are fighting the given actor
/**ie AiCombat is active and the target is the actor **/

View file

@ -46,27 +46,29 @@ bool MWMechanics::AiCast::execute(const MWWorld::Ptr& actor, MWMechanics::Charac
{
return false;
}
osg::Vec3f targetPos = target.getRefData().getPosition().asVec3();
if (target.getClass().isActor())
{
osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(target);
targetPos.z() += halfExtents.z() * 2 * 0.75f;
}
osg::Vec3f actorPos = actor.getRefData().getPosition().asVec3();
osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(actor);
actorPos.z() += halfExtents.z() * 2 * 0.75f;
osg::Vec3f dir = targetPos - actorPos;
bool turned = smoothTurn(actor, getZAngleToDir(dir), 2, osg::DegreesToRadians(3.f));
turned &= smoothTurn(actor, getXAngleToDir(dir), 0, osg::DegreesToRadians(3.f));
if (!turned)
return false;
}
osg::Vec3f targetPos = target.getRefData().getPosition().asVec3();
// If the target of an on-target spell is an actor that is not the caster
// the target position must be adjusted so that it's not casted at the actor's feet.
if (target != actor && target.getClass().isActor())
{
osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(target);
targetPos.z() += halfExtents.z() * 2 * 0.75f;
}
osg::Vec3f actorPos = actor.getRefData().getPosition().asVec3();
osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(actor);
actorPos.z() += halfExtents.z() * 2 * 0.75f;
osg::Vec3f dir = targetPos - actorPos;
bool turned = smoothTurn(actor, getZAngleToDir(dir), 2, osg::DegreesToRadians(3.f));
turned &= smoothTurn(actor, getXAngleToDir(dir), 0, osg::DegreesToRadians(3.f));
if (!turned)
return false;
// Check if the actor is already casting another spell
bool isCasting = MWBase::Environment::get().getMechanicsManager()->isCastingSpell(actor);
if (isCasting && !mCasting)

View file

@ -124,24 +124,22 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte
if (!mActive)
return false;
// The distances below are approximations based on observations of the original engine.
// If only one actor is following the target, it uses 186.
// If there are multiple actors following the same target, they form a group with each group member at 313 + (130 * i) distance to the target.
short followDistance = 186;
std::list<int> followers = MWBase::Environment::get().getMechanicsManager()->getActorsFollowingIndices(target);
if (followers.size() >= 2)
// In the original engine the first follower stays closer to the player than any subsequent followers.
// Followers beyond the first usually attempt to stand inside each other.
osg::Vec3f::value_type floatingDistance = 0;
auto followers = MWBase::Environment::get().getMechanicsManager()->getActorsFollowingByIndex(target);
if (followers.size() >= 2 && followers.cbegin()->first != mFollowIndex)
{
followDistance = 313;
short i = 0;
followers.sort();
for (int followIndex : followers)
for(auto& follower : followers)
{
if (followIndex == mFollowIndex)
followDistance += 130 * i;
++i;
auto halfExtent = MWBase::Environment::get().getWorld()->getHalfExtents(follower.second).y();
if(halfExtent > floatingDistance)
floatingDistance = halfExtent;
}
}
floatingDistance += MWBase::Environment::get().getWorld()->getHalfExtents(target).y();
floatingDistance += MWBase::Environment::get().getWorld()->getHalfExtents(actor).y() * 2;
short followDistance = static_cast<short>(floatingDistance);
if (!mAlwaysFollow) //Update if you only follow for a bit
{

View file

@ -158,7 +158,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f&
zTurn(actor, getZAngleToPoint(position, dest));
smoothTurn(actor, getXAngleToPoint(position, dest), 0);
world->removeActorPath(actor);
return true;
return isDestReached;
}
world->updateActorPath(actor, mPathFinder.getPath(), halfExtents, position, dest);

View file

@ -403,7 +403,7 @@ const AiPackage& MWMechanics::AiSequence::getActivePackage()
void AiSequence::fill(const ESM::AIPackageList &list)
{
// If there is more than one package in the list, enable repeating
if (!list.mList.empty() && list.mList.begin() != (list.mList.end()-1))
if (list.mList.size() >= 2)
mRepeat = true;
for (const auto& esmPackage : list.mList)
@ -459,8 +459,15 @@ void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence)
int count = 0;
for (auto& container : sequence.mPackages)
{
if (isActualAiPackage(static_cast<AiPackageTypeId>(container.mType)))
count++;
switch (container.mType)
{
case ESM::AiSequence::Ai_Wander:
case ESM::AiSequence::Ai_Travel:
case ESM::AiSequence::Ai_Escort:
case ESM::AiSequence::Ai_Follow:
case ESM::AiSequence::Ai_Activate:
++count;
}
}
if (count > 1)

View file

@ -2156,7 +2156,7 @@ void CharacterController::update(float duration, bool animationOnly)
movementSettings.mSpeedFactor *= 2.f;
static const bool smoothMovement = Settings::Manager::getBool("smooth movement", "Game");
if (smoothMovement && !isFirstPersonPlayer)
if (smoothMovement)
{
static const float playerTurningCoef = 1.0 / std::max(0.01f, Settings::Manager::getFloat("smooth movement player turning delay", "Game"));
float angle = mPtr.getRefData().getPosition().rot[2];
@ -2166,7 +2166,9 @@ void CharacterController::update(float duration, bool animationOnly)
float deltaLen = delta.length();
float maxDelta;
if (std::abs(speedDelta) < deltaLen / 2)
if (isFirstPersonPlayer)
maxDelta = 1;
else if (std::abs(speedDelta) < deltaLen / 2)
// Turning is smooth for player and less smooth for NPCs (otherwise NPC can miss a path point).
maxDelta = duration * (isPlayer ? playerTurningCoef : 6.f);
else if (isPlayer && speedDelta < -deltaLen / 2)
@ -2204,7 +2206,10 @@ void CharacterController::update(float duration, bool animationOnly)
bool canMove = cls.getMaxSpeed(mPtr) > 0;
static const bool turnToMovementDirection = Settings::Manager::getBool("turn to movement direction", "Game");
if (!turnToMovementDirection || isFirstPersonPlayer)
{
movementSettings.mIsStrafing = std::abs(vec.x()) > std::abs(vec.y()) * 2;
stats.setSideMovementAngle(0);
}
else if (canMove)
{
float targetMovementAngle = vec.y() >= 0 ? std::atan2(-vec.x(), vec.y()) : std::atan2(vec.x(), -vec.y());
@ -2456,18 +2461,19 @@ void CharacterController::update(float duration, bool animationOnly)
sndMgr->playSound3D(mPtr, sound, 1.f, 1.f, MWSound::Type::Foot, MWSound::PlayMode::NoPlayerLocal);
}
if (turnToMovementDirection)
if (turnToMovementDirection && !isFirstPersonPlayer &&
(movestate == CharState_SwimRunForward || movestate == CharState_SwimWalkForward ||
movestate == CharState_SwimRunBack || movestate == CharState_SwimWalkBack))
{
float targetSwimmingPitch;
if (inwater && vec.y() != 0 && !isFirstPersonPlayer && !movementSettings.mIsStrafing)
targetSwimmingPitch = -mPtr.getRefData().getPosition().rot[0];
else
targetSwimmingPitch = 0;
float maxSwimPitchDelta = 3.0f * duration;
float swimmingPitch = mAnimation->getBodyPitchRadians();
float targetSwimmingPitch = -mPtr.getRefData().getPosition().rot[0];
float maxSwimPitchDelta = 3.0f * duration;
swimmingPitch += osg::clampBetween(targetSwimmingPitch - swimmingPitch, -maxSwimPitchDelta, maxSwimPitchDelta);
mAnimation->setBodyPitchRadians(swimmingPitch);
}
else
mAnimation->setBodyPitchRadians(0);
static const bool swimUpwardCorrection = Settings::Manager::getBool("swim upward correction", "Game");
if (inwater && isPlayer && !isFirstPersonPlayer && swimUpwardCorrection)
{
@ -2615,7 +2621,7 @@ void CharacterController::update(float duration, bool animationOnly)
moved.y() *= scale;
// Ensure we're moving in generally the right direction...
if(speed > 0.f)
if (speed > 0.f && moved != osg::Vec3f())
{
float l = moved.length();
if (std::abs(movement.x() - moved.x()) > std::abs(moved.x()) / 2 ||
@ -2631,8 +2637,14 @@ void CharacterController::update(float duration, bool animationOnly)
}
}
if (mFloatToSurface && cls.isActor() && cls.getCreatureStats(mPtr).isDead() && cls.canSwim(mPtr))
moved.z() = 1.0;
if (mFloatToSurface && cls.isActor() && cls.canSwim(mPtr))
{
if (cls.getCreatureStats(mPtr).isDead()
|| (!godmode && cls.getCreatureStats(mPtr).isParalyzed()))
{
moved.z() = 1.0;
}
}
// Update movement
if(!animationOnly && mMovementAnimationControlled && mPtr.getClass().isActor())

View file

@ -1794,6 +1794,11 @@ namespace MWMechanics
return mActors.getActorsFollowingIndices(actor);
}
std::map<int, MWWorld::Ptr> MechanicsManager::getActorsFollowingByIndex(const MWWorld::Ptr& actor)
{
return mActors.getActorsFollowingByIndex(actor);
}
std::list<MWWorld::Ptr> MechanicsManager::getActorsFighting(const MWWorld::Ptr& actor) {
return mActors.getActorsFighting(actor);
}

View file

@ -160,6 +160,7 @@ namespace MWMechanics
std::list<MWWorld::Ptr> getActorsSidingWith(const MWWorld::Ptr& actor) override;
std::list<MWWorld::Ptr> getActorsFollowing(const MWWorld::Ptr& actor) override;
std::list<int> getActorsFollowingIndices(const MWWorld::Ptr& actor) override;
std::map<int, MWWorld::Ptr> getActorsFollowingByIndex(const MWWorld::Ptr& actor) override;
std::list<MWWorld::Ptr> getActorsFighting(const MWWorld::Ptr& actor) override;
std::list<MWWorld::Ptr> getEnemiesNearby(const MWWorld::Ptr& actor) override;

View file

@ -383,6 +383,11 @@ void MWMechanics::NpcStats::updateHealth()
setHealth(floor(0.5f * (strength + endurance)));
}
int MWMechanics::NpcStats::getLevelUpAttributeIncrease(int attribute) const
{
return mSkillIncreases[attribute];
}
int MWMechanics::NpcStats::getLevelupAttributeMultiplier(int attribute) const
{
int num = mSkillIncreases[attribute];

View file

@ -131,6 +131,8 @@ namespace MWMechanics
End of tes3mp addition
*/
int getLevelUpAttributeIncrease(int attribute) const;
int getLevelupAttributeMultiplier(int attribute) const;
int getSkillIncreasesForSpecialization(int spec) const;

View file

@ -13,7 +13,6 @@
End of tes3mp addition
*/
#include <BulletCollision/CollisionShapes/btCapsuleShape.h>
#include <BulletCollision/CollisionShapes/btBoxShape.h>
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
@ -27,13 +26,15 @@
#include "collisiontype.hpp"
#include "mtphysics.hpp"
#include <cmath>
namespace MWPhysics
{
Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, PhysicsTaskScheduler* scheduler)
: mStandingOnPtr(nullptr), mCanWaterWalk(false), mWalkingOnWater(false)
, mCollisionObject(nullptr), mMeshTranslation(shape->mCollisionBoxTranslate), mHalfExtents(shape->mCollisionBoxHalfExtents)
, mCollisionObject(nullptr), mMeshTranslation(shape->mCollisionBox.center), mHalfExtents(shape->mCollisionBox.extents)
, mForce(0.f, 0.f, 0.f), mOnGround(true), mOnSlope(false)
, mInternalCollisionMode(true)
, mExternalCollisionMode(true)
@ -65,17 +66,8 @@ Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, Physic
Log(Debug::Error) << "Error: Failed to calculate bounding box for actor \"" << ptr.getCellRef().getRefId() << "\".";
}
// Use capsule shape only if base is square (nonuniform scaling apparently doesn't work on it)
if (std::abs(mHalfExtents.x()-mHalfExtents.y())<mHalfExtents.x()*0.05 && mHalfExtents.z() >= mHalfExtents.x())
{
mShape.reset(new btCapsuleShapeZ(mHalfExtents.x(), 2*mHalfExtents.z() - 2*mHalfExtents.x()));
mRotationallyInvariant = true;
}
else
{
mShape.reset(new btBoxShape(Misc::Convert::toBullet(mHalfExtents)));
mRotationallyInvariant = false;
}
mShape.reset(new btBoxShape(Misc::Convert::toBullet(mHalfExtents)));
mRotationallyInvariant = (mMeshTranslation.x() == 0.0 && mMeshTranslation.y() == 0.0) && std::fabs(mHalfExtents.x() - mHalfExtents.y()) < 2.2;
mConvexShape = static_cast<btConvexShape*>(mShape.get());
@ -83,11 +75,14 @@ Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, Physic
mCollisionObject->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT);
mCollisionObject->setActivationState(DISABLE_DEACTIVATION);
mCollisionObject->setCollisionShape(mShape.get());
mCollisionObject->setUserPointer(static_cast<PtrHolder*>(this));
mCollisionObject->setUserPointer(this);
updateRotation();
updateScale();
resetPosition();
if(!mRotationallyInvariant)
updateRotation();
updatePosition();
addCollisionMask(getCollisionMask());
/*
@ -110,6 +105,8 @@ Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, Physic
/*
End of tes3mp addition
*/
updateCollisionObjectPosition();
}
Actor::~Actor()
@ -152,26 +149,34 @@ int Actor::getCollisionMask() const
return collisionMask;
}
void Actor::updatePositionUnsafe()
{
mWorldPosition = mPtr.getRefData().getPosition().asVec3();
}
void Actor::updatePosition()
{
std::scoped_lock lock(mPositionMutex);
updatePositionUnsafe();
updateWorldPosition();
mPreviousPosition = mWorldPosition;
mPosition = mWorldPosition;
mSimulationPosition = mWorldPosition;
mStandingOnPtr = nullptr;
mSkipSimulation = true;
}
void Actor::updateWorldPosition()
{
if (mWorldPosition != mPtr.getRefData().getPosition().asVec3())
mWorldPositionChanged = true;
mWorldPosition = mPtr.getRefData().getPosition().asVec3();
}
osg::Vec3f Actor::getWorldPosition() const
{
std::scoped_lock lock(mPositionMutex);
return mWorldPosition;
}
void Actor::setSimulationPosition(const osg::Vec3f& position)
{
mSimulationPosition = position;
if (!mSkipSimulation)
mSimulationPosition = position;
mSkipSimulation = false;
}
osg::Vec3f Actor::getSimulationPosition() const
@ -179,20 +184,21 @@ osg::Vec3f Actor::getSimulationPosition() const
return mSimulationPosition;
}
void Actor::updateCollisionObjectPositionUnsafe()
osg::Vec3f Actor::getScaledMeshTranslation() const
{
return mRotation * osg::componentMultiply(mMeshTranslation, mScale);
}
void Actor::updateCollisionObjectPosition()
{
std::scoped_lock lock(mPositionMutex);
mShape->setLocalScaling(Misc::Convert::toBullet(mScale));
osg::Vec3f scaledTranslation = mRotation * osg::componentMultiply(mMeshTranslation, mScale);
osg::Vec3f newPosition = scaledTranslation + mPosition;
mLocalTransform.setOrigin(Misc::Convert::toBullet(newPosition));
mLocalTransform.setRotation(Misc::Convert::toBullet(mRotation));
mCollisionObject->setWorldTransform(mLocalTransform);
}
void Actor::updateCollisionObjectPosition()
{
std::scoped_lock lock(mPositionMutex);
updateCollisionObjectPositionUnsafe();
mWorldPositionChanged = false;
}
osg::Vec3f Actor::getCollisionObjectPosition() const
@ -201,28 +207,20 @@ osg::Vec3f Actor::getCollisionObjectPosition() const
return Misc::Convert::toOsg(mLocalTransform.getOrigin());
}
void Actor::setPosition(const osg::Vec3f& position)
bool Actor::setPosition(const osg::Vec3f& position)
{
std::scoped_lock lock(mPositionMutex);
mPreviousPosition = mPosition;
mPosition = position;
bool hasChanged = mPosition != position || mPositionOffset.length() != 0 || mWorldPositionChanged;
mPreviousPosition = mPosition + mPositionOffset;
mPosition = position + mPositionOffset;
mPositionOffset = osg::Vec3f();
return hasChanged;
}
void Actor::adjustPosition(const osg::Vec3f& offset)
{
std::scoped_lock lock(mPositionMutex);
mPosition += offset;
mPreviousPosition += offset;
}
void Actor::resetPosition()
{
std::scoped_lock lock(mPositionMutex);
updatePositionUnsafe();
mPreviousPosition = mWorldPosition;
mPosition = mWorldPosition;
mSimulationPosition = mWorldPosition;
updateCollisionObjectPositionUnsafe();
mPositionOffset += offset;
}
osg::Vec3f Actor::getPosition() const
@ -238,8 +236,6 @@ osg::Vec3f Actor::getPreviousPosition() const
void Actor::updateRotation ()
{
std::scoped_lock lock(mPositionMutex);
if (mRotation == mPtr.getRefData().getBaseNode()->getAttitude())
return;
mRotation = mPtr.getRefData().getBaseNode()->getAttitude();
}
@ -270,7 +266,6 @@ osg::Vec3f Actor::getHalfExtents() const
osg::Vec3f Actor::getOriginalHalfExtents() const
{
std::scoped_lock lock(mPositionMutex);
return mHalfExtents;
}
@ -307,7 +302,6 @@ void Actor::setWalkingOnWater(bool walkingOnWater)
void Actor::setCanWaterWalk(bool waterWalk)
{
std::scoped_lock lock(mPositionMutex);
if (waterWalk != mCanWaterWalk)
{
mCanWaterWalk = waterWalk;

View file

@ -60,7 +60,7 @@ namespace MWPhysics
* Set mWorldPosition to the position in the Ptr's RefData. This is used by the physics simulation to account for
* when an object is "instantly" moved/teleported as opposed to being moved by the physics simulation.
*/
void updatePosition();
void updateWorldPosition();
osg::Vec3f getWorldPosition() const;
/**
@ -82,6 +82,9 @@ namespace MWPhysics
*/
osg::Vec3f getOriginalHalfExtents() const;
/// Returns the mesh translation, scaled and rotated as necessary
osg::Vec3f getScaledMeshTranslation() const;
/**
* Returns the position of the collision body
* @note The collision shape's origin is in its center, so the position returned can be described as center of the actor collision box in world space.
@ -90,9 +93,10 @@ namespace MWPhysics
/**
* Store the current position into mPreviousPosition, then move to this position.
* Returns true if the new position is different.
*/
void setPosition(const osg::Vec3f& position);
void resetPosition();
bool setPosition(const osg::Vec3f& position);
void updatePosition();
void adjustPosition(const osg::Vec3f& offset);
osg::Vec3f getPosition() const;
@ -154,8 +158,6 @@ namespace MWPhysics
void updateCollisionMask();
void addCollisionMask(int collisionMask);
int getCollisionMask() const;
void updateCollisionObjectPositionUnsafe();
void updatePositionUnsafe();
bool mCanWaterWalk;
std::atomic<bool> mWalkingOnWater;
@ -177,6 +179,9 @@ namespace MWPhysics
osg::Vec3f mSimulationPosition;
osg::Vec3f mPosition;
osg::Vec3f mPreviousPosition;
osg::Vec3f mPositionOffset;
bool mWorldPositionChanged;
bool mSkipSimulation;
btTransform mLocalTransform;
mutable std::mutex mPositionMutex;

View file

@ -1,20 +1,92 @@
#include <mutex>
#include "closestnotmeconvexresultcallback.hpp"
#include "collisiontype.hpp"
#include "contacttestwrapper.h"
#include <BulletCollision/CollisionDispatch/btCollisionObject.h>
#include <components/misc/convert.hpp>
#include "collisiontype.hpp"
#include "projectile.hpp"
namespace MWPhysics
{
ClosestNotMeConvexResultCallback::ClosestNotMeConvexResultCallback(const btCollisionObject *me, const btVector3 &motion, btScalar minCollisionDot)
class ActorOverlapTester : public btCollisionWorld::ContactResultCallback
{
public:
bool overlapping = false;
btScalar addSingleResult(btManifoldPoint& cp,
const btCollisionObjectWrapper* colObj0Wrap,
int partId0,
int index0,
const btCollisionObjectWrapper* colObj1Wrap,
int partId1,
int index1) override
{
if(cp.getDistance() <= 0.0f)
overlapping = true;
return btScalar(1);
}
};
ClosestNotMeConvexResultCallback::ClosestNotMeConvexResultCallback(const btCollisionObject *me, const btVector3 &motion, btScalar minCollisionDot, const btCollisionWorld * world)
: btCollisionWorld::ClosestConvexResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0)),
mMe(me), mMotion(motion), mMinCollisionDot(minCollisionDot)
mMe(me), mMotion(motion), mMinCollisionDot(minCollisionDot), mWorld(world)
{
}
btScalar ClosestNotMeConvexResultCallback::addSingleResult(btCollisionWorld::LocalConvexResult& convexResult,bool normalInWorldSpace)
btScalar ClosestNotMeConvexResultCallback::addSingleResult(btCollisionWorld::LocalConvexResult& convexResult, bool normalInWorldSpace)
{
if (convexResult.m_hitCollisionObject == mMe)
return btScalar(1);
// override data for actor-actor collisions
// vanilla Morrowind seems to make overlapping actors collide as though they are both cylinders with a diameter of the distance between them
// For some reason this doesn't work as well as it should when using capsules, but it still helps a lot.
if(convexResult.m_hitCollisionObject->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Actor)
{
ActorOverlapTester isOverlapping;
// FIXME: This is absolutely terrible and bullet should feel terrible for not making contactPairTest const-correct.
ContactTestWrapper::contactPairTest(const_cast<btCollisionWorld*>(mWorld), const_cast<btCollisionObject*>(mMe), const_cast<btCollisionObject*>(convexResult.m_hitCollisionObject), isOverlapping);
if(isOverlapping.overlapping)
{
auto originA = Misc::Convert::toOsg(mMe->getWorldTransform().getOrigin());
auto originB = Misc::Convert::toOsg(convexResult.m_hitCollisionObject->getWorldTransform().getOrigin());
osg::Vec3f motion = Misc::Convert::toOsg(mMotion);
osg::Vec3f normal = (originA-originB);
normal.z() = 0;
normal.normalize();
// only collide if horizontally moving towards the hit actor (note: the motion vector appears to be inverted)
// FIXME: This kinda screws with standing on actors that walk up slopes for some reason. Makes you fall through them.
// It happens in vanilla Morrowind too, but much less often.
// I tried hunting down why but couldn't figure it out. Possibly a stair stepping or ground ejection bug.
if(normal * motion > 0.0f)
{
convexResult.m_hitFraction = 0.0f;
convexResult.m_hitNormalLocal = Misc::Convert::toBullet(normal);
return ClosestConvexResultCallback::addSingleResult(convexResult, true);
}
else
{
return btScalar(1);
}
}
}
if (convexResult.m_hitCollisionObject->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Projectile)
{
auto* projectileHolder = static_cast<Projectile*>(convexResult.m_hitCollisionObject->getUserPointer());
if (!projectileHolder->isActive())
return btScalar(1);
auto* targetHolder = static_cast<PtrHolder*>(mMe->getUserPointer());
const MWWorld::Ptr target = targetHolder->getPtr();
if (projectileHolder->isValidTarget(target))
projectileHolder->hit(target, convexResult.m_hitPointLocal, convexResult.m_hitNormalLocal);
return btScalar(1);
}
btVector3 hitNormalWorld;
if (normalInWorldSpace)
hitNormalWorld = convexResult.m_hitNormalLocal;

View file

@ -10,7 +10,7 @@ namespace MWPhysics
class ClosestNotMeConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback
{
public:
ClosestNotMeConvexResultCallback(const btCollisionObject *me, const btVector3 &motion, btScalar minCollisionDot);
ClosestNotMeConvexResultCallback(const btCollisionObject *me, const btVector3 &motion, btScalar minCollisionDot, const btCollisionWorld * world);
btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult,bool normalInWorldSpace) override;
@ -18,6 +18,7 @@ namespace MWPhysics
const btCollisionObject *mMe;
const btVector3 mMotion;
const btScalar mMinCollisionDot;
const btCollisionWorld * mWorld;
};
}

View file

@ -1,18 +1,22 @@
#include "closestnotmerayresultcallback.hpp"
#include <algorithm>
#include <utility>
#include <BulletCollision/CollisionDispatch/btCollisionObject.h>
#include "../mwworld/class.hpp"
#include "actor.hpp"
#include "collisiontype.hpp"
#include "projectile.hpp"
#include "ptrholder.hpp"
namespace MWPhysics
{
ClosestNotMeRayResultCallback::ClosestNotMeRayResultCallback(const btCollisionObject* me, const std::vector<const btCollisionObject*>& targets, const btVector3& from, const btVector3& to)
ClosestNotMeRayResultCallback::ClosestNotMeRayResultCallback(const btCollisionObject* me, std::vector<const btCollisionObject*> targets, const btVector3& from, const btVector3& to, Projectile* proj)
: btCollisionWorld::ClosestRayResultCallback(from, to)
, mMe(me), mTargets(targets)
, mMe(me), mTargets(std::move(targets)), mProjectile(proj)
{
}
@ -20,15 +24,41 @@ namespace MWPhysics
{
if (rayResult.m_collisionObject == mMe)
return 1.f;
if (mProjectile && rayResult.m_collisionObject == mProjectile->getCollisionObject())
return 1.f;
if (!mTargets.empty())
{
if ((std::find(mTargets.begin(), mTargets.end(), rayResult.m_collisionObject) == mTargets.end()))
{
PtrHolder* holder = static_cast<PtrHolder*>(rayResult.m_collisionObject->getUserPointer());
auto* holder = static_cast<PtrHolder*>(rayResult.m_collisionObject->getUserPointer());
if (holder && !holder->getPtr().isEmpty() && holder->getPtr().getClass().isActor())
return 1.f;
}
}
return btCollisionWorld::ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace);
btCollisionWorld::ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace);
if (mProjectile)
{
switch (rayResult.m_collisionObject->getBroadphaseHandle()->m_collisionFilterGroup)
{
case CollisionType_Actor:
{
auto* target = static_cast<Actor*>(rayResult.m_collisionObject->getUserPointer());
mProjectile->hit(target->getPtr(), m_hitPointWorld, m_hitNormalWorld);
break;
}
case CollisionType_Projectile:
{
auto* target = static_cast<Projectile*>(rayResult.m_collisionObject->getUserPointer());
target->hit(mProjectile->getPtr(), m_hitPointWorld, m_hitNormalWorld);
mProjectile->hit(target->getPtr(), m_hitPointWorld, m_hitNormalWorld);
break;
}
}
}
return rayResult.m_hitFraction;
}
}

View file

@ -9,15 +9,18 @@ class btCollisionObject;
namespace MWPhysics
{
class Projectile;
class ClosestNotMeRayResultCallback : public btCollisionWorld::ClosestRayResultCallback
{
public:
ClosestNotMeRayResultCallback(const btCollisionObject* me, const std::vector<const btCollisionObject*>& targets, const btVector3& from, const btVector3& to);
ClosestNotMeRayResultCallback(const btCollisionObject* me, std::vector<const btCollisionObject*> targets, const btVector3& from, const btVector3& to, Projectile* proj=nullptr);
btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) override;
private:
const btCollisionObject* mMe;
const std::vector<const btCollisionObject*> mTargets;
Projectile* mProjectile;
};
}

View file

@ -5,12 +5,22 @@ namespace MWPhysics
{
static const float sStepSizeUp = 34.0f;
static const float sStepSizeDown = 62.0f;
static const float sMinStep = 10.f;
static const float sMinStep = 10.0f; // hack to skip over tiny unwalkable slopes
static const float sMinStep2 = 20.0f; // hack to skip over shorter but longer/wider/further unwalkable slopes
// whether to do the above stairstepping logic hacks to work around bad morrowind assets - disabling causes problems but improves performance
static const bool sDoExtraStairHacks = true;
static const float sGroundOffset = 1.0f;
static const float sMaxSlope = 49.0f;
// Arbitrary number. To prevent infinite loops. They shouldn't happen but it's good to be prepared.
static const int sMaxIterations = 8;
// Allows for more precise movement solving without getting stuck or snagging too easily.
static const float sCollisionMargin = 0.1;
// Allow for a small amount of penetration to prevent numerical precision issues from causing the "unstuck"ing code to run unnecessarily
// Currently set to 0 because having the "unstuck"ing code run whenever possible prevents some glitchy snagging issues
static const float sAllowedPenetration = 0.0;
}
#endif

View file

@ -0,0 +1,21 @@
#include <mutex>
#include "contacttestwrapper.h"
namespace MWPhysics
{
// Concurrent calls to contactPairTest (and by extension contactTest) are forbidden.
static std::mutex contactMutex;
void ContactTestWrapper::contactTest(btCollisionWorld* collisionWorld, btCollisionObject* colObj, btCollisionWorld::ContactResultCallback& resultCallback)
{
std::unique_lock lock(contactMutex);
collisionWorld->contactTest(colObj, resultCallback);
}
void ContactTestWrapper::contactPairTest(btCollisionWorld* collisionWorld, btCollisionObject* colObjA, btCollisionObject* colObjB, btCollisionWorld::ContactResultCallback& resultCallback)
{
std::unique_lock lock(contactMutex);
collisionWorld->contactPairTest(colObjA, colObjB, resultCallback);
}
}

View file

@ -0,0 +1,14 @@
#ifndef OPENMW_MWPHYSICS_CONTACTTESTWRAPPER_H
#define OPENMW_MWPHYSICS_CONTACTTESTWRAPPER_H
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
namespace MWPhysics
{
struct ContactTestWrapper
{
static void contactTest(btCollisionWorld* collisionWorld, btCollisionObject* colObj, btCollisionWorld::ContactResultCallback& resultCallback);
static void contactPairTest(btCollisionWorld* collisionWorld, btCollisionObject* colObjA, btCollisionObject* colObjB, btCollisionWorld::ContactResultCallback& resultCallback);
};
}
#endif

View file

@ -17,10 +17,13 @@
#include "actor.hpp"
#include "collisiontype.hpp"
#include "constants.hpp"
#include "contacttestwrapper.h"
#include "physicssystem.hpp"
#include "stepper.hpp"
#include "trace.h"
#include <cmath>
namespace MWPhysics
{
static bool isActor(const btCollisionObject *obj)
@ -29,12 +32,50 @@ namespace MWPhysics
return obj->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Actor;
}
template <class Vec3>
static bool isWalkableSlope(const Vec3 &normal)
class ContactCollectionCallback : public btCollisionWorld::ContactResultCallback
{
static const float sMaxSlopeCos = std::cos(osg::DegreesToRadians(sMaxSlope));
return (normal.z() > sMaxSlopeCos);
}
public:
ContactCollectionCallback(const btCollisionObject * me, osg::Vec3f velocity) : mMe(me)
{
m_collisionFilterGroup = me->getBroadphaseHandle()->m_collisionFilterGroup;
m_collisionFilterMask = me->getBroadphaseHandle()->m_collisionFilterMask;
mVelocity = Misc::Convert::toBullet(velocity);
}
btScalar addSingleResult(btManifoldPoint & contact, const btCollisionObjectWrapper * colObj0Wrap, int partId0, int index0, const btCollisionObjectWrapper * colObj1Wrap, int partId1, int index1) override
{
if (isActor(colObj0Wrap->getCollisionObject()) && isActor(colObj1Wrap->getCollisionObject()))
return 0.0;
// ignore overlap if we're moving in the same direction as it would push us out (don't change this to >=, that would break detection when not moving)
if (contact.m_normalWorldOnB.dot(mVelocity) > 0.0)
return 0.0;
auto delta = contact.m_normalWorldOnB * -contact.m_distance1;
mContactSum += delta;
mMaxX = std::max(std::abs(delta.x()), mMaxX);
mMaxY = std::max(std::abs(delta.y()), mMaxY);
mMaxZ = std::max(std::abs(delta.z()), mMaxZ);
if (contact.m_distance1 < mDistance)
{
mDistance = contact.m_distance1;
mNormal = contact.m_normalWorldOnB;
mDelta = delta;
return mDistance;
}
else
{
return 0.0;
}
}
btScalar mMaxX = 0.0;
btScalar mMaxY = 0.0;
btScalar mMaxZ = 0.0;
btVector3 mContactSum{0.0, 0.0, 0.0};
btVector3 mNormal{0.0, 0.0, 0.0}; // points towards "me"
btVector3 mDelta{0.0, 0.0, 0.0}; // points towards "me"
btScalar mDistance = 0.0; // negative or zero
protected:
btVector3 mVelocity;
const btCollisionObject * mMe;
};
osg::Vec3f MovementSolver::traceDown(const MWWorld::Ptr &ptr, const osg::Vec3f& position, Actor* actor, btCollisionWorld* collisionWorld, float maxHeight)
{
@ -78,12 +119,14 @@ namespace MWPhysics
WorldFrameData& worldData)
{
auto* physicActor = actor.mActorRaw;
auto ptr = actor.mPtr;
const ESM::Position& refpos = actor.mRefpos;
// Early-out for totally static creatures
// (Not sure if gravity should still apply?)
if (!ptr.getClass().isMobile(ptr))
return;
{
const auto ptr = physicActor->getPtr();
if (!ptr.getClass().isMobile(ptr))
return;
}
// Reset per-frame data
physicActor->setWalkingOnWater(false);
@ -97,13 +140,13 @@ namespace MWPhysics
}
const btCollisionObject *colobj = physicActor->getCollisionObject();
osg::Vec3f halfExtents = physicActor->getHalfExtents();
// NOTE: here we don't account for the collision box translation (i.e. physicActor->getPosition() - refpos.pos).
// That means the collision shape used for moving this actor is in a different spot than the collision shape
// other actors are using to collide against this actor.
// While this is strictly speaking wrong, it's needed for MW compatibility.
actor.mPosition.z() += halfExtents.z();
// Adjust for collision mesh offset relative to actor's "location"
// (doTrace doesn't take local/interior collision shape translation into account, so we have to do it on our own)
// for compatibility with vanilla assets, we have to derive this from the vertical half extent instead of from internal hull translation
// if not for this hack, the "correct" collision hull position would be physicActor->getScaledMeshTranslation()
osg::Vec3f halfExtents = physicActor->getHalfExtents();
actor.mPosition.z() += halfExtents.z(); // vanilla-accurate
static const float fSwimHeightScale = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fSwimHeightScale")->mValue.getFloat();
float swimlevel = actor.mWaterlevel + halfExtents.z() - (physicActor->getRenderingHalfExtents().z() * 2 * fSwimHeightScale);
@ -128,8 +171,9 @@ namespace MWPhysics
velocity = velocity + inertia;
}
// dead actors underwater will float to the surface, if the CharacterController tells us to do so
if (actor.mMovement.z() > 0 && actor.mIsDead && actor.mPosition.z() < swimlevel)
// Dead and paralyzed actors underwater will float to the surface,
// if the CharacterController tells us to do so
if (actor.mMovement.z() > 0 && actor.mFloatToSurface && actor.mPosition.z() < swimlevel)
velocity = osg::Vec3f(0,0,1) * 25;
if (actor.mWantJump)
@ -153,6 +197,13 @@ namespace MWPhysics
* The initial velocity was set earlier (see above).
*/
float remainingTime = time;
bool seenGround = physicActor->getOnGround() && !physicActor->getOnSlope() && !actor.mFlying;
int numTimesSlid = 0;
osg::Vec3f lastSlideNormal(0,0,1);
osg::Vec3f lastSlideNormalFallback(0,0,1);
bool forceGroundTest = false;
for (int iterations = 0; iterations < sMaxIterations && remainingTime > 0.01f; ++iterations)
{
osg::Vec3f nextpos = newPosition + velocity * remainingTime;
@ -161,7 +212,7 @@ namespace MWPhysics
if(!actor.mFlying && nextpos.z() > swimlevel && newPosition.z() < swimlevel)
{
const osg::Vec3f down(0,0,-1);
velocity = slide(velocity, down);
velocity = reject(velocity, down);
// NOTE: remainingTime is unchanged before the loop continues
continue; // velocity updated, calculate nextpos again
}
@ -190,91 +241,158 @@ namespace MWPhysics
break;
}
// We are touching something.
if (tracer.mFraction < 1E-9f)
{
// Try to separate by backing off slighly to unstuck the solver
osg::Vec3f backOff = (newPosition - tracer.mHitPoint) * 1E-2f;
newPosition += backOff;
}
if (isWalkableSlope(tracer.mPlaneNormal) && !actor.mFlying && newPosition.z() >= swimlevel)
seenGround = true;
// We hit something. Check if we can step up.
float hitHeight = tracer.mHitPoint.z() - tracer.mEndPos.z() + halfExtents.z();
osg::Vec3f oldPosition = newPosition;
bool result = false;
bool usedStepLogic = false;
if (hitHeight < sStepSizeUp && !isActor(tracer.mHitObject))
{
// Try to step up onto it.
// NOTE: stepMove does not allow stepping over, modifies newPosition if successful
result = stepper.step(newPosition, velocity*remainingTime, remainingTime);
// NOTE: this modifies newPosition and velocity on its own if successful
usedStepLogic = stepper.step(newPosition, velocity, remainingTime, seenGround, iterations == 0);
}
if (result)
if (usedStepLogic)
{
// don't let pure water creatures move out of water after stepMove
const auto ptr = physicActor->getPtr();
if (ptr.getClass().isPureWaterCreature(ptr) && newPosition.z() + halfExtents.z() > actor.mWaterlevel)
newPosition = oldPosition;
else if(!actor.mFlying && actor.mPosition.z() >= swimlevel)
forceGroundTest = true;
}
else
{
// Can't move this way, try to find another spot along the plane
osg::Vec3f newVelocity = slide(velocity, tracer.mPlaneNormal);
// Can't step up, so slide against what we ran into
remainingTime *= (1.0f-tracer.mFraction);
// Do not allow sliding upward if there is gravity.
// Stepping will have taken care of that.
if(!(newPosition.z() < swimlevel || actor.mFlying))
newVelocity.z() = std::min(newVelocity.z(), 0.0f);
auto planeNormal = tracer.mPlaneNormal;
if ((newVelocity-velocity).length2() < 0.01)
// If we touched the ground this frame, and whatever we ran into is a wall of some sort,
// pretend that its collision normal is pointing horizontally
// (fixes snagging on slightly downward-facing walls, and crawling up the bases of very steep walls because of the collision margin)
if (seenGround && !isWalkableSlope(planeNormal) && planeNormal.z() != 0)
{
planeNormal.z() = 0;
planeNormal.normalize();
}
// Move up to what we ran into (with a bit of a collision margin)
if ((newPosition-tracer.mEndPos).length2() > sCollisionMargin*sCollisionMargin)
{
auto direction = velocity;
direction.normalize();
newPosition = tracer.mEndPos;
newPosition -= direction*sCollisionMargin;
}
osg::Vec3f newVelocity = (velocity * planeNormal <= 0.0) ? reject(velocity, planeNormal) : velocity;
bool usedSeamLogic = false;
// check for the current and previous collision planes forming an acute angle; slide along the seam if they do
if(numTimesSlid > 0)
{
auto dotA = lastSlideNormal * planeNormal;
auto dotB = lastSlideNormalFallback * planeNormal;
if(numTimesSlid <= 1) // ignore fallback normal if this is only the first or second slide
dotB = 1.0;
if(dotA <= 0.0 || dotB <= 0.0)
{
osg::Vec3f bestNormal = lastSlideNormal;
// use previous-to-previous collision plane if it's acute with current plane but actual previous plane isn't
if(dotB < dotA)
{
bestNormal = lastSlideNormalFallback;
lastSlideNormal = lastSlideNormalFallback;
}
auto constraintVector = bestNormal ^ planeNormal; // cross product
if(constraintVector.length2() > 0) // only if it's not zero length
{
constraintVector.normalize();
newVelocity = project(velocity, constraintVector);
// version of surface rejection for acute crevices/seams
auto averageNormal = bestNormal + planeNormal;
averageNormal.normalize();
tracer.doTrace(colobj, newPosition, newPosition + averageNormal*(sCollisionMargin*2.0), collisionWorld);
newPosition = (newPosition + tracer.mEndPos)/2.0;
usedSeamLogic = true;
}
}
}
// otherwise just keep the normal vector rejection
// if this isn't the first iteration, or if the first iteration is also the last iteration,
// move away from the collision plane slightly, if possible
// this reduces getting stuck in some concave geometry, like the gaps above the railings in some ald'ruhn buildings
// this is different from the normal collision margin, because the normal collision margin is along the movement path,
// but this is along the collision normal
if(!usedSeamLogic && (iterations > 0 || remainingTime < 0.01f))
{
tracer.doTrace(colobj, newPosition, newPosition + planeNormal*(sCollisionMargin*2.0), collisionWorld);
newPosition = (newPosition + tracer.mEndPos)/2.0;
}
// Do not allow sliding up steep slopes if there is gravity.
if (newPosition.z() >= swimlevel && !actor.mFlying && !isWalkableSlope(planeNormal))
newVelocity.z() = std::min(newVelocity.z(), velocity.z());
if (newVelocity * origVelocity <= 0.0f)
break;
if ((newVelocity * origVelocity) <= 0.f)
break; // ^ dot product
numTimesSlid += 1;
lastSlideNormalFallback = lastSlideNormal;
lastSlideNormal = planeNormal;
velocity = newVelocity;
}
}
bool isOnGround = false;
bool isOnSlope = false;
if (!(inertia.z() > 0.f) && !(newPosition.z() < swimlevel))
if (forceGroundTest || (inertia.z() <= 0.f && newPosition.z() >= swimlevel))
{
osg::Vec3f from = newPosition;
osg::Vec3f to = newPosition - (physicActor->getOnGround() ? osg::Vec3f(0,0,sStepSizeDown + 2*sGroundOffset) : osg::Vec3f(0,0,2*sGroundOffset));
auto dropDistance = 2*sGroundOffset + (physicActor->getOnGround() ? sStepSizeDown : 0);
osg::Vec3f to = newPosition - osg::Vec3f(0,0,dropDistance);
tracer.doTrace(colobj, from, to, collisionWorld);
if(tracer.mFraction < 1.0f && !isActor(tracer.mHitObject))
if(tracer.mFraction < 1.0f)
{
const btCollisionObject* standingOn = tracer.mHitObject;
PtrHolder* ptrHolder = static_cast<PtrHolder*>(standingOn->getUserPointer());
if (ptrHolder)
actor.mStandingOn = ptrHolder->getPtr();
if (standingOn->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Water)
physicActor->setWalkingOnWater(true);
if (!actor.mFlying)
newPosition.z() = tracer.mEndPos.z() + sGroundOffset;
isOnGround = true;
isOnSlope = !isWalkableSlope(tracer.mPlaneNormal);
}
else
{
// standing on actors is not allowed (see above).
// in addition to that, apply a sliding effect away from the center of the actor,
// so that we do not stay suspended in air indefinitely.
if (tracer.mFraction < 1.0f && isActor(tracer.mHitObject))
if (!isActor(tracer.mHitObject))
{
if (osg::Vec3f(velocity.x(), velocity.y(), 0).length2() < 100.f*100.f)
isOnGround = true;
isOnSlope = !isWalkableSlope(tracer.mPlaneNormal);
const btCollisionObject* standingOn = tracer.mHitObject;
PtrHolder* ptrHolder = static_cast<PtrHolder*>(standingOn->getUserPointer());
if (ptrHolder)
actor.mStandingOn = ptrHolder->getPtr();
if (standingOn->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Water)
physicActor->setWalkingOnWater(true);
if (!actor.mFlying && !isOnSlope)
{
btVector3 aabbMin, aabbMax;
tracer.mHitObject->getCollisionShape()->getAabb(tracer.mHitObject->getWorldTransform(), aabbMin, aabbMax);
btVector3 center = (aabbMin + aabbMax) / 2.f;
inertia = osg::Vec3f(actor.mPosition.x() - center.x(), actor.mPosition.y() - center.y(), 0);
inertia.normalize();
inertia *= 100;
if (tracer.mFraction*dropDistance > sGroundOffset)
newPosition.z() = tracer.mEndPos.z() + sGroundOffset;
else
{
newPosition.z() = tracer.mEndPos.z();
tracer.doTrace(colobj, newPosition, newPosition + osg::Vec3f(0, 0, 2*sGroundOffset), collisionWorld);
newPosition = (newPosition+tracer.mEndPos)/2.0;
}
}
}
else
{
// Vanilla allows actors to float on top of other actors. Do not push them off.
if (!actor.mFlying && isWalkableSlope(tracer.mPlaneNormal) && tracer.mEndPos.z()+sGroundOffset <= newPosition.z())
newPosition.z() = tracer.mEndPos.z() + sGroundOffset;
isOnGround = false;
isOnGround = false;
}
}
}
@ -294,7 +412,98 @@ namespace MWPhysics
physicActor->setOnGround(isOnGround);
physicActor->setOnSlope(isOnSlope);
newPosition.z() -= halfExtents.z(); // remove what was added at the beginning
actor.mPosition = newPosition;
// remove what was added earlier in compensating for doTrace not taking interior transformation into account
actor.mPosition.z() -= halfExtents.z(); // vanilla-accurate
}
btVector3 addMarginToDelta(btVector3 delta)
{
if(delta.length2() == 0.0)
return delta;
return delta + delta.normalized() * sCollisionMargin;
}
void MovementSolver::unstuck(ActorFrameData& actor, const btCollisionWorld* collisionWorld)
{
const auto& ptr = actor.mActorRaw->getPtr();
if (!ptr.getClass().isMobile(ptr))
return;
auto* physicActor = actor.mActorRaw;
if(!physicActor->getCollisionMode()) // noclipping/tcl
return;
auto* collisionObject = physicActor->getCollisionObject();
auto tempPosition = actor.mPosition;
// use vanilla-accurate collision hull position hack (do same hitbox offset hack as movement solver)
// if vanilla compatibility didn't matter, the "correct" collision hull position would be physicActor->getScaledMeshTranslation()
const auto verticalHalfExtent = osg::Vec3f(0.0, 0.0, physicActor->getHalfExtents().z());
// use a 3d approximation of the movement vector to better judge player intent
const ESM::Position& refpos = ptr.getRefData().getPosition();
auto velocity = (osg::Quat(refpos.rot[0], osg::Vec3f(-1, 0, 0)) * osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1))) * actor.mMovement;
// try to pop outside of the world before doing anything else if we're inside of it
if (!physicActor->getOnGround() || physicActor->getOnSlope())
velocity += physicActor->getInertialForce();
// because of the internal collision box offset hack, and the fact that we're moving the collision box manually,
// we need to replicate part of the collision box's transform process from scratch
osg::Vec3f refPosition = tempPosition + verticalHalfExtent;
osg::Vec3f goodPosition = refPosition;
const btTransform oldTransform = collisionObject->getWorldTransform();
btTransform newTransform = oldTransform;
auto gatherContacts = [&](btVector3 newOffset) -> ContactCollectionCallback
{
goodPosition = refPosition + Misc::Convert::toOsg(addMarginToDelta(newOffset));
newTransform.setOrigin(Misc::Convert::toBullet(goodPosition));
collisionObject->setWorldTransform(newTransform);
ContactCollectionCallback callback{collisionObject, velocity};
ContactTestWrapper::contactTest(const_cast<btCollisionWorld*>(collisionWorld), collisionObject, callback);
return callback;
};
// check whether we're inside the world with our collision box with manually-derived offset
auto contactCallback = gatherContacts({0.0, 0.0, 0.0});
if(contactCallback.mDistance < -sAllowedPenetration)
{
// we are; try moving it out of the world
auto positionDelta = contactCallback.mContactSum;
// limit rejection delta to the largest known individual rejections
if(std::abs(positionDelta.x()) > contactCallback.mMaxX)
positionDelta *= contactCallback.mMaxX / std::abs(positionDelta.x());
if(std::abs(positionDelta.y()) > contactCallback.mMaxY)
positionDelta *= contactCallback.mMaxY / std::abs(positionDelta.y());
if(std::abs(positionDelta.z()) > contactCallback.mMaxZ)
positionDelta *= contactCallback.mMaxZ / std::abs(positionDelta.z());
auto contactCallback2 = gatherContacts(positionDelta);
// successfully moved further out from contact (does not have to be in open space, just less inside of things)
if(contactCallback2.mDistance > contactCallback.mDistance)
tempPosition = goodPosition - verticalHalfExtent;
// try again but only upwards (fixes some bad coc floors)
else
{
// upwards-only offset
auto contactCallback3 = gatherContacts({0.0, 0.0, std::abs(positionDelta.z())});
// success
if(contactCallback3.mDistance > contactCallback.mDistance)
tempPosition = goodPosition - verticalHalfExtent;
else
// try again but fixed distance up
{
auto contactCallback4 = gatherContacts({0.0, 0.0, 10.0});
// success
if(contactCallback4.mDistance > contactCallback.mDistance)
tempPosition = goodPosition - verticalHalfExtent;
}
}
}
collisionObject->setWorldTransform(oldTransform);
actor.mPosition = tempPosition;
}
}

View file

@ -5,6 +5,9 @@
#include <osg/Vec3f>
#include "constants.hpp"
#include "../mwworld/ptr.hpp"
class btCollisionWorld;
namespace MWWorld
@ -14,29 +17,35 @@ namespace MWWorld
namespace MWPhysics
{
/// Vector projection
static inline osg::Vec3f project(const osg::Vec3f& u, const osg::Vec3f &v)
{
return v * (u * v);
}
/// Vector rejection
static inline osg::Vec3f reject(const osg::Vec3f& direction, const osg::Vec3f &planeNormal)
{
return direction - project(direction, planeNormal);
}
template <class Vec3>
static bool isWalkableSlope(const Vec3 &normal)
{
static const float sMaxSlopeCos = std::cos(osg::DegreesToRadians(sMaxSlope));
return (normal.z() > sMaxSlopeCos);
}
class Actor;
struct ActorFrameData;
struct WorldFrameData;
class MovementSolver
{
private:
///Project a vector u on another vector v
static inline osg::Vec3f project(const osg::Vec3f& u, const osg::Vec3f &v)
{
return v * (u * v);
// ^ dot product
}
///Helper for computing the character sliding
static inline osg::Vec3f slide(const osg::Vec3f& direction, const osg::Vec3f &planeNormal)
{
return direction - project(direction, planeNormal);
}
public:
static osg::Vec3f traceDown(const MWWorld::Ptr &ptr, const osg::Vec3f& position, Actor* actor, btCollisionWorld* collisionWorld, float maxHeight);
static void move(ActorFrameData& actor, float time, const btCollisionWorld* collisionWorld, WorldFrameData& worldData);
static void unstuck(ActorFrameData& actor, const btCollisionWorld* collisionWorld);
};
}

View file

@ -13,10 +13,12 @@
#include "../mwworld/player.hpp"
#include "actor.hpp"
#include "contacttestwrapper.h"
#include "movementsolver.hpp"
#include "mtphysics.hpp"
#include "object.hpp"
#include "physicssystem.hpp"
#include "projectile.hpp"
namespace
{
@ -86,12 +88,13 @@ namespace
void updateMechanics(MWPhysics::ActorFrameData& actorData)
{
auto ptr = actorData.mActorRaw->getPtr();
if (actorData.mDidJump)
handleJump(actorData.mPtr);
handleJump(ptr);
MWMechanics::CreatureStats& stats = actorData.mPtr.getClass().getCreatureStats(actorData.mPtr);
MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
if (actorData.mNeedLand)
stats.land(actorData.mPtr == MWMechanics::getPlayer() && (actorData.mFlying || actorData.mSwimming));
stats.land(ptr == MWMechanics::getPlayer() && (actorData.mFlying || actorData.mSwimming));
else if (actorData.mFallHeight < 0)
stats.addToFallHeight(-actorData.mFallHeight);
}
@ -99,15 +102,6 @@ namespace
osg::Vec3f interpolateMovements(MWPhysics::ActorFrameData& actorData, float timeAccum, float physicsDt)
{
const float interpolationFactor = timeAccum / physicsDt;
// account for force change of actor's position in the main thread
const auto correction = actorData.mActorRaw->getWorldPosition() - actorData.mOrigin;
if (correction.length() != 0)
{
actorData.mActorRaw->adjustPosition(correction);
actorData.mPosition = actorData.mActorRaw->getPosition();
}
return actorData.mPosition * interpolationFactor + actorData.mActorRaw->getPreviousPosition() * (1.f - interpolationFactor);
}
@ -174,7 +168,14 @@ namespace MWPhysics
mPreStepBarrier = std::make_unique<Misc::Barrier>(mNumThreads, [&]()
{
if (mDeferAabbUpdate)
updateAabbs();
for (auto& data : mActorsFrameData)
if (data.mActor.lock())
{
std::unique_lock lock(mCollisionWorldMutex);
MovementSolver::unstuck(data, mCollisionWorld.get());
}
});
mPostStepBarrier = std::make_unique<Misc::Barrier>(mNumThreads, [&]()
@ -212,45 +213,43 @@ namespace MWPhysics
thread.join();
}
const PtrPositionList& PhysicsTaskScheduler::moveActors(int numSteps, float timeAccum, std::vector<ActorFrameData>&& actorsData, bool skipSimulation, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats)
const std::vector<MWWorld::Ptr>& PhysicsTaskScheduler::moveActors(int numSteps, float timeAccum, std::vector<ActorFrameData>&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats)
{
// This function run in the main thread.
// While the mSimulationMutex is held, background physics threads can't run.
std::unique_lock lock(mSimulationMutex);
for (auto& data : actorsData)
data.updatePosition();
mMovedActors.clear();
// start by finishing previous background computation
if (mNumThreads != 0)
{
for (auto& data : mActorsFrameData)
{
// Ignore actors that were deleted while the background thread was running
if (!data.mActor.lock())
continue;
const auto actorActive = [&data](const auto& newFrameData) -> bool
{
const auto actor = data.mActor.lock();
return actor && actor->getPtr() == newFrameData.mActorRaw->getPtr();
};
// Only return actors that are still part of the scene
if (std::any_of(actorsData.begin(), actorsData.end(), actorActive))
{
updateMechanics(data);
updateMechanics(data);
if (mAdvanceSimulation)
data.mActorRaw->setStandingOnPtr(data.mStandingOn);
if (mMovementResults.find(data.mPtr) != mMovementResults.end())
data.mActorRaw->setSimulationPosition(mMovementResults[data.mPtr]);
// these variables are accessed directly from the main thread, update them here to prevent accessing "too new" values
if (mAdvanceSimulation)
data.mActorRaw->setStandingOnPtr(data.mStandingOn);
data.mActorRaw->setSimulationPosition(interpolateMovements(data, mTimeAccum, mPhysicsDt));
mMovedActors.emplace_back(data.mActorRaw->getPtr());
}
}
if (mFrameNumber == frameNumber - 1)
{
stats.setAttribute(mFrameNumber, "physicsworker_time_begin", mTimer->delta_s(mFrameStart, mTimeBegin));
stats.setAttribute(mFrameNumber, "physicsworker_time_taken", mTimer->delta_s(mTimeBegin, mTimeEnd));
stats.setAttribute(mFrameNumber, "physicsworker_time_end", mTimer->delta_s(mFrameStart, mTimeEnd));
}
mFrameStart = frameStart;
mTimeBegin = mTimer->tick();
mFrameNumber = frameNumber;
updateStats(frameStart, frameNumber, stats);
}
// init
for (auto& data : actorsData)
data.updatePosition();
mRemainingSteps = numSteps;
mTimeAccum = timeAccum;
mActorsFrameData = std::move(actorsData);
@ -263,52 +262,30 @@ namespace MWPhysics
if (mAdvanceSimulation)
mWorldFrameData = std::make_unique<WorldFrameData>();
// we are asked to skip the simulation (load a savegame for instance)
// just return the actors' reference position without applying the movements
if (skipSimulation)
{
mMovementResults.clear();
for (const auto& m : mActorsFrameData)
{
m.mActorRaw->setStandingOnPtr(nullptr);
m.mActorRaw->resetPosition();
mMovementResults[m.mPtr] = m.mActorRaw->getWorldPosition();
}
return mMovementResults;
}
if (mNumThreads == 0)
{
mMovementResults.clear();
syncComputation();
for (auto& data : mActorsFrameData)
{
if (mAdvanceSimulation)
data.mActorRaw->setStandingOnPtr(data.mStandingOn);
if (mMovementResults.find(data.mPtr) != mMovementResults.end())
data.mActorRaw->setSimulationPosition(mMovementResults[data.mPtr]);
}
return mMovementResults;
return mMovedActors;
}
// Remove actors that were deleted while the background thread was running
for (auto& data : mActorsFrameData)
{
if (!data.mActor.lock())
mMovementResults.erase(data.mPtr);
}
std::swap(mMovementResults, mPreviousMovementResults);
// mMovementResults is shared between all workers instance
// pre-allocate all nodes so that we don't need synchronization
mMovementResults.clear();
for (const auto& m : mActorsFrameData)
mMovementResults[m.mPtr] = m.mPosition;
lock.unlock();
mHasJob.notify_all();
return mPreviousMovementResults;
return mMovedActors;
}
const std::vector<MWWorld::Ptr>& PhysicsTaskScheduler::resetSimulation(const ActorMap& actors)
{
std::unique_lock lock(mSimulationMutex);
mMovedActors.clear();
mActorsFrameData.clear();
for (const auto& [_, actor] : actors)
{
actor->updatePosition();
actor->setSimulationPosition(actor->getWorldPosition()); // updatePosition skip next simulation, now we need to "consume" it
actor->updateCollisionObjectPosition();
mMovedActors.emplace_back(actor->getPtr());
}
return mMovedActors;
}
void PhysicsTaskScheduler::rayTest(const btVector3& rayFromWorld, const btVector3& rayToWorld, btCollisionWorld::RayResultCallback& resultCallback) const
@ -326,7 +303,7 @@ namespace MWPhysics
void PhysicsTaskScheduler::contactTest(btCollisionObject* colObj, btCollisionWorld::ContactResultCallback& resultCallback)
{
std::shared_lock lock(mCollisionWorldMutex);
mCollisionWorld->contactTest(colObj, resultCallback);
ContactTestWrapper::contactTest(mCollisionWorld.get(), colObj, resultCallback);
}
std::optional<btVector3> PhysicsTaskScheduler::getHitPoint(const btTransform& from, btCollisionObject* target)
@ -376,18 +353,18 @@ namespace MWPhysics
mCollisionWorld->removeCollisionObject(collisionObject);
}
void PhysicsTaskScheduler::updateSingleAabb(std::weak_ptr<PtrHolder> ptr)
void PhysicsTaskScheduler::updateSingleAabb(std::weak_ptr<PtrHolder> ptr, bool immediate)
{
if (mDeferAabbUpdate)
{
std::unique_lock lock(mUpdateAabbMutex);
mUpdateAabb.insert(std::move(ptr));
}
else
if (!mDeferAabbUpdate || immediate)
{
std::unique_lock lock(mCollisionWorldMutex);
updatePtrAabb(ptr);
}
else
{
std::unique_lock lock(mUpdateAabbMutex);
mUpdateAabb.insert(std::move(ptr));
}
}
bool PhysicsTaskScheduler::getLineOfSight(const std::weak_ptr<Actor>& actor1, const std::weak_ptr<Actor>& actor2)
@ -453,6 +430,11 @@ namespace MWPhysics
object->commitPositionChange();
mCollisionWorld->updateSingleAabb(object->getCollisionObject());
}
else if (const auto projectile = std::dynamic_pointer_cast<Projectile>(p))
{
projectile->commitPositionChange();
mCollisionWorld->updateSingleAabb(projectile->getCollisionObject());
}
};
}
@ -464,8 +446,7 @@ namespace MWPhysics
if (!mNewFrame)
mHasJob.wait(lock, [&]() { return mQuit || mNewFrame; });
if (mDeferAabbUpdate)
mPreStepBarrier->wait();
mPreStepBarrier->wait();
int job = 0;
while (mRemainingSteps && (job = mNextJob.fetch_add(1, std::memory_order_relaxed)) < mNumJobs)
@ -485,7 +466,6 @@ namespace MWPhysics
{
auto& actorData = mActorsFrameData[job];
handleFall(actorData, mAdvanceSimulation);
mMovementResults[actorData.mPtr] = interpolateMovements(actorData, mTimeAccum, mPhysicsDt);
}
}
@ -503,9 +483,7 @@ namespace MWPhysics
{
if(const auto actor = actorData.mActor.lock())
{
bool positionChanged = actorData.mPosition != actorData.mActorRaw->getPosition();
actorData.mActorRaw->setPosition(actorData.mPosition);
if (positionChanged)
if (actor->setPosition(actorData.mPosition))
{
actor->updateCollisionObjectPosition();
mCollisionWorld->updateSingleAabb(actor->getCollisionObject());
@ -534,7 +512,10 @@ namespace MWPhysics
while (mRemainingSteps--)
{
for (auto& actorData : mActorsFrameData)
{
MovementSolver::unstuck(actorData, mCollisionWorld.get());
MovementSolver::move(actorData, mPhysicsDt, mCollisionWorld.get(), *mWorldFrameData);
}
updateActorsPositions();
}
@ -542,8 +523,24 @@ namespace MWPhysics
for (auto& actorData : mActorsFrameData)
{
handleFall(actorData, mAdvanceSimulation);
mMovementResults[actorData.mPtr] = interpolateMovements(actorData, mTimeAccum, mPhysicsDt);
actorData.mActorRaw->setSimulationPosition(interpolateMovements(actorData, mTimeAccum, mPhysicsDt));
updateMechanics(actorData);
mMovedActors.emplace_back(actorData.mActorRaw->getPtr());
if (mAdvanceSimulation)
actorData.mActorRaw->setStandingOnPtr(actorData.mStandingOn);
}
}
void PhysicsTaskScheduler::updateStats(osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats)
{
if (mFrameNumber == frameNumber - 1)
{
stats.setAttribute(mFrameNumber, "physicsworker_time_begin", mTimer->delta_s(mFrameStart, mTimeBegin));
stats.setAttribute(mFrameNumber, "physicsworker_time_taken", mTimer->delta_s(mTimeBegin, mTimeEnd));
stats.setAttribute(mFrameNumber, "physicsworker_time_end", mTimer->delta_s(mFrameStart, mTimeEnd));
}
mFrameStart = frameStart;
mTimeBegin = mTimer->tick();
mFrameNumber = frameNumber;
}
}

View file

@ -32,7 +32,9 @@ namespace MWPhysics
/// @param timeAccum accumulated time from previous run to interpolate movements
/// @param actorsData per actor data needed to compute new positions
/// @return new position of each actor
const PtrPositionList& moveActors(int numSteps, float timeAccum, std::vector<ActorFrameData>&& actorsData, bool skip, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats);
const std::vector<MWWorld::Ptr>& moveActors(int numSteps, float timeAccum, std::vector<ActorFrameData>&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats);
const std::vector<MWWorld::Ptr>& resetSimulation(const ActorMap& actors);
// Thread safe wrappers
void rayTest(const btVector3& rayFromWorld, const btVector3& rayToWorld, btCollisionWorld::RayResultCallback& resultCallback) const;
@ -44,7 +46,7 @@ namespace MWPhysics
void setCollisionFilterMask(btCollisionObject* collisionObject, int collisionFilterMask);
void addCollisionObject(btCollisionObject* collisionObject, int collisionFilterGroup, int collisionFilterMask);
void removeCollisionObject(btCollisionObject* collisionObject);
void updateSingleAabb(std::weak_ptr<PtrHolder> ptr);
void updateSingleAabb(std::weak_ptr<PtrHolder> ptr, bool immediate=false);
bool getLineOfSight(const std::weak_ptr<Actor>& actor1, const std::weak_ptr<Actor>& actor2);
private:
@ -55,11 +57,11 @@ namespace MWPhysics
void refreshLOSCache();
void updateAabbs();
void updatePtrAabb(const std::weak_ptr<PtrHolder>& ptr);
void updateStats(osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats);
std::unique_ptr<WorldFrameData> mWorldFrameData;
std::vector<ActorFrameData> mActorsFrameData;
PtrPositionList mMovementResults;
PtrPositionList mPreviousMovementResults;
std::vector<MWWorld::Ptr> mMovedActors;
/*
Start of tes3mp change (major)

View file

@ -24,7 +24,7 @@ namespace MWPhysics
mCollisionObject.reset(new btCollisionObject);
mCollisionObject->setCollisionShape(shapeInstance->getCollisionShape());
mCollisionObject->setUserPointer(static_cast<PtrHolder*>(this));
mCollisionObject->setUserPointer(this);
setScale(ptr.getCellRef().getScale());
setRotation(Misc::Convert::toBullet(ptr.getRefData().getBaseNode()->getAttitude()));

View file

@ -46,6 +46,8 @@
#include "collisiontype.hpp"
#include "actor.hpp"
#include "projectile.hpp"
#include "trace.h"
#include "object.hpp"
#include "heightfield.hpp"
@ -64,6 +66,7 @@ namespace MWPhysics
, mResourceSystem(resourceSystem)
, mDebugDrawEnabled(false)
, mTimeAccum(0.0f)
, mProjectileId(0)
, mWaterHeight(0)
, mWaterEnabled(false)
, mParentNode(parentNode)
@ -112,7 +115,7 @@ namespace MWPhysics
mObjects.clear();
mActors.clear();
mProjectiles.clear();
}
void PhysicsSystem::setUnrefQueue(SceneUtil::UnrefQueue *unrefQueue)
@ -272,7 +275,7 @@ namespace MWPhysics
return 0.f;
}
RayCastingResult PhysicsSystem::castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore, std::vector<MWWorld::Ptr> targets, int mask, int group) const
RayCastingResult PhysicsSystem::castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore, std::vector<MWWorld::Ptr> targets, int mask, int group, int projId) const
{
if (from == to)
{
@ -309,7 +312,7 @@ namespace MWPhysics
}
}
ClosestNotMeRayResultCallback resultCallback(me, targetCollisionObjects, btFrom, btTo);
ClosestNotMeRayResultCallback resultCallback(me, targetCollisionObjects, btFrom, btTo, getProjectile(projId));
resultCallback.m_collisionFilterGroup = group;
resultCallback.m_collisionFilterMask = mask;
@ -458,7 +461,6 @@ namespace MWPhysics
ActorMap::iterator found = mActors.find(ptr);
if (found == mActors.end())
return ptr.getRefData().getPosition().asVec3();
found->second->resetPosition();
return MovementSolver::traceDown(ptr, position, found->second.get(), mCollisionWorld.get(), maxHeight);
}
@ -526,6 +528,13 @@ namespace MWPhysics
}
}
void PhysicsSystem::removeProjectile(const int projectileId)
{
ProjectileMap::iterator foundProjectile = mProjectiles.find(projectileId);
if (foundProjectile != mProjectiles.end())
mProjectiles.erase(foundProjectile);
}
void PhysicsSystem::updatePtr(const MWWorld::Ptr &old, const MWWorld::Ptr &updated)
{
ObjectMap::iterator found = mObjects.find(old);
@ -551,6 +560,13 @@ namespace MWPhysics
if (actor->getStandingOnPtr() == old)
actor->setStandingOnPtr(updated);
}
for (auto& [_, projectile] : mProjectiles)
{
if (projectile->getCaster() == old)
projectile->setCaster(updated);
}
}
Actor *PhysicsSystem::getActor(const MWWorld::Ptr &ptr)
@ -577,6 +593,14 @@ namespace MWPhysics
return nullptr;
}
Projectile* PhysicsSystem::getProjectile(int projectileId) const
{
ProjectileMap::const_iterator found = mProjectiles.find(projectileId);
if (found != mProjectiles.end())
return found->second.get();
return nullptr;
}
void PhysicsSystem::updateScale(const MWWorld::Ptr &ptr)
{
ObjectMap::iterator found = mObjects.find(ptr);
@ -596,6 +620,17 @@ namespace MWPhysics
}
}
void PhysicsSystem::updateProjectile(const int projectileId, const osg::Vec3f &position)
{
ProjectileMap::iterator foundProjectile = mProjectiles.find(projectileId);
if (foundProjectile != mProjectiles.end())
{
foundProjectile->second->setPosition(position);
mTaskScheduler->updateSingleAabb(foundProjectile->second);
return;
}
}
void PhysicsSystem::updateRotation(const MWWorld::Ptr &ptr)
{
ObjectMap::iterator found = mObjects.find(ptr);
@ -630,7 +665,7 @@ namespace MWPhysics
if (foundActor != mActors.end())
{
foundActor->second->updatePosition();
mTaskScheduler->updateSingleAabb(foundActor->second);
mTaskScheduler->updateSingleAabb(foundActor->second, true);
return;
}
}
@ -640,7 +675,7 @@ namespace MWPhysics
osg::ref_ptr<const Resource::BulletShape> shape = mShapeManager->getShape(mesh);
// Try to get shape from basic model as fallback for creatures
if (!ptr.getClass().isNpc() && shape && shape->mCollisionBoxHalfExtents.length2() == 0)
if (!ptr.getClass().isNpc() && shape && shape->mCollisionBox.extents.length2() == 0)
{
const std::string fallbackModel = ptr.getClass().getModel(ptr);
if (fallbackModel != mesh)
@ -656,6 +691,15 @@ namespace MWPhysics
mActors.emplace(ptr, std::move(actor));
}
int PhysicsSystem::addProjectile (const MWWorld::Ptr& caster, const osg::Vec3f& position)
{
mProjectileId++;
auto projectile = std::make_shared<Projectile>(mProjectileId, caster, position, mTaskScheduler.get(), this);
mProjectiles.emplace(mProjectileId, std::move(projectile));
return mProjectileId;
}
bool PhysicsSystem::toggleCollisionMode()
{
ActorMap::iterator found = mActors.find(MWMechanics::getPlayer());
@ -690,7 +734,7 @@ namespace MWPhysics
mMovementQueue.clear();
}
const PtrPositionList& PhysicsSystem::applyQueuedMovement(float dt, bool skipSimulation, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats)
const std::vector<MWWorld::Ptr>& PhysicsSystem::applyQueuedMovement(float dt, bool skipSimulation, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats)
{
mTimeAccum += dt;
@ -700,7 +744,10 @@ namespace MWPhysics
mTimeAccum -= numSteps * mPhysicsDt;
return mTaskScheduler->moveActors(numSteps, mTimeAccum, prepareFrameData(numSteps), skipSimulation, frameStart, frameNumber, stats);
if (skipSimulation)
return mTaskScheduler->resetSimulation(mActors);
return mTaskScheduler->moveActors(numSteps, mTimeAccum, prepareFrameData(numSteps), frameStart, frameNumber, stats);
}
std::vector<ActorFrameData> PhysicsSystem::prepareFrameData(int numSteps)
@ -746,7 +793,7 @@ namespace MWPhysics
if (numSteps == 0)
standingOn = physicActor->getStandingOnPtr();
actorsFrameData.emplace_back(std::move(physicActor), character, standingOn, moveToWaterSurface, movement, slowFall, waterlevel);
actorsFrameData.emplace_back(std::move(physicActor), standingOn, moveToWaterSurface, movement, slowFall, waterlevel);
}
mMovementQueue.clear();
return actorsFrameData;
@ -888,33 +935,34 @@ namespace MWPhysics
mDebugDrawer->addCollision(position, normal);
}
ActorFrameData::ActorFrameData(const std::shared_ptr<Actor>& actor, const MWWorld::Ptr character, const MWWorld::Ptr standingOn,
ActorFrameData::ActorFrameData(const std::shared_ptr<Actor>& actor, const MWWorld::Ptr standingOn,
bool moveToWaterSurface, osg::Vec3f movement, float slowFall, float waterlevel)
: mActor(actor), mActorRaw(actor.get()), mStandingOn(standingOn),
mDidJump(false), mNeedLand(false), mMoveToWaterSurface(moveToWaterSurface),
mWaterlevel(waterlevel), mSlowFall(slowFall), mOldHeight(0), mFallHeight(0), mMovement(movement), mPosition(), mRefpos()
{
const MWBase::World *world = MWBase::Environment::get().getWorld();
mPtr = actor->getPtr();
mFlying = world->isFlying(character);
mSwimming = world->isSwimming(character);
mWantJump = mPtr.getClass().getMovementSettings(mPtr).mPosition[2] != 0;
mIsDead = mPtr.getClass().getCreatureStats(mPtr).isDead();
const auto ptr = actor->getPtr();
mFlying = world->isFlying(ptr);
mSwimming = world->isSwimming(ptr);
mWantJump = ptr.getClass().getMovementSettings(ptr).mPosition[2] != 0;
auto& stats = ptr.getClass().getCreatureStats(ptr);
const bool godmode = ptr == world->getPlayerConstPtr() && world->getGodModeState();
mFloatToSurface = stats.isDead() || (!godmode && stats.isParalyzed());
mWasOnGround = actor->getOnGround();
}
void ActorFrameData::updatePosition()
{
mActorRaw->updatePosition();
mOrigin = mActorRaw->getSimulationPosition();
mActorRaw->updateWorldPosition();
mPosition = mActorRaw->getPosition();
if (mMoveToWaterSurface)
{
mPosition.z() = mWaterlevel;
mActorRaw->setPosition(mPosition);
MWBase::Environment::get().getWorld()->moveObject(mActorRaw->getPtr(), mPosition.x(), mPosition.y(), mPosition.z());
}
mOldHeight = mPosition.z();
mRefpos = mPtr.getRefData().getPosition();
mRefpos = mActorRaw->getPtr().getRefData().getPosition();
}
WorldFrameData::WorldFrameData()

View file

@ -50,12 +50,13 @@ class btVector3;
namespace MWPhysics
{
using PtrPositionList = std::map<MWWorld::Ptr, osg::Vec3f>;
class HeightField;
class Object;
class Actor;
class PhysicsTaskScheduler;
class Projectile;
using ActorMap = std::map<MWWorld::ConstPtr, std::shared_ptr<Actor>>;
struct ContactPoint
{
@ -77,18 +78,17 @@ namespace MWPhysics
struct ActorFrameData
{
ActorFrameData(const std::shared_ptr<Actor>& actor, const MWWorld::Ptr character, const MWWorld::Ptr standingOn, bool moveToWaterSurface, osg::Vec3f movement, float slowFall, float waterlevel);
ActorFrameData(const std::shared_ptr<Actor>& actor, const MWWorld::Ptr standingOn, bool moveToWaterSurface, osg::Vec3f movement, float slowFall, float waterlevel);
void updatePosition();
std::weak_ptr<Actor> mActor;
Actor* mActorRaw;
MWWorld::Ptr mPtr;
MWWorld::Ptr mStandingOn;
bool mFlying;
bool mSwimming;
bool mWasOnGround;
bool mWantJump;
bool mDidJump;
bool mIsDead;
bool mFloatToSurface;
bool mNeedLand;
bool mMoveToWaterSurface;
float mWaterlevel;
@ -96,7 +96,6 @@ namespace MWPhysics
float mOldHeight;
float mFallHeight;
osg::Vec3f mMovement;
osg::Vec3f mOrigin;
osg::Vec3f mPosition;
ESM::Position mRefpos;
};
@ -125,6 +124,10 @@ namespace MWPhysics
void addObject (const MWWorld::Ptr& ptr, const std::string& mesh, int collisionType = CollisionType_World);
void addActor (const MWWorld::Ptr& ptr, const std::string& mesh);
int addProjectile(const MWWorld::Ptr& caster, const osg::Vec3f& position);
void updateProjectile(const int projectileId, const osg::Vec3f &position);
void removeProjectile(const int projectileId);
void updatePtr (const MWWorld::Ptr& old, const MWWorld::Ptr& updated);
Actor* getActor(const MWWorld::Ptr& ptr);
@ -132,6 +135,8 @@ namespace MWPhysics
const Object* getObject(const MWWorld::ConstPtr& ptr) const;
Projectile* getProjectile(int projectileId) const;
// Object or Actor
void remove (const MWWorld::Ptr& ptr);
@ -139,7 +144,6 @@ namespace MWPhysics
void updateRotation (const MWWorld::Ptr& ptr);
void updatePosition (const MWWorld::Ptr& ptr);
void addHeightField (const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject);
void removeHeightField (int x, int y);
@ -170,7 +174,7 @@ namespace MWPhysics
/// @param me Optional, a Ptr to ignore in the list of results. targets are actors to filter for, ignoring all other actors.
RayCastingResult castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore = MWWorld::ConstPtr(),
std::vector<MWWorld::Ptr> targets = std::vector<MWWorld::Ptr>(),
int mask = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door, int group=0xff) const override;
int mask = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door, int group=0xff, int projId=-1) const override;
RayCastingResult castSphere(const osg::Vec3f& from, const osg::Vec3f& to, float radius) const override;
@ -202,7 +206,7 @@ namespace MWPhysics
void queueObjectMovement(const MWWorld::Ptr &ptr, const osg::Vec3f &velocity);
/// Apply all queued movements, then clear the list.
const PtrPositionList& applyQueuedMovement(float dt, bool skipSimulation, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats);
const std::vector<MWWorld::Ptr>& applyQueuedMovement(float dt, bool skipSimulation, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats);
/// Clear the queued movements list without applying.
void clearQueuedMovement();
@ -275,9 +279,11 @@ namespace MWPhysics
std::set<Object*> mAnimatedObjects; // stores pointers to elements in mObjects
using ActorMap = std::map<MWWorld::ConstPtr, std::shared_ptr<Actor>>;
ActorMap mActors;
using ProjectileMap = std::map<int, std::shared_ptr<Projectile>>;
ProjectileMap mProjectiles;
using HeightFieldMap = std::map<std::pair<int, int>, HeightField *>;
HeightFieldMap mHeightFields;
@ -288,6 +294,8 @@ namespace MWPhysics
float mTimeAccum;
unsigned int mProjectileId;
float mWaterHeight;
bool mWaterEnabled;

View file

@ -0,0 +1,131 @@
#include <memory>
#include <BulletCollision/CollisionShapes/btSphereShape.h>
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
#include <LinearMath/btVector3.h>
#include <components/debug/debuglog.hpp>
#include <components/misc/convert.hpp>
#include <components/resource/bulletshape.hpp>
#include <components/sceneutil/positionattitudetransform.hpp>
#include "../mwworld/class.hpp"
#include "collisiontype.hpp"
#include "mtphysics.hpp"
#include "projectile.hpp"
namespace MWPhysics
{
Projectile::Projectile(int projectileId, const MWWorld::Ptr& caster, const osg::Vec3f& position, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem)
: mActive(true)
, mCaster(caster)
, mPhysics(physicssystem)
, mTaskScheduler(scheduler)
, mProjectileId(projectileId)
{
mShape.reset(new btSphereShape(1.f));
mConvexShape = static_cast<btConvexShape*>(mShape.get());
mCollisionObject.reset(new btCollisionObject);
mCollisionObject->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT);
mCollisionObject->setActivationState(DISABLE_DEACTIVATION);
mCollisionObject->setCollisionShape(mShape.get());
mCollisionObject->setUserPointer(this);
setPosition(position);
const int collisionMask = CollisionType_World | CollisionType_HeightMap |
CollisionType_Actor | CollisionType_Door | CollisionType_Water | CollisionType_Projectile;
mTaskScheduler->addCollisionObject(mCollisionObject.get(), CollisionType_Projectile, collisionMask);
commitPositionChange();
}
Projectile::~Projectile()
{
if (mCollisionObject)
{
if (!mActive)
mPhysics->reportCollision(mHitPosition, mHitNormal);
mTaskScheduler->removeCollisionObject(mCollisionObject.get());
}
}
void Projectile::commitPositionChange()
{
std::scoped_lock lock(mMutex);
if (mTransformUpdatePending)
{
mCollisionObject->setWorldTransform(mLocalTransform);
mTransformUpdatePending = false;
}
}
void Projectile::setPosition(const osg::Vec3f &position)
{
std::scoped_lock lock(mMutex);
mLocalTransform.setOrigin(Misc::Convert::toBullet(position));
mTransformUpdatePending = true;
}
void Projectile::hit(MWWorld::Ptr target, btVector3 pos, btVector3 normal)
{
if (!mActive.load(std::memory_order_acquire))
return;
std::scoped_lock lock(mMutex);
mHitTarget = target;
mHitPosition = pos;
mHitNormal = normal;
mActive.store(false, std::memory_order_release);
}
void Projectile::activate()
{
assert(!mActive);
mActive.store(true, std::memory_order_release);
}
MWWorld::Ptr Projectile::getCaster() const
{
std::scoped_lock lock(mMutex);
return mCaster;
}
void Projectile::setCaster(MWWorld::Ptr caster)
{
std::scoped_lock lock(mMutex);
mCaster = caster;
}
void Projectile::setValidTargets(const std::vector<MWWorld::Ptr>& targets)
{
std::scoped_lock lock(mMutex);
mValidTargets = targets;
}
bool Projectile::isValidTarget(const MWWorld::Ptr& target) const
{
std::scoped_lock lock(mMutex);
if (mCaster == target)
return false;
if (!mValidTargets.empty())
{
bool validTarget = false;
for (const auto& targetActor : mValidTargets)
{
if (targetActor == target)
{
validTarget = true;
break;
}
}
return validTarget;
}
return true;
}
}

View file

@ -0,0 +1,112 @@
#ifndef OPENMW_MWPHYSICS_PROJECTILE_H
#define OPENMW_MWPHYSICS_PROJECTILE_H
#include <atomic>
#include <memory>
#include <mutex>
#include "components/misc/convert.hpp"
#include "ptrholder.hpp"
class btCollisionObject;
class btCollisionShape;
class btConvexShape;
class btVector3;
namespace osg
{
class Vec3f;
}
namespace Resource
{
class BulletShape;
}
namespace MWPhysics
{
class PhysicsTaskScheduler;
class PhysicsSystem;
class Projectile final : public PtrHolder
{
public:
Projectile(const int projectileId, const MWWorld::Ptr& caster, const osg::Vec3f& position, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem);
~Projectile() override;
btConvexShape* getConvexShape() const { return mConvexShape; }
void commitPositionChange();
void setPosition(const osg::Vec3f& position);
btCollisionObject* getCollisionObject() const
{
return mCollisionObject.get();
}
int getProjectileId() const
{
return mProjectileId;
}
bool isActive() const
{
return mActive.load(std::memory_order_acquire);
}
MWWorld::Ptr getTarget() const
{
assert(!mActive);
return mHitTarget;
}
MWWorld::Ptr getCaster() const;
void setCaster(MWWorld::Ptr caster);
osg::Vec3f getHitPos() const
{
assert(!mActive);
return Misc::Convert::toOsg(mHitPosition);
}
void hit(MWWorld::Ptr target, btVector3 pos, btVector3 normal);
void activate();
void setValidTargets(const std::vector<MWWorld::Ptr>& targets);
bool isValidTarget(const MWWorld::Ptr& target) const;
private:
std::unique_ptr<btCollisionShape> mShape;
btConvexShape* mConvexShape;
std::unique_ptr<btCollisionObject> mCollisionObject;
btTransform mLocalTransform;
bool mTransformUpdatePending;
std::atomic<bool> mActive;
MWWorld::Ptr mCaster;
MWWorld::Ptr mHitTarget;
btVector3 mHitPosition;
btVector3 mHitNormal;
std::vector<MWWorld::Ptr> mValidTargets;
mutable std::mutex mMutex;
osg::Vec3f mPosition;
PhysicsSystem *mPhysics;
PhysicsTaskScheduler *mTaskScheduler;
Projectile(const Projectile&);
Projectile& operator=(const Projectile&);
int mProjectileId;
};
}
#endif

View file

@ -1,6 +1,8 @@
#ifndef OPENMW_MWPHYSICS_PTRHOLDER_H
#define OPENMW_MWPHYSICS_PTRHOLDER_H
#include <mutex>
#include "../mwworld/ptr.hpp"
namespace MWPhysics
@ -12,21 +14,27 @@ namespace MWPhysics
void updatePtr(const MWWorld::Ptr& updated)
{
std::scoped_lock lock(mMutex);
mPtr = updated;
}
MWWorld::Ptr getPtr()
{
std::scoped_lock lock(mMutex);
return mPtr;
}
MWWorld::ConstPtr getPtr() const
{
std::scoped_lock lock(mMutex);
return mPtr;
}
protected:
MWWorld::Ptr mPtr;
private:
mutable std::mutex mMutex;
};
}

View file

@ -16,7 +16,7 @@ namespace MWPhysics
osg::Vec3f mHitNormal;
MWWorld::Ptr mHitObject;
};
class RayCastingInterface
{
public:
@ -29,7 +29,7 @@ namespace MWPhysics
/// @param me Optional, a Ptr to ignore in the list of results. targets are actors to filter for, ignoring all other actors.
virtual RayCastingResult castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore = MWWorld::ConstPtr(),
std::vector<MWWorld::Ptr> targets = std::vector<MWWorld::Ptr>(),
int mask = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door, int group=0xff) const = 0;
int mask = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door, int group=0xff, int projId=-1) const = 0;
virtual RayCastingResult castSphere(const osg::Vec3f& from, const osg::Vec3f& to, float radius) const = 0;

View file

@ -7,6 +7,7 @@
#include "collisiontype.hpp"
#include "constants.hpp"
#include "movementsolver.hpp"
namespace MWPhysics
{
@ -24,125 +25,155 @@ namespace MWPhysics
Stepper::Stepper(const btCollisionWorld *colWorld, const btCollisionObject *colObj)
: mColWorld(colWorld)
, mColObj(colObj)
, mHaveMoved(true)
{
}
bool Stepper::step(osg::Vec3f &position, const osg::Vec3f &toMove, float &remainingTime)
bool Stepper::step(osg::Vec3f &position, osg::Vec3f &velocity, float &remainingTime, const bool & onGround, bool firstIteration)
{
/*
* Slide up an incline or set of stairs. Should be called only after a
* collision detection otherwise unnecessary tracing will be performed.
*
* NOTE: with a small change this method can be used to step over an obstacle
* of height sStepSize.
*
* If successful return 'true' and update 'position' to the new possible
* location and adjust 'remainingTime'.
*
* If not successful return 'false'. May fail for these reasons:
* - can't move directly up from current position
* - having moved up by between epsilon() and sStepSize, can't move forward
* - having moved forward by between epsilon() and toMove,
* = moved down between 0 and just under sStepSize but slope was too steep, or
* = moved the full sStepSize down (FIXME: this could be a bug)
*
* Starting position. Obstacle or stairs with height upto sStepSize in front.
*
* +--+ +--+ |XX
* | | -------> toMove | | +--+XX
* | | | | |XXXXX
* | | +--+ | | +--+XXXXX
* | | |XX| | | |XXXXXXXX
* +--+ +--+ +--+ +--------
* ==============================================
*/
if(velocity.x() == 0.0 && velocity.y() == 0.0)
return false;
/*
* Try moving up sStepSize using stepper.
* FIXME: does not work in case there is no front obstacle but there is one above
*
* +--+ +--+
* | | | |
* | | | | |XX
* | | | | +--+XX
* | | | | |XXXXX
* +--+ +--+ +--+ +--+XXXXX
* |XX| |XXXXXXXX
* +--+ +--------
* ==============================================
*/
if (mHaveMoved)
// Stairstepping algorithms work by moving up to avoid the step, moving forwards, then moving back down onto the ground.
// This algorithm has a couple of minor problems, but they don't cause problems for sane geometry, and just prevent stepping on insane geometry.
mUpStepper.doTrace(mColObj, position, position+osg::Vec3f(0.0f,0.0f,sStepSizeUp), mColWorld);
float upDistance = 0;
if(!mUpStepper.mHitObject)
upDistance = sStepSizeUp;
else if(mUpStepper.mFraction*sStepSizeUp > sCollisionMargin)
upDistance = mUpStepper.mFraction*sStepSizeUp - sCollisionMargin;
else
{
mHaveMoved = false;
mUpStepper.doTrace(mColObj, position, position+osg::Vec3f(0.0f,0.0f,sStepSizeUp), mColWorld);
if (mUpStepper.mFraction < std::numeric_limits<float>::epsilon())
return false; // didn't even move the smallest representable amount
// (TODO: shouldn't this be larger? Why bother with such a small amount?)
return false;
}
/*
* Try moving from the elevated position using tracer.
*
* +--+ +--+
* | | |YY| FIXME: collision with object YY
* | | +--+
* | |
* <------------------->| |
* +--+ +--+
* |XX| the moved amount is toMove*tracer.mFraction
* +--+
* ==============================================
*/
osg::Vec3f tracerPos = mUpStepper.mEndPos;
mTracer.doTrace(mColObj, tracerPos, tracerPos + toMove, mColWorld);
if (mTracer.mFraction < std::numeric_limits<float>::epsilon())
return false; // didn't even move the smallest representable amount
auto toMove = velocity * remainingTime;
/*
* Try moving back down sStepSizeDown using stepper.
* NOTE: if there is an obstacle below (e.g. stairs), we'll be "stepping up".
* Below diagram is the case where we "stepped over" an obstacle in front.
*
* +--+
* |YY|
* +--+ +--+
* | |
* | |
* +--+ | |
* |XX| | |
* +--+ +--+
* ==============================================
*/
mDownStepper.doTrace(mColObj, mTracer.mEndPos, mTracer.mEndPos-osg::Vec3f(0.0f,0.0f,sStepSizeDown), mColWorld);
if (!canStepDown(mDownStepper))
osg::Vec3f tracerPos = position + osg::Vec3f(0.0f, 0.0f, upDistance);
osg::Vec3f tracerDest;
auto normalMove = toMove;
auto moveDistance = normalMove.normalize();
// attempt 1: normal movement
// attempt 2: fixed distance movement, only happens on the first movement solver iteration/bounce each frame to avoid a glitch
// attempt 3: further, less tall fixed distance movement, same as above
// If you're making a full conversion you should purge the logic for attempts 2 and 3. Attempts 2 and 3 just try to work around problems with vanilla Morrowind assets.
int attempt = 0;
float downStepSize;
while(attempt < 3)
{
// Try again with increased step length
if (mTracer.mFraction < 1.0f || toMove.length2() > sMinStep*sMinStep)
return false;
attempt++;
osg::Vec3f direction = toMove;
direction.normalize();
mTracer.doTrace(mColObj, tracerPos, tracerPos + direction*sMinStep, mColWorld);
if (mTracer.mFraction < 0.001f)
if(attempt == 1)
tracerDest = tracerPos + toMove;
else if (!firstIteration || !sDoExtraStairHacks) // first attempt failed and not on first movement solver iteration, can't retry -- or we have extra hacks disabled
{
return false;
}
else if(attempt == 2)
{
moveDistance = sMinStep;
tracerDest = tracerPos + normalMove*sMinStep;
}
else if(attempt == 3)
{
if(upDistance > sStepSizeUp)
{
upDistance = sStepSizeUp;
tracerPos = position + osg::Vec3f(0.0f, 0.0f, upDistance);
}
moveDistance = sMinStep2;
tracerDest = tracerPos + normalMove*sMinStep2;
}
mTracer.doTrace(mColObj, tracerPos, tracerDest, mColWorld);
if(mTracer.mHitObject)
{
// map against what we hit, minus the safety margin
moveDistance *= mTracer.mFraction;
if(moveDistance <= sCollisionMargin) // didn't move enough to accomplish anything
{
return false;
}
moveDistance -= sCollisionMargin;
tracerDest = tracerPos + normalMove*moveDistance;
// safely eject from what we hit by the safety margin
auto tempDest = tracerDest + mTracer.mPlaneNormal*sCollisionMargin*2;
ActorTracer tempTracer;
tempTracer.doTrace(mColObj, tracerDest, tempDest, mColWorld);
if(tempTracer.mFraction > 0.5f) // distance to any object is greater than sCollisionMargin (we checked sCollisionMargin*2 distance)
{
auto effectiveFraction = tempTracer.mFraction*2.0f - 1.0f;
tracerDest += mTracer.mPlaneNormal*sCollisionMargin*effectiveFraction;
}
}
if(attempt > 2) // do not allow stepping down below original height for attempt 3
downStepSize = upDistance;
else
downStepSize = moveDistance + upDistance + sStepSizeDown;
mDownStepper.doTrace(mColObj, tracerDest, tracerDest + osg::Vec3f(0.0f, 0.0f, -downStepSize), mColWorld);
// can't step down onto air, non-walkable-slopes, or actors
// NOTE: using a capsule causes isWalkableSlope (used in canStepDown) to fail on certain geometry that were intended to be valid at the bottoms of stairs
// (like the bottoms of the staircases in aldruhn's guild of mages)
// The old code worked around this by trying to do mTracer again with a fixed distance of sMinStep (10.0) but it caused all sorts of other problems.
// Switched back to cylinders to avoid that and similer problems.
if(canStepDown(mDownStepper))
{
break;
}
else
{
// do not try attempt 3 if we just tried attempt 2 and the horizontal distance was rather large
// (forces actor to get snug against the defective ledge for attempt 3 to be tried)
if(attempt == 2 && moveDistance > upDistance-(mDownStepper.mFraction*downStepSize))
{
return false;
}
// do next attempt if first iteration of movement solver and not out of attempts
if(firstIteration && attempt < 3)
{
continue;
}
mDownStepper.doTrace(mColObj, mTracer.mEndPos, mTracer.mEndPos-osg::Vec3f(0.0f,0.0f,sStepSizeDown), mColWorld);
if (!canStepDown(mDownStepper))
return false;
}
}
if (mDownStepper.mFraction < 1.0f)
// note: can't downstep onto actors so no need to pick safety margin
float downDistance = 0;
if(mDownStepper.mFraction*downStepSize > sCollisionMargin)
downDistance = mDownStepper.mFraction*downStepSize - sCollisionMargin;
if(downDistance-sCollisionMargin-sGroundOffset > upDistance && !onGround)
return false;
auto newpos = tracerDest + osg::Vec3f(0.0f, 0.0f, -downDistance);
if((position-newpos).length2() < sCollisionMargin*sCollisionMargin)
return false;
if(mTracer.mHitObject)
{
// only step down onto semi-horizontal surfaces. don't step down onto the side of a house or a wall.
// TODO: stepper.mPlaneNormal does not appear to be reliable - needs more testing
// NOTE: caller's variables 'position' & 'remainingTime' are modified here
position = mDownStepper.mEndPos;
remainingTime *= (1.0f-mTracer.mFraction); // remaining time is proportional to remaining distance
mHaveMoved = true;
return true;
auto planeNormal = mTracer.mPlaneNormal;
if (onGround && !isWalkableSlope(planeNormal) && planeNormal.z() != 0)
{
planeNormal.z() = 0;
planeNormal.normalize();
}
velocity = reject(velocity, planeNormal);
}
return false;
velocity = reject(velocity, mDownStepper.mPlaneNormal);
position = newpos;
remainingTime *= (1.0f-mTracer.mFraction); // remaining time is proportional to remaining distance
return true;
}
}

View file

@ -20,12 +20,11 @@ namespace MWPhysics
const btCollisionObject *mColObj;
ActorTracer mTracer, mUpStepper, mDownStepper;
bool mHaveMoved;
public:
Stepper(const btCollisionWorld *colWorld, const btCollisionObject *colObj);
bool step(osg::Vec3f &position, const osg::Vec3f &toMove, float &remainingTime);
bool step(osg::Vec3f &position, osg::Vec3f &velocity, float &remainingTime, const bool & onGround, bool firstIteration);
};
}

View file

@ -24,7 +24,7 @@ void ActorTracer::doTrace(const btCollisionObject *actor, const osg::Vec3f& star
to.setOrigin(btend);
const btVector3 motion = btstart-btend;
ClosestNotMeConvexResultCallback newTraceCallback(actor, motion, btScalar(0.0));
ClosestNotMeConvexResultCallback newTraceCallback(actor, motion, btScalar(0.0), world);
// Inherit the actor's collision group and mask
newTraceCallback.m_collisionFilterGroup = actor->getBroadphaseHandle()->m_collisionFilterGroup;
newTraceCallback.m_collisionFilterMask = actor->getBroadphaseHandle()->m_collisionFilterMask;
@ -62,7 +62,7 @@ void ActorTracer::findGround(const Actor* actor, const osg::Vec3f& start, const
btTransform to(trans.getBasis(), btend);
const btVector3 motion = btstart-btend;
ClosestNotMeConvexResultCallback newTraceCallback(actor->getCollisionObject(), motion, btScalar(0.0));
ClosestNotMeConvexResultCallback newTraceCallback(actor->getCollisionObject(), motion, btScalar(0.0), world);
// Inherit the actor's collision group and mask
newTraceCallback.m_collisionFilterGroup = actor->getCollisionObject()->getBroadphaseHandle()->m_collisionFilterGroup;
newTraceCallback.m_collisionFilterMask = actor->getCollisionObject()->getBroadphaseHandle()->m_collisionFilterMask;

View file

@ -782,8 +782,6 @@ namespace MWRender
NodeMap::const_iterator found = nodeMap.find("bip01");
if (found == nodeMap.end())
found = nodeMap.find("root bone");
if (found == nodeMap.end())
found = nodeMap.find("root");
if (found != nodeMap.end())
mAccumRoot = found->second;

View file

@ -7,6 +7,9 @@
#include <components/debug/debuglog.hpp>
#include <components/misc/convert.hpp>
#include <osg/PolygonMode>
#include <osg/ShapeDrawable>
#include <osg/StateSet>
#include "bulletdebugdraw.hpp"
#include "vismask.hpp"
@ -41,6 +44,14 @@ void DebugDrawer::createGeometry()
mGeometry->addPrimitiveSet(mDrawArrays);
mParentNode->addChild(mGeometry);
auto* stateSet = new osg::StateSet;
stateSet->setAttributeAndModes(new osg::PolygonMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::LINE), osg::StateAttribute::ON);
mShapesRoot = new osg::Group;
mShapesRoot->setStateSet(stateSet);
mShapesRoot->setDataVariance(osg::Object::DYNAMIC);
mShapesRoot->setNodeMask(Mask_Debug);
mParentNode->addChild(mShapesRoot);
}
}
@ -49,6 +60,7 @@ void DebugDrawer::destroyGeometry()
if (mGeometry)
{
mParentNode->removeChild(mGeometry);
mParentNode->removeChild(mShapesRoot);
mGeometry = nullptr;
mVertices = nullptr;
mDrawArrays = nullptr;
@ -66,6 +78,7 @@ void DebugDrawer::step()
{
mVertices->clear();
mColors->clear();
mShapesRoot->removeChildren(0, mShapesRoot->getNumChildren());
mWorld->debugDrawWorld();
showCollisions();
mDrawArrays->setCount(mVertices->size());
@ -106,12 +119,11 @@ void DebugDrawer::showCollisions()
mCollisionViews.end());
}
void DebugDrawer::drawContactPoint(const btVector3 &PointOnB, const btVector3 &normalOnB, btScalar distance, int lifeTime, const btVector3 &color)
void DebugDrawer::drawSphere(btScalar radius, const btTransform& transform, const btVector3& color)
{
mVertices->push_back(Misc::Convert::toOsg(PointOnB));
mVertices->push_back(Misc::Convert::toOsg(PointOnB) + (Misc::Convert::toOsg(normalOnB) * distance * 20));
mColors->push_back({1,1,1,1});
mColors->push_back({1,1,1,1});
auto* geom = new osg::ShapeDrawable(new osg::Sphere(Misc::Convert::toOsg(transform.getOrigin()), radius));
geom->setColor(osg::Vec4(1, 1, 1, 1));
mShapesRoot->addChild(geom);
}
void DebugDrawer::reportErrorWarning(const char *warningString)

View file

@ -32,6 +32,7 @@ private:
CollisionView(btVector3 orig, btVector3 normal) : mOrig(orig), mEnd(orig + normal * 20), mCreated(std::chrono::steady_clock::now()) {};
};
std::vector<CollisionView> mCollisionViews;
osg::ref_ptr<osg::Group> mShapesRoot;
protected:
osg::ref_ptr<osg::Group> mParentNode;
@ -59,7 +60,8 @@ public:
void showCollisions();
void drawContactPoint(const btVector3& PointOnB,const btVector3& normalOnB,btScalar distance,int lifeTime,const btVector3& color) override;
void drawContactPoint(const btVector3& PointOnB,const btVector3& normalOnB,btScalar distance,int lifeTime,const btVector3& color) override {};
void drawSphere(btScalar radius, const btTransform& transform, const btVector3& color) override;
void reportErrorWarning(const char* warningString) override;

View file

@ -356,9 +356,6 @@ namespace MWRender
else
mViewModeToggleQueued = false;
if (mTrackingPtr.getClass().isActor())
mTrackingPtr.getClass().getCreatureStats(mTrackingPtr).setSideMovementAngle(0);
mFirstPersonView = !mFirstPersonView;
updateStandingPreviewMode();
instantTransition();

View file

@ -1,5 +1,5 @@
#ifndef OPENMW_COMPONENTS_ESMTERRAIN_LANDMANAGER_H
#define OPENMW_COMPONENTS_ESMTERRAIN_LANDMANAGER_H
#ifndef OPENMW_MWRENDER_LANDMANAGER_H
#define OPENMW_MWRENDER_LANDMANAGER_H
#include <osg/Object>

View file

@ -1,5 +1,5 @@
#ifndef OPENMW_COMPONENTS_ESMPAGING_CHUNKMANAGER_H
#define OPENMW_COMPONENTS_ESMPAGING_CHUNKMANAGER_H
#ifndef OPENMW_MWRENDER_OBJECTPAGING_H
#define OPENMW_MWRENDER_OBJECTPAGING_H
#include <components/terrain/quadtreeworld.hpp>
#include <components/resource/resourcemanager.hpp>

View file

@ -305,7 +305,7 @@ namespace MWRender
mTerrain->setWorkQueue(mWorkQueue.get());
// water goes after terrain for correct waterculling order
mWater.reset(new Water(mRootNode, sceneRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), resourcePath));
mWater.reset(new Water(sceneRoot->getParent(0), sceneRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), resourcePath));
mCamera.reset(new Camera(mViewer->getCamera()));
if (Settings::Manager::getBool("view over shoulder", "Camera"))

View file

@ -235,13 +235,14 @@ public:
Refraction()
{
unsigned int rttSize = Settings::Manager::getInt("rtt size", "Water");
setRenderOrder(osg::Camera::PRE_RENDER);
setRenderOrder(osg::Camera::PRE_RENDER, 1);
setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT);
setReferenceFrame(osg::Camera::RELATIVE_RF);
setSmallFeatureCullingPixelSize(Settings::Manager::getInt("small feature culling pixel size", "Water"));
setName("RefractionCamera");
setCullCallback(new InheritViewPointCallback);
setComputeNearFarMode(osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR);
setCullMask(Mask_Effect|Mask_Scene|Mask_Object|Mask_Static|Mask_Terrain|Mask_Actor|Mask_ParticleSystem|Mask_Sky|Mask_Sun|Mask_Player|Mask_Lighting);
setNodeMask(Mask_RenderToTexture);
@ -284,7 +285,8 @@ public:
attach(osg::Camera::DEPTH_BUFFER, mRefractionDepthTexture);
SceneUtil::ShadowManager::disableShadowsForStateSet(getOrCreateStateSet());
if (Settings::Manager::getFloat("refraction scale", "Water") != 1) // TODO: to be removed with issue #5709
SceneUtil::ShadowManager::disableShadowsForStateSet(getOrCreateStateSet());
}
void setScene(osg::Node* scene)

View file

@ -49,11 +49,7 @@ namespace MWScript
std::vector<MWWorld::Ptr> actors;
MWBase::Environment::get().getWorld()->getActorsStandingOn (ptr, actors);
for (auto& actor : actors)
{
osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3());
actorPos += diff;
MWBase::Environment::get().getWorld()->moveObject(actor, actorPos.x(), actorPos.y(), actorPos.z());
}
MWBase::Environment::get().getWorld()->moveObjectBy(actor, diff);
}
template<class R>
@ -903,14 +899,12 @@ namespace MWScript
return;
osg::Vec3f diff = ptr.getRefData().getBaseNode()->getAttitude() * posChange;
osg::Vec3f worldPos(ptr.getRefData().getPosition().asVec3());
worldPos += diff;
// We should move actors, standing on moving object, too.
// This approach can be used to create elevators.
moveStandingActors(ptr, diff);
dynamic_cast<MWScript::InterpreterContext&>(runtime.getContext()).updatePtr(ptr,
MWBase::Environment::get().getWorld()->moveObject(ptr, worldPos.x(), worldPos.y(), worldPos.z()));
MWBase::Environment::get().getWorld()->moveObjectBy(ptr, diff));
}
};
@ -931,15 +925,14 @@ namespace MWScript
Interpreter::Type_Float movement = (runtime[0].mFloat*MWBase::Environment::get().getFrameDuration());
runtime.pop();
const float *objPos = ptr.getRefData().getPosition().pos;
osg::Vec3f diff;
if (axis == "x")
diff.x() += movement;
diff.x() = movement;
else if (axis == "y")
diff.y() += movement;
diff.y() = movement;
else if (axis == "z")
diff.z() += movement;
diff.z() = movement;
else
return;
@ -947,7 +940,7 @@ namespace MWScript
// This approach can be used to create elevators.
moveStandingActors(ptr, diff);
dynamic_cast<MWScript::InterpreterContext&>(runtime.getContext()).updatePtr(ptr,
MWBase::Environment::get().getWorld()->moveObject(ptr, objPos[0]+diff.x(), objPos[1]+diff.y(), objPos[2]+diff.z()));
MWBase::Environment::get().getWorld()->moveObjectBy(ptr, diff));
}
};

View file

@ -39,7 +39,7 @@ namespace
void addScripts(MWWorld::ContainerStore& store, MWWorld::CellStore* cell)
{
auto& scripts = MWBase::Environment::get().getWorld()->getLocalScripts();
for(const MWWorld::Ptr& ptr : store)
for(const auto&& ptr : store)
{
const std::string& script = ptr.getClass().getScript(ptr);
if(!script.empty())
@ -56,13 +56,10 @@ namespace
{
float sum = 0;
for (typename MWWorld::CellRefList<T>::List::const_iterator iter (
cellRefList.mList.begin());
iter!=cellRefList.mList.end();
++iter)
for (const auto& iter : cellRefList.mList)
{
if (iter->mData.getCount()>0)
sum += iter->mData.getCount()*iter->mBase->mData.mWeight;
if (iter.mData.getCount()>0)
sum += iter.mData.getCount()*iter.mBase->mData.mWeight;
}
return sum;
@ -75,12 +72,11 @@ namespace
store->resolve();
std::string id2 = Misc::StringUtils::lowerCase (id);
for (typename MWWorld::CellRefList<T>::List::iterator iter (list.mList.begin());
iter!=list.mList.end(); ++iter)
for (auto& iter : list.mList)
{
if (Misc::StringUtils::ciEqual(iter->mBase->mId, id2) && iter->mData.getCount())
if (Misc::StringUtils::ciEqual(iter.mBase->mId, id2) && iter.mData.getCount())
{
MWWorld::Ptr ptr (&*iter, nullptr);
MWWorld::Ptr ptr (&iter, nullptr);
ptr.setContainerStore (store);
return ptr;
}
@ -94,7 +90,7 @@ MWWorld::ResolutionListener::~ResolutionListener()
{
if(!mStore.mModified && mStore.mResolved && !mStore.mPtr.isEmpty())
{
for(const MWWorld::Ptr& ptr : mStore)
for(const auto&& ptr : mStore)
ptr.getRefData().setCount(0);
mStore.fillNonRandom(mStore.mPtr.get<ESM::Container>()->mBase->mInventory, "", mStore.mSeed);
addScripts(mStore, mStore.mPtr.mCell);
@ -140,15 +136,14 @@ template<typename T>
void MWWorld::ContainerStore::storeStates (const CellRefList<T>& collection,
ESM::InventoryState& inventory, int& index, bool equipable) const
{
for (typename CellRefList<T>::List::const_iterator iter (collection.mList.begin());
iter!=collection.mList.end(); ++iter)
for (const auto& iter : collection.mList)
{
if (iter->mData.getCount() == 0)
if (iter.mData.getCount() == 0)
continue;
ESM::ObjectState state;
storeState (*iter, state);
storeState (iter, state);
if (equipable)
storeEquipmentState(*iter, index, inventory);
storeEquipmentState(iter, index, inventory);
inventory.mItems.push_back (state);
++index;
}
@ -201,7 +196,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::end()
int MWWorld::ContainerStore::count(const std::string &id) const
{
int total=0;
for (const auto& iter : *this)
for (const auto&& iter : *this)
if (Misc::StringUtils::ciEqual(iter.getCellRef().getRefId(), id))
total += iter.getRefData().getCount();
return total;
@ -494,14 +489,14 @@ void MWWorld::ContainerStore::rechargeItems(float duration)
updateRechargingItems();
mRechargingItemsUpToDate = true;
}
for (TRechargingItems::iterator it = mRechargingItems.begin(); it != mRechargingItems.end(); ++it)
for (auto& it : mRechargingItems)
{
if (!MWMechanics::rechargeItem(*it->first, it->second, duration))
if (!MWMechanics::rechargeItem(*it.first, it.second, duration))
continue;
// attempt to restack when fully recharged
if (it->first->getCellRef().getEnchantmentCharge() == it->second)
it->first = restack(*it->first);
if (it.first->getCellRef().getEnchantmentCharge() == it.second)
it.first = restack(*it.first);
}
}
@ -545,9 +540,9 @@ int MWWorld::ContainerStore::remove(const std::string& itemId, int count, const
bool MWWorld::ContainerStore::hasVisibleItems() const
{
for (auto iter(begin()); iter != end(); ++iter)
for (const auto&& iter : *this)
{
if (iter->getClass().showsInInventory(*iter))
if (iter.getClass().showsInInventory(iter))
return true;
}
@ -692,8 +687,8 @@ void MWWorld::ContainerStore::addInitialItemImp(const MWWorld::Ptr& ptr, const s
void MWWorld::ContainerStore::clear()
{
for (ContainerStoreIterator iter (begin()); iter!=end(); ++iter)
iter->getRefData().setCount (0);
for (auto&& iter : *this)
iter.getRefData().setCount (0);
flagAsModified();
mModified = true;
@ -728,7 +723,7 @@ void MWWorld::ContainerStore::resolve()
{
if(!mResolved && !mPtr.isEmpty())
{
for(const MWWorld::Ptr& ptr : *this)
for(const auto&& ptr : *this)
ptr.getRefData().setCount(0);
Misc::Rng::Seed seed{mSeed};
fill(mPtr.get<ESM::Container>()->mBase->mInventory, "", seed);
@ -749,7 +744,7 @@ MWWorld::ResolutionHandle MWWorld::ContainerStore::resolveTemporarily()
}
if(!mResolved && !mPtr.isEmpty())
{
for(const MWWorld::Ptr& ptr : *this)
for(const auto&& ptr : *this)
ptr.getRefData().setCount(0);
Misc::Rng::Seed seed{mSeed};
fill(mPtr.get<ESM::Container>()->mBase->mInventory, "", seed);
@ -832,10 +827,10 @@ MWWorld::Ptr MWWorld::ContainerStore::findReplacement(const std::string& id)
{
MWWorld::Ptr item;
int itemHealth = 1;
for (MWWorld::ContainerStoreIterator iter = begin(); iter != end(); ++iter)
for (auto&& iter : *this)
{
int iterHealth = iter->getClass().hasItemHealth(*iter) ? iter->getClass().getItemHealth(*iter) : 1;
if (Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), id))
int iterHealth = iter.getClass().hasItemHealth(iter) ? iter.getClass().getItemHealth(iter) : 1;
if (Misc::StringUtils::ciEqual(iter.getCellRef().getRefId(), id))
{
// Prefer the stack with the lowest remaining uses
// Try to get item with zero durability only if there are no other items found
@ -843,7 +838,7 @@ MWWorld::Ptr MWWorld::ContainerStore::findReplacement(const std::string& id)
(iterHealth > 0 && iterHealth < itemHealth) ||
(itemHealth <= 0 && iterHealth > 0))
{
item = *iter;
item = iter;
itemHealth = iterHealth;
}
}
@ -972,11 +967,8 @@ void MWWorld::ContainerStore::readState (const ESM::InventoryState& inventory)
mResolved = true;
int index = 0;
for (std::vector<ESM::ObjectState>::const_iterator
iter (inventory.mItems.begin()); iter!=inventory.mItems.end(); ++iter)
for (const ESM::ObjectState& state : inventory.mItems)
{
const ESM::ObjectState& state = *iter;
int type = MWBase::Environment::get().getWorld()->getStore().find(state.mRef.mRefID);
int thisIndex = index++;

View file

@ -601,6 +601,14 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor)
}
}
MWWorld::ContainerStoreIterator MWWorld::InventoryStore::getPreferredShield(const MWWorld::Ptr& actor)
{
TSlots slots;
initSlots (slots);
autoEquipArmor(actor, slots);
return slots[Slot_CarriedLeft];
}
const MWMechanics::MagicEffects& MWWorld::InventoryStore::getMagicEffects() const
{
return mMagicEffects;

View file

@ -153,6 +153,8 @@ namespace MWWorld
ContainerStoreIterator getSlot (int slot);
ConstContainerStoreIterator getSlot(int slot) const;
ContainerStoreIterator getPreferredShield(const MWWorld::Ptr& actor);
void unequipAll(const MWWorld::Ptr& actor);
///< Unequip all currently equipped items.

View file

@ -2,6 +2,7 @@
#include <iomanip>
#include <memory>
#include <osg/PositionAttitudeTransform>
#include <components/debug/debuglog.hpp>
@ -43,6 +44,7 @@
#include "../mwsound/sound.hpp"
#include "../mwphysics/physicssystem.hpp"
#include "../mwphysics/projectile.hpp"
namespace
{
@ -284,7 +286,7 @@ namespace MWWorld
else
state.mActorId = -1;
std::string texture = "";
std::string texture;
state.mEffects = getMagicBoltData(state.mIdMagic, state.mSoundIds, state.mSpeed, texture, state.mSourceName, state.mSpellId);
@ -302,6 +304,7 @@ namespace MWWorld
MWWorld::Ptr ptr = ref.getPtr();
osg::Vec4 lightDiffuseColor = getMagicBoltLightDiffuseColor(state.mEffects);
createModel(state, ptr.getClass().getModel(ptr), pos, orient, true, true, lightDiffuseColor, texture);
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
@ -312,7 +315,9 @@ namespace MWWorld
if (sound)
state.mSounds.push_back(sound);
}
state.mProjectileId = mPhysics->addProjectile(caster, pos);
state.mToDelete = false;
mMagicBolts.push_back(state);
}
@ -325,7 +330,6 @@ namespace MWWorld
state.mIdArrow = projectile.getCellRef().getRefId();
state.mCasterHandle = actor;
state.mAttackStrength = attackStrength;
int type = projectile.get<ESM::Weapon>()->mBase->mData.mType;
state.mThrown = MWMechanics::getWeaponType(type)->mWeaponClass == ESM::WeaponType::Thrown;
@ -336,6 +340,8 @@ namespace MWWorld
if (!ptr.getClass().getEnchantment(ptr).empty())
SceneUtil::addEnchantedGlow(state.mNode, mResourceSystem, ptr.getClass().getEnchantmentColor(ptr));
state.mProjectileId = mPhysics->addProjectile(actor, pos);
state.mToDelete = false;
mProjectiles.push_back(state);
}
@ -360,72 +366,68 @@ namespace MWWorld
return (state.mNode->getPosition() - playerPos).length2() >= farawayThreshold*farawayThreshold;
};
for (std::vector<ProjectileState>::iterator it = mProjectiles.begin(); it != mProjectiles.end();)
for (auto& projectileState : mProjectiles)
{
if (isCleanable(*it))
{
cleanupProjectile(*it);
it = mProjectiles.erase(it);
}
else
++it;
if (isCleanable(projectileState))
cleanupProjectile(projectileState);
}
for (std::vector<MagicBoltState>::iterator it = mMagicBolts.begin(); it != mMagicBolts.end();)
for (auto& magicBoltState : mMagicBolts)
{
if (isCleanable(*it))
{
cleanupMagicBolt(*it);
it = mMagicBolts.erase(it);
}
else
++it;
if (isCleanable(magicBoltState))
cleanupMagicBolt(magicBoltState);
}
}
}
void ProjectileManager::moveMagicBolts(float duration)
{
for (std::vector<MagicBoltState>::iterator it = mMagicBolts.begin(); it != mMagicBolts.end();)
for (auto& magicBoltState : mMagicBolts)
{
if (magicBoltState.mToDelete)
continue;
auto* projectile = mPhysics->getProjectile(magicBoltState.mProjectileId);
if (!projectile->isActive())
continue;
// If the actor caster is gone, the magic bolt needs to be removed from the scene during the next frame.
MWWorld::Ptr caster = it->getCaster();
MWWorld::Ptr caster = magicBoltState.getCaster();
if (!caster.isEmpty() && caster.getClass().isActor())
{
if (caster.getRefData().getCount() <= 0 || caster.getClass().getCreatureStats(caster).isDead())
{
cleanupMagicBolt(*it);
it = mMagicBolts.erase(it);
cleanupMagicBolt(magicBoltState);
continue;
}
}
osg::Quat orient = it->mNode->getAttitude();
osg::Quat orient = magicBoltState.mNode->getAttitude();
static float fTargetSpellMaxSpeed = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>()
.find("fTargetSpellMaxSpeed")->mValue.getFloat();
float speed = fTargetSpellMaxSpeed * it->mSpeed;
float speed = fTargetSpellMaxSpeed * magicBoltState.mSpeed;
osg::Vec3f direction = orient * osg::Vec3f(0,1,0);
direction.normalize();
osg::Vec3f pos(it->mNode->getPosition());
osg::Vec3f pos(magicBoltState.mNode->getPosition());
osg::Vec3f newPos = pos + direction * duration * speed;
for (size_t soundIter = 0; soundIter != it->mSounds.size(); soundIter++)
{
it->mSounds.at(soundIter)->setPosition(newPos);
}
for (const auto& sound : magicBoltState.mSounds)
sound->setPosition(newPos);
it->mNode->setPosition(newPos);
magicBoltState.mNode->setPosition(newPos);
update(*it, duration);
mPhysics->updateProjectile(magicBoltState.mProjectileId, newPos);
update(magicBoltState, duration);
// For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result.
std::vector<MWWorld::Ptr> targetActors;
if (!caster.isEmpty() && caster.getClass().isActor() && caster != MWMechanics::getPlayer())
caster.getClass().getCreatureStats(caster).getAiSequence().getCombatTargets(targetActors);
projectile->setValidTargets(targetActors);
// Check for impact
// TODO: use a proper btRigidBody / btGhostObject?
MWPhysics::RayCastingResult result = mPhysics->castRay(pos, newPos, caster, targetActors, 0xff, MWPhysics::CollisionType_Projectile);
const auto result = mPhysics->castRay(pos, newPos, caster, targetActors, 0xff, MWPhysics::CollisionType_Projectile, magicBoltState.mProjectileId);
bool hit = false;
if (result.mHit)
@ -433,16 +435,16 @@ namespace MWWorld
hit = true;
if (result.mHitObject.isEmpty())
{
// terrain
// terrain or projectile
}
else
{
MWMechanics::CastSpell cast(caster, result.mHitObject);
cast.mHitPosition = pos;
cast.mId = it->mSpellId;
cast.mSourceName = it->mSourceName;
cast.mId = magicBoltState.mSpellId;
cast.mSourceName = magicBoltState.mSourceName;
cast.mStack = false;
cast.inflict(result.mHitObject, caster, it->mEffects, ESM::RT_Target, false, true);
cast.inflict(result.mHitObject, caster, magicBoltState.mEffects, ESM::RT_Target, false, true);
mPhysics->reportCollision(Misc::Convert::toBullet(result.mHitPos), Misc::Convert::toBullet(result.mHitNormal));
}
}
@ -453,99 +455,172 @@ namespace MWWorld
if (hit)
{
MWBase::Environment::get().getWorld()->explodeSpell(pos, it->mEffects, caster, result.mHitObject,
ESM::RT_Target, it->mSpellId, it->mSourceName);
MWBase::Environment::get().getWorld()->explodeSpell(pos, magicBoltState.mEffects, caster, result.mHitObject,
ESM::RT_Target, magicBoltState.mSpellId, magicBoltState.mSourceName);
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
for (size_t soundIter = 0; soundIter != it->mSounds.size(); soundIter++)
sndMgr->stopSound(it->mSounds.at(soundIter));
mParent->removeChild(it->mNode);
it = mMagicBolts.erase(it);
continue;
cleanupMagicBolt(magicBoltState);
}
else
++it;
}
}
void ProjectileManager::moveProjectiles(float duration)
{
for (std::vector<ProjectileState>::iterator it = mProjectiles.begin(); it != mProjectiles.end();)
for (auto& projectileState : mProjectiles)
{
if (projectileState.mToDelete)
continue;
auto* projectile = mPhysics->getProjectile(projectileState.mProjectileId);
if (!projectile->isActive())
continue;
// gravity constant - must be way lower than the gravity affecting actors, since we're not
// simulating aerodynamics at all
it->mVelocity -= osg::Vec3f(0, 0, Constants::GravityConst * Constants::UnitsPerMeter * 0.1f) * duration;
projectileState.mVelocity -= osg::Vec3f(0, 0, Constants::GravityConst * Constants::UnitsPerMeter * 0.1f) * duration;
osg::Vec3f pos(it->mNode->getPosition());
osg::Vec3f newPos = pos + it->mVelocity * duration;
osg::Vec3f pos(projectileState.mNode->getPosition());
osg::Vec3f newPos = pos + projectileState.mVelocity * duration;
// rotation does not work well for throwing projectiles - their roll angle will depend on shooting direction.
if (!it->mThrown)
if (!projectileState.mThrown)
{
osg::Quat orient;
orient.makeRotate(osg::Vec3f(0,1,0), it->mVelocity);
it->mNode->setAttitude(orient);
orient.makeRotate(osg::Vec3f(0,1,0), projectileState.mVelocity);
projectileState.mNode->setAttitude(orient);
}
it->mNode->setPosition(newPos);
projectileState.mNode->setPosition(newPos);
update(*it, duration);
mPhysics->updateProjectile(projectileState.mProjectileId, newPos);
MWWorld::Ptr caster = it->getCaster();
update(projectileState, duration);
MWWorld::Ptr caster = projectileState.getCaster();
// For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result.
std::vector<MWWorld::Ptr> targetActors;
if (!caster.isEmpty() && caster.getClass().isActor() && caster != MWMechanics::getPlayer())
caster.getClass().getCreatureStats(caster).getAiSequence().getCombatTargets(targetActors);
projectile->setValidTargets(targetActors);
// Check for impact
// TODO: use a proper btRigidBody / btGhostObject?
MWPhysics::RayCastingResult result = mPhysics->castRay(pos, newPos, caster, targetActors, 0xff, MWPhysics::CollisionType_Projectile);
const auto result = mPhysics->castRay(pos, newPos, caster, targetActors, 0xff, MWPhysics::CollisionType_Projectile, projectileState.mProjectileId);
bool underwater = MWBase::Environment::get().getWorld()->isUnderwater(MWMechanics::getPlayer().getCell(), newPos);
if (result.mHit || underwater)
{
MWWorld::ManualRef projectileRef(MWBase::Environment::get().getWorld()->getStore(), it->mIdArrow);
// Try to get a Ptr to the bow that was used. It might no longer exist.
MWWorld::ManualRef projectileRef(MWBase::Environment::get().getWorld()->getStore(), projectileState.mIdArrow);
MWWorld::Ptr bow = projectileRef.getPtr();
if (!caster.isEmpty() && it->mIdArrow != it->mBowId)
if (!caster.isEmpty() && projectileState.mIdArrow != projectileState.mBowId)
{
MWWorld::InventoryStore& inv = caster.getClass().getInventoryStore(caster);
MWWorld::ContainerStoreIterator invIt = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
if (invIt != inv.end() && Misc::StringUtils::ciEqual(invIt->getCellRef().getRefId(), it->mBowId))
if (invIt != inv.end() && Misc::StringUtils::ciEqual(invIt->getCellRef().getRefId(), projectileState.mBowId))
bow = *invIt;
}
if (caster.isEmpty())
caster = result.mHitObject;
MWMechanics::projectileHit(caster, result.mHitObject, bow, projectileRef.getPtr(), result.mHit ? result.mHitPos : newPos, it->mAttackStrength);
MWMechanics::projectileHit(caster, result.mHitObject, bow, projectileRef.getPtr(), result.mHit ? result.mHitPos : newPos, projectileState.mAttackStrength);
mPhysics->reportCollision(Misc::Convert::toBullet(result.mHitPos), Misc::Convert::toBullet(result.mHitNormal));
if (underwater)
mRendering->emitWaterRipple(newPos);
mParent->removeChild(it->mNode);
it = mProjectiles.erase(it);
cleanupProjectile(projectileState);
}
}
}
void ProjectileManager::processHits()
{
for (auto& projectileState : mProjectiles)
{
if (projectileState.mToDelete)
continue;
auto* projectile = mPhysics->getProjectile(projectileState.mProjectileId);
if (projectile->isActive())
continue;
const auto target = projectile->getTarget();
const auto pos = projectile->getHitPos();
MWWorld::Ptr caster = projectileState.getCaster();
assert(target != caster);
if (!projectile->isValidTarget(target))
{
projectile->activate();
continue;
}
++it;
if (caster.isEmpty())
caster = target;
// Try to get a Ptr to the bow that was used. It might no longer exist.
MWWorld::ManualRef projectileRef(MWBase::Environment::get().getWorld()->getStore(), projectileState.mIdArrow);
MWWorld::Ptr bow = projectileRef.getPtr();
if (!caster.isEmpty() && projectileState.mIdArrow != projectileState.mBowId)
{
MWWorld::InventoryStore& inv = caster.getClass().getInventoryStore(caster);
MWWorld::ContainerStoreIterator invIt = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
if (invIt != inv.end() && Misc::StringUtils::ciEqual(invIt->getCellRef().getRefId(), projectileState.mBowId))
bow = *invIt;
}
projectileState.mHitPosition = pos;
cleanupProjectile(projectileState);
MWMechanics::projectileHit(caster, target, bow, projectileRef.getPtr(), pos, projectileState.mAttackStrength);
}
for (auto& magicBoltState : mMagicBolts)
{
if (magicBoltState.mToDelete)
continue;
auto* projectile = mPhysics->getProjectile(magicBoltState.mProjectileId);
if (projectile->isActive())
continue;
const auto target = projectile->getTarget();
const auto pos = projectile->getHitPos();
MWWorld::Ptr caster = magicBoltState.getCaster();
assert(target != caster);
if (!projectile->isValidTarget(target))
{
projectile->activate();
continue;
}
magicBoltState.mHitPosition = pos;
cleanupMagicBolt(magicBoltState);
MWMechanics::CastSpell cast(caster, target);
cast.mHitPosition = pos;
cast.mId = magicBoltState.mSpellId;
cast.mSourceName = magicBoltState.mSourceName;
cast.mStack = false;
cast.inflict(target, caster, magicBoltState.mEffects, ESM::RT_Target, false, true);
MWBase::Environment::get().getWorld()->explodeSpell(pos, magicBoltState.mEffects, caster, target, ESM::RT_Target, magicBoltState.mSpellId, magicBoltState.mSourceName);
}
mProjectiles.erase(std::remove_if(mProjectiles.begin(), mProjectiles.end(), [](const State& state) { return state.mToDelete; }),
mProjectiles.end());
mMagicBolts.erase(std::remove_if(mMagicBolts.begin(), mMagicBolts.end(), [](const State& state) { return state.mToDelete; }),
mMagicBolts.end());
}
void ProjectileManager::cleanupProjectile(ProjectileManager::ProjectileState& state)
{
mParent->removeChild(state.mNode);
mPhysics->removeProjectile(state.mProjectileId);
state.mToDelete = true;
}
void ProjectileManager::cleanupMagicBolt(ProjectileManager::MagicBoltState& state)
{
mParent->removeChild(state.mNode);
mPhysics->removeProjectile(state.mProjectileId);
state.mToDelete = true;
for (size_t soundIter = 0; soundIter != state.mSounds.size(); soundIter++)
{
MWBase::Environment::get().getSoundManager()->stopSound(state.mSounds.at(soundIter));
@ -554,15 +629,12 @@ namespace MWWorld
void ProjectileManager::clear()
{
for (std::vector<ProjectileState>::iterator it = mProjectiles.begin(); it != mProjectiles.end(); ++it)
{
cleanupProjectile(*it);
}
for (auto& mProjectile : mProjectiles)
cleanupProjectile(mProjectile);
mProjectiles.clear();
for (std::vector<MagicBoltState>::iterator it = mMagicBolts.begin(); it != mMagicBolts.end(); ++it)
{
cleanupMagicBolt(*it);
}
for (auto& mMagicBolt : mMagicBolts)
cleanupMagicBolt(mMagicBolt);
mMagicBolts.clear();
}
@ -619,6 +691,7 @@ namespace MWWorld
state.mVelocity = esm.mVelocity;
state.mIdArrow = esm.mId;
state.mAttackStrength = esm.mAttackStrength;
state.mToDelete = false;
std::string model;
try
@ -626,9 +699,10 @@ namespace MWWorld
MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), esm.mId);
MWWorld::Ptr ptr = ref.getPtr();
model = ptr.getClass().getModel(ptr);
int weaponType = ptr.get<ESM::Weapon>()->mBase->mData.mType;
state.mThrown = MWMechanics::getWeaponType(weaponType)->mWeaponClass == ESM::WeaponType::Thrown;
state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition));
}
catch(...)
{
@ -640,7 +714,7 @@ namespace MWWorld
mProjectiles.push_back(state);
return true;
}
else if (type == ESM::REC_MPRJ)
if (type == ESM::REC_MPRJ)
{
ESM::MagicBoltState esm;
esm.load(reader);
@ -649,7 +723,8 @@ namespace MWWorld
state.mIdMagic.push_back(esm.mId);
state.mSpellId = esm.mSpellId;
state.mActorId = esm.mActorId;
std::string texture = "";
state.mToDelete = false;
std::string texture;
try
{
@ -672,6 +747,7 @@ namespace MWWorld
MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), state.mIdMagic.at(0));
MWWorld::Ptr ptr = ref.getPtr();
model = ptr.getClass().getModel(ptr);
state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition));
}
catch(...)
{

View file

@ -56,6 +56,8 @@ namespace MWWorld
void update(float dt);
void processHits();
/// Removes all current projectiles. Should be called when switching to a new worldspace.
void clear();
@ -76,6 +78,9 @@ namespace MWWorld
std::shared_ptr<MWRender::EffectAnimationTime> mEffectAnimationTime;
int mActorId;
int mProjectileId;
osg::Vec3f mHitPosition;
// TODO: this will break when the game is saved and reloaded, since there is currently
// no way to write identifiers for non-actors to a savegame.
@ -88,6 +93,8 @@ namespace MWWorld
// MW-id of an arrow projectile
std::string mIdArrow;
bool mToDelete;
};
struct MagicBoltState : public State

View file

@ -1417,6 +1417,18 @@ namespace MWWorld
return moveObjectImp(ptr, x, y, z, true, moveToActive);
}
MWWorld::Ptr World::moveObjectBy(const Ptr& ptr, osg::Vec3f vec)
{
auto* actor = mPhysics->getActor(ptr);
if (actor)
{
actor->adjustPosition(vec);
return ptr;
}
osg::Vec3f newpos = ptr.getRefData().getPosition().asVec3() + vec;
return moveObject(ptr, newpos.x(), newpos.y(), newpos.z());
}
void World::scaleObject (const Ptr& ptr, float scale)
{
if (mPhysics->getActor(ptr))
@ -1498,22 +1510,15 @@ namespace MWWorld
return;
}
float terrainHeight = -std::numeric_limits<float>::max();
if (ptr.getCell()->isExterior())
terrainHeight = getTerrainHeightAt(pos);
if (pos.z() < terrainHeight)
pos.z() = terrainHeight;
pos.z() += 20; // place slightly above. will snap down to ground with code below
const float terrainHeight = ptr.getCell()->isExterior() ? getTerrainHeightAt(pos) : -std::numeric_limits<float>::max();
pos.z() = std::max(pos.z(), terrainHeight) + 20; // place slightly above terrain. will snap down to ground with code below
// We still should trace down dead persistent actors - they do not use the "swimdeath" animation.
bool swims = ptr.getClass().isActor() && isSwimming(ptr) && !(ptr.getClass().isPersistent(ptr) && ptr.getClass().getCreatureStats(ptr).isDeathAnimationFinished());
if (force || !ptr.getClass().isActor() || (!isFlying(ptr) && !swims && isActorCollisionEnabled(ptr)))
{
osg::Vec3f traced = mPhysics->traceDown(ptr, pos, Constants::CellSizeInUnits);
if (traced.z() < pos.z())
pos.z() = traced.z();
pos.z() = std::min(pos.z(), traced.z());
}
moveObject(ptr, ptr.getCell(), pos.x(), pos.y(), pos.z());
@ -1615,17 +1620,11 @@ namespace MWWorld
ipos.pos[1] = spawnPoint.y();
ipos.pos[2] = spawnPoint.z();
if (!referenceObject.getClass().isActor())
{
ipos.rot[0] = referenceObject.getRefData().getPosition().rot[0];
ipos.rot[1] = referenceObject.getRefData().getPosition().rot[1];
}
else
if (referenceObject.getClass().isActor())
{
ipos.rot[0] = 0;
ipos.rot[1] = 0;
}
ipos.rot[2] = referenceObject.getRefData().getPosition().rot[2];
MWWorld::Ptr placed = copyObjectToCell(ptr, referenceCell, ipos, ptr.getRefData().getCount(), false);
adjustPosition(placed, true); // snap to ground
@ -1697,19 +1696,30 @@ namespace MWWorld
mProjectileManager->update(duration);
const auto results = mPhysics->applyQueuedMovement(duration, mDiscardMovements, frameStart, frameNumber, stats);
const auto& results = mPhysics->applyQueuedMovement(duration, mDiscardMovements, frameStart, frameNumber, stats);
mProjectileManager->processHits();
mDiscardMovements = false;
for(const auto& [actor, position]: results)
for(const auto& actor : results)
{
// Handle player last, in case a cell transition occurs
if(actor != getPlayerPtr())
{
auto* physactor = mPhysics->getActor(actor);
assert(physactor);
const auto position = physactor->getSimulationPosition();
moveObjectImp(actor, position.x(), position.y(), position.z(), false);
}
}
const auto player = results.find(getPlayerPtr());
const auto player = std::find(results.begin(), results.end(), getPlayerPtr());
if (player != results.end())
moveObjectImp(player->first, player->second.x(), player->second.y(), player->second.z(), false);
{
auto* physactor = mPhysics->getActor(*player);
assert(physactor);
const auto position = physactor->getSimulationPosition();
moveObjectImp(*player, position.x(), position.y(), position.z(), false);
}
}
void World::updateNavigator()
@ -2548,8 +2558,12 @@ namespace MWWorld
if (stats.isDead())
return false;
const bool isPlayer = ptr == getPlayerConstPtr();
if (!(isPlayer && mGodMode) && stats.isParalyzed())
return false;
if (ptr.getClass().canFly(ptr))
return !stats.isParalyzed();
return true;
if(stats.getMagicEffects().get(ESM::MagicEffect::Levitate).getMagnitude() > 0
&& isLevitationEnabled())
@ -3309,7 +3323,7 @@ namespace MWWorld
mRendering->rebuildPtr(getPlayerPtr());
}
bool World::getGodModeState()
bool World::getGodModeState() const
{
return mGodMode;
}

View file

@ -466,6 +466,9 @@ namespace MWWorld
MWWorld::Ptr moveObject (const Ptr& ptr, CellStore* newCell, float x, float y, float z, bool movePhysics=true) override;
///< @return an updated Ptr
MWWorld::Ptr moveObjectBy(const Ptr& ptr, osg::Vec3f vec) override;
///< @return an updated Ptr
void scaleObject (const Ptr& ptr, float scale) override;
/// World rotates object, uses radians
@ -776,7 +779,7 @@ namespace MWWorld
/// Returns true if levitation spell effect is allowed.
bool isLevitationEnabled() const override;
bool getGodModeState() override;
bool getGodModeState() const override;
bool toggleGodMode() override;

View file

@ -1,5 +1,5 @@
find_package(GTest REQUIRED)
find_package(GMock REQUIRED)
find_package(GTest 1.10 REQUIRED)
find_package(GMock 1.10 REQUIRED)
if (GTEST_FOUND AND GMOCK_FOUND)
include_directories(SYSTEM ${GTEST_INCLUDE_DIRS})

View file

@ -15,7 +15,7 @@ namespace
struct DetourNavigatorRecastMeshObjectTest : Test
{
btBoxShape mBoxShape {btVector3(1, 2, 3)};
btCompoundShape mCompoundShape {btVector3(1, 2, 3)};
btCompoundShape mCompoundShape {true};
btTransform mTransform {btQuaternion(btVector3(1, 2, 3), 1), btVector3(1, 2, 3)};
DetourNavigatorRecastMeshObjectTest()

View file

@ -3,6 +3,7 @@
#include <boost/filesystem/fstream.hpp>
#include <components/files/configurationmanager.hpp>
#include <components/files/escape.hpp>
#include <components/esm/esmreader.hpp>
#include <components/esm/esmwriter.hpp>
#include <components/loadinglistener/loadinglistener.hpp>
@ -58,10 +59,10 @@ struct ContentFileTest : public ::testing::Test
boost::program_options::options_description desc("Allowed options");
desc.add_options()
("data", boost::program_options::value<Files::PathContainer>()->default_value(Files::PathContainer(), "data")->multitoken()->composing())
("content", boost::program_options::value<std::vector<std::string> >()->default_value(std::vector<std::string>(), "")
->multitoken(), "content file(s): esm/esp, or omwgame/omwaddon")
("data-local", boost::program_options::value<std::string>()->default_value(""));
("data", boost::program_options::value<Files::EscapePathContainer>()->default_value(Files::EscapePathContainer(), "data")->multitoken()->composing())
("content", boost::program_options::value<Files::EscapeStringVector>()->default_value(Files::EscapeStringVector(), "")
->multitoken()->composing(), "content file(s): esm/esp, or omwgame/omwaddon")
("data-local", boost::program_options::value<Files::EscapePath>()->default_value(Files::EscapePath(), ""));
boost::program_options::notify(variables);
@ -69,12 +70,12 @@ struct ContentFileTest : public ::testing::Test
Files::PathContainer dataDirs, dataLocal;
if (!variables["data"].empty()) {
dataDirs = Files::PathContainer(variables["data"].as<Files::PathContainer>());
dataDirs = Files::EscapePath::toPathContainer(variables["data"].as<Files::EscapePathContainer>());
}
std::string local = variables["data-local"].as<std::string>();
Files::PathContainer::value_type local(variables["data-local"].as<Files::EscapePath>().mPath);
if (!local.empty()) {
dataLocal.push_back(Files::PathContainer::value_type(local));
dataLocal.push_back(local);
}
mConfigurationManager.processPaths (dataDirs);
@ -85,7 +86,7 @@ struct ContentFileTest : public ::testing::Test
Files::Collections collections (dataDirs, true);
std::vector<std::string> contentFiles = variables["content"].as<std::vector<std::string> >();
std::vector<std::string> contentFiles = variables["content"].as<Files::EscapeStringVector>().toStdStringVector();
for (auto & contentFile : contentFiles)
mContentFiles.push_back(collections.getPath(contentFile));
}

View file

@ -162,8 +162,8 @@ namespace Resource
{
return compareObjects(lhs.mCollisionShape, rhs.mCollisionShape)
&& compareObjects(lhs.mAvoidCollisionShape, rhs.mAvoidCollisionShape)
&& lhs.mCollisionBoxHalfExtents == rhs.mCollisionBoxHalfExtents
&& lhs.mCollisionBoxTranslate == rhs.mCollisionBoxTranslate
&& lhs.mCollisionBox.extents == rhs.mCollisionBox.extents
&& lhs.mCollisionBox.center == rhs.mCollisionBox.center
&& lhs.mAnimatedShapes == rhs.mAnimatedShapes;
}
@ -172,7 +172,8 @@ namespace Resource
return stream << "Resource::BulletShape {"
<< value.mCollisionShape << ", "
<< value.mAvoidCollisionShape << ", "
<< "osg::Vec3f {" << value.mCollisionBoxHalfExtents << "}" << ", "
<< "osg::Vec3f {" << value.mCollisionBox.extents << "}" << ", "
<< "osg::Vec3f {" << value.mCollisionBox.center << "}" << ", "
<< value.mAnimatedShapes
<< "}";
}
@ -258,14 +259,19 @@ namespace
value.isBone = false;
}
void init(Nif::NiTriShape& value)
void init(Nif::NiGeometry& value)
{
init(static_cast<Nif::Node&>(value));
value.recType = Nif::RC_NiTriShape;
value.data = Nif::NiTriShapeDataPtr(nullptr);
value.data = Nif::NiGeometryDataPtr(nullptr);
value.skin = Nif::NiSkinInstancePtr(nullptr);
}
void init(Nif::NiTriShape& value)
{
init(static_cast<Nif::NiGeometry&>(value));
value.recType = Nif::RC_NiTriShape;
}
void init(Nif::NiSkinInstance& value)
{
value.data = Nif::NiSkinDataPtr(nullptr);
@ -293,22 +299,22 @@ namespace
struct NifFileMock : Nif::File
{
MOCK_CONST_METHOD1(getRecord, Nif::Record* (std::size_t));
MOCK_CONST_METHOD0(numRecords, std::size_t ());
MOCK_CONST_METHOD1(getRoot, Nif::Record* (std::size_t));
MOCK_CONST_METHOD0(numRoots, std::size_t ());
MOCK_CONST_METHOD1(getString, std::string (uint32_t));
MOCK_METHOD1(setUseSkinning, void (bool));
MOCK_CONST_METHOD0(getUseSkinning, bool ());
MOCK_CONST_METHOD0(getFilename, std::string ());
MOCK_CONST_METHOD0(getVersion, unsigned int ());
MOCK_CONST_METHOD0(getUserVersion, unsigned int ());
MOCK_CONST_METHOD0(getBethVersion, unsigned int ());
MOCK_METHOD(Nif::Record*, getRecord, (std::size_t), (const, override));
MOCK_METHOD(std::size_t, numRecords, (), (const, override));
MOCK_METHOD(Nif::Record*, getRoot, (std::size_t), (const, override));
MOCK_METHOD(std::size_t, numRoots, (), (const, override));
MOCK_METHOD(std::string, getString, (uint32_t), (const, override));
MOCK_METHOD(void, setUseSkinning, (bool), (override));
MOCK_METHOD(bool, getUseSkinning, (), (const, override));
MOCK_METHOD(std::string, getFilename, (), (const, override));
MOCK_METHOD(unsigned int, getVersion, (), (const, override));
MOCK_METHOD(unsigned int, getUserVersion, (), (const, override));
MOCK_METHOD(unsigned int, getBethVersion, (), (const, override));
};
struct RecordMock : Nif::Record
{
MOCK_METHOD1(read, void (Nif::NIFStream *nif));
MOCK_METHOD(void, read, (Nif::NIFStream *nif), (override));
};
struct TestBulletNifLoader : Test
@ -360,13 +366,15 @@ namespace
init(mNiStringExtraData2);
init(mController);
mNiTriShapeData.recType = Nif::RC_NiTriShapeData;
mNiTriShapeData.vertices = {osg::Vec3f(0, 0, 0), osg::Vec3f(1, 0, 0), osg::Vec3f(1, 1, 0)};
mNiTriShapeData.triangles = {0, 1, 2};
mNiTriShape.data = Nif::NiTriShapeDataPtr(&mNiTriShapeData);
mNiTriShape.data = Nif::NiGeometryDataPtr(&mNiTriShapeData);
mNiTriShapeData2.recType = Nif::RC_NiTriShapeData;
mNiTriShapeData2.vertices = {osg::Vec3f(0, 0, 1), osg::Vec3f(1, 0, 1), osg::Vec3f(1, 1, 1)};
mNiTriShapeData2.triangles = {0, 1, 2};
mNiTriShape2.data = Nif::NiTriShapeDataPtr(&mNiTriShapeData2);
mNiTriShape2.data = Nif::NiGeometryDataPtr(&mNiTriShapeData2);
}
};
@ -433,8 +441,8 @@ namespace
const auto result = mLoader.load(mNifFile);
Resource::BulletShape expected;
expected.mCollisionBoxHalfExtents = osg::Vec3f(1, 2, 3);
expected.mCollisionBoxTranslate = osg::Vec3f(-1, -2, -3);
expected.mCollisionBox.extents = osg::Vec3f(1, 2, 3);
expected.mCollisionBox.center = osg::Vec3f(-1, -2, -3);
std::unique_ptr<btBoxShape> box(new btBoxShape(btVector3(1, 2, 3)));
std::unique_ptr<btCompoundShape> shape(new btCompoundShape);
shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release());
@ -458,8 +466,8 @@ namespace
const auto result = mLoader.load(mNifFile);
Resource::BulletShape expected;
expected.mCollisionBoxHalfExtents = osg::Vec3f(1, 2, 3);
expected.mCollisionBoxTranslate = osg::Vec3f(-1, -2, -3);
expected.mCollisionBox.extents = osg::Vec3f(1, 2, 3);
expected.mCollisionBox.center = osg::Vec3f(-1, -2, -3);
std::unique_ptr<btBoxShape> box(new btBoxShape(btVector3(1, 2, 3)));
std::unique_ptr<btCompoundShape> shape(new btCompoundShape);
shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release());
@ -488,8 +496,8 @@ namespace
const auto result = mLoader.load(mNifFile);
Resource::BulletShape expected;
expected.mCollisionBoxHalfExtents = osg::Vec3f(1, 2, 3);
expected.mCollisionBoxTranslate = osg::Vec3f(-1, -2, -3);
expected.mCollisionBox.extents = osg::Vec3f(1, 2, 3);
expected.mCollisionBox.center = osg::Vec3f(-1, -2, -3);
std::unique_ptr<btBoxShape> box(new btBoxShape(btVector3(1, 2, 3)));
std::unique_ptr<btCompoundShape> shape(new btCompoundShape);
shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release());
@ -523,8 +531,8 @@ namespace
const auto result = mLoader.load(mNifFile);
Resource::BulletShape expected;
expected.mCollisionBoxHalfExtents = osg::Vec3f(1, 2, 3);
expected.mCollisionBoxTranslate = osg::Vec3f(-1, -2, -3);
expected.mCollisionBox.extents = osg::Vec3f(1, 2, 3);
expected.mCollisionBox.center = osg::Vec3f(-1, -2, -3);
std::unique_ptr<btBoxShape> box(new btBoxShape(btVector3(1, 2, 3)));
std::unique_ptr<btCompoundShape> shape(new btCompoundShape);
shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release());
@ -558,8 +566,8 @@ namespace
const auto result = mLoader.load(mNifFile);
Resource::BulletShape expected;
expected.mCollisionBoxHalfExtents = osg::Vec3f(4, 5, 6);
expected.mCollisionBoxTranslate = osg::Vec3f(-4, -5, -6);
expected.mCollisionBox.extents = osg::Vec3f(4, 5, 6);
expected.mCollisionBox.center = osg::Vec3f(-4, -5, -6);
std::unique_ptr<btBoxShape> box(new btBoxShape(btVector3(4, 5, 6)));
std::unique_ptr<btCompoundShape> shape(new btCompoundShape);
shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-4, -5, -6)), box.release());
@ -581,8 +589,8 @@ namespace
const auto result = mLoader.load(mNifFile);
Resource::BulletShape expected;
expected.mCollisionBoxHalfExtents = osg::Vec3f(1, 2, 3);
expected.mCollisionBoxTranslate = osg::Vec3f(-1, -2, -3);
expected.mCollisionBox.extents = osg::Vec3f(1, 2, 3);
expected.mCollisionBox.center = osg::Vec3f(-1, -2, -3);
EXPECT_EQ(*result, expected);
}
@ -615,8 +623,8 @@ namespace
const auto result = mLoader.load(mNifFile);
Resource::BulletShape expected;
expected.mCollisionBoxHalfExtents = osg::Vec3f(1, 2, 3);
expected.mCollisionBoxTranslate = osg::Vec3f(-1, -2, -3);
expected.mCollisionBox.extents = osg::Vec3f(1, 2, 3);
expected.mCollisionBox.center = osg::Vec3f(-1, -2, -3);
EXPECT_EQ(*result, expected);
}
@ -872,7 +880,7 @@ namespace
TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_empty_data_should_return_shape_with_null_collision_shape)
{
mNiTriShape.data = Nif::NiTriShapeDataPtr(nullptr);
mNiTriShape.data = Nif::NiGeometryDataPtr(nullptr);
mNiNode.children = Nif::NodeList(std::vector<Nif::NodePtr>({Nif::NodePtr(&mNiTriShape)}));
EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1));
@ -887,7 +895,8 @@ namespace
TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_empty_data_triangles_should_return_shape_with_null_collision_shape)
{
mNiTriShape.data->triangles.clear();
auto data = static_cast<Nif::NiTriShapeData*>(mNiTriShape.data.getPtr());
data->triangles.clear();
mNiNode.children = Nif::NodeList(std::vector<Nif::NodePtr>({Nif::NodePtr(&mNiTriShape)}));
EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1));

View file

@ -157,6 +157,8 @@ void Wizard::MainWizard::setupGameSettings()
mGameSettings.readUserFile(stream);
}
file.close();
// Now the rest
QStringList paths;
paths.append(userPath + QLatin1String("openmw.cfg"));

View file

@ -50,7 +50,7 @@ install:
before_build:
- cmd: git submodule update --init --recursive
- cmd: sh %APPVEYOR_BUILD_FOLDER%\CI\before_script.msvc.sh -c %configuration% -p %PLATFORM% -v %msvc% -V -i %APPVEYOR_BUILD_FOLDER%\install
- cmd: sh %APPVEYOR_BUILD_FOLDER%\CI\before_script.msvc.sh -c %configuration% -p %PLATFORM% -v %msvc% -V -i %APPVEYOR_BUILD_FOLDER%\install -D
build_script:
- cmd: if %PLATFORM%==Win32 set build=MSVC%msvc%_32

67
cmake/OSIdentity.cmake Normal file
View file

@ -0,0 +1,67 @@
if (UNIX)
if (APPLE)
set(CMAKE_OS_NAME "OSX" CACHE STRING "Operating system name" FORCE)
else (APPLE)
## Check for Debian GNU/Linux ________________
find_file(DEBIAN_FOUND debian_version debconf.conf
PATHS /etc
)
if (DEBIAN_FOUND)
set(CMAKE_OS_NAME "Debian" CACHE STRING "Operating system name" FORCE)
endif (DEBIAN_FOUND)
## Check for Fedora _________________________
find_file(FEDORA_FOUND fedora-release
PATHS /etc
)
if (FEDORA_FOUND)
set(CMAKE_OS_NAME "Fedora" CACHE STRING "Operating system name" FORCE)
endif (FEDORA_FOUND)
## Check for RedHat _________________________
find_file(REDHAT_FOUND redhat-release inittab.RH
PATHS /etc
)
if (REDHAT_FOUND)
set(CMAKE_OS_NAME "RedHat" CACHE STRING "Operating system name" FORCE)
endif (REDHAT_FOUND)
## Extra check for Ubuntu ____________________
if (DEBIAN_FOUND)
## At its core Ubuntu is a Debian system, with
## a slightly altered configuration; hence from
## a first superficial inspection a system will
## be considered as Debian, which signifies an
## extra check is required.
find_file(UBUNTU_EXTRA legal issue
PATHS /etc
)
if (UBUNTU_EXTRA)
## Scan contents of file
file(STRINGS ${UBUNTU_EXTRA} UBUNTU_FOUND
REGEX Ubuntu
)
## Check result of string search
if (UBUNTU_FOUND)
set(CMAKE_OS_NAME "Ubuntu" CACHE STRING "Operating system name" FORCE)
set(DEBIAN_FOUND FALSE)
endif (UBUNTU_FOUND)
endif (UBUNTU_EXTRA)
endif (DEBIAN_FOUND)
endif (APPLE)
endif (UNIX)

View file

@ -224,13 +224,23 @@ add_component_dir (fallback
fallback validate
)
if(NOT WIN32 AND NOT ANDROID)
if (BUILD_OPENMW OR BUILD_OPENCS)
# Start of tes3mp change (major)
#
# Don't require the crashcatcher when building the server
if (BUILD_OPENMW OR BUILD_OPENCS)
if(WIN32)
add_component_dir (crashcatcher
windows_crashcatcher
windows_crashmonitor
windows_crashshm
)
elseif(NOT ANDROID)
add_component_dir (crashcatcher
crashcatcher
)
endif()
endif()
# End of tes3mp change (major)
if (BUILD_OPENMW OR BUILD_OPENCS)
add_component_dir(detournavigator
@ -309,19 +319,28 @@ target_link_libraries(components
${OSGGA_LIBRARIES}
${OSGSHADOW_LIBRARIES}
${OSGANIMATION_LIBRARIES}
${BULLET_LIBRARIES}
${SDL2_LIBRARIES}
${OPENGL_gl_LIBRARY}
${MyGUI_LIBRARIES}
LZ4::LZ4
)
# Start of tes3mp change (major)
#
# Don't require RecastNavigation when building the server
if (BUILD_OPENMW OR BUILD_OPENCS)
target_link_libraries(components
RecastNavigation::DebugUtils
RecastNavigation::Detour
RecastNavigation::Recast)
endif ()
# End of tes3mp change (major)
if (BULLET_USE_DOUBLES AND (UBUNTU_FOUND OR DEBIAN_FOUND))
target_link_libraries(components BulletCollision-float64 LinearMath-float64)
else()
target_link_libraries(components ${BULLET_LIBRARIES})
endif()
if (WIN32)
target_link_libraries(components

View file

@ -14,8 +14,6 @@ Config::GameSettings::GameSettings(Files::ConfigurationManager &cfg)
{
}
Config::GameSettings::~GameSettings() = default;
void Config::GameSettings::validatePaths()
{
QStringList paths = mSettings.values(QString("data"));

View file

@ -21,7 +21,6 @@ namespace Config
{
public:
GameSettings(Files::ConfigurationManager &cfg);
~GameSettings();
inline QString value(const QString &key, const QString &defaultValue = QString())
{
@ -54,11 +53,11 @@ namespace Config
mUserSettings.remove(key);
}
inline QStringList getDataDirs() { return mDataDirs; }
inline QStringList getDataDirs() const { return mDataDirs; }
inline void removeDataDir(const QString &dir) { if(!dir.isEmpty()) mDataDirs.removeAll(dir); }
inline void addDataDir(const QString &dir) { if(!dir.isEmpty()) mDataDirs.append(dir); }
inline QString getDataLocal() {return mDataLocal; }
inline QString getDataLocal() const {return mDataLocal; }
bool hasMaster();

View file

@ -12,10 +12,6 @@ const char Config::LauncherSettings::sLauncherConfigFileName[] = "launcher.cfg";
const char Config::LauncherSettings::sContentListsSectionPrefix[] = "Profiles/";
const char Config::LauncherSettings::sContentListSuffix[] = "/content";
Config::LauncherSettings::LauncherSettings() = default;
Config::LauncherSettings::~LauncherSettings() = default;
QStringList Config::LauncherSettings::subKeys(const QString &key)
{
QMultiMap<QString, QString> settings = SettingsBase::getSettings();

View file

@ -9,9 +9,6 @@ namespace Config
class LauncherSettings : public SettingsBase<QMultiMap<QString, QString> >
{
public:
LauncherSettings();
~LauncherSettings();
bool writeFile(QTextStream &stream);
/// \return names of all Content Lists in the launcher's .cfg file.

View file

@ -0,0 +1,205 @@
#include <cassert>
#include <cwchar>
#include <iostream>
#include <sstream>
#include <thread>
#include "windows_crashcatcher.hpp"
#include "windows_crashmonitor.hpp"
#include "windows_crashshm.hpp"
#include <SDL_messagebox.h>
namespace Crash
{
HANDLE duplicateHandle(HANDLE handle)
{
HANDLE duplicate;
if (!DuplicateHandle(GetCurrentProcess(), handle,
GetCurrentProcess(), &duplicate,
0, TRUE, DUPLICATE_SAME_ACCESS))
{
throw std::runtime_error("Crash monitor could not duplicate handle");
}
return duplicate;
}
CrashCatcher* CrashCatcher::sInstance = nullptr;
CrashCatcher::CrashCatcher(int argc, char **argv, const std::string& crashLogPath)
{
assert(sInstance == nullptr); // don't allow two instances
sInstance = this;
HANDLE shmHandle = nullptr;
for (int i=0; i<argc; ++i)
{
if (strcmp(argv[i], "--crash-monitor"))
continue;
if (i >= argc - 1)
throw std::runtime_error("Crash monitor is missing the SHM handle argument");
sscanf(argv[i + 1], "%p", &shmHandle);
break;
}
if (!shmHandle)
{
setupIpc();
startMonitorProcess(crashLogPath);
installHandler();
}
else
{
CrashMonitor(shmHandle).run();
exit(0);
}
}
CrashCatcher::~CrashCatcher()
{
sInstance = nullptr;
if (mShm && mSignalMonitorEvent)
{
shmLock();
mShm->mEvent = CrashSHM::Event::Shutdown;
shmUnlock();
SetEvent(mSignalMonitorEvent);
}
if (mShmHandle)
CloseHandle(mShmHandle);
}
void CrashCatcher::setupIpc()
{
SECURITY_ATTRIBUTES attributes;
ZeroMemory(&attributes, sizeof(attributes));
attributes.bInheritHandle = TRUE;
mSignalAppEvent = CreateEventW(&attributes, FALSE, FALSE, NULL);
mSignalMonitorEvent = CreateEventW(&attributes, FALSE, FALSE, NULL);
mShmHandle = CreateFileMappingW(INVALID_HANDLE_VALUE, &attributes, PAGE_READWRITE, HIWORD(sizeof(CrashSHM)), LOWORD(sizeof(CrashSHM)), NULL);
if (mShmHandle == nullptr)
throw std::runtime_error("Failed to allocate crash catcher shared memory");
mShm = reinterpret_cast<CrashSHM*>(MapViewOfFile(mShmHandle, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(CrashSHM)));
if (mShm == nullptr)
throw std::runtime_error("Failed to map crash catcher shared memory");
mShmMutex = CreateMutexW(&attributes, FALSE, NULL);
if (mShmMutex == nullptr)
throw std::runtime_error("Failed to create crash catcher shared memory mutex");
}
void CrashCatcher::shmLock()
{
if (WaitForSingleObject(mShmMutex, CrashCatcherTimeout) != WAIT_OBJECT_0)
throw std::runtime_error("SHM lock timed out");
}
void CrashCatcher::shmUnlock()
{
ReleaseMutex(mShmMutex);
}
void CrashCatcher::waitMonitor()
{
if (WaitForSingleObject(mSignalAppEvent, CrashCatcherTimeout) != WAIT_OBJECT_0)
throw std::runtime_error("Waiting for monitor failed");
}
void CrashCatcher::signalMonitor()
{
SetEvent(mSignalMonitorEvent);
}
void CrashCatcher::installHandler()
{
SetUnhandledExceptionFilter(vectoredExceptionHandler);
}
void CrashCatcher::startMonitorProcess(const std::string& crashLogPath)
{
std::wstring executablePath;
DWORD copied = 0;
do {
executablePath.resize(executablePath.size() + MAX_PATH);
copied = GetModuleFileNameW(nullptr, executablePath.data(), executablePath.size());
} while (copied >= executablePath.size());
executablePath.resize(copied);
memset(mShm->mStartup.mLogFilePath, 0, sizeof(mShm->mStartup.mLogFilePath));
int length = crashLogPath.length();
if (length > MAX_LONG_PATH) length = MAX_LONG_PATH;
strncpy(mShm->mStartup.mLogFilePath, crashLogPath.c_str(), length);
mShm->mStartup.mLogFilePath[length] = '\0';
// note that we don't need to lock the SHM here, the other process has not started yet
mShm->mEvent = CrashSHM::Event::Startup;
mShm->mStartup.mShmMutex = duplicateHandle(mShmMutex);
mShm->mStartup.mAppProcessHandle = duplicateHandle(GetCurrentProcess());
mShm->mStartup.mSignalApp = duplicateHandle(mSignalAppEvent);
mShm->mStartup.mSignalMonitor = duplicateHandle(mSignalMonitorEvent);
std::wstringstream ss;
ss << "--crash-monitor " << std::hex << duplicateHandle(mShmHandle);
std::wstring arguments(ss.str());
STARTUPINFOW si;
ZeroMemory(&si, sizeof(si));
PROCESS_INFORMATION pi;
ZeroMemory(&pi, sizeof(pi));
if (!CreateProcessW(executablePath.data(), arguments.data(), NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi))
throw std::runtime_error("Could not start crash monitor process");
waitMonitor();
}
LONG CrashCatcher::vectoredExceptionHandler(PEXCEPTION_POINTERS info)
{
switch (info->ExceptionRecord->ExceptionCode)
{
case EXCEPTION_SINGLE_STEP:
case EXCEPTION_BREAKPOINT:
case DBG_PRINTEXCEPTION_C:
return EXCEPTION_EXECUTE_HANDLER;
}
if (!sInstance)
return EXCEPTION_EXECUTE_HANDLER;
sInstance->handleVectoredException(info);
_Exit(1);
return EXCEPTION_CONTINUE_SEARCH;
}
void CrashCatcher::handleVectoredException(PEXCEPTION_POINTERS info)
{
shmLock();
mShm->mEvent = CrashSHM::Event::Crashed;
mShm->mCrashed.mThreadId = GetCurrentThreadId();
mShm->mCrashed.mContext = *info->ContextRecord;
mShm->mCrashed.mExceptionRecord = *info->ExceptionRecord;
shmUnlock();
signalMonitor();
// must remain until monitor has finished
waitMonitor();
std::string message = "OpenMW has encountered a fatal error.\nCrash log saved to '" + std::string(mShm->mStartup.mLogFilePath) + "'.\n Please report this to https://gitlab.com/OpenMW/openmw/issues !";
SDL_ShowSimpleMessageBox(0, "Fatal Error", message.c_str(), nullptr);
}
} // namespace Crash

View file

@ -0,0 +1,79 @@
#ifndef WINDOWS_CRASHCATCHER_HPP
#define WINDOWS_CRASHCATCHER_HPP
#include <string>
#undef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <components/crashcatcher/crashcatcher.hpp>
namespace Crash
{
// The implementation spawns the current executable as a monitor process which waits
// for a global synchronization event which is sent when the parent process crashes.
// The monitor process then extracts crash information from the parent process while
// the parent process waits for the monitor process to finish. The crashed process
// quits and the monitor writes the crash information to a file.
//
// To detect unexpected shutdowns of the application which are not handled by the
// crash handler, the monitor periodically checks the exit code of the parent
// process and exits if it does not return STILL_ACTIVE. You can test this by closing
// the main openmw process in task manager.
static constexpr const int CrashCatcherTimeout = 2500;
struct CrashSHM;
class CrashCatcher final
{
public:
CrashCatcher(int argc, char **argv, const std::string& crashLogPath);
~CrashCatcher();
private:
static CrashCatcher* sInstance;
// mapped SHM area
CrashSHM* mShm = nullptr;
// the handle is allocated by the catcher and passed to the monitor
// process via the command line which maps the SHM and sends / receives
// events through it
HANDLE mShmHandle = nullptr;
// mutex which guards SHM area
HANDLE mShmMutex = nullptr;
// triggered when the monitor signals the application
HANDLE mSignalAppEvent = INVALID_HANDLE_VALUE;
// triggered when the application wants to wake the monitor process
HANDLE mSignalMonitorEvent = INVALID_HANDLE_VALUE;
void setupIpc();
void shmLock();
void shmUnlock();
void startMonitorProcess(const std::string& crashLogPath);
void waitMonitor();
void signalMonitor();
void installHandler();
void handleVectoredException(PEXCEPTION_POINTERS info);
public:
static LONG WINAPI vectoredExceptionHandler(PEXCEPTION_POINTERS info);
};
} // namespace Crash
#endif // WINDOWS_CRASHCATCHER_HPP

View file

@ -0,0 +1,188 @@
#undef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <Psapi.h>
#include <DbgHelp.h>
#include <iostream>
#include <memory>
#include <sstream>
#include "windows_crashcatcher.hpp"
#include "windows_crashmonitor.hpp"
#include "windows_crashshm.hpp"
#include <components/debug/debuglog.hpp>
namespace Crash
{
CrashMonitor::CrashMonitor(HANDLE shmHandle)
: mShmHandle(shmHandle)
{
mShm = reinterpret_cast<CrashSHM*>(MapViewOfFile(mShmHandle, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(CrashSHM)));
if (mShm == nullptr)
throw std::runtime_error("Failed to map crash monitor shared memory");
// accessing SHM without lock is OK here, the parent waits for a signal before continuing
mShmMutex = mShm->mStartup.mShmMutex;
mAppProcessHandle = mShm->mStartup.mAppProcessHandle;
mSignalAppEvent = mShm->mStartup.mSignalApp;
mSignalMonitorEvent = mShm->mStartup.mSignalMonitor;
}
CrashMonitor::~CrashMonitor()
{
if (mShm)
UnmapViewOfFile(mShm);
// the handles received from the app are duplicates, we must close them
if (mShmHandle)
CloseHandle(mShmHandle);
if (mShmMutex)
CloseHandle(mShmMutex);
if (mSignalAppEvent)
CloseHandle(mSignalAppEvent);
if (mSignalMonitorEvent)
CloseHandle(mSignalMonitorEvent);
}
void CrashMonitor::shmLock()
{
if (WaitForSingleObject(mShmMutex, CrashCatcherTimeout) != WAIT_OBJECT_0)
throw std::runtime_error("SHM monitor lock timed out");
}
void CrashMonitor::shmUnlock()
{
ReleaseMutex(mShmMutex);
}
void CrashMonitor::signalApp() const
{
SetEvent(mSignalAppEvent);
}
bool CrashMonitor::waitApp() const
{
return WaitForSingleObject(mSignalMonitorEvent, CrashCatcherTimeout) == WAIT_OBJECT_0;
}
bool CrashMonitor::isAppAlive() const
{
DWORD code = 0;
GetExitCodeProcess(mAppProcessHandle, &code);
return code == STILL_ACTIVE;
}
void CrashMonitor::run()
{
try
{
// app waits for monitor start up, let it continue
signalApp();
bool running = true;
while (isAppAlive() && running)
{
if (waitApp())
{
shmLock();
switch (mShm->mEvent)
{
case CrashSHM::Event::None:
break;
case CrashSHM::Event::Crashed:
handleCrash();
running = false;
break;
case CrashSHM::Event::Shutdown:
running = false;
break;
case CrashSHM::Event::Startup:
break;
}
shmUnlock();
}
}
}
catch (...)
{
Log(Debug::Error) << "Exception in crash monitor, exiting";
}
signalApp();
}
std::wstring utf8ToUtf16(const std::string& utf8)
{
const int nLenWide = MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), utf8.size(), nullptr, 0);
std::wstring utf16;
utf16.resize(nLenWide);
if (MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), utf8.size(), utf16.data(), nLenWide) != nLenWide)
return {};
return utf16;
}
void CrashMonitor::handleCrash()
{
DWORD processId = GetProcessId(mAppProcessHandle);
try
{
HMODULE dbghelp = LoadLibraryA("dbghelp.dll");
if (dbghelp == NULL)
return;
using MiniDumpWirteDumpFn = BOOL (WINAPI*)(
HANDLE hProcess, DWORD ProcessId, HANDLE hFile, MINIDUMP_TYPE DumpType, PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, PMINIDUMP_CALLBACK_INFORMATION CallbackParam
);
MiniDumpWirteDumpFn miniDumpWriteDump = (MiniDumpWirteDumpFn)GetProcAddress(dbghelp, "MiniDumpWriteDump");
if (miniDumpWriteDump == NULL)
return;
std::wstring utf16Path = utf8ToUtf16(mShm->mStartup.mLogFilePath);
if (utf16Path.empty())
return;
if (utf16Path.length() > MAX_PATH)
utf16Path = LR"(\\?\)" + utf16Path;
HANDLE hCrashLog = CreateFileW(utf16Path.c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
if (hCrashLog == NULL || hCrashLog == INVALID_HANDLE_VALUE)
return;
if (auto err = GetLastError(); err != ERROR_ALREADY_EXISTS && err != 0)
return;
EXCEPTION_POINTERS exp;
exp.ContextRecord = &mShm->mCrashed.mContext;
exp.ExceptionRecord = &mShm->mCrashed.mExceptionRecord;
MINIDUMP_EXCEPTION_INFORMATION infos = {};
infos.ThreadId = mShm->mCrashed.mThreadId;
infos.ExceptionPointers = &exp;
infos.ClientPointers = FALSE;
MINIDUMP_TYPE type = (MINIDUMP_TYPE)(MiniDumpWithDataSegs | MiniDumpWithHandleData);
miniDumpWriteDump(mAppProcessHandle, processId, hCrashLog, type, &infos, 0, 0);
}
catch (const std::exception&e)
{
Log(Debug::Error) << "CrashMonitor: " << e.what();
}
catch (...)
{
Log(Debug::Error) << "CrashMonitor: unknown exception";
}
}
} // namespace Crash

Some files were not shown because too many files have changed in this diff Show more