1
0
Fork 1
mirror of https://github.com/TES3MP/openmw-tes3mp.git synced 2025-01-16 18:19:55 +00:00

Merge branch 'master' into HEAD

This commit is contained in:
Bret Curtis 2018-11-09 11:23:28 +01:00
commit 3fe7106668
365 changed files with 76943 additions and 1704 deletions

View file

@ -15,11 +15,11 @@ Debian:
- apt-get update -yq
- apt-get -o dir::cache::archives="$APT_CACHE_DIR" install -y cmake libboost-filesystem-dev libboost-program-options-dev libboost-system-dev libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev libsdl2-dev libqt4-dev libopenal-dev libopenscenegraph-3.4-dev libunshield-dev libtinyxml-dev
# - apt-get install -y libmygui-dev libbullet-dev # to be updated to latest below because stretch is too old
- curl http://ftp.us.debian.org/debian/pool/main/b/bullet/libbullet-dev_2.87+dfsg-2_amd64.deb -o libbullet-dev_2.87+dfsg-2_amd64.deb
- curl http://ftp.us.debian.org/debian/pool/main/b/bullet/libbullet2.87_2.87+dfsg-2_amd64.deb -o libbullet2.87_2.87+dfsg-2_amd64.deb
- curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmygui.openglplatform0debian1v5_3.2.2+dfsg-1_amd64.deb -o libmygui.openglplatform0debian1v5_3.2.2+dfsg-1_amd64.deb
- curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmyguiengine3debian1v5_3.2.2+dfsg-1_amd64.deb -o libmyguiengine3debian1v5_3.2.2+dfsg-1_amd64.deb
- curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmygui-dev_3.2.2+dfsg-1_amd64.deb -o libmygui-dev_3.2.2+dfsg-1_amd64.deb
- curl -L http://ftp.us.debian.org/debian/pool/main/b/bullet/libbullet-dev_2.87+dfsg-2_amd64.deb -o libbullet-dev_2.87+dfsg-2_amd64.deb
- curl -L http://ftp.us.debian.org/debian/pool/main/b/bullet/libbullet2.87_2.87+dfsg-2_amd64.deb -o libbullet2.87_2.87+dfsg-2_amd64.deb
- curl -L https://http.kali.org/pool/main/m/mygui/libmygui.openglplatform0debian1v5_3.2.2+dfsg-1_amd64.deb -o libmygui.openglplatform0debian1v5_3.2.2+dfsg-1_amd64.deb
- curl -L https://http.kali.org/pool/main/m/mygui/libmyguiengine3debian1v5_3.2.2+dfsg-1_amd64.deb -o libmyguiengine3debian1v5_3.2.2+dfsg-1_amd64.deb
- curl -L https://http.kali.org/pool/main/m/mygui/libmygui-dev_3.2.2+dfsg-1_amd64.deb -o libmygui-dev_3.2.2+dfsg-1_amd64.deb
- dpkg --ignore-depends=libmygui.ogreplatform0debian1v5 -i *.deb
stage: build
script:

View file

@ -1,10 +1,4 @@
os:
- linux
- osx
osx_image: xcode9.4
language: cpp
sudo: required
dist: trusty
branches:
only:
- master
@ -20,16 +14,16 @@ addons:
sources:
- sourceline: 'ppa:openmw/openmw'
- ubuntu-toolchain-r-test
- llvm-toolchain-precise-3.6
- llvm-toolchain-trusty-7
packages: [
# Dev
cmake, clang-3.6, libunshield-dev, libtinyxml-dev,
cmake, clang-7, clang-tools-7, gcc-8, g++-8,
# Boost
libboost-filesystem-dev, libboost-program-options-dev, libboost-system-dev,
# FFmpeg
libavcodec-dev, libavformat-dev, libavutil-dev, libswscale-dev,
# Audio & Video
libsdl2-dev, libqt4-dev, libopenal-dev,
# Audio, Video and Misc. deps
libsdl2-dev, libqt4-dev, libopenal-dev, libunshield-dev, libtinyxml-dev,
# The other ones from OpenMW ppa
libbullet-dev, libswresample-dev, libopenscenegraph-3.4-dev, libmygui-dev
]
@ -44,18 +38,44 @@ addons:
branch_pattern: coverity_scan
matrix:
include:
- os: linux
- name: OpenMW (all) on MacOS xcode9.4
os: osx
osx_image: xcode9.4
- name: OpenMW (all) on Ubuntu Trusty GCC-8
os: linux
dist: trusty
sudo: required
env:
ANALYZE="scan-build-3.6 --use-cc clang-3.6 --use-c++ clang++-3.6 "
- MATRIX_EVAL="CC=gcc-8 && CXX=g++-8"
- name: OpenMW (openmw) on Ubuntu Trusty Clang-7 with Static Analysis
os: linux
dist: trusty
sudo: required
env:
- MATRIX_EVAL="CC=clang-7 && CXX=clang++-7"
- ANALYZE="scan-build-7 --use-cc clang-7 --use-c++ clang++-7"
- BUILD_OPENMW_CS="OFF"
compiler: clang
allow_failures:
- env: ANALYZE="scan-build-3.6 --use-cc clang-3.6 --use-c++ clang++-3.6 "
- name: OpenMW (openmw-cs) on Ubuntu Trusty Clang-7 with Static Analysis
os: linux
dist: trusty
sudo: required
env:
- MATRIX_EVAL="CC=clang-7 && CXX=clang++-7"
- ANALYZE="scan-build-7 --use-cc clang-7 --use-c++ clang++-7"
- BUILD_OPENMW="OFF"
compiler: clang
# allow_failures:
# - name: OpenMW (openmw) on Ubuntu Trusty Clang-7 with Static Analysis
before_install: ./CI/before_install.${TRAVIS_OS_NAME}.sh
before_script: ./CI/before_script.${TRAVIS_OS_NAME}.sh
before_install:
- if [ "${TRAVIS_OS_NAME}" = "linux" ]; then eval "${MATRIX_EVAL}"; fi
- ./CI/before_install.${TRAVIS_OS_NAME}.sh
before_script:
- ./CI/before_script.${TRAVIS_OS_NAME}.sh
script:
- cd ./build
- if [ "$COVERITY_SCAN_BRANCH" != 1 ]; then ${ANALYZE}make -j3; fi
- if [ "$COVERITY_SCAN_BRANCH" != 1 ]; then ${ANALYZE} make -j3; fi
- if [ "$COVERITY_SCAN_BRANCH" != 1 ] && [ "${TRAVIS_OS_NAME}" = "osx" ]; then make package; fi
- if [ "$COVERITY_SCAN_BRANCH" != 1 ] && [ "${TRAVIS_OS_NAME}" = "linux" ]; then ./openmw_test_suite; fi
- if [ "$COVERITY_SCAN_BRANCH" != 1 ] && [ "${TRAVIS_OS_NAME}" = "linux" ]; then cd .. && ./CI/check_tabs.sh; fi

View file

@ -1,3 +1,14 @@
0.46.0
------
Bug #3623: Fix HiDPI on Windows
Bug #4540: Rain delay when exiting water
Bug #4701: PrisonMarker record is not hardcoded like other markers
Feature #2229: Improve pathfinding AI
Feature #3442: Default values for fallbacks from ini file
Feature #4673: Weapon sheathing
Task #4686: Upgrade media decoder to a more current FFmpeg API
0.45.0
------
@ -8,6 +19,7 @@
Bug #2256: Landing sound not playing when jumping immediately after landing
Bug #2274: Thin platform clips through player character instead of lifting
Bug #2326: After a bound item expires the last equipped item of that type is not automatically re-equipped
Bug #2446: Restore Attribute/Skill should allow restoring drained attributes
Bug #2455: Creatures attacks degrade armor
Bug #2562: Forcing AI to activate a teleport door sometimes causes a crash
Bug #2626: Resurrecting the player does not resume the game
@ -33,6 +45,7 @@
Bug #3788: GetPCInJail and GetPCTraveling do not work as in vanilla
Bug #3836: Script fails to compile when command argument contains "\n"
Bug #3876: Landscape texture painting is misaligned
Bug #3890: Magic light source attenuation is inaccurate
Bug #3897: Have Goodbye give all choices the effects of Goodbye
Bug #3911: [macOS] Typing in the "Content List name" dialog box produces double characters
Bug #3920: RemoveSpellEffects doesn't remove constant effects
@ -45,6 +58,7 @@
Bug #4110: Fixed undo / redo menu text losing the assigned shortcuts
Bug #4125: OpenMW logo cropped on bugtracker
Bug #4215: OpenMW shows book text after last EOL tag
Bug #4217: Fixme implementation differs from Morrowind's
Bug #4221: Characters get stuck in V-shaped terrain
Bug #4230: AiTravel package issues break some Tribunal quests
Bug #4231: Infected rats from the "Crimson Plague" quest rendered unconscious by change in Drain Fatigue functionality
@ -54,6 +68,7 @@
Bug #4274: Pre-0.43 death animations are not forward-compatible with 0.43+
Bug #4286: Scripted animations can be interrupted
Bug #4291: Non-persistent actors that started the game as dead do not play death animations
Bug #4292: CenterOnCell implementation differs from vanilla
Bug #4293: Faction members are not aware of faction ownerships in barter
Bug #4304: "Follow" not working as a second AI package
Bug #4307: World cleanup should remove dead bodies only if death animation is finished
@ -64,7 +79,7 @@
Bug #4368: Settings window ok button doesn't have key focus by default
Bug #4378: On-self absorb spells restore stats
Bug #4393: NPCs walk back to where they were after using ResetActors
Bug #4416: Handle exception if we try to play non-music file
Bug #4416: Non-music files crash the game when they are tried to be played
Bug #4419: MRK NiStringExtraData is handled incorrectly
Bug #4426: RotateWorld behavior is incorrect
Bug #4429: [Windows] Error on build INSTALL.vcxproj project (debug) with cmake 3.7.2
@ -80,6 +95,7 @@
Bug #4459: NotCell dialogue condition doesn't support partial matches
Bug #4460: Script function "Equip" doesn't bypass beast restrictions
Bug #4461: "Open" spell from non-player caster isn't a crime
Bug #4463: %g format doesn't return more digits
Bug #4464: OpenMW keeps AiState cached storages even after we cancel AI packages
Bug #4467: Content selector: cyrillic characters are decoded incorrectly in plugin descriptions
Bug #4469: Abot Silt Striders Model turn 90 degrees on horizontal
@ -143,12 +159,17 @@
Bug #4674: Journal can be opened when settings window is open
Bug #4677: Crash in ESM reader when NPC record has DNAM record without DODT one
Bug #4678: Crash in ESP parser when SCVR has no variable names
Bug #4684: Spell Absorption is additive
Bug #4685: Missing sound causes an exception inside Say command
Bug #4689: Default creature soundgen entries are not used
Bug #4691: Loading bar for cell should be moved up when text is still active at bottom of screen
Feature #912: Editor: Add missing icons to UniversalId tables
Feature #1221: Editor: Creature/NPC rendering
Feature #1617: Editor: Enchantment effect record verifier
Feature #1645: Casting effects from objects
Feature #2606: Editor: Implemented (optional) case sensitive global search
Feature #2787: Use the autogenerated collision box, if the creature mesh has no predefined one
Feature #2845: Editor: add record view and preview default keybindings
Feature #2847: Content selector: allow to copy the path to a file by using the context menu
Feature #3083: Play animation when NPC is casting spell via script
Feature #3103: Provide option for disposition to get increased by successful trade
@ -178,7 +199,9 @@
Feature #4632: AI priority: utilize vanilla AI GMSTs for priority rating
Feature #4636: Use sTo GMST in spellmaking menu
Feature #4642: Batching potion creation
Feature #4647: Cull actors outside of AI processing range
Feature #4682: Use the collision box from basic creature mesh if the X one have no collisions
Feature #4697: Use the real thrown weapon damage in tooltips and AI
Task #2490: Don't open command prompt window on Release-mode builds automatically
Task #4545: Enable is_pod string test
Task #4605: Optimize skinning

View file

@ -1,3 +1,4 @@
#!/bin/sh
sudo ln -s /usr/bin/clang-3.6 /usr/local/bin/clang
sudo ln -s /usr/bin/clang++-3.6 /usr/local/bin/clang++
#!/bin/bash -ex
sudo ln -sf /usr/bin/clang-7 /usr/local/bin/clang
sudo ln -sf /usr/bin/clang++-7 /usr/local/bin/clang++

View file

@ -1,4 +1,4 @@
#!/bin/sh
#!/bin/sh -e
brew update
@ -6,5 +6,5 @@ brew outdated cmake || brew upgrade cmake
brew outdated pkgconfig || brew upgrade pkgconfig
brew install qt
curl -fSL -R -J https://downloads.openmw.org/osx/dependencies/openmw-deps-100d2e0.zip -o ~/openmw-deps.zip
curl -fSL -R -J https://downloads.openmw.org/osx/dependencies/openmw-deps-7cf2789.zip -o ~/openmw-deps.zip
unzip -o ~/openmw-deps.zip -d /private/tmp/openmw-deps > /dev/null

View file

@ -1,4 +1,4 @@
#!/bin/sh -e
#!/bin/bash -ex
free -m
@ -8,8 +8,22 @@ GOOGLETEST_DIR="$(pwd)/googletest/build"
mkdir build
cd build
export CODE_COVERAGE=1
if [ "${CC}" = "clang" ]; then export CODE_COVERAGE=0; fi
${ANALYZE}cmake \
if [[ "${CC}" =~ "clang" ]]; then export CODE_COVERAGE=0; fi
if [[ -z "${BUILD_OPENMW}" ]]; then export BUILD_OPENMW=ON; fi
if [[ -z "${BUILD_OPENMW_CS}" ]]; then export BUILD_OPENMW_CS=ON; fi
${ANALYZE} cmake \
-DBUILD_OPENMW=${BUILD_OPENMW} \
-DBUILD_OPENCS=${BUILD_OPENMW_CS} \
-DBUILD_LAUNCHER=${BUILD_OPENMW_CS} \
-DBUILD_BSATOOL=${BUILD_OPENMW_CS} \
-DBUILD_ESMTOOL=${BUILD_OPENMW_CS} \
-DBUILD_MWINIIMPORTER=${BUILD_OPENMW_CS} \
-DBUILD_ESSIMPORTER=${BUILD_OPENMW_CS} \
-DBUILD_WIZARD=${BUILD_OPENMW_CS} \
-DBUILD_NIFTEST=${BUILD_OPENMW_CS} \
-DBUILD_MYGUI_PLUGIN=${BUILD_OPENMW_CS} \
-DBUILD_WITH_CODE_COVERAGE=${CODE_COVERAGE} \
-DBUILD_UNITTESTS=1 \
-DCMAKE_INSTALL_PREFIX=/usr \

View file

@ -194,7 +194,11 @@ download() {
}
real_pwd() {
pwd | sed "s,/\(.\),\1:,"
if type cygpath >/dev/null 2>&1; then
cygpath -am "$PWD"
else
pwd # not git bash, Cygwin or the like
fi
}
CMAKE_OPTS=""
@ -304,6 +308,10 @@ if ! [ -z $UNITY_BUILD ]; then
add_cmake_opts "-DOPENMW_UNITY_BUILD=True"
fi
if [ ${BITS} -eq 64 ]; then
GENERATOR="${GENERATOR} Win64"
fi
echo
echo "==================================="
echo "Starting prebuild on MSVC${MSVC_DISPLAY_YEAR} WIN${BITS}"
@ -324,7 +332,7 @@ if [ -z $SKIP_DOWNLOAD ]; then
if [ -z $APPVEYOR ]; then
download "Boost 1.67.0" \
"https://sourceforge.net/projects/boost/files/boost-binaries/1.67.0/boost_1_67_0-msvc-${MSVC_VER}-${BITS}.exe" \
"boost-1.67.0-msvc${MSVC_YEAR}-win${BITS}.exe"
"boost-1.67.0-msvc${MSVC_VER}-win${BITS}.exe"
fi
# Bullet
@ -430,7 +438,7 @@ fi
rm -rf Boost
CI_EXTRA_INNO_OPTIONS=""
[ -n "$CI" ] && CI_EXTRA_INNO_OPTIONS="//SUPPRESSMSGBOXES //LOG='boost_install.log'"
"${DEPS}/boost-1.67.0-msvc${MSVC_YEAR}-win${BITS}.exe" //DIR="${CWD_DRIVE_ROOT}" //VERYSILENT //NORESTART ${CI_EXTRA_INNO_OPTIONS}
"${DEPS}/boost-1.67.0-msvc${MSVC_VER}-win${BITS}.exe" //DIR="${CWD_DRIVE_ROOT}" //VERYSILENT //NORESTART ${CI_EXTRA_INNO_OPTIONS}
mv "${CWD_DRIVE_ROOT_BASH}" "${BOOST_SDK}"
fi
add_cmake_opts -DBOOST_ROOT="$BOOST_SDK" \
@ -449,7 +457,7 @@ fi
else
LIB_SUFFIX="0"
fi
add_cmake_opts -DBOOST_ROOT="$BOOST_SDK" \
-DBOOST_LIBRARYDIR="${BOOST_SDK}/lib${BITS}-msvc-${MSVC_VER}.${LIB_SUFFIX}"
add_cmake_opts -DBoost_COMPILER="-${TOOLSET}"

View file

@ -1,4 +1,4 @@
#!/bin/sh
#!/bin/sh -e
export CXX=clang++
export CC=clang

View file

