mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-01-16 18:49:58 +00:00
Add OpenMW commits up to 13 Apr 2021
# Conflicts: # .travis.yml # apps/openmw/mwmechanics/actors.cpp # apps/openmw/mwmechanics/summoning.cpp # apps/openmw/mwphysics/mtphysics.hpp
This commit is contained in:
commit
cedf70f367
174 changed files with 2919 additions and 1450 deletions
|
@ -30,6 +30,29 @@ stages:
|
|||
paths:
|
||||
- build/install/
|
||||
|
||||
Coverity:
|
||||
extends: .Debian
|
||||
rules:
|
||||
- if: '$CI_PIPELINE_SOURCE == "schedule"'
|
||||
before_script:
|
||||
- CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic coverity
|
||||
- curl -o /tmp/cov-analysis-linux64.tgz https://scan.coverity.com/download/linux64 --form project=$COVERITY_SCAN_PROJECT_NAME --form token=$COVERITY_SCAN_TOKEN
|
||||
- tar xfz /tmp/cov-analysis-linux64.tgz
|
||||
script:
|
||||
- CI/before_script.linux.sh
|
||||
# Add more than just `openmw` once we can build everything under 3h
|
||||
- cov-analysis-linux64-*/bin/cov-build --dir cov-int cmake --build build -- -j $(nproc) openmw
|
||||
after_script:
|
||||
- tar cfz cov-int.tar.gz cov-int
|
||||
- curl https://scan.coverity.com/builds?project=$COVERITY_SCAN_PROJECT_NAME \
|
||||
--form token=$COVERITY_SCAN_TOKEN --form email=$GITLAB_USER_EMAIL \
|
||||
--form file=@cov-int.tar.gz --form version="`git describe --tags`" \
|
||||
--form description="`git describe --tags` / $CI_COMMIT_TITLE / $CI_COMMIT_REF_NAME:$CI_PIPELINE_ID
|
||||
variables:
|
||||
CC: gcc
|
||||
CXX: g++
|
||||
timeout: 8h
|
||||
|
||||
Debian_GCC:
|
||||
extends: .Debian
|
||||
cache:
|
||||
|
@ -93,24 +116,49 @@ Debian_Clang_tests:
|
|||
CCACHE_SIZE: 1G
|
||||
BUILD_TESTS_ONLY: 1
|
||||
|
||||
MacOS:
|
||||
.MacOS:
|
||||
image: macos-11-xcode-12
|
||||
tags:
|
||||
- macos
|
||||
stage: build
|
||||
only:
|
||||
variables:
|
||||
- $CI_PROJECT_ID == "7107382"
|
||||
cache:
|
||||
paths:
|
||||
- ccache/
|
||||
script:
|
||||
- rm -fr build/* # remove anything in the build directory
|
||||
- export CCACHE_BASEDIR="$(pwd)"
|
||||
- export CCACHE_DIR="$(pwd)/ccache"
|
||||
- mkdir -pv "${CCACHE_DIR}"
|
||||
- ccache -z -M "${CCACHE_SIZE}"
|
||||
- CI/before_install.osx.sh
|
||||
- CI/before_script.osx.sh
|
||||
- cd build; make -j2 package
|
||||
- cd build; make -j $(sysctl -n hw.logicalcpu) package
|
||||
- for dmg in *.dmg; do mv "$dmg" "${dmg%.dmg}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}.dmg"; done
|
||||
- ccache -s
|
||||
artifacts:
|
||||
paths:
|
||||
- build/OpenMW-*.dmg
|
||||
- "build/**/*.log"
|
||||
|
||||
macOS11_Xcode12:
|
||||
extends: .MacOS
|
||||
image: macos-11-xcode-12
|
||||
cache:
|
||||
key: macOS11_Xcode12.v1
|
||||
variables:
|
||||
CCACHE_SIZE: 3G
|
||||
|
||||
macOS10.15_Xcode11:
|
||||
extends: .MacOS
|
||||
image: macos-10.15-xcode-11
|
||||
cache:
|
||||
key: macOS10.15_Xcode11.v1
|
||||
variables:
|
||||
CCACHE_SIZE: 3G
|
||||
|
||||
variables: &engine-targets
|
||||
targets: "openmw,openmw-essimporter,openmw-iniimporter,openmw-launcher,openmw-wizard"
|
||||
package: "Engine"
|
||||
|
|
|
@ -18,7 +18,7 @@ addons:
|
|||
- ubuntu-toolchain-r-test
|
||||
packages: [
|
||||
# Dev
|
||||
cmake, clang-tools, gcc-8, g++-8, ccache,
|
||||
cmake, clang-tools-7, gcc-8, g++-8, ccache,
|
||||
# Boost
|
||||
libboost-filesystem-dev, libboost-iostreams-dev, libboost-program-options-dev, libboost-system-dev,
|
||||
# FFmpeg
|
||||
|
|
10
AUTHORS.md
10
AUTHORS.md
|
@ -4,7 +4,7 @@ Contributors
|
|||
The OpenMW project was started in 2008 by Nicolay Korslund.
|
||||
In the course of years many people have contributed to the project.
|
||||
|
||||
If you feel your name is missing from this list, please notify a developer.
|
||||
If you feel your name is missing from this list, please add it to `AUTHORS.md`.
|
||||
|
||||
|
||||
Programmers
|
||||
|
@ -24,7 +24,7 @@ Programmers
|
|||
Alex McKibben
|
||||
alexanderkjall
|
||||
Alexander Nadeau (wareya)
|
||||
Alexander Olofsson (Ace)
|
||||
Alexander Olofsson (Ananace)
|
||||
Alex Rice
|
||||
Alex S (docwest)
|
||||
Allofich
|
||||
|
@ -131,6 +131,7 @@ Programmers
|
|||
Martin Otto (MAtahualpa)
|
||||
Mateusz Kołaczek (PL_kolek)
|
||||
Mateusz Malisz (malice)
|
||||
Max Henzerling (SaintMercury)
|
||||
megaton
|
||||
Michael Hogan (Xethik)
|
||||
Michael Mc Donnell
|
||||
|
@ -183,6 +184,7 @@ Programmers
|
|||
sergoz
|
||||
ShadowRadiance
|
||||
Siimacore
|
||||
Simon Meulenbeek (simonmb)
|
||||
sir_herrbatka
|
||||
smbas
|
||||
Sophie Kirschner (pineapplemachine)
|
||||
|
@ -196,6 +198,7 @@ Programmers
|
|||
Sylvain Thesnieres (Garvek)
|
||||
t6
|
||||
terrorfisch
|
||||
Tess (tescoShoppah)
|
||||
thegriglat
|
||||
Thomas Luppi (Digmaster)
|
||||
tlmullis
|
||||
|
@ -234,7 +237,8 @@ Documentation
|
|||
Packagers
|
||||
---------
|
||||
|
||||
Alexander Olofsson (Ace) - Windows
|
||||
Alexander Olofsson (Ananace) - Windows and Flatpak
|
||||
Alexey Sokolov (DarthGandalf) - Gentoo Linux
|
||||
Bret Curtis (psi29a) - Debian and Ubuntu Linux
|
||||
Edmondo Tommasina (edmondo) - Gentoo Linux
|
||||
Julian Ospald (hasufell) - Gentoo Linux
|
||||
|
|
10
CHANGELOG.md
10
CHANGELOG.md
|
@ -57,6 +57,7 @@
|
|||
Bug #5424: Creatures do not headtrack player
|
||||
Bug #5425: Poison effect only appears for one frame
|
||||
Bug #5427: GetDistance unknown ID error is misleading
|
||||
Bug #5431: Physics performance degradation after a specific number of actors on a scene
|
||||
Bug #5435: Enemies can't hurt the player when collision is off
|
||||
Bug #5441: Enemies can't push a player character when in critical strike stance
|
||||
Bug #5451: Magic projectiles don't disappear with the caster
|
||||
|
@ -87,6 +88,7 @@
|
|||
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 #5680: Bull Netches incorrectly aim over the player character's head and always miss
|
||||
Bug #5681: Player character can clip or pass through bridges instead of colliding against them
|
||||
Bug #5687: Bound items covering the same inventory slot expiring at the same time freezes the game
|
||||
Bug #5688: Water shader broken indoors with enable indoor shadows = false
|
||||
|
@ -111,6 +113,10 @@
|
|||
Bug #5899: Visible modal windows and dropdowns crashing game on exit
|
||||
Bug #5902: NiZBufferProperty is unable to disable the depth test
|
||||
Bug #5906: Sunglare doesn't work with Mesa drivers and AMD GPUs
|
||||
Bug #5912: ImprovedBound mod doesn't work
|
||||
Bug #5914: BM: The Swimmer can't reach destination
|
||||
Bug #5923: Clicking on empty spaces between journal entries might show random topics
|
||||
Bug #5934: AddItem command doesn't accept negative values
|
||||
Feature #390: 3rd person look "over the shoulder"
|
||||
Feature #832: OpenMW-CS: Handle deleted references
|
||||
Feature #1536: Show more information about level on menu
|
||||
|
@ -118,6 +124,7 @@
|
|||
Feature #2404: Levelled List can not be placed into a container
|
||||
Feature #2686: Timestamps in openmw.log
|
||||
Feature #3171: OpenMW-CS: Instance drag selection
|
||||
Feature #3983: Wizard: Add link to buy Morrowind
|
||||
Feature #4894: Consider actors as obstacles for pathfinding
|
||||
Feature #4899: Alpha-To-Coverage Anti-Aliasing for alpha testing
|
||||
Feature #4977: Use the "default icon.tga" when an item's icon is not found
|
||||
|
@ -129,6 +136,7 @@
|
|||
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 #5511: Add in game option to toggle HRTF support in OpenMW
|
||||
Feature #5519: Code Patch tab in launcher
|
||||
Feature #5524: Resume failed script execution after reload
|
||||
Feature #5545: Option to allow stealing from an unconscious NPC during combat
|
||||
|
@ -143,6 +151,8 @@
|
|||
Feature #5730: Add graphic herbalism option to the launcher and documents
|
||||
Feature #5771: ori command should report where a mesh is loaded from and whether the x version is used.
|
||||
Feature #5813: Instanced groundcover support
|
||||
Feature #5814: Bsatool should be able to create BSA archives, not only to extract it
|
||||
Feature #5910: Fall back to delta time when physics can't keep up
|
||||
Task #5480: Drop Qt4 support
|
||||
Task #5520: Improve cell name autocompleter implementation
|
||||
Task #5844: Update 'toggle sneak' documentation
|
||||
|
|
|
@ -848,9 +848,11 @@ fi
|
|||
wrappedExit 1
|
||||
fi
|
||||
|
||||
if ! [ -e "aqt-venv/${VENV_BIN_DIR}/aqt" ]; then
|
||||
# check version
|
||||
aqt-venv/${VENV_BIN_DIR}/pip list | grep 'aqtinstall\s*1.1.3' || [ $? -ne 0 ]
|
||||
if [ $? -eq 0 ]; then
|
||||
echo " Installing aqt wheel into virtualenv..."
|
||||
run_cmd "aqt-venv/${VENV_BIN_DIR}/pip" install aqtinstall==0.9.2
|
||||
run_cmd "aqt-venv/${VENV_BIN_DIR}/pip" install aqtinstall==1.1.3
|
||||
fi
|
||||
popd > /dev/null
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ cmake \
|
|||
-D CMAKE_CXX_FLAGS="-stdlib=libc++" \
|
||||
-D CMAKE_C_FLAGS_RELEASE="-g -O0" \
|
||||
-D CMAKE_CXX_FLAGS_RELEASE="-g -O0" \
|
||||
-D CMAKE_OSX_DEPLOYMENT_TARGET="10.12" \
|
||||
-D CMAKE_OSX_DEPLOYMENT_TARGET="10.14" \
|
||||
-D CMAKE_BUILD_TYPE=RELEASE \
|
||||
-D OPENMW_OSX_DEPLOYMENT=TRUE \
|
||||
-D BUILD_OPENMW=TRUE \
|
||||
|
|
|
@ -28,6 +28,8 @@ declare -rA GROUPED_DEPS=(
|
|||
# These dependencies can alternatively be built and linked statically.
|
||||
[openmw-deps-dynamic]="libmygui-dev libopenscenegraph-dev"
|
||||
|
||||
[coverity]="curl"
|
||||
|
||||
# Pre-requisites for building MyGUI and OSG for static linking.
|
||||
#
|
||||
# * MyGUI and OSG: libsdl2-dev liblz4-dev libfreetype6-dev
|
||||
|
|
|
@ -20,6 +20,7 @@ struct Arguments
|
|||
std::string mode;
|
||||
std::string filename;
|
||||
std::string extractfile;
|
||||
std::string addfile;
|
||||
std::string outdir;
|
||||
|
||||
bool longformat;
|
||||
|
@ -36,6 +37,10 @@ bool parseOptions (int argc, char** argv, Arguments &info)
|
|||
" Extract a file from the input archive.\n\n"
|
||||
" bsatool extractall archivefile [output_directory]\n"
|
||||
" Extract all files from the input archive.\n\n"
|
||||
" bsatool add [-a] archivefile file_to_add\n"
|
||||
" Add a file to the input archive.\n\n"
|
||||
" bsatool create [-c] archivefile\n"
|
||||
" Create an archive.\n\n"
|
||||
"Allowed options");
|
||||
|
||||
desc.add_options()
|
||||
|
@ -95,7 +100,7 @@ bool parseOptions (int argc, char** argv, Arguments &info)
|
|||
}
|
||||
|
||||
info.mode = variables["mode"].as<std::string>();
|
||||
if (!(info.mode == "list" || info.mode == "extract" || info.mode == "extractall"))
|
||||
if (!(info.mode == "list" || info.mode == "extract" || info.mode == "extractall" || info.mode == "add" || info.mode == "create"))
|
||||
{
|
||||
std::cout << std::endl << "ERROR: invalid mode \"" << info.mode << "\"\n\n"
|
||||
<< desc << std::endl;
|
||||
|
@ -126,6 +131,17 @@ bool parseOptions (int argc, char** argv, Arguments &info)
|
|||
if (variables["input-file"].as< std::vector<std::string> >().size() > 2)
|
||||
info.outdir = variables["input-file"].as< std::vector<std::string> >()[2];
|
||||
}
|
||||
else if (info.mode == "add")
|
||||
{
|
||||
if (variables["input-file"].as< std::vector<std::string> >().size() < 1)
|
||||
{
|
||||
std::cout << "\nERROR: file to add unspecified\n\n"
|
||||
<< desc << std::endl;
|
||||
return false;
|
||||
}
|
||||
if (variables["input-file"].as< std::vector<std::string> >().size() > 1)
|
||||
info.addfile = variables["input-file"].as< std::vector<std::string> >()[1];
|
||||
}
|
||||
else if (variables["input-file"].as< std::vector<std::string> >().size() > 1)
|
||||
info.outdir = variables["input-file"].as< std::vector<std::string> >()[1];
|
||||
|
||||
|
@ -138,6 +154,7 @@ bool parseOptions (int argc, char** argv, Arguments &info)
|
|||
int list(std::unique_ptr<Bsa::BSAFile>& bsa, Arguments& info);
|
||||
int extract(std::unique_ptr<Bsa::BSAFile>& bsa, Arguments& info);
|
||||
int extractAll(std::unique_ptr<Bsa::BSAFile>& bsa, Arguments& info);
|
||||
int add(std::unique_ptr<Bsa::BSAFile>& bsa, Arguments& info);
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
|
@ -157,6 +174,12 @@ int main(int argc, char** argv)
|
|||
else
|
||||
bsa = std::make_unique<Bsa::BSAFile>(Bsa::BSAFile());
|
||||
|
||||
if (info.mode == "create")
|
||||
{
|
||||
bsa->open(info.filename);
|
||||
return 0;
|
||||
}
|
||||
|
||||
bsa->open(info.filename);
|
||||
|
||||
if (info.mode == "list")
|
||||
|
@ -165,6 +188,8 @@ int main(int argc, char** argv)
|
|||
return extract(bsa, info);
|
||||
else if (info.mode == "extractall")
|
||||
return extractAll(bsa, info);
|
||||
else if (info.mode == "add")
|
||||
return add(bsa, info);
|
||||
else
|
||||
{
|
||||
std::cout << "Unsupported mode. That is not supposed to happen." << std::endl;
|
||||
|
@ -188,13 +213,13 @@ int list(std::unique_ptr<Bsa::BSAFile>& bsa, Arguments& info)
|
|||
{
|
||||
// Long format
|
||||
std::ios::fmtflags f(std::cout.flags());
|
||||
std::cout << std::setw(50) << std::left << file.name;
|
||||
std::cout << std::setw(50) << std::left << file.name();
|
||||
std::cout << std::setw(8) << std::left << std::dec << file.fileSize;
|
||||
std::cout << "@ 0x" << std::hex << file.offset << std::endl;
|
||||
std::cout.flags(f);
|
||||
}
|
||||
else
|
||||
std::cout << file.name << std::endl;
|
||||
std::cout << file.name() << std::endl;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -253,7 +278,7 @@ int extractAll(std::unique_ptr<Bsa::BSAFile>& bsa, Arguments& info)
|
|||
{
|
||||
for (const auto &file : bsa->getList())
|
||||
{
|
||||
std::string extractPath(file.name);
|
||||
std::string extractPath(file.name());
|
||||
Misc::StringUtils::replaceAll(extractPath, "\\", "/");
|
||||
|
||||
// Get the target path (the path the file will be extracted to)
|
||||
|
@ -272,7 +297,7 @@ int extractAll(std::unique_ptr<Bsa::BSAFile>& bsa, Arguments& info)
|
|||
|
||||
// Get a stream for the file to extract
|
||||
// (inefficient because getFile iter on the list again)
|
||||
Files::IStreamPtr data = bsa->getFile(file.name);
|
||||
Files::IStreamPtr data = bsa->getFile(file.name());
|
||||
bfs::ofstream out(target, std::ios::binary);
|
||||
|
||||
// Write the file to disk
|
||||
|
@ -283,3 +308,11 @@ int extractAll(std::unique_ptr<Bsa::BSAFile>& bsa, Arguments& info)
|
|||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int add(std::unique_ptr<Bsa::BSAFile>& bsa, Arguments& info)
|
||||
{
|
||||
boost::filesystem::fstream stream(info.addfile, std::ios_base::binary | std::ios_base::out | std::ios_base::in);
|
||||
bsa->addFile(info.addfile, stream);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ set(LAUNCHER
|
|||
utils/profilescombobox.cpp
|
||||
utils/textinputdialog.cpp
|
||||
utils/lineedit.cpp
|
||||
utils/openalutil.cpp
|
||||
|
||||
${CMAKE_SOURCE_DIR}/files/windows/launcher.rc
|
||||
)
|
||||
|
@ -31,6 +32,7 @@ set(LAUNCHER_HEADER
|
|||
utils/profilescombobox.hpp
|
||||
utils/textinputdialog.hpp
|
||||
utils/lineedit.hpp
|
||||
utils/openalutil.hpp
|
||||
)
|
||||
|
||||
# Headers that must be pre-processed
|
||||
|
@ -47,6 +49,7 @@ set(LAUNCHER_HEADER_MOC
|
|||
utils/textinputdialog.hpp
|
||||
utils/profilescombobox.hpp
|
||||
utils/lineedit.hpp
|
||||
utils/openalutil.hpp
|
||||
|
||||
)
|
||||
|
||||
|
@ -95,6 +98,7 @@ endif (WIN32)
|
|||
|
||||
target_link_libraries(openmw-launcher
|
||||
${SDL2_LIBRARY_ONLY}
|
||||
${OPENAL_LIBRARY}
|
||||
components
|
||||
)
|
||||
|
||||
|
|
|
@ -5,11 +5,14 @@
|
|||
#include <QFileDialog>
|
||||
#include <QCompleter>
|
||||
#include <QProxyStyle>
|
||||
#include <QString>
|
||||
#include <components/contentselector/view/contentselector.hpp>
|
||||
#include <components/contentselector/model/esmfile.hpp>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "utils/openalutil.hpp"
|
||||
|
||||
Launcher::AdvancedPage::AdvancedPage(Config::GameSettings &gameSettings,
|
||||
Settings::Manager &engineSettings, QWidget *parent)
|
||||
: QWidget(parent)
|
||||
|
@ -19,7 +22,17 @@ Launcher::AdvancedPage::AdvancedPage(Config::GameSettings &gameSettings,
|
|||
setObjectName ("AdvancedPage");
|
||||
setupUi(this);
|
||||
|
||||
for(const char * name : Launcher::enumerateOpenALDevices())
|
||||
{
|
||||
audioDeviceSelectorComboBox->addItem(QString::fromUtf8(name), QString::fromUtf8(name));
|
||||
}
|
||||
for(const char * name : Launcher::enumerateOpenALDevicesHrtf())
|
||||
{
|
||||
hrtfProfileSelectorComboBox->addItem(QString::fromUtf8(name), QString::fromUtf8(name));
|
||||
}
|
||||
|
||||
loadSettings();
|
||||
|
||||
mCellNameCompleter.setModel(&mCellNameCompleterModel);
|
||||
startDefaultCharacterAtField->setCompleter(&mCellNameCompleter);
|
||||
}
|
||||
|
@ -95,6 +108,7 @@ bool Launcher::AdvancedPage::loadSettings()
|
|||
int numPhysicsThreads = mEngineSettings.getInt("async num threads", "Physics");
|
||||
if (numPhysicsThreads >= 0)
|
||||
physicsThreadsSpinBox->setValue(numPhysicsThreads);
|
||||
loadSettingBool(allowNPCToFollowOverWaterSurfaceCheckBox, "allow actors to follow over water surface", "Game");
|
||||
}
|
||||
|
||||
// Visuals
|
||||
|
@ -126,6 +140,34 @@ bool Launcher::AdvancedPage::loadSettings()
|
|||
viewingDistanceComboBox->setValue(convertToCells(mEngineSettings.getInt("viewing distance", "Camera")));
|
||||
}
|
||||
|
||||
// Audio
|
||||
{
|
||||
std::string selectedAudioDevice = mEngineSettings.getString("device", "Sound");
|
||||
if (selectedAudioDevice.empty() == false)
|
||||
{
|
||||
int audioDeviceIndex = audioDeviceSelectorComboBox->findData(QString::fromStdString(selectedAudioDevice));
|
||||
if (audioDeviceIndex != -1)
|
||||
{
|
||||
audioDeviceSelectorComboBox->setCurrentIndex(audioDeviceIndex);
|
||||
}
|
||||
}
|
||||
int hrtfEnabledIndex = mEngineSettings.getInt("hrtf enable", "Sound");
|
||||
if (hrtfEnabledIndex >= -1 && hrtfEnabledIndex <= 1)
|
||||
{
|
||||
enableHRTFComboBox->setCurrentIndex(hrtfEnabledIndex + 1);
|
||||
}
|
||||
std::string selectedHRTFProfile = mEngineSettings.getString("hrtf", "Sound");
|
||||
if (selectedHRTFProfile.empty() == false)
|
||||
{
|
||||
int hrtfProfileIndex = hrtfProfileSelectorComboBox->findData(QString::fromStdString(selectedHRTFProfile));
|
||||
if (hrtfProfileIndex != -1)
|
||||
{
|
||||
hrtfProfileSelectorComboBox->setCurrentIndex(hrtfProfileIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Camera
|
||||
{
|
||||
loadSettingBool(viewOverShoulderCheckBox, "view over shoulder", "Camera");
|
||||
|
@ -248,6 +290,33 @@ void Launcher::AdvancedPage::saveSettings()
|
|||
}
|
||||
}
|
||||
|
||||
// Audio
|
||||
{
|
||||
int audioDeviceIndex = audioDeviceSelectorComboBox->currentIndex();
|
||||
if (audioDeviceIndex != 0)
|
||||
{
|
||||
mEngineSettings.setString("device", "Sound", audioDeviceSelectorComboBox->currentText().toUtf8().constData());
|
||||
}
|
||||
else
|
||||
{
|
||||
mEngineSettings.setString("device", "Sound", "");
|
||||
}
|
||||
int hrtfEnabledIndex = enableHRTFComboBox->currentIndex() - 1;
|
||||
if (hrtfEnabledIndex != mEngineSettings.getInt("hrtf enable", "Sound"))
|
||||
{
|
||||
mEngineSettings.setInt("hrtf enable", "Sound", hrtfEnabledIndex);
|
||||
}
|
||||
int selectedHRTFProfileIndex = hrtfProfileSelectorComboBox->currentIndex();
|
||||
if (selectedHRTFProfileIndex != 0)
|
||||
{
|
||||
mEngineSettings.setString("hrtf", "Sound", hrtfProfileSelectorComboBox->currentText().toUtf8().constData());
|
||||
}
|
||||
else
|
||||
{
|
||||
mEngineSettings.setString("hrtf", "Sound", "");
|
||||
}
|
||||
}
|
||||
|
||||
// Camera
|
||||
{
|
||||
saveSettingBool(viewOverShoulderCheckBox, "view over shoulder", "Camera");
|
||||
|
|
|
@ -20,6 +20,9 @@
|
|||
QString getAspect(int x, int y)
|
||||
{
|
||||
int gcd = std::gcd (x, y);
|
||||
if (gcd == 0)
|
||||
return QString();
|
||||
|
||||
int xaspect = x / gcd;
|
||||
int yaspect = y / gcd;
|
||||
// special case: 8 : 5 is usually referred to as 16:10
|
||||
|
@ -298,9 +301,9 @@ QStringList Launcher::GraphicsPage::getAvailableResolutions(int screen)
|
|||
return result;
|
||||
}
|
||||
|
||||
QString aspect = getAspect(mode.w, mode.h);
|
||||
QString resolution = QString::number(mode.w) + QString(" x ") + QString::number(mode.h);
|
||||
|
||||
QString aspect = getAspect(mode.w, mode.h);
|
||||
if (aspect == QLatin1String("16:9") || aspect == QLatin1String("16:10")) {
|
||||
resolution.append(tr("\t(Wide ") + aspect + ")");
|
||||
|
||||
|
|
55
apps/launcher/utils/openalutil.cpp
Normal file
55
apps/launcher/utils/openalutil.cpp
Normal file
|
@ -0,0 +1,55 @@
|
|||
#include <cstring>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
#include <apps/openmw/mwsound/alext.h>
|
||||
|
||||
#include "openalutil.hpp"
|
||||
|
||||
#ifndef ALC_ALL_DEVICES_SPECIFIER
|
||||
#define ALC_ALL_DEVICES_SPECIFIER 0x1013
|
||||
#endif
|
||||
|
||||
std::vector<const char *> Launcher::enumerateOpenALDevices()
|
||||
{
|
||||
std::vector<const char *> devlist;
|
||||
const ALCchar *devnames;
|
||||
|
||||
if(alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT"))
|
||||
{
|
||||
devnames = alcGetString(nullptr, ALC_ALL_DEVICES_SPECIFIER);
|
||||
}
|
||||
else
|
||||
{
|
||||
devnames = alcGetString(nullptr, ALC_DEVICE_SPECIFIER);
|
||||
}
|
||||
|
||||
while(devnames && *devnames)
|
||||
{
|
||||
devlist.emplace_back(devnames);
|
||||
devnames += strlen(devnames)+1;
|
||||
}
|
||||
return devlist;
|
||||
}
|
||||
|
||||
std::vector<const char *> Launcher::enumerateOpenALDevicesHrtf()
|
||||
{
|
||||
std::vector<const char *> ret;
|
||||
|
||||
ALCdevice *device = alcOpenDevice(nullptr);
|
||||
if(device && alcIsExtensionPresent(device, "ALC_SOFT_HRTF"))
|
||||
{
|
||||
LPALCGETSTRINGISOFT alcGetStringiSOFT = nullptr;
|
||||
void* funcPtr = alcGetProcAddress(device, "alcGetStringiSOFT");
|
||||
memcpy(&alcGetStringiSOFT, &funcPtr, sizeof(funcPtr));
|
||||
ALCint num_hrtf;
|
||||
alcGetIntegerv(device, ALC_NUM_HRTF_SPECIFIERS_SOFT, 1, &num_hrtf);
|
||||
ret.reserve(num_hrtf);
|
||||
for(ALCint i = 0;i < num_hrtf && i < 20;++i)
|
||||
{
|
||||
const ALCchar *entry = alcGetStringiSOFT(device, ALC_HRTF_SPECIFIER_SOFT, i);
|
||||
ret.emplace_back(entry);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
7
apps/launcher/utils/openalutil.hpp
Normal file
7
apps/launcher/utils/openalutil.hpp
Normal file
|
@ -0,0 +1,7 @@
|
|||
#include <vector>
|
||||
|
||||
namespace Launcher
|
||||
{
|
||||
std::vector<const char *> enumerateOpenALDevices();
|
||||
std::vector<const char *> enumerateOpenALDevicesHrtf();
|
||||
}
|
|
@ -34,7 +34,7 @@ namespace CSMPrefs
|
|||
void storeValue(const QKeySequence& sequence);
|
||||
void resetState();
|
||||
|
||||
static const int MaxKeys = 4;
|
||||
static constexpr int MaxKeys = 4;
|
||||
|
||||
QPushButton* mButton;
|
||||
|
||||
|
|
|
@ -56,7 +56,9 @@ void CSMWorld::PotionRefIdAdapter::setData (const RefIdColumn *column, RefIdData
|
|||
|
||||
|
||||
CSMWorld::IngredientColumns::IngredientColumns (const InventoryColumns& columns)
|
||||
: InventoryColumns (columns) {}
|
||||
: InventoryColumns (columns)
|
||||
, mEffects(nullptr)
|
||||
{}
|
||||
|
||||
CSMWorld::IngredientRefIdAdapter::IngredientRefIdAdapter (const IngredientColumns& columns)
|
||||
: InventoryRefIdAdapter<ESM::Ingredient> (UniversalId::Type_Ingredient, columns),
|
||||
|
@ -585,7 +587,13 @@ void CSMWorld::DoorRefIdAdapter::setData (const RefIdColumn *column, RefIdData&
|
|||
}
|
||||
|
||||
CSMWorld::LightColumns::LightColumns (const InventoryColumns& columns)
|
||||
: InventoryColumns (columns) {}
|
||||
: InventoryColumns (columns)
|
||||
, mTime(nullptr)
|
||||
, mRadius(nullptr)
|
||||
, mColor(nullptr)
|
||||
, mSound(nullptr)
|
||||
, mEmitterType(nullptr)
|
||||
{}
|
||||
|
||||
CSMWorld::LightRefIdAdapter::LightRefIdAdapter (const LightColumns& columns)
|
||||
: InventoryRefIdAdapter<ESM::Light> (UniversalId::Type_Light, columns), mColumns (columns)
|
||||
|
@ -1454,7 +1462,15 @@ int CSMWorld::CreatureMiscRefIdAdapter::getNestedRowsCount(const RefIdColumn *co
|
|||
}
|
||||
|
||||
CSMWorld::WeaponColumns::WeaponColumns (const EnchantableColumns& columns)
|
||||
: EnchantableColumns (columns) {}
|
||||
: EnchantableColumns (columns)
|
||||
, mType(nullptr)
|
||||
, mHealth(nullptr)
|
||||
, mSpeed(nullptr)
|
||||
, mReach(nullptr)
|
||||
, mChop{nullptr}
|
||||
, mSlash{nullptr}
|
||||
, mThrust{nullptr}
|
||||
{}
|
||||
|
||||
CSMWorld::WeaponRefIdAdapter::WeaponRefIdAdapter (const WeaponColumns& columns)
|
||||
: EnchantableRefIdAdapter<ESM::Weapon> (UniversalId::Type_Weapon, columns), mColumns (columns)
|
||||
|
|
|
@ -178,7 +178,11 @@ namespace CSMWorld
|
|||
const RefIdColumn *mName;
|
||||
const RefIdColumn *mScript;
|
||||
|
||||
NameColumns (const ModelColumns& base) : ModelColumns (base) {}
|
||||
NameColumns (const ModelColumns& base)
|
||||
: ModelColumns (base)
|
||||
, mName(nullptr)
|
||||
, mScript(nullptr)
|
||||
{}
|
||||
};
|
||||
|
||||
/// \brief Adapter for IDs with names (all but levelled lists and statics)
|
||||
|
@ -247,7 +251,12 @@ namespace CSMWorld
|
|||
const RefIdColumn *mWeight;
|
||||
const RefIdColumn *mValue;
|
||||
|
||||
InventoryColumns (const NameColumns& base) : NameColumns (base) {}
|
||||
InventoryColumns (const NameColumns& base)
|
||||
: NameColumns (base)
|
||||
, mIcon(nullptr)
|
||||
, mWeight(nullptr)
|
||||
, mValue(nullptr)
|
||||
{}
|
||||
};
|
||||
|
||||
/// \brief Adapter for IDs that can go into an inventory
|
||||
|
@ -405,7 +414,11 @@ namespace CSMWorld
|
|||
const RefIdColumn *mEnchantment;
|
||||
const RefIdColumn *mEnchantmentPoints;
|
||||
|
||||
EnchantableColumns (const InventoryColumns& base) : InventoryColumns (base) {}
|
||||
EnchantableColumns (const InventoryColumns& base)
|
||||
: InventoryColumns (base)
|
||||
, mEnchantment(nullptr)
|
||||
, mEnchantmentPoints(nullptr)
|
||||
{}
|
||||
};
|
||||
|
||||
/// \brief Adapter for enchantable IDs
|
||||
|
@ -474,7 +487,11 @@ namespace CSMWorld
|
|||
const RefIdColumn *mQuality;
|
||||
const RefIdColumn *mUses;
|
||||
|
||||
ToolColumns (const InventoryColumns& base) : InventoryColumns (base) {}
|
||||
ToolColumns (const InventoryColumns& base)
|
||||
: InventoryColumns (base)
|
||||
, mQuality(nullptr)
|
||||
, mUses(nullptr)
|
||||
{}
|
||||
};
|
||||
|
||||
/// \brief Adapter for tools with limited uses IDs (lockpick, repair, probes)
|
||||
|
@ -549,7 +566,17 @@ namespace CSMWorld
|
|||
const RefIdColumn *mAiPackages;
|
||||
std::map<const RefIdColumn *, unsigned int> mServices;
|
||||
|
||||
ActorColumns (const NameColumns& base) : NameColumns (base) {}
|
||||
ActorColumns (const NameColumns& base)
|
||||
: NameColumns (base)
|
||||
, mHello(nullptr)
|
||||
, mFlee(nullptr)
|
||||
, mFight(nullptr)
|
||||
, mAlarm(nullptr)
|
||||
, mInventory(nullptr)
|
||||
, mSpells(nullptr)
|
||||
, mDestinations(nullptr)
|
||||
, mAiPackages(nullptr)
|
||||
{}
|
||||
};
|
||||
|
||||
/// \brief Adapter for actor IDs (handles common AI functionality)
|
||||
|
@ -2054,7 +2081,11 @@ namespace CSMWorld
|
|||
const RefIdColumn *mLevList;
|
||||
const RefIdColumn *mNestedListLevList;
|
||||
|
||||
LevListColumns (const BaseColumns& base) : BaseColumns (base) {}
|
||||
LevListColumns (const BaseColumns& base)
|
||||
: BaseColumns (base)
|
||||
, mLevList(nullptr)
|
||||
, mNestedListLevList(nullptr)
|
||||
{}
|
||||
};
|
||||
|
||||
template<typename RecordT>
|
||||
|
|
|
@ -33,26 +33,6 @@ namespace Compiler
|
|||
class Context;
|
||||
}
|
||||
|
||||
namespace MWScript
|
||||
{
|
||||
class ScriptManager;
|
||||
}
|
||||
|
||||
namespace MWSound
|
||||
{
|
||||
class SoundManager;
|
||||
}
|
||||
|
||||
namespace MWWorld
|
||||
{
|
||||
class World;
|
||||
}
|
||||
|
||||
namespace MWGui
|
||||
{
|
||||
class WindowManager;
|
||||
}
|
||||
|
||||
namespace Files
|
||||
{
|
||||
struct ConfigurationManager;
|
||||
|
|
|
@ -32,7 +32,6 @@ namespace MyGUI
|
|||
|
||||
namespace ESM
|
||||
{
|
||||
struct Class;
|
||||
class ESMReader;
|
||||
class ESMWriter;
|
||||
struct CellId;
|
||||
|
|
|
@ -401,13 +401,13 @@ namespace MWBase
|
|||
virtual void deleteObject (const MWWorld::Ptr& ptr) = 0;
|
||||
virtual void undeleteObject (const MWWorld::Ptr& ptr) = 0;
|
||||
|
||||
virtual MWWorld::Ptr moveObject (const MWWorld::Ptr& ptr, float x, float y, float z, bool moveToActive=false) = 0;
|
||||
virtual MWWorld::Ptr moveObject (const MWWorld::Ptr& ptr, float x, float y, float z, bool movePhysics=true, bool moveToActive=false) = 0;
|
||||
///< @return an updated Ptr in case the Ptr's cell changes
|
||||
|
||||
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;
|
||||
virtual MWWorld::Ptr moveObjectBy(const MWWorld::Ptr &ptr, osg::Vec3f vec, bool moveToActive) = 0;
|
||||
///< @return an updated Ptr
|
||||
|
||||
virtual void scaleObject (const MWWorld::Ptr& ptr, float scale) = 0;
|
||||
|
@ -587,6 +587,8 @@ namespace MWBase
|
|||
virtual void applyDeferredPreviewRotationToPlayer(float dt) = 0;
|
||||
virtual void disableDeferredPreviewRotation() = 0;
|
||||
|
||||
virtual void saveLoaded() = 0;
|
||||
|
||||
virtual void setupPlayer() = 0;
|
||||
virtual void renderPlayer() = 0;
|
||||
|
||||
|
@ -799,7 +801,7 @@ namespace MWBase
|
|||
|
||||
/// Return a vector aiming the actor's weapon towards a target.
|
||||
/// @note The length of the vector is the distance between actor and target.
|
||||
virtual osg::Vec3f aimToTarget(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target) = 0;
|
||||
virtual osg::Vec3f aimToTarget(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target, bool isRangedCombat) = 0;
|
||||
|
||||
/// Return the distance between actor's weapon and target's collision box.
|
||||
virtual float getHitDistance(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target) = 0;
|
||||
|
|
|
@ -58,11 +58,6 @@ namespace MWClass
|
|||
mStore.readState(inventory);
|
||||
}
|
||||
|
||||
MWWorld::CustomData *ContainerCustomData::clone() const
|
||||
{
|
||||
return new ContainerCustomData (*this);
|
||||
}
|
||||
|
||||
ContainerCustomData& ContainerCustomData::asContainerCustomData()
|
||||
{
|
||||
return *this;
|
||||
|
@ -84,7 +79,7 @@ namespace MWClass
|
|||
MWWorld::LiveCellRef<ESM::Container> *ref = ptr.get<ESM::Container>();
|
||||
|
||||
// store
|
||||
ptr.getRefData().setCustomData (std::make_unique<ContainerCustomData>(*ref->mBase, ptr.getCell()).release());
|
||||
ptr.getRefData().setCustomData (std::make_unique<ContainerCustomData>(*ref->mBase, ptr.getCell()));
|
||||
|
||||
MWBase::Environment::get().getWorld()->addContainerScripts(ptr, ptr.getCell());
|
||||
}
|
||||
|
@ -381,7 +376,7 @@ namespace MWClass
|
|||
return;
|
||||
|
||||
const ESM::ContainerState& containerState = state.asContainerState();
|
||||
ptr.getRefData().setCustomData(std::make_unique<ContainerCustomData>(containerState.mInventory).release());
|
||||
ptr.getRefData().setCustomData(std::make_unique<ContainerCustomData>(containerState.mInventory));
|
||||
}
|
||||
|
||||
void Container::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const
|
||||
|
|
|
@ -13,15 +13,13 @@ namespace ESM
|
|||
|
||||
namespace MWClass
|
||||
{
|
||||
class ContainerCustomData : public MWWorld::CustomData
|
||||
class ContainerCustomData : public MWWorld::TypedCustomData<ContainerCustomData>
|
||||
{
|
||||
MWWorld::ContainerStore mStore;
|
||||
public:
|
||||
ContainerCustomData(const ESM::Container& container, MWWorld::CellStore* cell);
|
||||
ContainerCustomData(const ESM::InventoryState& inventory);
|
||||
|
||||
MWWorld::CustomData *clone() const override;
|
||||
|
||||
ContainerCustomData& asContainerCustomData() override;
|
||||
const ContainerCustomData& asContainerCustomData() const override;
|
||||
|
||||
|
|
|
@ -68,14 +68,16 @@ namespace
|
|||
namespace MWClass
|
||||
{
|
||||
|
||||
class CreatureCustomData : public MWWorld::CustomData
|
||||
class CreatureCustomData : public MWWorld::TypedCustomData<CreatureCustomData>
|
||||
{
|
||||
public:
|
||||
MWMechanics::CreatureStats mCreatureStats;
|
||||
MWWorld::ContainerStore* mContainerStore; // may be InventoryStore for some creatures
|
||||
std::unique_ptr<MWWorld::ContainerStore> mContainerStore; // may be InventoryStore for some creatures
|
||||
MWMechanics::Movement mMovement;
|
||||
|
||||
MWWorld::CustomData *clone() const override;
|
||||
CreatureCustomData() = default;
|
||||
CreatureCustomData(const CreatureCustomData& other);
|
||||
CreatureCustomData(CreatureCustomData&& other) noexcept = default;
|
||||
|
||||
CreatureCustomData& asCreatureCustomData() override
|
||||
{
|
||||
|
@ -85,16 +87,13 @@ namespace MWClass
|
|||
{
|
||||
return *this;
|
||||
}
|
||||
|
||||
CreatureCustomData() : mContainerStore(nullptr) {}
|
||||
virtual ~CreatureCustomData() { delete mContainerStore; }
|
||||
};
|
||||
|
||||
MWWorld::CustomData *CreatureCustomData::clone() const
|
||||
CreatureCustomData::CreatureCustomData(const CreatureCustomData& other)
|
||||
: mCreatureStats(other.mCreatureStats),
|
||||
mContainerStore(other.mContainerStore->clone()),
|
||||
mMovement(other.mMovement)
|
||||
{
|
||||
CreatureCustomData* cloned = new CreatureCustomData (*this);
|
||||
cloned->mContainerStore = mContainerStore->clone();
|
||||
return cloned;
|
||||
}
|
||||
|
||||
const Creature::GMST& Creature::getGmst()
|
||||
|
@ -165,16 +164,16 @@ namespace MWClass
|
|||
// inventory
|
||||
bool hasInventory = hasInventoryStore(ptr);
|
||||
if (hasInventory)
|
||||
data->mContainerStore = new MWWorld::InventoryStore();
|
||||
data->mContainerStore = std::make_unique<MWWorld::InventoryStore>();
|
||||
else
|
||||
data->mContainerStore = new MWWorld::ContainerStore();
|
||||
data->mContainerStore = std::make_unique<MWWorld::ContainerStore>();
|
||||
|
||||
data->mCreatureStats.setGoldPool(ref->mBase->mData.mGold);
|
||||
|
||||
data->mCreatureStats.setNeedRecalcDynamicStats(false);
|
||||
|
||||
// store
|
||||
ptr.getRefData().setCustomData(data.release());
|
||||
ptr.getRefData().setCustomData(std::move(data));
|
||||
|
||||
getContainerStore(ptr).fill(ref->mBase->mInventory, ptr.getCellRef().getRefId());
|
||||
|
||||
|
@ -946,11 +945,11 @@ namespace MWClass
|
|||
std::unique_ptr<CreatureCustomData> data (new CreatureCustomData);
|
||||
|
||||
if (hasInventoryStore(ptr))
|
||||
data->mContainerStore = new MWWorld::InventoryStore();
|
||||
data->mContainerStore = std::make_unique<MWWorld::InventoryStore>();
|
||||
else
|
||||
data->mContainerStore = new MWWorld::ContainerStore();
|
||||
data->mContainerStore = std::make_unique<MWWorld::ContainerStore>();
|
||||
|
||||
ptr.getRefData().setCustomData (data.release());
|
||||
ptr.getRefData().setCustomData (std::move(data));
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
|
@ -10,15 +10,13 @@
|
|||
|
||||
namespace MWClass
|
||||
{
|
||||
class CreatureLevListCustomData : public MWWorld::CustomData
|
||||
class CreatureLevListCustomData : public MWWorld::TypedCustomData<CreatureLevListCustomData>
|
||||
{
|
||||
public:
|
||||
// actorId of the creature we spawned
|
||||
int mSpawnActorId;
|
||||
bool mSpawn; // Should a new creature be spawned?
|
||||
|
||||
MWWorld::CustomData *clone() const override;
|
||||
|
||||
CreatureLevListCustomData& asCreatureLevListCustomData() override
|
||||
{
|
||||
return *this;
|
||||
|
@ -29,11 +27,6 @@ namespace MWClass
|
|||
}
|
||||
};
|
||||
|
||||
MWWorld::CustomData *CreatureLevListCustomData::clone() const
|
||||
{
|
||||
return new CreatureLevListCustomData (*this);
|
||||
}
|
||||
|
||||
std::string CreatureLevList::getName (const MWWorld::ConstPtr& ptr) const
|
||||
{
|
||||
return "";
|
||||
|
@ -149,11 +142,11 @@ namespace MWClass
|
|||
{
|
||||
if (!ptr.getRefData().getCustomData())
|
||||
{
|
||||
std::unique_ptr<CreatureLevListCustomData> data (new CreatureLevListCustomData);
|
||||
std::unique_ptr<CreatureLevListCustomData> data = std::make_unique<CreatureLevListCustomData>();
|
||||
data->mSpawnActorId = -1;
|
||||
data->mSpawn = true;
|
||||
|
||||
ptr.getRefData().setCustomData(data.release());
|
||||
ptr.getRefData().setCustomData(std::move(data));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -43,13 +43,11 @@
|
|||
|
||||
namespace MWClass
|
||||
{
|
||||
class DoorCustomData : public MWWorld::CustomData
|
||||
class DoorCustomData : public MWWorld::TypedCustomData<DoorCustomData>
|
||||
{
|
||||
public:
|
||||
MWWorld::DoorState mDoorState = MWWorld::DoorState::Idle;
|
||||
|
||||
MWWorld::CustomData *clone() const override;
|
||||
|
||||
DoorCustomData& asDoorCustomData() override
|
||||
{
|
||||
return *this;
|
||||
|
@ -60,11 +58,6 @@ namespace MWClass
|
|||
}
|
||||
};
|
||||
|
||||
MWWorld::CustomData *DoorCustomData::clone() const
|
||||
{
|
||||
return new DoorCustomData (*this);
|
||||
}
|
||||
|
||||
void Door::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const
|
||||
{
|
||||
if (!model.empty())
|
||||
|
@ -419,8 +412,7 @@ namespace MWClass
|
|||
{
|
||||
if (!ptr.getRefData().getCustomData())
|
||||
{
|
||||
std::unique_ptr<DoorCustomData> data(new DoorCustomData);
|
||||
ptr.getRefData().setCustomData(data.release());
|
||||
ptr.getRefData().setCustomData(std::make_unique<DoorCustomData>());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -263,15 +263,13 @@ namespace
|
|||
namespace MWClass
|
||||
{
|
||||
|
||||
class NpcCustomData : public MWWorld::CustomData
|
||||
class NpcCustomData : public MWWorld::TypedCustomData<NpcCustomData>
|
||||
{
|
||||
public:
|
||||
MWMechanics::NpcStats mNpcStats;
|
||||
MWMechanics::Movement mMovement;
|
||||
MWWorld::InventoryStore mInventoryStore;
|
||||
|
||||
MWWorld::CustomData *clone() const override;
|
||||
|
||||
NpcCustomData& asNpcCustomData() override
|
||||
{
|
||||
return *this;
|
||||
|
@ -282,11 +280,6 @@ namespace MWClass
|
|||
}
|
||||
};
|
||||
|
||||
MWWorld::CustomData *NpcCustomData::clone() const
|
||||
{
|
||||
return new NpcCustomData (*this);
|
||||
}
|
||||
|
||||
const Npc::GMST& Npc::getGmst()
|
||||
{
|
||||
static GMST gmst;
|
||||
|
@ -414,7 +407,7 @@ namespace MWClass
|
|||
data->mNpcStats.setGoldPool(gold);
|
||||
|
||||
// store
|
||||
ptr.getRefData().setCustomData (data.release());
|
||||
ptr.getRefData().setCustomData(std::move(data));
|
||||
|
||||
getInventoryStore(ptr).autoEquip(ptr);
|
||||
}
|
||||
|
@ -1525,8 +1518,7 @@ namespace MWClass
|
|||
if (!ptr.getRefData().getCustomData())
|
||||
{
|
||||
// Create a CustomData, but don't fill it from ESM records (not needed)
|
||||
std::unique_ptr<NpcCustomData> data (new NpcCustomData);
|
||||
ptr.getRefData().setCustomData (data.release());
|
||||
ptr.getRefData().setCustomData(std::make_unique<NpcCustomData>());
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
#include "bookpage.hpp"
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "MyGUI_RenderItem.h"
|
||||
#include "MyGUI_RenderManager.h"
|
||||
#include "MyGUI_TextureUtility.h"
|
||||
|
@ -894,6 +896,27 @@ protected:
|
|||
return mIsPageReset || (mPage != page);
|
||||
}
|
||||
|
||||
std::optional<MyGUI::IntPoint> getAdjustedPos(int left, int top, bool move = false)
|
||||
{
|
||||
if (!mBook)
|
||||
return {};
|
||||
|
||||
if (mPage >= mBook->mPages.size())
|
||||
return {};
|
||||
|
||||
MyGUI::IntPoint pos (left, top);
|
||||
#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3)
|
||||
// work around inconsistency in MyGUI where the mouse press coordinates aren't
|
||||
// transformed by the current Layer (even though mouse *move* events are).
|
||||
if(!move)
|
||||
pos = mNode->getLayer()->getPosition(left, top);
|
||||
#endif
|
||||
pos.left -= mCroppedParent->getAbsoluteLeft ();
|
||||
pos.top -= mCroppedParent->getAbsoluteTop ();
|
||||
pos.top += mViewTop;
|
||||
return pos;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
typedef TypesetBookImpl::StyleImpl Style;
|
||||
|
@ -952,16 +975,10 @@ public:
|
|||
|
||||
void onMouseMove (int left, int top)
|
||||
{
|
||||
if (!mBook)
|
||||
return;
|
||||
|
||||
if (mPage >= mBook->mPages.size())
|
||||
return;
|
||||
|
||||
left -= mCroppedParent->getAbsoluteLeft ();
|
||||
top -= mCroppedParent->getAbsoluteTop ();
|
||||
|
||||
Style * hit = mBook->hitTestWithMargin (left, mViewTop + top);
|
||||
Style * hit = nullptr;
|
||||
if(auto pos = getAdjustedPos(left, top, true))
|
||||
if(pos->top <= mViewBottom)
|
||||
hit = mBook->hitTestWithMargin (pos->left, pos->top);
|
||||
|
||||
if (mLastDown == MyGUI::MouseButton::None)
|
||||
{
|
||||
|
@ -991,24 +1008,11 @@ public:
|
|||
|
||||
void onMouseButtonPressed (int left, int top, MyGUI::MouseButton id)
|
||||
{
|
||||
if (!mBook)
|
||||
return;
|
||||
auto pos = getAdjustedPos(left, top);
|
||||
|
||||
if (mPage >= mBook->mPages.size())
|
||||
return;
|
||||
|
||||
// work around inconsistency in MyGUI where the mouse press coordinates aren't
|
||||
// transformed by the current Layer (even though mouse *move* events are).
|
||||
MyGUI::IntPoint pos (left, top);
|
||||
#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3)
|
||||
pos = mNode->getLayer()->getPosition(left, top);
|
||||
#endif
|
||||
pos.left -= mCroppedParent->getAbsoluteLeft ();
|
||||
pos.top -= mCroppedParent->getAbsoluteTop ();
|
||||
|
||||
if (mLastDown == MyGUI::MouseButton::None)
|
||||
if (pos && mLastDown == MyGUI::MouseButton::None)
|
||||
{
|
||||
mFocusItem = mBook->hitTestWithMargin (pos.left, mViewTop + pos.top);
|
||||
mFocusItem = pos->top <= mViewBottom ? mBook->hitTestWithMargin (pos->left, pos->top) : nullptr;
|
||||
mItemActive = true;
|
||||
|
||||
dirtyFocusItem ();
|
||||
|
@ -1019,25 +1023,11 @@ public:
|
|||
|
||||
void onMouseButtonReleased(int left, int top, MyGUI::MouseButton id)
|
||||
{
|
||||
if (!mBook)
|
||||
return;
|
||||
auto pos = getAdjustedPos(left, top);
|
||||
|
||||
if (mPage >= mBook->mPages.size())
|
||||
return;
|
||||
|
||||
// work around inconsistency in MyGUI where the mouse release coordinates aren't
|
||||
// transformed by the current Layer (even though mouse *move* events are).
|
||||
MyGUI::IntPoint pos (left, top);
|
||||
#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3)
|
||||
pos = mNode->getLayer()->getPosition(left, top);
|
||||
#endif
|
||||
|
||||
pos.left -= mCroppedParent->getAbsoluteLeft ();
|
||||
pos.top -= mCroppedParent->getAbsoluteTop ();
|
||||
|
||||
if (mLastDown == id)
|
||||
if (pos && mLastDown == id)
|
||||
{
|
||||
Style * item = mBook->hitTestWithMargin (pos.left, mViewTop + pos.top);
|
||||
Style * item = pos->top <= mViewBottom ? mBook->hitTestWithMargin (pos->left, pos->top) : nullptr;
|
||||
|
||||
bool clicked = mFocusItem == item;
|
||||
|
||||
|
|
|
@ -6,11 +6,6 @@
|
|||
|
||||
#include "itemmodel.hpp"
|
||||
|
||||
namespace MWWorld
|
||||
{
|
||||
class Environment;
|
||||
}
|
||||
|
||||
namespace MyGUI
|
||||
{
|
||||
class Gui;
|
||||
|
@ -19,7 +14,6 @@ namespace MyGUI
|
|||
|
||||
namespace MWGui
|
||||
{
|
||||
class WindowManager;
|
||||
class ContainerWindow;
|
||||
class ItemView;
|
||||
class SortFilterItemModel;
|
||||
|
|
|
@ -15,11 +15,6 @@ namespace Gui
|
|||
class MWList;
|
||||
}
|
||||
|
||||
namespace MWGui
|
||||
{
|
||||
class WindowManager;
|
||||
}
|
||||
|
||||
namespace MWGui
|
||||
{
|
||||
class ResponseCallback;
|
||||
|
|
|
@ -85,7 +85,7 @@ namespace MWGui
|
|||
, mUpdateTimer(0.f)
|
||||
{
|
||||
float uiScale = Settings::Manager::getFloat("scaling factor", "GUI");
|
||||
if (uiScale > 1.0)
|
||||
if (uiScale > 0.f)
|
||||
mScaleFactor = uiScale;
|
||||
|
||||
mPreviewTexture.reset(new osgMyGUI::OSGTexture(mPreview->getTexture()));
|
||||
|
|
|
@ -7,11 +7,6 @@
|
|||
#include <MyGUI_RenderManager.h>
|
||||
|
||||
|
||||
namespace MWGui
|
||||
{
|
||||
class WindowManager;
|
||||
}
|
||||
|
||||
namespace MWRender
|
||||
{
|
||||
class RaceSelectionPreview;
|
||||
|
|
|
@ -11,11 +11,6 @@ namespace ESM
|
|||
struct Spell;
|
||||
}
|
||||
|
||||
namespace MWGui
|
||||
{
|
||||
class WindowManager;
|
||||
}
|
||||
|
||||
namespace MWGui
|
||||
{
|
||||
class ReviewDialog : public WindowModal
|
||||
|
|
|
@ -61,6 +61,9 @@ namespace
|
|||
std::string getAspect (int x, int y)
|
||||
{
|
||||
int gcd = std::gcd (x, y);
|
||||
if (gcd == 0)
|
||||
return std::string();
|
||||
|
||||
int xaspect = x / gcd;
|
||||
int yaspect = y / gcd;
|
||||
// special case: 8 : 5 is usually referred to as 16:10
|
||||
|
@ -264,8 +267,10 @@ namespace MWGui
|
|||
std::sort(resolutions.begin(), resolutions.end(), sortResolutions);
|
||||
for (std::pair<int, int>& resolution : resolutions)
|
||||
{
|
||||
std::string str = MyGUI::utility::toString(resolution.first) + " x " + MyGUI::utility::toString(resolution.second)
|
||||
+ " (" + getAspect(resolution.first, resolution.second) + ")";
|
||||
std::string str = MyGUI::utility::toString(resolution.first) + " x " + MyGUI::utility::toString(resolution.second);
|
||||
std::string aspect = getAspect(resolution.first, resolution.second);
|
||||
if (!aspect.empty())
|
||||
str = str + " (" + aspect + ")";
|
||||
|
||||
if (mResolutionList->findItemIndexWith(str) == MyGUI::ITEM_NONE)
|
||||
mResolutionList->addItem(str);
|
||||
|
|
|
@ -3,11 +3,6 @@
|
|||
|
||||
#include "windowbase.hpp"
|
||||
|
||||
namespace MWGui
|
||||
{
|
||||
class WindowManager;
|
||||
}
|
||||
|
||||
namespace MWGui
|
||||
{
|
||||
class SettingsWindow : public WindowBase
|
||||
|
|
|
@ -35,20 +35,20 @@ namespace MWGui
|
|||
bool onDropItem(const MWWorld::Ptr &item, int count) override;
|
||||
bool onTakeItem(const MWWorld::Ptr &item, int count) override;
|
||||
|
||||
static const int Category_Weapon = (1<<1);
|
||||
static const int Category_Apparel = (1<<2);
|
||||
static const int Category_Misc = (1<<3);
|
||||
static const int Category_Magic = (1<<4);
|
||||
static const int Category_All = 255;
|
||||
static constexpr int Category_Weapon = (1<<1);
|
||||
static constexpr int Category_Apparel = (1<<2);
|
||||
static constexpr int Category_Misc = (1<<3);
|
||||
static constexpr int Category_Magic = (1<<4);
|
||||
static constexpr int Category_All = 255;
|
||||
|
||||
static const int Filter_OnlyIngredients = (1<<0);
|
||||
static const int Filter_OnlyEnchanted = (1<<1);
|
||||
static const int Filter_OnlyEnchantable = (1<<2);
|
||||
static const int Filter_OnlyChargedSoulstones = (1<<3);
|
||||
static const int Filter_OnlyUsableItems = (1<<4); // Only items with a Use action
|
||||
static const int Filter_OnlyRepairable = (1<<5);
|
||||
static const int Filter_OnlyRechargable = (1<<6);
|
||||
static const int Filter_OnlyRepairTools = (1<<7);
|
||||
static constexpr int Filter_OnlyIngredients = (1<<0);
|
||||
static constexpr int Filter_OnlyEnchanted = (1<<1);
|
||||
static constexpr int Filter_OnlyEnchantable = (1<<2);
|
||||
static constexpr int Filter_OnlyChargedSoulstones = (1<<3);
|
||||
static constexpr int Filter_OnlyUsableItems = (1<<4); // Only items with a Use action
|
||||
static constexpr int Filter_OnlyRepairable = (1<<5);
|
||||
static constexpr int Filter_OnlyRechargable = (1<<6);
|
||||
static constexpr int Filter_OnlyRepairTools = (1<<7);
|
||||
|
||||
|
||||
private:
|
||||
|
|
|
@ -15,11 +15,6 @@ namespace MyGUI
|
|||
class Widget;
|
||||
}
|
||||
|
||||
namespace MWGui
|
||||
{
|
||||
class WindowManager;
|
||||
}
|
||||
|
||||
namespace MWGui
|
||||
{
|
||||
class SpellBuyingWindow : public ReferenceInterface, public WindowBase
|
||||
|
|
|
@ -405,7 +405,8 @@ namespace MWGui
|
|||
MWWorld::Ptr player = MWMechanics::getPlayer();
|
||||
int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId);
|
||||
|
||||
if (MyGUI::utility::parseInt(mPriceLabel->getCaption()) > playerGold)
|
||||
int price = MyGUI::utility::parseInt(mPriceLabel->getCaption());
|
||||
if (price > playerGold)
|
||||
{
|
||||
MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage18}");
|
||||
return;
|
||||
|
@ -413,8 +414,6 @@ namespace MWGui
|
|||
|
||||
mSpell.mName = mNameEdit->getCaption();
|
||||
|
||||
int price = MyGUI::utility::parseInt(mPriceLabel->getCaption());
|
||||
|
||||
player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player);
|
||||
|
||||
// add gold to NPC trading gold pool
|
||||
|
|
|
@ -6,8 +6,6 @@
|
|||
|
||||
namespace MWGui
|
||||
{
|
||||
class WindowManager;
|
||||
|
||||
class StatsWindow : public WindowPinnableBase, public NoDrop, public StatsListener
|
||||
{
|
||||
public:
|
||||
|
|
|
@ -3,11 +3,6 @@
|
|||
|
||||
#include "windowbase.hpp"
|
||||
|
||||
namespace MWGui
|
||||
{
|
||||
class WindowManager;
|
||||
}
|
||||
|
||||
namespace MWGui
|
||||
{
|
||||
class TextInputDialog : public WindowModal
|
||||
|
|
|
@ -58,12 +58,12 @@ namespace MWGui
|
|||
}
|
||||
}
|
||||
|
||||
int TimeAdvancer::getHours()
|
||||
int TimeAdvancer::getHours() const
|
||||
{
|
||||
return mHours;
|
||||
}
|
||||
|
||||
bool TimeAdvancer::isRunning()
|
||||
bool TimeAdvancer::isRunning() const
|
||||
{
|
||||
return mRunning;
|
||||
}
|
||||
|
|
|
@ -14,8 +14,8 @@ namespace MWGui
|
|||
void stop();
|
||||
void onFrame(float dt);
|
||||
|
||||
int getHours();
|
||||
bool isRunning();
|
||||
int getHours() const;
|
||||
bool isRunning() const;
|
||||
|
||||
// signals
|
||||
typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void;
|
||||
|
|
|
@ -11,12 +11,6 @@ namespace MyGUI
|
|||
class Widget;
|
||||
}
|
||||
|
||||
namespace MWGui
|
||||
{
|
||||
class WindowManager;
|
||||
}
|
||||
|
||||
|
||||
namespace MWGui
|
||||
{
|
||||
class TravelWindow : public ReferenceInterface, public WindowBase
|
||||
|
|
|
@ -268,7 +268,7 @@ namespace MWGui
|
|||
void initialiseOverride() override;
|
||||
|
||||
private:
|
||||
static const int sIconOffset = 24;
|
||||
static constexpr int sIconOffset = 24;
|
||||
|
||||
void updateWidgets();
|
||||
|
||||
|
|
|
@ -3,11 +3,6 @@
|
|||
|
||||
#include "layout.hpp"
|
||||
|
||||
namespace MWBase
|
||||
{
|
||||
class WindowManager;
|
||||
}
|
||||
|
||||
namespace MWWorld
|
||||
{
|
||||
class Ptr;
|
||||
|
@ -15,7 +10,6 @@ namespace MWWorld
|
|||
|
||||
namespace MWGui
|
||||
{
|
||||
class WindowManager;
|
||||
class DragAndDrop;
|
||||
|
||||
class WindowBase: public Layout
|
||||
|
|
|
@ -114,7 +114,6 @@ namespace MWGui
|
|||
class TrainingWindow;
|
||||
class SpellIcons;
|
||||
class MerchantRepair;
|
||||
class Repair;
|
||||
class SoulgemDialog;
|
||||
class Recharge;
|
||||
class CompanionWindow;
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
|
||||
namespace MWGui
|
||||
{
|
||||
class WindowManager;
|
||||
|
||||
class WindowPinnableBase: public WindowBase
|
||||
{
|
||||
public:
|
||||
|
|
|
@ -70,7 +70,7 @@ namespace MWInput
|
|||
}
|
||||
|
||||
float uiScale = Settings::Manager::getFloat("scaling factor", "GUI");
|
||||
if (uiScale != 0.f)
|
||||
if (uiScale > 0.f)
|
||||
mInvUiScalingFactor = 1.f / uiScale;
|
||||
|
||||
float deadZoneRadius = Settings::Manager::getFloat("joystick dead zone", "Input");
|
||||
|
|
|
@ -37,7 +37,7 @@ namespace MWInput
|
|||
, mGuiCursorEnabled(true)
|
||||
{
|
||||
float uiScale = Settings::Manager::getFloat("scaling factor", "GUI");
|
||||
if (uiScale != 0.f)
|
||||
if (uiScale > 0.f)
|
||||
mInvUiScalingFactor = 1.f / uiScale;
|
||||
|
||||
int w,h;
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
|
||||
#include "../mwmechanics/actorutil.hpp"
|
||||
|
||||
#include <components/misc/timer.hpp>
|
||||
|
||||
namespace MWRender
|
||||
{
|
||||
class Animation;
|
||||
|
@ -41,12 +43,18 @@ namespace MWMechanics
|
|||
bool isTurningToPlayer() const;
|
||||
void setTurningToPlayer(bool turning);
|
||||
|
||||
Misc::TimerStatus updateEngageCombatTimer(float duration)
|
||||
{
|
||||
return mEngageCombat.update(duration);
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<CharacterController> mCharacterController;
|
||||
int mGreetingTimer{0};
|
||||
float mTargetAngleRadians{0.f};
|
||||
GreetingState mGreetingState{Greet_None};
|
||||
bool mIsTurningToPlayer{false};
|
||||
Misc::DeviatingPeriodicTimer mEngageCombat{1.0f, 0.25f, Misc::Rng::deviate(0, 0.25f)};
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -379,7 +379,11 @@ namespace MWMechanics
|
|||
if (actor != MWMechanics::getPlayer())
|
||||
return;
|
||||
|
||||
MWWorld::Ptr newItem = *store.getSlot(slot);
|
||||
MWWorld::Ptr newItem;
|
||||
auto it = store.getSlot(slot);
|
||||
// Equip can fail because beast races cannot equip boots/helmets
|
||||
if(it != store.end())
|
||||
newItem = *it;
|
||||
|
||||
if (newItem.isEmpty() || boundPtr != newItem)
|
||||
return;
|
||||
|
@ -2022,14 +2026,11 @@ namespace MWMechanics
|
|||
{
|
||||
if(!paused)
|
||||
{
|
||||
static float timerUpdateAITargets = 0;
|
||||
static float timerUpdateHeadTrack = 0;
|
||||
static float timerUpdateEquippedLight = 0;
|
||||
static float timerUpdateHello = 0;
|
||||
const float updateEquippedLightInterval = 1.0f;
|
||||
|
||||
// target lists get updated once every 1.0 sec
|
||||
if (timerUpdateAITargets >= 1.0f) timerUpdateAITargets = 0;
|
||||
if (timerUpdateHeadTrack >= 0.3f) timerUpdateHeadTrack = 0;
|
||||
if (timerUpdateHello >= 0.25f) timerUpdateHello = 0;
|
||||
if (mTimerDisposeSummonsCorpses >= 0.2f) mTimerDisposeSummonsCorpses = 0;
|
||||
|
@ -2124,6 +2125,8 @@ namespace MWMechanics
|
|||
|
||||
iter->first.getClass().getCreatureStats(iter->first).getActiveSpells().update(duration);
|
||||
|
||||
const Misc::TimerStatus engageCombatTimerStatus = iter->second->updateEngageCombatTimer(duration);
|
||||
|
||||
// For dead actors we need to update looping spell particles
|
||||
if (iter->first.getClass().getCreatureStats(iter->first).isDead())
|
||||
{
|
||||
|
@ -2161,7 +2164,7 @@ namespace MWMechanics
|
|||
|
||||
if (inProcessingRange && (aiActive || isLocalActor || isDedicatedActor))
|
||||
{
|
||||
if (timerUpdateAITargets == 0 && (isLocalActor || aiActive))
|
||||
if (engageCombatTimerStatus == Misc::TimerStatus::Elapsed && (isLocalActor || aiActive))
|
||||
{
|
||||
if (!isPlayer)
|
||||
adjustCommandedActor(iter->first);
|
||||
|
@ -2251,7 +2254,6 @@ namespace MWMechanics
|
|||
if (avoidCollisions)
|
||||
predictAndAvoidCollisions();
|
||||
|
||||
timerUpdateAITargets += duration;
|
||||
timerUpdateHeadTrack += duration;
|
||||
timerUpdateEquippedLight += duration;
|
||||
timerUpdateHello += duration;
|
||||
|
|
|
@ -23,4 +23,10 @@ namespace MWMechanics
|
|||
MWBase::World* world = MWBase::Environment::get().getWorld();
|
||||
return (actor.getClass().canSwim(actor) && world->isSwimming(actor)) || world->isFlying(actor);
|
||||
}
|
||||
|
||||
bool hasWaterWalking(const MWWorld::Ptr& actor)
|
||||
{
|
||||
const MWMechanics::MagicEffects& effects = actor.getClass().getCreatureStats(actor).getMagicEffects();
|
||||
return effects.get(ESM::MagicEffect::WaterWalking).getMagnitude() > 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ namespace MWMechanics
|
|||
MWWorld::Ptr getPlayer();
|
||||
bool isPlayerInCombat();
|
||||
bool canActorMoveByZAxis(const MWWorld::Ptr& actor);
|
||||
bool hasWaterWalking(const MWWorld::Ptr& actor);
|
||||
|
||||
template<class T>
|
||||
void setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value)
|
||||
|
|
|
@ -45,13 +45,13 @@ bool MWMechanics::AiAvoidDoor::execute (const MWWorld::Ptr& actor, CharacterCont
|
|||
return true; //Door is no longer opening
|
||||
|
||||
ESM::Position tPos = mDoorPtr.getRefData().getPosition(); //Position of the door
|
||||
float x = pos.pos[0] - tPos.pos[0];
|
||||
float y = pos.pos[1] - tPos.pos[1];
|
||||
float x = pos.pos[1] - tPos.pos[1];
|
||||
float y = pos.pos[0] - tPos.pos[0];
|
||||
|
||||
actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true);
|
||||
|
||||
// Turn away from the door and move when turn completed
|
||||
if (zTurn(actor, std::atan2(x,y) + getAdjustedAngle(), osg::DegreesToRadians(5.f)))
|
||||
if (zTurn(actor, std::atan2(y,x) + getAdjustedAngle(), osg::DegreesToRadians(5.f)))
|
||||
actor.getClass().getMovementSettings(actor).mPosition[1] = 1;
|
||||
else
|
||||
actor.getClass().getMovementSettings(actor).mPosition[1] = 0;
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
#include "aicast.hpp"
|
||||
|
||||
#include <components/misc/constants.hpp>
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/mechanicsmanager.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
|
@ -54,12 +56,12 @@ bool MWMechanics::AiCast::execute(const MWWorld::Ptr& actor, MWMechanics::Charac
|
|||
if (target != actor && target.getClass().isActor())
|
||||
{
|
||||
osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(target);
|
||||
targetPos.z() += halfExtents.z() * 2 * 0.75f;
|
||||
targetPos.z() += halfExtents.z() * 2 * Constants::TorsoHeight;
|
||||
}
|
||||
|
||||
osg::Vec3f actorPos = actor.getRefData().getPosition().asVec3();
|
||||
osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(actor);
|
||||
actorPos.z() += halfExtents.z() * 2 * 0.75f;
|
||||
actorPos.z() += halfExtents.z() * 2 * Constants::TorsoHeight;
|
||||
|
||||
osg::Vec3f dir = targetPos - actorPos;
|
||||
|
||||
|
|
|
@ -178,19 +178,10 @@ namespace MWMechanics
|
|||
}
|
||||
storage.mActionCooldown -= duration;
|
||||
|
||||
float& timerReact = storage.mTimerReact;
|
||||
if (timerReact < AI_REACTION_TIME)
|
||||
{
|
||||
timerReact += duration;
|
||||
}
|
||||
else
|
||||
{
|
||||
timerReact = 0;
|
||||
if (attack(actor, target, storage, characterController))
|
||||
return true;
|
||||
}
|
||||
|
||||
if (storage.mReaction.update(duration) == Misc::TimerStatus::Waiting)
|
||||
return false;
|
||||
|
||||
return attack(actor, target, storage, characterController);
|
||||
}
|
||||
|
||||
bool AiCombat::attack(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, AiCombatStorage& storage, CharacterController& characterController)
|
||||
|
@ -305,7 +296,6 @@ namespace MWMechanics
|
|||
const osg::Vec3f vActorPos(pos.asVec3());
|
||||
const osg::Vec3f vTargetPos(target.getRefData().getPosition().asVec3());
|
||||
|
||||
osg::Vec3f vAimDir = MWBase::Environment::get().getWorld()->aimToTarget(actor, target);
|
||||
float distToTarget = MWBase::Environment::get().getWorld()->getHitDistance(actor, target);
|
||||
|
||||
storage.mReadyToAttack = (currentAction->isAttackingOrSpell() && distToTarget <= rangeAttack && storage.mLOS);
|
||||
|
@ -313,13 +303,14 @@ namespace MWMechanics
|
|||
if (isRangedCombat)
|
||||
{
|
||||
// rotate actor taking into account target movement direction and projectile speed
|
||||
vAimDir = AimDirToMovingTarget(actor, target, storage.mLastTargetPos, AI_REACTION_TIME, (weapon ? weapon->mData.mType : 0), storage.mStrength);
|
||||
osg::Vec3f vAimDir = AimDirToMovingTarget(actor, target, storage.mLastTargetPos, AI_REACTION_TIME, (weapon ? weapon->mData.mType : 0), storage.mStrength);
|
||||
|
||||
storage.mMovement.mRotation[0] = getXAngleToDir(vAimDir);
|
||||
storage.mMovement.mRotation[2] = getZAngleToDir(vAimDir);
|
||||
}
|
||||
else
|
||||
{
|
||||
osg::Vec3f vAimDir = MWBase::Environment::get().getWorld()->aimToTarget(actor, target, false);
|
||||
storage.mMovement.mRotation[0] = getXAngleToDir(vAimDir);
|
||||
storage.mMovement.mRotation[2] = getZAngleToDir((vTargetPos-vActorPos)); // using vAimDir results in spastic movements since the head is animated
|
||||
}
|
||||
|
@ -799,7 +790,7 @@ osg::Vec3f AimDirToMovingTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& t
|
|||
// idea: perpendicular to dir to target speed components of target move vector and projectile vector should be the same
|
||||
|
||||
osg::Vec3f vTargetPos = target.getRefData().getPosition().asVec3();
|
||||
osg::Vec3f vDirToTarget = MWBase::Environment::get().getWorld()->aimToTarget(actor, target);
|
||||
osg::Vec3f vDirToTarget = MWBase::Environment::get().getWorld()->aimToTarget(actor, target, true);
|
||||
float distToTarget = vDirToTarget.length();
|
||||
|
||||
osg::Vec3f vTargetMoveDir = vTargetPos - vLastTargetPos;
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include "pathfinding.hpp"
|
||||
#include "movement.hpp"
|
||||
#include "obstacle.hpp"
|
||||
#include "aitimer.hpp"
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
|
@ -27,7 +28,7 @@ namespace MWMechanics
|
|||
struct AiCombatStorage : AiTemporaryBase
|
||||
{
|
||||
float mAttackCooldown;
|
||||
float mTimerReact;
|
||||
AiReactionTimer mReaction;
|
||||
float mTimerCombatMove;
|
||||
bool mReadyToAttack;
|
||||
bool mAttack;
|
||||
|
@ -60,7 +61,6 @@ namespace MWMechanics
|
|||
|
||||
AiCombatStorage():
|
||||
mAttackCooldown(0.0f),
|
||||
mTimerReact(AI_REACTION_TIME),
|
||||
mTimerCombatMove(0.0f),
|
||||
mReadyToAttack(false),
|
||||
mAttack(false),
|
||||
|
|
|
@ -73,23 +73,25 @@ namespace MWMechanics
|
|||
const MWWorld::Ptr follower = MWBase::Environment::get().getWorld()->getPtr(mTargetActorRefId, false);
|
||||
const osg::Vec3f leaderPos = actor.getRefData().getPosition().asVec3();
|
||||
const osg::Vec3f followerPos = follower.getRefData().getPosition().asVec3();
|
||||
const osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(actor);
|
||||
const float maxHalfExtent = std::max(halfExtents.x(), std::max(halfExtents.y(), halfExtents.z()));
|
||||
|
||||
if ((leaderPos - followerPos).length2() <= mMaxDist * mMaxDist)
|
||||
{
|
||||
const osg::Vec3f dest(mX, mY, mZ);
|
||||
if (pathTo(actor, dest, duration)) //Returns true on path complete
|
||||
if (pathTo(actor, dest, duration, maxHalfExtent)) //Returns true on path complete
|
||||
{
|
||||
mRemainingDuration = mDuration;
|
||||
return true;
|
||||
}
|
||||
mMaxDist = 450;
|
||||
mMaxDist = maxHalfExtent + 450.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Stop moving if the player is too far away
|
||||
MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, "idle3", 0, 1);
|
||||
actor.getClass().getMovementSettings(actor).mPosition[1] = 0;
|
||||
mMaxDist = 250;
|
||||
mMaxDist = maxHalfExtent + 250.0f;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
|
|
@ -28,7 +28,6 @@
|
|||
MWMechanics::AiPackage::AiPackage(AiPackageTypeId typeId, const Options& options) :
|
||||
mTypeId(typeId),
|
||||
mOptions(options),
|
||||
mTimer(AI_REACTION_TIME + 1.0f), // to force initial pathbuild
|
||||
mTargetActorRefId(""),
|
||||
mTargetActorId(-1),
|
||||
mRotateOnTheRunChecks(0),
|
||||
|
@ -64,7 +63,7 @@ MWWorld::Ptr MWMechanics::AiPackage::getTarget() const
|
|||
void MWMechanics::AiPackage::reset()
|
||||
{
|
||||
// reset all members
|
||||
mTimer = AI_REACTION_TIME + 1.0f;
|
||||
mReaction.reset();
|
||||
mIsShortcutting = false;
|
||||
mShortcutProhibited = false;
|
||||
mShortcutFailPos = osg::Vec3f();
|
||||
|
@ -75,7 +74,7 @@ void MWMechanics::AiPackage::reset()
|
|||
|
||||
bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& dest, float duration, float destTolerance)
|
||||
{
|
||||
mTimer += duration; //Update timer
|
||||
const Misc::TimerStatus timerStatus = mReaction.update(duration);
|
||||
|
||||
const osg::Vec3f position = actor.getRefData().getPosition().asVec3(); //position of the actor
|
||||
MWBase::World* world = MWBase::Environment::get().getWorld();
|
||||
|
@ -98,7 +97,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f&
|
|||
const bool isDestReached = (distToTarget <= destTolerance);
|
||||
const bool actorCanMoveByZ = canActorMoveByZAxis(actor);
|
||||
|
||||
if (!isDestReached && mTimer > AI_REACTION_TIME)
|
||||
if (!isDestReached && timerStatus == Misc::TimerStatus::Elapsed)
|
||||
{
|
||||
if (actor.getClass().isBipedal(actor))
|
||||
openDoors(actor);
|
||||
|
@ -115,7 +114,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f&
|
|||
if (wasShortcutting || doesPathNeedRecalc(dest, actor)) // if need to rebuild path
|
||||
{
|
||||
const auto pathfindingHalfExtents = world->getPathfindingHalfExtents(actor);
|
||||
mPathFinder.buildPath(actor, position, dest, actor.getCell(), getPathGridGraph(actor.getCell()),
|
||||
mPathFinder.buildLimitedPath(actor, position, dest, actor.getCell(), getPathGridGraph(actor.getCell()),
|
||||
pathfindingHalfExtents, getNavigatorFlags(actor), getAreaCosts(actor));
|
||||
mRotateOnTheRunChecks = 3;
|
||||
|
||||
|
@ -142,8 +141,6 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f&
|
|||
mPathFinder.addPointToPath(dest); //Adds the final destination to the path, to try to get to where you want to go
|
||||
}
|
||||
}
|
||||
|
||||
mTimer = 0;
|
||||
}
|
||||
|
||||
const float actorTolerance = 2 * actor.getClass().getMaxSpeed(actor) * duration
|
||||
|
@ -414,10 +411,16 @@ bool MWMechanics::AiPackage::isReachableRotatingOnTheRun(const MWWorld::Ptr& act
|
|||
|
||||
DetourNavigator::Flags MWMechanics::AiPackage::getNavigatorFlags(const MWWorld::Ptr& actor) const
|
||||
{
|
||||
static const bool allowToFollowOverWaterSurface = Settings::Manager::getBool("allow actors to follow over water surface", "Game");
|
||||
|
||||
const MWWorld::Class& actorClass = actor.getClass();
|
||||
DetourNavigator::Flags result = DetourNavigator::Flag_none;
|
||||
|
||||
if (actorClass.isPureWaterCreature(actor) || (getTypeId() != AiPackageTypeId::Wander && actorClass.canSwim(actor)))
|
||||
if (actorClass.isPureWaterCreature(actor)
|
||||
|| (getTypeId() != AiPackageTypeId::Wander
|
||||
&& ((allowToFollowOverWaterSurface && getTypeId() == AiPackageTypeId::Follow)
|
||||
|| actorClass.canSwim(actor)
|
||||
|| hasWaterWalking(actor))))
|
||||
result |= DetourNavigator::Flag_swim;
|
||||
|
||||
if (actorClass.canWalk(actor))
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include "obstacle.hpp"
|
||||
#include "aistate.hpp"
|
||||
#include "aipackagetypeid.hpp"
|
||||
#include "aitimer.hpp"
|
||||
|
||||
namespace MWWorld
|
||||
{
|
||||
|
@ -28,8 +29,6 @@ namespace ESM
|
|||
|
||||
namespace MWMechanics
|
||||
{
|
||||
const float AI_REACTION_TIME = 0.25f;
|
||||
|
||||
class CharacterController;
|
||||
class PathgridGraph;
|
||||
|
||||
|
@ -158,7 +157,7 @@ namespace MWMechanics
|
|||
PathFinder mPathFinder;
|
||||
ObstacleCheck mObstacleCheck;
|
||||
|
||||
float mTimer;
|
||||
AiReactionTimer mReaction;
|
||||
|
||||
std::string mTargetActorRefId;
|
||||
mutable int mTargetActorId;
|
||||
|
|
26
apps/openmw/mwmechanics/aitimer.hpp
Normal file
26
apps/openmw/mwmechanics/aitimer.hpp
Normal file
|
@ -0,0 +1,26 @@
|
|||
#ifndef OPENMW_MECHANICS_AITIMER_H
|
||||
#define OPENMW_MECHANICS_AITIMER_H
|
||||
|
||||
#include <components/misc/rng.hpp>
|
||||
#include <components/misc/timer.hpp>
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
constexpr float AI_REACTION_TIME = 0.25f;
|
||||
|
||||
class AiReactionTimer
|
||||
{
|
||||
public:
|
||||
static constexpr float sDeviation = 0.1f;
|
||||
|
||||
Misc::TimerStatus update(float duration) { return mImpl.update(duration); }
|
||||
|
||||
void reset() { mImpl.reset(Misc::Rng::deviate(0, sDeviation)); }
|
||||
|
||||
private:
|
||||
Misc::DeviatingPeriodicTimer mImpl {AI_REACTION_TIME, sDeviation,
|
||||
Misc::Rng::deviate(0, sDeviation)};
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
|
@ -223,15 +223,10 @@ namespace MWMechanics
|
|||
|
||||
doPerFrameActionsForState(actor, duration, storage);
|
||||
|
||||
float& lastReaction = storage.mReaction;
|
||||
lastReaction += duration;
|
||||
if (AI_REACTION_TIME <= lastReaction)
|
||||
{
|
||||
lastReaction = 0;
|
||||
return reactionTimeActions(actor, storage, pos);
|
||||
}
|
||||
else
|
||||
if (storage.mReaction.update(duration) == Misc::TimerStatus::Waiting)
|
||||
return false;
|
||||
|
||||
return reactionTimeActions(actor, storage, pos);
|
||||
}
|
||||
|
||||
bool AiWander::reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage, ESM::Position& pos)
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include "pathfinding.hpp"
|
||||
#include "obstacle.hpp"
|
||||
#include "aistate.hpp"
|
||||
#include "aitimer.hpp"
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
|
@ -25,7 +26,7 @@ namespace MWMechanics
|
|||
/// \brief This class holds the variables AiWander needs which are deleted if the package becomes inactive.
|
||||
struct AiWanderStorage : AiTemporaryBase
|
||||
{
|
||||
float mReaction; // update some actions infrequently
|
||||
AiReactionTimer mReaction;
|
||||
|
||||
// AiWander states
|
||||
enum WanderState
|
||||
|
@ -57,7 +58,6 @@ namespace MWMechanics
|
|||
int mStuckCount;
|
||||
|
||||
AiWanderStorage():
|
||||
mReaction(0),
|
||||
mState(Wander_ChooseAction),
|
||||
mIsWanderingManually(false),
|
||||
mCanWanderAlongPathGrid(true),
|
||||
|
|
|
@ -228,39 +228,49 @@ void CharacterController::refreshHitRecoilAnims(CharacterState& idle)
|
|||
if(mHitState == CharState_None)
|
||||
{
|
||||
if ((mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() < 0
|
||||
|| mPtr.getClass().getCreatureStats(mPtr).getFatigue().getBase() == 0)
|
||||
&& mAnimation->hasAnimation("knockout"))
|
||||
|| mPtr.getClass().getCreatureStats(mPtr).getFatigue().getBase() == 0))
|
||||
{
|
||||
mTimeUntilWake = Misc::Rng::rollClosedProbability() * 2 + 1; // Wake up after 1 to 3 seconds
|
||||
if (isSwimming && mAnimation->hasAnimation("swimknockout"))
|
||||
{
|
||||
mHitState = CharState_SwimKnockOut;
|
||||
mCurrentHit = "swimknockout";
|
||||
mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, false, 1, "start", "stop", 0.0f, ~0ul);
|
||||
}
|
||||
else
|
||||
else if (!isSwimming && mAnimation->hasAnimation("knockout"))
|
||||
{
|
||||
mHitState = CharState_KnockOut;
|
||||
mCurrentHit = "knockout";
|
||||
mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, false, 1, "start", "stop", 0.0f, ~0ul);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Knockout animations are missing. Fall back to idle animation, so target actor still can be killed via HtH.
|
||||
mCurrentHit.erase();
|
||||
}
|
||||
|
||||
mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, false, 1, "start", "stop", 0.0f, ~0ul);
|
||||
mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(true);
|
||||
}
|
||||
else if(knockdown && mAnimation->hasAnimation("knockdown"))
|
||||
else if (knockdown)
|
||||
{
|
||||
if (isSwimming && mAnimation->hasAnimation("swimknockdown"))
|
||||
{
|
||||
mHitState = CharState_SwimKnockDown;
|
||||
mCurrentHit = "swimknockdown";
|
||||
mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0);
|
||||
}
|
||||
else
|
||||
else if (!isSwimming && mAnimation->hasAnimation("knockdown"))
|
||||
{
|
||||
mHitState = CharState_KnockDown;
|
||||
mCurrentHit = "knockdown";
|
||||
}
|
||||
|
||||
mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Knockdown animation is missing. Cancel knockdown state.
|
||||
mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(false);
|
||||
}
|
||||
}
|
||||
else if (recovery)
|
||||
{
|
||||
std::string anim = chooseRandomGroup("swimhit");
|
||||
|
@ -3179,7 +3189,7 @@ void CharacterController::updateHeadTracking(float duration)
|
|||
}
|
||||
else
|
||||
// no head node to look at, fall back to look at center of collision box
|
||||
direction = MWBase::Environment::get().getWorld()->aimToTarget(mPtr, mHeadTrackTarget);
|
||||
direction = MWBase::Environment::get().getWorld()->aimToTarget(mPtr, mHeadTrackTarget, false);
|
||||
}
|
||||
direction.normalize();
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ namespace MWMechanics
|
|||
{
|
||||
struct CorprusStats
|
||||
{
|
||||
static const int sWorseningPeriod = 24;
|
||||
static constexpr int sWorseningPeriod = 24;
|
||||
|
||||
int mWorsenings[ESM::Attribute::Length];
|
||||
MWWorld::TimeStamp mNextWorsening;
|
||||
|
|
|
@ -708,7 +708,7 @@ namespace MWMechanics
|
|||
// Deviating from Morrowind here: it doesn't increase disposition on marginal wins,
|
||||
// which seems to be a bug (MCP fixes it too).
|
||||
// Original logic: x = 0, y = -iPerMinChange
|
||||
x = -iPerMinChange;
|
||||
x = iPerMinChange;
|
||||
y = x; // This goes unused.
|
||||
}
|
||||
else
|
||||
|
|
|
@ -12,7 +12,7 @@ namespace MWMechanics
|
|||
{
|
||||
struct Movement;
|
||||
|
||||
static const int NUM_EVADE_DIRECTIONS = 4;
|
||||
static constexpr int NUM_EVADE_DIRECTIONS = 4;
|
||||
|
||||
/// tests actor's proximity to a closed door by default
|
||||
bool proximityToDoor(const MWWorld::Ptr& actor, float minDist);
|
||||
|
|
|
@ -442,4 +442,21 @@ namespace MWMechanics
|
|||
|
||||
std::copy(prePath.rbegin(), prePath.rend(), std::front_inserter(mPath));
|
||||
}
|
||||
|
||||
void PathFinder::buildLimitedPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
|
||||
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph, const osg::Vec3f& halfExtents,
|
||||
const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts)
|
||||
{
|
||||
const auto navigator = MWBase::Environment::get().getWorld()->getNavigator();
|
||||
const auto maxDistance = std::min(
|
||||
navigator->getMaxNavmeshAreaRealRadius(),
|
||||
static_cast<float>(Constants::CellSizeInUnits)
|
||||
);
|
||||
const auto startToEnd = endPoint - startPoint;
|
||||
const auto distance = startToEnd.length();
|
||||
if (distance <= maxDistance)
|
||||
return buildPath(actor, startPoint, endPoint, cell, pathgridGraph, halfExtents, flags, areaCosts);
|
||||
const auto end = startPoint + startToEnd * maxDistance / distance;
|
||||
buildPath(actor, startPoint, end, cell, pathgridGraph, halfExtents, flags, areaCosts);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -101,6 +101,10 @@ namespace MWMechanics
|
|||
void buildPathByNavMeshToNextPoint(const MWWorld::ConstPtr& actor, const osg::Vec3f& halfExtents,
|
||||
const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts);
|
||||
|
||||
void buildLimitedPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
|
||||
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph, const osg::Vec3f& halfExtents,
|
||||
const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts);
|
||||
|
||||
/// Remove front point if exist and within tolerance
|
||||
void update(const osg::Vec3f& position, float pointTolerance, float destinationTolerance,
|
||||
bool shortenIfAlmostStraight, bool canMoveByZ);
|
||||
|
|
|
@ -194,22 +194,12 @@ namespace MWMechanics
|
|||
|
||||
for (std::map<ESM::SummonKey, int>::iterator it = creatureMap.begin(); it != creatureMap.end(); )
|
||||
{
|
||||
/*
|
||||
Start of tes3mp addition
|
||||
|
||||
If we're iterating over a SummonKey matching an actorId of -1, that means it's a summon
|
||||
yet to be sent back to us by the server and we should skip over it, because deleting it
|
||||
here would mean it becomes just a regular creature when the server sends it back to us
|
||||
*/
|
||||
if (it->second == -1)
|
||||
if(it->second == -1)
|
||||
{
|
||||
++it;
|
||||
// Keep the spell effect active if we failed to spawn anything
|
||||
it++;
|
||||
continue;
|
||||
}
|
||||
/*
|
||||
End of tes3mp addition
|
||||
*/
|
||||
|
||||
MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(it->second);
|
||||
if (ptr.isEmpty() || (ptr.getClass().getCreatureStats(ptr).isDead() && ptr.getClass().getCreatureStats(ptr).isDeathAnimationFinished()))
|
||||
{
|
||||
|
|
|
@ -35,6 +35,7 @@ namespace MWPhysics
|
|||
Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, PhysicsTaskScheduler* scheduler)
|
||||
: mStandingOnPtr(nullptr), mCanWaterWalk(false), mWalkingOnWater(false)
|
||||
, mCollisionObject(nullptr), mMeshTranslation(shape->mCollisionBox.center), mHalfExtents(shape->mCollisionBox.extents)
|
||||
, mStuckFrames(0), mLastStuckPosition{0, 0, 0}
|
||||
, mForce(0.f, 0.f, 0.f), mOnGround(true), mOnSlope(false)
|
||||
, mInternalCollisionMode(true)
|
||||
, mExternalCollisionMode(true)
|
||||
|
@ -213,9 +214,10 @@ bool Actor::setPosition(const osg::Vec3f& position)
|
|||
if (mSkipSimulation)
|
||||
return false;
|
||||
bool hasChanged = mPosition != position || mPositionOffset.length() != 0 || mWorldPositionChanged;
|
||||
mPreviousPosition = mPosition + mPositionOffset;
|
||||
mPosition = position + mPositionOffset;
|
||||
mPositionOffset = osg::Vec3f();
|
||||
updateWorldPosition();
|
||||
applyOffsetChange();
|
||||
mPreviousPosition = mPosition;
|
||||
mPosition = position;
|
||||
return hasChanged;
|
||||
}
|
||||
|
||||
|
@ -232,6 +234,7 @@ void Actor::applyOffsetChange()
|
|||
mWorldPosition += mPositionOffset;
|
||||
mPosition += mPositionOffset;
|
||||
mPreviousPosition += mPositionOffset;
|
||||
mSimulationPosition += mPositionOffset;
|
||||
mPositionOffset = osg::Vec3f();
|
||||
mWorldPositionChanged = true;
|
||||
}
|
||||
|
|
|
@ -159,6 +159,24 @@ namespace MWPhysics
|
|||
MWWorld::Ptr getStandingOnPtr() const;
|
||||
void setStandingOnPtr(const MWWorld::Ptr& ptr);
|
||||
|
||||
unsigned int getStuckFrames() const
|
||||
{
|
||||
return mStuckFrames;
|
||||
}
|
||||
void setStuckFrames(unsigned int frames)
|
||||
{
|
||||
mStuckFrames = frames;
|
||||
}
|
||||
|
||||
const osg::Vec3f &getLastStuckPosition() const
|
||||
{
|
||||
return mLastStuckPosition;
|
||||
}
|
||||
void setLastStuckPosition(osg::Vec3f position)
|
||||
{
|
||||
mLastStuckPosition = position;
|
||||
}
|
||||
|
||||
private:
|
||||
MWWorld::Ptr mStandingOnPtr;
|
||||
/// Removes then re-adds the collision object to the dynamics world
|
||||
|
@ -192,6 +210,9 @@ namespace MWPhysics
|
|||
btTransform mLocalTransform;
|
||||
mutable std::mutex mPositionMutex;
|
||||
|
||||
unsigned int mStuckFrames;
|
||||
osg::Vec3f mLastStuckPosition;
|
||||
|
||||
osg::Vec3f mForce;
|
||||
std::atomic<bool> mOnGround;
|
||||
std::atomic<bool> mOnSlope;
|
||||
|
|
|
@ -3,24 +3,24 @@
|
|||
|
||||
namespace MWPhysics
|
||||
{
|
||||
static const float sStepSizeUp = 34.0f;
|
||||
static const float sStepSizeDown = 62.0f;
|
||||
static constexpr float sStepSizeUp = 34.0f;
|
||||
static constexpr float sStepSizeDown = 62.0f;
|
||||
|
||||
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
|
||||
static constexpr float sMinStep = 10.0f; // hack to skip over tiny unwalkable slopes
|
||||
static constexpr 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 constexpr bool sDoExtraStairHacks = true;
|
||||
|
||||
static const float sGroundOffset = 1.0f;
|
||||
static const float sMaxSlope = 49.0f;
|
||||
static constexpr float sGroundOffset = 1.0f;
|
||||
static constexpr 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;
|
||||
static constexpr int sMaxIterations = 8;
|
||||
// Allows for more precise movement solving without getting stuck or snagging too easily.
|
||||
static const float sCollisionMargin = 0.1;
|
||||
static constexpr 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;
|
||||
static constexpr float sAllowedPenetration = 0.0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -204,7 +204,7 @@ namespace MWPhysics
|
|||
osg::Vec3f lastSlideNormalFallback(0,0,1);
|
||||
bool forceGroundTest = false;
|
||||
|
||||
for (int iterations = 0; iterations < sMaxIterations && remainingTime > 0.01f; ++iterations)
|
||||
for (int iterations = 0; iterations < sMaxIterations && remainingTime > 0.0001f; ++iterations)
|
||||
{
|
||||
osg::Vec3f nextpos = newPosition + velocity * remainingTime;
|
||||
|
||||
|
@ -394,6 +394,12 @@ namespace MWPhysics
|
|||
isOnGround = false;
|
||||
}
|
||||
}
|
||||
// forcibly treat stuck actors as if they're on flat ground because buggy collisions when inside of things can/will break ground detection
|
||||
if(physicActor->getStuckFrames() > 0)
|
||||
{
|
||||
isOnGround = true;
|
||||
isOnSlope = false;
|
||||
}
|
||||
}
|
||||
|
||||
if((isOnGround && !isOnSlope) || newPosition.z() < swimlevel || actor.mFlying)
|
||||
|
@ -437,13 +443,23 @@ namespace MWPhysics
|
|||
auto* collisionObject = physicActor->getCollisionObject();
|
||||
auto tempPosition = actor.mPosition;
|
||||
|
||||
if(physicActor->getStuckFrames() >= 10)
|
||||
{
|
||||
if((physicActor->getLastStuckPosition() - actor.mPosition).length2() < 100)
|
||||
return;
|
||||
else
|
||||
{
|
||||
physicActor->setStuckFrames(0);
|
||||
physicActor->setLastStuckPosition({0, 0, 0});
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
auto velocity = (osg::Quat(actor.mRefpos.rot[0], osg::Vec3f(-1, 0, 0)) * osg::Quat(actor.mRefpos.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();
|
||||
|
@ -470,6 +486,8 @@ namespace MWPhysics
|
|||
auto contactCallback = gatherContacts({0.0, 0.0, 0.0});
|
||||
if(contactCallback.mDistance < -sAllowedPenetration)
|
||||
{
|
||||
physicActor->setStuckFrames(physicActor->getStuckFrames() + 1);
|
||||
physicActor->setLastStuckPosition(actor.mPosition);
|
||||
// we are; try moving it out of the world
|
||||
auto positionDelta = contactCallback.mContactSum;
|
||||
// limit rejection delta to the largest known individual rejections
|
||||
|
@ -502,6 +520,11 @@ namespace MWPhysics
|
|||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
physicActor->setStuckFrames(0);
|
||||
physicActor->setLastStuckPosition({0, 0, 0});
|
||||
}
|
||||
|
||||
collisionObject->setWorldTransform(oldTransform);
|
||||
actor.mPosition = tempPosition;
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include "components/settings/settings.hpp"
|
||||
#include "../mwmechanics/actorutil.hpp"
|
||||
#include "../mwmechanics/movement.hpp"
|
||||
#include "../mwrender/bulletdebugdraw.hpp"
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwworld/player.hpp"
|
||||
|
||||
|
@ -101,7 +102,7 @@ namespace
|
|||
|
||||
osg::Vec3f interpolateMovements(MWPhysics::ActorFrameData& actorData, float timeAccum, float physicsDt)
|
||||
{
|
||||
const float interpolationFactor = timeAccum / physicsDt;
|
||||
const float interpolationFactor = std::clamp(timeAccum / physicsDt, 0.0f, 1.0f);
|
||||
return actorData.mPosition * interpolationFactor + actorData.mActorRaw->getPreviousPosition() * (1.f - interpolationFactor);
|
||||
}
|
||||
|
||||
|
@ -137,10 +138,12 @@ namespace
|
|||
|
||||
namespace MWPhysics
|
||||
{
|
||||
PhysicsTaskScheduler::PhysicsTaskScheduler(float physicsDt, std::shared_ptr<btCollisionWorld> collisionWorld)
|
||||
: mPhysicsDt(physicsDt)
|
||||
PhysicsTaskScheduler::PhysicsTaskScheduler(float physicsDt, btCollisionWorld *collisionWorld, MWRender::DebugDrawer* debugDrawer)
|
||||
: mDefaultPhysicsDt(physicsDt)
|
||||
, mPhysicsDt(physicsDt)
|
||||
, mTimeAccum(0.f)
|
||||
, mCollisionWorld(std::move(collisionWorld))
|
||||
, mCollisionWorld(collisionWorld)
|
||||
, mDebugDrawer(debugDrawer)
|
||||
, mNumJobs(0)
|
||||
, mRemainingSteps(0)
|
||||
, mLOSCacheExpiry(Settings::Manager::getInt("lineofsight keep inactive cache", "Physics"))
|
||||
|
@ -152,6 +155,11 @@ namespace MWPhysics
|
|||
, mNextLOS(0)
|
||||
, mFrameNumber(0)
|
||||
, mTimer(osg::Timer::instance())
|
||||
, mPrevStepCount(1)
|
||||
, mBudget(physicsDt)
|
||||
, mAsyncBudget(0.0f)
|
||||
, mBudgetCursor(0)
|
||||
, mAsyncStartTime(0)
|
||||
, mTimeBegin(0)
|
||||
, mTimeEnd(0)
|
||||
, mFrameStart(0)
|
||||
|
@ -179,7 +187,7 @@ namespace MWPhysics
|
|||
if (data.mActor.lock())
|
||||
{
|
||||
std::unique_lock lock(mCollisionWorldMutex);
|
||||
MovementSolver::unstuck(data, mCollisionWorld.get());
|
||||
MovementSolver::unstuck(data, mCollisionWorld);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -220,13 +228,61 @@ namespace MWPhysics
|
|||
thread.join();
|
||||
}
|
||||
|
||||
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)
|
||||
std::tuple<int, float> PhysicsTaskScheduler::calculateStepConfig(float timeAccum) const
|
||||
{
|
||||
int maxAllowedSteps = 2;
|
||||
int numSteps = timeAccum / mDefaultPhysicsDt;
|
||||
|
||||
// adjust maximum step count based on whether we're likely physics bottlenecked or not
|
||||
// if maxAllowedSteps ends up higher than numSteps, we will not invoke delta time
|
||||
// if it ends up lower than numSteps, but greater than 1, we will run a number of true delta time physics steps that we expect to be within budget
|
||||
// if it ends up lower than numSteps and also 1, we will run a single delta time physics step
|
||||
// if we did not do this, and had a fixed step count limit,
|
||||
// we would have an unnecessarily low render framerate if we were only physics bottlenecked,
|
||||
// and we would be unnecessarily invoking true delta time if we were only render bottlenecked
|
||||
|
||||
// get physics timing stats
|
||||
float budgetMeasurement = std::max(mBudget.get(), mAsyncBudget.get());
|
||||
// time spent per step in terms of the intended physics framerate
|
||||
budgetMeasurement /= mDefaultPhysicsDt;
|
||||
// ensure sane minimum value
|
||||
budgetMeasurement = std::max(0.00001f, budgetMeasurement);
|
||||
// we're spending almost or more than realtime per physics frame; limit to a single step
|
||||
if (budgetMeasurement > 0.95)
|
||||
maxAllowedSteps = 1;
|
||||
// physics is fairly cheap; limit based on expense
|
||||
if (budgetMeasurement < 0.5)
|
||||
maxAllowedSteps = std::ceil(1.0/budgetMeasurement);
|
||||
// limit to a reasonable amount
|
||||
maxAllowedSteps = std::min(10, maxAllowedSteps);
|
||||
|
||||
// fall back to delta time for this frame if fixed timestep physics would fall behind
|
||||
float actualDelta = mDefaultPhysicsDt;
|
||||
if (numSteps > maxAllowedSteps)
|
||||
{
|
||||
numSteps = maxAllowedSteps;
|
||||
// ensure that we do not simulate a frame ahead when doing delta time; this reduces stutter and latency
|
||||
// this causes interpolation to 100% use the most recent physics result when true delta time is happening
|
||||
// and we deliberately simulate up to exactly the timestamp that we want to render
|
||||
actualDelta = timeAccum/float(numSteps+1);
|
||||
// actually: if this results in a per-step delta less than the target physics steptime, clamp it
|
||||
// this might reintroduce some stutter, but only comes into play in obscure cases
|
||||
// (because numSteps is originally based on mDefaultPhysicsDt, this won't cause us to overrun)
|
||||
actualDelta = std::max(actualDelta, mDefaultPhysicsDt);
|
||||
}
|
||||
|
||||
return std::make_tuple(numSteps, actualDelta);
|
||||
}
|
||||
|
||||
const std::vector<MWWorld::Ptr>& PhysicsTaskScheduler::moveActors(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);
|
||||
|
||||
double timeStart = mTimer->tick();
|
||||
|
||||
mMovedActors.clear();
|
||||
|
||||
// start by finishing previous background computation
|
||||
|
@ -251,14 +307,21 @@ namespace MWPhysics
|
|||
mMovedActors.emplace_back(data.mActorRaw->getPtr());
|
||||
}
|
||||
}
|
||||
if(mAdvanceSimulation)
|
||||
mAsyncBudget.update(mTimer->delta_s(mAsyncStartTime, mTimeEnd), mPrevStepCount, mBudgetCursor);
|
||||
updateStats(frameStart, frameNumber, stats);
|
||||
}
|
||||
|
||||
auto [numSteps, newDelta] = calculateStepConfig(timeAccum);
|
||||
timeAccum -= numSteps*newDelta;
|
||||
|
||||
// init
|
||||
for (auto& data : actorsData)
|
||||
data.updatePosition();
|
||||
data.updatePosition(mCollisionWorld);
|
||||
mPrevStepCount = numSteps;
|
||||
mRemainingSteps = numSteps;
|
||||
mTimeAccum = timeAccum;
|
||||
mPhysicsDt = newDelta;
|
||||
mActorsFrameData = std::move(actorsData);
|
||||
mAdvanceSimulation = (mRemainingSteps != 0);
|
||||
mNewFrame = true;
|
||||
|
@ -269,20 +332,30 @@ namespace MWPhysics
|
|||
if (mAdvanceSimulation)
|
||||
mWorldFrameData = std::make_unique<WorldFrameData>();
|
||||
|
||||
if (mAdvanceSimulation)
|
||||
mBudgetCursor += 1;
|
||||
|
||||
if (mNumThreads == 0)
|
||||
{
|
||||
syncComputation();
|
||||
if(mAdvanceSimulation)
|
||||
mBudget.update(mTimer->delta_s(timeStart, mTimer->tick()), numSteps, mBudgetCursor);
|
||||
return mMovedActors;
|
||||
}
|
||||
|
||||
mAsyncStartTime = mTimer->tick();
|
||||
lock.unlock();
|
||||
mHasJob.notify_all();
|
||||
if (mAdvanceSimulation)
|
||||
mBudget.update(mTimer->delta_s(timeStart, mTimer->tick()), 1, mBudgetCursor);
|
||||
return mMovedActors;
|
||||
}
|
||||
|
||||
const std::vector<MWWorld::Ptr>& PhysicsTaskScheduler::resetSimulation(const ActorMap& actors)
|
||||
{
|
||||
std::unique_lock lock(mSimulationMutex);
|
||||
mBudget.reset(mDefaultPhysicsDt);
|
||||
mAsyncBudget.reset(0.0f);
|
||||
mMovedActors.clear();
|
||||
mActorsFrameData.clear();
|
||||
for (const auto& [_, actor] : actors)
|
||||
|
@ -310,7 +383,7 @@ namespace MWPhysics
|
|||
void PhysicsTaskScheduler::contactTest(btCollisionObject* colObj, btCollisionWorld::ContactResultCallback& resultCallback)
|
||||
{
|
||||
std::shared_lock lock(mCollisionWorldMutex);
|
||||
ContactTestWrapper::contactTest(mCollisionWorld.get(), colObj, resultCallback);
|
||||
ContactTestWrapper::contactTest(mCollisionWorld, colObj, resultCallback);
|
||||
}
|
||||
|
||||
std::optional<btVector3> PhysicsTaskScheduler::getHitPoint(const btTransform& from, btCollisionObject* target)
|
||||
|
@ -461,7 +534,7 @@ namespace MWPhysics
|
|||
if(const auto actor = mActorsFrameData[job].mActor.lock())
|
||||
{
|
||||
MaybeSharedLock lockColWorld(mCollisionWorldMutex, mThreadSafeBullet);
|
||||
MovementSolver::move(mActorsFrameData[job], mPhysicsDt, mCollisionWorld.get(), *mWorldFrameData);
|
||||
MovementSolver::move(mActorsFrameData[job], mPhysicsDt, mCollisionWorld, *mWorldFrameData);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -523,8 +596,8 @@ namespace MWPhysics
|
|||
{
|
||||
for (auto& actorData : mActorsFrameData)
|
||||
{
|
||||
MovementSolver::unstuck(actorData, mCollisionWorld.get());
|
||||
MovementSolver::move(actorData, mPhysicsDt, mCollisionWorld.get(), *mWorldFrameData);
|
||||
MovementSolver::unstuck(actorData, mCollisionWorld);
|
||||
MovementSolver::move(actorData, mPhysicsDt, mCollisionWorld, *mWorldFrameData);
|
||||
}
|
||||
|
||||
updateActorsPositions();
|
||||
|
@ -555,4 +628,10 @@ namespace MWPhysics
|
|||
mTimeBegin = mTimer->tick();
|
||||
mFrameNumber = frameNumber;
|
||||
}
|
||||
|
||||
void PhysicsTaskScheduler::debugDraw()
|
||||
{
|
||||
std::shared_lock lock(mCollisionWorldMutex);
|
||||
mDebugDrawer->step();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,18 +13,24 @@
|
|||
|
||||
#include "physicssystem.hpp"
|
||||
#include "ptrholder.hpp"
|
||||
#include "components/misc/budgetmeasurement.hpp"
|
||||
|
||||
namespace Misc
|
||||
{
|
||||
class Barrier;
|
||||
}
|
||||
|
||||
namespace MWRender
|
||||
{
|
||||
class DebugDrawer;
|
||||
}
|
||||
|
||||
namespace MWPhysics
|
||||
{
|
||||
class PhysicsTaskScheduler
|
||||
{
|
||||
public:
|
||||
PhysicsTaskScheduler(float physicsDt, std::shared_ptr<btCollisionWorld> collisionWorld);
|
||||
PhysicsTaskScheduler(float physicsDt, btCollisionWorld* collisionWorld, MWRender::DebugDrawer* debugDrawer);
|
||||
~PhysicsTaskScheduler();
|
||||
|
||||
/// @brief move actors taking into account desired movements and collisions
|
||||
|
@ -32,7 +38,7 @@ 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 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>& moveActors(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);
|
||||
|
||||
|
@ -48,6 +54,7 @@ namespace MWPhysics
|
|||
void removeCollisionObject(btCollisionObject* collisionObject);
|
||||
void updateSingleAabb(std::weak_ptr<PtrHolder> ptr, bool immediate=false);
|
||||
bool getLineOfSight(const std::weak_ptr<Actor>& actor1, const std::weak_ptr<Actor>& actor2);
|
||||
void debugDraw();
|
||||
|
||||
private:
|
||||
void syncComputation();
|
||||
|
@ -58,14 +65,16 @@ namespace MWPhysics
|
|||
void updateAabbs();
|
||||
void updatePtrAabb(const std::weak_ptr<PtrHolder>& ptr);
|
||||
void updateStats(osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats);
|
||||
std::tuple<int, float> calculateStepConfig(float timeAccum) const;
|
||||
|
||||
std::unique_ptr<WorldFrameData> mWorldFrameData;
|
||||
std::vector<ActorFrameData> mActorsFrameData;
|
||||
std::vector<MWWorld::Ptr> mMovedActors;
|
||||
float mDefaultPhysicsDt;
|
||||
/*
|
||||
Start of tes3mp change (major)
|
||||
|
||||
Turn mPhysicsDt into a non-const public variable so it can be set from elsewhere
|
||||
Turn mPhysicsDt into a public variable so it can be set from elsewhere
|
||||
*/
|
||||
public:
|
||||
float mPhysicsDt;
|
||||
|
@ -74,7 +83,8 @@ namespace MWPhysics
|
|||
End of tes3mp change (major)
|
||||
*/
|
||||
float mTimeAccum;
|
||||
std::shared_ptr<btCollisionWorld> mCollisionWorld;
|
||||
btCollisionWorld* mCollisionWorld;
|
||||
MWRender::DebugDrawer* mDebugDrawer;
|
||||
std::vector<LOSRequest> mLOSCache;
|
||||
std::set<std::weak_ptr<PtrHolder>, std::owner_less<std::weak_ptr<PtrHolder>>> mUpdateAabb;
|
||||
|
||||
|
@ -104,6 +114,12 @@ namespace MWPhysics
|
|||
|
||||
unsigned int mFrameNumber;
|
||||
const osg::Timer* mTimer;
|
||||
|
||||
int mPrevStepCount;
|
||||
Misc::BudgetMeasurement mBudget;
|
||||
Misc::BudgetMeasurement mAsyncBudget;
|
||||
unsigned int mBudgetCursor;
|
||||
osg::Timer_t mAsyncStartTime;
|
||||
osg::Timer_t mTimeBegin;
|
||||
osg::Timer_t mTimeEnd;
|
||||
osg::Timer_t mFrameStart;
|
||||
|
|
|
@ -60,6 +60,22 @@
|
|||
#include "movementsolver.hpp"
|
||||
#include "mtphysics.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
bool canMoveToWaterSurface(const MWPhysics::Actor* physicActor, const float waterlevel, btCollisionWorld* world)
|
||||
{
|
||||
if (!physicActor)
|
||||
return false;
|
||||
const float halfZ = physicActor->getHalfExtents().z();
|
||||
const osg::Vec3f actorPosition = physicActor->getPosition();
|
||||
const osg::Vec3f startingPosition(actorPosition.x(), actorPosition.y(), actorPosition.z() + halfZ);
|
||||
const osg::Vec3f destinationPosition(actorPosition.x(), actorPosition.y(), waterlevel + halfZ);
|
||||
MWPhysics::ActorTracer tracer;
|
||||
tracer.doTrace(physicActor->getCollisionObject(), startingPosition, destinationPosition, world);
|
||||
return (tracer.mFraction >= 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
namespace MWPhysics
|
||||
{
|
||||
PhysicsSystem::PhysicsSystem(Resource::ResourceSystem* resourceSystem, osg::ref_ptr<osg::Group> parentNode)
|
||||
|
@ -79,7 +95,7 @@ namespace MWPhysics
|
|||
mDispatcher = std::make_unique<btCollisionDispatcher>(mCollisionConfiguration.get());
|
||||
mBroadphase = std::make_unique<btDbvtBroadphase>();
|
||||
|
||||
mCollisionWorld = std::make_shared<btCollisionWorld>(mDispatcher.get(), mBroadphase.get(), mCollisionConfiguration.get());
|
||||
mCollisionWorld = std::make_unique<btCollisionWorld>(mDispatcher.get(), mBroadphase.get(), mCollisionConfiguration.get());
|
||||
|
||||
// Don't update AABBs of all objects every frame. Most objects in MW are static, so we don't need this.
|
||||
// Should a "static" object ever be moved, we have to update its AABB manually using DynamicsWorld::updateSingleAabb.
|
||||
|
@ -97,8 +113,8 @@ namespace MWPhysics
|
|||
}
|
||||
}
|
||||
|
||||
mTaskScheduler = std::make_unique<PhysicsTaskScheduler>(mPhysicsDt, mCollisionWorld);
|
||||
mDebugDrawer = std::make_unique<MWRender::DebugDrawer>(mParentNode, mCollisionWorld.get(), mDebugDrawEnabled);
|
||||
mTaskScheduler = std::make_unique<PhysicsTaskScheduler>(mPhysicsDt, mCollisionWorld.get(), mDebugDrawer.get());
|
||||
}
|
||||
|
||||
PhysicsSystem::~PhysicsSystem()
|
||||
|
@ -371,16 +387,7 @@ namespace MWPhysics
|
|||
|
||||
bool PhysicsSystem::canMoveToWaterSurface(const MWWorld::ConstPtr &actor, const float waterlevel)
|
||||
{
|
||||
const Actor* physicActor = getActor(actor);
|
||||
if (!physicActor)
|
||||
return false;
|
||||
const float halfZ = physicActor->getHalfExtents().z();
|
||||
const osg::Vec3f actorPosition = physicActor->getPosition();
|
||||
const osg::Vec3f startingPosition(actorPosition.x(), actorPosition.y(), actorPosition.z() + halfZ);
|
||||
const osg::Vec3f destinationPosition(actorPosition.x(), actorPosition.y(), waterlevel + halfZ);
|
||||
ActorTracer tracer;
|
||||
tracer.doTrace(physicActor->getCollisionObject(), startingPosition, destinationPosition, mCollisionWorld.get());
|
||||
return (tracer.mFraction >= 1.0f);
|
||||
return ::canMoveToWaterSurface(getActor(actor), waterlevel, mCollisionWorld.get());
|
||||
}
|
||||
|
||||
osg::Vec3f PhysicsSystem::getHalfExtents(const MWWorld::ConstPtr &actor) const
|
||||
|
@ -768,19 +775,14 @@ namespace MWPhysics
|
|||
{
|
||||
mTimeAccum += dt;
|
||||
|
||||
const int maxAllowedSteps = 20;
|
||||
int numSteps = mTimeAccum / mPhysicsDt;
|
||||
numSteps = std::min(numSteps, maxAllowedSteps);
|
||||
|
||||
mTimeAccum -= numSteps * mPhysicsDt;
|
||||
|
||||
if (skipSimulation)
|
||||
return mTaskScheduler->resetSimulation(mActors);
|
||||
|
||||
return mTaskScheduler->moveActors(numSteps, mTimeAccum, prepareFrameData(numSteps), frameStart, frameNumber, stats);
|
||||
// modifies mTimeAccum
|
||||
return mTaskScheduler->moveActors(mTimeAccum, prepareFrameData(mTimeAccum >= mPhysicsDt), frameStart, frameNumber, stats);
|
||||
}
|
||||
|
||||
std::vector<ActorFrameData> PhysicsSystem::prepareFrameData(int numSteps)
|
||||
std::vector<ActorFrameData> PhysicsSystem::prepareFrameData(bool willSimulate)
|
||||
{
|
||||
std::vector<ActorFrameData> actorsFrameData;
|
||||
actorsFrameData.reserve(mMovementQueue.size());
|
||||
|
@ -801,16 +803,10 @@ namespace MWPhysics
|
|||
const MWMechanics::MagicEffects& effects = character.getClass().getCreatureStats(character).getMagicEffects();
|
||||
|
||||
bool waterCollision = false;
|
||||
bool moveToWaterSurface = false;
|
||||
if (cell->getCell()->hasWater() && effects.get(ESM::MagicEffect::WaterWalking).getMagnitude())
|
||||
{
|
||||
if (!world->isUnderwater(character.getCell(), osg::Vec3f(character.getRefData().getPosition().asVec3())))
|
||||
if (physicActor->getCollisionMode() || !world->isUnderwater(character.getCell(), osg::Vec3f(character.getRefData().getPosition().asVec3())))
|
||||
waterCollision = true;
|
||||
else if (physicActor->getCollisionMode() && canMoveToWaterSurface(character, waterlevel))
|
||||
{
|
||||
moveToWaterSurface = true;
|
||||
waterCollision = true;
|
||||
}
|
||||
}
|
||||
|
||||
physicActor->setCanWaterWalk(waterCollision);
|
||||
|
@ -820,10 +816,10 @@ namespace MWPhysics
|
|||
|
||||
// Ue current value only if we don't advance the simulation. Otherwise we might get a stale value.
|
||||
MWWorld::Ptr standingOn;
|
||||
if (numSteps == 0)
|
||||
if (!willSimulate)
|
||||
standingOn = physicActor->getStandingOnPtr();
|
||||
|
||||
actorsFrameData.emplace_back(std::move(physicActor), standingOn, moveToWaterSurface, movement, slowFall, waterlevel);
|
||||
actorsFrameData.emplace_back(std::move(physicActor), standingOn, waterCollision, movement, slowFall, waterlevel);
|
||||
}
|
||||
mMovementQueue.clear();
|
||||
return actorsFrameData;
|
||||
|
@ -856,7 +852,7 @@ namespace MWPhysics
|
|||
void PhysicsSystem::debugDraw()
|
||||
{
|
||||
if (mDebugDrawEnabled)
|
||||
mDebugDrawer->step();
|
||||
mTaskScheduler->debugDraw();
|
||||
}
|
||||
|
||||
bool PhysicsSystem::isActorStandingOn(const MWWorld::Ptr &actor, const MWWorld::ConstPtr &object) const
|
||||
|
@ -966,9 +962,9 @@ namespace MWPhysics
|
|||
}
|
||||
|
||||
ActorFrameData::ActorFrameData(const std::shared_ptr<Actor>& actor, const MWWorld::Ptr standingOn,
|
||||
bool moveToWaterSurface, osg::Vec3f movement, float slowFall, float waterlevel)
|
||||
bool waterCollision, osg::Vec3f movement, float slowFall, float waterlevel)
|
||||
: mActor(actor), mActorRaw(actor.get()), mStandingOn(standingOn),
|
||||
mDidJump(false), mNeedLand(false), mMoveToWaterSurface(moveToWaterSurface),
|
||||
mDidJump(false), mNeedLand(false), mWaterCollision(waterCollision),
|
||||
mWaterlevel(waterlevel), mSlowFall(slowFall), mOldHeight(0), mFallHeight(0), mMovement(movement), mPosition(), mRefpos()
|
||||
{
|
||||
const MWBase::World *world = MWBase::Environment::get().getWorld();
|
||||
|
@ -982,7 +978,7 @@ namespace MWPhysics
|
|||
mWasOnGround = actor->getOnGround();
|
||||
}
|
||||
|
||||
void ActorFrameData::updatePosition()
|
||||
void ActorFrameData::updatePosition(btCollisionWorld* world)
|
||||
{
|
||||
mActorRaw->updateWorldPosition();
|
||||
// If physics runs "fast enough", position are interpolated without simulation
|
||||
|
@ -990,10 +986,10 @@ namespace MWPhysics
|
|||
// regardless of simulation speed.
|
||||
mActorRaw->applyOffsetChange();
|
||||
mPosition = mActorRaw->getPosition();
|
||||
if (mMoveToWaterSurface)
|
||||
if (mWaterCollision && mPosition.z() < mWaterlevel && canMoveToWaterSurface(mActorRaw, mWaterlevel, world))
|
||||
{
|
||||
mPosition.z() = mWaterlevel;
|
||||
MWBase::Environment::get().getWorld()->moveObject(mActorRaw->getPtr(), mPosition.x(), mPosition.y(), mPosition.z());
|
||||
MWBase::Environment::get().getWorld()->moveObject(mActorRaw->getPtr(), mPosition.x(), mPosition.y(), mPosition.z(), false);
|
||||
}
|
||||
mOldHeight = mPosition.z();
|
||||
mRefpos = mActorRaw->getPtr().getRefData().getPosition();
|
||||
|
|
|
@ -79,7 +79,7 @@ namespace MWPhysics
|
|||
struct ActorFrameData
|
||||
{
|
||||
ActorFrameData(const std::shared_ptr<Actor>& actor, const MWWorld::Ptr standingOn, bool moveToWaterSurface, osg::Vec3f movement, float slowFall, float waterlevel);
|
||||
void updatePosition();
|
||||
void updatePosition(btCollisionWorld* world);
|
||||
std::weak_ptr<Actor> mActor;
|
||||
Actor* mActorRaw;
|
||||
MWWorld::Ptr mStandingOn;
|
||||
|
@ -90,7 +90,7 @@ namespace MWPhysics
|
|||
bool mDidJump;
|
||||
bool mFloatToSurface;
|
||||
bool mNeedLand;
|
||||
bool mMoveToWaterSurface;
|
||||
bool mWaterCollision;
|
||||
float mWaterlevel;
|
||||
float mSlowFall;
|
||||
float mOldHeight;
|
||||
|
@ -262,14 +262,14 @@ namespace MWPhysics
|
|||
|
||||
void updateWater();
|
||||
|
||||
std::vector<ActorFrameData> prepareFrameData(int numSteps);
|
||||
std::vector<ActorFrameData> prepareFrameData(bool willSimulate);
|
||||
|
||||
osg::ref_ptr<SceneUtil::UnrefQueue> mUnrefQueue;
|
||||
|
||||
std::unique_ptr<btBroadphaseInterface> mBroadphase;
|
||||
std::unique_ptr<btDefaultCollisionConfiguration> mCollisionConfiguration;
|
||||
std::unique_ptr<btCollisionDispatcher> mDispatcher;
|
||||
std::shared_ptr<btCollisionWorld> mCollisionWorld;
|
||||
std::unique_ptr<btCollisionWorld> mCollisionWorld;
|
||||
std::unique_ptr<PhysicsTaskScheduler> mTaskScheduler;
|
||||
|
||||
std::unique_ptr<Resource::BulletShapeManager> mShapeManager;
|
||||
|
|
|
@ -102,7 +102,7 @@ public:
|
|||
BlendMask_All = BlendMask_LowerBody | BlendMask_UpperBody
|
||||
};
|
||||
/* This is the number of *discrete* blend masks. */
|
||||
static const size_t sNumBlendMasks = 4;
|
||||
static constexpr size_t sNumBlendMasks = 4;
|
||||
|
||||
/// Holds an animation priority value for each BoneGroup.
|
||||
struct AnimPriority
|
||||
|
|
|
@ -90,7 +90,7 @@ LocalMap::LocalMap(osg::Group* root)
|
|||
{
|
||||
// Increase map resolution, if use UI scaling
|
||||
float uiScale = Settings::Manager::getFloat("scaling factor", "GUI");
|
||||
if (uiScale > 1.0)
|
||||
if (uiScale > 0.f)
|
||||
mMapResolution *= uiScale;
|
||||
|
||||
SceneUtil::FindByNameVisitor find("Scene Root");
|
||||
|
|
|
@ -63,7 +63,7 @@ namespace MWRender
|
|||
{
|
||||
std::set<ESM::RefNum> mDisabled;
|
||||
std::set<ESM::RefNum> mBlacklist;
|
||||
bool operator==(const RefTracker&other) { return mDisabled == other.mDisabled && mBlacklist == other.mBlacklist; }
|
||||
bool operator==(const RefTracker&other) const { return mDisabled == other.mDisabled && mBlacklist == other.mBlacklist; }
|
||||
};
|
||||
RefTracker mRefTracker;
|
||||
RefTracker mRefTrackerNew;
|
||||
|
|
|
@ -815,6 +815,7 @@ namespace MWRender
|
|||
RenderingManager::RayResult result;
|
||||
result.mHit = false;
|
||||
result.mHitRefnum.mContentFile = -1;
|
||||
result.mHitRefnum.mIndex = -1;
|
||||
result.mRatio = 0;
|
||||
if (intersector->containsIntersections())
|
||||
{
|
||||
|
|
|
@ -45,6 +45,9 @@
|
|||
#include <components/sceneutil/visitor.hpp>
|
||||
#include <components/sceneutil/shadow.hpp>
|
||||
|
||||
#include <components/settings/settings.hpp>
|
||||
#include <components/misc/stringops.hpp>
|
||||
|
||||
#include <components/nifosg/particle.hpp>
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
|
@ -1164,7 +1167,7 @@ void SkyManager::create()
|
|||
{
|
||||
assert(!mCreated);
|
||||
|
||||
mAtmosphereDay = mSceneManager->getInstance("meshes/sky_atmosphere.nif", mEarlyRenderBinRoot);
|
||||
mAtmosphereDay = mSceneManager->getInstance(Settings::Manager::getString("skyatmosphere", "Models"), mEarlyRenderBinRoot);
|
||||
ModVertexAlphaVisitor modAtmosphere(0);
|
||||
mAtmosphereDay->accept(modAtmosphere);
|
||||
|
||||
|
@ -1176,10 +1179,10 @@ void SkyManager::create()
|
|||
mEarlyRenderBinRoot->addChild(mAtmosphereNightNode);
|
||||
|
||||
osg::ref_ptr<osg::Node> atmosphereNight;
|
||||
if (mSceneManager->getVFS()->exists("meshes/sky_night_02.nif"))
|
||||
atmosphereNight = mSceneManager->getInstance("meshes/sky_night_02.nif", mAtmosphereNightNode);
|
||||
if (mSceneManager->getVFS()->exists(Settings::Manager::getString("skynight02", "Models")))
|
||||
atmosphereNight = mSceneManager->getInstance(Settings::Manager::getString("skynight02", "Models"), mAtmosphereNightNode);
|
||||
else
|
||||
atmosphereNight = mSceneManager->getInstance("meshes/sky_night_01.nif", mAtmosphereNightNode);
|
||||
atmosphereNight = mSceneManager->getInstance(Settings::Manager::getString("skynight01", "Models"), mAtmosphereNightNode);
|
||||
atmosphereNight->getOrCreateStateSet()->setAttributeAndModes(createAlphaTrackingUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE);
|
||||
ModVertexAlphaVisitor modStars(2);
|
||||
atmosphereNight->accept(modStars);
|
||||
|
@ -1193,14 +1196,14 @@ void SkyManager::create()
|
|||
|
||||
mCloudNode = new osg::PositionAttitudeTransform;
|
||||
mEarlyRenderBinRoot->addChild(mCloudNode);
|
||||
mCloudMesh = mSceneManager->getInstance("meshes/sky_clouds_01.nif", mCloudNode);
|
||||
mCloudMesh = mSceneManager->getInstance(Settings::Manager::getString("skyclouds", "Models"), mCloudNode);
|
||||
ModVertexAlphaVisitor modClouds(1);
|
||||
mCloudMesh->accept(modClouds);
|
||||
mCloudUpdater = new CloudUpdater;
|
||||
mCloudUpdater->setOpacity(1.f);
|
||||
mCloudMesh->addUpdateCallback(mCloudUpdater);
|
||||
|
||||
mCloudMesh2 = mSceneManager->getInstance("meshes/sky_clouds_01.nif", mCloudNode);
|
||||
mCloudMesh2 = mSceneManager->getInstance(Settings::Manager::getString("skyclouds", "Models"), mCloudNode);
|
||||
mCloudMesh2->accept(modClouds);
|
||||
mCloudUpdater2 = new CloudUpdater;
|
||||
mCloudUpdater2->setOpacity(0.f);
|
||||
|
@ -1597,7 +1600,7 @@ void SkyManager::update(float duration)
|
|||
if (mParticleNode)
|
||||
{
|
||||
// Morrowind deliberately rotates the blizzard mesh, so so should we.
|
||||
if (mCurrentParticleEffect == "meshes\\blizzard.nif")
|
||||
if (mCurrentParticleEffect == Settings::Manager::getString("weatherblizzard", "Models"))
|
||||
quat.makeRotate(osg::Vec3f(-1,0,0), mStormDirection);
|
||||
mParticleNode->setAttitude(quat);
|
||||
}
|
||||
|
@ -1897,16 +1900,16 @@ void SkyManager::setWaterHeight(float height)
|
|||
|
||||
void SkyManager::listAssetsToPreload(std::vector<std::string>& models, std::vector<std::string>& textures)
|
||||
{
|
||||
models.emplace_back("meshes/sky_atmosphere.nif");
|
||||
if (mSceneManager->getVFS()->exists("meshes/sky_night_02.nif"))
|
||||
models.emplace_back("meshes/sky_night_02.nif");
|
||||
models.emplace_back("meshes/sky_night_01.nif");
|
||||
models.emplace_back("meshes/sky_clouds_01.nif");
|
||||
models.emplace_back(Settings::Manager::getString("skyatmosphere", "Models"));
|
||||
if (mSceneManager->getVFS()->exists(Settings::Manager::getString("skynight02", "Models")))
|
||||
models.emplace_back(Settings::Manager::getString("skynight02", "Models"));
|
||||
models.emplace_back(Settings::Manager::getString("skynight01", "Models"));
|
||||
models.emplace_back(Settings::Manager::getString("skyclouds", "Models"));
|
||||
|
||||
models.emplace_back("meshes\\ashcloud.nif");
|
||||
models.emplace_back("meshes\\blightcloud.nif");
|
||||
models.emplace_back("meshes\\snow.nif");
|
||||
models.emplace_back("meshes\\blizzard.nif");
|
||||
models.emplace_back(Settings::Manager::getString("weatherashcloud", "Models"));
|
||||
models.emplace_back(Settings::Manager::getString("weatherblightcloud", "Models"));
|
||||
models.emplace_back(Settings::Manager::getString("weathersnow", "Models"));
|
||||
models.emplace_back(Settings::Manager::getString("weatherblizzard", "Models"));
|
||||
|
||||
textures.emplace_back("textures/tx_mooncircle_full_s.dds");
|
||||
textures.emplace_back("textures/tx_mooncircle_full_m.dds");
|
||||
|
|
|
@ -109,7 +109,7 @@ namespace MWScript
|
|||
runtime.pop();
|
||||
|
||||
if (count<0)
|
||||
throw std::runtime_error ("second argument for AddItem must be non-negative");
|
||||
count = static_cast<uint16_t>(count);
|
||||
|
||||
// no-op
|
||||
if (count == 0)
|
||||
|
|
|
@ -250,13 +250,20 @@ namespace MWScript
|
|||
}
|
||||
}
|
||||
|
||||
bool GlobalScripts::readRecord (ESM::ESMReader& reader, uint32_t type)
|
||||
bool GlobalScripts::readRecord (ESM::ESMReader& reader, uint32_t type, const std::map<int, int>& contentFileMap)
|
||||
{
|
||||
if (type==ESM::REC_GSCR)
|
||||
{
|
||||
ESM::GlobalScript script;
|
||||
script.load (reader);
|
||||
|
||||
if (script.mTargetRef.hasContentFile())
|
||||
{
|
||||
auto iter = contentFileMap.find(script.mTargetRef.mContentFile);
|
||||
if (iter != contentFileMap.end())
|
||||
script.mTargetRef.mContentFile = iter->second;
|
||||
}
|
||||
|
||||
auto iter = mScripts.find (script.mId);
|
||||
|
||||
if (iter==mScripts.end())
|
||||
|
|
|
@ -73,7 +73,7 @@ namespace MWScript
|
|||
|
||||
void write (ESM::ESMWriter& writer, Loading::Listener& progress) const;
|
||||
|
||||
bool readRecord (ESM::ESMReader& reader, uint32_t type);
|
||||
bool readRecord (ESM::ESMReader& reader, uint32_t type, const std::map<int, int>& contentFileMap);
|
||||
///< Records for variables that do not exist are dropped silently.
|
||||
///
|
||||
/// \return Known type?
|
||||
|
|
|
@ -10,16 +10,6 @@
|
|||
|
||||
#include "../mwworld/ptr.hpp"
|
||||
|
||||
namespace MWSound
|
||||
{
|
||||
class SoundManager;
|
||||
}
|
||||
|
||||
namespace MWInput
|
||||
{
|
||||
struct MWInputManager;
|
||||
}
|
||||
|
||||
namespace MWScript
|
||||
{
|
||||
class Locals;
|
||||
|
|
|
@ -14,7 +14,7 @@ namespace MWScript
|
|||
{
|
||||
struct ExplicitRef
|
||||
{
|
||||
static const bool implicit = false;
|
||||
static constexpr bool implicit = false;
|
||||
|
||||
MWWorld::Ptr operator() (Interpreter::Runtime& runtime, bool required = true,
|
||||
bool activeOnly = false) const;
|
||||
|
@ -22,7 +22,7 @@ namespace MWScript
|
|||
|
||||
struct ImplicitRef
|
||||
{
|
||||
static const bool implicit = true;
|
||||
static constexpr bool implicit = true;
|
||||
|
||||
MWWorld::Ptr operator() (Interpreter::Runtime& runtime, bool required = true,
|
||||
bool activeOnly = false) const;
|
||||
|
|
|
@ -49,7 +49,7 @@ namespace MWScript
|
|||
std::vector<MWWorld::Ptr> actors;
|
||||
MWBase::Environment::get().getWorld()->getActorsStandingOn (ptr, actors);
|
||||
for (auto& actor : actors)
|
||||
MWBase::Environment::get().getWorld()->moveObjectBy(actor, diff);
|
||||
MWBase::Environment::get().getWorld()->moveObjectBy(actor, diff, false);
|
||||
}
|
||||
|
||||
template<class R>
|
||||
|
@ -363,7 +363,7 @@ namespace MWScript
|
|||
}
|
||||
|
||||
dynamic_cast<MWScript::InterpreterContext&>(runtime.getContext()).updatePtr(ptr,
|
||||
MWBase::Environment::get().getWorld()->moveObjectBy(ptr, newPos - curPos));
|
||||
MWBase::Environment::get().getWorld()->moveObjectBy(ptr, newPos - curPos, true));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -499,7 +499,7 @@ namespace MWScript
|
|||
}
|
||||
else
|
||||
{
|
||||
ptr = MWBase::Environment::get().getWorld()->moveObject(ptr, x, y, z, true);
|
||||
ptr = MWBase::Environment::get().getWorld()->moveObject(ptr, x, y, z, true, true);
|
||||
}
|
||||
dynamic_cast<MWScript::InterpreterContext&>(runtime.getContext()).updatePtr(base,ptr);
|
||||
|
||||
|
@ -902,7 +902,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()->moveObjectBy(ptr, diff));
|
||||
MWBase::Environment::get().getWorld()->moveObjectBy(ptr, diff, false));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -938,7 +938,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()->moveObjectBy(ptr, diff));
|
||||
MWBase::Environment::get().getWorld()->moveObjectBy(ptr, diff, false));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -69,15 +69,13 @@ namespace MWSound
|
|||
FFmpeg_Decoder& operator=(const FFmpeg_Decoder &rhs);
|
||||
FFmpeg_Decoder(const FFmpeg_Decoder &rhs);
|
||||
|
||||
FFmpeg_Decoder(const VFS::Manager* vfs);
|
||||
public:
|
||||
explicit FFmpeg_Decoder(const VFS::Manager* vfs);
|
||||
|
||||
virtual ~FFmpeg_Decoder();
|
||||
|
||||
friend class SoundManager;
|
||||
};
|
||||
#ifndef DEFAULT_DECODER
|
||||
#define DEFAULT_DECODER (::MWSound::FFmpeg_Decoder)
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -98,9 +98,6 @@ namespace MWSound
|
|||
OpenAL_Output(SoundManager &mgr);
|
||||
virtual ~OpenAL_Output();
|
||||
};
|
||||
#ifndef DEFAULT_OUTPUT
|
||||
#define DEFAULT_OUTPUT(x) ::MWSound::OpenAL_Output((x))
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -54,7 +54,7 @@ namespace MWSound
|
|||
|
||||
SoundManager::SoundManager(const VFS::Manager* vfs, bool useSound)
|
||||
: mVFS(vfs)
|
||||
, mOutput(new DEFAULT_OUTPUT(*this))
|
||||
, mOutput(new OpenAL_Output(*this))
|
||||
, mWaterSoundUpdater(makeWaterSoundUpdaterSettings())
|
||||
, mSoundBuffers(*vfs, *mOutput)
|
||||
, mListenerUnderwater(false)
|
||||
|
@ -109,7 +109,7 @@ namespace MWSound
|
|||
|
||||
SoundManager::~SoundManager()
|
||||
{
|
||||
clear();
|
||||
SoundManager::clear();
|
||||
mSoundBuffers.clear();
|
||||
mOutput.reset();
|
||||
}
|
||||
|
@ -117,7 +117,7 @@ namespace MWSound
|
|||
// Return a new decoder instance, used as needed by the output implementations
|
||||
DecoderPtr SoundManager::getDecoder()
|
||||
{
|
||||
return DecoderPtr(new DEFAULT_DECODER (mVFS));
|
||||
return std::make_shared<FFmpeg_Decoder>(mVFS);
|
||||
}
|
||||
|
||||
DecoderPtr SoundManager::loadVoice(const std::string &voicefile)
|
||||
|
|
|
@ -141,7 +141,7 @@ namespace MWSound
|
|||
|
||||
public:
|
||||
SoundManager(const VFS::Manager* vfs, bool useSound);
|
||||
virtual ~SoundManager();
|
||||
~SoundManager() override;
|
||||
|
||||
void processChangedSettings(const Settings::CategorySettingVector& settings) override;
|
||||
|
||||
|
|
|
@ -18,14 +18,14 @@ void MWState::QuickSaveManager::visitSave(const Slot *saveSlot)
|
|||
}
|
||||
}
|
||||
|
||||
bool MWState::QuickSaveManager::isOldestSave(const Slot *compare)
|
||||
bool MWState::QuickSaveManager::isOldestSave(const Slot *compare) const
|
||||
{
|
||||
if(mOldestSlotVisited == nullptr)
|
||||
return true;
|
||||
return (compare->mTimeStamp <= mOldestSlotVisited->mTimeStamp);
|
||||
}
|
||||
|
||||
bool MWState::QuickSaveManager::shouldCreateNewSlot()
|
||||
bool MWState::QuickSaveManager::shouldCreateNewSlot() const
|
||||
{
|
||||
return (mSlotsVisited < mMaxSaves);
|
||||
}
|
||||
|
|
|
@ -13,8 +13,8 @@ namespace MWState{
|
|||
unsigned int mSlotsVisited;
|
||||
const Slot *mOldestSlotVisited;
|
||||
private:
|
||||
bool shouldCreateNewSlot();
|
||||
bool isOldestSave(const Slot *compare);
|
||||
bool shouldCreateNewSlot() const;
|
||||
bool isOldestSave(const Slot *compare) const;
|
||||
public:
|
||||
QuickSaveManager(std::string &saveName, unsigned int maxSaves);
|
||||
///< A utility class to manage multiple quicksave slots
|
||||
|
|
|
@ -471,7 +471,7 @@ void MWState::StateManager::loadGame (const Character *character, const std::str
|
|||
|
||||
case ESM::REC_GSCR:
|
||||
|
||||
MWBase::Environment::get().getScriptManager()->getGlobalScripts().readRecord (reader, n.intval);
|
||||
MWBase::Environment::get().getScriptManager()->getGlobalScripts().readRecord (reader, n.intval, contentFileMap);
|
||||
break;
|
||||
|
||||
case ESM::REC_GMAP:
|
||||
|
@ -515,6 +515,7 @@ void MWState::StateManager::loadGame (const Character *character, const std::str
|
|||
character->getPath().filename().string());
|
||||
|
||||
MWBase::Environment::get().getWindowManager()->setNewGame(false);
|
||||
MWBase::Environment::get().getWorld()->saveLoaded();
|
||||
MWBase::Environment::get().getWorld()->setupPlayer();
|
||||
MWBase::Environment::get().getWorld()->renderPlayer();
|
||||
MWBase::Environment::get().getWindowManager()->updatePlayer();
|
||||
|
|
|
@ -155,9 +155,11 @@ void MWWorld::Cells::writeCell (ESM::ESMWriter& writer, CellStore& cell) const
|
|||
|
||||
MWWorld::Cells::Cells (const MWWorld::ESMStore& store, std::vector<ESM::ESMReader>& reader)
|
||||
: mStore (store), mReader (reader),
|
||||
mIdCache (Settings::Manager::getInt("pointers cache size", "Cells"), std::pair<std::string, CellStore *> ("", (CellStore*)nullptr)),
|
||||
mIdCacheIndex (0)
|
||||
{}
|
||||
{
|
||||
int cacheSize = std::max(Settings::Manager::getInt("pointers cache size", "Cells"), 0);
|
||||
mIdCache = IdCache(cacheSize, std::pair<std::string, CellStore *> ("", (CellStore*)nullptr));
|
||||
}
|
||||
|
||||
MWWorld::CellStore *MWWorld::Cells::getExterior (int x, int y)
|
||||
{
|
||||
|
@ -282,8 +284,7 @@ MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name, CellStore& cell,
|
|||
MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name)
|
||||
{
|
||||
// First check the cache
|
||||
for (std::vector<std::pair<std::string, CellStore *> >::iterator iter (mIdCache.begin());
|
||||
iter!=mIdCache.end(); ++iter)
|
||||
for (IdCache::iterator iter (mIdCache.begin()); iter!=mIdCache.end(); ++iter)
|
||||
if (iter->first==name && iter->second)
|
||||
{
|
||||
Ptr ptr = getPtr (name, *iter->second);
|
||||
|
|
|
@ -28,11 +28,12 @@ namespace MWWorld
|
|||
/// \brief Cell container
|
||||
class Cells
|
||||
{
|
||||
typedef std::vector<std::pair<std::string, CellStore *> > IdCache;
|
||||
const MWWorld::ESMStore& mStore;
|
||||
std::vector<ESM::ESMReader>& mReader;
|
||||
mutable std::map<std::string, CellStore> mInteriors;
|
||||
mutable std::map<std::pair<int, int>, CellStore> mExteriors;
|
||||
std::vector<std::pair<std::string, CellStore *> > mIdCache;
|
||||
IdCache mIdCache;
|
||||
std::size_t mIdCacheIndex;
|
||||
|
||||
Cells (const Cells&);
|
||||
|
|
|
@ -89,11 +89,25 @@ namespace
|
|||
MWWorld::ResolutionListener::~ResolutionListener()
|
||||
{
|
||||
if(!mStore.mModified && mStore.mResolved && !mStore.mPtr.isEmpty())
|
||||
{
|
||||
try
|
||||
{
|
||||
for(const auto&& ptr : mStore)
|
||||
ptr.getRefData().setCount(0);
|
||||
}
|
||||
catch(const std::exception& e)
|
||||
{
|
||||
Log(Debug::Warning) << "Failed to clear temporary container contents of " << mStore.mPtr.get<ESM::Container>()->mBase->mId << ": " << e.what();
|
||||
}
|
||||
mStore.fillNonRandom(mStore.mPtr.get<ESM::Container>()->mBase->mInventory, "", mStore.mSeed);
|
||||
try
|
||||
{
|
||||
addScripts(mStore, mStore.mPtr.mCell);
|
||||
}
|
||||
catch(const std::exception& e)
|
||||
{
|
||||
Log(Debug::Warning) << "Failed to restart item scripts inside " << mStore.mPtr.get<ESM::Container>()->mBase->mId << ": " << e.what();
|
||||
}
|
||||
mStore.mResolved = false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -73,22 +73,22 @@ namespace MWWorld
|
|||
{
|
||||
public:
|
||||
|
||||
static const int Type_Potion = 0x0001;
|
||||
static const int Type_Apparatus = 0x0002;
|
||||
static const int Type_Armor = 0x0004;
|
||||
static const int Type_Book = 0x0008;
|
||||
static const int Type_Clothing = 0x0010;
|
||||
static const int Type_Ingredient = 0x0020;
|
||||
static const int Type_Light = 0x0040;
|
||||
static const int Type_Lockpick = 0x0080;
|
||||
static const int Type_Miscellaneous = 0x0100;
|
||||
static const int Type_Probe = 0x0200;
|
||||
static const int Type_Repair = 0x0400;
|
||||
static const int Type_Weapon = 0x0800;
|
||||
static constexpr int Type_Potion = 0x0001;
|
||||
static constexpr int Type_Apparatus = 0x0002;
|
||||
static constexpr int Type_Armor = 0x0004;
|
||||
static constexpr int Type_Book = 0x0008;
|
||||
static constexpr int Type_Clothing = 0x0010;
|
||||
static constexpr int Type_Ingredient = 0x0020;
|
||||
static constexpr int Type_Light = 0x0040;
|
||||
static constexpr int Type_Lockpick = 0x0080;
|
||||
static constexpr int Type_Miscellaneous = 0x0100;
|
||||
static constexpr int Type_Probe = 0x0200;
|
||||
static constexpr int Type_Repair = 0x0400;
|
||||
static constexpr int Type_Weapon = 0x0800;
|
||||
|
||||
static const int Type_Last = Type_Weapon;
|
||||
static constexpr int Type_Last = Type_Weapon;
|
||||
|
||||
static const int Type_All = 0xffff;
|
||||
static constexpr int Type_All = 0xffff;
|
||||
|
||||
static const std::string sGoldId;
|
||||
|
||||
|
@ -153,7 +153,7 @@ namespace MWWorld
|
|||
|
||||
virtual ~ContainerStore();
|
||||
|
||||
virtual ContainerStore* clone() { return new ContainerStore(*this); }
|
||||
virtual std::unique_ptr<ContainerStore> clone() { return std::make_unique<ContainerStore>(*this); }
|
||||
|
||||
ConstContainerStoreIterator cbegin (int mask = Type_All) const;
|
||||
ConstContainerStoreIterator cend() const;
|
||||
|
@ -276,13 +276,13 @@ namespace MWWorld
|
|||
template<class From, class To, class Dummy>
|
||||
struct IsConvertible
|
||||
{
|
||||
static const bool value = true;
|
||||
static constexpr bool value = true;
|
||||
};
|
||||
|
||||
template<class Dummy>
|
||||
struct IsConvertible<ConstPtr, Ptr, Dummy>
|
||||
{
|
||||
static const bool value = false;
|
||||
static constexpr bool value = false;
|
||||
};
|
||||
|
||||
template<class T, class U>
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
#ifndef GAME_MWWORLD_CUSTOMDATA_H
|
||||
#define GAME_MWWORLD_CUSTOMDATA_H
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace MWClass
|
||||
{
|
||||
class CreatureCustomData;
|
||||
|
@ -19,7 +21,7 @@ namespace MWWorld
|
|||
|
||||
virtual ~CustomData() {}
|
||||
|
||||
virtual CustomData *clone() const = 0;
|
||||
virtual std::unique_ptr<CustomData> clone() const = 0;
|
||||
|
||||
// Fast version of dynamic_cast<X&>. Needs to be overridden in the respective class.
|
||||
|
||||
|
@ -38,6 +40,15 @@ namespace MWWorld
|
|||
virtual MWClass::CreatureLevListCustomData& asCreatureLevListCustomData();
|
||||
virtual const MWClass::CreatureLevListCustomData& asCreatureLevListCustomData() const;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct TypedCustomData : CustomData
|
||||
{
|
||||
std::unique_ptr<CustomData> clone() const final
|
||||
{
|
||||
return std::make_unique<T>(*static_cast<const T*>(this));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -48,6 +48,57 @@ namespace
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<ESM::NPC> getNPCsToReplace(const MWWorld::Store<ESM::Faction>& factions, const MWWorld::Store<ESM::Class>& classes, const std::map<std::string, ESM::NPC>& npcs)
|
||||
{
|
||||
// Cache first class from store - we will use it if current class is not found
|
||||
std::string defaultCls;
|
||||
auto it = classes.begin();
|
||||
if (it != classes.end())
|
||||
defaultCls = it->mId;
|
||||
else
|
||||
throw std::runtime_error("List of NPC classes is empty!");
|
||||
|
||||
// Validate NPCs for non-existing class and faction.
|
||||
// We will replace invalid entries by fixed ones
|
||||
std::vector<ESM::NPC> npcsToReplace;
|
||||
|
||||
for (auto it : npcs)
|
||||
{
|
||||
ESM::NPC npc = it.second;
|
||||
bool changed = false;
|
||||
|
||||
const std::string npcFaction = npc.mFaction;
|
||||
if (!npcFaction.empty())
|
||||
{
|
||||
const ESM::Faction *fact = factions.search(npcFaction);
|
||||
if (!fact)
|
||||
{
|
||||
Log(Debug::Verbose) << "NPC '" << npc.mId << "' (" << npc.mName << ") has nonexistent faction '" << npc.mFaction << "', ignoring it.";
|
||||
npc.mFaction.clear();
|
||||
npc.mNpdt.mRank = 0;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
std::string npcClass = npc.mClass;
|
||||
if (!npcClass.empty())
|
||||
{
|
||||
const ESM::Class *cls = classes.search(npcClass);
|
||||
if (!cls)
|
||||
{
|
||||
Log(Debug::Verbose) << "NPC '" << npc.mId << "' (" << npc.mName << ") has nonexistent class '" << npc.mClass << "', using '" << defaultCls << "' class as replacement.";
|
||||
npc.mClass = defaultCls;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (changed)
|
||||
npcsToReplace.push_back(npc);
|
||||
}
|
||||
|
||||
return npcsToReplace;
|
||||
}
|
||||
}
|
||||
|
||||
namespace MWWorld
|
||||
|
@ -218,49 +269,7 @@ int ESMStore::getRefCount(const std::string& id) const
|
|||
|
||||
void ESMStore::validate()
|
||||
{
|
||||
// Cache first class from store - we will use it if current class is not found
|
||||
std::string defaultCls = "";
|
||||
Store<ESM::Class>::iterator it = mClasses.begin();
|
||||
if (it != mClasses.end())
|
||||
defaultCls = it->mId;
|
||||
else
|
||||
throw std::runtime_error("List of NPC classes is empty!");
|
||||
|
||||
// Validate NPCs for non-existing class and faction.
|
||||
// We will replace invalid entries by fixed ones
|
||||
std::vector<ESM::NPC> npcsToReplace;
|
||||
for (ESM::NPC npc : mNpcs)
|
||||
{
|
||||
bool changed = false;
|
||||
|
||||
const std::string npcFaction = npc.mFaction;
|
||||
if (!npcFaction.empty())
|
||||
{
|
||||
const ESM::Faction *fact = mFactions.search(npcFaction);
|
||||
if (!fact)
|
||||
{
|
||||
Log(Debug::Verbose) << "NPC '" << npc.mId << "' (" << npc.mName << ") has nonexistent faction '" << npc.mFaction << "', ignoring it.";
|
||||
npc.mFaction.clear();
|
||||
npc.mNpdt.mRank = 0;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
std::string npcClass = npc.mClass;
|
||||
if (!npcClass.empty())
|
||||
{
|
||||
const ESM::Class *cls = mClasses.search(npcClass);
|
||||
if (!cls)
|
||||
{
|
||||
Log(Debug::Verbose) << "NPC '" << npc.mId << "' (" << npc.mName << ") has nonexistent class '" << npc.mClass << "', using '" << defaultCls << "' class as replacement.";
|
||||
npc.mClass = defaultCls;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (changed)
|
||||
npcsToReplace.push_back(npc);
|
||||
}
|
||||
std::vector<ESM::NPC> npcsToReplace = getNPCsToReplace(mFactions, mClasses, mNpcs.mStatic);
|
||||
|
||||
for (const ESM::NPC &npc : npcsToReplace)
|
||||
{
|
||||
|
@ -331,6 +340,14 @@ void ESMStore::validate()
|
|||
}
|
||||
}
|
||||
|
||||
void ESMStore::validateDynamic()
|
||||
{
|
||||
std::vector<ESM::NPC> npcsToReplace = getNPCsToReplace(mFactions, mClasses, mNpcs.mDynamic);
|
||||
|
||||
for (const ESM::NPC &npc : npcsToReplace)
|
||||
mNpcs.insert(npc);
|
||||
}
|
||||
|
||||
int ESMStore::countSavedGameRecords() const
|
||||
{
|
||||
return 1 // DYNA (dynamic name counter)
|
||||
|
@ -384,12 +401,14 @@ void ESMStore::validate()
|
|||
case ESM::REC_ENCH:
|
||||
case ESM::REC_SPEL:
|
||||
case ESM::REC_WEAP:
|
||||
case ESM::REC_NPC_:
|
||||
case ESM::REC_LEVI:
|
||||
case ESM::REC_LEVC:
|
||||
mStores[type]->read (reader);
|
||||
return true;
|
||||
case ESM::REC_NPC_:
|
||||
case ESM::REC_CREA:
|
||||
case ESM::REC_CONT:
|
||||
mStores[type]->read (reader);
|
||||
mStores[type]->read (reader, true);
|
||||
return true;
|
||||
|
||||
case ESM::REC_DYNA:
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue