mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-01-16 19:19:56 +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:
|
paths:
|
||||||
- build/install/
|
- 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:
|
Debian_GCC:
|
||||||
extends: .Debian
|
extends: .Debian
|
||||||
cache:
|
cache:
|
||||||
|
@ -93,24 +116,49 @@ Debian_Clang_tests:
|
||||||
CCACHE_SIZE: 1G
|
CCACHE_SIZE: 1G
|
||||||
BUILD_TESTS_ONLY: 1
|
BUILD_TESTS_ONLY: 1
|
||||||
|
|
||||||
MacOS:
|
.MacOS:
|
||||||
|
image: macos-11-xcode-12
|
||||||
tags:
|
tags:
|
||||||
- macos
|
- macos
|
||||||
stage: build
|
stage: build
|
||||||
only:
|
only:
|
||||||
variables:
|
variables:
|
||||||
- $CI_PROJECT_ID == "7107382"
|
- $CI_PROJECT_ID == "7107382"
|
||||||
|
cache:
|
||||||
|
paths:
|
||||||
|
- ccache/
|
||||||
script:
|
script:
|
||||||
- rm -fr build/* # remove anything in the build directory
|
- 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_install.osx.sh
|
||||||
- CI/before_script.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
|
- for dmg in *.dmg; do mv "$dmg" "${dmg%.dmg}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}.dmg"; done
|
||||||
|
- ccache -s
|
||||||
artifacts:
|
artifacts:
|
||||||
paths:
|
paths:
|
||||||
- build/OpenMW-*.dmg
|
- build/OpenMW-*.dmg
|
||||||
- "build/**/*.log"
|
- "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
|
variables: &engine-targets
|
||||||
targets: "openmw,openmw-essimporter,openmw-iniimporter,openmw-launcher,openmw-wizard"
|
targets: "openmw,openmw-essimporter,openmw-iniimporter,openmw-launcher,openmw-wizard"
|
||||||
package: "Engine"
|
package: "Engine"
|
||||||
|
|
|
@ -18,7 +18,7 @@ addons:
|
||||||
- ubuntu-toolchain-r-test
|
- ubuntu-toolchain-r-test
|
||||||
packages: [
|
packages: [
|
||||||
# Dev
|
# Dev
|
||||||
cmake, clang-tools, gcc-8, g++-8, ccache,
|
cmake, clang-tools-7, gcc-8, g++-8, ccache,
|
||||||
# Boost
|
# Boost
|
||||||
libboost-filesystem-dev, libboost-iostreams-dev, libboost-program-options-dev, libboost-system-dev,
|
libboost-filesystem-dev, libboost-iostreams-dev, libboost-program-options-dev, libboost-system-dev,
|
||||||
# FFmpeg
|
# FFmpeg
|
||||||
|
@ -97,4 +97,4 @@ script:
|
||||||
# - "chat.freenode.net#openmw"
|
# - "chat.freenode.net#openmw"
|
||||||
# on_success: change
|
# on_success: change
|
||||||
# on_failure: always
|
# on_failure: always
|
||||||
# use_notice: true
|
# use_notice: true
|
||||||
|
|
10
AUTHORS.md
10
AUTHORS.md
|
@ -4,7 +4,7 @@ Contributors
|
||||||
The OpenMW project was started in 2008 by Nicolay Korslund.
|
The OpenMW project was started in 2008 by Nicolay Korslund.
|
||||||
In the course of years many people have contributed to the project.
|
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
|
Programmers
|
||||||
|
@ -24,7 +24,7 @@ Programmers
|
||||||
Alex McKibben
|
Alex McKibben
|
||||||
alexanderkjall
|
alexanderkjall
|
||||||
Alexander Nadeau (wareya)
|
Alexander Nadeau (wareya)
|
||||||
Alexander Olofsson (Ace)
|
Alexander Olofsson (Ananace)
|
||||||
Alex Rice
|
Alex Rice
|
||||||
Alex S (docwest)
|
Alex S (docwest)
|
||||||
Allofich
|
Allofich
|
||||||
|
@ -131,6 +131,7 @@ Programmers
|
||||||
Martin Otto (MAtahualpa)
|
Martin Otto (MAtahualpa)
|
||||||
Mateusz Kołaczek (PL_kolek)
|
Mateusz Kołaczek (PL_kolek)
|
||||||
Mateusz Malisz (malice)
|
Mateusz Malisz (malice)
|
||||||
|
Max Henzerling (SaintMercury)
|
||||||
megaton
|
megaton
|
||||||
Michael Hogan (Xethik)
|
Michael Hogan (Xethik)
|
||||||
Michael Mc Donnell
|
Michael Mc Donnell
|
||||||
|
@ -183,6 +184,7 @@ Programmers
|
||||||
sergoz
|
sergoz
|
||||||
ShadowRadiance
|
ShadowRadiance
|
||||||
Siimacore
|
Siimacore
|
||||||
|
Simon Meulenbeek (simonmb)
|
||||||
sir_herrbatka
|
sir_herrbatka
|
||||||
smbas
|
smbas
|
||||||
Sophie Kirschner (pineapplemachine)
|
Sophie Kirschner (pineapplemachine)
|
||||||
|
@ -196,6 +198,7 @@ Programmers
|
||||||
Sylvain Thesnieres (Garvek)
|
Sylvain Thesnieres (Garvek)
|
||||||
t6
|
t6
|
||||||
terrorfisch
|
terrorfisch
|
||||||
|
Tess (tescoShoppah)
|
||||||
thegriglat
|
thegriglat
|
||||||
Thomas Luppi (Digmaster)
|
Thomas Luppi (Digmaster)
|
||||||
tlmullis
|
tlmullis
|
||||||
|
@ -234,7 +237,8 @@ Documentation
|
||||||
Packagers
|
Packagers
|
||||||
---------
|
---------
|
||||||
|
|
||||||
Alexander Olofsson (Ace) - Windows
|
Alexander Olofsson (Ananace) - Windows and Flatpak
|
||||||
|
Alexey Sokolov (DarthGandalf) - Gentoo Linux
|
||||||
Bret Curtis (psi29a) - Debian and Ubuntu Linux
|
Bret Curtis (psi29a) - Debian and Ubuntu Linux
|
||||||
Edmondo Tommasina (edmondo) - Gentoo Linux
|
Edmondo Tommasina (edmondo) - Gentoo Linux
|
||||||
Julian Ospald (hasufell) - 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 #5424: Creatures do not headtrack player
|
||||||
Bug #5425: Poison effect only appears for one frame
|
Bug #5425: Poison effect only appears for one frame
|
||||||
Bug #5427: GetDistance unknown ID error is misleading
|
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 #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 #5441: Enemies can't push a player character when in critical strike stance
|
||||||
Bug #5451: Magic projectiles don't disappear with the caster
|
Bug #5451: Magic projectiles don't disappear with the caster
|
||||||
|
@ -87,6 +88,7 @@
|
||||||
Bug #5656: Sneaking characters block hits while standing
|
Bug #5656: Sneaking characters block hits while standing
|
||||||
Bug #5661: Region sounds don't play at the right interval
|
Bug #5661: Region sounds don't play at the right interval
|
||||||
Bug #5675: OpenMW-cs. FRMR subrecords are saved with the wrong MastIdx
|
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 #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 #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
|
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 #5899: Visible modal windows and dropdowns crashing game on exit
|
||||||
Bug #5902: NiZBufferProperty is unable to disable the depth test
|
Bug #5902: NiZBufferProperty is unable to disable the depth test
|
||||||
Bug #5906: Sunglare doesn't work with Mesa drivers and AMD GPUs
|
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 #390: 3rd person look "over the shoulder"
|
||||||
Feature #832: OpenMW-CS: Handle deleted references
|
Feature #832: OpenMW-CS: Handle deleted references
|
||||||
Feature #1536: Show more information about level on menu
|
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 #2404: Levelled List can not be placed into a container
|
||||||
Feature #2686: Timestamps in openmw.log
|
Feature #2686: Timestamps in openmw.log
|
||||||
Feature #3171: OpenMW-CS: Instance drag selection
|
Feature #3171: OpenMW-CS: Instance drag selection
|
||||||
|
Feature #3983: Wizard: Add link to buy Morrowind
|
||||||
Feature #4894: Consider actors as obstacles for pathfinding
|
Feature #4894: Consider actors as obstacles for pathfinding
|
||||||
Feature #4899: Alpha-To-Coverage Anti-Aliasing for alpha testing
|
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
|
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 #5456: Basic collada animation support
|
||||||
Feature #5457: Realistic diagonal movement
|
Feature #5457: Realistic diagonal movement
|
||||||
Feature #5486: Fixes trainers to choose their training skills based on their base skill points
|
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 #5519: Code Patch tab in launcher
|
||||||
Feature #5524: Resume failed script execution after reload
|
Feature #5524: Resume failed script execution after reload
|
||||||
Feature #5545: Option to allow stealing from an unconscious NPC during combat
|
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 #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 #5771: ori command should report where a mesh is loaded from and whether the x version is used.
|
||||||
Feature #5813: Instanced groundcover support
|
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 #5480: Drop Qt4 support
|
||||||
Task #5520: Improve cell name autocompleter implementation
|
Task #5520: Improve cell name autocompleter implementation
|
||||||
Task #5844: Update 'toggle sneak' documentation
|
Task #5844: Update 'toggle sneak' documentation
|
||||||
|
|
|
@ -848,9 +848,11 @@ fi
|
||||||
wrappedExit 1
|
wrappedExit 1
|
||||||
fi
|
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..."
|
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
|
fi
|
||||||
popd > /dev/null
|
popd > /dev/null
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ cmake \
|
||||||
-D CMAKE_CXX_FLAGS="-stdlib=libc++" \
|
-D CMAKE_CXX_FLAGS="-stdlib=libc++" \
|
||||||
-D CMAKE_C_FLAGS_RELEASE="-g -O0" \
|
-D CMAKE_C_FLAGS_RELEASE="-g -O0" \
|
||||||
-D CMAKE_CXX_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 CMAKE_BUILD_TYPE=RELEASE \
|
||||||
-D OPENMW_OSX_DEPLOYMENT=TRUE \
|
-D OPENMW_OSX_DEPLOYMENT=TRUE \
|
||||||
-D BUILD_OPENMW=TRUE \
|
-D BUILD_OPENMW=TRUE \
|
||||||
|
|
|
@ -27,6 +27,8 @@ declare -rA GROUPED_DEPS=(
|
||||||
|
|
||||||
# These dependencies can alternatively be built and linked statically.
|
# These dependencies can alternatively be built and linked statically.
|
||||||
[openmw-deps-dynamic]="libmygui-dev libopenscenegraph-dev"
|
[openmw-deps-dynamic]="libmygui-dev libopenscenegraph-dev"
|
||||||
|
|
||||||
|
[coverity]="curl"
|
||||||
|
|
||||||
# Pre-requisites for building MyGUI and OSG for static linking.
|
# Pre-requisites for building MyGUI and OSG for static linking.
|
||||||
#
|
#
|
||||||
|
|
|
@ -20,6 +20,7 @@ struct Arguments
|
||||||
std::string mode;
|
std::string mode;
|
||||||
std::string filename;
|
std::string filename;
|
||||||
std::string extractfile;
|
std::string extractfile;
|
||||||
|
std::string addfile;
|
||||||
std::string outdir;
|
std::string outdir;
|
||||||
|
|
||||||
bool longformat;
|
bool longformat;
|
||||||
|
@ -36,6 +37,10 @@ bool parseOptions (int argc, char** argv, Arguments &info)
|
||||||
" Extract a file from the input archive.\n\n"
|
" Extract a file from the input archive.\n\n"
|
||||||
" bsatool extractall archivefile [output_directory]\n"
|
" bsatool extractall archivefile [output_directory]\n"
|
||||||
" Extract all files from the input archive.\n\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");
|
"Allowed options");
|
||||||
|
|
||||||
desc.add_options()
|
desc.add_options()
|
||||||
|
@ -95,7 +100,7 @@ bool parseOptions (int argc, char** argv, Arguments &info)
|
||||||
}
|
}
|
||||||
|
|
||||||
info.mode = variables["mode"].as<std::string>();
|
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"
|
std::cout << std::endl << "ERROR: invalid mode \"" << info.mode << "\"\n\n"
|
||||||
<< desc << std::endl;
|
<< 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)
|
if (variables["input-file"].as< std::vector<std::string> >().size() > 2)
|
||||||
info.outdir = variables["input-file"].as< std::vector<std::string> >()[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)
|
else if (variables["input-file"].as< std::vector<std::string> >().size() > 1)
|
||||||
info.outdir = variables["input-file"].as< std::vector<std::string> >()[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 list(std::unique_ptr<Bsa::BSAFile>& bsa, Arguments& info);
|
||||||
int extract(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 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)
|
int main(int argc, char** argv)
|
||||||
{
|
{
|
||||||
|
@ -157,6 +174,12 @@ int main(int argc, char** argv)
|
||||||
else
|
else
|
||||||
bsa = std::make_unique<Bsa::BSAFile>(Bsa::BSAFile());
|
bsa = std::make_unique<Bsa::BSAFile>(Bsa::BSAFile());
|
||||||
|
|
||||||
|
if (info.mode == "create")
|
||||||
|
{
|
||||||
|
bsa->open(info.filename);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
bsa->open(info.filename);
|
bsa->open(info.filename);
|
||||||
|
|
||||||
if (info.mode == "list")
|
if (info.mode == "list")
|
||||||
|
@ -165,6 +188,8 @@ int main(int argc, char** argv)
|
||||||
return extract(bsa, info);
|
return extract(bsa, info);
|
||||||
else if (info.mode == "extractall")
|
else if (info.mode == "extractall")
|
||||||
return extractAll(bsa, info);
|
return extractAll(bsa, info);
|
||||||
|
else if (info.mode == "add")
|
||||||
|
return add(bsa, info);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
std::cout << "Unsupported mode. That is not supposed to happen." << std::endl;
|
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
|
// Long format
|
||||||
std::ios::fmtflags f(std::cout.flags());
|
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 << std::setw(8) << std::left << std::dec << file.fileSize;
|
||||||
std::cout << "@ 0x" << std::hex << file.offset << std::endl;
|
std::cout << "@ 0x" << std::hex << file.offset << std::endl;
|
||||||
std::cout.flags(f);
|
std::cout.flags(f);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
std::cout << file.name << std::endl;
|
std::cout << file.name() << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -253,7 +278,7 @@ int extractAll(std::unique_ptr<Bsa::BSAFile>& bsa, Arguments& info)
|
||||||
{
|
{
|
||||||
for (const auto &file : bsa->getList())
|
for (const auto &file : bsa->getList())
|
||||||
{
|
{
|
||||||
std::string extractPath(file.name);
|
std::string extractPath(file.name());
|
||||||
Misc::StringUtils::replaceAll(extractPath, "\\", "/");
|
Misc::StringUtils::replaceAll(extractPath, "\\", "/");
|
||||||
|
|
||||||
// Get the target path (the path the file will be extracted to)
|
// 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
|
// Get a stream for the file to extract
|
||||||
// (inefficient because getFile iter on the list again)
|
// (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);
|
bfs::ofstream out(target, std::ios::binary);
|
||||||
|
|
||||||
// Write the file to disk
|
// Write the file to disk
|
||||||
|
@ -283,3 +308,11 @@ int extractAll(std::unique_ptr<Bsa::BSAFile>& bsa, Arguments& info)
|
||||||
|
|
||||||
return 0;
|
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/profilescombobox.cpp
|
||||||
utils/textinputdialog.cpp
|
utils/textinputdialog.cpp
|
||||||
utils/lineedit.cpp
|
utils/lineedit.cpp
|
||||||
|
utils/openalutil.cpp
|
||||||
|
|
||||||
${CMAKE_SOURCE_DIR}/files/windows/launcher.rc
|
${CMAKE_SOURCE_DIR}/files/windows/launcher.rc
|
||||||
)
|
)
|
||||||
|
@ -31,6 +32,7 @@ set(LAUNCHER_HEADER
|
||||||
utils/profilescombobox.hpp
|
utils/profilescombobox.hpp
|
||||||
utils/textinputdialog.hpp
|
utils/textinputdialog.hpp
|
||||||
utils/lineedit.hpp
|
utils/lineedit.hpp
|
||||||
|
utils/openalutil.hpp
|
||||||
)
|
)
|
||||||
|
|
||||||
# Headers that must be pre-processed
|
# Headers that must be pre-processed
|
||||||
|
@ -47,6 +49,7 @@ set(LAUNCHER_HEADER_MOC
|
||||||
utils/textinputdialog.hpp
|
utils/textinputdialog.hpp
|
||||||
utils/profilescombobox.hpp
|
utils/profilescombobox.hpp
|
||||||
utils/lineedit.hpp
|
utils/lineedit.hpp
|
||||||
|
utils/openalutil.hpp
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -95,6 +98,7 @@ endif (WIN32)
|
||||||
|
|
||||||
target_link_libraries(openmw-launcher
|
target_link_libraries(openmw-launcher
|
||||||
${SDL2_LIBRARY_ONLY}
|
${SDL2_LIBRARY_ONLY}
|
||||||
|
${OPENAL_LIBRARY}
|
||||||
components
|
components
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -5,11 +5,14 @@
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
#include <QCompleter>
|
#include <QCompleter>
|
||||||
#include <QProxyStyle>
|
#include <QProxyStyle>
|
||||||
|
#include <QString>
|
||||||
#include <components/contentselector/view/contentselector.hpp>
|
#include <components/contentselector/view/contentselector.hpp>
|
||||||
#include <components/contentselector/model/esmfile.hpp>
|
#include <components/contentselector/model/esmfile.hpp>
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
|
#include "utils/openalutil.hpp"
|
||||||
|
|
||||||
Launcher::AdvancedPage::AdvancedPage(Config::GameSettings &gameSettings,
|
Launcher::AdvancedPage::AdvancedPage(Config::GameSettings &gameSettings,
|
||||||
Settings::Manager &engineSettings, QWidget *parent)
|
Settings::Manager &engineSettings, QWidget *parent)
|
||||||
: QWidget(parent)
|
: QWidget(parent)
|
||||||
|
@ -19,7 +22,17 @@ Launcher::AdvancedPage::AdvancedPage(Config::GameSettings &gameSettings,
|
||||||
setObjectName ("AdvancedPage");
|
setObjectName ("AdvancedPage");
|
||||||
setupUi(this);
|
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();
|
loadSettings();
|
||||||
|
|
||||||
mCellNameCompleter.setModel(&mCellNameCompleterModel);
|
mCellNameCompleter.setModel(&mCellNameCompleterModel);
|
||||||
startDefaultCharacterAtField->setCompleter(&mCellNameCompleter);
|
startDefaultCharacterAtField->setCompleter(&mCellNameCompleter);
|
||||||
}
|
}
|
||||||
|
@ -95,6 +108,7 @@ bool Launcher::AdvancedPage::loadSettings()
|
||||||
int numPhysicsThreads = mEngineSettings.getInt("async num threads", "Physics");
|
int numPhysicsThreads = mEngineSettings.getInt("async num threads", "Physics");
|
||||||
if (numPhysicsThreads >= 0)
|
if (numPhysicsThreads >= 0)
|
||||||
physicsThreadsSpinBox->setValue(numPhysicsThreads);
|
physicsThreadsSpinBox->setValue(numPhysicsThreads);
|
||||||
|
loadSettingBool(allowNPCToFollowOverWaterSurfaceCheckBox, "allow actors to follow over water surface", "Game");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Visuals
|
// Visuals
|
||||||
|
@ -126,6 +140,34 @@ bool Launcher::AdvancedPage::loadSettings()
|
||||||
viewingDistanceComboBox->setValue(convertToCells(mEngineSettings.getInt("viewing distance", "Camera")));
|
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
|
// Camera
|
||||||
{
|
{
|
||||||
loadSettingBool(viewOverShoulderCheckBox, "view over shoulder", "Camera");
|
loadSettingBool(viewOverShoulderCheckBox, "view over shoulder", "Camera");
|
||||||
|
@ -247,6 +289,33 @@ void Launcher::AdvancedPage::saveSettings()
|
||||||
mEngineSettings.setInt("viewing distance", "Camera", convertToUnits(viewingDistance));
|
mEngineSettings.setInt("viewing distance", "Camera", convertToUnits(viewingDistance));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
// Camera
|
||||||
{
|
{
|
||||||
|
|
|
@ -20,6 +20,9 @@
|
||||||
QString getAspect(int x, int y)
|
QString getAspect(int x, int y)
|
||||||
{
|
{
|
||||||
int gcd = std::gcd (x, y);
|
int gcd = std::gcd (x, y);
|
||||||
|
if (gcd == 0)
|
||||||
|
return QString();
|
||||||
|
|
||||||
int xaspect = x / gcd;
|
int xaspect = x / gcd;
|
||||||
int yaspect = y / gcd;
|
int yaspect = y / gcd;
|
||||||
// special case: 8 : 5 is usually referred to as 16:10
|
// special case: 8 : 5 is usually referred to as 16:10
|
||||||
|
@ -298,9 +301,9 @@ QStringList Launcher::GraphicsPage::getAvailableResolutions(int screen)
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString aspect = getAspect(mode.w, mode.h);
|
|
||||||
QString resolution = QString::number(mode.w) + QString(" x ") + QString::number(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")) {
|
if (aspect == QLatin1String("16:9") || aspect == QLatin1String("16:10")) {
|
||||||
resolution.append(tr("\t(Wide ") + aspect + ")");
|
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 storeValue(const QKeySequence& sequence);
|
||||||
void resetState();
|
void resetState();
|
||||||
|
|
||||||
static const int MaxKeys = 4;
|
static constexpr int MaxKeys = 4;
|
||||||
|
|
||||||
QPushButton* mButton;
|
QPushButton* mButton;
|
||||||
|
|
||||||
|
|
|
@ -56,7 +56,9 @@ void CSMWorld::PotionRefIdAdapter::setData (const RefIdColumn *column, RefIdData
|
||||||
|
|
||||||
|
|
||||||
CSMWorld::IngredientColumns::IngredientColumns (const InventoryColumns& columns)
|
CSMWorld::IngredientColumns::IngredientColumns (const InventoryColumns& columns)
|
||||||
: InventoryColumns (columns) {}
|
: InventoryColumns (columns)
|
||||||
|
, mEffects(nullptr)
|
||||||
|
{}
|
||||||
|
|
||||||
CSMWorld::IngredientRefIdAdapter::IngredientRefIdAdapter (const IngredientColumns& columns)
|
CSMWorld::IngredientRefIdAdapter::IngredientRefIdAdapter (const IngredientColumns& columns)
|
||||||
: InventoryRefIdAdapter<ESM::Ingredient> (UniversalId::Type_Ingredient, 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)
|
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)
|
CSMWorld::LightRefIdAdapter::LightRefIdAdapter (const LightColumns& columns)
|
||||||
: InventoryRefIdAdapter<ESM::Light> (UniversalId::Type_Light, columns), mColumns (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)
|
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)
|
CSMWorld::WeaponRefIdAdapter::WeaponRefIdAdapter (const WeaponColumns& columns)
|
||||||
: EnchantableRefIdAdapter<ESM::Weapon> (UniversalId::Type_Weapon, columns), mColumns (columns)
|
: EnchantableRefIdAdapter<ESM::Weapon> (UniversalId::Type_Weapon, columns), mColumns (columns)
|
||||||
|
|
|
@ -178,7 +178,11 @@ namespace CSMWorld
|
||||||
const RefIdColumn *mName;
|
const RefIdColumn *mName;
|
||||||
const RefIdColumn *mScript;
|
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)
|
/// \brief Adapter for IDs with names (all but levelled lists and statics)
|
||||||
|
@ -247,7 +251,12 @@ namespace CSMWorld
|
||||||
const RefIdColumn *mWeight;
|
const RefIdColumn *mWeight;
|
||||||
const RefIdColumn *mValue;
|
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
|
/// \brief Adapter for IDs that can go into an inventory
|
||||||
|
@ -405,7 +414,11 @@ namespace CSMWorld
|
||||||
const RefIdColumn *mEnchantment;
|
const RefIdColumn *mEnchantment;
|
||||||
const RefIdColumn *mEnchantmentPoints;
|
const RefIdColumn *mEnchantmentPoints;
|
||||||
|
|
||||||
EnchantableColumns (const InventoryColumns& base) : InventoryColumns (base) {}
|
EnchantableColumns (const InventoryColumns& base)
|
||||||
|
: InventoryColumns (base)
|
||||||
|
, mEnchantment(nullptr)
|
||||||
|
, mEnchantmentPoints(nullptr)
|
||||||
|
{}
|
||||||
};
|
};
|
||||||
|
|
||||||
/// \brief Adapter for enchantable IDs
|
/// \brief Adapter for enchantable IDs
|
||||||
|
@ -474,7 +487,11 @@ namespace CSMWorld
|
||||||
const RefIdColumn *mQuality;
|
const RefIdColumn *mQuality;
|
||||||
const RefIdColumn *mUses;
|
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)
|
/// \brief Adapter for tools with limited uses IDs (lockpick, repair, probes)
|
||||||
|
@ -549,7 +566,17 @@ namespace CSMWorld
|
||||||
const RefIdColumn *mAiPackages;
|
const RefIdColumn *mAiPackages;
|
||||||
std::map<const RefIdColumn *, unsigned int> mServices;
|
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)
|
/// \brief Adapter for actor IDs (handles common AI functionality)
|
||||||
|
@ -2054,7 +2081,11 @@ namespace CSMWorld
|
||||||
const RefIdColumn *mLevList;
|
const RefIdColumn *mLevList;
|
||||||
const RefIdColumn *mNestedListLevList;
|
const RefIdColumn *mNestedListLevList;
|
||||||
|
|
||||||
LevListColumns (const BaseColumns& base) : BaseColumns (base) {}
|
LevListColumns (const BaseColumns& base)
|
||||||
|
: BaseColumns (base)
|
||||||
|
, mLevList(nullptr)
|
||||||
|
, mNestedListLevList(nullptr)
|
||||||
|
{}
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename RecordT>
|
template<typename RecordT>
|
||||||
|
|
|
@ -33,26 +33,6 @@ namespace Compiler
|
||||||
class Context;
|
class Context;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace MWScript
|
|
||||||
{
|
|
||||||
class ScriptManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace MWSound
|
|
||||||
{
|
|
||||||
class SoundManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace MWWorld
|
|
||||||
{
|
|
||||||
class World;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace MWGui
|
|
||||||
{
|
|
||||||
class WindowManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace Files
|
namespace Files
|
||||||
{
|
{
|
||||||
struct ConfigurationManager;
|
struct ConfigurationManager;
|
||||||
|
|
|
@ -32,7 +32,6 @@ namespace MyGUI
|
||||||
|
|
||||||
namespace ESM
|
namespace ESM
|
||||||
{
|
{
|
||||||
struct Class;
|
|
||||||
class ESMReader;
|
class ESMReader;
|
||||||
class ESMWriter;
|
class ESMWriter;
|
||||||
struct CellId;
|
struct CellId;
|
||||||
|
|
|
@ -401,13 +401,13 @@ namespace MWBase
|
||||||
virtual void deleteObject (const MWWorld::Ptr& ptr) = 0;
|
virtual void deleteObject (const MWWorld::Ptr& ptr) = 0;
|
||||||
virtual void undeleteObject (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
|
///< @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;
|
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
|
///< @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
|
///< @return an updated Ptr
|
||||||
|
|
||||||
virtual void scaleObject (const MWWorld::Ptr& ptr, float scale) = 0;
|
virtual void scaleObject (const MWWorld::Ptr& ptr, float scale) = 0;
|
||||||
|
@ -587,6 +587,8 @@ namespace MWBase
|
||||||
virtual void applyDeferredPreviewRotationToPlayer(float dt) = 0;
|
virtual void applyDeferredPreviewRotationToPlayer(float dt) = 0;
|
||||||
virtual void disableDeferredPreviewRotation() = 0;
|
virtual void disableDeferredPreviewRotation() = 0;
|
||||||
|
|
||||||
|
virtual void saveLoaded() = 0;
|
||||||
|
|
||||||
virtual void setupPlayer() = 0;
|
virtual void setupPlayer() = 0;
|
||||||
virtual void renderPlayer() = 0;
|
virtual void renderPlayer() = 0;
|
||||||
|
|
||||||
|
@ -799,7 +801,7 @@ namespace MWBase
|
||||||
|
|
||||||
/// Return a vector aiming the actor's weapon towards a target.
|
/// Return a vector aiming the actor's weapon towards a target.
|
||||||
/// @note The length of the vector is the distance between actor and 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.
|
/// Return the distance between actor's weapon and target's collision box.
|
||||||
virtual float getHitDistance(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target) = 0;
|
virtual float getHitDistance(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target) = 0;
|
||||||
|
|
|
@ -58,11 +58,6 @@ namespace MWClass
|
||||||
mStore.readState(inventory);
|
mStore.readState(inventory);
|
||||||
}
|
}
|
||||||
|
|
||||||
MWWorld::CustomData *ContainerCustomData::clone() const
|
|
||||||
{
|
|
||||||
return new ContainerCustomData (*this);
|
|
||||||
}
|
|
||||||
|
|
||||||
ContainerCustomData& ContainerCustomData::asContainerCustomData()
|
ContainerCustomData& ContainerCustomData::asContainerCustomData()
|
||||||
{
|
{
|
||||||
return *this;
|
return *this;
|
||||||
|
@ -84,7 +79,7 @@ namespace MWClass
|
||||||
MWWorld::LiveCellRef<ESM::Container> *ref = ptr.get<ESM::Container>();
|
MWWorld::LiveCellRef<ESM::Container> *ref = ptr.get<ESM::Container>();
|
||||||
|
|
||||||
// store
|
// 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());
|
MWBase::Environment::get().getWorld()->addContainerScripts(ptr, ptr.getCell());
|
||||||
}
|
}
|
||||||
|
@ -381,7 +376,7 @@ namespace MWClass
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const ESM::ContainerState& containerState = state.asContainerState();
|
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
|
void Container::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const
|
||||||
|
|
|
@ -13,15 +13,13 @@ namespace ESM
|
||||||
|
|
||||||
namespace MWClass
|
namespace MWClass
|
||||||
{
|
{
|
||||||
class ContainerCustomData : public MWWorld::CustomData
|
class ContainerCustomData : public MWWorld::TypedCustomData<ContainerCustomData>
|
||||||
{
|
{
|
||||||
MWWorld::ContainerStore mStore;
|
MWWorld::ContainerStore mStore;
|
||||||
public:
|
public:
|
||||||
ContainerCustomData(const ESM::Container& container, MWWorld::CellStore* cell);
|
ContainerCustomData(const ESM::Container& container, MWWorld::CellStore* cell);
|
||||||
ContainerCustomData(const ESM::InventoryState& inventory);
|
ContainerCustomData(const ESM::InventoryState& inventory);
|
||||||
|
|
||||||
MWWorld::CustomData *clone() const override;
|
|
||||||
|
|
||||||
ContainerCustomData& asContainerCustomData() override;
|
ContainerCustomData& asContainerCustomData() override;
|
||||||
const ContainerCustomData& asContainerCustomData() const override;
|
const ContainerCustomData& asContainerCustomData() const override;
|
||||||
|
|
||||||
|
|
|
@ -68,14 +68,16 @@ namespace
|
||||||
namespace MWClass
|
namespace MWClass
|
||||||
{
|
{
|
||||||
|
|
||||||
class CreatureCustomData : public MWWorld::CustomData
|
class CreatureCustomData : public MWWorld::TypedCustomData<CreatureCustomData>
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
MWMechanics::CreatureStats mCreatureStats;
|
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;
|
MWMechanics::Movement mMovement;
|
||||||
|
|
||||||
MWWorld::CustomData *clone() const override;
|
CreatureCustomData() = default;
|
||||||
|
CreatureCustomData(const CreatureCustomData& other);
|
||||||
|
CreatureCustomData(CreatureCustomData&& other) noexcept = default;
|
||||||
|
|
||||||
CreatureCustomData& asCreatureCustomData() override
|
CreatureCustomData& asCreatureCustomData() override
|
||||||
{
|
{
|
||||||
|
@ -85,16 +87,13 @@ namespace MWClass
|
||||||
{
|
{
|
||||||
return *this;
|
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()
|
const Creature::GMST& Creature::getGmst()
|
||||||
|
@ -165,16 +164,16 @@ namespace MWClass
|
||||||
// inventory
|
// inventory
|
||||||
bool hasInventory = hasInventoryStore(ptr);
|
bool hasInventory = hasInventoryStore(ptr);
|
||||||
if (hasInventory)
|
if (hasInventory)
|
||||||
data->mContainerStore = new MWWorld::InventoryStore();
|
data->mContainerStore = std::make_unique<MWWorld::InventoryStore>();
|
||||||
else
|
else
|
||||||
data->mContainerStore = new MWWorld::ContainerStore();
|
data->mContainerStore = std::make_unique<MWWorld::ContainerStore>();
|
||||||
|
|
||||||
data->mCreatureStats.setGoldPool(ref->mBase->mData.mGold);
|
data->mCreatureStats.setGoldPool(ref->mBase->mData.mGold);
|
||||||
|
|
||||||
data->mCreatureStats.setNeedRecalcDynamicStats(false);
|
data->mCreatureStats.setNeedRecalcDynamicStats(false);
|
||||||
|
|
||||||
// store
|
// store
|
||||||
ptr.getRefData().setCustomData(data.release());
|
ptr.getRefData().setCustomData(std::move(data));
|
||||||
|
|
||||||
getContainerStore(ptr).fill(ref->mBase->mInventory, ptr.getCellRef().getRefId());
|
getContainerStore(ptr).fill(ref->mBase->mInventory, ptr.getCellRef().getRefId());
|
||||||
|
|
||||||
|
@ -946,11 +945,11 @@ namespace MWClass
|
||||||
std::unique_ptr<CreatureCustomData> data (new CreatureCustomData);
|
std::unique_ptr<CreatureCustomData> data (new CreatureCustomData);
|
||||||
|
|
||||||
if (hasInventoryStore(ptr))
|
if (hasInventoryStore(ptr))
|
||||||
data->mContainerStore = new MWWorld::InventoryStore();
|
data->mContainerStore = std::make_unique<MWWorld::InventoryStore>();
|
||||||
else
|
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
|
else
|
||||||
|
|
|
@ -10,15 +10,13 @@
|
||||||
|
|
||||||
namespace MWClass
|
namespace MWClass
|
||||||
{
|
{
|
||||||
class CreatureLevListCustomData : public MWWorld::CustomData
|
class CreatureLevListCustomData : public MWWorld::TypedCustomData<CreatureLevListCustomData>
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
// actorId of the creature we spawned
|
// actorId of the creature we spawned
|
||||||
int mSpawnActorId;
|
int mSpawnActorId;
|
||||||
bool mSpawn; // Should a new creature be spawned?
|
bool mSpawn; // Should a new creature be spawned?
|
||||||
|
|
||||||
MWWorld::CustomData *clone() const override;
|
|
||||||
|
|
||||||
CreatureLevListCustomData& asCreatureLevListCustomData() override
|
CreatureLevListCustomData& asCreatureLevListCustomData() override
|
||||||
{
|
{
|
||||||
return *this;
|
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
|
std::string CreatureLevList::getName (const MWWorld::ConstPtr& ptr) const
|
||||||
{
|
{
|
||||||
return "";
|
return "";
|
||||||
|
@ -149,11 +142,11 @@ namespace MWClass
|
||||||
{
|
{
|
||||||
if (!ptr.getRefData().getCustomData())
|
if (!ptr.getRefData().getCustomData())
|
||||||
{
|
{
|
||||||
std::unique_ptr<CreatureLevListCustomData> data (new CreatureLevListCustomData);
|
std::unique_ptr<CreatureLevListCustomData> data = std::make_unique<CreatureLevListCustomData>();
|
||||||
data->mSpawnActorId = -1;
|
data->mSpawnActorId = -1;
|
||||||
data->mSpawn = true;
|
data->mSpawn = true;
|
||||||
|
|
||||||
ptr.getRefData().setCustomData(data.release());
|
ptr.getRefData().setCustomData(std::move(data));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -43,13 +43,11 @@
|
||||||
|
|
||||||
namespace MWClass
|
namespace MWClass
|
||||||
{
|
{
|
||||||
class DoorCustomData : public MWWorld::CustomData
|
class DoorCustomData : public MWWorld::TypedCustomData<DoorCustomData>
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
MWWorld::DoorState mDoorState = MWWorld::DoorState::Idle;
|
MWWorld::DoorState mDoorState = MWWorld::DoorState::Idle;
|
||||||
|
|
||||||
MWWorld::CustomData *clone() const override;
|
|
||||||
|
|
||||||
DoorCustomData& asDoorCustomData() override
|
DoorCustomData& asDoorCustomData() override
|
||||||
{
|
{
|
||||||
return *this;
|
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
|
void Door::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const
|
||||||
{
|
{
|
||||||
if (!model.empty())
|
if (!model.empty())
|
||||||
|
@ -419,8 +412,7 @@ namespace MWClass
|
||||||
{
|
{
|
||||||
if (!ptr.getRefData().getCustomData())
|
if (!ptr.getRefData().getCustomData())
|
||||||
{
|
{
|
||||||
std::unique_ptr<DoorCustomData> data(new DoorCustomData);
|
ptr.getRefData().setCustomData(std::make_unique<DoorCustomData>());
|
||||||
ptr.getRefData().setCustomData(data.release());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -263,15 +263,13 @@ namespace
|
||||||
namespace MWClass
|
namespace MWClass
|
||||||
{
|
{
|
||||||
|
|
||||||
class NpcCustomData : public MWWorld::CustomData
|
class NpcCustomData : public MWWorld::TypedCustomData<NpcCustomData>
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
MWMechanics::NpcStats mNpcStats;
|
MWMechanics::NpcStats mNpcStats;
|
||||||
MWMechanics::Movement mMovement;
|
MWMechanics::Movement mMovement;
|
||||||
MWWorld::InventoryStore mInventoryStore;
|
MWWorld::InventoryStore mInventoryStore;
|
||||||
|
|
||||||
MWWorld::CustomData *clone() const override;
|
|
||||||
|
|
||||||
NpcCustomData& asNpcCustomData() override
|
NpcCustomData& asNpcCustomData() override
|
||||||
{
|
{
|
||||||
return *this;
|
return *this;
|
||||||
|
@ -282,11 +280,6 @@ namespace MWClass
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
MWWorld::CustomData *NpcCustomData::clone() const
|
|
||||||
{
|
|
||||||
return new NpcCustomData (*this);
|
|
||||||
}
|
|
||||||
|
|
||||||
const Npc::GMST& Npc::getGmst()
|
const Npc::GMST& Npc::getGmst()
|
||||||
{
|
{
|
||||||
static GMST gmst;
|
static GMST gmst;
|
||||||
|
@ -414,7 +407,7 @@ namespace MWClass
|
||||||
data->mNpcStats.setGoldPool(gold);
|
data->mNpcStats.setGoldPool(gold);
|
||||||
|
|
||||||
// store
|
// store
|
||||||
ptr.getRefData().setCustomData (data.release());
|
ptr.getRefData().setCustomData(std::move(data));
|
||||||
|
|
||||||
getInventoryStore(ptr).autoEquip(ptr);
|
getInventoryStore(ptr).autoEquip(ptr);
|
||||||
}
|
}
|
||||||
|
@ -1525,8 +1518,7 @@ namespace MWClass
|
||||||
if (!ptr.getRefData().getCustomData())
|
if (!ptr.getRefData().getCustomData())
|
||||||
{
|
{
|
||||||
// Create a CustomData, but don't fill it from ESM records (not needed)
|
// Create a CustomData, but don't fill it from ESM records (not needed)
|
||||||
std::unique_ptr<NpcCustomData> data (new NpcCustomData);
|
ptr.getRefData().setCustomData(std::make_unique<NpcCustomData>());
|
||||||
ptr.getRefData().setCustomData (data.release());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
#include "bookpage.hpp"
|
#include "bookpage.hpp"
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
#include "MyGUI_RenderItem.h"
|
#include "MyGUI_RenderItem.h"
|
||||||
#include "MyGUI_RenderManager.h"
|
#include "MyGUI_RenderManager.h"
|
||||||
#include "MyGUI_TextureUtility.h"
|
#include "MyGUI_TextureUtility.h"
|
||||||
|
@ -894,6 +896,27 @@ protected:
|
||||||
return mIsPageReset || (mPage != page);
|
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:
|
public:
|
||||||
|
|
||||||
typedef TypesetBookImpl::StyleImpl Style;
|
typedef TypesetBookImpl::StyleImpl Style;
|
||||||
|
@ -952,16 +975,10 @@ public:
|
||||||
|
|
||||||
void onMouseMove (int left, int top)
|
void onMouseMove (int left, int top)
|
||||||
{
|
{
|
||||||
if (!mBook)
|
Style * hit = nullptr;
|
||||||
return;
|
if(auto pos = getAdjustedPos(left, top, true))
|
||||||
|
if(pos->top <= mViewBottom)
|
||||||
if (mPage >= mBook->mPages.size())
|
hit = mBook->hitTestWithMargin (pos->left, pos->top);
|
||||||
return;
|
|
||||||
|
|
||||||
left -= mCroppedParent->getAbsoluteLeft ();
|
|
||||||
top -= mCroppedParent->getAbsoluteTop ();
|
|
||||||
|
|
||||||
Style * hit = mBook->hitTestWithMargin (left, mViewTop + top);
|
|
||||||
|
|
||||||
if (mLastDown == MyGUI::MouseButton::None)
|
if (mLastDown == MyGUI::MouseButton::None)
|
||||||
{
|
{
|
||||||
|
@ -991,24 +1008,11 @@ public:
|
||||||
|
|
||||||
void onMouseButtonPressed (int left, int top, MyGUI::MouseButton id)
|
void onMouseButtonPressed (int left, int top, MyGUI::MouseButton id)
|
||||||
{
|
{
|
||||||
if (!mBook)
|
auto pos = getAdjustedPos(left, top);
|
||||||
return;
|
|
||||||
|
|
||||||
if (mPage >= mBook->mPages.size())
|
if (pos && mLastDown == MyGUI::MouseButton::None)
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
mFocusItem = mBook->hitTestWithMargin (pos.left, mViewTop + pos.top);
|
mFocusItem = pos->top <= mViewBottom ? mBook->hitTestWithMargin (pos->left, pos->top) : nullptr;
|
||||||
mItemActive = true;
|
mItemActive = true;
|
||||||
|
|
||||||
dirtyFocusItem ();
|
dirtyFocusItem ();
|
||||||
|
@ -1019,25 +1023,11 @@ public:
|
||||||
|
|
||||||
void onMouseButtonReleased(int left, int top, MyGUI::MouseButton id)
|
void onMouseButtonReleased(int left, int top, MyGUI::MouseButton id)
|
||||||
{
|
{
|
||||||
if (!mBook)
|
auto pos = getAdjustedPos(left, top);
|
||||||
return;
|
|
||||||
|
|
||||||
if (mPage >= mBook->mPages.size())
|
if (pos && mLastDown == id)
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
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;
|
bool clicked = mFocusItem == item;
|
||||||
|
|
||||||
|
|
|
@ -6,11 +6,6 @@
|
||||||
|
|
||||||
#include "itemmodel.hpp"
|
#include "itemmodel.hpp"
|
||||||
|
|
||||||
namespace MWWorld
|
|
||||||
{
|
|
||||||
class Environment;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace MyGUI
|
namespace MyGUI
|
||||||
{
|
{
|
||||||
class Gui;
|
class Gui;
|
||||||
|
@ -19,7 +14,6 @@ namespace MyGUI
|
||||||
|
|
||||||
namespace MWGui
|
namespace MWGui
|
||||||
{
|
{
|
||||||
class WindowManager;
|
|
||||||
class ContainerWindow;
|
class ContainerWindow;
|
||||||
class ItemView;
|
class ItemView;
|
||||||
class SortFilterItemModel;
|
class SortFilterItemModel;
|
||||||
|
|
|
@ -15,11 +15,6 @@ namespace Gui
|
||||||
class MWList;
|
class MWList;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace MWGui
|
|
||||||
{
|
|
||||||
class WindowManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace MWGui
|
namespace MWGui
|
||||||
{
|
{
|
||||||
class ResponseCallback;
|
class ResponseCallback;
|
||||||
|
|
|
@ -85,7 +85,7 @@ namespace MWGui
|
||||||
, mUpdateTimer(0.f)
|
, mUpdateTimer(0.f)
|
||||||
{
|
{
|
||||||
float uiScale = Settings::Manager::getFloat("scaling factor", "GUI");
|
float uiScale = Settings::Manager::getFloat("scaling factor", "GUI");
|
||||||
if (uiScale > 1.0)
|
if (uiScale > 0.f)
|
||||||
mScaleFactor = uiScale;
|
mScaleFactor = uiScale;
|
||||||
|
|
||||||
mPreviewTexture.reset(new osgMyGUI::OSGTexture(mPreview->getTexture()));
|
mPreviewTexture.reset(new osgMyGUI::OSGTexture(mPreview->getTexture()));
|
||||||
|
|
|
@ -7,11 +7,6 @@
|
||||||
#include <MyGUI_RenderManager.h>
|
#include <MyGUI_RenderManager.h>
|
||||||
|
|
||||||
|
|
||||||
namespace MWGui
|
|
||||||
{
|
|
||||||
class WindowManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace MWRender
|
namespace MWRender
|
||||||
{
|
{
|
||||||
class RaceSelectionPreview;
|
class RaceSelectionPreview;
|
||||||
|
|
|
@ -11,11 +11,6 @@ namespace ESM
|
||||||
struct Spell;
|
struct Spell;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace MWGui
|
|
||||||
{
|
|
||||||
class WindowManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace MWGui
|
namespace MWGui
|
||||||
{
|
{
|
||||||
class ReviewDialog : public WindowModal
|
class ReviewDialog : public WindowModal
|
||||||
|
|
|
@ -61,6 +61,9 @@ namespace
|
||||||
std::string getAspect (int x, int y)
|
std::string getAspect (int x, int y)
|
||||||
{
|
{
|
||||||
int gcd = std::gcd (x, y);
|
int gcd = std::gcd (x, y);
|
||||||
|
if (gcd == 0)
|
||||||
|
return std::string();
|
||||||
|
|
||||||
int xaspect = x / gcd;
|
int xaspect = x / gcd;
|
||||||
int yaspect = y / gcd;
|
int yaspect = y / gcd;
|
||||||
// special case: 8 : 5 is usually referred to as 16:10
|
// special case: 8 : 5 is usually referred to as 16:10
|
||||||
|
@ -264,8 +267,10 @@ namespace MWGui
|
||||||
std::sort(resolutions.begin(), resolutions.end(), sortResolutions);
|
std::sort(resolutions.begin(), resolutions.end(), sortResolutions);
|
||||||
for (std::pair<int, int>& resolution : resolutions)
|
for (std::pair<int, int>& resolution : resolutions)
|
||||||
{
|
{
|
||||||
std::string str = MyGUI::utility::toString(resolution.first) + " x " + MyGUI::utility::toString(resolution.second)
|
std::string str = MyGUI::utility::toString(resolution.first) + " x " + MyGUI::utility::toString(resolution.second);
|
||||||
+ " (" + getAspect(resolution.first, resolution.second) + ")";
|
std::string aspect = getAspect(resolution.first, resolution.second);
|
||||||
|
if (!aspect.empty())
|
||||||
|
str = str + " (" + aspect + ")";
|
||||||
|
|
||||||
if (mResolutionList->findItemIndexWith(str) == MyGUI::ITEM_NONE)
|
if (mResolutionList->findItemIndexWith(str) == MyGUI::ITEM_NONE)
|
||||||
mResolutionList->addItem(str);
|
mResolutionList->addItem(str);
|
||||||
|
|
|
@ -3,11 +3,6 @@
|
||||||
|
|
||||||
#include "windowbase.hpp"
|
#include "windowbase.hpp"
|
||||||
|
|
||||||
namespace MWGui
|
|
||||||
{
|
|
||||||
class WindowManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace MWGui
|
namespace MWGui
|
||||||
{
|
{
|
||||||
class SettingsWindow : public WindowBase
|
class SettingsWindow : public WindowBase
|
||||||
|
|
|
@ -35,20 +35,20 @@ namespace MWGui
|
||||||
bool onDropItem(const MWWorld::Ptr &item, int count) override;
|
bool onDropItem(const MWWorld::Ptr &item, int count) override;
|
||||||
bool onTakeItem(const MWWorld::Ptr &item, int count) override;
|
bool onTakeItem(const MWWorld::Ptr &item, int count) override;
|
||||||
|
|
||||||
static const int Category_Weapon = (1<<1);
|
static constexpr int Category_Weapon = (1<<1);
|
||||||
static const int Category_Apparel = (1<<2);
|
static constexpr int Category_Apparel = (1<<2);
|
||||||
static const int Category_Misc = (1<<3);
|
static constexpr int Category_Misc = (1<<3);
|
||||||
static const int Category_Magic = (1<<4);
|
static constexpr int Category_Magic = (1<<4);
|
||||||
static const int Category_All = 255;
|
static constexpr int Category_All = 255;
|
||||||
|
|
||||||
static const int Filter_OnlyIngredients = (1<<0);
|
static constexpr int Filter_OnlyIngredients = (1<<0);
|
||||||
static const int Filter_OnlyEnchanted = (1<<1);
|
static constexpr int Filter_OnlyEnchanted = (1<<1);
|
||||||
static const int Filter_OnlyEnchantable = (1<<2);
|
static constexpr int Filter_OnlyEnchantable = (1<<2);
|
||||||
static const int Filter_OnlyChargedSoulstones = (1<<3);
|
static constexpr int Filter_OnlyChargedSoulstones = (1<<3);
|
||||||
static const int Filter_OnlyUsableItems = (1<<4); // Only items with a Use action
|
static constexpr int Filter_OnlyUsableItems = (1<<4); // Only items with a Use action
|
||||||
static const int Filter_OnlyRepairable = (1<<5);
|
static constexpr int Filter_OnlyRepairable = (1<<5);
|
||||||
static const int Filter_OnlyRechargable = (1<<6);
|
static constexpr int Filter_OnlyRechargable = (1<<6);
|
||||||
static const int Filter_OnlyRepairTools = (1<<7);
|
static constexpr int Filter_OnlyRepairTools = (1<<7);
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -15,11 +15,6 @@ namespace MyGUI
|
||||||
class Widget;
|
class Widget;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace MWGui
|
|
||||||
{
|
|
||||||
class WindowManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace MWGui
|
namespace MWGui
|
||||||
{
|
{
|
||||||
class SpellBuyingWindow : public ReferenceInterface, public WindowBase
|
class SpellBuyingWindow : public ReferenceInterface, public WindowBase
|
||||||
|
|
|
@ -405,7 +405,8 @@ namespace MWGui
|
||||||
MWWorld::Ptr player = MWMechanics::getPlayer();
|
MWWorld::Ptr player = MWMechanics::getPlayer();
|
||||||
int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId);
|
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}");
|
MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage18}");
|
||||||
return;
|
return;
|
||||||
|
@ -413,8 +414,6 @@ namespace MWGui
|
||||||
|
|
||||||
mSpell.mName = mNameEdit->getCaption();
|
mSpell.mName = mNameEdit->getCaption();
|
||||||
|
|
||||||
int price = MyGUI::utility::parseInt(mPriceLabel->getCaption());
|
|
||||||
|
|
||||||
player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player);
|
player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player);
|
||||||
|
|
||||||
// add gold to NPC trading gold pool
|
// add gold to NPC trading gold pool
|
||||||
|
|
|
@ -6,8 +6,6 @@
|
||||||
|
|
||||||
namespace MWGui
|
namespace MWGui
|
||||||
{
|
{
|
||||||
class WindowManager;
|
|
||||||
|
|
||||||
class StatsWindow : public WindowPinnableBase, public NoDrop, public StatsListener
|
class StatsWindow : public WindowPinnableBase, public NoDrop, public StatsListener
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -3,11 +3,6 @@
|
||||||
|
|
||||||
#include "windowbase.hpp"
|
#include "windowbase.hpp"
|
||||||
|
|
||||||
namespace MWGui
|
|
||||||
{
|
|
||||||
class WindowManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace MWGui
|
namespace MWGui
|
||||||
{
|
{
|
||||||
class TextInputDialog : public WindowModal
|
class TextInputDialog : public WindowModal
|
||||||
|
|
|
@ -58,12 +58,12 @@ namespace MWGui
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int TimeAdvancer::getHours()
|
int TimeAdvancer::getHours() const
|
||||||
{
|
{
|
||||||
return mHours;
|
return mHours;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TimeAdvancer::isRunning()
|
bool TimeAdvancer::isRunning() const
|
||||||
{
|
{
|
||||||
return mRunning;
|
return mRunning;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,8 +14,8 @@ namespace MWGui
|
||||||
void stop();
|
void stop();
|
||||||
void onFrame(float dt);
|
void onFrame(float dt);
|
||||||
|
|
||||||
int getHours();
|
int getHours() const;
|
||||||
bool isRunning();
|
bool isRunning() const;
|
||||||
|
|
||||||
// signals
|
// signals
|
||||||
typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void;
|
typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void;
|
||||||
|
|
|
@ -11,12 +11,6 @@ namespace MyGUI
|
||||||
class Widget;
|
class Widget;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace MWGui
|
|
||||||
{
|
|
||||||
class WindowManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
namespace MWGui
|
namespace MWGui
|
||||||
{
|
{
|
||||||
class TravelWindow : public ReferenceInterface, public WindowBase
|
class TravelWindow : public ReferenceInterface, public WindowBase
|
||||||
|
|
|
@ -268,7 +268,7 @@ namespace MWGui
|
||||||
void initialiseOverride() override;
|
void initialiseOverride() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static const int sIconOffset = 24;
|
static constexpr int sIconOffset = 24;
|
||||||
|
|
||||||
void updateWidgets();
|
void updateWidgets();
|
||||||
|
|
||||||
|
|
|
@ -3,11 +3,6 @@
|
||||||
|
|
||||||
#include "layout.hpp"
|
#include "layout.hpp"
|
||||||
|
|
||||||
namespace MWBase
|
|
||||||
{
|
|
||||||
class WindowManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace MWWorld
|
namespace MWWorld
|
||||||
{
|
{
|
||||||
class Ptr;
|
class Ptr;
|
||||||
|
@ -15,7 +10,6 @@ namespace MWWorld
|
||||||
|
|
||||||
namespace MWGui
|
namespace MWGui
|
||||||
{
|
{
|
||||||
class WindowManager;
|
|
||||||
class DragAndDrop;
|
class DragAndDrop;
|
||||||
|
|
||||||
class WindowBase: public Layout
|
class WindowBase: public Layout
|
||||||
|
|
|
@ -114,7 +114,6 @@ namespace MWGui
|
||||||
class TrainingWindow;
|
class TrainingWindow;
|
||||||
class SpellIcons;
|
class SpellIcons;
|
||||||
class MerchantRepair;
|
class MerchantRepair;
|
||||||
class Repair;
|
|
||||||
class SoulgemDialog;
|
class SoulgemDialog;
|
||||||
class Recharge;
|
class Recharge;
|
||||||
class CompanionWindow;
|
class CompanionWindow;
|
||||||
|
|
|
@ -5,8 +5,6 @@
|
||||||
|
|
||||||
namespace MWGui
|
namespace MWGui
|
||||||
{
|
{
|
||||||
class WindowManager;
|
|
||||||
|
|
||||||
class WindowPinnableBase: public WindowBase
|
class WindowPinnableBase: public WindowBase
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -70,7 +70,7 @@ namespace MWInput
|
||||||
}
|
}
|
||||||
|
|
||||||
float uiScale = Settings::Manager::getFloat("scaling factor", "GUI");
|
float uiScale = Settings::Manager::getFloat("scaling factor", "GUI");
|
||||||
if (uiScale != 0.f)
|
if (uiScale > 0.f)
|
||||||
mInvUiScalingFactor = 1.f / uiScale;
|
mInvUiScalingFactor = 1.f / uiScale;
|
||||||
|
|
||||||
float deadZoneRadius = Settings::Manager::getFloat("joystick dead zone", "Input");
|
float deadZoneRadius = Settings::Manager::getFloat("joystick dead zone", "Input");
|
||||||
|
|
|
@ -37,7 +37,7 @@ namespace MWInput
|
||||||
, mGuiCursorEnabled(true)
|
, mGuiCursorEnabled(true)
|
||||||
{
|
{
|
||||||
float uiScale = Settings::Manager::getFloat("scaling factor", "GUI");
|
float uiScale = Settings::Manager::getFloat("scaling factor", "GUI");
|
||||||
if (uiScale != 0.f)
|
if (uiScale > 0.f)
|
||||||
mInvUiScalingFactor = 1.f / uiScale;
|
mInvUiScalingFactor = 1.f / uiScale;
|
||||||
|
|
||||||
int w,h;
|
int w,h;
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
|
|
||||||
#include "../mwmechanics/actorutil.hpp"
|
#include "../mwmechanics/actorutil.hpp"
|
||||||
|
|
||||||
|
#include <components/misc/timer.hpp>
|
||||||
|
|
||||||
namespace MWRender
|
namespace MWRender
|
||||||
{
|
{
|
||||||
class Animation;
|
class Animation;
|
||||||
|
@ -41,12 +43,18 @@ namespace MWMechanics
|
||||||
bool isTurningToPlayer() const;
|
bool isTurningToPlayer() const;
|
||||||
void setTurningToPlayer(bool turning);
|
void setTurningToPlayer(bool turning);
|
||||||
|
|
||||||
|
Misc::TimerStatus updateEngageCombatTimer(float duration)
|
||||||
|
{
|
||||||
|
return mEngageCombat.update(duration);
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unique_ptr<CharacterController> mCharacterController;
|
std::unique_ptr<CharacterController> mCharacterController;
|
||||||
int mGreetingTimer{0};
|
int mGreetingTimer{0};
|
||||||
float mTargetAngleRadians{0.f};
|
float mTargetAngleRadians{0.f};
|
||||||
GreetingState mGreetingState{Greet_None};
|
GreetingState mGreetingState{Greet_None};
|
||||||
bool mIsTurningToPlayer{false};
|
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())
|
if (actor != MWMechanics::getPlayer())
|
||||||
return;
|
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)
|
if (newItem.isEmpty() || boundPtr != newItem)
|
||||||
return;
|
return;
|
||||||
|
@ -2022,14 +2026,11 @@ namespace MWMechanics
|
||||||
{
|
{
|
||||||
if(!paused)
|
if(!paused)
|
||||||
{
|
{
|
||||||
static float timerUpdateAITargets = 0;
|
|
||||||
static float timerUpdateHeadTrack = 0;
|
static float timerUpdateHeadTrack = 0;
|
||||||
static float timerUpdateEquippedLight = 0;
|
static float timerUpdateEquippedLight = 0;
|
||||||
static float timerUpdateHello = 0;
|
static float timerUpdateHello = 0;
|
||||||
const float updateEquippedLightInterval = 1.0f;
|
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 (timerUpdateHeadTrack >= 0.3f) timerUpdateHeadTrack = 0;
|
||||||
if (timerUpdateHello >= 0.25f) timerUpdateHello = 0;
|
if (timerUpdateHello >= 0.25f) timerUpdateHello = 0;
|
||||||
if (mTimerDisposeSummonsCorpses >= 0.2f) mTimerDisposeSummonsCorpses = 0;
|
if (mTimerDisposeSummonsCorpses >= 0.2f) mTimerDisposeSummonsCorpses = 0;
|
||||||
|
@ -2124,6 +2125,8 @@ namespace MWMechanics
|
||||||
|
|
||||||
iter->first.getClass().getCreatureStats(iter->first).getActiveSpells().update(duration);
|
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
|
// For dead actors we need to update looping spell particles
|
||||||
if (iter->first.getClass().getCreatureStats(iter->first).isDead())
|
if (iter->first.getClass().getCreatureStats(iter->first).isDead())
|
||||||
{
|
{
|
||||||
|
@ -2161,7 +2164,7 @@ namespace MWMechanics
|
||||||
|
|
||||||
if (inProcessingRange && (aiActive || isLocalActor || isDedicatedActor))
|
if (inProcessingRange && (aiActive || isLocalActor || isDedicatedActor))
|
||||||
{
|
{
|
||||||
if (timerUpdateAITargets == 0 && (isLocalActor || aiActive))
|
if (engageCombatTimerStatus == Misc::TimerStatus::Elapsed && (isLocalActor || aiActive))
|
||||||
{
|
{
|
||||||
if (!isPlayer)
|
if (!isPlayer)
|
||||||
adjustCommandedActor(iter->first);
|
adjustCommandedActor(iter->first);
|
||||||
|
@ -2251,7 +2254,6 @@ namespace MWMechanics
|
||||||
if (avoidCollisions)
|
if (avoidCollisions)
|
||||||
predictAndAvoidCollisions();
|
predictAndAvoidCollisions();
|
||||||
|
|
||||||
timerUpdateAITargets += duration;
|
|
||||||
timerUpdateHeadTrack += duration;
|
timerUpdateHeadTrack += duration;
|
||||||
timerUpdateEquippedLight += duration;
|
timerUpdateEquippedLight += duration;
|
||||||
timerUpdateHello += duration;
|
timerUpdateHello += duration;
|
||||||
|
|
|
@ -23,4 +23,10 @@ namespace MWMechanics
|
||||||
MWBase::World* world = MWBase::Environment::get().getWorld();
|
MWBase::World* world = MWBase::Environment::get().getWorld();
|
||||||
return (actor.getClass().canSwim(actor) && world->isSwimming(actor)) || world->isFlying(actor);
|
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();
|
MWWorld::Ptr getPlayer();
|
||||||
bool isPlayerInCombat();
|
bool isPlayerInCombat();
|
||||||
bool canActorMoveByZAxis(const MWWorld::Ptr& actor);
|
bool canActorMoveByZAxis(const MWWorld::Ptr& actor);
|
||||||
|
bool hasWaterWalking(const MWWorld::Ptr& actor);
|
||||||
|
|
||||||
template<class T>
|
template<class T>
|
||||||
void setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value)
|
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
|
return true; //Door is no longer opening
|
||||||
|
|
||||||
ESM::Position tPos = mDoorPtr.getRefData().getPosition(); //Position of the door
|
ESM::Position tPos = mDoorPtr.getRefData().getPosition(); //Position of the door
|
||||||
float x = pos.pos[0] - tPos.pos[0];
|
float x = pos.pos[1] - tPos.pos[1];
|
||||||
float y = pos.pos[1] - tPos.pos[1];
|
float y = pos.pos[0] - tPos.pos[0];
|
||||||
|
|
||||||
actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true);
|
actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true);
|
||||||
|
|
||||||
// Turn away from the door and move when turn completed
|
// 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;
|
actor.getClass().getMovementSettings(actor).mPosition[1] = 1;
|
||||||
else
|
else
|
||||||
actor.getClass().getMovementSettings(actor).mPosition[1] = 0;
|
actor.getClass().getMovementSettings(actor).mPosition[1] = 0;
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
#include "aicast.hpp"
|
#include "aicast.hpp"
|
||||||
|
|
||||||
|
#include <components/misc/constants.hpp>
|
||||||
|
|
||||||
#include "../mwbase/environment.hpp"
|
#include "../mwbase/environment.hpp"
|
||||||
#include "../mwbase/mechanicsmanager.hpp"
|
#include "../mwbase/mechanicsmanager.hpp"
|
||||||
#include "../mwbase/world.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())
|
if (target != actor && target.getClass().isActor())
|
||||||
{
|
{
|
||||||
osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(target);
|
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 actorPos = actor.getRefData().getPosition().asVec3();
|
||||||
osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(actor);
|
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;
|
osg::Vec3f dir = targetPos - actorPos;
|
||||||
|
|
||||||
|
|
|
@ -178,19 +178,10 @@ namespace MWMechanics
|
||||||
}
|
}
|
||||||
storage.mActionCooldown -= duration;
|
storage.mActionCooldown -= duration;
|
||||||
|
|
||||||
float& timerReact = storage.mTimerReact;
|
if (storage.mReaction.update(duration) == Misc::TimerStatus::Waiting)
|
||||||
if (timerReact < AI_REACTION_TIME)
|
return false;
|
||||||
{
|
|
||||||
timerReact += duration;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
timerReact = 0;
|
|
||||||
if (attack(actor, target, storage, characterController))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return attack(actor, target, storage, characterController);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AiCombat::attack(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, AiCombatStorage& storage, CharacterController& 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 vActorPos(pos.asVec3());
|
||||||
const osg::Vec3f vTargetPos(target.getRefData().getPosition().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);
|
float distToTarget = MWBase::Environment::get().getWorld()->getHitDistance(actor, target);
|
||||||
|
|
||||||
storage.mReadyToAttack = (currentAction->isAttackingOrSpell() && distToTarget <= rangeAttack && storage.mLOS);
|
storage.mReadyToAttack = (currentAction->isAttackingOrSpell() && distToTarget <= rangeAttack && storage.mLOS);
|
||||||
|
@ -313,13 +303,14 @@ namespace MWMechanics
|
||||||
if (isRangedCombat)
|
if (isRangedCombat)
|
||||||
{
|
{
|
||||||
// rotate actor taking into account target movement direction and projectile speed
|
// 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[0] = getXAngleToDir(vAimDir);
|
||||||
storage.mMovement.mRotation[2] = getZAngleToDir(vAimDir);
|
storage.mMovement.mRotation[2] = getZAngleToDir(vAimDir);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
osg::Vec3f vAimDir = MWBase::Environment::get().getWorld()->aimToTarget(actor, target, false);
|
||||||
storage.mMovement.mRotation[0] = getXAngleToDir(vAimDir);
|
storage.mMovement.mRotation[0] = getXAngleToDir(vAimDir);
|
||||||
storage.mMovement.mRotation[2] = getZAngleToDir((vTargetPos-vActorPos)); // using vAimDir results in spastic movements since the head is animated
|
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
|
// 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 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();
|
float distToTarget = vDirToTarget.length();
|
||||||
|
|
||||||
osg::Vec3f vTargetMoveDir = vTargetPos - vLastTargetPos;
|
osg::Vec3f vTargetMoveDir = vTargetPos - vLastTargetPos;
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
#include "pathfinding.hpp"
|
#include "pathfinding.hpp"
|
||||||
#include "movement.hpp"
|
#include "movement.hpp"
|
||||||
#include "obstacle.hpp"
|
#include "obstacle.hpp"
|
||||||
|
#include "aitimer.hpp"
|
||||||
|
|
||||||
namespace ESM
|
namespace ESM
|
||||||
{
|
{
|
||||||
|
@ -27,7 +28,7 @@ namespace MWMechanics
|
||||||
struct AiCombatStorage : AiTemporaryBase
|
struct AiCombatStorage : AiTemporaryBase
|
||||||
{
|
{
|
||||||
float mAttackCooldown;
|
float mAttackCooldown;
|
||||||
float mTimerReact;
|
AiReactionTimer mReaction;
|
||||||
float mTimerCombatMove;
|
float mTimerCombatMove;
|
||||||
bool mReadyToAttack;
|
bool mReadyToAttack;
|
||||||
bool mAttack;
|
bool mAttack;
|
||||||
|
@ -60,7 +61,6 @@ namespace MWMechanics
|
||||||
|
|
||||||
AiCombatStorage():
|
AiCombatStorage():
|
||||||
mAttackCooldown(0.0f),
|
mAttackCooldown(0.0f),
|
||||||
mTimerReact(AI_REACTION_TIME),
|
|
||||||
mTimerCombatMove(0.0f),
|
mTimerCombatMove(0.0f),
|
||||||
mReadyToAttack(false),
|
mReadyToAttack(false),
|
||||||
mAttack(false),
|
mAttack(false),
|
||||||
|
|
|
@ -73,23 +73,25 @@ namespace MWMechanics
|
||||||
const MWWorld::Ptr follower = MWBase::Environment::get().getWorld()->getPtr(mTargetActorRefId, false);
|
const MWWorld::Ptr follower = MWBase::Environment::get().getWorld()->getPtr(mTargetActorRefId, false);
|
||||||
const osg::Vec3f leaderPos = actor.getRefData().getPosition().asVec3();
|
const osg::Vec3f leaderPos = actor.getRefData().getPosition().asVec3();
|
||||||
const osg::Vec3f followerPos = follower.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)
|
if ((leaderPos - followerPos).length2() <= mMaxDist * mMaxDist)
|
||||||
{
|
{
|
||||||
const osg::Vec3f dest(mX, mY, mZ);
|
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;
|
mRemainingDuration = mDuration;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
mMaxDist = 450;
|
mMaxDist = maxHalfExtent + 450.0f;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Stop moving if the player is too far away
|
// Stop moving if the player is too far away
|
||||||
MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, "idle3", 0, 1);
|
MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, "idle3", 0, 1);
|
||||||
actor.getClass().getMovementSettings(actor).mPosition[1] = 0;
|
actor.getClass().getMovementSettings(actor).mPosition[1] = 0;
|
||||||
mMaxDist = 250;
|
mMaxDist = maxHalfExtent + 250.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -28,7 +28,6 @@
|
||||||
MWMechanics::AiPackage::AiPackage(AiPackageTypeId typeId, const Options& options) :
|
MWMechanics::AiPackage::AiPackage(AiPackageTypeId typeId, const Options& options) :
|
||||||
mTypeId(typeId),
|
mTypeId(typeId),
|
||||||
mOptions(options),
|
mOptions(options),
|
||||||
mTimer(AI_REACTION_TIME + 1.0f), // to force initial pathbuild
|
|
||||||
mTargetActorRefId(""),
|
mTargetActorRefId(""),
|
||||||
mTargetActorId(-1),
|
mTargetActorId(-1),
|
||||||
mRotateOnTheRunChecks(0),
|
mRotateOnTheRunChecks(0),
|
||||||
|
@ -64,7 +63,7 @@ MWWorld::Ptr MWMechanics::AiPackage::getTarget() const
|
||||||
void MWMechanics::AiPackage::reset()
|
void MWMechanics::AiPackage::reset()
|
||||||
{
|
{
|
||||||
// reset all members
|
// reset all members
|
||||||
mTimer = AI_REACTION_TIME + 1.0f;
|
mReaction.reset();
|
||||||
mIsShortcutting = false;
|
mIsShortcutting = false;
|
||||||
mShortcutProhibited = false;
|
mShortcutProhibited = false;
|
||||||
mShortcutFailPos = osg::Vec3f();
|
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)
|
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
|
const osg::Vec3f position = actor.getRefData().getPosition().asVec3(); //position of the actor
|
||||||
MWBase::World* world = MWBase::Environment::get().getWorld();
|
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 isDestReached = (distToTarget <= destTolerance);
|
||||||
const bool actorCanMoveByZ = canActorMoveByZAxis(actor);
|
const bool actorCanMoveByZ = canActorMoveByZAxis(actor);
|
||||||
|
|
||||||
if (!isDestReached && mTimer > AI_REACTION_TIME)
|
if (!isDestReached && timerStatus == Misc::TimerStatus::Elapsed)
|
||||||
{
|
{
|
||||||
if (actor.getClass().isBipedal(actor))
|
if (actor.getClass().isBipedal(actor))
|
||||||
openDoors(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
|
if (wasShortcutting || doesPathNeedRecalc(dest, actor)) // if need to rebuild path
|
||||||
{
|
{
|
||||||
const auto pathfindingHalfExtents = world->getPathfindingHalfExtents(actor);
|
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));
|
pathfindingHalfExtents, getNavigatorFlags(actor), getAreaCosts(actor));
|
||||||
mRotateOnTheRunChecks = 3;
|
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
|
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
|
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
|
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();
|
const MWWorld::Class& actorClass = actor.getClass();
|
||||||
DetourNavigator::Flags result = DetourNavigator::Flag_none;
|
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;
|
result |= DetourNavigator::Flag_swim;
|
||||||
|
|
||||||
if (actorClass.canWalk(actor))
|
if (actorClass.canWalk(actor))
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
#include "obstacle.hpp"
|
#include "obstacle.hpp"
|
||||||
#include "aistate.hpp"
|
#include "aistate.hpp"
|
||||||
#include "aipackagetypeid.hpp"
|
#include "aipackagetypeid.hpp"
|
||||||
|
#include "aitimer.hpp"
|
||||||
|
|
||||||
namespace MWWorld
|
namespace MWWorld
|
||||||
{
|
{
|
||||||
|
@ -28,8 +29,6 @@ namespace ESM
|
||||||
|
|
||||||
namespace MWMechanics
|
namespace MWMechanics
|
||||||
{
|
{
|
||||||
const float AI_REACTION_TIME = 0.25f;
|
|
||||||
|
|
||||||
class CharacterController;
|
class CharacterController;
|
||||||
class PathgridGraph;
|
class PathgridGraph;
|
||||||
|
|
||||||
|
@ -158,7 +157,7 @@ namespace MWMechanics
|
||||||
PathFinder mPathFinder;
|
PathFinder mPathFinder;
|
||||||
ObstacleCheck mObstacleCheck;
|
ObstacleCheck mObstacleCheck;
|
||||||
|
|
||||||
float mTimer;
|
AiReactionTimer mReaction;
|
||||||
|
|
||||||
std::string mTargetActorRefId;
|
std::string mTargetActorRefId;
|
||||||
mutable int mTargetActorId;
|
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);
|
doPerFrameActionsForState(actor, duration, storage);
|
||||||
|
|
||||||
float& lastReaction = storage.mReaction;
|
if (storage.mReaction.update(duration) == Misc::TimerStatus::Waiting)
|
||||||
lastReaction += duration;
|
|
||||||
if (AI_REACTION_TIME <= lastReaction)
|
|
||||||
{
|
|
||||||
lastReaction = 0;
|
|
||||||
return reactionTimeActions(actor, storage, pos);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
return reactionTimeActions(actor, storage, pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AiWander::reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage, ESM::Position& pos)
|
bool AiWander::reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage, ESM::Position& pos)
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
#include "pathfinding.hpp"
|
#include "pathfinding.hpp"
|
||||||
#include "obstacle.hpp"
|
#include "obstacle.hpp"
|
||||||
#include "aistate.hpp"
|
#include "aistate.hpp"
|
||||||
|
#include "aitimer.hpp"
|
||||||
|
|
||||||
namespace ESM
|
namespace ESM
|
||||||
{
|
{
|
||||||
|
@ -25,7 +26,7 @@ namespace MWMechanics
|
||||||
/// \brief This class holds the variables AiWander needs which are deleted if the package becomes inactive.
|
/// \brief This class holds the variables AiWander needs which are deleted if the package becomes inactive.
|
||||||
struct AiWanderStorage : AiTemporaryBase
|
struct AiWanderStorage : AiTemporaryBase
|
||||||
{
|
{
|
||||||
float mReaction; // update some actions infrequently
|
AiReactionTimer mReaction;
|
||||||
|
|
||||||
// AiWander states
|
// AiWander states
|
||||||
enum WanderState
|
enum WanderState
|
||||||
|
@ -57,7 +58,6 @@ namespace MWMechanics
|
||||||
int mStuckCount;
|
int mStuckCount;
|
||||||
|
|
||||||
AiWanderStorage():
|
AiWanderStorage():
|
||||||
mReaction(0),
|
|
||||||
mState(Wander_ChooseAction),
|
mState(Wander_ChooseAction),
|
||||||
mIsWanderingManually(false),
|
mIsWanderingManually(false),
|
||||||
mCanWanderAlongPathGrid(true),
|
mCanWanderAlongPathGrid(true),
|
||||||
|
|
|
@ -228,38 +228,48 @@ void CharacterController::refreshHitRecoilAnims(CharacterState& idle)
|
||||||
if(mHitState == CharState_None)
|
if(mHitState == CharState_None)
|
||||||
{
|
{
|
||||||
if ((mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() < 0
|
if ((mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() < 0
|
||||||
|| mPtr.getClass().getCreatureStats(mPtr).getFatigue().getBase() == 0)
|
|| mPtr.getClass().getCreatureStats(mPtr).getFatigue().getBase() == 0))
|
||||||
&& mAnimation->hasAnimation("knockout"))
|
|
||||||
{
|
{
|
||||||
mTimeUntilWake = Misc::Rng::rollClosedProbability() * 2 + 1; // Wake up after 1 to 3 seconds
|
mTimeUntilWake = Misc::Rng::rollClosedProbability() * 2 + 1; // Wake up after 1 to 3 seconds
|
||||||
if (isSwimming && mAnimation->hasAnimation("swimknockout"))
|
if (isSwimming && mAnimation->hasAnimation("swimknockout"))
|
||||||
{
|
{
|
||||||
mHitState = CharState_SwimKnockOut;
|
mHitState = CharState_SwimKnockOut;
|
||||||
mCurrentHit = "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;
|
mHitState = CharState_KnockOut;
|
||||||
mCurrentHit = "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);
|
mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(true);
|
||||||
}
|
}
|
||||||
else if(knockdown && mAnimation->hasAnimation("knockdown"))
|
else if (knockdown)
|
||||||
{
|
{
|
||||||
if (isSwimming && mAnimation->hasAnimation("swimknockdown"))
|
if (isSwimming && mAnimation->hasAnimation("swimknockdown"))
|
||||||
{
|
{
|
||||||
mHitState = CharState_SwimKnockDown;
|
mHitState = CharState_SwimKnockDown;
|
||||||
mCurrentHit = "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;
|
mHitState = CharState_KnockDown;
|
||||||
mCurrentHit = "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);
|
||||||
}
|
}
|
||||||
|
|
||||||
mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0);
|
|
||||||
}
|
}
|
||||||
else if (recovery)
|
else if (recovery)
|
||||||
{
|
{
|
||||||
|
@ -3179,7 +3189,7 @@ void CharacterController::updateHeadTracking(float duration)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
// no head node to look at, fall back to look at center of collision box
|
// 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();
|
direction.normalize();
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ namespace MWMechanics
|
||||||
{
|
{
|
||||||
struct CorprusStats
|
struct CorprusStats
|
||||||
{
|
{
|
||||||
static const int sWorseningPeriod = 24;
|
static constexpr int sWorseningPeriod = 24;
|
||||||
|
|
||||||
int mWorsenings[ESM::Attribute::Length];
|
int mWorsenings[ESM::Attribute::Length];
|
||||||
MWWorld::TimeStamp mNextWorsening;
|
MWWorld::TimeStamp mNextWorsening;
|
||||||
|
|
|
@ -708,7 +708,7 @@ namespace MWMechanics
|
||||||
// Deviating from Morrowind here: it doesn't increase disposition on marginal wins,
|
// Deviating from Morrowind here: it doesn't increase disposition on marginal wins,
|
||||||
// which seems to be a bug (MCP fixes it too).
|
// which seems to be a bug (MCP fixes it too).
|
||||||
// Original logic: x = 0, y = -iPerMinChange
|
// Original logic: x = 0, y = -iPerMinChange
|
||||||
x = -iPerMinChange;
|
x = iPerMinChange;
|
||||||
y = x; // This goes unused.
|
y = x; // This goes unused.
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
@ -12,7 +12,7 @@ namespace MWMechanics
|
||||||
{
|
{
|
||||||
struct Movement;
|
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
|
/// tests actor's proximity to a closed door by default
|
||||||
bool proximityToDoor(const MWWorld::Ptr& actor, float minDist);
|
bool proximityToDoor(const MWWorld::Ptr& actor, float minDist);
|
||||||
|
|
|
@ -442,4 +442,21 @@ namespace MWMechanics
|
||||||
|
|
||||||
std::copy(prePath.rbegin(), prePath.rend(), std::front_inserter(mPath));
|
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,
|
void buildPathByNavMeshToNextPoint(const MWWorld::ConstPtr& actor, const osg::Vec3f& halfExtents,
|
||||||
const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts);
|
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
|
/// Remove front point if exist and within tolerance
|
||||||
void update(const osg::Vec3f& position, float pointTolerance, float destinationTolerance,
|
void update(const osg::Vec3f& position, float pointTolerance, float destinationTolerance,
|
||||||
bool shortenIfAlmostStraight, bool canMoveByZ);
|
bool shortenIfAlmostStraight, bool canMoveByZ);
|
||||||
|
|
|
@ -194,22 +194,12 @@ namespace MWMechanics
|
||||||
|
|
||||||
for (std::map<ESM::SummonKey, int>::iterator it = creatureMap.begin(); it != creatureMap.end(); )
|
for (std::map<ESM::SummonKey, int>::iterator it = creatureMap.begin(); it != creatureMap.end(); )
|
||||||
{
|
{
|
||||||
/*
|
if(it->second == -1)
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
++it;
|
// Keep the spell effect active if we failed to spawn anything
|
||||||
|
it++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
End of tes3mp addition
|
|
||||||
*/
|
|
||||||
|
|
||||||
MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(it->second);
|
MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(it->second);
|
||||||
if (ptr.isEmpty() || (ptr.getClass().getCreatureStats(ptr).isDead() && ptr.getClass().getCreatureStats(ptr).isDeathAnimationFinished()))
|
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)
|
Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, PhysicsTaskScheduler* scheduler)
|
||||||
: mStandingOnPtr(nullptr), mCanWaterWalk(false), mWalkingOnWater(false)
|
: mStandingOnPtr(nullptr), mCanWaterWalk(false), mWalkingOnWater(false)
|
||||||
, mCollisionObject(nullptr), mMeshTranslation(shape->mCollisionBox.center), mHalfExtents(shape->mCollisionBox.extents)
|
, 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)
|
, mForce(0.f, 0.f, 0.f), mOnGround(true), mOnSlope(false)
|
||||||
, mInternalCollisionMode(true)
|
, mInternalCollisionMode(true)
|
||||||
, mExternalCollisionMode(true)
|
, mExternalCollisionMode(true)
|
||||||
|
@ -213,9 +214,10 @@ bool Actor::setPosition(const osg::Vec3f& position)
|
||||||
if (mSkipSimulation)
|
if (mSkipSimulation)
|
||||||
return false;
|
return false;
|
||||||
bool hasChanged = mPosition != position || mPositionOffset.length() != 0 || mWorldPositionChanged;
|
bool hasChanged = mPosition != position || mPositionOffset.length() != 0 || mWorldPositionChanged;
|
||||||
mPreviousPosition = mPosition + mPositionOffset;
|
updateWorldPosition();
|
||||||
mPosition = position + mPositionOffset;
|
applyOffsetChange();
|
||||||
mPositionOffset = osg::Vec3f();
|
mPreviousPosition = mPosition;
|
||||||
|
mPosition = position;
|
||||||
return hasChanged;
|
return hasChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -232,6 +234,7 @@ void Actor::applyOffsetChange()
|
||||||
mWorldPosition += mPositionOffset;
|
mWorldPosition += mPositionOffset;
|
||||||
mPosition += mPositionOffset;
|
mPosition += mPositionOffset;
|
||||||
mPreviousPosition += mPositionOffset;
|
mPreviousPosition += mPositionOffset;
|
||||||
|
mSimulationPosition += mPositionOffset;
|
||||||
mPositionOffset = osg::Vec3f();
|
mPositionOffset = osg::Vec3f();
|
||||||
mWorldPositionChanged = true;
|
mWorldPositionChanged = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -159,6 +159,24 @@ namespace MWPhysics
|
||||||
MWWorld::Ptr getStandingOnPtr() const;
|
MWWorld::Ptr getStandingOnPtr() const;
|
||||||
void setStandingOnPtr(const MWWorld::Ptr& ptr);
|
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:
|
private:
|
||||||
MWWorld::Ptr mStandingOnPtr;
|
MWWorld::Ptr mStandingOnPtr;
|
||||||
/// Removes then re-adds the collision object to the dynamics world
|
/// Removes then re-adds the collision object to the dynamics world
|
||||||
|
@ -192,6 +210,9 @@ namespace MWPhysics
|
||||||
btTransform mLocalTransform;
|
btTransform mLocalTransform;
|
||||||
mutable std::mutex mPositionMutex;
|
mutable std::mutex mPositionMutex;
|
||||||
|
|
||||||
|
unsigned int mStuckFrames;
|
||||||
|
osg::Vec3f mLastStuckPosition;
|
||||||
|
|
||||||
osg::Vec3f mForce;
|
osg::Vec3f mForce;
|
||||||
std::atomic<bool> mOnGround;
|
std::atomic<bool> mOnGround;
|
||||||
std::atomic<bool> mOnSlope;
|
std::atomic<bool> mOnSlope;
|
||||||
|
|
|
@ -3,24 +3,24 @@
|
||||||
|
|
||||||
namespace MWPhysics
|
namespace MWPhysics
|
||||||
{
|
{
|
||||||
static const float sStepSizeUp = 34.0f;
|
static constexpr float sStepSizeUp = 34.0f;
|
||||||
static const float sStepSizeDown = 62.0f;
|
static constexpr float sStepSizeDown = 62.0f;
|
||||||
|
|
||||||
static const float sMinStep = 10.0f; // hack to skip over tiny unwalkable slopes
|
static constexpr 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 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
|
// 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 constexpr float sGroundOffset = 1.0f;
|
||||||
static const float sMaxSlope = 49.0f;
|
static constexpr float sMaxSlope = 49.0f;
|
||||||
|
|
||||||
// Arbitrary number. To prevent infinite loops. They shouldn't happen but it's good to be prepared.
|
// 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.
|
// 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
|
// 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
|
// 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
|
#endif
|
||||||
|
|
|
@ -204,7 +204,7 @@ namespace MWPhysics
|
||||||
osg::Vec3f lastSlideNormalFallback(0,0,1);
|
osg::Vec3f lastSlideNormalFallback(0,0,1);
|
||||||
bool forceGroundTest = false;
|
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;
|
osg::Vec3f nextpos = newPosition + velocity * remainingTime;
|
||||||
|
|
||||||
|
@ -394,6 +394,12 @@ namespace MWPhysics
|
||||||
isOnGround = false;
|
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)
|
if((isOnGround && !isOnSlope) || newPosition.z() < swimlevel || actor.mFlying)
|
||||||
|
@ -437,13 +443,23 @@ namespace MWPhysics
|
||||||
auto* collisionObject = physicActor->getCollisionObject();
|
auto* collisionObject = physicActor->getCollisionObject();
|
||||||
auto tempPosition = actor.mPosition;
|
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)
|
// 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()
|
// 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());
|
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
|
// use a 3d approximation of the movement vector to better judge player intent
|
||||||
const ESM::Position& refpos = ptr.getRefData().getPosition();
|
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;
|
||||||
auto velocity = (osg::Quat(refpos.rot[0], osg::Vec3f(-1, 0, 0)) * osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1))) * actor.mMovement;
|
|
||||||
// try to pop outside of the world before doing anything else if we're inside of it
|
// try to pop outside of the world before doing anything else if we're inside of it
|
||||||
if (!physicActor->getOnGround() || physicActor->getOnSlope())
|
if (!physicActor->getOnGround() || physicActor->getOnSlope())
|
||||||
velocity += physicActor->getInertialForce();
|
velocity += physicActor->getInertialForce();
|
||||||
|
@ -470,6 +486,8 @@ namespace MWPhysics
|
||||||
auto contactCallback = gatherContacts({0.0, 0.0, 0.0});
|
auto contactCallback = gatherContacts({0.0, 0.0, 0.0});
|
||||||
if(contactCallback.mDistance < -sAllowedPenetration)
|
if(contactCallback.mDistance < -sAllowedPenetration)
|
||||||
{
|
{
|
||||||
|
physicActor->setStuckFrames(physicActor->getStuckFrames() + 1);
|
||||||
|
physicActor->setLastStuckPosition(actor.mPosition);
|
||||||
// we are; try moving it out of the world
|
// we are; try moving it out of the world
|
||||||
auto positionDelta = contactCallback.mContactSum;
|
auto positionDelta = contactCallback.mContactSum;
|
||||||
// limit rejection delta to the largest known individual rejections
|
// 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);
|
collisionObject->setWorldTransform(oldTransform);
|
||||||
actor.mPosition = tempPosition;
|
actor.mPosition = tempPosition;
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
#include "components/settings/settings.hpp"
|
#include "components/settings/settings.hpp"
|
||||||
#include "../mwmechanics/actorutil.hpp"
|
#include "../mwmechanics/actorutil.hpp"
|
||||||
#include "../mwmechanics/movement.hpp"
|
#include "../mwmechanics/movement.hpp"
|
||||||
|
#include "../mwrender/bulletdebugdraw.hpp"
|
||||||
#include "../mwworld/class.hpp"
|
#include "../mwworld/class.hpp"
|
||||||
#include "../mwworld/player.hpp"
|
#include "../mwworld/player.hpp"
|
||||||
|
|
||||||
|
@ -101,7 +102,7 @@ namespace
|
||||||
|
|
||||||
osg::Vec3f interpolateMovements(MWPhysics::ActorFrameData& actorData, float timeAccum, float physicsDt)
|
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);
|
return actorData.mPosition * interpolationFactor + actorData.mActorRaw->getPreviousPosition() * (1.f - interpolationFactor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,10 +138,12 @@ namespace
|
||||||
|
|
||||||
namespace MWPhysics
|
namespace MWPhysics
|
||||||
{
|
{
|
||||||
PhysicsTaskScheduler::PhysicsTaskScheduler(float physicsDt, std::shared_ptr<btCollisionWorld> collisionWorld)
|
PhysicsTaskScheduler::PhysicsTaskScheduler(float physicsDt, btCollisionWorld *collisionWorld, MWRender::DebugDrawer* debugDrawer)
|
||||||
: mPhysicsDt(physicsDt)
|
: mDefaultPhysicsDt(physicsDt)
|
||||||
|
, mPhysicsDt(physicsDt)
|
||||||
, mTimeAccum(0.f)
|
, mTimeAccum(0.f)
|
||||||
, mCollisionWorld(std::move(collisionWorld))
|
, mCollisionWorld(collisionWorld)
|
||||||
|
, mDebugDrawer(debugDrawer)
|
||||||
, mNumJobs(0)
|
, mNumJobs(0)
|
||||||
, mRemainingSteps(0)
|
, mRemainingSteps(0)
|
||||||
, mLOSCacheExpiry(Settings::Manager::getInt("lineofsight keep inactive cache", "Physics"))
|
, mLOSCacheExpiry(Settings::Manager::getInt("lineofsight keep inactive cache", "Physics"))
|
||||||
|
@ -152,6 +155,11 @@ namespace MWPhysics
|
||||||
, mNextLOS(0)
|
, mNextLOS(0)
|
||||||
, mFrameNumber(0)
|
, mFrameNumber(0)
|
||||||
, mTimer(osg::Timer::instance())
|
, mTimer(osg::Timer::instance())
|
||||||
|
, mPrevStepCount(1)
|
||||||
|
, mBudget(physicsDt)
|
||||||
|
, mAsyncBudget(0.0f)
|
||||||
|
, mBudgetCursor(0)
|
||||||
|
, mAsyncStartTime(0)
|
||||||
, mTimeBegin(0)
|
, mTimeBegin(0)
|
||||||
, mTimeEnd(0)
|
, mTimeEnd(0)
|
||||||
, mFrameStart(0)
|
, mFrameStart(0)
|
||||||
|
@ -179,7 +187,7 @@ namespace MWPhysics
|
||||||
if (data.mActor.lock())
|
if (data.mActor.lock())
|
||||||
{
|
{
|
||||||
std::unique_lock lock(mCollisionWorldMutex);
|
std::unique_lock lock(mCollisionWorldMutex);
|
||||||
MovementSolver::unstuck(data, mCollisionWorld.get());
|
MovementSolver::unstuck(data, mCollisionWorld);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -220,13 +228,61 @@ namespace MWPhysics
|
||||||
thread.join();
|
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.
|
// This function run in the main thread.
|
||||||
// While the mSimulationMutex is held, background physics threads can't run.
|
// While the mSimulationMutex is held, background physics threads can't run.
|
||||||
|
|
||||||
std::unique_lock lock(mSimulationMutex);
|
std::unique_lock lock(mSimulationMutex);
|
||||||
|
|
||||||
|
double timeStart = mTimer->tick();
|
||||||
|
|
||||||
mMovedActors.clear();
|
mMovedActors.clear();
|
||||||
|
|
||||||
// start by finishing previous background computation
|
// start by finishing previous background computation
|
||||||
|
@ -251,14 +307,21 @@ namespace MWPhysics
|
||||||
mMovedActors.emplace_back(data.mActorRaw->getPtr());
|
mMovedActors.emplace_back(data.mActorRaw->getPtr());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if(mAdvanceSimulation)
|
||||||
|
mAsyncBudget.update(mTimer->delta_s(mAsyncStartTime, mTimeEnd), mPrevStepCount, mBudgetCursor);
|
||||||
updateStats(frameStart, frameNumber, stats);
|
updateStats(frameStart, frameNumber, stats);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto [numSteps, newDelta] = calculateStepConfig(timeAccum);
|
||||||
|
timeAccum -= numSteps*newDelta;
|
||||||
|
|
||||||
// init
|
// init
|
||||||
for (auto& data : actorsData)
|
for (auto& data : actorsData)
|
||||||
data.updatePosition();
|
data.updatePosition(mCollisionWorld);
|
||||||
|
mPrevStepCount = numSteps;
|
||||||
mRemainingSteps = numSteps;
|
mRemainingSteps = numSteps;
|
||||||
mTimeAccum = timeAccum;
|
mTimeAccum = timeAccum;
|
||||||
|
mPhysicsDt = newDelta;
|
||||||
mActorsFrameData = std::move(actorsData);
|
mActorsFrameData = std::move(actorsData);
|
||||||
mAdvanceSimulation = (mRemainingSteps != 0);
|
mAdvanceSimulation = (mRemainingSteps != 0);
|
||||||
mNewFrame = true;
|
mNewFrame = true;
|
||||||
|
@ -269,20 +332,30 @@ namespace MWPhysics
|
||||||
if (mAdvanceSimulation)
|
if (mAdvanceSimulation)
|
||||||
mWorldFrameData = std::make_unique<WorldFrameData>();
|
mWorldFrameData = std::make_unique<WorldFrameData>();
|
||||||
|
|
||||||
|
if (mAdvanceSimulation)
|
||||||
|
mBudgetCursor += 1;
|
||||||
|
|
||||||
if (mNumThreads == 0)
|
if (mNumThreads == 0)
|
||||||
{
|
{
|
||||||
syncComputation();
|
syncComputation();
|
||||||
|
if(mAdvanceSimulation)
|
||||||
|
mBudget.update(mTimer->delta_s(timeStart, mTimer->tick()), numSteps, mBudgetCursor);
|
||||||
return mMovedActors;
|
return mMovedActors;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mAsyncStartTime = mTimer->tick();
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
mHasJob.notify_all();
|
mHasJob.notify_all();
|
||||||
|
if (mAdvanceSimulation)
|
||||||
|
mBudget.update(mTimer->delta_s(timeStart, mTimer->tick()), 1, mBudgetCursor);
|
||||||
return mMovedActors;
|
return mMovedActors;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<MWWorld::Ptr>& PhysicsTaskScheduler::resetSimulation(const ActorMap& actors)
|
const std::vector<MWWorld::Ptr>& PhysicsTaskScheduler::resetSimulation(const ActorMap& actors)
|
||||||
{
|
{
|
||||||
std::unique_lock lock(mSimulationMutex);
|
std::unique_lock lock(mSimulationMutex);
|
||||||
|
mBudget.reset(mDefaultPhysicsDt);
|
||||||
|
mAsyncBudget.reset(0.0f);
|
||||||
mMovedActors.clear();
|
mMovedActors.clear();
|
||||||
mActorsFrameData.clear();
|
mActorsFrameData.clear();
|
||||||
for (const auto& [_, actor] : actors)
|
for (const auto& [_, actor] : actors)
|
||||||
|
@ -310,7 +383,7 @@ namespace MWPhysics
|
||||||
void PhysicsTaskScheduler::contactTest(btCollisionObject* colObj, btCollisionWorld::ContactResultCallback& resultCallback)
|
void PhysicsTaskScheduler::contactTest(btCollisionObject* colObj, btCollisionWorld::ContactResultCallback& resultCallback)
|
||||||
{
|
{
|
||||||
std::shared_lock lock(mCollisionWorldMutex);
|
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)
|
std::optional<btVector3> PhysicsTaskScheduler::getHitPoint(const btTransform& from, btCollisionObject* target)
|
||||||
|
@ -461,7 +534,7 @@ namespace MWPhysics
|
||||||
if(const auto actor = mActorsFrameData[job].mActor.lock())
|
if(const auto actor = mActorsFrameData[job].mActor.lock())
|
||||||
{
|
{
|
||||||
MaybeSharedLock lockColWorld(mCollisionWorldMutex, mThreadSafeBullet);
|
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)
|
for (auto& actorData : mActorsFrameData)
|
||||||
{
|
{
|
||||||
MovementSolver::unstuck(actorData, mCollisionWorld.get());
|
MovementSolver::unstuck(actorData, mCollisionWorld);
|
||||||
MovementSolver::move(actorData, mPhysicsDt, mCollisionWorld.get(), *mWorldFrameData);
|
MovementSolver::move(actorData, mPhysicsDt, mCollisionWorld, *mWorldFrameData);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateActorsPositions();
|
updateActorsPositions();
|
||||||
|
@ -555,4 +628,10 @@ namespace MWPhysics
|
||||||
mTimeBegin = mTimer->tick();
|
mTimeBegin = mTimer->tick();
|
||||||
mFrameNumber = frameNumber;
|
mFrameNumber = frameNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PhysicsTaskScheduler::debugDraw()
|
||||||
|
{
|
||||||
|
std::shared_lock lock(mCollisionWorldMutex);
|
||||||
|
mDebugDrawer->step();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,18 +13,24 @@
|
||||||
|
|
||||||
#include "physicssystem.hpp"
|
#include "physicssystem.hpp"
|
||||||
#include "ptrholder.hpp"
|
#include "ptrholder.hpp"
|
||||||
|
#include "components/misc/budgetmeasurement.hpp"
|
||||||
|
|
||||||
namespace Misc
|
namespace Misc
|
||||||
{
|
{
|
||||||
class Barrier;
|
class Barrier;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace MWRender
|
||||||
|
{
|
||||||
|
class DebugDrawer;
|
||||||
|
}
|
||||||
|
|
||||||
namespace MWPhysics
|
namespace MWPhysics
|
||||||
{
|
{
|
||||||
class PhysicsTaskScheduler
|
class PhysicsTaskScheduler
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
PhysicsTaskScheduler(float physicsDt, std::shared_ptr<btCollisionWorld> collisionWorld);
|
PhysicsTaskScheduler(float physicsDt, btCollisionWorld* collisionWorld, MWRender::DebugDrawer* debugDrawer);
|
||||||
~PhysicsTaskScheduler();
|
~PhysicsTaskScheduler();
|
||||||
|
|
||||||
/// @brief move actors taking into account desired movements and collisions
|
/// @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 timeAccum accumulated time from previous run to interpolate movements
|
||||||
/// @param actorsData per actor data needed to compute new positions
|
/// @param actorsData per actor data needed to compute new positions
|
||||||
/// @return new position of each actor
|
/// @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);
|
const std::vector<MWWorld::Ptr>& resetSimulation(const ActorMap& actors);
|
||||||
|
|
||||||
|
@ -48,6 +54,7 @@ namespace MWPhysics
|
||||||
void removeCollisionObject(btCollisionObject* collisionObject);
|
void removeCollisionObject(btCollisionObject* collisionObject);
|
||||||
void updateSingleAabb(std::weak_ptr<PtrHolder> ptr, bool immediate=false);
|
void updateSingleAabb(std::weak_ptr<PtrHolder> ptr, bool immediate=false);
|
||||||
bool getLineOfSight(const std::weak_ptr<Actor>& actor1, const std::weak_ptr<Actor>& actor2);
|
bool getLineOfSight(const std::weak_ptr<Actor>& actor1, const std::weak_ptr<Actor>& actor2);
|
||||||
|
void debugDraw();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void syncComputation();
|
void syncComputation();
|
||||||
|
@ -58,14 +65,16 @@ namespace MWPhysics
|
||||||
void updateAabbs();
|
void updateAabbs();
|
||||||
void updatePtrAabb(const std::weak_ptr<PtrHolder>& ptr);
|
void updatePtrAabb(const std::weak_ptr<PtrHolder>& ptr);
|
||||||
void updateStats(osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats);
|
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::unique_ptr<WorldFrameData> mWorldFrameData;
|
||||||
std::vector<ActorFrameData> mActorsFrameData;
|
std::vector<ActorFrameData> mActorsFrameData;
|
||||||
std::vector<MWWorld::Ptr> mMovedActors;
|
std::vector<MWWorld::Ptr> mMovedActors;
|
||||||
|
float mDefaultPhysicsDt;
|
||||||
/*
|
/*
|
||||||
Start of tes3mp change (major)
|
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:
|
public:
|
||||||
float mPhysicsDt;
|
float mPhysicsDt;
|
||||||
|
@ -74,7 +83,8 @@ namespace MWPhysics
|
||||||
End of tes3mp change (major)
|
End of tes3mp change (major)
|
||||||
*/
|
*/
|
||||||
float mTimeAccum;
|
float mTimeAccum;
|
||||||
std::shared_ptr<btCollisionWorld> mCollisionWorld;
|
btCollisionWorld* mCollisionWorld;
|
||||||
|
MWRender::DebugDrawer* mDebugDrawer;
|
||||||
std::vector<LOSRequest> mLOSCache;
|
std::vector<LOSRequest> mLOSCache;
|
||||||
std::set<std::weak_ptr<PtrHolder>, std::owner_less<std::weak_ptr<PtrHolder>>> mUpdateAabb;
|
std::set<std::weak_ptr<PtrHolder>, std::owner_less<std::weak_ptr<PtrHolder>>> mUpdateAabb;
|
||||||
|
|
||||||
|
@ -104,6 +114,12 @@ namespace MWPhysics
|
||||||
|
|
||||||
unsigned int mFrameNumber;
|
unsigned int mFrameNumber;
|
||||||
const osg::Timer* mTimer;
|
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 mTimeBegin;
|
||||||
osg::Timer_t mTimeEnd;
|
osg::Timer_t mTimeEnd;
|
||||||
osg::Timer_t mFrameStart;
|
osg::Timer_t mFrameStart;
|
||||||
|
|
|
@ -60,6 +60,22 @@
|
||||||
#include "movementsolver.hpp"
|
#include "movementsolver.hpp"
|
||||||
#include "mtphysics.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
|
namespace MWPhysics
|
||||||
{
|
{
|
||||||
PhysicsSystem::PhysicsSystem(Resource::ResourceSystem* resourceSystem, osg::ref_ptr<osg::Group> parentNode)
|
PhysicsSystem::PhysicsSystem(Resource::ResourceSystem* resourceSystem, osg::ref_ptr<osg::Group> parentNode)
|
||||||
|
@ -79,7 +95,7 @@ namespace MWPhysics
|
||||||
mDispatcher = std::make_unique<btCollisionDispatcher>(mCollisionConfiguration.get());
|
mDispatcher = std::make_unique<btCollisionDispatcher>(mCollisionConfiguration.get());
|
||||||
mBroadphase = std::make_unique<btDbvtBroadphase>();
|
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.
|
// 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.
|
// 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);
|
mDebugDrawer = std::make_unique<MWRender::DebugDrawer>(mParentNode, mCollisionWorld.get(), mDebugDrawEnabled);
|
||||||
|
mTaskScheduler = std::make_unique<PhysicsTaskScheduler>(mPhysicsDt, mCollisionWorld.get(), mDebugDrawer.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
PhysicsSystem::~PhysicsSystem()
|
PhysicsSystem::~PhysicsSystem()
|
||||||
|
@ -371,16 +387,7 @@ namespace MWPhysics
|
||||||
|
|
||||||
bool PhysicsSystem::canMoveToWaterSurface(const MWWorld::ConstPtr &actor, const float waterlevel)
|
bool PhysicsSystem::canMoveToWaterSurface(const MWWorld::ConstPtr &actor, const float waterlevel)
|
||||||
{
|
{
|
||||||
const Actor* physicActor = getActor(actor);
|
return ::canMoveToWaterSurface(getActor(actor), waterlevel, mCollisionWorld.get());
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
osg::Vec3f PhysicsSystem::getHalfExtents(const MWWorld::ConstPtr &actor) const
|
osg::Vec3f PhysicsSystem::getHalfExtents(const MWWorld::ConstPtr &actor) const
|
||||||
|
@ -768,19 +775,14 @@ namespace MWPhysics
|
||||||
{
|
{
|
||||||
mTimeAccum += dt;
|
mTimeAccum += dt;
|
||||||
|
|
||||||
const int maxAllowedSteps = 20;
|
|
||||||
int numSteps = mTimeAccum / mPhysicsDt;
|
|
||||||
numSteps = std::min(numSteps, maxAllowedSteps);
|
|
||||||
|
|
||||||
mTimeAccum -= numSteps * mPhysicsDt;
|
|
||||||
|
|
||||||
if (skipSimulation)
|
if (skipSimulation)
|
||||||
return mTaskScheduler->resetSimulation(mActors);
|
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;
|
std::vector<ActorFrameData> actorsFrameData;
|
||||||
actorsFrameData.reserve(mMovementQueue.size());
|
actorsFrameData.reserve(mMovementQueue.size());
|
||||||
|
@ -801,16 +803,10 @@ namespace MWPhysics
|
||||||
const MWMechanics::MagicEffects& effects = character.getClass().getCreatureStats(character).getMagicEffects();
|
const MWMechanics::MagicEffects& effects = character.getClass().getCreatureStats(character).getMagicEffects();
|
||||||
|
|
||||||
bool waterCollision = false;
|
bool waterCollision = false;
|
||||||
bool moveToWaterSurface = false;
|
|
||||||
if (cell->getCell()->hasWater() && effects.get(ESM::MagicEffect::WaterWalking).getMagnitude())
|
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;
|
waterCollision = true;
|
||||||
else if (physicActor->getCollisionMode() && canMoveToWaterSurface(character, waterlevel))
|
|
||||||
{
|
|
||||||
moveToWaterSurface = true;
|
|
||||||
waterCollision = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
physicActor->setCanWaterWalk(waterCollision);
|
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.
|
// Ue current value only if we don't advance the simulation. Otherwise we might get a stale value.
|
||||||
MWWorld::Ptr standingOn;
|
MWWorld::Ptr standingOn;
|
||||||
if (numSteps == 0)
|
if (!willSimulate)
|
||||||
standingOn = physicActor->getStandingOnPtr();
|
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();
|
mMovementQueue.clear();
|
||||||
return actorsFrameData;
|
return actorsFrameData;
|
||||||
|
@ -856,7 +852,7 @@ namespace MWPhysics
|
||||||
void PhysicsSystem::debugDraw()
|
void PhysicsSystem::debugDraw()
|
||||||
{
|
{
|
||||||
if (mDebugDrawEnabled)
|
if (mDebugDrawEnabled)
|
||||||
mDebugDrawer->step();
|
mTaskScheduler->debugDraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PhysicsSystem::isActorStandingOn(const MWWorld::Ptr &actor, const MWWorld::ConstPtr &object) const
|
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,
|
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),
|
: 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()
|
mWaterlevel(waterlevel), mSlowFall(slowFall), mOldHeight(0), mFallHeight(0), mMovement(movement), mPosition(), mRefpos()
|
||||||
{
|
{
|
||||||
const MWBase::World *world = MWBase::Environment::get().getWorld();
|
const MWBase::World *world = MWBase::Environment::get().getWorld();
|
||||||
|
@ -982,7 +978,7 @@ namespace MWPhysics
|
||||||
mWasOnGround = actor->getOnGround();
|
mWasOnGround = actor->getOnGround();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ActorFrameData::updatePosition()
|
void ActorFrameData::updatePosition(btCollisionWorld* world)
|
||||||
{
|
{
|
||||||
mActorRaw->updateWorldPosition();
|
mActorRaw->updateWorldPosition();
|
||||||
// If physics runs "fast enough", position are interpolated without simulation
|
// If physics runs "fast enough", position are interpolated without simulation
|
||||||
|
@ -990,10 +986,10 @@ namespace MWPhysics
|
||||||
// regardless of simulation speed.
|
// regardless of simulation speed.
|
||||||
mActorRaw->applyOffsetChange();
|
mActorRaw->applyOffsetChange();
|
||||||
mPosition = mActorRaw->getPosition();
|
mPosition = mActorRaw->getPosition();
|
||||||
if (mMoveToWaterSurface)
|
if (mWaterCollision && mPosition.z() < mWaterlevel && canMoveToWaterSurface(mActorRaw, mWaterlevel, world))
|
||||||
{
|
{
|
||||||
mPosition.z() = mWaterlevel;
|
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();
|
mOldHeight = mPosition.z();
|
||||||
mRefpos = mActorRaw->getPtr().getRefData().getPosition();
|
mRefpos = mActorRaw->getPtr().getRefData().getPosition();
|
||||||
|
|
|
@ -79,7 +79,7 @@ namespace MWPhysics
|
||||||
struct ActorFrameData
|
struct ActorFrameData
|
||||||
{
|
{
|
||||||
ActorFrameData(const std::shared_ptr<Actor>& actor, const MWWorld::Ptr standingOn, bool moveToWaterSurface, osg::Vec3f movement, float slowFall, float waterlevel);
|
ActorFrameData(const std::shared_ptr<Actor>& actor, const MWWorld::Ptr standingOn, bool moveToWaterSurface, osg::Vec3f movement, float slowFall, float waterlevel);
|
||||||
void updatePosition();
|
void updatePosition(btCollisionWorld* world);
|
||||||
std::weak_ptr<Actor> mActor;
|
std::weak_ptr<Actor> mActor;
|
||||||
Actor* mActorRaw;
|
Actor* mActorRaw;
|
||||||
MWWorld::Ptr mStandingOn;
|
MWWorld::Ptr mStandingOn;
|
||||||
|
@ -90,7 +90,7 @@ namespace MWPhysics
|
||||||
bool mDidJump;
|
bool mDidJump;
|
||||||
bool mFloatToSurface;
|
bool mFloatToSurface;
|
||||||
bool mNeedLand;
|
bool mNeedLand;
|
||||||
bool mMoveToWaterSurface;
|
bool mWaterCollision;
|
||||||
float mWaterlevel;
|
float mWaterlevel;
|
||||||
float mSlowFall;
|
float mSlowFall;
|
||||||
float mOldHeight;
|
float mOldHeight;
|
||||||
|
@ -262,14 +262,14 @@ namespace MWPhysics
|
||||||
|
|
||||||
void updateWater();
|
void updateWater();
|
||||||
|
|
||||||
std::vector<ActorFrameData> prepareFrameData(int numSteps);
|
std::vector<ActorFrameData> prepareFrameData(bool willSimulate);
|
||||||
|
|
||||||
osg::ref_ptr<SceneUtil::UnrefQueue> mUnrefQueue;
|
osg::ref_ptr<SceneUtil::UnrefQueue> mUnrefQueue;
|
||||||
|
|
||||||
std::unique_ptr<btBroadphaseInterface> mBroadphase;
|
std::unique_ptr<btBroadphaseInterface> mBroadphase;
|
||||||
std::unique_ptr<btDefaultCollisionConfiguration> mCollisionConfiguration;
|
std::unique_ptr<btDefaultCollisionConfiguration> mCollisionConfiguration;
|
||||||
std::unique_ptr<btCollisionDispatcher> mDispatcher;
|
std::unique_ptr<btCollisionDispatcher> mDispatcher;
|
||||||
std::shared_ptr<btCollisionWorld> mCollisionWorld;
|
std::unique_ptr<btCollisionWorld> mCollisionWorld;
|
||||||
std::unique_ptr<PhysicsTaskScheduler> mTaskScheduler;
|
std::unique_ptr<PhysicsTaskScheduler> mTaskScheduler;
|
||||||
|
|
||||||
std::unique_ptr<Resource::BulletShapeManager> mShapeManager;
|
std::unique_ptr<Resource::BulletShapeManager> mShapeManager;
|
||||||
|
|
|
@ -102,7 +102,7 @@ public:
|
||||||
BlendMask_All = BlendMask_LowerBody | BlendMask_UpperBody
|
BlendMask_All = BlendMask_LowerBody | BlendMask_UpperBody
|
||||||
};
|
};
|
||||||
/* This is the number of *discrete* blend masks. */
|
/* 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.
|
/// Holds an animation priority value for each BoneGroup.
|
||||||
struct AnimPriority
|
struct AnimPriority
|
||||||
|
|
|
@ -90,7 +90,7 @@ LocalMap::LocalMap(osg::Group* root)
|
||||||
{
|
{
|
||||||
// Increase map resolution, if use UI scaling
|
// Increase map resolution, if use UI scaling
|
||||||
float uiScale = Settings::Manager::getFloat("scaling factor", "GUI");
|
float uiScale = Settings::Manager::getFloat("scaling factor", "GUI");
|
||||||
if (uiScale > 1.0)
|
if (uiScale > 0.f)
|
||||||
mMapResolution *= uiScale;
|
mMapResolution *= uiScale;
|
||||||
|
|
||||||
SceneUtil::FindByNameVisitor find("Scene Root");
|
SceneUtil::FindByNameVisitor find("Scene Root");
|
||||||
|
|
|
@ -63,7 +63,7 @@ namespace MWRender
|
||||||
{
|
{
|
||||||
std::set<ESM::RefNum> mDisabled;
|
std::set<ESM::RefNum> mDisabled;
|
||||||
std::set<ESM::RefNum> mBlacklist;
|
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 mRefTracker;
|
||||||
RefTracker mRefTrackerNew;
|
RefTracker mRefTrackerNew;
|
||||||
|
|
|
@ -815,6 +815,7 @@ namespace MWRender
|
||||||
RenderingManager::RayResult result;
|
RenderingManager::RayResult result;
|
||||||
result.mHit = false;
|
result.mHit = false;
|
||||||
result.mHitRefnum.mContentFile = -1;
|
result.mHitRefnum.mContentFile = -1;
|
||||||
|
result.mHitRefnum.mIndex = -1;
|
||||||
result.mRatio = 0;
|
result.mRatio = 0;
|
||||||
if (intersector->containsIntersections())
|
if (intersector->containsIntersections())
|
||||||
{
|
{
|
||||||
|
|
|
@ -45,6 +45,9 @@
|
||||||
#include <components/sceneutil/visitor.hpp>
|
#include <components/sceneutil/visitor.hpp>
|
||||||
#include <components/sceneutil/shadow.hpp>
|
#include <components/sceneutil/shadow.hpp>
|
||||||
|
|
||||||
|
#include <components/settings/settings.hpp>
|
||||||
|
#include <components/misc/stringops.hpp>
|
||||||
|
|
||||||
#include <components/nifosg/particle.hpp>
|
#include <components/nifosg/particle.hpp>
|
||||||
|
|
||||||
#include "../mwbase/environment.hpp"
|
#include "../mwbase/environment.hpp"
|
||||||
|
@ -1164,7 +1167,7 @@ void SkyManager::create()
|
||||||
{
|
{
|
||||||
assert(!mCreated);
|
assert(!mCreated);
|
||||||
|
|
||||||
mAtmosphereDay = mSceneManager->getInstance("meshes/sky_atmosphere.nif", mEarlyRenderBinRoot);
|
mAtmosphereDay = mSceneManager->getInstance(Settings::Manager::getString("skyatmosphere", "Models"), mEarlyRenderBinRoot);
|
||||||
ModVertexAlphaVisitor modAtmosphere(0);
|
ModVertexAlphaVisitor modAtmosphere(0);
|
||||||
mAtmosphereDay->accept(modAtmosphere);
|
mAtmosphereDay->accept(modAtmosphere);
|
||||||
|
|
||||||
|
@ -1176,10 +1179,10 @@ void SkyManager::create()
|
||||||
mEarlyRenderBinRoot->addChild(mAtmosphereNightNode);
|
mEarlyRenderBinRoot->addChild(mAtmosphereNightNode);
|
||||||
|
|
||||||
osg::ref_ptr<osg::Node> atmosphereNight;
|
osg::ref_ptr<osg::Node> atmosphereNight;
|
||||||
if (mSceneManager->getVFS()->exists("meshes/sky_night_02.nif"))
|
if (mSceneManager->getVFS()->exists(Settings::Manager::getString("skynight02", "Models")))
|
||||||
atmosphereNight = mSceneManager->getInstance("meshes/sky_night_02.nif", mAtmosphereNightNode);
|
atmosphereNight = mSceneManager->getInstance(Settings::Manager::getString("skynight02", "Models"), mAtmosphereNightNode);
|
||||||
else
|
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);
|
atmosphereNight->getOrCreateStateSet()->setAttributeAndModes(createAlphaTrackingUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE);
|
||||||
ModVertexAlphaVisitor modStars(2);
|
ModVertexAlphaVisitor modStars(2);
|
||||||
atmosphereNight->accept(modStars);
|
atmosphereNight->accept(modStars);
|
||||||
|
@ -1193,14 +1196,14 @@ void SkyManager::create()
|
||||||
|
|
||||||
mCloudNode = new osg::PositionAttitudeTransform;
|
mCloudNode = new osg::PositionAttitudeTransform;
|
||||||
mEarlyRenderBinRoot->addChild(mCloudNode);
|
mEarlyRenderBinRoot->addChild(mCloudNode);
|
||||||
mCloudMesh = mSceneManager->getInstance("meshes/sky_clouds_01.nif", mCloudNode);
|
mCloudMesh = mSceneManager->getInstance(Settings::Manager::getString("skyclouds", "Models"), mCloudNode);
|
||||||
ModVertexAlphaVisitor modClouds(1);
|
ModVertexAlphaVisitor modClouds(1);
|
||||||
mCloudMesh->accept(modClouds);
|
mCloudMesh->accept(modClouds);
|
||||||
mCloudUpdater = new CloudUpdater;
|
mCloudUpdater = new CloudUpdater;
|
||||||
mCloudUpdater->setOpacity(1.f);
|
mCloudUpdater->setOpacity(1.f);
|
||||||
mCloudMesh->addUpdateCallback(mCloudUpdater);
|
mCloudMesh->addUpdateCallback(mCloudUpdater);
|
||||||
|
|
||||||
mCloudMesh2 = mSceneManager->getInstance("meshes/sky_clouds_01.nif", mCloudNode);
|
mCloudMesh2 = mSceneManager->getInstance(Settings::Manager::getString("skyclouds", "Models"), mCloudNode);
|
||||||
mCloudMesh2->accept(modClouds);
|
mCloudMesh2->accept(modClouds);
|
||||||
mCloudUpdater2 = new CloudUpdater;
|
mCloudUpdater2 = new CloudUpdater;
|
||||||
mCloudUpdater2->setOpacity(0.f);
|
mCloudUpdater2->setOpacity(0.f);
|
||||||
|
@ -1597,7 +1600,7 @@ void SkyManager::update(float duration)
|
||||||
if (mParticleNode)
|
if (mParticleNode)
|
||||||
{
|
{
|
||||||
// Morrowind deliberately rotates the blizzard mesh, so so should we.
|
// 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);
|
quat.makeRotate(osg::Vec3f(-1,0,0), mStormDirection);
|
||||||
mParticleNode->setAttitude(quat);
|
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)
|
void SkyManager::listAssetsToPreload(std::vector<std::string>& models, std::vector<std::string>& textures)
|
||||||
{
|
{
|
||||||
models.emplace_back("meshes/sky_atmosphere.nif");
|
models.emplace_back(Settings::Manager::getString("skyatmosphere", "Models"));
|
||||||
if (mSceneManager->getVFS()->exists("meshes/sky_night_02.nif"))
|
if (mSceneManager->getVFS()->exists(Settings::Manager::getString("skynight02", "Models")))
|
||||||
models.emplace_back("meshes/sky_night_02.nif");
|
models.emplace_back(Settings::Manager::getString("skynight02", "Models"));
|
||||||
models.emplace_back("meshes/sky_night_01.nif");
|
models.emplace_back(Settings::Manager::getString("skynight01", "Models"));
|
||||||
models.emplace_back("meshes/sky_clouds_01.nif");
|
models.emplace_back(Settings::Manager::getString("skyclouds", "Models"));
|
||||||
|
|
||||||
models.emplace_back("meshes\\ashcloud.nif");
|
models.emplace_back(Settings::Manager::getString("weatherashcloud", "Models"));
|
||||||
models.emplace_back("meshes\\blightcloud.nif");
|
models.emplace_back(Settings::Manager::getString("weatherblightcloud", "Models"));
|
||||||
models.emplace_back("meshes\\snow.nif");
|
models.emplace_back(Settings::Manager::getString("weathersnow", "Models"));
|
||||||
models.emplace_back("meshes\\blizzard.nif");
|
models.emplace_back(Settings::Manager::getString("weatherblizzard", "Models"));
|
||||||
|
|
||||||
textures.emplace_back("textures/tx_mooncircle_full_s.dds");
|
textures.emplace_back("textures/tx_mooncircle_full_s.dds");
|
||||||
textures.emplace_back("textures/tx_mooncircle_full_m.dds");
|
textures.emplace_back("textures/tx_mooncircle_full_m.dds");
|
||||||
|
|
|
@ -109,7 +109,7 @@ namespace MWScript
|
||||||
runtime.pop();
|
runtime.pop();
|
||||||
|
|
||||||
if (count<0)
|
if (count<0)
|
||||||
throw std::runtime_error ("second argument for AddItem must be non-negative");
|
count = static_cast<uint16_t>(count);
|
||||||
|
|
||||||
// no-op
|
// no-op
|
||||||
if (count == 0)
|
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)
|
if (type==ESM::REC_GSCR)
|
||||||
{
|
{
|
||||||
ESM::GlobalScript script;
|
ESM::GlobalScript script;
|
||||||
script.load (reader);
|
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);
|
auto iter = mScripts.find (script.mId);
|
||||||
|
|
||||||
if (iter==mScripts.end())
|
if (iter==mScripts.end())
|
||||||
|
|
|
@ -73,7 +73,7 @@ namespace MWScript
|
||||||
|
|
||||||
void write (ESM::ESMWriter& writer, Loading::Listener& progress) const;
|
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.
|
///< Records for variables that do not exist are dropped silently.
|
||||||
///
|
///
|
||||||
/// \return Known type?
|
/// \return Known type?
|
||||||
|
|
|
@ -10,16 +10,6 @@
|
||||||
|
|
||||||
#include "../mwworld/ptr.hpp"
|
#include "../mwworld/ptr.hpp"
|
||||||
|
|
||||||
namespace MWSound
|
|
||||||
{
|
|
||||||
class SoundManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace MWInput
|
|
||||||
{
|
|
||||||
struct MWInputManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace MWScript
|
namespace MWScript
|
||||||
{
|
{
|
||||||
class Locals;
|
class Locals;
|
||||||
|
|
|
@ -14,7 +14,7 @@ namespace MWScript
|
||||||
{
|
{
|
||||||
struct ExplicitRef
|
struct ExplicitRef
|
||||||
{
|
{
|
||||||
static const bool implicit = false;
|
static constexpr bool implicit = false;
|
||||||
|
|
||||||
MWWorld::Ptr operator() (Interpreter::Runtime& runtime, bool required = true,
|
MWWorld::Ptr operator() (Interpreter::Runtime& runtime, bool required = true,
|
||||||
bool activeOnly = false) const;
|
bool activeOnly = false) const;
|
||||||
|
@ -22,7 +22,7 @@ namespace MWScript
|
||||||
|
|
||||||
struct ImplicitRef
|
struct ImplicitRef
|
||||||
{
|
{
|
||||||
static const bool implicit = true;
|
static constexpr bool implicit = true;
|
||||||
|
|
||||||
MWWorld::Ptr operator() (Interpreter::Runtime& runtime, bool required = true,
|
MWWorld::Ptr operator() (Interpreter::Runtime& runtime, bool required = true,
|
||||||
bool activeOnly = false) const;
|
bool activeOnly = false) const;
|
||||||
|
|
|
@ -49,7 +49,7 @@ namespace MWScript
|
||||||
std::vector<MWWorld::Ptr> actors;
|
std::vector<MWWorld::Ptr> actors;
|
||||||
MWBase::Environment::get().getWorld()->getActorsStandingOn (ptr, actors);
|
MWBase::Environment::get().getWorld()->getActorsStandingOn (ptr, actors);
|
||||||
for (auto& actor : actors)
|
for (auto& actor : actors)
|
||||||
MWBase::Environment::get().getWorld()->moveObjectBy(actor, diff);
|
MWBase::Environment::get().getWorld()->moveObjectBy(actor, diff, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class R>
|
template<class R>
|
||||||
|
@ -350,11 +350,11 @@ namespace MWScript
|
||||||
float terrainHeight = -std::numeric_limits<float>::max();
|
float terrainHeight = -std::numeric_limits<float>::max();
|
||||||
if (ptr.getCell()->isExterior())
|
if (ptr.getCell()->isExterior())
|
||||||
terrainHeight = MWBase::Environment::get().getWorld()->getTerrainHeightAt(curPos);
|
terrainHeight = MWBase::Environment::get().getWorld()->getTerrainHeightAt(curPos);
|
||||||
|
|
||||||
if (pos < terrainHeight)
|
if (pos < terrainHeight)
|
||||||
pos = terrainHeight;
|
pos = terrainHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
newPos[2] = pos;
|
newPos[2] = pos;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -363,7 +363,7 @@ namespace MWScript
|
||||||
}
|
}
|
||||||
|
|
||||||
dynamic_cast<MWScript::InterpreterContext&>(runtime.getContext()).updatePtr(ptr,
|
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
|
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);
|
dynamic_cast<MWScript::InterpreterContext&>(runtime.getContext()).updatePtr(base,ptr);
|
||||||
|
|
||||||
|
@ -902,7 +902,7 @@ namespace MWScript
|
||||||
// This approach can be used to create elevators.
|
// This approach can be used to create elevators.
|
||||||
moveStandingActors(ptr, diff);
|
moveStandingActors(ptr, diff);
|
||||||
dynamic_cast<MWScript::InterpreterContext&>(runtime.getContext()).updatePtr(ptr,
|
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.
|
// This approach can be used to create elevators.
|
||||||
moveStandingActors(ptr, diff);
|
moveStandingActors(ptr, diff);
|
||||||
dynamic_cast<MWScript::InterpreterContext&>(runtime.getContext()).updatePtr(ptr,
|
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& operator=(const FFmpeg_Decoder &rhs);
|
||||||
FFmpeg_Decoder(const FFmpeg_Decoder &rhs);
|
FFmpeg_Decoder(const FFmpeg_Decoder &rhs);
|
||||||
|
|
||||||
FFmpeg_Decoder(const VFS::Manager* vfs);
|
|
||||||
public:
|
public:
|
||||||
|
explicit FFmpeg_Decoder(const VFS::Manager* vfs);
|
||||||
|
|
||||||
virtual ~FFmpeg_Decoder();
|
virtual ~FFmpeg_Decoder();
|
||||||
|
|
||||||
friend class SoundManager;
|
friend class SoundManager;
|
||||||
};
|
};
|
||||||
#ifndef DEFAULT_DECODER
|
|
||||||
#define DEFAULT_DECODER (::MWSound::FFmpeg_Decoder)
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -98,9 +98,6 @@ namespace MWSound
|
||||||
OpenAL_Output(SoundManager &mgr);
|
OpenAL_Output(SoundManager &mgr);
|
||||||
virtual ~OpenAL_Output();
|
virtual ~OpenAL_Output();
|
||||||
};
|
};
|
||||||
#ifndef DEFAULT_OUTPUT
|
|
||||||
#define DEFAULT_OUTPUT(x) ::MWSound::OpenAL_Output((x))
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -54,7 +54,7 @@ namespace MWSound
|
||||||
|
|
||||||
SoundManager::SoundManager(const VFS::Manager* vfs, bool useSound)
|
SoundManager::SoundManager(const VFS::Manager* vfs, bool useSound)
|
||||||
: mVFS(vfs)
|
: mVFS(vfs)
|
||||||
, mOutput(new DEFAULT_OUTPUT(*this))
|
, mOutput(new OpenAL_Output(*this))
|
||||||
, mWaterSoundUpdater(makeWaterSoundUpdaterSettings())
|
, mWaterSoundUpdater(makeWaterSoundUpdaterSettings())
|
||||||
, mSoundBuffers(*vfs, *mOutput)
|
, mSoundBuffers(*vfs, *mOutput)
|
||||||
, mListenerUnderwater(false)
|
, mListenerUnderwater(false)
|
||||||
|
@ -109,7 +109,7 @@ namespace MWSound
|
||||||
|
|
||||||
SoundManager::~SoundManager()
|
SoundManager::~SoundManager()
|
||||||
{
|
{
|
||||||
clear();
|
SoundManager::clear();
|
||||||
mSoundBuffers.clear();
|
mSoundBuffers.clear();
|
||||||
mOutput.reset();
|
mOutput.reset();
|
||||||
}
|
}
|
||||||
|
@ -117,7 +117,7 @@ namespace MWSound
|
||||||
// Return a new decoder instance, used as needed by the output implementations
|
// Return a new decoder instance, used as needed by the output implementations
|
||||||
DecoderPtr SoundManager::getDecoder()
|
DecoderPtr SoundManager::getDecoder()
|
||||||
{
|
{
|
||||||
return DecoderPtr(new DEFAULT_DECODER (mVFS));
|
return std::make_shared<FFmpeg_Decoder>(mVFS);
|
||||||
}
|
}
|
||||||
|
|
||||||
DecoderPtr SoundManager::loadVoice(const std::string &voicefile)
|
DecoderPtr SoundManager::loadVoice(const std::string &voicefile)
|
||||||
|
|
|
@ -141,7 +141,7 @@ namespace MWSound
|
||||||
|
|
||||||
public:
|
public:
|
||||||
SoundManager(const VFS::Manager* vfs, bool useSound);
|
SoundManager(const VFS::Manager* vfs, bool useSound);
|
||||||
virtual ~SoundManager();
|
~SoundManager() override;
|
||||||
|
|
||||||
void processChangedSettings(const Settings::CategorySettingVector& settings) 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)
|
if(mOldestSlotVisited == nullptr)
|
||||||
return true;
|
return true;
|
||||||
return (compare->mTimeStamp <= mOldestSlotVisited->mTimeStamp);
|
return (compare->mTimeStamp <= mOldestSlotVisited->mTimeStamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MWState::QuickSaveManager::shouldCreateNewSlot()
|
bool MWState::QuickSaveManager::shouldCreateNewSlot() const
|
||||||
{
|
{
|
||||||
return (mSlotsVisited < mMaxSaves);
|
return (mSlotsVisited < mMaxSaves);
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,8 +13,8 @@ namespace MWState{
|
||||||
unsigned int mSlotsVisited;
|
unsigned int mSlotsVisited;
|
||||||
const Slot *mOldestSlotVisited;
|
const Slot *mOldestSlotVisited;
|
||||||
private:
|
private:
|
||||||
bool shouldCreateNewSlot();
|
bool shouldCreateNewSlot() const;
|
||||||
bool isOldestSave(const Slot *compare);
|
bool isOldestSave(const Slot *compare) const;
|
||||||
public:
|
public:
|
||||||
QuickSaveManager(std::string &saveName, unsigned int maxSaves);
|
QuickSaveManager(std::string &saveName, unsigned int maxSaves);
|
||||||
///< A utility class to manage multiple quicksave slots
|
///< 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:
|
case ESM::REC_GSCR:
|
||||||
|
|
||||||
MWBase::Environment::get().getScriptManager()->getGlobalScripts().readRecord (reader, n.intval);
|
MWBase::Environment::get().getScriptManager()->getGlobalScripts().readRecord (reader, n.intval, contentFileMap);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ESM::REC_GMAP:
|
case ESM::REC_GMAP:
|
||||||
|
@ -515,6 +515,7 @@ void MWState::StateManager::loadGame (const Character *character, const std::str
|
||||||
character->getPath().filename().string());
|
character->getPath().filename().string());
|
||||||
|
|
||||||
MWBase::Environment::get().getWindowManager()->setNewGame(false);
|
MWBase::Environment::get().getWindowManager()->setNewGame(false);
|
||||||
|
MWBase::Environment::get().getWorld()->saveLoaded();
|
||||||
MWBase::Environment::get().getWorld()->setupPlayer();
|
MWBase::Environment::get().getWorld()->setupPlayer();
|
||||||
MWBase::Environment::get().getWorld()->renderPlayer();
|
MWBase::Environment::get().getWorld()->renderPlayer();
|
||||||
MWBase::Environment::get().getWindowManager()->updatePlayer();
|
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)
|
MWWorld::Cells::Cells (const MWWorld::ESMStore& store, std::vector<ESM::ESMReader>& reader)
|
||||||
: mStore (store), mReader (reader),
|
: mStore (store), mReader (reader),
|
||||||
mIdCache (Settings::Manager::getInt("pointers cache size", "Cells"), std::pair<std::string, CellStore *> ("", (CellStore*)nullptr)),
|
|
||||||
mIdCacheIndex (0)
|
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)
|
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)
|
MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name)
|
||||||
{
|
{
|
||||||
// First check the cache
|
// First check the cache
|
||||||
for (std::vector<std::pair<std::string, CellStore *> >::iterator iter (mIdCache.begin());
|
for (IdCache::iterator iter (mIdCache.begin()); iter!=mIdCache.end(); ++iter)
|
||||||
iter!=mIdCache.end(); ++iter)
|
|
||||||
if (iter->first==name && iter->second)
|
if (iter->first==name && iter->second)
|
||||||
{
|
{
|
||||||
Ptr ptr = getPtr (name, *iter->second);
|
Ptr ptr = getPtr (name, *iter->second);
|
||||||
|
|
|
@ -28,11 +28,12 @@ namespace MWWorld
|
||||||
/// \brief Cell container
|
/// \brief Cell container
|
||||||
class Cells
|
class Cells
|
||||||
{
|
{
|
||||||
|
typedef std::vector<std::pair<std::string, CellStore *> > IdCache;
|
||||||
const MWWorld::ESMStore& mStore;
|
const MWWorld::ESMStore& mStore;
|
||||||
std::vector<ESM::ESMReader>& mReader;
|
std::vector<ESM::ESMReader>& mReader;
|
||||||
mutable std::map<std::string, CellStore> mInteriors;
|
mutable std::map<std::string, CellStore> mInteriors;
|
||||||
mutable std::map<std::pair<int, int>, CellStore> mExteriors;
|
mutable std::map<std::pair<int, int>, CellStore> mExteriors;
|
||||||
std::vector<std::pair<std::string, CellStore *> > mIdCache;
|
IdCache mIdCache;
|
||||||
std::size_t mIdCacheIndex;
|
std::size_t mIdCacheIndex;
|
||||||
|
|
||||||
Cells (const Cells&);
|
Cells (const Cells&);
|
||||||
|
|
|
@ -90,10 +90,24 @@ MWWorld::ResolutionListener::~ResolutionListener()
|
||||||
{
|
{
|
||||||
if(!mStore.mModified && mStore.mResolved && !mStore.mPtr.isEmpty())
|
if(!mStore.mModified && mStore.mResolved && !mStore.mPtr.isEmpty())
|
||||||
{
|
{
|
||||||
for(const auto&& ptr : mStore)
|
try
|
||||||
ptr.getRefData().setCount(0);
|
{
|
||||||
|
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);
|
mStore.fillNonRandom(mStore.mPtr.get<ESM::Container>()->mBase->mInventory, "", mStore.mSeed);
|
||||||
addScripts(mStore, mStore.mPtr.mCell);
|
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;
|
mStore.mResolved = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,22 +73,22 @@ namespace MWWorld
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
||||||
static const int Type_Potion = 0x0001;
|
static constexpr int Type_Potion = 0x0001;
|
||||||
static const int Type_Apparatus = 0x0002;
|
static constexpr int Type_Apparatus = 0x0002;
|
||||||
static const int Type_Armor = 0x0004;
|
static constexpr int Type_Armor = 0x0004;
|
||||||
static const int Type_Book = 0x0008;
|
static constexpr int Type_Book = 0x0008;
|
||||||
static const int Type_Clothing = 0x0010;
|
static constexpr int Type_Clothing = 0x0010;
|
||||||
static const int Type_Ingredient = 0x0020;
|
static constexpr int Type_Ingredient = 0x0020;
|
||||||
static const int Type_Light = 0x0040;
|
static constexpr int Type_Light = 0x0040;
|
||||||
static const int Type_Lockpick = 0x0080;
|
static constexpr int Type_Lockpick = 0x0080;
|
||||||
static const int Type_Miscellaneous = 0x0100;
|
static constexpr int Type_Miscellaneous = 0x0100;
|
||||||
static const int Type_Probe = 0x0200;
|
static constexpr int Type_Probe = 0x0200;
|
||||||
static const int Type_Repair = 0x0400;
|
static constexpr int Type_Repair = 0x0400;
|
||||||
static const int Type_Weapon = 0x0800;
|
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;
|
static const std::string sGoldId;
|
||||||
|
|
||||||
|
@ -153,7 +153,7 @@ namespace MWWorld
|
||||||
|
|
||||||
virtual ~ContainerStore();
|
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 cbegin (int mask = Type_All) const;
|
||||||
ConstContainerStoreIterator cend() const;
|
ConstContainerStoreIterator cend() const;
|
||||||
|
@ -276,13 +276,13 @@ namespace MWWorld
|
||||||
template<class From, class To, class Dummy>
|
template<class From, class To, class Dummy>
|
||||||
struct IsConvertible
|
struct IsConvertible
|
||||||
{
|
{
|
||||||
static const bool value = true;
|
static constexpr bool value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<class Dummy>
|
template<class Dummy>
|
||||||
struct IsConvertible<ConstPtr, Ptr, Dummy>
|
struct IsConvertible<ConstPtr, Ptr, Dummy>
|
||||||
{
|
{
|
||||||
static const bool value = false;
|
static constexpr bool value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<class T, class U>
|
template<class T, class U>
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
#ifndef GAME_MWWORLD_CUSTOMDATA_H
|
#ifndef GAME_MWWORLD_CUSTOMDATA_H
|
||||||
#define GAME_MWWORLD_CUSTOMDATA_H
|
#define GAME_MWWORLD_CUSTOMDATA_H
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
namespace MWClass
|
namespace MWClass
|
||||||
{
|
{
|
||||||
class CreatureCustomData;
|
class CreatureCustomData;
|
||||||
|
@ -19,7 +21,7 @@ namespace MWWorld
|
||||||
|
|
||||||
virtual ~CustomData() {}
|
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.
|
// 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 MWClass::CreatureLevListCustomData& asCreatureLevListCustomData();
|
||||||
virtual const MWClass::CreatureLevListCustomData& asCreatureLevListCustomData() const;
|
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
|
#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
|
namespace MWWorld
|
||||||
|
@ -218,49 +269,7 @@ int ESMStore::getRefCount(const std::string& id) const
|
||||||
|
|
||||||
void ESMStore::validate()
|
void ESMStore::validate()
|
||||||
{
|
{
|
||||||
// Cache first class from store - we will use it if current class is not found
|
std::vector<ESM::NPC> npcsToReplace = getNPCsToReplace(mFactions, mClasses, mNpcs.mStatic);
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const ESM::NPC &npc : npcsToReplace)
|
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
|
int ESMStore::countSavedGameRecords() const
|
||||||
{
|
{
|
||||||
return 1 // DYNA (dynamic name counter)
|
return 1 // DYNA (dynamic name counter)
|
||||||
|
@ -384,12 +401,14 @@ void ESMStore::validate()
|
||||||
case ESM::REC_ENCH:
|
case ESM::REC_ENCH:
|
||||||
case ESM::REC_SPEL:
|
case ESM::REC_SPEL:
|
||||||
case ESM::REC_WEAP:
|
case ESM::REC_WEAP:
|
||||||
case ESM::REC_NPC_:
|
|
||||||
case ESM::REC_LEVI:
|
case ESM::REC_LEVI:
|
||||||
case ESM::REC_LEVC:
|
case ESM::REC_LEVC:
|
||||||
|
mStores[type]->read (reader);
|
||||||
|
return true;
|
||||||
|
case ESM::REC_NPC_:
|
||||||
case ESM::REC_CREA:
|
case ESM::REC_CREA:
|
||||||
case ESM::REC_CONT:
|
case ESM::REC_CONT:
|
||||||
mStores[type]->read (reader);
|
mStores[type]->read (reader, true);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case ESM::REC_DYNA:
|
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