@ -153,7 +153,52 @@ if (USE_QT)
endif()
# Sound setup
# Require at least ffmpeg 3.2 for now
SET(FFVER_OK FALSE)
find_package(FFmpeg REQUIRED COMPONENTS AVCODEC AVFORMAT AVUTIL SWSCALE SWRESAMPLE)
if(FFmpeg_FOUND)
SET(FFVER_OK TRUE)
# Can not detect FFmpeg version on Windows for now
if (NOT WIN32)
if(FFmpeg_AVFORMAT_VERSION VERSION_LESS "57.56.100")
message(STATUS "libavformat is too old! (${FFmpeg_AVFORMAT_VERSION}, wanted 57.56.100)")
set(FFVER_OK FALSE)
endif()
if(FFmpeg_AVCODEC_VERSION VERSION_LESS "57.64.100")
message(STATUS "libavcodec is too old! (${FFmpeg_AVCODEC_VERSION}, wanted 57.64.100)")
set(FFVER_OK FALSE)
endif()
if(FFmpeg_AVUTIL_VERSION VERSION_LESS "55.34.100")
message(STATUS "libavutil is too old! (${FFmpeg_AVUTIL_VERSION}, wanted 55.34.100)")
set(FFVER_OK FALSE)
endif()
if(FFmpeg_SWSCALE_VERSION VERSION_LESS "4.2.100")
message(STATUS "libswscale is too old! (${FFmpeg_SWSCALE_VERSION}, wanted 4.2.100)")
set(FFVER_OK FALSE)
endif()
if(FFmpeg_SWRESAMPLE_VERSION VERSION_LESS "2.3.100")
message(STATUS "libswresample is too old! (${FFmpeg_SWRESAMPLE_VERSION}, wanted 2.3.100)")
set(FFVER_OK FALSE)
endif()
endif()
endif()
if(NOT FFmpeg_FOUND)
message(FATAL_ERROR "FFmpeg was not found" )
endif()
if(NOT FFVER_OK)
message(FATAL_ERROR "FFmpeg version is too old, 3.2 is required" )
endif()
if(WIN32)
message("Can not detect FFmpeg version, at least the 3.2 is required" )
endif()
# Required for building the FFmpeg headers
add_definitions(-D__STDC_CONSTANT_MACROS)
@ -275,9 +320,6 @@ if (APPLE)
"${APP_BUNDLE_DIR}/Contents/Resources/OpenMW.icns" COPYONLY)
endif (APPLE)
# Set up DEBUG define
set_directory_properties(PROPERTIES COMPILE_DEFINITIONS_DEBUG DEBUG=1)
if (NOT APPLE)
set(OPENMW_MYGUI_FILES_ROOT ${OpenMW_BINARY_DIR})
set(OPENMW_SHADERS_ROOT ${OpenMW_BINARY_DIR})
@ -469,7 +511,7 @@ if(WIN32)
INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/Debug/resources" DESTINATION "." CONFIGURATIONS Debug)
INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/Release/resources" DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel)
FILE(GLOB plugin_dir_debug "${OpenMW_BINARY_DIR}/Debug/osgPlugins-*")
FILE(GLOB plugin_dir_release "${OpenMW_BINARY_DIR}/Release/osgPlugins-*")
INSTALL(DIRECTORY ${plugin_dir_debug} DESTINATION "." CONFIGURATIONS Debug)
@ -536,6 +578,11 @@ if(WIN32)
endif(WIN32)
# Extern
set(RECASTNAVIGATION_DEMO OFF CACHE BOOL "Do not build RecastDemo")
set(RECASTNAVIGATION_STATIC ON CACHE BOOL "Build recastnavigation static libraries")
set(RECASTNAVIGATION_TESTS OFF CACHE BOOL "Do not build recastnavigation tests")
add_subdirectory (extern/recastnavigation)
add_subdirectory (extern/osg-ffmpeg-videoplayer)
add_subdirectory (extern/oics)
if (BUILD_OPENCS)
@ -602,7 +649,7 @@ if (WIN32)
if (USE_DEBUG_CONSOLE AND BUILD_OPENMW)
set_target_properties(openmw PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:CONSOLE")
set_target_properties(openmw PROPERTIES LINK_FLAGS_RELWITHDEBINFO "/SUBSYSTEM:CONSOLE")
set_target_properties(openmw PROPERTIES COMPILE_DEFINITIONS_DEBUG "_CONSOLE")
set_target_properties(openmw PROPERTIES COMPILE_DEFINITIONS $<$<CONFIG:Debug>:_CONSOLE>)
elseif (BUILD_OPENMW)
# Turn off debug console, debug output will be written to visual studio output instead
set_target_properties(openmw PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:WINDOWS")
@ -612,7 +659,6 @@ if (WIN32)
if (BUILD_OPENMW)
# Release builds don't use the debug console
set_target_properties(openmw PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS")
set_target_properties(openmw PROPERTIES COMPILE_DEFINITIONS_RELEASE "_WINDOWS")
set_target_properties(openmw PROPERTIES LINK_FLAGS_MINSIZEREL "/SUBSYSTEM:WINDOWS")
endif()

View file

@ -7,6 +7,7 @@
#include <components/debug/debuglog.hpp>
#include <components/fallback/validate.hpp>
#include <components/misc/rng.hpp>
#include <components/nifosg/nifloader.hpp>
#include "model/doc/document.hpp"
@ -355,6 +356,8 @@ int CS::Editor::run()
if (mLocal.empty())
return 1;
Misc::Rng::init();
mStartup.show();
QApplication::setQuitOnLastWindowClosed (true);

View file

@ -317,8 +317,8 @@ void CSMPrefs::State::declare()
declareShortcut ("table-remove", "Remove Row/Record", QKeySequence(Qt::Key_Delete));
declareShortcut ("table-moveup", "Move Record Up", QKeySequence());
declareShortcut ("table-movedown", "Move Record Down", QKeySequence());
declareShortcut ("table-view", "View Record", QKeySequence());
declareShortcut ("table-preview", "Preview Record", QKeySequence());
declareShortcut ("table-view", "View Record", QKeySequence(Qt::ShiftModifier | Qt::Key_C));
declareShortcut ("table-preview", "Preview Record", QKeySequence(Qt::ShiftModifier | Qt::Key_V));
declareShortcut ("table-extendeddelete", "Extended Record Deletion", QKeySequence());
declareShortcut ("table-extendedrevert", "Extended Record Revertion", QKeySequence());

View file

@ -977,15 +977,19 @@ int CSMWorld::Data::startLoading (const boost::filesystem::path& path, bool base
void CSMWorld::Data::loadFallbackEntries()
{
// Load default marker definitions, if game files do not have them for some reason
std::pair<std::string, std::string> markers[] = {
std::make_pair("divinemarker", "marker_divine.nif"),
std::make_pair("doormarker", "marker_arrow.nif"),
std::make_pair("northmarker", "marker_north.nif"),
std::make_pair("templemarker", "marker_temple.nif"),
std::make_pair("travelmarker", "marker_travel.nif")
std::pair<std::string, std::string> staticMarkers[] = {
std::make_pair("DivineMarker", "marker_divine.nif"),
std::make_pair("DoorMarker", "marker_arrow.nif"),
std::make_pair("NorthMarker", "marker_north.nif"),
std::make_pair("TempleMarker", "marker_temple.nif"),
std::make_pair("TravelMarker", "marker_travel.nif")
};
for (const std::pair<std::string, std::string> marker : markers)
std::pair<std::string, std::string> doorMarkers[] = {
std::make_pair("PrisonMarker", "marker_prison.nif")
};
for (const std::pair<std::string, std::string> marker : staticMarkers)
{
if (mReferenceables.searchId (marker.first)==-1)
{
@ -995,6 +999,17 @@ void CSMWorld::Data::loadFallbackEntries()
mReferenceables.appendRecord (record, CSMWorld::UniversalId::Type_Static);
}
}
for (const std::pair<std::string, std::string> marker : doorMarkers)
{
if (mReferenceables.searchId (marker.first)==-1)
{
CSMWorld::Record<ESM::Door> record;
record.mBase = ESM::Door(marker.first, std::string(), marker.second, std::string(), std::string(), std::string());
record.mState = CSMWorld::RecordBase::State_BaseOnly;
mReferenceables.appendRecord (record, CSMWorld::UniversalId::Type_Door);
}
}
}
bool CSMWorld::Data::continueLoading (CSMDoc::Messages& messages)

View file

@ -520,7 +520,6 @@ std::pair<float, float> CSMWorld::ConstInfoSelectWrapper::getConditionFloatRange
const float FloatMax = std::numeric_limits<float>::infinity();
const float FloatMin = -std::numeric_limits<float>::infinity();
const float Epsilon = std::numeric_limits<float>::epsilon();
const std::pair<float, float> InvalidRange(FloatMax, FloatMin);
float value = mConstSelect.mValue.getFloat();

View file

@ -292,6 +292,7 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id,
mEditAction = new QAction (tr ("Edit Record"), this);
connect (mEditAction, SIGNAL (triggered()), this, SLOT (editRecord()));
mEditAction->setIcon(QIcon(":edit-edit"));
addAction (mEditAction);
CSMPrefs::Shortcut* editShortcut = new CSMPrefs::Shortcut("table-edit", this);
editShortcut->associateAction(mEditAction);
@ -300,12 +301,14 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id,
{
mCreateAction = new QAction (tr ("Add Record"), this);
connect (mCreateAction, SIGNAL (triggered()), this, SIGNAL (createRequest()));
mCreateAction->setIcon(QIcon(":edit-add"));
addAction (mCreateAction);
CSMPrefs::Shortcut* createShortcut = new CSMPrefs::Shortcut("table-add", this);
createShortcut->associateAction(mCreateAction);
mCloneAction = new QAction (tr ("Clone Record"), this);
connect(mCloneAction, SIGNAL (triggered()), this, SLOT (cloneRecord()));
mCloneAction->setIcon(QIcon(":edit-clone"));
addAction(mCloneAction);
CSMPrefs::Shortcut* cloneShortcut = new CSMPrefs::Shortcut("table-clone", this);
cloneShortcut->associateAction(mCloneAction);
@ -315,6 +318,7 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id,
{
mTouchAction = new QAction(tr("Touch Record"), this);
connect(mTouchAction, SIGNAL(triggered()), this, SLOT(touchRecord()));
mTouchAction->setIcon(QIcon(":edit-touch"));
addAction(mTouchAction);
CSMPrefs::Shortcut* touchShortcut = new CSMPrefs::Shortcut("table-touch", this);
touchShortcut->associateAction(mTouchAction);
@ -322,49 +326,56 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id,
mRevertAction = new QAction (tr ("Revert Record"), this);
connect (mRevertAction, SIGNAL (triggered()), mDispatcher, SLOT (executeRevert()));
mRevertAction->setIcon(QIcon(":edit-undo"));
addAction (mRevertAction);
CSMPrefs::Shortcut* revertShortcut = new CSMPrefs::Shortcut("table-revert", this);
revertShortcut->associateAction(mRevertAction);
mDeleteAction = new QAction (tr ("Delete Record"), this);
connect (mDeleteAction, SIGNAL (triggered()), mDispatcher, SLOT (executeDelete()));
mDeleteAction->setIcon(QIcon(":edit-delete"));
addAction (mDeleteAction);
CSMPrefs::Shortcut* deleteShortcut = new CSMPrefs::Shortcut("table-remove", this);
deleteShortcut->associateAction(mDeleteAction);
mMoveUpAction = new QAction (tr ("Move Up"), this);
connect (mMoveUpAction, SIGNAL (triggered()), this, SLOT (moveUpRecord()));
mMoveUpAction->setIcon(QIcon(":record-up"));
addAction (mMoveUpAction);
CSMPrefs::Shortcut* moveUpShortcut = new CSMPrefs::Shortcut("table-moveup", this);
moveUpShortcut->associateAction(mMoveUpAction);
mMoveDownAction = new QAction (tr ("Move Down"), this);
connect (mMoveDownAction, SIGNAL (triggered()), this, SLOT (moveDownRecord()));
mMoveDownAction->setIcon(QIcon(":record-down"));
addAction (mMoveDownAction);
CSMPrefs::Shortcut* moveDownShortcut = new CSMPrefs::Shortcut("table-movedown", this);
moveDownShortcut->associateAction(mMoveDownAction);
mViewAction = new QAction (tr ("View"), this);
connect (mViewAction, SIGNAL (triggered()), this, SLOT (viewRecord()));
mViewAction->setIcon(QIcon(":/cell.png"));
addAction (mViewAction);
CSMPrefs::Shortcut* viewShortcut = new CSMPrefs::Shortcut("table-view", this);
viewShortcut->associateAction(mViewAction);
mPreviewAction = new QAction (tr ("Preview"), this);
connect (mPreviewAction, SIGNAL (triggered()), this, SLOT (previewRecord()));
mPreviewAction->setIcon(QIcon(":edit-preview"));
addAction (mPreviewAction);
CSMPrefs::Shortcut* previewShortcut = new CSMPrefs::Shortcut("table-preview", this);
previewShortcut->associateAction(mPreviewAction);
mExtendedDeleteAction = new QAction (tr ("Extended Delete Record"), this);
connect (mExtendedDeleteAction, SIGNAL (triggered()), this, SLOT (executeExtendedDelete()));
mExtendedDeleteAction->setIcon(QIcon(":edit-delete"));
addAction (mExtendedDeleteAction);
CSMPrefs::Shortcut* extendedDeleteShortcut = new CSMPrefs::Shortcut("table-extendeddelete", this);
extendedDeleteShortcut->associateAction(mExtendedDeleteAction);
mExtendedRevertAction = new QAction (tr ("Extended Revert Record"), this);
connect (mExtendedRevertAction, SIGNAL (triggered()), this, SLOT (executeExtendedRevert()));
mExtendedRevertAction->setIcon(QIcon(":edit-undo"));
addAction (mExtendedRevertAction);
CSMPrefs::Shortcut* extendedRevertShortcut = new CSMPrefs::Shortcut("table-extendedrevert", this);
extendedRevertShortcut->associateAction(mExtendedRevertAction);

View file

@ -4,6 +4,7 @@ set(GAME
engine.cpp
${CMAKE_SOURCE_DIR}/files/windows/openmw.rc
${CMAKE_SOURCE_DIR}/files/windows/openmw.exe.manifest
)
if (ANDROID)
@ -21,7 +22,7 @@ add_openmw_dir (mwrender
actors objects renderingmanager animation rotatecontroller sky npcanimation vismask
creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation
bulletdebugdraw globalmap characterpreview camera localmap water terrainstorage ripplesimulation
renderbin actoranimation landmanager
renderbin actoranimation landmanager navmesh actorspaths
)
add_openmw_dir (mwinput
@ -70,7 +71,7 @@ add_openmw_dir (mwworld
)
add_openmw_dir (mwphysics
physicssystem trace collisiontype actor convert
physicssystem trace collisiontype actor convert object heightfield
)
add_openmw_dir (mwclass
@ -133,6 +134,7 @@ target_link_libraries(openmw
${FFmpeg_LIBRARIES}
${MyGUI_LIBRARIES}
${SDL2_LIBRARY}
${RecastNavigation_LIBRARIES}
"osg-ffmpeg-videoplayer"
"oics"
components

View file

@ -7,7 +7,6 @@
#include "engine.hpp"
#if defined(_WIN32)
// For OutputDebugString
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif

View file

@ -233,6 +233,10 @@ namespace MWBase
virtual void castSpell(const MWWorld::Ptr& ptr, const std::string spellId, bool manualSpell) = 0;
virtual void processChangedSettings (const std::set< std::pair<std::string, std::string> >& settings) = 0;
virtual float getActorsProcessingRange() const = 0;
/// Check if the target actor was detected by an observer
/// If the observer is a non-NPC, check all actors in AI processing distance as observers
virtual bool isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer) = 0;

View file

@ -285,6 +285,8 @@ namespace MWBase
virtual void setEnemy (const MWWorld::Ptr& enemy) = 0;
virtual int getMessagesCount() const = 0;
virtual const Translation::Storage& getTranslationDataStorage() const = 0;
/// Warning: do not use MyGUI::InputManager::setKeyFocusWidget directly. Instead use this.

View file

@ -4,6 +4,7 @@
#include <vector>
#include <map>
#include <set>
#include <deque>
#include <components/esm/cellid.hpp>
@ -54,6 +55,11 @@ namespace MWMechanics
struct Movement;
}
namespace DetourNavigator
{
class Navigator;
}
namespace MWWorld
{
class CellStore;
@ -262,8 +268,8 @@ namespace MWBase
///< Adjust position after load to be on ground. Must be called after model load.
/// @param force do this even if the ptr is flying
virtual void fixPosition (const MWWorld::Ptr& actor) = 0;
///< Attempt to fix position so that the Ptr is no longer inside collision geometry.
virtual void fixPosition () = 0;
///< Attempt to fix position so that the player is not stuck inside the geometry.
/// @note No-op for items in containers. Use ContainerStore::removeItem instead.
virtual void deleteObject (const MWWorld::Ptr& ptr) = 0;
@ -302,6 +308,9 @@ namespace MWBase
virtual bool castRay (float x1, float y1, float z1, float x2, float y2, float z2) = 0;
virtual void setActorCollisionMode(const MWWorld::Ptr& ptr, bool enabled) = 0;
virtual bool isActorCollisionEnabled(const MWWorld::Ptr& ptr) = 0;
virtual bool toggleCollisionMode() = 0;
///< Toggle collision mode for player. If disabled player object should ignore
/// collisions and gravity.
@ -592,6 +601,15 @@ namespace MWBase
/// Preload VFX associated with this effect list
virtual void preloadEffects(const ESM::EffectList* effectList) = 0;
virtual DetourNavigator::Navigator* getNavigator() const = 0;
virtual void updateActorPath(const MWWorld::ConstPtr& actor, const std::deque<osg::Vec3f>& path,
const osg::Vec3f& halfExtents, const osg::Vec3f& start, const osg::Vec3f& end) const = 0;
virtual void removeActorPath(const MWWorld::ConstPtr& actor) const = 0;
virtual void setNavMeshNumberToRender(const std::size_t value) = 0;
};
}

View file

@ -138,7 +138,7 @@ namespace MWClass
std::string Activator::getSoundIdFromSndGen(const MWWorld::Ptr &ptr, const std::string &name) const
{
std::string model = getModel(ptr); // Assume it's not empty, since we wouldn't have gotten the soundgen otherwise
const std::string model = getModel(ptr); // Assume it's not empty, since we wouldn't have gotten the soundgen otherwise
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
std::string creatureId;
@ -151,21 +151,35 @@ namespace MWClass
}
}
if (creatureId.empty())
return std::string();
int type = getSndGenTypeFromName(name);
std::vector<const ESM::SoundGenerator*> sounds;
for (auto sound = store.get<ESM::SoundGenerator>().begin(); sound != store.get<ESM::SoundGenerator>().end(); ++sound)
if (type == sound->mType && !sound->mCreature.empty() && (Misc::StringUtils::ciEqual(creatureId, sound->mCreature)))
sounds.push_back(&*sound);
std::vector<const ESM::SoundGenerator*> fallbacksounds;
if (!creatureId.empty())
{
std::vector<const ESM::SoundGenerator*> sounds;
for (auto sound = store.get<ESM::SoundGenerator>().begin(); sound != store.get<ESM::SoundGenerator>().end(); ++sound)
{
if (type == sound->mType && !sound->mCreature.empty() && (Misc::StringUtils::ciEqual(creatureId, sound->mCreature)))
sounds.push_back(&*sound);
if (type == sound->mType && sound->mCreature.empty())
fallbacksounds.push_back(&*sound);
}
if (!sounds.empty())
return sounds[Misc::Rng::rollDice(sounds.size())]->mSound;
if (!sounds.empty())
return sounds[Misc::Rng::rollDice(sounds.size())]->mSound;
if (!fallbacksounds.empty())
return fallbacksounds[Misc::Rng::rollDice(fallbacksounds.size())]->mSound;
}
else
{
// The activator doesn't have a corresponding creature ID, but we can try to use the defaults
for (auto sound = store.get<ESM::SoundGenerator>().begin(); sound != store.get<ESM::SoundGenerator>().end(); ++sound)
if (type == sound->mType && sound->mCreature.empty())
fallbacksounds.push_back(&*sound);
if (type == ESM::SoundGenerator::Land)
return "Body Fall Large";
if (!fallbacksounds.empty())
return fallbacksounds[Misc::Rng::rollDice(fallbacksounds.size())]->mSound;
}
return std::string();
}

View file

@ -297,11 +297,11 @@ namespace MWClass
{
const MWWorld::InventoryStore& invStore = npc.getClass().getInventoryStore(npc);
if (ptr.getCellRef().getCharge() == 0)
if (getItemHealth(ptr) == 0)
return std::make_pair(0, "#{sInventoryMessage1}");
// slots that this item can be equipped in
std::pair<std::vector<int>, bool> slots_ = ptr.getClass().getEquipmentSlots(ptr);
std::pair<std::vector<int>, bool> slots_ = getEquipmentSlots(ptr);
if (slots_.first.empty())
return std::make_pair(0, "");

View file

@ -215,7 +215,7 @@ namespace MWClass
std::pair<int, std::string> Clothing::canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const
{
// slots that this item can be equipped in
std::pair<std::vector<int>, bool> slots_ = ptr.getClass().getEquipmentSlots(ptr);
std::pair<std::vector<int>, bool> slots_ = getEquipmentSlots(ptr);
if (slots_.first.empty())
return std::make_pair(0, "");

View file

@ -137,7 +137,7 @@ namespace MWClass
// Persistent actors with 0 health do not play death animation
if (data->mCreatureStats.isDead())
data->mCreatureStats.setDeathAnimationFinished(ptr.getClass().isPersistent(ptr));
data->mCreatureStats.setDeathAnimationFinished(isPersistent(ptr));
// spells
for (std::vector<std::string>::const_iterator iter (ref->mBase->mSpells.mList.begin());
@ -166,7 +166,7 @@ namespace MWClass
getContainerStore(ptr).fill(ref->mBase->mInventory, ptr.getCellRef().getRefId());
if (hasInventory)
getInventoryStore(ptr).autoEquipShield(ptr);
getInventoryStore(ptr).autoEquip(ptr);
}
}
@ -194,9 +194,9 @@ namespace MWClass
models.push_back(model);
// FIXME: use const version of InventoryStore functions once they are available
if (ptr.getClass().hasInventoryStore(ptr))
if (hasInventoryStore(ptr))
{
const MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore(ptr);
const MWWorld::InventoryStore& invStore = getInventoryStore(ptr);
for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot)
{
MWWorld::ConstContainerStoreIterator equipped = invStore.getSlot(slot);
@ -237,7 +237,7 @@ namespace MWClass
// Get the weapon used (if hand-to-hand, weapon = inv.end())
MWWorld::Ptr weapon;
if (ptr.getClass().hasInventoryStore(ptr))
if (hasInventoryStore(ptr))
{
MWWorld::InventoryStore &inv = getInventoryStore(ptr);
MWWorld::ContainerStoreIterator weaponslot = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
@ -253,8 +253,7 @@ namespace MWClass
// For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result.
std::vector<MWWorld::Ptr> targetActors;
if (!ptr.isEmpty() && ptr.getClass().isActor())
ptr.getClass().getCreatureStats(ptr).getAiSequence().getCombatTargets(targetActors);
stats.getAiSequence().getCombatTargets(targetActors);
std::pair<MWWorld::Ptr, osg::Vec3f> result = MWBase::Environment::get().getWorld()->getHitContact(ptr, dist, targetActors);
if (result.first.isEmpty())
@ -332,7 +331,7 @@ namespace MWClass
void Creature::onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful) const
{
MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
MWMechanics::CreatureStats& stats = getCreatureStats(ptr);
// NOTE: 'object' and/or 'attacker' may be empty.
if (!attacker.isEmpty() && attacker.getClass().isActor() && !stats.getAiSequence().isInCombat(attacker))
@ -346,7 +345,7 @@ namespace MWClass
setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker);
// Attacker and target store each other as hitattemptactor if they have no one stored yet
if (!attacker.isEmpty() && attacker.getClass().isActor() && !ptr.isEmpty() && ptr.getClass().isActor())
if (!attacker.isEmpty() && attacker.getClass().isActor())
{
MWMechanics::CreatureStats& statsAttacker = attacker.getClass().getCreatureStats(attacker);
// First handle the attacked actor
@ -522,7 +521,7 @@ namespace MWClass
const MWBase::World *world = MWBase::Environment::get().getWorld();
const MWMechanics::MagicEffects &mageffects = stats.getMagicEffects();
bool running = ptr.getClass().getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Run);
bool running = stats.getStance(MWMechanics::CreatureStats::Stance_Run);
// The Run speed difference for creatures comes from the animation speed difference (see runStateToWalkState in character.cpp)
float runSpeed = walkSpeed;
@ -632,25 +631,27 @@ namespace MWClass
if(type >= 0)
{
std::vector<const ESM::SoundGenerator*> sounds;
std::vector<const ESM::SoundGenerator*> fallbacksounds;
MWWorld::LiveCellRef<ESM::Creature>* ref = ptr.get<ESM::Creature>();
const std::string& ourId = (ref->mBase->mOriginal.empty()) ? ptr.getCellRef().getRefId() : ref->mBase->mOriginal;
MWWorld::Store<ESM::SoundGenerator>::iterator sound = store.begin();
while(sound != store.end())
while (sound != store.end())
{
if (type == sound->mType && !sound->mCreature.empty() && (Misc::StringUtils::ciEqual(ourId, sound->mCreature)))
sounds.push_back(&*sound);
if (type == sound->mType && sound->mCreature.empty())
fallbacksounds.push_back(&*sound);
++sound;
}
if(!sounds.empty())
if (!sounds.empty())
return sounds[Misc::Rng::rollDice(sounds.size())]->mSound;
if (!fallbacksounds.empty())
return fallbacksounds[Misc::Rng::rollDice(fallbacksounds.size())]->mSound;
}
if (type == ESM::SoundGenerator::Land)
return "Body Fall Large";
return "";
}
@ -806,7 +807,7 @@ namespace MWClass
void Creature::respawn(const MWWorld::Ptr &ptr) const
{
const MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr);
const MWMechanics::CreatureStats& creatureStats = getCreatureStats(ptr);
if (ptr.getRefData().getCount() > 0 && !creatureStats.isDead())
return;

View file

@ -357,7 +357,7 @@ namespace MWClass
// Persistent actors with 0 health do not play death animation
if (data->mNpcStats.isDead())
data->mNpcStats.setDeathAnimationFinished(ptr.getClass().isPersistent(ptr));
data->mNpcStats.setDeathAnimationFinished(isPersistent(ptr));
// race powers
const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(ref->mBase->mRace);
@ -469,41 +469,38 @@ namespace MWClass
// FIXME: use const version of InventoryStore functions once they are available
// preload equipped items
if (ptr.getClass().hasInventoryStore(ptr))
const MWWorld::InventoryStore& invStore = getInventoryStore(ptr);
for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot)
{
const MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore(ptr);
for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot)
MWWorld::ConstContainerStoreIterator equipped = invStore.getSlot(slot);
if (equipped != invStore.end())
{
MWWorld::ConstContainerStoreIterator equipped = invStore.getSlot(slot);
if (equipped != invStore.end())
std::vector<ESM::PartReference> parts;
if(equipped->getTypeName() == typeid(ESM::Clothing).name())
{
std::vector<ESM::PartReference> parts;
if(equipped->getTypeName() == typeid(ESM::Clothing).name())
{
const ESM::Clothing *clothes = equipped->get<ESM::Clothing>()->mBase;
parts = clothes->mParts.mParts;
}
else if(equipped->getTypeName() == typeid(ESM::Armor).name())
{
const ESM::Armor *armor = equipped->get<ESM::Armor>()->mBase;
parts = armor->mParts.mParts;
}
else
{
std::string model = equipped->getClass().getModel(*equipped);
if (!model.empty())
models.push_back(model);
}
const ESM::Clothing *clothes = equipped->get<ESM::Clothing>()->mBase;
parts = clothes->mParts.mParts;
}
else if(equipped->getTypeName() == typeid(ESM::Armor).name())
{
const ESM::Armor *armor = equipped->get<ESM::Armor>()->mBase;
parts = armor->mParts.mParts;
}
else
{
std::string model = equipped->getClass().getModel(*equipped);
if (!model.empty())
models.push_back(model);
}
for (std::vector<ESM::PartReference>::const_iterator it = parts.begin(); it != parts.end(); ++it)
{
std::string partname = female ? it->mFemale : it->mMale;
if (partname.empty())
partname = female ? it->mMale : it->mFemale;
const ESM::BodyPart* part = MWBase::Environment::get().getWorld()->getStore().get<ESM::BodyPart>().search(partname);
if (part && !part->mModel.empty())
models.push_back("meshes/"+part->mModel);
}
for (std::vector<ESM::PartReference>::const_iterator it = parts.begin(); it != parts.end(); ++it)
{
std::string partname = female ? it->mFemale : it->mMale;
if (partname.empty())
partname = female ? it->mMale : it->mFemale;
const ESM::BodyPart* part = MWBase::Environment::get().getWorld()->getStore().get<ESM::BodyPart>().search(partname);
if (part && !part->mModel.empty())
models.push_back("meshes/"+part->mModel);
}
}
}
@ -573,8 +570,8 @@ namespace MWClass
// For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result.
std::vector<MWWorld::Ptr> targetActors;
if (!ptr.isEmpty() && ptr.getClass().isActor() && ptr != MWMechanics::getPlayer())
ptr.getClass().getCreatureStats(ptr).getAiSequence().getCombatTargets(targetActors);
if (ptr != MWMechanics::getPlayer())
getCreatureStats(ptr).getAiSequence().getCombatTargets(targetActors);
// TODO: Use second to work out the hit angle
std::pair<MWWorld::Ptr, osg::Vec3f> result = world->getHitContact(ptr, dist, targetActors);
@ -597,7 +594,7 @@ namespace MWClass
if(!weapon.isEmpty())
weapskill = weapon.getClass().getEquipmentSkill(weapon);
float hitchance = MWMechanics::getHitChance(ptr, victim, ptr.getClass().getSkill(ptr, weapskill));
float hitchance = MWMechanics::getHitChance(ptr, victim, getSkill(ptr, weapskill));
if (Misc::Rng::roll0to99() >= hitchance)
{
@ -667,7 +664,7 @@ namespace MWClass
void Npc::onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful) const
{
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
MWMechanics::CreatureStats& stats = getCreatureStats(ptr);
bool wasDead = stats.isDead();
// Note OnPcHitMe is not set for friendly hits.
@ -681,7 +678,7 @@ namespace MWClass
}
// Attacker and target store each other as hitattemptactor if they have no one stored yet
if (!attacker.isEmpty() && attacker.getClass().isActor() && !ptr.isEmpty() && ptr.getClass().isActor())
if (!attacker.isEmpty() && attacker.getClass().isActor())
{
MWMechanics::CreatureStats& statsAttacker = attacker.getClass().getCreatureStats(attacker);
// First handle the attacked actor
@ -702,7 +699,7 @@ namespace MWClass
if (setOnPcHitMe && !attacker.isEmpty() && attacker == MWMechanics::getPlayer())
{
const std::string &script = ptr.getClass().getScript(ptr);
const std::string &script = getScript(ptr);
/* Set the OnPCHitMe script variable. The script is responsible for clearing it. */
if(!script.empty())
ptr.getRefData().getLocals().setVarByInt(script, "onpchitme", 1);
@ -891,12 +888,11 @@ namespace MWClass
if(stats.getAiSequence().isInCombat())
return std::shared_ptr<MWWorld::Action>(new MWWorld::FailedAction(""));
if(getCreatureStats(actor).getStance(MWMechanics::CreatureStats::Stance_Sneak)
|| ptr.getClass().getCreatureStats(ptr).getKnockedDown())
if(getCreatureStats(actor).getStance(MWMechanics::CreatureStats::Stance_Sneak) || stats.getKnockedDown())
return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr)); // stealing
// Can't talk to werewolfs
if(ptr.getClass().isNpc() && ptr.getClass().getNpcStats(ptr).isWerewolf())
if(getNpcStats(ptr).isWerewolf())
return std::shared_ptr<MWWorld::Action> (new MWWorld::FailedAction(""));
return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionTalk(ptr));
@ -927,7 +923,7 @@ namespace MWClass
float Npc::getSpeed(const MWWorld::Ptr& ptr) const
{
const MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
const MWMechanics::CreatureStats& stats = getCreatureStats(ptr);
if (stats.isParalyzed() || stats.getKnockedDown() || stats.isDead())
return 0.f;
@ -993,7 +989,7 @@ namespace MWClass
if(getEncumbrance(ptr) > getCapacity(ptr))
return 0.f;
const MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
const MWMechanics::CreatureStats& stats = getCreatureStats(ptr);
if (stats.isParalyzed() || stats.getKnockedDown() || stats.isDead())
return 0.f;
@ -1148,9 +1144,7 @@ namespace MWClass
const bool hasHealth = it->getClass().hasItemHealth(*it);
if (hasHealth)
{
int armorHealth = it->getClass().getItemHealth(*it);
int armorMaxHealth = it->getClass().getItemMaxHealth(*it);
ratings[i] *= (float(armorHealth) / armorMaxHealth);
ratings[i] *= it->getClass().getItemNormalizedHealth(*it);
}
}
}
@ -1215,13 +1209,13 @@ namespace MWClass
return (name == "left") ? "FootWaterLeft" : "FootWaterRight";
if(world->isOnGround(ptr))
{
if (ptr.getClass().getNpcStats(ptr).isWerewolf()
&& ptr.getClass().getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Run))
if (getNpcStats(ptr).isWerewolf()
&& getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Run))
{
MWMechanics::WeaponType weaponType = MWMechanics::WeapType_None;
MWMechanics::getActiveWeapon(ptr.getClass().getCreatureStats(ptr), ptr.getClass().getInventoryStore(ptr), &weaponType);
MWMechanics::getActiveWeapon(getCreatureStats(ptr), getInventoryStore(ptr), &weaponType);
if (weaponType == MWMechanics::WeapType_None)
return "";
return std::string();
}
const MWWorld::InventoryStore &inv = Npc::getInventoryStore(ptr);
@ -1239,12 +1233,12 @@ namespace MWClass
return (name == "left") ? "FootHeavyLeft" : "FootHeavyRight";
}
}
return "";
return std::string();
}
// Morrowind ignores land soundgen for NPCs
if(name == "land")
return "";
return std::string();
if(name == "swimleft")
return "Swim Left";
if(name == "swimright")
@ -1254,11 +1248,11 @@ namespace MWClass
// only for biped creatures?
if(name == "moan")
return "";
return std::string();
if(name == "roar")
return "";
return std::string();
if(name == "scream")
return "";
return std::string();
throw std::runtime_error(std::string("Unexpected soundgen type: ")+name);
}
@ -1272,7 +1266,7 @@ namespace MWClass
int Npc::getSkill(const MWWorld::Ptr& ptr, int skill) const
{
return ptr.getClass().getNpcStats(ptr).getSkill(skill).getModified();
return getNpcStats(ptr).getSkill(skill).getModified();
}
int Npc::getBloodTexture(const MWWorld::ConstPtr &ptr) const
@ -1354,7 +1348,7 @@ namespace MWClass
void Npc::respawn(const MWWorld::Ptr &ptr) const
{
const MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr);
const MWMechanics::CreatureStats& creatureStats = getCreatureStats(ptr);
if (ptr.getRefData().getCount() > 0 && !creatureStats.isDead())
return;

View file

@ -265,7 +265,7 @@ namespace MWClass
std::string text;
// weapon type & damage
if ((ref->mBase->mData.mType < 12 || Settings::Manager::getBool("show projectile damage", "Game")) && ref->mBase->mData.mType < 14)
if ((ref->mBase->mData.mType < ESM::Weapon::Arrow || Settings::Manager::getBool("show projectile damage", "Game")) && ref->mBase->mData.mType <= ESM::Weapon::Bolt)
{
text += "\n#{sType} ";
@ -295,7 +295,15 @@ namespace MWClass
((oneOrTwoHanded != "") ? ", " + store.get<ESM::GameSetting>().find(oneOrTwoHanded)->mValue.getString() : "");
// weapon damage
if (ref->mBase->mData.mType >= 9)
if (ref->mBase->mData.mType == ESM::Weapon::MarksmanThrown)
{
// Thrown weapons have 2x real damage applied
// as they're both the weapon and the ammo
text += "\n#{sAttack}: "
+ MWGui::ToolTips::toString(static_cast<int>(ref->mBase->mData.mChop[0] * 2))
+ " - " + MWGui::ToolTips::toString(static_cast<int>(ref->mBase->mData.mChop[1] * 2));
}
else if (ref->mBase->mData.mType >= ESM::Weapon::MarksmanBow)
{
// marksman
text += "\n#{sAttack}: "
@ -383,7 +391,7 @@ namespace MWClass
std::pair<int, std::string> Weapon::canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const
{
if (hasItemHealth(ptr) && ptr.getCellRef().getCharge() == 0)
if (hasItemHealth(ptr) && getItemHealth(ptr) == 0)
return std::make_pair(0, "#{sInventoryMessage1}");
// Do not allow equip weapons from inventory during attack
@ -391,7 +399,7 @@ namespace MWClass
&& MWBase::Environment::get().getWindowManager()->isGuiMode())
return std::make_pair(0, "#{sCantEquipWeapWarning}");
std::pair<std::vector<int>, bool> slots_ = ptr.getClass().getEquipmentSlots(ptr);
std::pair<std::vector<int>, bool> slots_ = getEquipmentSlots(ptr);
if (slots_.first.empty())
return std::make_pair (0, "");

View file

@ -96,7 +96,7 @@ namespace MWGui
Log(Debug::Warning) << "Warning: no splash screens found!";
}
void LoadingScreen::setLabel(const std::string &label, bool important)
void LoadingScreen::setLabel(const std::string &label, bool important, bool center)
{
mImportantLabel = important;
@ -105,7 +105,11 @@ namespace MWGui
MyGUI::IntSize size(mLoadingText->getTextSize().width+padding, mLoadingBox->getHeight());
size.width = std::max(300, size.width);
mLoadingBox->setSize(size);
mLoadingBox->setPosition(mMainWidget->getWidth()/2 - mLoadingBox->getWidth()/2, mLoadingBox->getTop());
if (center)
mLoadingBox->setPosition(mMainWidget->getWidth()/2 - mLoadingBox->getWidth()/2, mMainWidget->getHeight()/2 - mLoadingBox->getHeight()/2);
else
mLoadingBox->setPosition(mMainWidget->getWidth()/2 - mLoadingBox->getWidth()/2, mMainWidget->getHeight() - mLoadingBox->getHeight() - 8);
}
void LoadingScreen::setVisible(bool visible)

View file

@ -34,7 +34,7 @@ namespace MWGui
virtual ~LoadingScreen();
/// Overridden from Loading::Listener, see the Loading::Listener documentation for usage details
virtual void setLabel (const std::string& label, bool important);
virtual void setLabel (const std::string& label, bool important, bool center);
virtual void loadingOn(bool visible=true);
virtual void loadingOff();
virtual void setProgressRange (size_t range);

View file

@ -51,7 +51,7 @@ void MerchantRepair::setPtr(const MWWorld::Ptr &actor)
{
int maxDurability = iter->getClass().getItemMaxHealth(*iter);
int durability = iter->getClass().getItemHealth(*iter);
if (maxDurability == durability)
if (maxDurability == durability || maxDurability == 0)
continue;
int basePrice = iter->getClass().getValue(*iter);

View file

@ -35,6 +35,11 @@ namespace MWGui
}
}
int MessageBoxManager::getMessagesCount()
{
return mMessageBoxes.size();
}
void MessageBoxManager::clear()
{
if (mInterMessageBoxe)

View file

@ -28,6 +28,8 @@ namespace MWGui
bool createInteractiveMessageBox (const std::string& message, const std::vector<std::string>& buttons);
bool isInteractiveMessageBox ();
int getMessagesCount();
const InteractiveMessageBox* getInteractiveMessageBox() const { return mInterMessageBoxe; }
/// Remove all message boxes

View file

@ -20,6 +20,7 @@
#include "../mwbase/world.hpp"
#include "../mwbase/soundmanager.hpp"
#include "../mwbase/inputmanager.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/windowmanager.hpp"
#include "confirmationdialog.hpp"
@ -437,6 +438,7 @@ namespace MWGui
MWBase::Environment::get().getSoundManager()->processChangedSettings(changed);
MWBase::Environment::get().getWindowManager()->processChangedSettings(changed);
MWBase::Environment::get().getInputManager()->processChangedSettings(changed);
MWBase::Environment::get().getMechanicsManager()->processChangedSettings(changed);
}
void SettingsWindow::onKeyboardSwitchClicked(MyGUI::Widget* _sender)

View file

@ -91,8 +91,7 @@ namespace
if (ench->mData.mType == ESM::Enchantment::ConstantEffect)
leftChargePercent = 101;
else
leftChargePercent = (left.mBase.getCellRef().getEnchantmentCharge() == -1) ? 100
: static_cast<int>(left.mBase.getCellRef().getEnchantmentCharge() / static_cast<float>(ench->mData.mCharge) * 100);
leftChargePercent = static_cast<int>(left.mBase.getCellRef().getNormalizedEnchantmentCharge(ench->mData.mCharge) * 100);
}
}
@ -104,8 +103,7 @@ namespace
if (ench->mData.mType == ESM::Enchantment::ConstantEffect)
rightChargePercent = 101;
else
rightChargePercent = (right.mBase.getCellRef().getEnchantmentCharge() == -1) ? 100
: static_cast<int>(right.mBase.getCellRef().getEnchantmentCharge() / static_cast<float>(ench->mData.mCharge) * 100);
rightChargePercent = static_cast<int>(right.mBase.getCellRef().getNormalizedEnchantmentCharge(ench->mData.mCharge) * 100);
}
}

View file

@ -35,8 +35,7 @@ namespace
float price = static_cast<float>(item.getClass().getValue(item));
if (item.getClass().hasItemHealth(item))
{
price *= item.getClass().getItemHealth(item);
price /= item.getClass().getItemMaxHealth(item);
price *= item.getClass().getItemNormalizedHealth(item);
}
return static_cast<int>(price * count);
}

View file

