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:
commit
da3316daf8
147 changed files with 3230 additions and 1257 deletions
|
@ -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)
|
||||
|
|
26
CHANGELOG.md
26
CHANGELOG.md
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -73,7 +73,7 @@ CONFIGURATIONS=()
|
|||
TEST_FRAMEWORK=""
|
||||
GOOGLE_INSTALL_ROOT=""
|
||||
INSTALL_PREFIX="."
|
||||
BULLET_DOUBLE=""
|
||||
BULLET_DOUBLE=true
|
||||
BULLET_DBL=""
|
||||
BULLET_DBL_DISPLAY="Single precision"
|
||||
|
||||
|
|
|
@ -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}")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 ¤t, bool savePrevious);
|
||||
void removeProfile (const QString &profile);
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -45,4 +45,5 @@ QString CellNameLoader::getCellName(ESM::ESMReader &esmReader)
|
|||
cell.loadNameAndData(esmReader, isDeleted);
|
||||
|
||||
return QString::fromStdString(cell.mName);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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 **/
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -73,6 +73,8 @@ namespace MWGui
|
|||
bool mClosingTag;
|
||||
std::map<std::string, Events> mTagTypes;
|
||||
std::string mBuffer;
|
||||
|
||||
size_t mPlainTextEnd;
|
||||
};
|
||||
|
||||
class Paginator
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()) + "/"
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 **/
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
21
apps/openmw/mwphysics/contacttestwrapper.cpp
Normal file
21
apps/openmw/mwphysics/contacttestwrapper.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
14
apps/openmw/mwphysics/contacttestwrapper.h
Normal file
14
apps/openmw/mwphysics/contacttestwrapper.h
Normal 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
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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()));
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
131
apps/openmw/mwphysics/projectile.cpp
Normal file
131
apps/openmw/mwphysics/projectile.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
112
apps/openmw/mwphysics/projectile.hpp
Normal file
112
apps/openmw/mwphysics/projectile.hpp
Normal 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
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -356,9 +356,6 @@ namespace MWRender
|
|||
else
|
||||
mViewModeToggleQueued = false;
|
||||
|
||||
if (mTrackingPtr.getClass().isActor())
|
||||
mTrackingPtr.getClass().getCreatureStats(mTrackingPtr).setSideMovementAngle(0);
|
||||
|
||||
mFirstPersonView = !mFirstPersonView;
|
||||
updateStandingPreviewMode();
|
||||
instantTransition();
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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"))
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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++;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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(...)
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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})
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -157,6 +157,8 @@ void Wizard::MainWizard::setupGameSettings()
|
|||
mGameSettings.readUserFile(stream);
|
||||
}
|
||||
|
||||
file.close();
|
||||
|
||||
// Now the rest
|
||||
QStringList paths;
|
||||
paths.append(userPath + QLatin1String("openmw.cfg"));
|
||||
|
|
|
@ -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
67
cmake/OSIdentity.cmake
Normal 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)
|
|
@ -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
|
||||
|
|
|
@ -14,8 +14,6 @@ Config::GameSettings::GameSettings(Files::ConfigurationManager &cfg)
|
|||
{
|
||||
}
|
||||
|
||||
Config::GameSettings::~GameSettings() = default;
|
||||
|
||||
void Config::GameSettings::validatePaths()
|
||||
{
|
||||
QStringList paths = mSettings.values(QString("data"));
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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.
|
||||
|
|
205
components/crashcatcher/windows_crashcatcher.cpp
Normal file
205
components/crashcatcher/windows_crashcatcher.cpp
Normal 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
|
79
components/crashcatcher/windows_crashcatcher.hpp
Normal file
79
components/crashcatcher/windows_crashcatcher.hpp
Normal 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
|
188
components/crashcatcher/windows_crashmonitor.cpp
Normal file
188
components/crashcatcher/windows_crashmonitor.cpp
Normal 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
Loading…
Reference in a new issue