@ -1369,8 +1369,7 @@ namespace MWGui
const ESM::Enchantment* ench = mStore->get<ESM::Enchantment>()
.find(item.getClass().getEnchantment(item));
int chargePercent = (item.getCellRef().getEnchantmentCharge() == -1) ? 100
: static_cast<int>(item.getCellRef().getEnchantmentCharge() / static_cast<float>(ench->mData.mCharge) * 100);
int chargePercent = static_cast<int>(item.getCellRef().getNormalizedEnchantmentCharge(ench->mData.mCharge) * 100);
mHud->setSelectedEnchantItem(item, chargePercent);
mSpellWindow->setTitle(item.getClass().getName(item));
}
@ -1386,7 +1385,7 @@ namespace MWGui
int durabilityPercent = 100;
if (item.getClass().hasItemHealth(item))
{
durabilityPercent = static_cast<int>(item.getClass().getItemHealth(item) / static_cast<float>(item.getClass().getItemMaxHealth(item)) * 100);
durabilityPercent = static_cast<int>(item.getClass().getItemNormalizedHealth(item) * 100);
}
mHud->setSelectedWeapon(item, durabilityPercent);
mInventoryWindow->setTitle(item.getClass().getName(item));
@ -1671,6 +1670,15 @@ namespace MWGui
mHud->setEnemy(enemy);
}
int WindowManager::getMessagesCount() const
{
int count = 0;
if (mMessageBoxManager)
count = mMessageBoxManager->getMessagesCount();
return count;
}
Loading::Listener* WindowManager::getLoadingScreen()
{
return mLoadingScreen;

View file

@ -316,6 +316,8 @@ namespace MWGui
virtual void setEnemy (const MWWorld::Ptr& enemy);
virtual int getMessagesCount() const;
virtual const Translation::Storage& getTranslationDataStorage() const;
void onSoulgemDialogButtonPressed (int button);

View file

@ -147,9 +147,6 @@ void getRestorationPerHourOfSleep (const MWWorld::Ptr& ptr, float& health, float
namespace MWMechanics
{
const float aiProcessingDistance = 7168;
const float sqrAiProcessingDistance = aiProcessingDistance*aiProcessingDistance;
class SoulTrap : public MWMechanics::EffectSourceVisitor
{
MWWorld::Ptr mCreature;
@ -364,7 +361,8 @@ namespace MWMechanics
const ESM::Position& actor1Pos = actor1.getRefData().getPosition();
const ESM::Position& actor2Pos = actor2.getRefData().getPosition();
float sqrDist = (actor1Pos.asVec3() - actor2Pos.asVec3()).length2();
if (sqrDist > sqrAiProcessingDistance)
if (sqrDist > mActorsProcessingRange*mActorsProcessingRange)
return;
// No combat for totally static creatures
@ -1130,8 +1128,11 @@ namespace MWMechanics
}
}
Actors::Actors() {
Actors::Actors()
{
mTimerDisposeSummonsCorpses = 0.2f; // We should add a delay between summoned creature death and its corpse despawning
updateProcessingRange();
}
Actors::~Actors()
@ -1139,6 +1140,23 @@ namespace MWMechanics
clear();
}
float Actors::getProcessingRange() const
{
return mActorsProcessingRange;
}
void Actors::updateProcessingRange()
{
// We have to cap it since using high values (larger than 7168) will make some quests harder or impossible to complete (bug #1876)
static const float maxProcessingRange = 7168.f;
static const float minProcessingRange = maxProcessingRange / 2.f;
float actorsProcessingRange = Settings::Manager::getFloat("actors processing range", "Game");
actorsProcessingRange = std::min(actorsProcessingRange, maxProcessingRange);
actorsProcessingRange = std::max(actorsProcessingRange, minProcessingRange);
mActorsProcessingRange = actorsProcessingRange;
}
void Actors::addActor (const MWWorld::Ptr& ptr, bool updateImmediately)
{
removeActor(ptr);
@ -1184,7 +1202,7 @@ namespace MWMechanics
// Otherwise check if any actor in AI processing range sees the target actor
std::vector<MWWorld::Ptr> actors;
osg::Vec3f position (actor.getRefData().getPosition().asVec3());
getObjectsInRange(position, aiProcessingDistance, actors);
getObjectsInRange(position, mActorsProcessingRange, actors);
for(std::vector<MWWorld::Ptr>::iterator it = actors.begin(); it != actors.end(); ++it)
{
if (*it == actor)
@ -1242,7 +1260,7 @@ namespace MWMechanics
{
if (iter->first == player) continue;
bool inProcessingRange = (playerPos - iter->first.getRefData().getPosition().asVec3()).length2() <= sqrAiProcessingDistance;
bool inProcessingRange = (playerPos - iter->first.getRefData().getPosition().asVec3()).length2() <= mActorsProcessingRange*mActorsProcessingRange;
if (inProcessingRange)
{
MWMechanics::CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first);
@ -1288,7 +1306,8 @@ namespace MWMechanics
if (timerUpdateEquippedLight >= updateEquippedLightInterval) timerUpdateEquippedLight = 0;
// show torches only when there are darkness and no precipitations
bool showTorches = MWBase::Environment::get().getWorld()->useTorches();
MWBase::World* world = MWBase::Environment::get().getWorld();
bool showTorches = world->useTorches();
MWWorld::Ptr player = getPlayer();
const osg::Vec3f playerPos = player.getRefData().getPosition().asVec3();
@ -1301,7 +1320,7 @@ namespace MWMechanics
int attackedByPlayerId = player.getClass().getCreatureStats(player).getHitAttemptActorId();
if (attackedByPlayerId != -1)
{
const MWWorld::Ptr playerHitAttemptActor = MWBase::Environment::get().getWorld()->searchPtrViaActorId(attackedByPlayerId);
const MWWorld::Ptr playerHitAttemptActor = world->searchPtrViaActorId(attackedByPlayerId);
if (!playerHitAttemptActor.isInCell())
player.getClass().getCreatureStats(player).setHitAttemptActorId(-1);
@ -1314,14 +1333,11 @@ namespace MWMechanics
CharacterController* ctrl = iter->second->getCharacterController();
float distSqr = (playerPos - iter->first.getRefData().getPosition().asVec3()).length2();
// AI processing is only done within distance of 7168 units to the player. Note the "AI distance" slider doesn't affect this
// (it only does some throttling for targets beyond the "AI distance", so doesn't give any guarantees as to whether AI will be enabled or not)
// This distance could be made configurable later, but the setting must be marked with a big warning:
// using higher values will make a quest in Bloodmoon harder or impossible to complete (bug #1876)
bool inProcessingRange = distSqr <= sqrAiProcessingDistance;
// AI processing is only done within given distance to the player.
bool inProcessingRange = distSqr <= mActorsProcessingRange*mActorsProcessingRange;
if (isPlayer)
ctrl->setAttackingOrSpell(MWBase::Environment::get().getWorld()->getPlayer().getAttackingOrSpell());
ctrl->setAttackingOrSpell(world->getPlayer().getAttackingOrSpell());
// If dead or no longer in combat, no longer store any actors who attempted to hit us. Also remove for the player.
if (iter->first != player && (iter->first.getClass().getCreatureStats(iter->first).isDead()
@ -1335,10 +1351,10 @@ namespace MWMechanics
if (!iter->first.getClass().getCreatureStats(iter->first).isDead())
{
bool cellChanged = MWBase::Environment::get().getWorld()->hasCellChanged();
bool cellChanged = world->hasCellChanged();
MWWorld::Ptr actor = iter->first; // make a copy of the map key to avoid it being invalidated when the player teleports
updateActor(actor, duration);
if (!cellChanged && MWBase::Environment::get().getWorld()->hasCellChanged())
if (!cellChanged && world->hasCellChanged())
{
return; // for now abort update of the old cell when cell changes by teleportation magic effect
// a better solution might be to apply cell changes at the end of the frame
@ -1363,7 +1379,7 @@ namespace MWMechanics
MWWorld::Ptr headTrackTarget;
MWMechanics::CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first);
bool firstPersonPlayer = isPlayer && MWBase::Environment::get().getWorld()->isFirstPerson();
bool firstPersonPlayer = isPlayer && world->isFirstPerson();
// 1. Unconsious actor can not track target
// 2. Actors in combat and pursue mode do not bother to headtrack
@ -1423,27 +1439,25 @@ namespace MWMechanics
CharacterController* playerCharacter = nullptr;
for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter)
{
const float animationDistance = aiProcessingDistance + 400; // Slightly larger than AI distance so there is time to switch back to the idle animation.
const float distSqr = (playerPos - iter->first.getRefData().getPosition().asVec3()).length2();
const float dist = (playerPos - iter->first.getRefData().getPosition().asVec3()).length();
bool isPlayer = iter->first == player;
bool inAnimationRange = isPlayer || (animationDistance == 0 || distSqr <= animationDistance*animationDistance);
bool inRange = isPlayer || dist <= mActorsProcessingRange;
int activeFlag = 1; // Can be changed back to '2' to keep updating bounding boxes off screen (more accurate, but slower)
if (isPlayer)
activeFlag = 2;
int active = inAnimationRange ? activeFlag : 0;
bool canFly = iter->first.getClass().canFly(iter->first);
if (canFly)
{
// Keep animating flying creatures so they don't just hover in-air
inAnimationRange = true;
active = std::max(1, active);
}
int active = inRange ? activeFlag : 0;
CharacterController* ctrl = iter->second->getCharacterController();
ctrl->setActive(active);
if (!inAnimationRange)
if (!inRange)
{
iter->first.getRefData().getBaseNode()->setNodeMask(0);
world->setActorCollisionMode(iter->first, false);
continue;
}
else if (!isPlayer)
iter->first.getRefData().getBaseNode()->setNodeMask(1<<3);
if (iter->first.getClass().getCreatureStats(iter->first).isParalyzed())
ctrl->skipAnim();
@ -1455,11 +1469,28 @@ namespace MWMechanics
playerCharacter = ctrl;
continue;
}
world->setActorCollisionMode(iter->first, true);
ctrl->update(duration);
// Fade away actors on large distance (>90% of actor's processing distance)
float visibilityRatio = 1.0;
float fadeStartDistance = mActorsProcessingRange*0.9f;
float fadeEndDistance = mActorsProcessingRange;
float fadeRatio = (dist - fadeStartDistance)/(fadeEndDistance - fadeStartDistance);
if (fadeRatio > 0)
visibilityRatio -= std::max(0.f, fadeRatio);
visibilityRatio = std::min(1.f, visibilityRatio);
ctrl->setVisibility(visibilityRatio);
}
if (playerCharacter)
{
playerCharacter->update(duration);
playerCharacter->setVisibility(1.f);
}
for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter)
{
@ -1572,6 +1603,7 @@ namespace MWMechanics
continue;
}
MWBase::Environment::get().getWorld()->removeActorPath(iter->first);
CharacterController::KillResult killResult = iter->second->getCharacterController()->kill();
if (killResult == CharacterController::Result_DeathAnimStarted)
{
@ -1671,7 +1703,7 @@ namespace MWMechanics
restoreDynamicStats(iter->first, sleep);
if ((!iter->first.getRefData().getBaseNode()) ||
(playerPos - iter->first.getRefData().getPosition().asVec3()).length2() > sqrAiProcessingDistance)
(playerPos - iter->first.getRefData().getPosition().asVec3()).length2() > mActorsProcessingRange*mActorsProcessingRange)
continue;
adjustMagicEffects (iter->first);
@ -1915,7 +1947,7 @@ namespace MWMechanics
std::list<MWWorld::Ptr> list;
std::vector<MWWorld::Ptr> neighbors;
osg::Vec3f position (actor.getRefData().getPosition().asVec3());
getObjectsInRange(position, aiProcessingDistance, neighbors);
getObjectsInRange(position, mActorsProcessingRange, neighbors);
for(auto neighbor = neighbors.begin(); neighbor != neighbors.end(); ++neighbor)
{
if (*neighbor == actor)
@ -1936,7 +1968,7 @@ namespace MWMechanics
std::list<MWWorld::Ptr> list;
std::vector<MWWorld::Ptr> neighbors;
osg::Vec3f position (actor.getRefData().getPosition().asVec3());
getObjectsInRange(position, aiProcessingDistance, neighbors);
getObjectsInRange(position, mActorsProcessingRange, neighbors);
std::set<MWWorld::Ptr> followers;
getActorsFollowing(actor, followers);

View file

@ -65,6 +65,9 @@ namespace MWMechanics
/// paused we may want to do it manually (after equipping permanent enchantment)
void updateMagicEffects (const MWWorld::Ptr& ptr);
void updateProcessingRange();
float getProcessingRange() const;
void addActor (const MWWorld::Ptr& ptr, bool updateImmediately=false);
///< Register an actor for stats management
///
@ -168,6 +171,7 @@ namespace MWMechanics
private:
PtrActorMap mActors;
float mTimerDisposeSummonsCorpses;
float mActorsProcessingRange;
};
}

View file

@ -29,14 +29,13 @@ namespace MWMechanics
actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing);
if (target == MWWorld::Ptr() ||
!target.getRefData().getCount() || !target.getRefData().isEnabled() // Really we should check whether the target is currently registered
// with the MechanicsManager
)
return true; //Target doesn't exist
// Stop if the target doesn't exist
// Really we should be checking whether the target is currently registered with the MechanicsManager
if (target == MWWorld::Ptr() || !target.getRefData().getCount() || !target.getRefData().isEnabled())
return true;
//Set the target destination for the actor
ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos;
const osg::Vec3f dest = target.getRefData().getPosition().asVec3();
if (pathTo(actor, dest, duration, MWBase::Environment::get().getWorld()->getMaxActivationDistance())) //Stop when you get in activation range
{

View file

@ -26,9 +26,9 @@ bool MWMechanics::AiBreathe::execute (const MWWorld::Ptr& actor, CharacterContro
{
if (actorClass.getNpcStats(actor).getTimeToStartDrowning() < fHoldBreathTime / 2)
{
actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true);
actorClass.getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true);
actor.getClass().getMovementSettings(actor).mPosition[1] = 1;
actorClass.getMovementSettings(actor).mPosition[1] = 1;
smoothTurn(actor, -180, 0);
return false;

View file

@ -37,7 +37,7 @@ bool MWMechanics::AiCast::execute(const MWWorld::Ptr& actor, MWMechanics::Charac
if (!target)
return true;
if (!mManual && !pathTo(actor, target.getRefData().getPosition().pos, duration, mDistance))
if (!mManual && !pathTo(actor, target.getRefData().getPosition().asVec3(), duration, mDistance))
{
return false;
}

View file

@ -128,7 +128,7 @@ namespace MWMechanics
float targetReachedTolerance = 0.0f;
if (storage.mLOS)
targetReachedTolerance = storage.mAttackRange;
bool is_target_reached = pathTo(actor, target.getRefData().getPosition().pos, duration, targetReachedTolerance);
const bool is_target_reached = pathTo(actor, target.getRefData().getPosition().asVec3(), duration, targetReachedTolerance);
if (is_target_reached) storage.mReadyToAttack = true;
}
@ -307,7 +307,7 @@ namespace MWMechanics
osg::Vec3f localPos = actor.getRefData().getPosition().asVec3();
coords.toLocal(localPos);
int closestPointIndex = PathFinder::GetClosestPoint(pathgrid, localPos);
int closestPointIndex = PathFinder::getClosestPoint(pathgrid, localPos);
for (int i = 0; i < static_cast<int>(pathgrid->mPoints.size()); i++)
{
if (i != closestPointIndex && getPathGridGraph(storage.mCell).isPointConnected(closestPointIndex, i))
@ -359,7 +359,7 @@ namespace MWMechanics
float dist = (actor.getRefData().getPosition().asVec3() - target.getRefData().getPosition().asVec3()).length();
if ((dist > fFleeDistance && !storage.mLOS)
|| pathTo(actor, storage.mFleeDest, duration))
|| pathTo(actor, PathFinder::makeOsgVec3(storage.mFleeDest), duration))
{
state = AiCombatStorage::FleeState_Idle;
}

View file

@ -83,24 +83,13 @@ namespace MWMechanics
actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, false);
const MWWorld::Ptr follower = MWBase::Environment::get().getWorld()->getPtr(mTargetActorRefId, false);
const float* const leaderPos = actor.getRefData().getPosition().pos;
const float* const followerPos = follower.getRefData().getPosition().pos;
double differenceBetween[3];
const osg::Vec3f leaderPos = actor.getRefData().getPosition().asVec3();
const osg::Vec3f followerPos = follower.getRefData().getPosition().asVec3();
for (short counter = 0; counter < 3; counter++)
differenceBetween[counter] = (leaderPos[counter] - followerPos[counter]);
double distanceBetweenResult =
(differenceBetween[0] * differenceBetween[0]) + (differenceBetween[1] * differenceBetween[1]) + (differenceBetween[2] *
differenceBetween[2]);
if (distanceBetweenResult <= mMaxDist * mMaxDist)
if ((leaderPos - followerPos).length2() <= mMaxDist * mMaxDist)
{
ESM::Pathgrid::Point point(static_cast<int>(mX), static_cast<int>(mY), static_cast<int>(mZ));
point.mAutogenerated = 0;
point.mConnectionNum = 0;
point.mUnknown = 0;
if (pathTo(actor,point,duration)) //Returns true on path complete
const osg::Vec3f dest(mX, mY, mZ);
if (pathTo(actor, dest, duration)) //Returns true on path complete
{
mRemainingDuration = mDuration;
return true;

View file

@ -74,12 +74,12 @@ AiFollow::AiFollow(const ESM::AiSequence::AiFollow *follow)
bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration)
{
MWWorld::Ptr target = getTarget();
const MWWorld::Ptr target = getTarget();
if (target.isEmpty() || !target.getRefData().getCount() || !target.getRefData().isEnabled() // Really we should be checking whether the target is currently registered
// with the MechanicsManager
)
return false; // Target is not here right now, wait for it to return
// Target is not here right now, wait for it to return
// Really we should be checking whether the target is currently registered with the MechanicsManager
if (target == MWWorld::Ptr() || !target.getRefData().getCount() || !target.getRefData().isEnabled())
return false;
actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing);
@ -94,6 +94,10 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte
return false;
}
const osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3());
const osg::Vec3f targetPos(target.getRefData().getPosition().asVec3());
const osg::Vec3f targetDir = targetPos - actorPos;
// AiFollow requires the target to be in range and within sight for the initial activation
if (!mActive)
{
@ -101,9 +105,7 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte
if (storage.mTimer < 0)
{
if ((actor.getRefData().getPosition().asVec3() - target.getRefData().getPosition().asVec3()).length2()
< 500*500
&& MWBase::Environment::get().getWorld()->getLOS(actor, target))
if (targetDir.length2() < 500*500 && MWBase::Environment::get().getWorld()->getLOS(actor, target))
mActive = true;
storage.mTimer = 0.5f;
}
@ -111,8 +113,6 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte
if (!mActive)
return false;
ESM::Position pos = actor.getRefData().getPosition(); //position of the actor
// The distances below are approximations based on observations of the original engine.
// If only one actor is following the target, it uses 186.
// If there are multiple actors following the same target, they form a group with each group member at 313 + (130 * i) distance to the target.
@ -145,9 +145,8 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte
}
}
if ((pos.pos[0]-mX)*(pos.pos[0]-mX) +
(pos.pos[1]-mY)*(pos.pos[1]-mY) +
(pos.pos[2]-mZ)*(pos.pos[2]-mZ) < followDistance*followDistance) //Close-ish to final position
osg::Vec3f finalPos(mX, mY, mZ);
if ((actorPos-finalPos).length2() < followDistance*followDistance) //Close-ish to final position
{
if (actor.getCell()->isExterior()) //Outside?
{
@ -162,8 +161,6 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte
}
}
//Set the target destination from the actor
ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos;
short baseFollowDistance = followDistance;
short threshold = 30; // to avoid constant switching between moving/stopping
@ -172,15 +169,9 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte
else
followDistance += threshold;
osg::Vec3f targetPos(target.getRefData().getPosition().asVec3());
osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3());
osg::Vec3f dir = targetPos - actorPos;
float targetDistSqr = dir.length2();
if (targetDistSqr <= followDistance * followDistance)
if (targetDir.length2() <= followDistance * followDistance)
{
float faceAngleRadians = std::atan2(dir.x(), dir.y());
float faceAngleRadians = std::atan2(targetDir.x(), targetDir.y());
if (!zTurn(actor, faceAngleRadians, osg::DegreesToRadians(45.f)))
{
@ -191,16 +182,14 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte
return false;
}
storage.mMoving = !pathTo(actor, dest, duration, baseFollowDistance); // Go to the destination
storage.mMoving = !pathTo(actor, targetPos, duration, baseFollowDistance); // Go to the destination
if (storage.mMoving)
{
//Check if you're far away
float dist = distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]);
if (dist > 450)
if (targetDir.length2() > 450 * 450)
actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, true); //Make NPC run
else if (dist < 325) //Have a bit of a dead zone, otherwise npc will constantly flip between running and not when right on the edge of the running threshold
else if (targetDir.length2() < 325 * 325) //Have a bit of a dead zone, otherwise npc will constantly flip between running and not when right on the edge of the running threshold
actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, false); //make NPC walk
}

View file

@ -5,6 +5,7 @@
#include <components/esm/loadcell.hpp>
#include <components/esm/loadland.hpp>
#include <components/esm/loadmgef.hpp>
#include <components/detournavigator/navigator.hpp>
#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
@ -25,7 +26,7 @@
MWMechanics::AiPackage::~AiPackage() {}
MWMechanics::AiPackage::AiPackage() :
MWMechanics::AiPackage::AiPackage() :
mTimer(AI_REACTION_TIME + 1.0f), // to force initial pathbuild
mTargetActorRefId(""),
mTargetActorId(-1),
@ -90,70 +91,65 @@ void MWMechanics::AiPackage::reset()
mTimer = AI_REACTION_TIME + 1.0f;
mIsShortcutting = false;
mShortcutProhibited = false;
mShortcutFailPos = ESM::Pathgrid::Point();
mShortcutFailPos = osg::Vec3f();
mPathFinder.clearPath();
mObstacleCheck.clear();
}
bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgrid::Point& 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
ESM::Position pos = actor.getRefData().getPosition(); //position of the actor
const osg::Vec3f position = actor.getRefData().getPosition().asVec3(); //position of the actor
MWBase::World* world = MWBase::Environment::get().getWorld();
const osg::Vec3f halfExtents = world->getHalfExtents(actor);
/// Stops the actor when it gets too close to a unloaded cell
//... At current time, this test is unnecessary. AI shuts down when actor is more than 7168
//... At current time, this test is unnecessary. AI shuts down when actor is more than "actors processing range" setting value
//... units from player, and exterior cells are 8192 units long and wide.
//... But AI processing distance may increase in the future.
if (isNearInactiveCell(pos))
if (isNearInactiveCell(position))
{
actor.getClass().getMovementSettings(actor).mPosition[1] = 0;
world->updateActorPath(actor, mPathFinder.getPath(), halfExtents, position, dest);
return false;
}
// handle path building and shortcutting
ESM::Pathgrid::Point start = pos.pos;
float distToTarget = distance(start, dest);
bool isDestReached = (distToTarget <= destTolerance);
const float distToTarget = distance(position, dest);
const bool isDestReached = (distToTarget <= destTolerance);
if (!isDestReached && mTimer > AI_REACTION_TIME)
{
if (actor.getClass().isBipedal(actor))
openDoors(actor);
bool wasShortcutting = mIsShortcutting;
const bool wasShortcutting = mIsShortcutting;
bool destInLOS = false;
const MWWorld::Class& actorClass = actor.getClass();
MWBase::World* world = MWBase::Environment::get().getWorld();
// check if actor can move along z-axis
bool actorCanMoveByZ = (actorClass.canSwim(actor) && MWBase::Environment::get().getWorld()->isSwimming(actor))
|| world->isFlying(actor);
const bool actorCanMoveByZ = canActorMoveByZAxis(actor);
// Prohibit shortcuts for AiWander, if the actor can not move in 3 dimensions.
if (actorCanMoveByZ || getTypeId() != TypeIdWander)
mIsShortcutting = shortcutPath(start, dest, actor, &destInLOS, actorCanMoveByZ); // try to shortcut first
mIsShortcutting = actorCanMoveByZ
&& shortcutPath(position, dest, actor, &destInLOS, actorCanMoveByZ); // try to shortcut first
if (!mIsShortcutting)
{
if (wasShortcutting || doesPathNeedRecalc(dest, actor.getCell())) // if need to rebuild path
{
mPathFinder.buildSyncedPath(start, dest, actor.getCell(), getPathGridGraph(actor.getCell()));
const osg::Vec3f playerHalfExtents = world->getHalfExtents(getPlayer()); // Using player half extents for better performance
mPathFinder.buildPath(actor, position, dest, actor.getCell(), getPathGridGraph(actor.getCell()),
playerHalfExtents, getNavigatorFlags(actor));
mRotateOnTheRunChecks = 3;
// give priority to go directly on target if there is minimal opportunity
if (destInLOS && mPathFinder.getPath().size() > 1)
{
// get point just before dest
std::list<ESM::Pathgrid::Point>::const_iterator pPointBeforeDest = mPathFinder.getPath().end();
--pPointBeforeDest;
--pPointBeforeDest;
auto pPointBeforeDest = mPathFinder.getPath().rbegin() + 1;
// if start point is closer to the target then last point of path (excluding target itself) then go straight on the target
if (distance(start, dest) <= distance(dest, *pPointBeforeDest))
if (distance(position, dest) <= distance(dest, *pPointBeforeDest))
{
mPathFinder.clearPath();
mPathFinder.addPointToPath(dest);
@ -163,7 +159,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgr
if (!mPathFinder.getPath().empty()) //Path has points in it
{
ESM::Pathgrid::Point lastPos = mPathFinder.getPath().back(); //Get the end of the proposed path
const osg::Vec3f& lastPos = mPathFinder.getPath().back(); //Get the end of the proposed path
if(distance(dest, lastPos) > 100) //End of the path is far from the destination
mPathFinder.addPointToPath(dest); //Adds the final destination to the path, to try to get to where you want to go
@ -173,41 +169,44 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgr
mTimer = 0;
}
if (isDestReached || mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1])) // if path is finished
const float pointTolerance = std::min(actor.getClass().getSpeed(actor), DEFAULT_TOLERANCE);
mPathFinder.update(position, pointTolerance, DEFAULT_TOLERANCE);
if (isDestReached || mPathFinder.checkPathCompleted()) // if path is finished
{
// turn to destination point
zTurn(actor, getZAngleToPoint(start, dest));
smoothTurn(actor, getXAngleToPoint(start, dest), 0);
zTurn(actor, getZAngleToPoint(position, dest));
smoothTurn(actor, getXAngleToPoint(position, dest), 0);
world->removeActorPath(actor);
return true;
}
else
{
if (mRotateOnTheRunChecks == 0
|| isReachableRotatingOnTheRun(actor, *mPathFinder.getPath().begin())) // to prevent circling around a path point
{
actor.getClass().getMovementSettings(actor).mPosition[1] = 1; // move to the target
if (mRotateOnTheRunChecks > 0) mRotateOnTheRunChecks--;
}
// handle obstacles on the way
evadeObstacles(actor, duration, pos);
world->updateActorPath(actor, mPathFinder.getPath(), halfExtents, position, dest);
if (mRotateOnTheRunChecks == 0
|| isReachableRotatingOnTheRun(actor, *mPathFinder.getPath().begin())) // to prevent circling around a path point
{
actor.getClass().getMovementSettings(actor).mPosition[1] = 1; // move to the target
if (mRotateOnTheRunChecks > 0) mRotateOnTheRunChecks--;
}
// turn to next path point by X,Z axes
zTurn(actor, mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]));
smoothTurn(actor, mPathFinder.getXAngleToNext(pos.pos[0], pos.pos[1], pos.pos[2]), 0);
zTurn(actor, mPathFinder.getZAngleToNext(position.x(), position.y()));
smoothTurn(actor, mPathFinder.getXAngleToNext(position.x(), position.y(), position.z()), 0);
mObstacleCheck.update(actor, duration);
// handle obstacles on the way
evadeObstacles(actor);
return false;
}
void MWMechanics::AiPackage::evadeObstacles(const MWWorld::Ptr& actor, float duration, const ESM::Position& pos)
void MWMechanics::AiPackage::evadeObstacles(const MWWorld::Ptr& actor)
{
zTurn(actor, mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]));
MWMechanics::Movement& movement = actor.getClass().getMovementSettings(actor);
// check if stuck due to obstacles
if (!mObstacleCheck.check(actor, duration)) return;
if (!mObstacleCheck.isEvading()) return;
// first check if obstacle is a door
static float distance = MWBase::Environment::get().getWorld()->getMaxActivationDistance();
@ -219,13 +218,14 @@ void MWMechanics::AiPackage::evadeObstacles(const MWWorld::Ptr& actor, float dur
}
else
{
mObstacleCheck.takeEvasiveAction(movement);
mObstacleCheck.takeEvasiveAction(actor.getClass().getMovementSettings(actor));
}
}
void MWMechanics::AiPackage::openDoors(const MWWorld::Ptr& actor)
{
static float distance = MWBase::Environment::get().getWorld()->getMaxActivationDistance();
MWBase::World* world = MWBase::Environment::get().getWorld();
static float distance = world->getMaxActivationDistance();
const MWWorld::Ptr door = getNearbyDoor(actor, distance);
if (door == MWWorld::Ptr())
@ -236,7 +236,7 @@ void MWMechanics::AiPackage::openDoors(const MWWorld::Ptr& actor)
{
if ((door.getCellRef().getTrap().empty() && door.getCellRef().getLockLevel() <= 0 ))
{
MWBase::Environment::get().getWorld()->activate(door, actor);
world->activate(door, actor);
return;
}
@ -248,7 +248,7 @@ void MWMechanics::AiPackage::openDoors(const MWWorld::Ptr& actor)
MWWorld::Ptr keyPtr = invStore.search(keyId);
if (!keyPtr.isEmpty())
MWBase::Environment::get().getWorld()->activate(door, actor);
world->activate(door, actor);
}
}
@ -266,14 +266,15 @@ const MWMechanics::PathgridGraph& MWMechanics::AiPackage::getPathGridGraph(const
return *cache[id].get();
}
bool MWMechanics::AiPackage::shortcutPath(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor, bool *destInLOS, bool isPathClear)
bool MWMechanics::AiPackage::shortcutPath(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
const MWWorld::Ptr& actor, bool *destInLOS, bool isPathClear)
{
if (!mShortcutProhibited || (PathFinder::MakeOsgVec3(mShortcutFailPos) - PathFinder::MakeOsgVec3(startPoint)).length() >= PATHFIND_SHORTCUT_RETRY_DIST)
if (!mShortcutProhibited || (mShortcutFailPos - startPoint).length() >= PATHFIND_SHORTCUT_RETRY_DIST)
{
// check if target is clearly visible
isPathClear = !MWBase::Environment::get().getWorld()->castRay(
static_cast<float>(startPoint.mX), static_cast<float>(startPoint.mY), static_cast<float>(startPoint.mZ),
static_cast<float>(endPoint.mX), static_cast<float>(endPoint.mY), static_cast<float>(endPoint.mZ));
startPoint.x(), startPoint.y(), startPoint.z(),
endPoint.x(), endPoint.y(), endPoint.z());
if (destInLOS != nullptr) *destInLOS = isPathClear;
@ -294,46 +295,44 @@ bool MWMechanics::AiPackage::shortcutPath(const ESM::Pathgrid::Point& startPoint
return false;
}
bool MWMechanics::AiPackage::checkWayIsClearForActor(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor)
bool MWMechanics::AiPackage::checkWayIsClearForActor(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const MWWorld::Ptr& actor)
{
bool actorCanMoveByZ = (actor.getClass().canSwim(actor) && MWBase::Environment::get().getWorld()->isSwimming(actor))
|| MWBase::Environment::get().getWorld()->isFlying(actor);
if (actorCanMoveByZ)
if (canActorMoveByZAxis(actor))
return true;
float actorSpeed = actor.getClass().getSpeed(actor);
float maxAvoidDist = AI_REACTION_TIME * actorSpeed + actorSpeed / MAX_VEL_ANGULAR_RADIANS * 2; // *2 - for reliability
osg::Vec3f::value_type distToTarget = osg::Vec3f(static_cast<float>(endPoint.mX), static_cast<float>(endPoint.mY), 0).length();
const float actorSpeed = actor.getClass().getSpeed(actor);
const float maxAvoidDist = AI_REACTION_TIME * actorSpeed + actorSpeed / MAX_VEL_ANGULAR_RADIANS * 2; // *2 - for reliability
const float distToTarget = osg::Vec2f(endPoint.x(), endPoint.y()).length();
float offsetXY = distToTarget > maxAvoidDist*1.5? maxAvoidDist : maxAvoidDist/2;
bool isClear = checkWayIsClear(PathFinder::MakeOsgVec3(startPoint), PathFinder::MakeOsgVec3(endPoint), offsetXY);
const float offsetXY = distToTarget > maxAvoidDist*1.5? maxAvoidDist : maxAvoidDist/2;
// update shortcut prohibit state
if (isClear)
if (checkWayIsClear(startPoint, endPoint, offsetXY))
{
if (mShortcutProhibited)
{
mShortcutProhibited = false;
mShortcutFailPos = ESM::Pathgrid::Point();
mShortcutFailPos = osg::Vec3f();
}
return true;
}
if (!isClear)
else
{
if (mShortcutFailPos.mX == 0 && mShortcutFailPos.mY == 0 && mShortcutFailPos.mZ == 0)
if (mShortcutFailPos == osg::Vec3f())
{
mShortcutProhibited = true;
mShortcutFailPos = startPoint;
}
}
return isClear;
return false;
}
bool MWMechanics::AiPackage::doesPathNeedRecalc(const ESM::Pathgrid::Point& newDest, const MWWorld::CellStore* currentCell)
bool MWMechanics::AiPackage::doesPathNeedRecalc(const osg::Vec3f& newDest, const MWWorld::CellStore* currentCell)
{
return mPathFinder.getPath().empty() || (distance(mPathFinder.getPath().back(), newDest) > 10) || mPathFinder.getPathCell() != currentCell;
return mPathFinder.getPath().empty()
|| (distance(mPathFinder.getPath().back(), newDest) > 10)
|| mPathFinder.getPathCell() != currentCell;
}
bool MWMechanics::AiPackage::isTargetMagicallyHidden(const MWWorld::Ptr& target)
@ -343,23 +342,22 @@ bool MWMechanics::AiPackage::isTargetMagicallyHidden(const MWWorld::Ptr& target)
|| (magicEffects.get(ESM::MagicEffect::Chameleon).getMagnitude() > 75);
}
bool MWMechanics::AiPackage::isNearInactiveCell(const ESM::Position& actorPos)
bool MWMechanics::AiPackage::isNearInactiveCell(osg::Vec3f position)
{
const ESM::Cell* playerCell(getPlayer().getCell()->getCell());
if (playerCell->isExterior())
{
// get actor's distance from origin of center cell
osg::Vec3f actorOffset(actorPos.asVec3());
CoordinateConverter(playerCell).toLocal(actorOffset);
CoordinateConverter(playerCell).toLocal(position);
// currently assumes 3 x 3 grid for exterior cells, with player at center cell.
// ToDo: (Maybe) use "exterior cell load distance" setting to get count of actual active cells
// While AI Process distance is 7168, AI shuts down actors before they reach edges of 3 x 3 grid.
// AI shuts down actors before they reach edges of 3 x 3 grid.
const float distanceFromEdge = 200.0;
float minThreshold = (-1.0f * ESM::Land::REAL_SIZE) + distanceFromEdge;
float maxThreshold = (2.0f * ESM::Land::REAL_SIZE) - distanceFromEdge;
return (actorOffset[0] < minThreshold) || (maxThreshold < actorOffset[0])
|| (actorOffset[1] < minThreshold) || (maxThreshold < actorOffset[1]);
return (position.x() < minThreshold) || (maxThreshold < position.x())
|| (position.y() < minThreshold) || (maxThreshold < position.y());
}
else
{
@ -367,7 +365,7 @@ bool MWMechanics::AiPackage::isNearInactiveCell(const ESM::Position& actorPos)
}
}
bool MWMechanics::AiPackage::isReachableRotatingOnTheRun(const MWWorld::Ptr& actor, const ESM::Pathgrid::Point& dest)
bool MWMechanics::AiPackage::isReachableRotatingOnTheRun(const MWWorld::Ptr& actor, const osg::Vec3f& dest)
{
// get actor's shortest radius for moving in circle
float speed = actor.getClass().getSpeed(actor);
@ -381,17 +379,39 @@ bool MWMechanics::AiPackage::isReachableRotatingOnTheRun(const MWWorld::Ptr& act
osg::Vec3f radiusDir = dir ^ osg::Z_AXIS; // radius is perpendicular to a tangent
radiusDir.normalize();
radiusDir *= radius;
// pick up the nearest center candidate
osg::Vec3f dest_ = PathFinder::MakeOsgVec3(dest);
osg::Vec3f pos = actor.getRefData().getPosition().asVec3();
osg::Vec3f center1 = pos - radiusDir;
osg::Vec3f center2 = pos + radiusDir;
osg::Vec3f center = (center1 - dest_).length2() < (center2 - dest_).length2() ? center1 : center2;
osg::Vec3f center = (center1 - dest).length2() < (center2 - dest).length2() ? center1 : center2;
float distToDest = (center - dest_).length();
float distToDest = (center - dest).length();
// if pathpoint is reachable for the actor rotating on the run:
// no points of actor's circle should be farther from the center than destination point
return (radius <= distToDest);
}
DetourNavigator::Flags MWMechanics::AiPackage::getNavigatorFlags(const MWWorld::Ptr& actor) const
{
const MWWorld::Class& actorClass = actor.getClass();
DetourNavigator::Flags result = DetourNavigator::Flag_none;
if (actorClass.isPureWaterCreature(actor) || (getTypeId() != TypeIdWander && actorClass.canSwim(actor)))
result |= DetourNavigator::Flag_swim;
if (actorClass.canWalk(actor))
result |= DetourNavigator::Flag_walk;
if (actorClass.isBipedal(actor) && getTypeId() != TypeIdWander)
result |= DetourNavigator::Flag_openDoor;
return result;
}
bool MWMechanics::AiPackage::canActorMoveByZAxis(const MWWorld::Ptr& actor) const
{
MWBase::World* world = MWBase::Environment::get().getWorld();
return (actor.getClass().canSwim(actor) && world->isSwimming(actor)) || world->isFlying(actor);
}

View file

@ -7,6 +7,8 @@
#include "obstacle.hpp"
#include "aistate.hpp"
#include <boost/optional.hpp>
namespace MWWorld
{
class Ptr;
@ -105,29 +107,35 @@ namespace MWMechanics
bool isTargetMagicallyHidden(const MWWorld::Ptr& target);
/// Return if actor's rotation speed is sufficient to rotate to the destination pathpoint on the run. Otherwise actor should rotate while standing.
static bool isReachableRotatingOnTheRun(const MWWorld::Ptr& actor, const ESM::Pathgrid::Point& dest);
static bool isReachableRotatingOnTheRun(const MWWorld::Ptr& actor, const osg::Vec3f& dest);
protected:
/// Handles path building and shortcutting with obstacles avoiding
/** \return If the actor has arrived at his destination **/
bool pathTo(const MWWorld::Ptr& actor, const ESM::Pathgrid::Point& dest, float duration, float destTolerance = 0.0f);
bool pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& dest, float duration, float destTolerance = 0.0f);
/// Check if there aren't any obstacles along the path to make shortcut possible
/// If a shortcut is possible then path will be cleared and filled with the destination point.
/// \param destInLOS If not nullptr function will return ray cast check result
/// \return If can shortcut the path
bool shortcutPath(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor, bool *destInLOS, bool isPathClear);
bool shortcutPath(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const MWWorld::Ptr& actor,
bool *destInLOS, bool isPathClear);
/// Check if the way to the destination is clear, taking into account actor speed
bool checkWayIsClearForActor(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor);
bool checkWayIsClearForActor(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const MWWorld::Ptr& actor);
virtual bool doesPathNeedRecalc(const ESM::Pathgrid::Point& newDest, const MWWorld::CellStore* currentCell);
bool doesPathNeedRecalc(const osg::Vec3f& newDest, const MWWorld::CellStore* currentCell);
void evadeObstacles(const MWWorld::Ptr& actor);
void evadeObstacles(const MWWorld::Ptr& actor, float duration, const ESM::Position& pos);
void openDoors(const MWWorld::Ptr& actor);
const PathgridGraph& getPathGridGraph(const MWWorld::CellStore* cell);
DetourNavigator::Flags getNavigatorFlags(const MWWorld::Ptr& actor) const;
bool canActorMoveByZAxis(const MWWorld::Ptr& actor) const;
// TODO: all this does not belong here, move into temporary storage
PathFinder mPathFinder;
ObstacleCheck mObstacleCheck;
@ -137,16 +145,14 @@ namespace MWMechanics
std::string mTargetActorRefId;
mutable int mTargetActorId;
osg::Vec3f mLastActorPos;
short mRotateOnTheRunChecks; // attempts to check rotation to the pathpoint on the run possibility
bool mIsShortcutting; // if shortcutting at the moment
bool mShortcutProhibited; // shortcutting may be prohibited after unsuccessful attempt
ESM::Pathgrid::Point mShortcutFailPos; // position of last shortcut fail
osg::Vec3f mShortcutFailPos; // position of last shortcut fail
private:
bool isNearInactiveCell(const ESM::Position& actorPos);
bool isNearInactiveCell(osg::Vec3f position);
};
}

View file

@ -35,27 +35,27 @@ bool AiPursue::execute (const MWWorld::Ptr& actor, CharacterController& characte
const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); //The target to follow
if(target == MWWorld::Ptr() || !target.getRefData().getCount() || !target.getRefData().isEnabled() // Really we should be checking whether the target is currently registered
// with the MechanicsManager
)
return true; //Target doesn't exist
// Stop if the target doesn't exist
// Really we should be checking whether the target is currently registered with the MechanicsManager
if (target == MWWorld::Ptr() || !target.getRefData().getCount() || !target.getRefData().isEnabled())
return true;
if (isTargetMagicallyHidden(target))
return true;
if(target.getClass().getCreatureStats(target).isDead())
if (target.getClass().getCreatureStats(target).isDead())
return true;
actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing);
//Set the target desition from the actor
ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos;
ESM::Position aPos = actor.getRefData().getPosition();
//Set the target destination
const osg::Vec3f dest = target.getRefData().getPosition().asVec3();
const osg::Vec3f actorPos = actor.getRefData().getPosition().asVec3();
float pathTolerance = 100.0;
const float pathTolerance = 100.f;
if (pathTo(actor, dest, duration, pathTolerance) &&
std::abs(dest.mZ - aPos.pos[2]) < pathTolerance) // check the true distance in case the target is far away in Z-direction
std::abs(dest.z() - actorPos.z()) < pathTolerance) // check the true distance in case the target is far away in Z-direction
{
target.getClass().activate(target,actor).get()->execute(actor); //Arrest player when reached
return true;

View file

@ -3,8 +3,9 @@
#include <components/esm/aisequence.hpp>
#include <components/esm/loadcell.hpp>
#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/world.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/cellstore.hpp"
@ -20,8 +21,9 @@ bool isWithinMaxRange(const osg::Vec3f& pos1, const osg::Vec3f& pos2)
{
// Maximum travel distance for vanilla compatibility.
// Was likely meant to prevent NPCs walking into non-loaded exterior cells, but for some reason is used in interior cells as well.
// We can make this configurable at some point, but the default *must* be the below value. Anything else will break shoddily-written content (*cough* MW *cough*) in bizarre ways.
return (pos1 - pos2).length2() <= 7168*7168;
// The specific range below is configurable, but its limit is currently 7168 units. Anything greater will break shoddily-written content (*cough* MW *cough*) in bizarre ways.
float aiDistance = MWBase::Environment::get().getMechanicsManager()->getActorsProcessingRange();
return (pos1 - pos2).length2() <= aiDistance*aiDistance;
}
}
@ -45,19 +47,19 @@ namespace MWMechanics
bool AiTravel::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration)
{
ESM::Position pos = actor.getRefData().getPosition();
const osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3());
const osg::Vec3f targetPos(mX, mY, mZ);
actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, false);
actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing);
if (!isWithinMaxRange(osg::Vec3f(mX, mY, mZ), pos.asVec3()))
if (!isWithinMaxRange(targetPos, actorPos))
return false;
// Unfortunately, with vanilla assets destination is sometimes blocked by other actor.
// If we got close to target, check for actors nearby. If they are, finish AI package.
int destinationTolerance = 64;
ESM::Pathgrid::Point dest(static_cast<int>(mX), static_cast<int>(mY), static_cast<int>(mZ));
if (distance(pos.pos, dest) <= destinationTolerance)
if (distance(actorPos, targetPos) <= destinationTolerance)
{
std::vector<MWWorld::Ptr> targetActors;
std::pair<MWWorld::Ptr, osg::Vec3f> result = MWBase::Environment::get().getWorld()->getHitContact(actor, destinationTolerance, targetActors);
@ -69,7 +71,7 @@ namespace MWMechanics
}
}
if (pathTo(actor, dest, duration))
if (pathTo(actor, targetPos, duration))
{
actor.getClass().getMovementSettings(actor).mPosition[1] = 0;
return true;

View file

@ -50,7 +50,8 @@ namespace MWMechanics
AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector<unsigned char>& idle, bool repeat):
mDistance(distance), mDuration(duration), mRemainingDuration(duration), mTimeOfDay(timeOfDay), mIdle(idle),
mRepeat(repeat), mStoredInitialActorPosition(false), mInitialActorPosition(osg::Vec3f(0, 0, 0)), mHasDestination(false), mDestination(osg::Vec3f(0, 0, 0))
mRepeat(repeat), mStoredInitialActorPosition(false), mInitialActorPosition(osg::Vec3f(0, 0, 0)),
mHasDestination(false), mDestination(osg::Vec3f(0, 0, 0)), mUsePathgrid(false)
{
mIdle.resize(8, 0);
init();
@ -123,14 +124,13 @@ namespace MWMechanics
*/
bool AiWander::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration)
{
// get or create temporary storage
AiWanderStorage& storage = state.get<AiWanderStorage>();
const MWWorld::CellStore*& currentCell = storage.mCell;
MWMechanics::CreatureStats& cStats = actor.getClass().getCreatureStats(actor);
if(cStats.isDead() || cStats.getHealth().getCurrent() <= 0)
if (cStats.isDead() || cStats.getHealth().getCurrent() <= 0)
return true; // Don't bother with dead actors
// get or create temporary storage
AiWanderStorage& storage = state.get<AiWanderStorage>();
const MWWorld::CellStore*& currentCell = storage.mCell;
bool cellChange = currentCell && (actor.getCell() != currentCell);
if(!currentCell || cellChange)
{
@ -151,16 +151,23 @@ namespace MWMechanics
// rebuild a path to it
if (!mPathFinder.isPathConstructed() && mHasDestination)
{
ESM::Pathgrid::Point dest(PathFinder::MakePathgridPoint(mDestination));
ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(pos));
mPathFinder.buildSyncedPath(start, dest, actor.getCell(), getPathGridGraph(actor.getCell()));
if (mUsePathgrid)
{
mPathFinder.buildPathByPathgrid(pos.asVec3(), mDestination, actor.getCell(),
getPathGridGraph(actor.getCell()));
}
else
{
const osg::Vec3f playerHalfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(getPlayer()); // Using player half extents for better performance
mPathFinder.buildPath(actor, pos.asVec3(), mDestination, actor.getCell(),
getPathGridGraph(actor.getCell()), playerHalfExtents, getNavigatorFlags(actor));
}
if (mPathFinder.isPathConstructed())
storage.setState(AiWanderStorage::Wander_Walking);
}
doPerFrameActionsForState(actor, duration, storage, pos);
doPerFrameActionsForState(actor, duration, storage);
playIdleDialogueRandomly(actor);
@ -169,14 +176,14 @@ namespace MWMechanics
if (AI_REACTION_TIME <= lastReaction)
{
lastReaction = 0;
return reactionTimeActions(actor, storage, currentCell, cellChange, pos, duration);
return reactionTimeActions(actor, storage, currentCell, cellChange, pos);
}
else
return false;
}
bool AiWander::reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage,
const MWWorld::CellStore*& currentCell, bool cellChange, ESM::Position& pos, float duration)
const MWWorld::CellStore*& currentCell, bool cellChange, ESM::Position& pos)
{
if (mDistance <= 0)
storage.mCanWanderAlongPathGrid = false;
@ -226,7 +233,7 @@ namespace MWMechanics
}
// If Wandering manually and hit an obstacle, stop
if (storage.mIsWanderingManually && mObstacleCheck.check(actor, duration, 2.0f)) {
if (storage.mIsWanderingManually && mObstacleCheck.isEvading()) {
completeManualWalking(actor, storage);
}
@ -251,7 +258,9 @@ namespace MWMechanics
setPathToAnAllowedNode(actor, storage, pos);
}
}
} else if (storage.mIsWanderingManually && mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], DESTINATION_TOLERANCE)) {
}
else if (storage.mIsWanderingManually && mPathFinder.checkPathCompleted())
{
completeManualWalking(actor, storage);
}
@ -268,9 +277,7 @@ namespace MWMechanics
if (mHasDestination)
return mDestination;
const ESM::Pathgrid::Point currentPosition = actor.getRefData().getPosition().pos;
const osg::Vec3f currentPositionVec3f = osg::Vec3f(currentPosition.mX, currentPosition.mY, currentPosition.mZ);
return currentPositionVec3f;
return actor.getRefData().getPosition().asVec3();
}
bool AiWander::isPackageCompleted(const MWWorld::Ptr& actor, AiWanderStorage& storage)
@ -292,11 +299,9 @@ namespace MWMechanics
* Commands actor to walk to a random location near original spawn location.
*/
void AiWander::wanderNearStart(const MWWorld::Ptr &actor, AiWanderStorage &storage, int wanderDistance) {
const ESM::Pathgrid::Point currentPosition = actor.getRefData().getPosition().pos;
const osg::Vec3f currentPositionVec3f = osg::Vec3f(currentPosition.mX, currentPosition.mY, currentPosition.mZ);
const auto currentPosition = actor.getRefData().getPosition().asVec3();
std::size_t attempts = 10; // If a unit can't wander out of water, don't want to hang here
ESM::Pathgrid::Point destinationPosition;
bool isWaterCreature = actor.getClass().isPureWaterCreature(actor);
do {
// Determine a random location within radius of original position
@ -305,19 +310,23 @@ namespace MWMechanics
const float destinationX = mInitialActorPosition.x() + wanderRadius * std::cos(randomDirection);
const float destinationY = mInitialActorPosition.y() + wanderRadius * std::sin(randomDirection);
const float destinationZ = mInitialActorPosition.z();
destinationPosition = ESM::Pathgrid::Point(destinationX, destinationY, destinationZ);
mDestination = osg::Vec3f(destinationX, destinationY, destinationZ);
// Check if land creature will walk onto water or if water creature will swim onto land
if ((!isWaterCreature && !destinationIsAtWater(actor, mDestination)) ||
(isWaterCreature && !destinationThroughGround(currentPositionVec3f, mDestination))) {
mPathFinder.buildSyncedPath(currentPosition, destinationPosition, actor.getCell(), getPathGridGraph(actor.getCell()));
mPathFinder.addPointToPath(destinationPosition);
(isWaterCreature && !destinationThroughGround(currentPosition, mDestination)))
{
// Using player half extents for better performance
const osg::Vec3f playerHalfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(getPlayer());
mPathFinder.buildPath(actor, currentPosition, mDestination, actor.getCell(),
getPathGridGraph(actor.getCell()), playerHalfExtents, getNavigatorFlags(actor));
mPathFinder.addPointToPath(mDestination);
if (mPathFinder.isPathConstructed())
{
storage.setState(AiWanderStorage::Wander_Walking, true);
mHasDestination = true;
mUsePathgrid = false;
}
return;
}
@ -348,7 +357,7 @@ namespace MWMechanics
storage.setState(AiWanderStorage::Wander_IdleNow);
}
void AiWander::doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage, ESM::Position& pos)
void AiWander::doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage)
{
switch (storage.mState)
{
@ -357,7 +366,7 @@ namespace MWMechanics
break;
case AiWanderStorage::Wander_Walking:
onWalkingStatePerFrameActions(actor, duration, storage, pos);
onWalkingStatePerFrameActions(actor, duration, storage);
break;
case AiWanderStorage::Wander_ChooseAction:
@ -413,11 +422,10 @@ namespace MWMechanics
}
}
void AiWander::onWalkingStatePerFrameActions(const MWWorld::Ptr& actor,
float duration, AiWanderStorage& storage, ESM::Position& pos)
void AiWander::onWalkingStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage)
{
// Is there no destination or are we there yet?
if ((!mPathFinder.isPathConstructed()) || pathTo(actor, ESM::Pathgrid::Point(mPathFinder.getPath().back()), duration, DESTINATION_TOLERANCE))
if ((!mPathFinder.isPathConstructed()) || pathTo(actor, osg::Vec3f(mPathFinder.getPath().back()), duration, DESTINATION_TOLERANCE))
{
stopWalking(actor, storage);
storage.setState(AiWanderStorage::Wander_ChooseAction);
@ -425,7 +433,7 @@ namespace MWMechanics
else
{
// have not yet reached the destination
evadeObstacles(actor, storage, duration, pos);
evadeObstacles(actor, storage);
}
}
@ -456,7 +464,7 @@ namespace MWMechanics
storage.setState(AiWanderStorage::Wander_IdleNow);
}
void AiWander::evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage, float duration, ESM::Position& pos)
void AiWander::evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage)
{
if (mObstacleCheck.isEvading())
{
@ -507,9 +515,9 @@ namespace MWMechanics
// Only say Idle voices when player is in LOS
// A bit counterintuitive, likely vanilla did this to reduce the appearance of
// voices going through walls?
const ESM::Position& pos = actor.getRefData().getPosition();
if (roll < x && (player.getRefData().getPosition().asVec3() - pos.asVec3()).length2()
< 3000 * 3000 // maybe should be fAudioVoiceDefaultMaxDistance*fAudioMaxDistanceMult instead
const osg::Vec3f playerPos(player.getRefData().getPosition().asVec3());
const osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3());
if (roll < x && (playerPos - actorPos).length2() < 3000 * 3000 // maybe should be fAudioVoiceDefaultMaxDistance*fAudioMaxDistanceMult instead
&& MWBase::Environment::get().getWorld()->getLOS(player, actor))
MWBase::Environment::get().getDialogueManager()->say(actor, "idle");
}
@ -528,13 +536,12 @@ namespace MWMechanics
MWWorld::Ptr player = getPlayer();
osg::Vec3f playerPos(player.getRefData().getPosition().asVec3());
osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3());
float playerDistSqr = (playerPos - actorPos).length2();
int& greetingTimer = storage.mGreetingTimer;
AiWanderStorage::GreetingState& greetingState = storage.mSaidGreeting;
if (greetingState == AiWanderStorage::Greet_None)
{
if ((playerDistSqr <= helloDistance*helloDistance) &&
if ((playerPos - actorPos).length2() <= helloDistance*helloDistance &&
!player.getClass().getCreatureStats(player).isDead() && MWBase::Environment::get().getWorld()->getLOS(player, actor)
&& MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, actor))
greetingTimer++;
@ -570,7 +577,7 @@ namespace MWMechanics
if (greetingState == AiWanderStorage::Greet_Done)
{
float resetDist = 2 * helloDistance;
if (playerDistSqr >= resetDist*resetDist)
if ((playerPos - actorPos).length2() >= resetDist*resetDist)
greetingState = AiWanderStorage::Greet_None;
}
}
@ -592,15 +599,17 @@ namespace MWMechanics
ToWorldCoordinates(dest, storage.mCell->getCell());
// actor position is already in world coordinates
ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(actorPos));
const osg::Vec3f start = actorPos.asVec3();
// don't take shortcuts for wandering
mPathFinder.buildSyncedPath(start, dest, actor.getCell(), getPathGridGraph(actor.getCell()));
const osg::Vec3f destVec3f = PathFinder::makeOsgVec3(dest);
mPathFinder.buildPathByPathgrid(start, destVec3f, actor.getCell(), getPathGridGraph(actor.getCell()));
if (mPathFinder.isPathConstructed())
{
mDestination = osg::Vec3f(dest.mX, dest.mY, dest.mZ);
mDestination = destVec3f;
mHasDestination = true;
mUsePathgrid = true;
// Remove this node as an option and add back the previously used node (stops NPC from picking the same node):
ESM::Pathgrid::Point temp = storage.mAllowedNodes[randNode];
storage.mAllowedNodes.erase(storage.mAllowedNodes.begin() + randNode);
@ -631,15 +640,15 @@ namespace MWMechanics
// Every now and then check whether one of the doors is opened. (maybe
// at the end of playing idle?) If the door is opened then re-calculate
// allowed nodes starting from the spawn point.
std::list<ESM::Pathgrid::Point> paths = pathfinder.getPath();
auto paths = pathfinder.getPath();
while(paths.size() >= 2)
{
ESM::Pathgrid::Point pt = paths.back();
const auto pt = paths.back();
for(unsigned int j = 0; j < nodes.size(); j++)
{
// FIXME: doesn't handle a door with the same X/Y
// coordinates but with a different Z
if(nodes[j].mX == pt.mX && nodes[j].mY == pt.mY)
if (std::abs(nodes[j].mX - pt.x()) <= 0.5 && std::abs(nodes[j].mY - pt.y()) <= 0.5)
{
nodes.erase(nodes.begin() + j);
break;
@ -731,7 +740,7 @@ namespace MWMechanics
ESM::Pathgrid::Point worldDest = dest;
ToWorldCoordinates(worldDest, actor.getCell()->getCell());
bool isPathGridOccupied = MWBase::Environment::get().getMechanicsManager()->isAnyActorInRange(PathFinder::MakeOsgVec3(worldDest), 60);
bool isPathGridOccupied = MWBase::Environment::get().getMechanicsManager()->isAnyActorInRange(PathFinder::makeOsgVec3(worldDest), 60);
// add offset only if the selected pathgrid is occupied by another actor
if (isPathGridOccupied)
@ -752,18 +761,18 @@ namespace MWMechanics
ESM::Pathgrid::Point connDest = points[randomIndex];
// add an offset towards random neighboring node
osg::Vec3f dir = PathFinder::MakeOsgVec3(connDest) - PathFinder::MakeOsgVec3(dest);
osg::Vec3f dir = PathFinder::makeOsgVec3(connDest) - PathFinder::makeOsgVec3(dest);
float length = dir.length();
dir.normalize();
for (int j = 1; j <= 3; j++)
{
// move for 5-15% towards random neighboring node
dest = PathFinder::MakePathgridPoint(PathFinder::MakeOsgVec3(dest) + dir * (j * 5 * length / 100.f));
dest = PathFinder::makePathgridPoint(PathFinder::makeOsgVec3(dest) + dir * (j * 5 * length / 100.f));
worldDest = dest;
ToWorldCoordinates(worldDest, actor.getCell()->getCell());
isOccupied = MWBase::Environment::get().getMechanicsManager()->isAnyActorInRange(PathFinder::MakeOsgVec3(worldDest), 60);
isOccupied = MWBase::Environment::get().getMechanicsManager()->isAnyActorInRange(PathFinder::makeOsgVec3(worldDest), 60);
if (!isOccupied)
break;
@ -799,7 +808,7 @@ namespace MWMechanics
const ESM::Pathgrid *pathgrid =
MWBase::Environment::get().getWorld()->getStore().get<ESM::Pathgrid>().search(*currentCell->getCell());
int index = PathFinder::GetClosestPoint(pathgrid, PathFinder::MakeOsgVec3(dest));
int index = PathFinder::getClosestPoint(pathgrid, PathFinder::makeOsgVec3(dest));
getPathGridGraph(currentCell).getNeighbouringPoints(index, points);
}
@ -832,7 +841,7 @@ namespace MWMechanics
CoordinateConverter(cell).toLocal(npcPos);
// Find closest pathgrid point
int closestPointIndex = PathFinder::GetClosestPoint(pathgrid, npcPos);
int closestPointIndex = PathFinder::getClosestPoint(pathgrid, npcPos);
// mAllowedNodes for this actor with pathgrid point indexes based on mDistance
// and if the point is connected to the closest current point
@ -840,7 +849,7 @@ namespace MWMechanics
int pointIndex = 0;
for(unsigned int counter = 0; counter < pathgrid->mPoints.size(); counter++)
{
osg::Vec3f nodePos(PathFinder::MakeOsgVec3(pathgrid->mPoints[counter]));
osg::Vec3f nodePos(PathFinder::makeOsgVec3(pathgrid->mPoints[counter]));
if((npcPos - nodePos).length2() <= mDistance * mDistance &&
getPathGridGraph(cellStore).isPointConnected(closestPointIndex, counter))
{
@ -867,7 +876,7 @@ namespace MWMechanics
// 2. Partway along the path between the point and its connected points.
void AiWander::AddNonPathGridAllowedPoints(osg::Vec3f npcPos, const ESM::Pathgrid * pathGrid, int pointIndex, AiWanderStorage& storage)
{
storage.mAllowedNodes.push_back(PathFinder::MakePathgridPoint(npcPos));
storage.mAllowedNodes.push_back(PathFinder::makePathgridPoint(npcPos));
for (std::vector<ESM::Pathgrid::Edge>::const_iterator it = pathGrid->mEdges.begin(); it != pathGrid->mEdges.end(); ++it)
{
if (it->mV0 == pointIndex)
@ -879,8 +888,8 @@ namespace MWMechanics
void AiWander::AddPointBetweenPathGridPoints(const ESM::Pathgrid::Point& start, const ESM::Pathgrid::Point& end, AiWanderStorage& storage)
{
osg::Vec3f vectorStart = PathFinder::MakeOsgVec3(start);
osg::Vec3f delta = PathFinder::MakeOsgVec3(end) - vectorStart;
osg::Vec3f vectorStart = PathFinder::makeOsgVec3(start);
osg::Vec3f delta = PathFinder::makeOsgVec3(end) - vectorStart;
float length = delta.length();
delta.normalize();
@ -889,7 +898,7 @@ namespace MWMechanics
// must not travel longer than distance between waypoints or NPC goes past waypoint
distance = std::min(distance, static_cast<int>(length));
delta *= distance;
storage.mAllowedNodes.push_back(PathFinder::MakePathgridPoint(vectorStart + delta));
storage.mAllowedNodes.push_back(PathFinder::makePathgridPoint(vectorStart + delta));
}
void AiWander::SetCurrentNodeToClosestAllowedNode(const osg::Vec3f& npcPos, AiWanderStorage& storage)
@ -898,7 +907,7 @@ namespace MWMechanics
unsigned int index = 0;
for (unsigned int counterThree = 0; counterThree < storage.mAllowedNodes.size(); counterThree++)
{
osg::Vec3f nodePos(PathFinder::MakeOsgVec3(storage.mAllowedNodes[counterThree]));
osg::Vec3f nodePos(PathFinder::makeOsgVec3(storage.mAllowedNodes[counterThree]));
float tempDist = (npcPos - nodePos).length2();
if (tempDist < distanceToClosestNode)
{

View file

@ -137,15 +137,15 @@ namespace MWMechanics
short unsigned getRandomIdle();
void setPathToAnAllowedNode(const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos);
void playGreetingIfPlayerGetsTooClose(const MWWorld::Ptr& actor, AiWanderStorage& storage);
void evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage, float duration, ESM::Position& pos);
void evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage);
void playIdleDialogueRandomly(const MWWorld::Ptr& actor);
void turnActorToFacePlayer(const osg::Vec3f& actorPosition, const osg::Vec3f& playerPosition, AiWanderStorage& storage);
void doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage, ESM::Position& pos);
void doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage);
void onIdleStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage);
void onWalkingStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage, ESM::Position& pos);
void onWalkingStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage);
void onChooseActionStatePerFrameActions(const MWWorld::Ptr& actor, AiWanderStorage& storage);
bool reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage,
const MWWorld::CellStore*& currentCell, bool cellChange, ESM::Position& pos, float duration);
const MWWorld::CellStore*& currentCell, bool cellChange, ESM::Position& pos);
bool isPackageCompleted(const MWWorld::Ptr& actor, AiWanderStorage& storage);
void wanderNearStart(const MWWorld::Ptr &actor, AiWanderStorage &storage, int wanderDistance);
bool destinationIsAtWater(const MWWorld::Ptr &actor, const osg::Vec3f& destination);
@ -164,6 +164,7 @@ namespace MWMechanics
bool mHasDestination;
osg::Vec3f mDestination;
bool mUsePathgrid;
void getNeighbouringNodes(ESM::Pathgrid::Point dest, const MWWorld::CellStore* currentCell, ESM::Pathgrid::PointList& points);

View file

@ -30,6 +30,7 @@
#include "../mwrender/animation.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/world.hpp"
#include "../mwbase/soundmanager.hpp"
#include "../mwbase/windowmanager.hpp"
@ -1849,7 +1850,7 @@ void CharacterController::updateAnimQueue()
mAnimation->setLoopingEnabled(mAnimQueue.front().mGroup, mAnimQueue.size() <= 1);
}
void CharacterController::update(float duration)
void CharacterController::update(float duration, bool animationOnly)
{
MWBase::World *world = MWBase::Environment::get().getWorld();
const MWWorld::Class &cls = mPtr.getClass();
@ -2235,10 +2236,10 @@ void CharacterController::update(float duration)
world->rotateObject(mPtr, rot.x(), rot.y(), 0.0f, true);
}
if (!mMovementAnimationControlled)
if (!animationOnly && !mMovementAnimationControlled)
world->queueMovement(mPtr, vec);
}
else
else if (!animationOnly)
// We must always queue movement, even if there is none, to apply gravity.
world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f));
@ -2261,7 +2262,8 @@ void CharacterController::update(float duration)
playDeath(1.f, mDeathState);
}
// We must always queue movement, even if there is none, to apply gravity.
world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f));
if (!animationOnly)
world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f));
}
bool isPersist = isPersistentAnimPlaying();
@ -2295,7 +2297,7 @@ void CharacterController::update(float duration)
moved.z() = 1.0;
// Update movement
if(mMovementAnimationControlled && mPtr.getClass().isActor())
if(!animationOnly && mMovementAnimationControlled && mPtr.getClass().isActor())
world->queueMovement(mPtr, moved);
mSkipAnim = false;
@ -2521,6 +2523,7 @@ void CharacterController::resurrect()
mAnimation->disable(mCurrentDeath);
mCurrentDeath.clear();
mDeathState = CharState_None;
mWeaponType = WeapType_None;
}
void CharacterController::updateContinuousVfx()
@ -2544,20 +2547,6 @@ void CharacterController::updateMagicEffects()
{
if (!mPtr.getClass().isActor())
return;
float alpha = 1.f;
if (mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Invisibility).getModifier()) // Ignore base magnitude (see bug #3555).
{
if (mPtr == getPlayer())
alpha = 0.4f;
else
alpha = 0.f;
}
float chameleon = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Chameleon).getMagnitude();
if (chameleon)
{
alpha *= std::max(0.2f, (100.f - chameleon)/100.f);
}
mAnimation->setAlpha(alpha);
bool vampire = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Vampirism).getMagnitude() > 0.0f;
mAnimation->setVampire(vampire);
@ -2566,6 +2555,32 @@ void CharacterController::updateMagicEffects()
mAnimation->setLightEffect(light);
}
void CharacterController::setVisibility(float visibility)
{
// We should take actor's invisibility in account
if (mPtr.getClass().isActor())
{
float alpha = 1.f;
if (mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Invisibility).getModifier()) // Ignore base magnitude (see bug #3555).
{
if (mPtr == getPlayer())
alpha = 0.4f;
else
alpha = 0.f;
}
float chameleon = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Chameleon).getMagnitude();
if (chameleon)
{
alpha *= std::max(0.2f, (100.f - chameleon)/100.f);
}
visibility = std::min(visibility, alpha);
}
// TODO: implement a dithering shader rather than just change object transparency.
mAnimation->setAlpha(visibility);
}
void CharacterController::setAttackTypeBasedOnMovement()
{
float *move = mPtr.getClass().getMovementSettings(mPtr).mPosition;

View file

@ -257,7 +257,7 @@ public:
void updatePtr(const MWWorld::Ptr &ptr);
void update(float duration);
void update(float duration, bool animationOnly=false);
void persistAnimationState();
void unpersistAnimationState();
@ -292,6 +292,7 @@ public:
bool isTurning() const;
bool isAttackingOrSpell() const;
void setVisibility(float visibility);
void setAttackingOrSpell(bool attackingOrSpell);
void castSpell(const std::string spellId, bool manualSpell=false);
void setAIAttackType(const std::string& attackType);

View file

@ -371,11 +371,9 @@ namespace MWMechanics
return;
const bool weaphashealth = weapon.getClass().hasItemHealth(weapon);
if(weaphashealth)
if (weaphashealth)
{
int weaphealth = weapon.getClass().getItemHealth(weapon);
int weapmaxhealth = weapon.getClass().getItemMaxHealth(weapon);
damage *= (float(weaphealth) / weapmaxhealth);
damage *= weapon.getClass().getItemNormalizedHealth(weapon);
}
static const float fDamageStrengthBase = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>()

View file

@ -33,11 +33,12 @@ namespace MWMechanics
point.y() -= static_cast<float>(mCellY);
}
osg::Vec3f CoordinateConverter::toLocalVec3(const ESM::Pathgrid::Point& point)
osg::Vec3f CoordinateConverter::toLocalVec3(const osg::Vec3f& point)
{
return osg::Vec3f(
static_cast<float>(point.mX - mCellX),
static_cast<float>(point.mY - mCellY),
static_cast<float>(point.mZ));
point.x() - static_cast<float>(mCellX),
point.y() - static_cast<float>(mCellY),
point.z()
);
}
}

View file

@ -26,7 +26,7 @@ namespace MWMechanics
/// in-place conversion from world to local
void toLocal(osg::Vec3f& point);
osg::Vec3f toLocalVec3(const ESM::Pathgrid::Point& point);
osg::Vec3f toLocalVec3(const osg::Vec3f& point);
private:
int mCellX;

View file

@ -17,6 +17,7 @@
#include "../mwworld/ptr.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/statemanager.hpp"
#include "../mwbase/world.hpp"
#include "../mwbase/windowmanager.hpp"
#include "../mwbase/dialoguemanager.hpp"
@ -431,6 +432,29 @@ namespace MWMechanics
mObjects.update(duration, paused);
}
void MechanicsManager::processChangedSettings(const Settings::CategorySettingVector &changed)
{
for (Settings::CategorySettingVector::const_iterator it = changed.begin(); it != changed.end(); ++it)
{
if (it->first == "Game" && it->second == "actors processing range")
{
int state = MWBase::Environment::get().getStateManager()->getState();
if (state != MWBase::StateManager::State_Running)
continue;
mActors.updateProcessingRange();
// Update mechanics for new processing range immediately
update(0.f, false);
}
}
}
float MechanicsManager::getActorsProcessingRange() const
{
return mActors.getProcessingRange();
}
bool MechanicsManager::isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer)
{
return mActors.isActorDetected(actor, observer);

View file

@ -1,6 +1,8 @@
#ifndef GAME_MWMECHANICS_MECHANICSMANAGERIMP_H
#define GAME_MWMECHANICS_MECHANICSMANAGERIMP_H
#include <components/settings/settings.hpp>
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwworld/ptr.hpp"
@ -52,71 +54,71 @@ namespace MWMechanics
MechanicsManager();
virtual void add (const MWWorld::Ptr& ptr);
virtual void add (const MWWorld::Ptr& ptr) override;
///< Register an object for management
virtual void remove (const MWWorld::Ptr& ptr);
virtual void remove (const MWWorld::Ptr& ptr) override;
///< Deregister an object for management
virtual void updateCell(const MWWorld::Ptr &old, const MWWorld::Ptr &ptr);
virtual void updateCell(const MWWorld::Ptr &old, const MWWorld::Ptr &ptr) override;
///< Moves an object to a new cell
virtual void drop(const MWWorld::CellStore *cellStore);
virtual void drop(const MWWorld::CellStore *cellStore) override;
///< Deregister all objects in the given cell.
virtual void watchActor(const MWWorld::Ptr& ptr);
virtual void watchActor(const MWWorld::Ptr& ptr) override;
///< On each update look for changes in a previously registered actor and update the
/// GUI accordingly.
virtual void update (float duration, bool paused);
virtual void update (float duration, bool paused) override;
///< Update objects
///
/// \param paused In game type does not currently advance (this usually means some GUI
/// component is up).
virtual void advanceTime (float duration);
virtual void advanceTime (float duration) override;
virtual void setPlayerName (const std::string& name);
virtual void setPlayerName (const std::string& name) override;
///< Set player name.
virtual void setPlayerRace (const std::string& id, bool male, const std::string &head, const std::string &hair);
virtual void setPlayerRace (const std::string& id, bool male, const std::string &head, const std::string &hair) override;
///< Set player race.
virtual void setPlayerBirthsign (const std::string& id);
virtual void setPlayerBirthsign (const std::string& id) override;
///< Set player birthsign.
virtual void setPlayerClass (const std::string& id);
virtual void setPlayerClass (const std::string& id) override;
///< Set player class to stock class.
virtual void setPlayerClass (const ESM::Class& class_);
virtual void setPlayerClass (const ESM::Class& class_) override;
///< Set player class to custom class.
virtual void restoreDynamicStats(MWWorld::Ptr actor, bool sleep);
virtual void restoreDynamicStats(MWWorld::Ptr actor, bool sleep) override;
virtual void rest(bool sleep);
virtual void rest(bool sleep) override;
///< If the player is sleeping or waiting, this should be called every hour.
/// @param sleep is the player sleeping or waiting?
virtual int getHoursToRest() const;
virtual int getHoursToRest() const override;
///< Calculate how many hours the player needs to rest in order to be fully healed
virtual int getBarterOffer(const MWWorld::Ptr& ptr,int basePrice, bool buying);
virtual int getBarterOffer(const MWWorld::Ptr& ptr,int basePrice, bool buying) override;
///< This is used by every service to determine the price of objects given the trading skills of the player and NPC.
virtual int getDerivedDisposition(const MWWorld::Ptr& ptr, bool addTemporaryDispositionChange = true);
virtual int getDerivedDisposition(const MWWorld::Ptr& ptr, bool addTemporaryDispositionChange = true) override;
///< Calculate the diposition of an NPC toward the player.
virtual int countDeaths (const std::string& id) const;
virtual int countDeaths (const std::string& id) const override;
///< Return the number of deaths for actors with the given ID.
virtual void getPersuasionDispositionChange (const MWWorld::Ptr& npc, PersuasionType type, bool& success, float& tempChange, float& permChange);
virtual void getPersuasionDispositionChange (const MWWorld::Ptr& npc, PersuasionType type, bool& success, float& tempChange, float& permChange) override;
///< Perform a persuasion action on NPC
/// Check if \a observer is potentially aware of \a ptr. Does not do a line of sight check!
virtual bool awarenessCheck (const MWWorld::Ptr& ptr, const MWWorld::Ptr& observer);
virtual bool awarenessCheck (const MWWorld::Ptr& ptr, const MWWorld::Ptr& observer) override;
/// Makes \a ptr fight \a target. Also shouts a combat taunt.
virtual void startCombat (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target);
virtual void startCombat (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target) override;
/**
* @note victim may be empty
@ -126,122 +128,119 @@ namespace MWMechanics
* @return was the crime seen?
*/
virtual bool commitCrime (const MWWorld::Ptr& ptr, const MWWorld::Ptr& victim,
OffenseType type, int arg=0, bool victimAware=false);
OffenseType type, int arg=0, bool victimAware=false) override;
/// @return false if the attack was considered a "friendly hit" and forgiven
virtual bool actorAttacked (const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker);
virtual bool actorAttacked (const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker) override;
/// Notify that actor was killed, add a murder bounty if applicable
/// @note No-op for non-player attackers
virtual void actorKilled (const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker);
/// Checks if commiting a crime is currently valid
/// @param victim The actor being attacked
/// @param attacker The actor commiting the crime
/// @return true if the victim is a valid target for crime
virtual bool canCommitCrimeAgainst(const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker);
virtual void actorKilled (const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker) override;
/// Utility to check if taking this item is illegal and calling commitCrime if so
/// @param container The container the item is in; may be empty for an item in the world
virtual void itemTaken (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item, const MWWorld::Ptr& container,
int count, bool alarm = true);
int count, bool alarm = true) override;
/// Utility to check if opening (i.e. unlocking) this object is illegal and calling commitCrime if so
virtual void objectOpened (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item);
virtual void objectOpened (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item) override;
/// Attempt sleeping in a bed. If this is illegal, call commitCrime.
/// @return was it illegal, and someone saw you doing it? Also returns fail when enemies are nearby
virtual bool sleepInBed (const MWWorld::Ptr& ptr, const MWWorld::Ptr& bed);
virtual bool sleepInBed (const MWWorld::Ptr& ptr, const MWWorld::Ptr& bed) override;
virtual void forceStateUpdate(const MWWorld::Ptr &ptr);
virtual void forceStateUpdate(const MWWorld::Ptr &ptr) override;
/// Attempt to play an animation group
/// @return Success or error
virtual bool playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number, bool persist=false);
virtual void skipAnimation(const MWWorld::Ptr& ptr);
virtual bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string &groupName);
virtual void persistAnimationStates();
virtual bool playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number, bool persist=false) override;
virtual void skipAnimation(const MWWorld::Ptr& ptr) override;
virtual bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string &groupName) override;
virtual void persistAnimationStates() override;
/// Update magic effects for an actor. Usually done automatically once per frame, but if we're currently
/// paused we may want to do it manually (after equipping permanent enchantment)
virtual void updateMagicEffects (const MWWorld::Ptr& ptr);
virtual void updateMagicEffects (const MWWorld::Ptr& ptr) override;
virtual void getObjectsInRange (const osg::Vec3f& position, float radius, std::vector<MWWorld::Ptr>& objects);
virtual void getActorsInRange(const osg::Vec3f &position, float radius, std::vector<MWWorld::Ptr> &objects);
virtual void getObjectsInRange (const osg::Vec3f& position, float radius, std::vector<MWWorld::Ptr>& objects) override;
virtual void getActorsInRange(const osg::Vec3f &position, float radius, std::vector<MWWorld::Ptr> &objects) override;
/// Check if there are actors in selected range
virtual bool isAnyActorInRange(const osg::Vec3f &position, float radius);
virtual bool isAnyActorInRange(const osg::Vec3f &position, float radius) override;
virtual std::list<MWWorld::Ptr> getActorsSidingWith(const MWWorld::Ptr& actor);
virtual std::list<MWWorld::Ptr> getActorsFollowing(const MWWorld::Ptr& actor);
virtual std::list<int> getActorsFollowingIndices(const MWWorld::Ptr& actor);
virtual std::list<MWWorld::Ptr> getActorsSidingWith(const MWWorld::Ptr& actor) override;
virtual std::list<MWWorld::Ptr> getActorsFollowing(const MWWorld::Ptr& actor) override;
virtual std::list<int> getActorsFollowingIndices(const MWWorld::Ptr& actor) override;
virtual std::list<MWWorld::Ptr> getActorsFighting(const MWWorld::Ptr& actor);
virtual std::list<MWWorld::Ptr> getEnemiesNearby(const MWWorld::Ptr& actor);
virtual std::list<MWWorld::Ptr> getActorsFighting(const MWWorld::Ptr& actor) override;
virtual std::list<MWWorld::Ptr> getEnemiesNearby(const MWWorld::Ptr& actor) override;
/// Recursive version of getActorsFollowing
virtual void getActorsFollowing(const MWWorld::Ptr& actor, std::set<MWWorld::Ptr>& out);
virtual void getActorsFollowing(const MWWorld::Ptr& actor, std::set<MWWorld::Ptr>& out) override;
/// Recursive version of getActorsSidingWith
virtual void getActorsSidingWith(const MWWorld::Ptr& actor, std::set<MWWorld::Ptr>& out);
virtual void getActorsSidingWith(const MWWorld::Ptr& actor, std::set<MWWorld::Ptr>& out) override;
virtual bool toggleAI();
virtual bool isAIActive();
virtual bool toggleAI() override;
virtual bool isAIActive() override;
virtual void playerLoaded();
virtual void playerLoaded() override;
virtual int countSavedGameRecords() const;
virtual int countSavedGameRecords() const override;
virtual void write (ESM::ESMWriter& writer, Loading::Listener& listener) const;
virtual void write (ESM::ESMWriter& writer, Loading::Listener& listener) const override;
virtual void readRecord (ESM::ESMReader& reader, uint32_t type);
virtual void readRecord (ESM::ESMReader& reader, uint32_t type) override;
virtual void clear();
virtual void clear() override;
virtual bool isAggressive (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target);
virtual bool isAggressive (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target) override;
virtual void keepPlayerAlive();
virtual void keepPlayerAlive() override;
virtual bool isCastingSpell (const MWWorld::Ptr& ptr) const;
virtual bool isCastingSpell (const MWWorld::Ptr& ptr) const override;
virtual bool isReadyToBlock (const MWWorld::Ptr& ptr) const;
virtual bool isReadyToBlock (const MWWorld::Ptr& ptr) const override;
/// Is \a ptr casting spell or using weapon now?
virtual bool isAttackingOrSpell(const MWWorld::Ptr &ptr) const;
virtual bool isAttackingOrSpell(const MWWorld::Ptr &ptr) const override;
virtual void castSpell(const MWWorld::Ptr& ptr, const std::string spellId, bool manualSpell=false);
virtual void castSpell(const MWWorld::Ptr& ptr, const std::string spellId, bool manualSpell=false) override;
void processChangedSettings(const Settings::CategorySettingVector& settings) override;
virtual float getActorsProcessingRange() const override;
/// Check if the target actor was detected by an observer
/// If the observer is a non-NPC, check all actors in AI processing distance as observers
virtual bool isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer);
virtual bool isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer) override;
virtual void confiscateStolenItems (const MWWorld::Ptr& player, const MWWorld::Ptr& targetContainer);
virtual void confiscateStolenItems (const MWWorld::Ptr& player, const MWWorld::Ptr& targetContainer) override;
/// List the owners that the player has stolen this item from (the owner can be an NPC or a faction).
/// <Owner, item count>
virtual std::vector<std::pair<std::string, int> > getStolenItemOwners(const std::string& itemid);
virtual std::vector<std::pair<std::string, int> > getStolenItemOwners(const std::string& itemid) override;
/// Has the player stolen this item from the given owner?
virtual bool isItemStolenFrom(const std::string& itemid, const MWWorld::Ptr& ptr);
virtual bool isItemStolenFrom(const std::string& itemid, const MWWorld::Ptr& ptr) override;
virtual bool isBoundItem(const MWWorld::Ptr& item);
virtual bool isBoundItem(const MWWorld::Ptr& item) override;
/// @return is \a ptr allowed to take/use \a target or is it a crime?
virtual bool isAllowedToUse (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, MWWorld::Ptr& victim);
virtual bool isAllowedToUse (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, MWWorld::Ptr& victim) override;
virtual void setWerewolf(const MWWorld::Ptr& actor, bool werewolf);
virtual void applyWerewolfAcrobatics(const MWWorld::Ptr& actor);
virtual void setWerewolf(const MWWorld::Ptr& actor, bool werewolf) override;
virtual void applyWerewolfAcrobatics(const MWWorld::Ptr& actor) override;
virtual void cleanupSummonedCreature(const MWWorld::Ptr& caster, int creatureActorId);
virtual void cleanupSummonedCreature(const MWWorld::Ptr& caster, int creatureActorId) override;
virtual void confiscateStolenItemToOwner(const MWWorld::Ptr &player, const MWWorld::Ptr &item, const MWWorld::Ptr& victim, int count);
virtual void confiscateStolenItemToOwner(const MWWorld::Ptr &player, const MWWorld::Ptr &item, const MWWorld::Ptr& victim, int count) override;
virtual bool isAttackPreparing(const MWWorld::Ptr& ptr);
virtual bool isRunning(const MWWorld::Ptr& ptr);
virtual bool isSneaking(const MWWorld::Ptr& ptr);
virtual bool isAttackPreparing(const MWWorld::Ptr& ptr) override;
virtual bool isRunning(const MWWorld::Ptr& ptr) override;
virtual bool isSneaking(const MWWorld::Ptr& ptr) override;
private:
bool canCommitCrimeAgainst(const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker);
bool canReportCrime(const MWWorld::Ptr &actor, const MWWorld::Ptr &victim, std::set<MWWorld::Ptr> &playerFollowers);
bool reportCrime (const MWWorld::Ptr& ptr, const MWWorld::Ptr& victim,
OffenseType type, int arg=0);
};
}

View file

@ -96,11 +96,6 @@ namespace MWMechanics
mEvadeDuration = 0;
}
bool ObstacleCheck::isNormalState() const
{
return mWalkState == State_Norm;
}
bool ObstacleCheck::isEvading() const
{
return mWalkState == State_Evade;
@ -128,7 +123,7 @@ namespace MWMechanics
* u = how long to move sideways
*
*/
bool ObstacleCheck::check(const MWWorld::Ptr& actor, float duration, float scaleMinimumDistance)
void ObstacleCheck::update(const MWWorld::Ptr& actor, float duration, float scaleMinimumDistance)
{
const MWWorld::Class& cls = actor.getClass();
ESM::Position pos = actor.getRefData().getPosition();
@ -180,9 +175,7 @@ namespace MWMechanics
case State_Evade:
{
mEvadeDuration += duration;
if(mEvadeDuration < DURATION_TO_EVADE)
return true;
else
if(mEvadeDuration >= DURATION_TO_EVADE)
{
// tried to evade, assume all is ok and start again
mWalkState = State_Norm;
@ -191,10 +184,9 @@ namespace MWMechanics
}
/* NO DEFAULT CASE */
}
return false; // no obstacles to evade (yet)
}
void ObstacleCheck::takeEvasiveAction(MWMechanics::Movement& actorMovement)
void ObstacleCheck::takeEvasiveAction(MWMechanics::Movement& actorMovement) const
{
actorMovement.mPosition[0] = evadeDirections[mEvadeDirectionIndex][0];
actorMovement.mPosition[1] = evadeDirections[mEvadeDirectionIndex][1];

View file

@ -27,15 +27,13 @@ namespace MWMechanics
// Clear the timers and set the state machine to default
void clear();
bool isNormalState() const;
bool isEvading() const;
// Returns true if there is an obstacle and an evasive action
// should be taken
bool check(const MWWorld::Ptr& actor, float duration, float scaleMinimumDistance = 1.0f);
// Updates internal state, call each frame for moving actor
void update(const MWWorld::Ptr& actor, float duration, float scaleMinimumDistance = 1.0f);
// change direction to try to fix "stuck" actor
void takeEvasiveAction(MWMechanics::Movement& actorMovement);
void takeEvasiveAction(MWMechanics::Movement& actorMovement) const;
private:

View file

@ -1,13 +1,20 @@
#include "pathfinding.hpp"
#include <iterator>
#include <limits>
#include <components/detournavigator/exceptions.hpp>
#include <components/detournavigator/debug.hpp>
#include <components/detournavigator/navigator.hpp>
#include <components/debug/debuglog.hpp>
#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
#include "../mwphysics/collisiontype.hpp"
#include "../mwworld/cellstore.hpp"
#include "../mwworld/class.hpp"
#include "pathgrid.hpp"
#include "coordinateconverter.hpp"
@ -29,7 +36,7 @@ namespace
// points to a quadtree may help
for(unsigned int counter = 0; counter < grid->mPoints.size(); counter++)
{
float potentialDistBetween = MWMechanics::PathFinder::DistanceSquared(grid->mPoints[counter], pos);
float potentialDistBetween = MWMechanics::PathFinder::distanceSquared(grid->mPoints[counter], pos);
if (potentialDistBetween < closestDistanceReachable)
{
// found a closer one
@ -57,56 +64,19 @@ namespace
(closestReachableIndex, closestReachableIndex == closestIndex);
}
float sqrDistance(const osg::Vec2f& lhs, const osg::Vec2f& rhs)
{
return (lhs - rhs).length2();
}
float sqrDistanceIgnoreZ(const osg::Vec3f& lhs, const osg::Vec3f& rhs)
{
return sqrDistance(osg::Vec2f(lhs.x(), lhs.y()), osg::Vec2f(rhs.x(), rhs.y()));
}
}
namespace MWMechanics
{
float sqrDistanceIgnoreZ(const ESM::Pathgrid::Point& point, float x, float y)
{
x -= point.mX;
y -= point.mY;
return (x * x + y * y);
}
float distance(const ESM::Pathgrid::Point& point, float x, float y, float z)
{
x -= point.mX;
y -= point.mY;
z -= point.mZ;
return sqrt(x * x + y * y + z * z);
}
float distance(const ESM::Pathgrid::Point& a, const ESM::Pathgrid::Point& b)
{
float x = static_cast<float>(a.mX - b.mX);
float y = static_cast<float>(a.mY - b.mY);
float z = static_cast<float>(a.mZ - b.mZ);
return sqrt(x * x + y * y + z * z);
}
float getZAngleToDir(const osg::Vec3f& dir)
{
return std::atan2(dir.x(), dir.y());
}
float getXAngleToDir(const osg::Vec3f& dir)
{
float dirLen = dir.length();
return (dirLen != 0) ? -std::asin(dir.z() / dirLen) : 0;
}
float getZAngleToPoint(const ESM::Pathgrid::Point &origin, const ESM::Pathgrid::Point &dest)
{
osg::Vec3f dir = PathFinder::MakeOsgVec3(dest) - PathFinder::MakeOsgVec3(origin);
return getZAngleToDir(dir);
}
float getXAngleToPoint(const ESM::Pathgrid::Point &origin, const ESM::Pathgrid::Point &dest)
{
osg::Vec3f dir = PathFinder::MakeOsgVec3(dest) - PathFinder::MakeOsgVec3(origin);
return getXAngleToDir(dir);
}
bool checkWayIsClear(const osg::Vec3f& from, const osg::Vec3f& to, float offsetXY)
{
osg::Vec3f dir = to - from;
@ -121,18 +91,6 @@ namespace MWMechanics
return (std::abs(from.z() - h) <= PATHFIND_Z_REACH);
}
PathFinder::PathFinder()
: mPathgrid(nullptr)
, mCell(nullptr)
{
}
void PathFinder::clearPath()
{
if(!mPath.empty())
mPath.clear();
}
/*
* NOTE: This method may fail to find a path. The caller must check the
* result before using it. If there is no path the AI routies need to
@ -150,7 +108,7 @@ namespace MWMechanics
* pathgrid point (e.g. wander) then it may be worth while to call
* pop_back() to remove the redundant entry.
*
* NOTE: coordinates must be converted prior to calling GetClosestPoint()
* NOTE: coordinates must be converted prior to calling getClosestPoint()
*
* |
* | cell
@ -169,52 +127,44 @@ namespace MWMechanics
* j = @.x in local coordinates (i.e. within the cell)
* k = @.x in world coordinates
*/
void PathFinder::buildPath(const ESM::Pathgrid::Point &startPoint,
const ESM::Pathgrid::Point &endPoint,
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph)
void PathFinder::buildPathByPathgridImpl(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
const PathgridGraph& pathgridGraph, std::back_insert_iterator<std::deque<osg::Vec3f>> out)
{
mPath.clear();
// TODO: consider removing mCell / mPathgrid in favor of mPathgridGraph
if(mCell != cell || !mPathgrid)
{
mCell = cell;
mPathgrid = pathgridGraph.getPathgrid();
}
const auto pathgrid = pathgridGraph.getPathgrid();
// Refer to AiWander reseach topic on openmw forums for some background.
// Maybe there is no pathgrid for this cell. Just go to destination and let
// physics take care of any blockages.
if(!mPathgrid || mPathgrid->mPoints.empty())
if(!pathgrid || pathgrid->mPoints.empty())
{
mPath.push_back(endPoint);
*out++ = endPoint;
return;
}
// NOTE: GetClosestPoint expects local coordinates
// NOTE: getClosestPoint expects local coordinates
CoordinateConverter converter(mCell->getCell());
// NOTE: It is possible that GetClosestPoint returns a pathgrind point index
// NOTE: It is possible that getClosestPoint returns a pathgrind point index
// that is unreachable in some situations. e.g. actor is standing
// outside an area enclosed by walls, but there is a pathgrid
// point right behind the wall that is closer than any pathgrid
// point outside the wall
osg::Vec3f startPointInLocalCoords(converter.toLocalVec3(startPoint));
int startNode = GetClosestPoint(mPathgrid, startPointInLocalCoords);
int startNode = getClosestPoint(pathgrid, startPointInLocalCoords);
osg::Vec3f endPointInLocalCoords(converter.toLocalVec3(endPoint));
std::pair<int, bool> endNode = getClosestReachablePoint(mPathgrid, &pathgridGraph,
std::pair<int, bool> endNode = getClosestReachablePoint(pathgrid, &pathgridGraph,
endPointInLocalCoords,
startNode);
// if it's shorter for actor to travel from start to end, than to travel from either
// start or end to nearest pathgrid point, just travel from start to end.
float startToEndLength2 = (endPointInLocalCoords - startPointInLocalCoords).length2();
float endTolastNodeLength2 = DistanceSquared(mPathgrid->mPoints[endNode.first], endPointInLocalCoords);
float startTo1stNodeLength2 = DistanceSquared(mPathgrid->mPoints[startNode], startPointInLocalCoords);
float endTolastNodeLength2 = distanceSquared(pathgrid->mPoints[endNode.first], endPointInLocalCoords);
float startTo1stNodeLength2 = distanceSquared(pathgrid->mPoints[startNode], startPointInLocalCoords);
if ((startToEndLength2 < startTo1stNodeLength2) || (startToEndLength2 < endTolastNodeLength2))
{
mPath.push_back(endPoint);
*out++ = endPoint;
return;
}
@ -225,21 +175,21 @@ namespace MWMechanics
// nodes are the same
if(startNode == endNode.first)
{
ESM::Pathgrid::Point temp(mPathgrid->mPoints[startNode]);
ESM::Pathgrid::Point temp(pathgrid->mPoints[startNode]);
converter.toWorld(temp);
mPath.push_back(temp);
*out++ = makeOsgVec3(temp);
}
else
{
mPath = pathgridGraph.aStarSearch(startNode, endNode.first);
auto path = pathgridGraph.aStarSearch(startNode, endNode.first);
// If nearest path node is in opposite direction from second, remove it from path.
// Especially useful for wandering actors, if the nearest node is blocked for some reason.
if (mPath.size() > 1)
if (path.size() > 1)
{
ESM::Pathgrid::Point secondNode = *(++mPath.begin());
osg::Vec3f firstNodeVec3f = MakeOsgVec3(mPathgrid->mPoints[startNode]);
osg::Vec3f secondNodeVec3f = MakeOsgVec3(secondNode);
ESM::Pathgrid::Point secondNode = *(++path.begin());
osg::Vec3f firstNodeVec3f = makeOsgVec3(pathgrid->mPoints[startNode]);
osg::Vec3f secondNodeVec3f = makeOsgVec3(secondNode);
osg::Vec3f toSecondNodeVec3f = secondNodeVec3f - firstNodeVec3f;
osg::Vec3f toStartPointVec3f = startPointInLocalCoords - firstNodeVec3f;
if (toSecondNodeVec3f * toStartPointVec3f > 0)
@ -248,23 +198,25 @@ namespace MWMechanics
converter.toWorld(temp);
// Add Z offset since path node can overlap with other objects.
// Also ignore doors in raytesting.
int mask = MWPhysics::CollisionType_World;
const int mask = MWPhysics::CollisionType_World;
bool isPathClear = !MWBase::Environment::get().getWorld()->castRay(
startPoint.mX, startPoint.mY, startPoint.mZ+16, temp.mX, temp.mY, temp.mZ+16, mask);
startPoint.x(), startPoint.y(), startPoint.z() + 16, temp.mX, temp.mY, temp.mZ + 16, mask);
if (isPathClear)
mPath.pop_front();
path.pop_front();
}
}
// convert supplied path to world coordinates
for (std::list<ESM::Pathgrid::Point>::iterator iter(mPath.begin()); iter != mPath.end(); ++iter)
{
converter.toWorld(*iter);
}
std::transform(path.begin(), path.end(), out,
[&] (ESM::Pathgrid::Point& point)
{
converter.toWorld(point);
return makeOsgVec3(point);
});
}
// If endNode found is NOT the closest PathGrid point to the endPoint,
// assume endPoint is not reachable from endNode. In which case,
// assume endPoint is not reachable from endNode. In which case,
// path ends at endNode.
//
// So only add the destination (which may be different to the closest
@ -276,7 +228,7 @@ namespace MWMechanics
//
// The AI routines will have to deal with such situations.
if(endNode.second)
mPath.push_back(endPoint);
*out++ = endPoint;
}
float PathFinder::getZAngleToNext(float x, float y) const
@ -286,9 +238,9 @@ namespace MWMechanics
if(mPath.empty())
return 0.;
const ESM::Pathgrid::Point &nextPoint = *mPath.begin();
float directionX = nextPoint.mX - x;
float directionY = nextPoint.mY - y;
const auto& nextPoint = mPath.front();
const float directionX = nextPoint.x() - x;
const float directionY = nextPoint.y() - y;
return std::atan2(directionX, directionY);
}
@ -300,62 +252,64 @@ namespace MWMechanics
if(mPath.empty())
return 0.;
const ESM::Pathgrid::Point &nextPoint = *mPath.begin();
osg::Vec3f dir = MakeOsgVec3(nextPoint) - osg::Vec3f(x,y,z);
const osg::Vec3f dir = mPath.front() - osg::Vec3f(x, y, z);
return getXAngleToDir(dir);
}
bool PathFinder::checkPathCompleted(float x, float y, float tolerance)
void PathFinder::update(const osg::Vec3f& position, const float pointTolerance, const float destinationTolerance)
{
if(mPath.empty())
return true;
if (mPath.empty())
return;
const ESM::Pathgrid::Point& nextPoint = *mPath.begin();
if (sqrDistanceIgnoreZ(nextPoint, x, y) < tolerance*tolerance)
{
const auto tolerance = mPath.size() > 1 ? pointTolerance : destinationTolerance;
if (sqrDistanceIgnoreZ(mPath.front(), position) < tolerance * tolerance)
mPath.pop_front();
if(mPath.empty())
{
return true;
}
}
return false;
}
// see header for the rationale
void PathFinder::buildSyncedPath(const ESM::Pathgrid::Point &startPoint,
const ESM::Pathgrid::Point &endPoint,
const MWWorld::CellStore* cell, const MWMechanics::PathgridGraph& pathgridGraph)
void PathFinder::buildPathByPathgrid(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph)
{
if (mPath.size() < 2)
{
// if path has one point, then it's the destination.
// don't need to worry about bad path for this case
buildPath(startPoint, endPoint, cell, pathgridGraph);
}
else
{
const ESM::Pathgrid::Point oldStart(*getPath().begin());
buildPath(startPoint, endPoint, cell, pathgridGraph);
if (mPath.size() >= 2)
{
// if 2nd waypoint of new path == 1st waypoint of old,
// delete 1st waypoint of new path.
std::list<ESM::Pathgrid::Point>::iterator iter = ++mPath.begin();
if (iter->mX == oldStart.mX
&& iter->mY == oldStart.mY
&& iter->mZ == oldStart.mZ)
{
mPath.pop_front();
}
}
}
mPath.clear();
mCell = cell;
buildPathByPathgridImpl(startPoint, endPoint, pathgridGraph, std::back_inserter(mPath));
mConstructed = true;
}
const MWWorld::CellStore* PathFinder::getPathCell() const
void PathFinder::buildPath(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)
{
return mCell;
mPath.clear();
mCell = cell;
buildPathByNavigatorImpl(actor, startPoint, endPoint, halfExtents, flags, std::back_inserter(mPath));
if (mPath.empty())
buildPathByPathgridImpl(startPoint, endPoint, pathgridGraph, std::back_inserter(mPath));
mConstructed = true;
}
void PathFinder::buildPathByNavigatorImpl(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint,
const osg::Vec3f& endPoint, const osg::Vec3f& halfExtents, const DetourNavigator::Flags flags,
std::back_insert_iterator<std::deque<osg::Vec3f>> out)
{
try
{
const auto navigator = MWBase::Environment::get().getWorld()->getNavigator();
navigator->findPath(halfExtents, startPoint, endPoint, flags, out);
}
catch (const DetourNavigator::NavigatorException& exception)
{
DetourNavigator::log("PathFinder::buildPathByNavigator navigator exception: ", exception.what());
Log(Debug::Verbose) << "Build path by navigator exception: \"" << exception.what()
<< "\" for \"" << actor.getClass().getName(actor) << "\" (" << actor.getBase()
<< ") from " << startPoint << " to " << endPoint << " with flags ("
<< DetourNavigator::WriteFlags {flags} << ")";
}
}
}

View file

@ -1,33 +1,57 @@
#ifndef GAME_MWMECHANICS_PATHFINDING_H
#define GAME_MWMECHANICS_PATHFINDING_H
#include <list>
#include <deque>
#include <cassert>
#include <iterator>
#include <components/detournavigator/flags.hpp>
#include <components/esm/defs.hpp>
#include <components/esm/loadpgrd.hpp>
namespace MWWorld
{
class CellStore;
class ConstPtr;
}
namespace MWMechanics
{
class PathgridGraph;
float distance(const ESM::Pathgrid::Point& point, float x, float y, float);
float distance(const ESM::Pathgrid::Point& a, const ESM::Pathgrid::Point& b);
float getZAngleToDir(const osg::Vec3f& dir);
float getXAngleToDir(const osg::Vec3f& dir);
float getZAngleToPoint(const ESM::Pathgrid::Point &origin, const ESM::Pathgrid::Point &dest);
float getXAngleToPoint(const ESM::Pathgrid::Point &origin, const ESM::Pathgrid::Point &dest);
inline float distance(const osg::Vec3f& lhs, const osg::Vec3f& rhs)
{
return (lhs - rhs).length();
}
const float PATHFIND_Z_REACH = 50.0f;
inline float getZAngleToDir(const osg::Vec3f& dir)
{
return std::atan2(dir.x(), dir.y());
}
inline float getXAngleToDir(const osg::Vec3f& dir)
{
float dirLen = dir.length();
return (dirLen != 0) ? -std::asin(dir.z() / dirLen) : 0;
}
inline float getZAngleToPoint(const osg::Vec3f& origin, const osg::Vec3f& dest)
{
return getZAngleToDir(dest - origin);
}
inline float getXAngleToPoint(const osg::Vec3f& origin, const osg::Vec3f& dest)
{
return getXAngleToDir(dest - origin);
}
const float PATHFIND_Z_REACH = 50.0f;
//static const float sMaxSlope = 49.0f; // duplicate as in physicssystem
// distance after which actor (failed previously to shortcut) will try again
const float PATHFIND_SHORTCUT_RETRY_DIST = 300.0f;
const float DEFAULT_TOLERANCE = 32.0f;
// cast up-down ray with some offset from actor position to check for pits/obstacles on the way to target;
// magnitude of pits/obstacles is defined by PATHFIND_Z_REACH
bool checkWayIsClear(const osg::Vec3f& from, const osg::Vec3f& to, float offsetXY);
@ -35,31 +59,33 @@ namespace MWMechanics
class PathFinder
{
public:
PathFinder();
static const int PathTolerance = 32;
static float sgn(float val)
PathFinder()
: mConstructed(false)
, mCell(nullptr)
{
if(val > 0)
return 1.0;
return -1.0;
}
static int sgn(int a)
void clearPath()
{
if(a > 0)
return 1;
return -1;
mConstructed = false;
mPath.clear();
mCell = nullptr;
}
void clearPath();
void buildPathByPathgrid(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph);
void buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint,
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph);
void buildPath(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);
bool checkPathCompleted(float x, float y, float tolerance = PathTolerance);
///< \Returns true if we are within \a tolerance units of the last path point.
/// Remove front point if exist and within tolerance
void update(const osg::Vec3f& position, const float pointTolerance, const float destinationTolerance);
bool checkPathCompleted() const
{
return mConstructed && mPath.empty();
}
/// In radians
float getZAngleToNext(float x, float y) const;
@ -68,60 +94,54 @@ namespace MWMechanics
bool isPathConstructed() const
{
return !mPath.empty();
return mConstructed && !mPath.empty();
}
int getPathSize() const
std::size_t getPathSize() const
{
return mPath.size();
}
const std::list<ESM::Pathgrid::Point>& getPath() const
const std::deque<osg::Vec3f>& getPath() const
{
return mPath;
}
const MWWorld::CellStore* getPathCell() const;
/** Synchronize new path with old one to avoid visiting 1 waypoint 2 times
@note
BuildPath() takes closest PathGrid point to NPC as first point of path.
This is undesirable if NPC has just passed a Pathgrid point, as this
makes the 2nd point of the new path == the 1st point of old path.
Which results in NPC "running in a circle" back to the just passed waypoint.
*/
void buildSyncedPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint,
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph);
void addPointToPath(const ESM::Pathgrid::Point &point)
const MWWorld::CellStore* getPathCell() const
{
return mCell;
}
void addPointToPath(const osg::Vec3f& point)
{
mConstructed = true;
mPath.push_back(point);
}
/// utility function to convert a osg::Vec3f to a Pathgrid::Point
static ESM::Pathgrid::Point MakePathgridPoint(const osg::Vec3f& v)
static ESM::Pathgrid::Point makePathgridPoint(const osg::Vec3f& v)
{
return ESM::Pathgrid::Point(static_cast<int>(v[0]), static_cast<int>(v[1]), static_cast<int>(v[2]));
}
/// utility function to convert an ESM::Position to a Pathgrid::Point
static ESM::Pathgrid::Point MakePathgridPoint(const ESM::Position& p)
static ESM::Pathgrid::Point makePathgridPoint(const ESM::Position& p)
{
return ESM::Pathgrid::Point(static_cast<int>(p.pos[0]), static_cast<int>(p.pos[1]), static_cast<int>(p.pos[2]));
}
static osg::Vec3f MakeOsgVec3(const ESM::Pathgrid::Point& p)
static osg::Vec3f makeOsgVec3(const ESM::Pathgrid::Point& p)
{
return osg::Vec3f(static_cast<float>(p.mX), static_cast<float>(p.mY), static_cast<float>(p.mZ));
}
// Slightly cheaper version for comparisons.
// Caller needs to be careful for very short distances (i.e. less than 1)
// or when accumuating the results i.e. (a + b)^2 != a^2 + b^2
//
static float DistanceSquared(ESM::Pathgrid::Point point, const osg::Vec3f& pos)
static float distanceSquared(ESM::Pathgrid::Point point, const osg::Vec3f& pos)
{
return (MWMechanics::PathFinder::MakeOsgVec3(point) - pos).length2();
return (MWMechanics::PathFinder::makeOsgVec3(point) - pos).length2();
}
// Return the closest pathgrid point index from the specified position
@ -130,18 +150,18 @@ namespace MWMechanics
//
// NOTE: pos is expected to be in local coordinates, as is grid->mPoints
//
static int GetClosestPoint(const ESM::Pathgrid* grid, const osg::Vec3f& pos)
static int getClosestPoint(const ESM::Pathgrid* grid, const osg::Vec3f& pos)
{
assert(grid && !grid->mPoints.empty());
float distanceBetween = DistanceSquared(grid->mPoints[0], pos);
float distanceBetween = distanceSquared(grid->mPoints[0], pos);
int closestIndex = 0;
// TODO: if this full scan causes performance problems mapping pathgrid
// points to a quadtree may help
for(unsigned int counter = 1; counter < grid->mPoints.size(); counter++)
{
float potentialDistBetween = DistanceSquared(grid->mPoints[counter], pos);
float potentialDistBetween = distanceSquared(grid->mPoints[counter], pos);
if(potentialDistBetween < distanceBetween)
{
distanceBetween = potentialDistBetween;
@ -153,10 +173,17 @@ namespace MWMechanics
}
private:
std::list<ESM::Pathgrid::Point> mPath;
bool mConstructed;
std::deque<osg::Vec3f> mPath;
const ESM::Pathgrid *mPathgrid;
const MWWorld::CellStore* mCell;
void buildPathByPathgridImpl(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
const PathgridGraph& pathgridGraph, std::back_insert_iterator<std::deque<osg::Vec3f>> out);
void buildPathByNavigatorImpl(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint,
const osg::Vec3f& endPoint, const osg::Vec3f& halfExtents, const DetourNavigator::Flags flags,
std::back_insert_iterator<std::deque<osg::Vec3f>> out);
};
}

View file

@ -257,10 +257,9 @@ namespace MWMechanics
* pathgrid points form (currently they are converted to world
* coordinates). Essentially trading speed w/ memory.
*/
std::list<ESM::Pathgrid::Point> PathgridGraph::aStarSearch(const int start,
const int goal) const
std::deque<ESM::Pathgrid::Point> PathgridGraph::aStarSearch(const int start, const int goal) const
{
std::list<ESM::Pathgrid::Point> path;
std::deque<ESM::Pathgrid::Point> path;
if(!isPointConnected(start, goal))
{
return path; // there is no path, return an empty path

View file

@ -1,7 +1,7 @@
#ifndef GAME_MWMECHANICS_PATHGRID_H
#define GAME_MWMECHANICS_PATHGRID_H
#include <list>
#include <deque>
#include <components/esm/loadpgrd.hpp>
@ -38,8 +38,8 @@ namespace MWMechanics
// cells) coordinates
//
// NOTE: if start equals end an empty path is returned
std::list<ESM::Pathgrid::Point> aStarSearch(const int start,
const int end) const;
std::deque<ESM::Pathgrid::Point> aStarSearch(const int start, const int end) const;
private:
const ESM::Cell *mCell;

View file

@ -324,6 +324,34 @@ namespace MWMechanics
return true;
}
class GetAbsorptionProbability : public MWMechanics::EffectSourceVisitor
{
public:
float mProbability;
GetAbsorptionProbability(const MWWorld::Ptr& actor)
: mProbability(0.f){}
virtual void visit (MWMechanics::EffectKey key,
const std::string& sourceName, const std::string& sourceId, int casterActorId,
float magnitude, float remainingTime = -1, float totalTime = -1)
{
if (key.mId == ESM::MagicEffect::SpellAbsorption)
{
if (mProbability == 0.f)
mProbability = magnitude / 100;
else
{
// If there are different sources of SpellAbsorption effect, multiply failing probability for all effects.
// Real absorption probability will be the (1 - total fail chance) in this case.
float failProbability = 1.f - mProbability;
failProbability *= 1.f - magnitude / 100;
mProbability = 1.f - failProbability;
}
}
}
};
CastSpell::CastSpell(const MWWorld::Ptr &caster, const MWWorld::Ptr &target, const bool fromProjectile, const bool manualSpell)
: mCaster(caster)
, mTarget(target)
@ -444,22 +472,31 @@ namespace MWMechanics
MWBase::Environment::get().getWindowManager()->setEnemy(target);
// Try absorbing if it's a spell
// NOTE: Vanilla does this once per spell absorption effect source instead of adding the % from all sources together, not sure
// if that is worth replicating.
// Unlike Reflect, this is done once per spell absorption effect source
bool absorbed = false;
if (spell && caster != target && target.getClass().isActor())
{
float absorb = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).getMagnitude();
absorbed = (Misc::Rng::roll0to99() < absorb);
if (absorbed)
CreatureStats& stats = target.getClass().getCreatureStats(target);
if (stats.getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).getMagnitude() > 0.f)
{
const ESM::Static* absorbStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find ("VFX_Absorb");
MWBase::Environment::get().getWorld()->getAnimation(target)->addEffect(
"meshes\\" + absorbStatic->mModel, ESM::MagicEffect::SpellAbsorption, false, "");
// Magicka is increased by cost of spell
DynamicStat<float> magicka = target.getClass().getCreatureStats(target).getMagicka();
magicka.setCurrent(magicka.getCurrent() + spell->mData.mCost);
target.getClass().getCreatureStats(target).setMagicka(magicka);
GetAbsorptionProbability check(target);
stats.getActiveSpells().visitEffectSources(check);
stats.getSpells().visitEffectSources(check);
if (target.getClass().hasInventoryStore(target))
target.getClass().getInventoryStore(target).visitEffectSources(check);
int absorb = check.mProbability * 100;
absorbed = (Misc::Rng::roll0to99() < absorb);
if (absorbed)
{
const ESM::Static* absorbStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find ("VFX_Absorb");
MWBase::Environment::get().getWorld()->getAnimation(target)->addEffect(
"meshes\\" + absorbStatic->mModel, ESM::MagicEffect::SpellAbsorption, false, "");
// Magicka is increased by cost of spell
DynamicStat<float> magicka = stats.getMagicka();
magicka.setCurrent(magicka.getCurrent() + spell->mData.mCost);
stats.setMagicka(magicka);
}
}
}

View file

@ -58,8 +58,16 @@ namespace MWMechanics
}
const float chop = (weapon->mData.mChop[0] + weapon->mData.mChop[1]) / 2.f;
if (weapon->mData.mType >= ESM::Weapon::MarksmanBow)
// We need to account for the fact that thrown weapons have 2x real damage applied to the target
// as they're both the weapon and the ammo of the hit
if (weapon->mData.mType == ESM::Weapon::MarksmanThrown)
{
rating = chop * 2;
}
else if (weapon->mData.mType >= ESM::Weapon::MarksmanBow)
{
rating = chop;
}
else
{
const float slash = (weapon->mData.mSlash[0] + weapon->mData.mSlash[1]) / 2.f;

View file

@ -6,6 +6,7 @@
#include <components/sceneutil/positionattitudetransform.hpp>
#include <components/resource/bulletshape.hpp>
#include <components/debug/debuglog.hpp>
#include "../mwworld/class.hpp"
@ -28,6 +29,31 @@ Actor::Actor(const MWWorld::Ptr& ptr, osg::ref_ptr<const Resource::BulletShape>
mHalfExtents = shape->mCollisionBoxHalfExtents;
mMeshTranslation = shape->mCollisionBoxTranslate;
// We can not create actor without collisions - he will fall through the ground.
// In this case we should autogenerate collision box based on mesh shape
// (NPCs have bodyparts and use a different approach)
if (!ptr.getClass().isNpc() && mHalfExtents.length2() == 0.f)
{
const Resource::BulletShape* collisionShape = shape.get();
if (collisionShape && collisionShape->mCollisionShape)
{
btTransform transform;
transform.setIdentity();
btVector3 min;
btVector3 max;
collisionShape->mCollisionShape->getAabb(transform, min, max);
mHalfExtents.x() = (max[0] - min[0])/2.f;
mHalfExtents.y() = (max[1] - min[1])/2.f;
mHalfExtents.z() = (max[2] - min[2])/2.f;
mMeshTranslation = osg::Vec3f(0.f, 0.f, mHalfExtents.z());
}
if (mHalfExtents.length2() == 0.f)
Log(Debug::Error) << "Error: Failed to calculate bounding box for actor \"" << ptr.getCellRef().getRefId() << "\".";
}
// Use capsule shape only if base is square (nonuniform scaling apparently doesn't work on it)
if (std::abs(mHalfExtents.x()-mHalfExtents.y())<mHalfExtents.x()*0.05 && mHalfExtents.z() >= mHalfExtents.x())
{

View file

@ -3,7 +3,7 @@
#include <memory>
#include "../mwworld/ptr.hpp"
#include "ptrholder.hpp"
#include <osg/Vec3f>
#include <osg/Quat>
@ -22,30 +22,6 @@ namespace Resource
namespace MWPhysics
{
class PtrHolder
{
public:
virtual ~PtrHolder() {}
void updatePtr(const MWWorld::Ptr& updated)
{
mPtr = updated;
}
MWWorld::Ptr getPtr()
{
return mPtr;
}
MWWorld::ConstPtr getPtr() const
{
return mPtr;
}
protected:
MWWorld::Ptr mPtr;
};
class Actor : public PtrHolder
{
public:

View file

@ -0,0 +1,54 @@
#include "heightfield.hpp"
#include <osg/Object>
#include <BulletCollision/CollisionShapes/btHeightfieldTerrainShape.h>
#include <BulletCollision/CollisionDispatch/btCollisionObject.h>
#include <LinearMath/btTransform.h>
namespace MWPhysics
{
HeightField::HeightField(const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject)
{
mShape = new btHeightfieldTerrainShape(
sqrtVerts, sqrtVerts, heights, 1,
minH, maxH, 2,
PHY_FLOAT, false
);
mShape->setUseDiamondSubdivision(true);
mShape->setLocalScaling(btVector3(triSize, triSize, 1));
btTransform transform(btQuaternion::getIdentity(),
btVector3((x+0.5f) * triSize * (sqrtVerts-1),
(y+0.5f) * triSize * (sqrtVerts-1),
(maxH+minH)*0.5f));
mCollisionObject = new btCollisionObject;
mCollisionObject->setCollisionShape(mShape);
mCollisionObject->setWorldTransform(transform);
mHoldObject = holdObject;
}
HeightField::~HeightField()
{
delete mCollisionObject;
delete mShape;
}
btCollisionObject* HeightField::getCollisionObject()
{
return mCollisionObject;
}
const btCollisionObject* HeightField::getCollisionObject() const
{
return mCollisionObject;
}
const btHeightfieldTerrainShape* HeightField::getShape() const
{
return mShape;
}
}

View file

@ -0,0 +1,36 @@
#ifndef OPENMW_MWPHYSICS_HEIGHTFIELD_H
#define OPENMW_MWPHYSICS_HEIGHTFIELD_H
#include <osg/ref_ptr>
class btCollisionObject;
class btHeightfieldTerrainShape;
namespace osg
{
class Object;
}
namespace MWPhysics
{
class HeightField
{
public:
HeightField(const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject);
~HeightField();
btCollisionObject* getCollisionObject();
const btCollisionObject* getCollisionObject() const;
const btHeightfieldTerrainShape* getShape() const;
private:
btHeightfieldTerrainShape* mShape;
btCollisionObject* mCollisionObject;
osg::ref_ptr<const osg::Object> mHoldObject;
void operator=(const HeightField&);
HeightField(const HeightField&);
};
}
#endif

View file

@ -0,0 +1,130 @@
#include "object.hpp"
#include "convert.hpp"
#include <components/debug/debuglog.hpp>
#include <components/nifosg/particle.hpp>
#include <components/resource/bulletshape.hpp>
#include <components/sceneutil/positionattitudetransform.hpp>
#include <osg/Object>
#include <BulletCollision/CollisionShapes/btCompoundShape.h>
#include <BulletCollision/CollisionDispatch/btCollisionObject.h>
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
#include <LinearMath/btTransform.h>
namespace MWPhysics
{
Object::Object(const MWWorld::Ptr& ptr, osg::ref_ptr<Resource::BulletShapeInstance> shapeInstance)
: mShapeInstance(shapeInstance)
, mSolid(true)
{
mPtr = ptr;
mCollisionObject.reset(new btCollisionObject);
mCollisionObject->setCollisionShape(shapeInstance->getCollisionShape());
mCollisionObject->setUserPointer(static_cast<PtrHolder*>(this));
setScale(ptr.getCellRef().getScale());
setRotation(toBullet(ptr.getRefData().getBaseNode()->getAttitude()));
const float* pos = ptr.getRefData().getPosition().pos;
setOrigin(btVector3(pos[0], pos[1], pos[2]));
}
const Resource::BulletShapeInstance* Object::getShapeInstance() const
{
return mShapeInstance.get();
}
void Object::setScale(float scale)
{
mShapeInstance->setLocalScaling(btVector3(scale, scale, scale));
}
void Object::setRotation(const btQuaternion& quat)
{
mCollisionObject->getWorldTransform().setRotation(quat);
}
void Object::setOrigin(const btVector3& vec)
{
mCollisionObject->getWorldTransform().setOrigin(vec);
}
btCollisionObject* Object::getCollisionObject()
{
return mCollisionObject.get();
}
const btCollisionObject* Object::getCollisionObject() const
{
return mCollisionObject.get();
}
bool Object::isSolid() const
{
return mSolid;
}
void Object::setSolid(bool solid)
{
mSolid = solid;
}
bool Object::isAnimated() const
{
return !mShapeInstance->mAnimatedShapes.empty();
}
void Object::animateCollisionShapes(btCollisionWorld* collisionWorld)
{
if (mShapeInstance->mAnimatedShapes.empty())
return;
assert (mShapeInstance->getCollisionShape()->isCompound());
btCompoundShape* compound = static_cast<btCompoundShape*>(mShapeInstance->getCollisionShape());
for (std::map<int, int>::const_iterator it = mShapeInstance->mAnimatedShapes.begin(); it != mShapeInstance->mAnimatedShapes.end(); ++it)
{
int recIndex = it->first;
int shapeIndex = it->second;
std::map<int, osg::NodePath>::iterator nodePathFound = mRecIndexToNodePath.find(recIndex);
if (nodePathFound == mRecIndexToNodePath.end())
{
NifOsg::FindGroupByRecIndex visitor(recIndex);
mPtr.getRefData().getBaseNode()->accept(visitor);
if (!visitor.mFound)
{
Log(Debug::Warning) << "Warning: animateCollisionShapes can't find node " << recIndex << " for " << mPtr.getCellRef().getRefId();
// Remove nonexistent nodes from animated shapes map and early out
mShapeInstance->mAnimatedShapes.erase(recIndex);
return;
}
osg::NodePath nodePath = visitor.mFoundPath;
nodePath.erase(nodePath.begin());
nodePathFound = mRecIndexToNodePath.insert(std::make_pair(recIndex, nodePath)).first;
}
osg::NodePath& nodePath = nodePathFound->second;
osg::Matrixf matrix = osg::computeLocalToWorld(nodePath);
matrix.orthoNormalize(matrix);
btTransform transform;
transform.setOrigin(toBullet(matrix.getTrans()) * compound->getLocalScaling());
for (int i=0; i<3; ++i)
for (int j=0; j<3; ++j)
transform.getBasis()[i][j] = matrix(j,i); // NB column/row major difference
// Note: we can not apply scaling here for now since we treat scaled shapes
// as new shapes (btScaledBvhTriangleMeshShape) with 1.0 scale for now
if (!(transform == compound->getChildTransform(shapeIndex)))
compound->updateChildTransform(shapeIndex, transform);
}
collisionWorld->updateSingleAabb(mCollisionObject.get());
}
}

View file

@ -0,0 +1,48 @@
#ifndef OPENMW_MWPHYSICS_OBJECT_H
#define OPENMW_MWPHYSICS_OBJECT_H
#include "ptrholder.hpp"
#include <osg/Node>
#include <map>
#include <memory>
namespace Resource
{
class BulletShapeInstance;
}
class btCollisionObject;
class btCollisionWorld;
class btQuaternion;
class btVector3;
namespace MWPhysics
{
class Object : public PtrHolder
{
public:
Object(const MWWorld::Ptr& ptr, osg::ref_ptr<Resource::BulletShapeInstance> shapeInstance);
const Resource::BulletShapeInstance* getShapeInstance() const;
void setScale(float scale);
void setRotation(const btQuaternion& quat);
void setOrigin(const btVector3& vec);
btCollisionObject* getCollisionObject();
const btCollisionObject* getCollisionObject() const;
/// Return solid flag. Not used by the object itself, true by default.
bool isSolid() const;
void setSolid(bool solid);
bool isAnimated() const;
void animateCollisionShapes(btCollisionWorld* collisionWorld);
private:
std::unique_ptr<btCollisionObject> mCollisionObject;
osg::ref_ptr<Resource::BulletShapeInstance> mShapeInstance;
std::map<int, osg::NodePath> mRecIndexToNodePath;
bool mSolid;
};
}
#endif

View file

@ -1,6 +1,11 @@
#include "physicssystem.hpp"
#include "physicssystem.hpp"
#include <stdexcept>
#include <unordered_map>
#include <fstream>
#include <array>
#include <boost/optional.hpp>
#include <osg/Group>
@ -16,6 +21,12 @@
#include <LinearMath/btQuickprof.h>
#include <DetourCommon.h>
#include <DetourNavMesh.h>
#include <DetourNavMeshBuilder.h>
#include <DetourNavMeshQuery.h>
#include <Recast.h>
#include <components/nifbullet/bulletnifloader.hpp>
#include <components/resource/resourcesystem.hpp>
#include <components/resource/bulletshapemanager.hpp>
@ -48,12 +59,12 @@
#include "actor.hpp"
#include "convert.hpp"
#include "trace.h"
#include "object.hpp"
#include "heightfield.hpp"
namespace MWPhysics
{
static const float sMaxSlope = 49.0f;
static const float sStepSizeUp = 34.0f;
static const float sStepSizeDown = 62.0f;
static const float sMinStep = 10.f;
static const float sGroundOffset = 1.0f;
@ -507,175 +518,6 @@ namespace MWPhysics
// ---------------------------------------------------------------
class HeightField
{
public:
HeightField(const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject)
{
mShape = new btHeightfieldTerrainShape(
sqrtVerts, sqrtVerts, heights, 1,
minH, maxH, 2,
PHY_FLOAT, false
);
mShape->setUseDiamondSubdivision(true);
mShape->setLocalScaling(btVector3(triSize, triSize, 1));
btTransform transform(btQuaternion::getIdentity(),
btVector3((x+0.5f) * triSize * (sqrtVerts-1),
(y+0.5f) * triSize * (sqrtVerts-1),
(maxH+minH)*0.5f));
mCollisionObject = new btCollisionObject;
mCollisionObject->setCollisionShape(mShape);
mCollisionObject->setWorldTransform(transform);
mHoldObject = holdObject;
}
~HeightField()
{
delete mCollisionObject;
delete mShape;
}
btCollisionObject* getCollisionObject()
{
return mCollisionObject;
}
private:
btHeightfieldTerrainShape* mShape;
btCollisionObject* mCollisionObject;
osg::ref_ptr<const osg::Object> mHoldObject;
void operator=(const HeightField&);
HeightField(const HeightField&);
};
// --------------------------------------------------------------
class Object : public PtrHolder
{
public:
Object(const MWWorld::Ptr& ptr, osg::ref_ptr<Resource::BulletShapeInstance> shapeInstance)
: mShapeInstance(shapeInstance)
, mSolid(true)
{
mPtr = ptr;
mCollisionObject.reset(new btCollisionObject);
mCollisionObject->setCollisionShape(shapeInstance->getCollisionShape());
mCollisionObject->setUserPointer(static_cast<PtrHolder*>(this));
setScale(ptr.getCellRef().getScale());
setRotation(toBullet(ptr.getRefData().getBaseNode()->getAttitude()));
const float* pos = ptr.getRefData().getPosition().pos;
setOrigin(btVector3(pos[0], pos[1], pos[2]));
}
const Resource::BulletShapeInstance* getShapeInstance() const
{
return mShapeInstance.get();
}
void setScale(float scale)
{
mShapeInstance->getCollisionShape()->setLocalScaling(btVector3(scale,scale,scale));
}
void setRotation(const btQuaternion& quat)
{
mCollisionObject->getWorldTransform().setRotation(quat);
}
void setOrigin(const btVector3& vec)
{
mCollisionObject->getWorldTransform().setOrigin(vec);
}
btCollisionObject* getCollisionObject()
{
return mCollisionObject.get();
}
const btCollisionObject* getCollisionObject() const
{
return mCollisionObject.get();
}
/// Return solid flag. Not used by the object itself, true by default.
bool isSolid() const
{
return mSolid;
}
void setSolid(bool solid)
{
mSolid = solid;
}
bool isAnimated() const
{
return !mShapeInstance->mAnimatedShapes.empty();
}
void animateCollisionShapes(btCollisionWorld* collisionWorld)
{
if (mShapeInstance->mAnimatedShapes.empty())
return;
assert (mShapeInstance->getCollisionShape()->isCompound());
btCompoundShape* compound = static_cast<btCompoundShape*>(mShapeInstance->getCollisionShape());
for (std::map<int, int>::const_iterator it = mShapeInstance->mAnimatedShapes.begin(); it != mShapeInstance->mAnimatedShapes.end(); ++it)
{
int recIndex = it->first;
int shapeIndex = it->second;
std::map<int, osg::NodePath>::iterator nodePathFound = mRecIndexToNodePath.find(recIndex);
if (nodePathFound == mRecIndexToNodePath.end())
{
NifOsg::FindGroupByRecIndex visitor(recIndex);
mPtr.getRefData().getBaseNode()->accept(visitor);
if (!visitor.mFound)
{
Log(Debug::Warning) << "Warning: animateCollisionShapes can't find node " << recIndex << " for " << mPtr.getCellRef().getRefId();
// Remove nonexistent nodes from animated shapes map and early out
mShapeInstance->mAnimatedShapes.erase(recIndex);
return;
}
osg::NodePath nodePath = visitor.mFoundPath;
nodePath.erase(nodePath.begin());
nodePathFound = mRecIndexToNodePath.insert(std::make_pair(recIndex, nodePath)).first;
}
osg::NodePath& nodePath = nodePathFound->second;
osg::Matrixf matrix = osg::computeLocalToWorld(nodePath);
matrix.orthoNormalize(matrix);
btTransform transform;
transform.setOrigin(toBullet(matrix.getTrans()) * compound->getLocalScaling());
for (int i=0; i<3; ++i)
for (int j=0; j<3; ++j)
transform.getBasis()[i][j] = matrix(j,i); // NB column/row major difference
// Note: we can not apply scaling here for now since we treat scaled shapes
// as new shapes (btScaledBvhTriangleMeshShape) with 1.0 scale for now
if (!(transform == compound->getChildTransform(shapeIndex)))
compound->updateChildTransform(shapeIndex, transform);
}
collisionWorld->updateSingleAabb(mCollisionObject.get());
}
private:
std::unique_ptr<btCollisionObject> mCollisionObject;
osg::ref_ptr<Resource::BulletShapeInstance> mShapeInstance;
std::map<int, osg::NodePath> mRecIndexToNodePath;
bool mSolid;
};
// ---------------------------------------------------------------
PhysicsSystem::PhysicsSystem(Resource::ResourceSystem* resourceSystem, osg::ref_ptr<osg::Group> parentNode)
: mShapeManager(new Resource::BulletShapeManager(resourceSystem->getVFS(), resourceSystem->getSceneManager(), resourceSystem->getNifFileManager()))
@ -1171,6 +1013,14 @@ namespace MWPhysics
}
}
const HeightField* PhysicsSystem::getHeightField(int x, int y) const
{
const auto heightField = mHeightFields.find(std::make_pair(x, y));
if (heightField == mHeightFields.end())
return nullptr;
return heightField->second;
}
void PhysicsSystem::addObject (const MWWorld::Ptr& ptr, const std::string& mesh, int collisionType)
{
osg::ref_ptr<Resource::BulletShapeInstance> shapeInstance = mShapeManager->getInstance(mesh);
@ -1366,6 +1216,33 @@ namespace MWPhysics
return false;
}
void PhysicsSystem::setActorCollisionMode(const MWWorld::Ptr& ptr, bool enabled)
{
ActorMap::iterator found = mActors.find(ptr);
if (found != mActors.end())
{
bool cmode = found->second->getCollisionMode();
if (cmode == enabled)
return;
cmode = enabled;
found->second->enableCollisionMode(cmode);
found->second->enableCollisionBody(cmode);
}
}
bool PhysicsSystem::isActorCollisionEnabled(const MWWorld::Ptr& ptr)
{
ActorMap::iterator found = mActors.find(ptr);
if (found != mActors.end())
{
bool cmode = found->second->getCollisionMode();
return cmode;
}
return false;
}
void PhysicsSystem::queueObjectMovement(const MWWorld::Ptr &ptr, const osg::Vec3f &movement)
{
PtrVelocityList::iterator iter = mMovementQueue.begin();

View file

@ -4,6 +4,7 @@
#include <memory>
#include <map>
#include <set>
#include <algorithm>
#include <osg/Quat>
#include <osg/ref_ptr>
@ -49,6 +50,9 @@ namespace MWPhysics
class Object;
class Actor;
static const float sMaxSlope = 49.0f;
static const float sStepSizeUp = 34.0f;
class PhysicsSystem
{
public:
@ -85,7 +89,11 @@ namespace MWPhysics
void removeHeightField (int x, int y);
const HeightField* getHeightField(int x, int y) const;
bool toggleCollisionMode();
bool isActorCollisionEnabled(const MWWorld::Ptr& ptr);
void setActorCollisionMode(const MWWorld::Ptr& ptr, bool enabled);
void stepSimulation(float dt);
void debugDraw();
@ -170,6 +178,12 @@ namespace MWPhysics
bool isOnSolidGround (const MWWorld::Ptr& actor) const;
template <class Function>
void forEachAnimatedObject(Function&& function) const
{
std::for_each(mAnimatedObjects.begin(), mAnimatedObjects.end(), function);
}
private:
void updateWater();

View file

@ -0,0 +1,33 @@
#ifndef OPENMW_MWPHYSICS_PTRHOLDER_H
#define OPENMW_MWPHYSICS_PTRHOLDER_H
#include "../mwworld/ptr.hpp"
namespace MWPhysics
{
class PtrHolder
{
public:
virtual ~PtrHolder() {}
void updatePtr(const MWWorld::Ptr& updated)
{
mPtr = updated;
}
MWWorld::Ptr getPtr()
{
return mPtr;
}
MWWorld::ConstPtr getPtr() const
{
return mPtr;
}
protected:
MWWorld::Ptr mPtr;
};
}
#endif

View file

@ -1,5 +1,4 @@
#include "actoranimation.hpp"
#include <utility>
#include <osg/Node>
@ -9,11 +8,22 @@
#include <components/esm/loadligh.hpp>
#include <components/esm/loadcell.hpp>
#include <components/resource/resourcesystem.hpp>
#include <components/resource/scenemanager.hpp>
#include <components/sceneutil/attach.hpp>
#include <components/sceneutil/lightmanager.hpp>
#include <components/sceneutil/lightutil.hpp>
#include <components/sceneutil/visitor.hpp>
#include <components/fallback/fallback.hpp>
#include <components/misc/stringops.hpp>
#include <components/settings/settings.hpp>
#include <components/vfs/manager.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "../mwworld/ptr.hpp"
@ -43,6 +53,8 @@ ActorAnimation::ActorAnimation(const MWWorld::Ptr& ptr, osg::ref_ptr<osg::Group>
// Make sure we cleaned object from effects, just in cast if we re-use node
removeEffects();
mWeaponSheathing = Settings::Manager::getBool("weapon sheathing", "Game");
}
ActorAnimation::~ActorAnimation()
@ -51,6 +63,302 @@ ActorAnimation::~ActorAnimation()
{
mInsert->removeChild(iter->second);
}
mScabbard.reset();
}
PartHolderPtr ActorAnimation::getWeaponPart(const std::string& model, const std::string& bonename, bool enchantedGlow, osg::Vec4f* glowColor)
{
osg::Group* parent = getBoneByName(bonename);
if (!parent)
return nullptr;
osg::ref_ptr<osg::Node> instance = mResourceSystem->getSceneManager()->getInstance(model, parent);
const NodeMap& nodeMap = getNodeMap();
NodeMap::const_iterator found = nodeMap.find(Misc::StringUtils::lowerCase(bonename));
if (found == nodeMap.end())
return PartHolderPtr();
if (enchantedGlow)
addGlow(instance, *glowColor);
return PartHolderPtr(new PartHolder(instance));
}
osg::Group* ActorAnimation::getBoneByName(std::string boneName)
{
if (!mObjectRoot)
return nullptr;
SceneUtil::FindByNameVisitor findVisitor (boneName);
mObjectRoot->accept(findVisitor);
return findVisitor.mFoundNode;
}
std::string ActorAnimation::getHolsteredWeaponBoneName(const MWWorld::ConstPtr& weapon)
{
std::string boneName;
if(weapon.isEmpty())
return boneName;
const std::string &type = weapon.getClass().getTypeName();
if(type == typeid(ESM::Weapon).name())
{
const MWWorld::LiveCellRef<ESM::Weapon> *ref = weapon.get<ESM::Weapon>();
ESM::Weapon::Type weaponType = (ESM::Weapon::Type)ref->mBase->mData.mType;
return getHolsteredWeaponBoneName(weaponType);
}
return boneName;
}
std::string ActorAnimation::getHolsteredWeaponBoneName(const unsigned int weaponType)
{
std::string boneName;
switch(weaponType)
{
case ESM::Weapon::ShortBladeOneHand:
boneName = "Bip01 ShortBladeOneHand";
break;
case ESM::Weapon::LongBladeOneHand:
boneName = "Bip01 LongBladeOneHand";
break;
case ESM::Weapon::BluntOneHand:
boneName = "Bip01 BluntOneHand";
break;
case ESM::Weapon::AxeOneHand:
boneName = "Bip01 LongBladeOneHand";
break;
case ESM::Weapon::LongBladeTwoHand:
boneName = "Bip01 LongBladeTwoClose";
break;
case ESM::Weapon::BluntTwoClose:
boneName = "Bip01 BluntTwoClose";
break;
case ESM::Weapon::AxeTwoHand:
boneName = "Bip01 AxeTwoClose";
break;
case ESM::Weapon::BluntTwoWide:
boneName = "Bip01 BluntTwoWide";
break;
case ESM::Weapon::SpearTwoWide:
boneName = "Bip01 SpearTwoWide";
break;
case ESM::Weapon::MarksmanBow:
boneName = "Bip01 MarksmanBow";
break;
case ESM::Weapon::MarksmanCrossbow:
boneName = "Bip01 MarksmanCrossbow";
break;
case ESM::Weapon::MarksmanThrown:
boneName = "Bip01 MarksmanThrown";
break;
default:
break;
}
return boneName;
}
void ActorAnimation::injectWeaponBones()
{
if (!mResourceSystem->getVFS()->exists("meshes\\xbase_anim_sh.nif"))
{
mWeaponSheathing = false;
return;
}
osg::ref_ptr<osg::Node> sheathSkeleton = mResourceSystem->getSceneManager()->getInstance("meshes\\xbase_anim_sh.nif");
for (unsigned int type=0; type<=ESM::Weapon::MarksmanThrown; ++type)
{
const std::string holsteredBoneName = getHolsteredWeaponBoneName(type);
SceneUtil::FindByNameVisitor findVisitor (holsteredBoneName);
sheathSkeleton->accept(findVisitor);
osg::ref_ptr<osg::Node> sheathNode = findVisitor.mFoundNode;
if (sheathNode && sheathNode.get()->getNumParents())
{
osg::Group* sheathParent = getBoneByName(sheathNode.get()->getParent(0)->getName());
if (sheathParent)
{
sheathNode.get()->getParent(0)->removeChild(sheathNode);
sheathParent->addChild(sheathNode);
}
}
}
}
// To make sure we do not run morph controllers for weapons, i.e. bows
class EmptyCallback : public osg::NodeCallback
{
public:
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
{
}
};
void ActorAnimation::updateHolsteredWeapon(bool showHolsteredWeapons)
{
if (!mWeaponSheathing)
return;
if (!mPtr.getClass().hasInventoryStore(mPtr))
return;
mScabbard.reset();
const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr);
MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
if (weapon == inv.end() || weapon->getTypeName() != typeid(ESM::Weapon).name())
return;
// Since throwing weapons stack themselves, do not show such weapon itself
if (weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanThrown)
showHolsteredWeapons = false;
std::string mesh = weapon->getClass().getModel(*weapon);
std::string scabbardName = mesh;
std::string boneName = getHolsteredWeaponBoneName(*weapon);
if (mesh.empty() || boneName.empty())
return;
// If the scabbard is not found, use a weapon mesh as fallback
scabbardName = scabbardName.replace(scabbardName.size()-4, 4, "_sh.nif");
bool isEnchanted = !weapon->getClass().getEnchantment(*weapon).empty();
if(!mResourceSystem->getVFS()->exists(scabbardName))
{
if (showHolsteredWeapons)
{
osg::Vec4f glowColor = getEnchantmentColor(*weapon);
mScabbard = getWeaponPart(mesh, boneName, isEnchanted, &glowColor);
if (mScabbard)
mScabbard->getNode()->setUpdateCallback(new EmptyCallback);
}
return;
}
mScabbard = getWeaponPart(scabbardName, boneName);
osg::Group* weaponNode = getBoneByName("Bip01 Weapon");
if (!weaponNode)
return;
// When we draw weapon, hide the Weapon node from sheath model.
// Otherwise add the enchanted glow to it.
if (!showHolsteredWeapons)
{
weaponNode->setNodeMask(0);
}
else
{
// If mesh author declared empty weapon node, use transformation from this node, but use the common weapon mesh.
// This approach allows to tweak weapon position without need to store the whole weapon mesh in the _sh file.
if (!weaponNode->getNumChildren())
{
osg::ref_ptr<osg::Node> fallbackNode = mResourceSystem->getSceneManager()->getInstance(mesh, weaponNode);
fallbackNode->setUpdateCallback(new EmptyCallback);
}
if (isEnchanted)
{
osg::Vec4f glowColor = getEnchantmentColor(*weapon);
addGlow(weaponNode, glowColor);
}
}
}
void ActorAnimation::updateQuiver()
{
if (!mWeaponSheathing)
return;
if (!mPtr.getClass().hasInventoryStore(mPtr))
return;
const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr);
MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
if(weapon == inv.end() || weapon->getTypeName() != typeid(ESM::Weapon).name())
return;
std::string mesh = weapon->getClass().getModel(*weapon);
std::string boneName = getHolsteredWeaponBoneName(*weapon);
if (mesh.empty() || boneName.empty())
return;
osg::Group* ammoNode = getBoneByName("Bip01 Ammo");
if (!ammoNode)
return;
// Special case for throwing weapons - they do not use ammo, but they stack themselves
bool suitableAmmo = false;
MWWorld::ConstContainerStoreIterator ammo = weapon;
unsigned int ammoCount = 0;
if (weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanThrown)
{
ammoCount = ammo->getRefData().getCount();
osg::Group* throwingWeaponNode = getBoneByName("Weapon Bone");
if (throwingWeaponNode && throwingWeaponNode->getNumChildren())
ammoCount--;
suitableAmmo = true;
}
else
{
ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition);
if (ammo == inv.end())
return;
ammoCount = ammo->getRefData().getCount();
bool arrowAttached = isArrowAttached();
if (arrowAttached)
ammoCount--;
if (weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow)
suitableAmmo = ammo->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::Bolt;
else if (weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanBow)
suitableAmmo = ammo->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::Arrow;
}
if (ammoNode && suitableAmmo)
{
// We should not show more ammo than equipped and more than quiver mesh has
ammoCount = std::min(ammoCount, ammoNode->getNumChildren());
// Remove existing ammo nodes
for (unsigned int i=0; i<ammoNode->getNumChildren(); ++i)
{
osg::ref_ptr<osg::Group> arrowNode = ammoNode->getChild(i)->asGroup();
if (!arrowNode->getNumChildren())
continue;
osg::ref_ptr<osg::Node> arrowChildNode = arrowNode->getChild(0);
arrowNode->removeChild(arrowChildNode);
}
// Add new ones
osg::Vec4f glowColor = getEnchantmentColor(*ammo);
std::string model = ammo->getClass().getModel(*ammo);
for (unsigned int i=0; i<ammoCount; ++i)
{
osg::ref_ptr<osg::Group> arrowNode = ammoNode->getChild(i)->asGroup();
osg::ref_ptr<osg::Node> arrow = mResourceSystem->getSceneManager()->getInstance(model, arrowNode);
if (!ammo->getClass().getEnchantment(*ammo).empty())
addGlow(arrow, glowColor);
}
}
// recreate shaders for invisible actors, otherwise new nodes will be visible
if (mAlpha != 1.f)
mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot);
}
void ActorAnimation::itemAdded(const MWWorld::ConstPtr& item, int /*count*/)
@ -63,6 +371,24 @@ void ActorAnimation::itemAdded(const MWWorld::ConstPtr& item, int /*count*/)
addHiddenItemLight(item, light);
}
}
if (!mPtr.getClass().hasInventoryStore(mPtr))
return;
// If the count of equipped ammo or throwing weapon was changed, we should update quiver
const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr);
MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
if(weapon == inv.end() || weapon->getTypeName() != typeid(ESM::Weapon).name())
return;
MWWorld::ConstContainerStoreIterator ammo = inv.end();
if (weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanThrown)
ammo = weapon;
else
ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition);
if(ammo != inv.end() && item.getCellRef().getRefId() == ammo->getCellRef().getRefId())
updateQuiver();
}
void ActorAnimation::itemRemoved(const MWWorld::ConstPtr& item, int /*count*/)
@ -78,6 +404,24 @@ void ActorAnimation::itemRemoved(const MWWorld::ConstPtr& item, int /*count*/)
}
}
}
if (!mPtr.getClass().hasInventoryStore(mPtr))
return;
// If the count of equipped ammo or throwing weapon was changed, we should update quiver
const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr);
MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
if(weapon == inv.end() || weapon->getTypeName() != typeid(ESM::Weapon).name())
return;
MWWorld::ConstContainerStoreIterator ammo = inv.end();
if (weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanThrown)
ammo = weapon;
else
ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition);
if(ammo != inv.end() && item.getCellRef().getRefId() == ammo->getCellRef().getRefId())
updateQuiver();
}
void ActorAnimation::addHiddenItemLight(const MWWorld::ConstPtr& item, const ESM::Light* esmLight)

View file

@ -6,6 +6,7 @@
#include <osg/ref_ptr>
#include "../mwworld/containerstore.hpp"
#include "../mwworld/inventorystore.hpp"
#include "animation.hpp"
@ -36,6 +37,24 @@ class ActorAnimation : public Animation, public MWWorld::ContainerStoreListener
virtual void itemAdded(const MWWorld::ConstPtr& item, int count);
virtual void itemRemoved(const MWWorld::ConstPtr& item, int count);
virtual bool isArrowAttached() const { return false; }
protected:
bool mWeaponSheathing;
osg::Group* getBoneByName(std::string boneName);
virtual void updateHolsteredWeapon(bool showHolsteredWeapons);
virtual void injectWeaponBones();
virtual void updateQuiver();
virtual std::string getHolsteredWeaponBoneName(const MWWorld::ConstPtr& weapon);
virtual std::string getHolsteredWeaponBoneName(const unsigned int weaponType);
virtual PartHolderPtr getWeaponPart(const std::string& model, const std::string& bonename, bool enchantedGlow, osg::Vec4f* glowColor);
virtual PartHolderPtr getWeaponPart(const std::string& model, const std::string& bonename)
{
osg::Vec4f stubColor = osg::Vec4f(0,0,0,0);
return getWeaponPart(model, bonename, false, &stubColor);
};
PartHolderPtr mScabbard;
private:
void addHiddenItemLight(const MWWorld::ConstPtr& item, const ESM::Light* esmLight);

View file

@ -0,0 +1,99 @@
#include "actorspaths.hpp"
#include "vismask.hpp"
#include <components/sceneutil/agentpath.hpp>
#include <osg/PositionAttitudeTransform>
namespace MWRender
{
ActorsPaths::ActorsPaths(const osg::ref_ptr<osg::Group>& root, bool enabled)
: mRootNode(root)
, mEnabled(enabled)
{
}
ActorsPaths::~ActorsPaths()
{
if (mEnabled)
disable();
}
bool ActorsPaths::toggle()
{
if (mEnabled)
disable();
else
enable();
return mEnabled;
}
void ActorsPaths::update(const MWWorld::ConstPtr& actor, const std::deque<osg::Vec3f>& path,
const osg::Vec3f& halfExtents, const osg::Vec3f& start, const osg::Vec3f& end,
const DetourNavigator::Settings& settings)
{
if (!mEnabled)
return;
const auto group = mGroups.find(actor);
if (group != mGroups.end())
mRootNode->removeChild(group->second);
const auto newGroup = SceneUtil::createAgentPathGroup(path, halfExtents, start, end, settings);
if (newGroup)
{
newGroup->setNodeMask(Mask_Debug);
mRootNode->addChild(newGroup);
mGroups[actor] = newGroup;
}
}
void ActorsPaths::remove(const MWWorld::ConstPtr& actor)
{
const auto group = mGroups.find(actor);
if (group != mGroups.end())
{
mRootNode->removeChild(group->second);
mGroups.erase(group);
}
}
void ActorsPaths::removeCell(const MWWorld::CellStore* const store)
{
for (auto it = mGroups.begin(); it != mGroups.end(); )
{
if (it->first.getCell() == store)
{
mRootNode->removeChild(it->second);
it = mGroups.erase(it);
}
else
++it;
}
}
void ActorsPaths::updatePtr(const MWWorld::ConstPtr& old, const MWWorld::ConstPtr& updated)
{
const auto it = mGroups.find(old);
if (it == mGroups.end())
return;
auto group = std::move(it->second);
mGroups.erase(it);
mGroups.insert(std::make_pair(updated, std::move(group)));
}
void ActorsPaths::enable()
{
std::for_each(mGroups.begin(), mGroups.end(),
[&] (const Groups::value_type& v) { mRootNode->addChild(v.second); });
mEnabled = true;
}
void ActorsPaths::disable()
{
std::for_each(mGroups.begin(), mGroups.end(),
[&] (const Groups::value_type& v) { mRootNode->removeChild(v.second); });
mEnabled = false;
}
}

View file

@ -0,0 +1,51 @@
#ifndef OPENMW_MWRENDER_AGENTSPATHS_H
#define OPENMW_MWRENDER_AGENTSPATHS_H
#include <apps/openmw/mwworld/ptr.hpp>
#include <components/detournavigator/navigator.hpp>
#include <osg/ref_ptr>
#include <unordered_map>
#include <deque>
namespace osg
{
class Group;
}
namespace MWRender
{
class ActorsPaths
{
public:
ActorsPaths(const osg::ref_ptr<osg::Group>& root, bool enabled);
~ActorsPaths();
bool toggle();
void update(const MWWorld::ConstPtr& actor, const std::deque<osg::Vec3f>& path,
const osg::Vec3f& halfExtents, const osg::Vec3f& start, const osg::Vec3f& end,
const DetourNavigator::Settings& settings);
void remove(const MWWorld::ConstPtr& actor);
void removeCell(const MWWorld::CellStore* const store);
void updatePtr(const MWWorld::ConstPtr& old, const MWWorld::ConstPtr& updated);
void enable();
void disable();
private:
using Groups = std::map<MWWorld::ConstPtr, osg::ref_ptr<osg::Group>>;
osg::ref_ptr<osg::Group> mRootNode;
Groups mGroups;
bool mEnabled;
};
}
#endif

View file

@ -15,13 +15,13 @@
#include <components/debug/debuglog.hpp>
#include <components/nifosg/nifloader.hpp>
#include <components/resource/resourcesystem.hpp>
#include <components/resource/scenemanager.hpp>
#include <components/resource/keyframemanager.hpp>
#include <components/resource/imagemanager.hpp>
#include <components/misc/constants.hpp>
#include <components/nifosg/nifloader.hpp> // KeyframeHolder
#include <components/nifosg/controller.hpp>
@ -190,12 +190,6 @@ namespace
{
}
RemoveFinishedCallbackVisitor(int effectId)
: RemoveVisitor()
, mHasMagicEffects(false)
{
}
virtual void apply(osg::Node &node)
{
traverse(node);
@ -228,9 +222,6 @@ namespace
virtual void apply(osg::Geometry&)
{
}
private:
int mEffectId;
};
class RemoveCallbackVisitor : public RemoveVisitor
@ -1748,21 +1739,29 @@ namespace MWRender
if (alpha != 1.f)
{
osg::StateSet* stateset (new osg::StateSet);
// If we have an existing material for alpha transparency, just override alpha level
osg::StateSet* stateset = mObjectRoot->getOrCreateStateSet();
osg::Material* material = static_cast<osg::Material*>(stateset->getAttribute(osg::StateAttribute::MATERIAL));
if (material)
{
material->setAlpha(osg::Material::FRONT_AND_BACK, alpha);
}
else
{
osg::BlendFunc* blendfunc (new osg::BlendFunc);
stateset->setAttributeAndModes(blendfunc, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE);
osg::BlendFunc* blendfunc (new osg::BlendFunc);
stateset->setAttributeAndModes(blendfunc, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE);
// FIXME: overriding diffuse/ambient/emissive colors
material = new osg::Material;
material->setColorMode(osg::Material::OFF);
material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,alpha));
material->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1));
stateset->setAttributeAndModes(material, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE);
// FIXME: overriding diffuse/ambient/emissive colors
osg::Material* material (new osg::Material);
material->setColorMode(osg::Material::OFF);
material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,alpha));
material->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1));
stateset->setAttributeAndModes(material, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE);
mObjectRoot->setStateSet(stateset);
mObjectRoot->setStateSet(stateset);
mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot);
mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot);
}
}
else
{
@ -1798,9 +1797,12 @@ namespace MWRender
}
else
{
effect += 3;
float radius = effect * 66.f;
float linearAttenuation = 0.5f / effect;
// TODO: use global attenuation settings
// 1 pt of Light magnitude corresponds to 1 foot of radius
float radius = effect * std::ceil(Constants::UnitsPerFoot);
const float linearValue = 3.f; // Currently hardcoded: unmodified Morrowind attenuation settings
float linearAttenuation = linearValue / radius;
if (!mGlowLight || linearAttenuation != mGlowLight->getLight(0)->getLinearAttenuation())
{
@ -1822,7 +1824,8 @@ namespace MWRender
mGlowLight->setLight(light);
}
mGlowLight->setRadius(radius);
// Make the obvious cut-off a bit less obvious
mGlowLight->setRadius(radius * 3);
}
}

View file

@ -49,7 +49,12 @@ CreatureWeaponAnimation::CreatureWeaponAnimation(const MWWorld::Ptr &ptr, const
setObjectRoot(model, true, false, true);
if((ref->mBase->mFlags&ESM::Creature::Bipedal))
{
if (mWeaponSheathing)
injectWeaponBones();
addAnimSource("meshes\\xbase_anim.nif", model);
}
addAnimSource(model, model);
mPtr.getClass().getInventoryStore(mPtr).setInvListener(this, mPtr);
@ -84,6 +89,9 @@ void CreatureWeaponAnimation::updateParts()
mWeapon.reset();
mShield.reset();
updateHolsteredWeapon(!mShowWeapons);
updateQuiver();
if (mShowWeapons)
updatePart(mWeapon, MWWorld::InventoryStore::Slot_CarriedRight);
if (mShowCarriedLeft)
@ -157,14 +165,21 @@ void CreatureWeaponAnimation::updatePart(PartHolderPtr& scene, int slot)
}
}
bool CreatureWeaponAnimation::isArrowAttached() const
{
return mAmmunition != nullptr;
}
void CreatureWeaponAnimation::attachArrow()
{
WeaponAnimation::attachArrow(mPtr);
updateQuiver();
}
void CreatureWeaponAnimation::releaseArrow(float attackStrength)
{
WeaponAnimation::releaseArrow(mPtr, attackStrength);
updateQuiver();
}
osg::Group *CreatureWeaponAnimation::getArrowBone()

View file

@ -54,6 +54,8 @@ namespace MWRender
/// to indicate the facing orientation of the character.
virtual void setPitchFactor(float factor) { mPitchFactor = factor; }
protected:
virtual bool isArrowAttached() const;
private:
PartHolderPtr mWeapon;

View file

@ -0,0 +1,71 @@
#include "navmesh.hpp"
#include "vismask.hpp"
#include <components/sceneutil/navmesh.hpp>
#include <osg/PositionAttitudeTransform>
namespace MWRender
{
NavMesh::NavMesh(const osg::ref_ptr<osg::Group>& root, bool enabled)
: mRootNode(root)
, mEnabled(enabled)
, mGeneration(0)
, mRevision(0)
{
}
NavMesh::~NavMesh()
{
if (mEnabled)
disable();
}
bool NavMesh::toggle()
{
if (mEnabled)
disable();
else
enable();
return mEnabled;
}
void NavMesh::update(const dtNavMesh& navMesh, const std::size_t id,
const std::size_t generation, const std::size_t revision, const DetourNavigator::Settings& settings)
{
if (!mEnabled || (mId == id && mGeneration >= generation && mRevision >= revision))
return;
mId = id;
mGeneration = generation;
mRevision = revision;
if (mGroup)
mRootNode->removeChild(mGroup);
mGroup = SceneUtil::createNavMeshGroup(navMesh, settings);
if (mGroup)
{
mGroup->setNodeMask(Mask_Debug);
mRootNode->addChild(mGroup);
}
}
void NavMesh::reset()
{
if (mGroup)
mRootNode->removeChild(mGroup);
}
void NavMesh::enable()
{
if (mGroup)
mRootNode->addChild(mGroup);
mEnabled = true;
}
void NavMesh::disable()
{
reset();
mEnabled = false;
}
}

View file

@ -0,0 +1,43 @@
#ifndef OPENMW_MWRENDER_NAVMESH_H
#define OPENMW_MWRENDER_NAVMESH_H
#include <components/detournavigator/navigator.hpp>
#include <osg/ref_ptr>
namespace osg
{
class Group;
class Geometry;
}
namespace MWRender
{
class NavMesh
{
public:
NavMesh(const osg::ref_ptr<osg::Group>& root, bool enabled);
~NavMesh();
bool toggle();
void update(const dtNavMesh& navMesh, const std::size_t number, const std::size_t generation,
const std::size_t revision, const DetourNavigator::Settings& settings);
void reset();
void enable();
void disable();
private:
osg::ref_ptr<osg::Group> mRootNode;
bool mEnabled;
std::size_t mId = std::numeric_limits<std::size_t>::max();
std::size_t mGeneration;
std::size_t mRevision;
osg::ref_ptr<osg::Group> mGroup;
};
}
#endif

View file

@ -21,6 +21,8 @@
#include <components/sceneutil/skeleton.hpp>
#include <components/sceneutil/lightmanager.hpp>
#include <components/settings/settings.hpp>
#include <components/nifosg/nifloader.hpp> // TextKeyMapHolder
#include "../mwworld/esmstore.hpp"
@ -308,6 +310,12 @@ void NpcAnimation::setViewMode(NpcAnimation::ViewMode viewMode)
if(mViewMode == viewMode)
return;
// Disable weapon sheathing in the 1st-person mode
if (viewMode == VM_FirstPerson)
mWeaponSheathing = false;
else
mWeaponSheathing = Settings::Manager::getBool("weapon sheathing", "Game");
mViewMode = viewMode;
rebuild();
@ -389,6 +397,7 @@ void NpcAnimation::setRenderBin()
void NpcAnimation::rebuild()
{
mScabbard.reset();
updateNpcBase();
MWBase::Environment::get().getMechanicsManager()->forceStateUpdate(mPtr);
@ -460,6 +469,11 @@ void NpcAnimation::updateNpcBase()
setObjectRoot(smodel, true, true, false);
if (mWeaponSheathing)
injectWeaponBones();
updateParts();
if(!is1stPerson)
{
const std::string base = "meshes\\xbase_anim.nif";
@ -488,8 +502,6 @@ void NpcAnimation::updateNpcBase()
mObjectRoot->addCullCallback(new OverrideFieldOfViewCallback(mFirstPersonFieldOfView));
}
updateParts();
mWeaponAnimationTime->updateStartTime();
}
@ -899,7 +911,8 @@ void NpcAnimation::showWeapons(bool showWeapon)
attachArrow();
}
}
if (mAlpha != 1.f)
// Note: we will need to recreate shaders later if we use weapon sheathing anyway, so there is no point to update them here
if (mAlpha != 1.f && !mWeaponSheathing)
mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot);
}
else
@ -909,6 +922,9 @@ void NpcAnimation::showWeapons(bool showWeapon)
if (mPtr == MWMechanics::getPlayer())
MWBase::Environment::get().getWorld()->getPlayer().setAttackingOrSpell(false);
}
updateHolsteredWeapon(!mShowWeapons);
updateQuiver();
}
void NpcAnimation::showCarriedLeft(bool show)
@ -936,11 +952,13 @@ void NpcAnimation::showCarriedLeft(bool show)
void NpcAnimation::attachArrow()
{
WeaponAnimation::attachArrow(mPtr);
updateQuiver();
}
void NpcAnimation::releaseArrow(float attackStrength)
{
WeaponAnimation::releaseArrow(mPtr, attackStrength);
updateQuiver();
}
osg::Group* NpcAnimation::getArrowBone()
@ -1185,4 +1203,9 @@ void NpcAnimation::setAccurateAiming(bool enabled)
mAccurateAiming = enabled;
}
bool NpcAnimation::isArrowAttached() const
{
return mAmmunition != nullptr;
}
}

View file

@ -95,6 +95,7 @@ private:
protected:
virtual void addControllers();
virtual bool isArrowAttached() const;
public:
/**

View file

@ -46,6 +46,8 @@
#include <components/esm/loadcell.hpp>
#include <components/fallback/fallback.hpp>
#include <components/detournavigator/navigator.hpp>
#include <boost/algorithm/string.hpp>
#include "../mwworld/cellstore.hpp"
@ -63,6 +65,8 @@
#include "water.hpp"
#include "terrainstorage.hpp"
#include "util.hpp"
#include "navmesh.hpp"
#include "actorspaths.hpp"
namespace
{
@ -189,13 +193,16 @@ namespace MWRender
Resource::ResourceSystem* mResourceSystem;
};
RenderingManager::RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr<osg::Group> rootNode, Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue,
const Fallback::Map* fallback, const std::string& resourcePath)
RenderingManager::RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr<osg::Group> rootNode,
Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue,
const Fallback::Map* fallback, const std::string& resourcePath,
DetourNavigator::Navigator& navigator)
: mViewer(viewer)
, mRootNode(rootNode)
, mResourceSystem(resourceSystem)
, mWorkQueue(workQueue)
, mUnrefQueue(new SceneUtil::UnrefQueue)
, mNavigator(navigator)
, mLandFogStart(0.f)
, mLandFogEnd(std::numeric_limits<float>::max())
, mUnderwaterFogStart(0.f)
@ -228,6 +235,8 @@ namespace MWRender
mRootNode->addChild(mSceneRoot);
mNavMesh.reset(new NavMesh(mRootNode, Settings::Manager::getBool("enable nav mesh render", "Navigator")));
mActorsPaths.reset(new ActorsPaths(mRootNode, Settings::Manager::getBool("enable agents paths render", "Navigator")));
mPathgrid.reset(new Pathgrid(mRootNode));
mObjects.reset(new Objects(mResourceSystem, sceneRoot, mUnrefQueue.get()));
@ -465,6 +474,7 @@ namespace MWRender
void RenderingManager::removeCell(const MWWorld::CellStore *store)
{
mPathgrid->removeCell(store);
mActorsPaths->removeCell(store);
mObjects->removeCell(store);
if (store->getCell()->isExterior())
@ -482,7 +492,7 @@ namespace MWRender
{
mSky->setEnabled(enabled);
}
bool RenderingManager::toggleBorders()
{
mBorders = !mBorders;
@ -516,6 +526,14 @@ namespace MWRender
mViewer->getCamera()->setCullMask(mask);
return enabled;
}
else if (mode == Render_NavMesh)
{
return mNavMesh->toggle();
}
else if (mode == Render_ActorsPaths)
{
return mActorsPaths->toggle();
}
return false;
}
@ -581,6 +599,28 @@ namespace MWRender
mWater->update(dt);
}
const auto navMeshes = mNavigator.getNavMeshes();
auto it = navMeshes.begin();
for (std::size_t i = 0; it != navMeshes.end() && i < mNavMeshNumber; ++i)
++it;
if (it == navMeshes.end())
{
mNavMesh->reset();
}
else
{
try
{
const auto locked = it->second.lockConst();
mNavMesh->update(locked->getValue(), mNavMeshNumber, locked->getGeneration(),
locked->getNavMeshRevision(), mNavigator.getSettings());
}
catch (const std::exception& e)
{
Log(Debug::Error) << "NavMesh render update exception: " << e.what();
}
}
mCamera->update(dt, paused);
osg::Vec3f focal, cameraPos;
@ -642,6 +682,7 @@ namespace MWRender
void RenderingManager::removeObject(const MWWorld::Ptr &ptr)
{
mActorsPaths->remove(ptr);
mObjects->removeObject(ptr);
mWater->removeEmitter(ptr);
}
@ -749,8 +790,8 @@ namespace MWRender
osg::Vec3 directions[6] = {
rawCubemap ? osg::Vec3(1,0,0) : osg::Vec3(0,0,1),
osg::Vec3(0,0,-1),
osg::Vec3(-1,0,0),
osg::Vec3(0,0,-1),
osg::Vec3(-1,0,0),
rawCubemap ? osg::Vec3(0,0,1) : osg::Vec3(1,0,0),
osg::Vec3(0,1,0),
osg::Vec3(0,-1,0)};
@ -789,7 +830,7 @@ namespace MWRender
mFieldOfView = fovBackup;
if (rawCubemap) // for raw cubemap don't run on GPU, just merge the images
{
{
image->allocateImage(cubeSize * 6,cubeSize,images[0]->r(),images[0]->getPixelFormat(),images[0]->getDataType());
for (int i = 0; i < 6; ++i)
@ -797,7 +838,7 @@ namespace MWRender
return true;
}
// run on GPU now:
osg::ref_ptr<osg::TextureCubeMap> cubeTexture (new osg::TextureCubeMap);
@ -1025,6 +1066,7 @@ namespace MWRender
void RenderingManager::updatePtr(const MWWorld::Ptr &old, const MWWorld::Ptr &updated)
{
mObjects->updatePtr(old, updated);
mActorsPaths->updatePtr(old, updated);
}
void RenderingManager::spawnEffect(const std::string &model, const std::string &texture, const osg::Vec3f &worldPosition, float scale, bool isMagicVFX)
@ -1345,5 +1387,19 @@ namespace MWRender
return mTerrainStorage->getLandManager();
}
void RenderingManager::updateActorPath(const MWWorld::ConstPtr& actor, const std::deque<osg::Vec3f>& path,
const osg::Vec3f& halfExtents, const osg::Vec3f& start, const osg::Vec3f& end) const
{
mActorsPaths->update(actor, path, halfExtents, start, end, mNavigator.getSettings());
}
void RenderingManager::removeActorPath(const MWWorld::ConstPtr& actor) const
{
mActorsPaths->remove(actor);
}
void RenderingManager::setNavMeshNumber(const std::size_t value)
{
mNavMeshNumber = value;
}
}

View file

@ -12,6 +12,8 @@
#include "renderinginterface.hpp"
#include "rendermode.hpp"
#include <deque>
namespace osg
{
class Group;
@ -55,6 +57,12 @@ namespace SceneUtil
class UnrefQueue;
}
namespace DetourNavigator
{
class Navigator;
struct Settings;
}
namespace MWRender
{
@ -68,12 +76,16 @@ namespace MWRender
class Water;
class TerrainStorage;
class LandManager;
class NavMesh;
class ActorsPaths;
class RenderingManager : public MWRender::RenderingInterface
{
public:
RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr<osg::Group> rootNode, Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue,
const Fallback::Map* fallback, const std::string& resourcePath);
RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr<osg::Group> rootNode,
Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue,
const Fallback::Map* fallback, const std::string& resourcePath,
DetourNavigator::Navigator& navigator);
~RenderingManager();
MWRender::Objects& getObjects();
@ -211,6 +223,13 @@ namespace MWRender
bool toggleBorders();
void updateActorPath(const MWWorld::ConstPtr& actor, const std::deque<osg::Vec3f>& path,
const osg::Vec3f& halfExtents, const osg::Vec3f& start, const osg::Vec3f& end) const;
void removeActorPath(const MWWorld::ConstPtr& actor) const;
void setNavMeshNumber(const std::size_t value);
private:
void updateProjectionMatrix();
void updateTextureFiltering();
@ -235,6 +254,10 @@ namespace MWRender
osg::ref_ptr<osg::Light> mSunLight;
DetourNavigator::Navigator& mNavigator;
std::unique_ptr<NavMesh> mNavMesh;
std::size_t mNavMeshNumber = 0;
std::unique_ptr<ActorsPaths> mActorsPaths;
std::unique_ptr<Pathgrid> mPathgrid;
std::unique_ptr<Objects> mObjects;
std::unique_ptr<Water> mWater;

View file

@ -10,7 +10,9 @@ namespace MWRender
Render_Wireframe,
Render_Pathgrid,
Render_Water,
Render_Scene
Render_Scene,
Render_NavMesh,
Render_ActorsPaths,
};
}

View file

@ -401,11 +401,15 @@ public:
{
}
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
bool isUnderwater()
{
osg::Vec3f eyePoint = mCameraRelativeTransform->getLastEyePoint();
return mEnabled && eyePoint.z() < mWaterLevel;
}
if (mEnabled && eyePoint.z() < mWaterLevel)
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
{
if (isUnderwater())
return;
traverse(node, nv);
@ -1575,6 +1579,8 @@ void SkyManager::update(float duration)
mRainIntensityUniform->set((float) mWeatherAlpha);
}
switchUnderwaterRain();
if (mIsStorm)
{
osg::Quat quat;
@ -1626,6 +1632,15 @@ void SkyManager::updateRainParameters()
}
}
void SkyManager::switchUnderwaterRain()
{
if (!mRainParticleSystem)
return;
bool freeze = mUnderwaterSwitch->isUnderwater();
mRainParticleSystem->setFrozen(freeze);
}
void SkyManager::setWeather(const WeatherResult& weather)
{
if (!mCreated) return;

View file

@ -180,6 +180,7 @@ namespace MWRender
void createRain();
void destroyRain();
void switchUnderwaterRain();
void updateRainParameters();
Resource::SceneManager* mSceneManager;

View file

@ -455,5 +455,8 @@ op 0x2000304: Show
op 0x2000305: Show, explicit
op 0x2000306: OnActivate, explicit
op 0x2000307: ToggleBorders, tb
op 0x2000308: ToggleNavMesh
op 0x2000309: ToggleActorsPaths
op 0x200030a: SetNavMeshNumber
opcodes 0x2000308-0x3ffffff unused
opcodes 0x200030b-0x3ffffff unused

View file

@ -1317,6 +1317,53 @@ namespace MWScript
}
};
class OpToggleNavMesh : public Interpreter::Opcode0
{
public:
virtual void execute (Interpreter::Runtime& runtime)
{
bool enabled =
MWBase::Environment::get().getWorld()->toggleRenderMode (MWRender::Render_NavMesh);
runtime.getContext().report (enabled ?
"Navigation Mesh Rendering -> On" : "Navigation Mesh Rendering -> Off");
}
};
class OpToggleActorsPaths : public Interpreter::Opcode0
{
public:
virtual void execute (Interpreter::Runtime& runtime)
{
bool enabled =
MWBase::Environment::get().getWorld()->toggleRenderMode (MWRender::Render_ActorsPaths);
runtime.getContext().report (enabled ?
"Agents Paths Rendering -> On" : "Agents Paths Rendering -> Off");
}
};
class OpSetNavMeshNumberToRender : public Interpreter::Opcode0
{
public:
virtual void execute (Interpreter::Runtime& runtime)
{
const auto navMeshNumber = runtime[0].mInteger;
runtime.pop();
if (navMeshNumber < 0)
{
runtime.getContext().report("Invalid navmesh number: use not less than zero values");
return;
}
MWBase::Environment::get().getWorld()->setNavMeshNumberToRender(static_cast<std::size_t>(navMeshNumber));
}
};
void installOpcodes (Interpreter::Interpreter& interpreter)
{
interpreter.installSegment5 (Compiler::Misc::opcodeXBox, new OpXBox);
@ -1417,6 +1464,9 @@ namespace MWScript
interpreter.installSegment3 (Compiler::Misc::opcodeShowSceneGraph, new OpShowSceneGraph<ImplicitRef>);
interpreter.installSegment3 (Compiler::Misc::opcodeShowSceneGraphExplicit, new OpShowSceneGraph<ExplicitRef>);
interpreter.installSegment5 (Compiler::Misc::opcodeToggleBorders, new OpToggleBorders);
interpreter.installSegment5 (Compiler::Misc::opcodeToggleNavMesh, new OpToggleNavMesh);
interpreter.installSegment5 (Compiler::Misc::opcodeToggleActorsPaths, new OpToggleActorsPaths);
interpreter.installSegment5 (Compiler::Misc::opcodeSetNavMeshNumberToRender, new OpSetNavMeshNumberToRender);
}
}
}

View file

@ -738,8 +738,7 @@ namespace MWScript
virtual void execute (Interpreter::Runtime& runtime)
{
const MWWorld::Ptr ptr = MWMechanics::getPlayer();
MWBase::Environment::get().getWorld()->fixPosition(ptr);
MWBase::Environment::get().getWorld()->fixPosition();
}
};

View file

@ -82,7 +82,7 @@ bool FFmpeg_Decoder::getNextPacket()
}
/* Free the packet and look for another */
av_free_packet(&mPacket);
av_packet_unref(&mPacket);
}
return false;
@ -90,9 +90,9 @@ bool FFmpeg_Decoder::getNextPacket()
bool FFmpeg_Decoder::getAVAudioData()
{
int got_frame;
bool got_frame;
if((*mStream)->codec->codec_type != AVMEDIA_TYPE_AUDIO)
if(mCodecCtx->codec_type != AVMEDIA_TYPE_AUDIO)
return false;
do {
@ -100,19 +100,18 @@ bool FFmpeg_Decoder::getAVAudioData()
return false;
/* Decode some data, and check for errors */
int len = 0;
if((len=avcodec_decode_audio4((*mStream)->codec, mFrame, &got_frame, &mPacket)) < 0)
int ret = 0;
ret = avcodec_receive_frame(mCodecCtx, mFrame);
if (ret == 0)
got_frame = true;
if (ret == AVERROR(EAGAIN))
ret = 0;
if (ret == 0)
ret = avcodec_send_packet(mCodecCtx, &mPacket);
if (ret < 0 && ret != AVERROR(EAGAIN))
return false;
/* Move the unread data to the front and clear the end bits */
int remaining = mPacket.size - len;
if(remaining <= 0)
av_free_packet(&mPacket);
else
{
memmove(mPacket.data, &mPacket.data[len], remaining);
av_shrink_packet(&mPacket, remaining);
}
av_packet_unref(&mPacket);
if (!got_frame || mFrame->nb_samples == 0)
continue;
@ -139,8 +138,8 @@ bool FFmpeg_Decoder::getAVAudioData()
else
mFrameData = &mFrame->data[0];
} while(got_frame == 0 || mFrame->nb_samples == 0);
mNextPts += (double)mFrame->nb_samples / (double)(*mStream)->codec->sample_rate;
} while(!got_frame || mFrame->nb_samples == 0);
mNextPts += (double)mFrame->nb_samples / mCodecCtx->sample_rate;
return true;
}
@ -213,7 +212,7 @@ void FFmpeg_Decoder::open(const std::string &fname)
for(size_t j = 0;j < mFormatCtx->nb_streams;j++)
{
if(mFormatCtx->streams[j]->codec->codec_type == AVMEDIA_TYPE_AUDIO)
if(mFormatCtx->streams[j]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
{
mStream = &mFormatCtx->streams[j];
break;
@ -222,39 +221,48 @@ void FFmpeg_Decoder::open(const std::string &fname)
if(!mStream)
throw std::runtime_error("No audio streams in "+fname);
(*mStream)->codec->request_sample_fmt = (*mStream)->codec->sample_fmt;
AVCodec *codec = avcodec_find_decoder((*mStream)->codec->codec_id);
AVCodec *codec = avcodec_find_decoder((*mStream)->codecpar->codec_id);
if(!codec)
{
std::string ss = "No codec found for id " +
std::to_string((*mStream)->codec->codec_id);
std::to_string((*mStream)->codecpar->codec_id);
throw std::runtime_error(ss);
}
if(avcodec_open2((*mStream)->codec, codec, nullptr) < 0)
throw std::runtime_error(std::string("Failed to open audio codec ") +
codec->long_name);
AVCodecContext *avctx = avcodec_alloc_context3(codec);
avcodec_parameters_to_context(avctx, (*mStream)->codecpar);
// This is not needed anymore above FFMpeg version 4.0
#if LIBAVCODEC_VERSION_INT < 3805796
av_codec_set_pkt_timebase(avctx, (*mStream)->time_base);
#endif
mCodecCtx = avctx;
if(avcodec_open2(mCodecCtx, codec, nullptr) < 0)
throw std::runtime_error(std::string("Failed to open audio codec ") + codec->long_name);
mFrame = av_frame_alloc();
if((*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_FLT ||
(*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_FLTP)
if(mCodecCtx->sample_fmt == AV_SAMPLE_FMT_FLT || mCodecCtx->sample_fmt == AV_SAMPLE_FMT_FLTP)
mOutputSampleFormat = AV_SAMPLE_FMT_S16; // FIXME: Check for AL_EXT_FLOAT32 support
else if((*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_U8P)
else if(mCodecCtx->sample_fmt == AV_SAMPLE_FMT_U8P)
mOutputSampleFormat = AV_SAMPLE_FMT_U8;
else if((*mStream)->codec->sample_fmt == AV_SAMPLE_FMT_S16P)
else if(mCodecCtx->sample_fmt == AV_SAMPLE_FMT_S16P)
mOutputSampleFormat = AV_SAMPLE_FMT_S16;
else
mOutputSampleFormat = AV_SAMPLE_FMT_S16;
mOutputChannelLayout = (*mStream)->codec->channel_layout;
mOutputChannelLayout = (*mStream)->codecpar->channel_layout;
if(mOutputChannelLayout == 0)
mOutputChannelLayout = av_get_default_channel_layout((*mStream)->codec->channels);
mOutputChannelLayout = av_get_default_channel_layout(mCodecCtx->channels);
mCodecCtx->channel_layout = mOutputChannelLayout;
}
catch(...)
{
if(mStream)
avcodec_close((*mStream)->codec);
avcodec_free_context(&mCodecCtx);
mStream = nullptr;
if (mFormatCtx != nullptr)
@ -275,10 +283,10 @@ void FFmpeg_Decoder::open(const std::string &fname)
void FFmpeg_Decoder::close()
{
if(mStream)
avcodec_close((*mStream)->codec);
avcodec_free_context(&mCodecCtx);
mStream = nullptr;
av_free_packet(&mPacket);
av_packet_unref(&mPacket);
av_freep(&mFrame);
swr_free(&mSwr);
av_freep(&mDataBuf);
@ -308,7 +316,12 @@ void FFmpeg_Decoder::close()
std::string FFmpeg_Decoder::getName()
{
// In the FFMpeg 4.0 a "filename" field was replaced by "url"
#if LIBAVCODEC_VERSION_INT < 3805796
return mFormatCtx->filename;
#else
return mFormatCtx->url;
#endif
}
void FFmpeg_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType *type)
@ -341,11 +354,10 @@ void FFmpeg_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType *
else
{
char str[1024];
av_get_channel_layout_string(str, sizeof(str), (*mStream)->codec->channels,
(*mStream)->codec->channel_layout);
av_get_channel_layout_string(str, sizeof(str), mCodecCtx->channels, mCodecCtx->channel_layout);
Log(Debug::Error) << "Unsupported channel layout: "<< str;
if((*mStream)->codec->channels == 1)
if(mCodecCtx->channels == 1)
{
mOutputChannelLayout = AV_CH_LAYOUT_MONO;
*chans = ChannelConfig_Mono;
@ -357,27 +369,28 @@ void FFmpeg_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType *
}
}
*samplerate = (*mStream)->codec->sample_rate;
int64_t ch_layout = (*mStream)->codec->channel_layout;
*samplerate = mCodecCtx->sample_rate;
int64_t ch_layout = mCodecCtx->channel_layout;
if(ch_layout == 0)
ch_layout = av_get_default_channel_layout((*mStream)->codec->channels);
ch_layout = av_get_default_channel_layout(mCodecCtx->channels);
if(mOutputSampleFormat != (*mStream)->codec->sample_fmt ||
if(mOutputSampleFormat != mCodecCtx->sample_fmt ||
mOutputChannelLayout != ch_layout)
{
mSwr = swr_alloc_set_opts(mSwr, // SwrContext
mOutputChannelLayout, // output ch layout
mOutputSampleFormat, // output sample format
(*mStream)->codec->sample_rate, // output sample rate
mCodecCtx->sample_rate, // output sample rate
ch_layout, // input ch layout
(*mStream)->codec->sample_fmt, // input sample format
(*mStream)->codec->sample_rate, // input sample rate
mCodecCtx->sample_fmt, // input sample format
mCodecCtx->sample_rate, // input sample rate
0, // logging level offset
nullptr); // log context
if(!mSwr)
throw std::runtime_error("Couldn't allocate SwrContext");
if(swr_init(mSwr) < 0)
throw std::runtime_error("Couldn't initialize SwrContext");
int init=swr_init(mSwr);
if(init < 0)
throw std::runtime_error("Couldn't initialize SwrContext: "+std::to_string(init));
}
}
@ -412,7 +425,7 @@ size_t FFmpeg_Decoder::getSampleOffset()
{
int delay = (mFrameSize-mFramePos) / av_get_channel_layout_nb_channels(mOutputChannelLayout) /
av_get_bytes_per_sample(mOutputSampleFormat);
return (int)(mNextPts*(*mStream)->codec->sample_rate) - delay;
return (int)(mNextPts*mCodecCtx->sample_rate) - delay;
}
FFmpeg_Decoder::FFmpeg_Decoder(const VFS::Manager* vfs)
@ -437,7 +450,10 @@ FFmpeg_Decoder::FFmpeg_Decoder(const VFS::Manager* vfs)
static bool done_init = false;
if(!done_init)
{
// This is not needed anymore above FFMpeg version 4.0
#if LIBAVCODEC_VERSION_INT < 3805796
av_register_all();
#endif
av_log_set_level(AV_LOG_ERROR);
done_init = true;
}

View file

@ -6,18 +6,7 @@ extern "C"
{
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
// From libavutil version 52.2.0 and onward the declaration of
// AV_CH_LAYOUT_* is removed from libavcodec/avcodec.h and moved to
// libavutil/channel_layout.h
#if AV_VERSION_INT(52, 2, 0) <= AV_VERSION_INT(LIBAVUTIL_VERSION_MAJOR, \
LIBAVUTIL_VERSION_MINOR, LIBAVUTIL_VERSION_MICRO)
#include <libavutil/channel_layout.h>
#endif
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55,28,1)
#define av_frame_alloc avcodec_alloc_frame
#endif
#include <libavutil/channel_layout.h>
// From version 54.56 binkaudio encoding format changed from S16 to FLTP. See:
// https://gitorious.org/ffmpeg/ffmpeg/commit/7bfd1766d1c18f07b0a2dd042418a874d49ea60d
@ -38,6 +27,7 @@ namespace MWSound
class FFmpeg_Decoder final : public Sound_Decoder
{
AVFormatContext *mFormatCtx;
AVCodecContext *mCodecCtx;
AVStream **mStream;
AVPacket mPacket;

View file

@ -48,7 +48,7 @@ namespace MWSound
{
ssize_t clock_delay = (mFrameSize-mFramePos) / av_get_channel_layout_nb_channels(mOutputChannelLayout) /
av_get_bytes_per_sample(mOutputSampleFormat);
return (size_t)(mAudioClock*mAVStream->codec->sample_rate) - clock_delay;
return (size_t)(mAudioClock*mAudioContext->sample_rate) - clock_delay;
}
std::string getStreamName()
@ -61,7 +61,7 @@ namespace MWSound
virtual double getAudioClock()
{
return (double)getSampleOffset()/(double)mAVStream->codec->sample_rate -
return (double)getSampleOffset()/(double)mAudioContext->sample_rate -
MWBase::Environment::get().getSoundManager()->getTrackTimeDelay(mAudioTrack);
}

View file

@ -265,9 +265,10 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot
writer.save (stream);
Loading::Listener& listener = *MWBase::Environment::get().getWindowManager()->getLoadingScreen();
int messagesCount = MWBase::Environment::get().getWindowManager()->getMessagesCount();
// Using only Cells for progress information, since they typically have the largest records by far
listener.setProgressRange(MWBase::Environment::get().getWorld()->countSavedGameCells());
listener.setLabel("#{sNotifyMessage4}", true);
listener.setLabel("#{sNotifyMessage4}", true, messagesCount > 0);
Loading::ScopedLoad load(&listener);
@ -389,9 +390,10 @@ void MWState::StateManager::loadGame (const Character *character, const std::str
std::map<int, int> contentFileMap = buildContentFileIndexMap (reader);
Loading::Listener& listener = *MWBase::Environment::get().getWindowManager()->getLoadingScreen();
int messagesCount = MWBase::Environment::get().getWindowManager()->getMessagesCount();
listener.setProgressRange(100);
listener.setLabel("#{sLoadingMessage14}");
listener.setLabel("#{sLoadingMessage14}", false, messagesCount > 0);
Loading::ScopedLoad load(&listener);

View file

@ -70,6 +70,22 @@ namespace MWWorld
return mCellRef.mEnchantmentCharge;
}
float CellRef::getNormalizedEnchantmentCharge(int maxCharge) const
{
if (maxCharge == 0)
{
return 0;
}
else if (mCellRef.mEnchantmentCharge == -1)
{
return 1;
}
else
{
return mCellRef.mEnchantmentCharge / static_cast<float>(maxCharge);
}
}
void CellRef::setEnchantmentCharge(float charge)
{
if (charge != mCellRef.mEnchantmentCharge)

View file

@ -56,6 +56,9 @@ namespace MWWorld
// Remaining enchantment charge. This could be -1 if the charge was not touched yet (i.e. full).
float getEnchantmentCharge() const;
// Remaining enchantment charge rescaled to the supplied maximum charge (such as one of the enchantment).
float getNormalizedEnchantmentCharge(int maxCharge) const;
void setEnchantmentCharge(float charge);
// For weapon or armor, this is the remaining item health.

View file

@ -432,7 +432,7 @@ namespace MWWorld
mHasState = true;
}
int CellStore::count() const
std::size_t CellStore::count() const
{
return mMergedRefs.size();
}

View file

@ -240,7 +240,7 @@ namespace MWWorld
ESM::FogState* getFog () const;
int count() const;
std::size_t count() const;
///< Return total number of references, including deleted ones.
void load ();
@ -283,7 +283,7 @@ namespace MWWorld
/// \attention This function also lists deleted (count 0) objects!
/// \return Iteration completed?
template<class Visitor>
bool forEachConst (Visitor& visitor) const
bool forEachConst (Visitor&& visitor) const
{
if (mState != State_Loaded)
return false;

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