mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-03-14 01:19:59 +00:00
Merge branch 'master' into 'SHADER_HOT_RELOAD'
# Conflicts: # apps/openmw/mwrender/postprocessor.hpp
This commit is contained in:
commit
baadc06e98
@ -30,6 +30,8 @@ variables:
|
||||
- apt-cache/
|
||||
- ccache/
|
||||
stage: build
|
||||
variables:
|
||||
CMAKE_EXE_LINKER_FLAGS: -fuse-ld=mold
|
||||
script:
|
||||
- df -h
|
||||
- export CCACHE_BASEDIR="`pwd`"
|
||||
@ -61,28 +63,45 @@ variables:
|
||||
- build/install/
|
||||
|
||||
Coverity:
|
||||
extends: .Ubuntu_Image
|
||||
tags:
|
||||
- docker
|
||||
- linux
|
||||
image: ubuntu:20.04
|
||||
stage: build
|
||||
rules:
|
||||
- if: $CI_PIPELINE_SOURCE == "schedule"
|
||||
cache:
|
||||
key: Coverity.ubuntu_20.04.v1
|
||||
paths:
|
||||
- apt-cache/
|
||||
- ccache/
|
||||
variables:
|
||||
CCACHE_SIZE: 2G
|
||||
CC: clang
|
||||
CXX: clang++
|
||||
CMAKE_BUILD_TYPE: Debug
|
||||
CMAKE_CXX_FLAGS_DEBUG: -O0
|
||||
before_script:
|
||||
- CI/install_debian_deps.sh clang openmw-deps openmw-deps-dynamic
|
||||
- curl -o /tmp/cov-analysis-linux64.tgz https://scan.coverity.com/download/linux64 --form project=$COVERITY_SCAN_PROJECT_NAME --form token=$COVERITY_SCAN_TOKEN
|
||||
- CI/install_debian_deps.sh clang_ubuntu_20_04 openmw-deps openmw-deps-dynamic
|
||||
- curl -o /tmp/cov-analysis-linux64.tgz https://scan.coverity.com/download/linux64
|
||||
--form project=$COVERITY_SCAN_PROJECT_NAME --form token=$COVERITY_SCAN_TOKEN
|
||||
- tar xfz /tmp/cov-analysis-linux64.tgz
|
||||
script:
|
||||
- export CCACHE_BASEDIR="$(pwd)"
|
||||
- export CCACHE_DIR="$(pwd)/ccache"
|
||||
- mkdir -pv "${CCACHE_DIR}"
|
||||
- ccache -z -M "${CCACHE_SIZE}"
|
||||
- CI/before_script.linux.sh
|
||||
- cov-analysis-linux64-*/bin/cov-configure --template --comptype prefix --compiler ccache
|
||||
# Remove the specific targets and build everything once we can do it under 3h
|
||||
- cov-analysis-linux64-*/bin/cov-build --dir cov-int cmake --build build -- -j $(nproc) openmw esmtool bsatool niftest openmw-wizard openmw-launcher openmw-iniimporter openmw-essimporter openmw-navmeshtool openmw-cs
|
||||
- cov-analysis-linux64-*/bin/cov-build --dir cov-int cmake --build build -- -j $(nproc)
|
||||
- ccache -s
|
||||
after_script:
|
||||
- tar cfz cov-int.tar.gz cov-int
|
||||
- curl https://scan.coverity.com/builds?project=$COVERITY_SCAN_PROJECT_NAME
|
||||
--form token=$COVERITY_SCAN_TOKEN --form email=$GITLAB_USER_EMAIL
|
||||
--form file=@cov-int.tar.gz --form version="$CI_COMMIT_REF_NAME:$CI_COMMIT_SHORT_SHA"
|
||||
--form description="CI_COMMIT_SHORT_SHA / $CI_COMMIT_TITLE / $CI_COMMIT_REF_NAME:$CI_PIPELINE_ID"
|
||||
variables:
|
||||
CC: clang
|
||||
CXX: clang++
|
||||
CXXFLAGS: -O0
|
||||
artifacts:
|
||||
paths:
|
||||
- /builds/OpenMW/openmw/cov-int/build-log.txt
|
||||
@ -154,7 +173,7 @@ Ubuntu_GCC_tests_asan:
|
||||
BUILD_TESTS_ONLY: 1
|
||||
CMAKE_BUILD_TYPE: Debug
|
||||
CMAKE_CXX_FLAGS_DEBUG: -g -O1 -fno-omit-frame-pointer -fsanitize=address -fsanitize=pointer-compare -fsanitize=pointer-subtract -fsanitize=leak
|
||||
CMAKE_EXE_LINKER_FLAGS: -fsanitize=address -fsanitize=pointer-compare -fsanitize=pointer-subtract -fsanitize=leak
|
||||
CMAKE_EXE_LINKER_FLAGS: -fsanitize=address -fsanitize=pointer-compare -fsanitize=pointer-subtract -fsanitize=leak -fuse-ld=mold
|
||||
ASAN_OPTIONS: halt_on_error=1:strict_string_checks=1:detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1
|
||||
artifacts:
|
||||
paths: []
|
||||
@ -189,7 +208,7 @@ Ubuntu_GCC_tests_ubsan:
|
||||
BUILD_TESTS_ONLY: 1
|
||||
CMAKE_BUILD_TYPE: Debug
|
||||
CMAKE_CXX_FLAGS_DEBUG: -g -O2 -fno-omit-frame-pointer -fno-optimize-sibling-calls -fsanitize=thread -fPIE
|
||||
CMAKE_EXE_LINKER_FLAGS: -pthread -pie -fsanitize=thread
|
||||
CMAKE_EXE_LINKER_FLAGS: -pthread -pie -fsanitize=thread -fuse-ld=mold
|
||||
TSAN_OPTIONS: second_deadlock_stack=1:halt_on_error=1
|
||||
artifacts:
|
||||
paths: []
|
||||
|
23
CHANGELOG.md
23
CHANGELOG.md
@ -20,7 +20,7 @@
|
||||
Bug #4376: Moved actors don't respawn in their original cells
|
||||
Bug #4389: NPC's lips do not move if his head model has the NiBSAnimationNode root node
|
||||
Bug #4602: Robert's Bodies: crash inside createInstance()
|
||||
Bug #4700: Editor: Incorrect command implementation
|
||||
Bug #4700: OpenMW-CS: Incorrect command implementation
|
||||
Bug #4744: Invisible particles aren't always processed
|
||||
Bug #4949: Incorrect particle lighting
|
||||
Bug #5054: Non-biped creatures don't use spellcast equip/unequip animations
|
||||
@ -87,7 +87,7 @@
|
||||
Bug #6276: Deleted groundcover instances are not deleted in game
|
||||
Bug #6282: Laura craft doesn't follow the player character
|
||||
Bug #6283: Avis Dorsey follows you after her death
|
||||
Bug #6285: Brush template drawing and terrain selection drawing performance is very bad
|
||||
Bug #6285: OpenMW-CS: Brush template drawing and terrain selection drawing performance is very bad
|
||||
Bug #6289: Keyword search in dialogues expected the text to be all ASCII characters
|
||||
Bug #6291: Can't pickup the dead mage's journal from the mysterious hunter mod
|
||||
Bug #6302: Teleporting disabled actor breaks its disabled state
|
||||
@ -114,7 +114,6 @@
|
||||
Bug #6417: OpenMW doesn't always use the right node to accumulate movement
|
||||
Bug #6429: Wyrmhaven: Can't add AI packages to player
|
||||
Bug #6433: Items bound to Quick Keys sometimes do not appear until the Quick Key menu is opened
|
||||
Bug #6435: Add support for MSVC 2022
|
||||
Bug #6451: Weapon summoned from Cast When Used item will have the name "None"
|
||||
Bug #6473: Strings from NIF should be parsed only to first null terminator
|
||||
Bug #6493: Unlocking owned but not locked or unlocked containers is considered a crime
|
||||
@ -130,7 +129,6 @@
|
||||
Bug #6655: Constant effect absorb attribute causes the game to break
|
||||
Bug #6667: Pressing the Esc key while resting or waiting causes black screen.
|
||||
Bug #6670: Dialogue order is incorrect
|
||||
Bug #6672: Garbage object refs in groundcover plugins like Vurt's grass plugins
|
||||
Bug #6680: object.cpp handles nodetree unsafely, memory access with dangling pointer
|
||||
Bug #6682: HitOnMe doesn't fire as intended
|
||||
Bug #6697: Shaders vertex lighting incorrectly clamped
|
||||
@ -142,7 +140,6 @@
|
||||
Bug #6753: Info records without a DATA subrecords are loaded incorrectly
|
||||
Bug #6794: Light sources are attached to mesh bounds centers instead of mesh origins when AttachLight NiNode is missing
|
||||
Bug #6799: Game crashes if an NPC has no Class attached
|
||||
Bug #6818: ess-importer doesn't exactly reproduce camera
|
||||
Bug #6849: ImageButton texture is not scaled properly
|
||||
Bug #6869: Hits queue stagger during swing animation
|
||||
Bug #6890: SDL_PeepEvents errors are not handled
|
||||
@ -178,6 +175,7 @@
|
||||
Feature #6017: Separate persistent and temporary cell references when saving
|
||||
Feature #6019: Add antialias alpha test to the launcher or enable by default if possible
|
||||
Feature #6032: Reverse-z depth buffer
|
||||
Feature #6078: Do not clear depth buffer for first-person meshes
|
||||
Feature #6128: Soft Particles
|
||||
Feature #6171: In-game log viewer
|
||||
Feature #6189: Navigation mesh disk cache
|
||||
@ -196,23 +194,19 @@
|
||||
Feature #6557: Add support for controller gyroscope
|
||||
Feature #6592: Support for NiTriShape particle emitters
|
||||
Feature #6600: Support NiSortAdjustNode
|
||||
Feature #6631: Support FFMPEG 5
|
||||
Feature #6684: Support NiFltAnimationNode
|
||||
Feature #6699: Support Ignored flag
|
||||
Feature #6700: Support windowed fullscreen
|
||||
Feature #6706: Save the size of the Options window
|
||||
Feature #6721: [OpenMW-CS] Add option to open records in new window
|
||||
Feature #6721: OpenMW-CS: Add option to open records in new window
|
||||
Feature #6867: Add a way to localize hardcoded strings in GUI
|
||||
Feature #6888: Add switch for armor degradation fix.
|
||||
Task #6078: First person should not clear depth buffer
|
||||
Feature #6888: Add switch for armor degradation fix
|
||||
Feature #6925: Allow to use a mouse wheel to rotate a head in the race selection menu
|
||||
Task #6161: Refactor Sky to use shaders and be GLES/GL3 friendly
|
||||
Task #6162: Refactor GUI to use shaders and to be GLES and GL3+ friendly
|
||||
Task #6201: Remove the "Note: No relevant classes found. No output generated" warnings
|
||||
Task #6264: Remove the old classes in animation.cpp
|
||||
Task #6553: Simplify interpreter instruction registration
|
||||
Task #6435: Add support for MSVC 2022
|
||||
Task #6564: Remove predefined data paths `data="?global?data"`, `data=./data`
|
||||
Task #6631: Fix ffmpeg avio API usage causing hangs in ffmpeg version 5
|
||||
Task #6709: Move KeyframeController transformation magic to NifOsg::MatrixTransform
|
||||
Task #6763: gcc 12.1 multiple compiler warnings
|
||||
|
||||
0.47.0
|
||||
------
|
||||
@ -348,6 +342,7 @@
|
||||
Bug #6047: Mouse bindings can be triggered during save loading
|
||||
Bug #6136: Game freezes when NPCs try to open doors that are about to be closed
|
||||
Bug #6294: Game crashes with empty pathgrid
|
||||
Bug #6923: Dispose of corpse prevents respawning after load
|
||||
Feature #390: 3rd person look "over the shoulder"
|
||||
Feature #832: OpenMW-CS: Handle deleted references
|
||||
Feature #1536: Show more information about level on menu
|
||||
|
@ -11,8 +11,6 @@ BUILD_UNITTESTS=OFF
|
||||
BUILD_BENCHMARKS=OFF
|
||||
|
||||
if [[ "${BUILD_TESTS_ONLY}" ]]; then
|
||||
export GOOGLETEST_DIR="${PWD}/googletest/build/install"
|
||||
env GENERATOR='Unix Makefiles' CONFIGURATION=Release CI/build_googletest.sh
|
||||
BUILD_UNITTESTS=ON
|
||||
BUILD_BENCHMARKS=ON
|
||||
fi
|
||||
@ -28,9 +26,14 @@ declare -a CMAKE_CONF_OPTS=(
|
||||
-DUSE_SYSTEM_TINYXML=ON
|
||||
-DOPENMW_USE_SYSTEM_RECASTNAVIGATION=ON
|
||||
-DOPENMW_CXX_FLAGS="-Werror -Werror=implicit-fallthrough" # flags specific to OpenMW project
|
||||
-DCMAKE_EXE_LINKER_FLAGS="${CMAKE_EXE_LINKER_FLAGS} -fuse-ld=mold"
|
||||
)
|
||||
|
||||
if [[ "${CMAKE_EXE_LINKER_FLAGS}" ]]; then
|
||||
CMAKE_CONF_OPTS+=(
|
||||
-DCMAKE_EXE_LINKER_FLAGS="${CMAKE_EXE_LINKER_FLAGS}"
|
||||
)
|
||||
fi
|
||||
|
||||
if [[ $CI_OPENMW_USE_STATIC_DEPS ]]; then
|
||||
CMAKE_CONF_OPTS+=(
|
||||
-DOPENMW_USE_SYSTEM_MYGUI=OFF
|
||||
@ -99,8 +102,6 @@ if [[ "${BUILD_TESTS_ONLY}" ]]; then
|
||||
-DBUILD_NIFTEST=OFF \
|
||||
-DBUILD_UNITTESTS=${BUILD_UNITTESTS} \
|
||||
-DBUILD_BENCHMARKS=${BUILD_BENCHMARKS} \
|
||||
-DGTEST_ROOT="${GOOGLETEST_DIR}" \
|
||||
-DGMOCK_ROOT="${GOOGLETEST_DIR}" \
|
||||
..
|
||||
else
|
||||
${ANALYZE} cmake \
|
||||
|
@ -642,16 +642,6 @@ if [ -z $SKIP_DOWNLOAD ]; then
|
||||
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/LuaJIT-2.1.0-beta3-msvc${LUA_MSVC_YEAR}-win${BITS}.7z" \
|
||||
"LuaJIT-2.1.0-beta3-msvc${LUA_MSVC_YEAR}-win${BITS}.7z"
|
||||
|
||||
# Google test and mock
|
||||
if [ -n "$TEST_FRAMEWORK" ]; then
|
||||
echo "Google test 1.11.0..."
|
||||
if [ -d googletest ]; then
|
||||
printf " Google test exists, skipping."
|
||||
else
|
||||
git clone -b release-1.11.0 https://github.com/google/googletest.git
|
||||
fi
|
||||
fi
|
||||
|
||||
# ICU
|
||||
download "ICU ${ICU_VER/_/.}"\
|
||||
"https://github.com/unicode-org/icu/releases/download/release-${ICU_VER/_/-}/icu4c-${ICU_VER}-Win${BITS}-MSVC2019.zip" \
|
||||
@ -1003,61 +993,6 @@ printf "LuaJIT 2.1.0-beta3... "
|
||||
done
|
||||
echo Done.
|
||||
}
|
||||
cd $DEPS
|
||||
echo
|
||||
# Google Test and Google Mock
|
||||
if [ -n "$TEST_FRAMEWORK" ]; then
|
||||
printf "Google test 1.11.0 ..."
|
||||
|
||||
cd googletest
|
||||
mkdir -p build${MSVC_DISPLAY_YEAR}
|
||||
|
||||
cd build${MSVC_DISPLAY_YEAR}
|
||||
|
||||
GOOGLE_INSTALL_ROOT="${DEPS_INSTALL}/GoogleTest"
|
||||
|
||||
for CONFIGURATION in ${CONFIGURATIONS[@]}; do
|
||||
# FindGMock.cmake mentions Release explicitly, but not RelWithDebInfo. Only one optimised library config can be used, so go for the safer one.
|
||||
GTEST_CONFIG=$([ $CONFIGURATION == "RelWithDebInfo" ] && echo "Release" || echo "$CONFIGURATION" )
|
||||
if [ $GTEST_CONFIG == "Debug" ]; then
|
||||
DEBUG_SUFFIX="d"
|
||||
else
|
||||
DEBUG_SUFFIX=""
|
||||
fi
|
||||
|
||||
if [ ! -f "$GOOGLE_INSTALL_ROOT/lib/gtest${DEBUG_SUFFIX}.lib" ]; then
|
||||
# Always use MSBuild solution files as they don't need the environment activating
|
||||
cmake .. -DCMAKE_USE_WIN32_THREADS_INIT=1 -G "Visual Studio $MSVC_REAL_VER $MSVC_DISPLAY_YEAR" "-A $([ $BITS -eq 64 ] && echo "x64" || echo "Win32")" -DBUILD_SHARED_LIBS=1
|
||||
cmake --build . --config "${GTEST_CONFIG}"
|
||||
cmake --install . --config "${GTEST_CONFIG}" --prefix "${GOOGLE_INSTALL_ROOT}"
|
||||
fi
|
||||
|
||||
add_runtime_dlls $CONFIGURATION "${GOOGLE_INSTALL_ROOT}\bin\gtest_main${DEBUG_SUFFIX}.dll"
|
||||
add_runtime_dlls $CONFIGURATION "${GOOGLE_INSTALL_ROOT}\bin\gtest${DEBUG_SUFFIX}.dll"
|
||||
add_runtime_dlls $CONFIGURATION "${GOOGLE_INSTALL_ROOT}\bin\gmock_main${DEBUG_SUFFIX}.dll"
|
||||
add_runtime_dlls $CONFIGURATION "${GOOGLE_INSTALL_ROOT}\bin\gmock${DEBUG_SUFFIX}.dll"
|
||||
done
|
||||
|
||||
add_cmake_opts -DBUILD_UNITTESTS=yes
|
||||
# FindGTest and FindGMock do not work perfectly on Windows
|
||||
# but we can help them by telling them everything we know about installation
|
||||
add_cmake_opts -DGMOCK_ROOT="$GOOGLE_INSTALL_ROOT"
|
||||
add_cmake_opts -DGTEST_ROOT="$GOOGLE_INSTALL_ROOT"
|
||||
add_cmake_opts -DGTEST_LIBRARY="$GOOGLE_INSTALL_ROOT/lib/gtest.lib"
|
||||
add_cmake_opts -DGTEST_MAIN_LIBRARY="$GOOGLE_INSTALL_ROOT/lib/gtest_main.lib"
|
||||
add_cmake_opts -DGMOCK_LIBRARY="$GOOGLE_INSTALL_ROOT/lib/gmock.lib"
|
||||
add_cmake_opts -DGMOCK_MAIN_LIBRARY="$GOOGLE_INSTALL_ROOT/lib/gmock_main.lib"
|
||||
add_cmake_opts -DGTEST_LIBRARY_DEBUG="$GOOGLE_INSTALL_ROOT/lib/gtestd.lib"
|
||||
add_cmake_opts -DGTEST_MAIN_LIBRARY_DEBUG="$GOOGLE_INSTALL_ROOT/lib/gtest_maind.lib"
|
||||
add_cmake_opts -DGMOCK_LIBRARY_DEBUG="$GOOGLE_INSTALL_ROOT/lib/gmockd.lib"
|
||||
add_cmake_opts -DGMOCK_MAIN_LIBRARY_DEBUG="$GOOGLE_INSTALL_ROOT/lib/gmock_maind.lib"
|
||||
add_cmake_opts -DGTEST_LINKED_AS_SHARED_LIBRARY=True
|
||||
add_cmake_opts -DGTEST_LIBRARY_TYPE=SHARED
|
||||
add_cmake_opts -DGTEST_MAIN_LIBRARY_TYPE=SHARED
|
||||
|
||||
echo Done.
|
||||
|
||||
fi
|
||||
|
||||
cd $DEPS
|
||||
echo
|
||||
@ -1176,6 +1111,10 @@ if [ "${BUILD_BENCHMARKS}" ]; then
|
||||
add_cmake_opts -DBUILD_BENCHMARKS=ON
|
||||
fi
|
||||
|
||||
if [ -n "${TEST_FRAMEWORK}" ]; then
|
||||
add_cmake_opts -DBUILD_UNITTESTS=ON
|
||||
fi
|
||||
|
||||
if [ -n "$ACTIVATE_MSVC" ]; then
|
||||
echo -n "- Activating MSVC in the current shell... "
|
||||
command -v vswhere >/dev/null 2>&1 || { echo "Error: vswhere is not on the path."; wrappedExit 1; }
|
||||
|
@ -1,17 +0,0 @@
|
||||
#!/bin/sh -ex
|
||||
|
||||
git clone -b release-1.11.0 https://github.com/google/googletest.git
|
||||
cd googletest
|
||||
mkdir build
|
||||
cd build
|
||||
cmake \
|
||||
-D CMAKE_C_COMPILER="${CC}" \
|
||||
-D CMAKE_CXX_COMPILER="${CXX}" \
|
||||
-D CMAKE_C_COMPILER_LAUNCHER=ccache \
|
||||
-D CMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
||||
-D CMAKE_BUILD_TYPE="${CONFIGURATION}" \
|
||||
-D CMAKE_INSTALL_PREFIX="${GOOGLETEST_DIR}" \
|
||||
-G "${GENERATOR}" \
|
||||
..
|
||||
cmake --build . --config "${CONFIGURATION}" -- -j $(nproc)
|
||||
cmake --install . --config "${CONFIGURATION}"
|
@ -11,6 +11,7 @@ print_help() {
|
||||
declare -rA GROUPED_DEPS=(
|
||||
[gcc]="binutils gcc build-essential cmake ccache curl unzip git pkg-config mold"
|
||||
[clang]="binutils clang make cmake ccache curl unzip git pkg-config mold"
|
||||
[clang_ubuntu_20_04]="binutils clang make cmake ccache curl unzip git pkg-config"
|
||||
|
||||
# Common dependencies for building OpenMW.
|
||||
[openmw-deps]="
|
||||
|
@ -163,6 +163,7 @@ option(RECASTNAVIGATION_STATIC "Build recastnavigation static libraries" ${_reca
|
||||
option(OPENMW_USE_SYSTEM_SQLITE3 "Use system provided SQLite3 library" ON)
|
||||
|
||||
option(OPENMW_USE_SYSTEM_BENCHMARK "Use system Google Benchmark library." OFF)
|
||||
option(OPENMW_USE_SYSTEM_GOOGLETEST "Use system Google Test library." OFF)
|
||||
|
||||
option(OPENMW_UNITY_BUILD "Use fewer compilation units to speed up compile time" FALSE)
|
||||
option(OPENMW_LTO_BUILD "Build OpenMW with Link-Time Optimization (Needs ~2GB of RAM)" OFF)
|
||||
|
@ -112,7 +112,7 @@ namespace
|
||||
World,
|
||||
Gui,
|
||||
Lua,
|
||||
|
||||
LuaSyncUpdate,
|
||||
Number,
|
||||
};
|
||||
|
||||
@ -152,6 +152,10 @@ namespace
|
||||
template <>
|
||||
const UserStats UserStatsValue<UserStatsType::Lua>::sValue {"Lua", "lua"};
|
||||
|
||||
template <>
|
||||
const UserStats UserStatsValue<UserStatsType::LuaSyncUpdate>::sValue{ " -Sync", "luasyncupdate" };
|
||||
|
||||
|
||||
template <UserStatsType type>
|
||||
struct ForEachUserStatsValue
|
||||
{
|
||||
@ -336,10 +340,13 @@ bool OMW::Engine::frame(float frametime)
|
||||
|
||||
// Main menu opened? Then scripts are also paused.
|
||||
bool paused = mWindowManager->containsMode(MWGui::GM_MainMenu);
|
||||
|
||||
// Should be called after input manager update and before any change to the game world.
|
||||
// It applies to the game world queued changes from the previous frame.
|
||||
mLuaManager->synchronizedUpdate();
|
||||
|
||||
{
|
||||
ScopedProfile<UserStatsType::LuaSyncUpdate> profile(frameStart, frameNumber, *timer, *stats);
|
||||
// Should be called after input manager update and before any change to the game world.
|
||||
// It applies to the game world queued changes from the previous frame.
|
||||
mLuaManager->synchronizedUpdate();
|
||||
}
|
||||
|
||||
// update game state
|
||||
{
|
||||
|
@ -796,13 +796,13 @@ namespace MWClass
|
||||
return;
|
||||
}
|
||||
|
||||
if (ptr.getRefData().getCount() <= 0)
|
||||
const CreatureCustomData& customData = ptr.getRefData().getCustomData()->asCreatureCustomData();
|
||||
if (ptr.getRefData().getCount() <= 0 && (!isFlagBitSet(ptr, ESM::Creature::Respawn) || !customData.mCreatureStats.isDead()))
|
||||
{
|
||||
state.mHasCustomState = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const CreatureCustomData& customData = ptr.getRefData().getCustomData()->asCreatureCustomData();
|
||||
ESM::CreatureState& creatureState = state.asCreatureState();
|
||||
customData.mContainerStore->writeState (creatureState.mInventory);
|
||||
customData.mCreatureStats.writeState (creatureState.mCreatureStats);
|
||||
|
@ -1349,13 +1349,13 @@ namespace MWClass
|
||||
return;
|
||||
}
|
||||
|
||||
if (ptr.getRefData().getCount() <= 0)
|
||||
const NpcCustomData& customData = ptr.getRefData().getCustomData()->asNpcCustomData();
|
||||
if (ptr.getRefData().getCount() <= 0 && (!(ptr.get<ESM::NPC>()->mBase->mFlags & ESM::NPC::Respawn) || !customData.mNpcStats.isDead()))
|
||||
{
|
||||
state.mHasCustomState = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const NpcCustomData& customData = ptr.getRefData().getCustomData()->asNpcCustomData();
|
||||
ESM::NpcState& npcState = state.asNpcState();
|
||||
customData.mInventoryStore.writeState (npcState.mInventory);
|
||||
customData.mNpcStats.writeState (npcState.mNpcStats);
|
||||
|
@ -17,28 +17,6 @@
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/windowmanager.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
void saveChain()
|
||||
{
|
||||
auto* processor = MWBase::Environment::get().getWorld()->getPostProcessor();
|
||||
|
||||
std::vector<std::string> chain;
|
||||
|
||||
for (size_t i = 1; i < processor->getTechniques().size(); ++i)
|
||||
{
|
||||
auto technique = processor->getTechniques()[i];
|
||||
|
||||
if (!technique || technique->getDynamic())
|
||||
continue;
|
||||
|
||||
chain.push_back(technique->getName());
|
||||
}
|
||||
|
||||
Settings::Manager::setStringArray("chain", "Post Processing", chain);
|
||||
}
|
||||
}
|
||||
|
||||
namespace MWGui
|
||||
{
|
||||
void PostProcessorHud::ListWrapper::onKeyButtonPressed(MyGUI::KeyCode key, MyGUI::Char ch)
|
||||
@ -145,7 +123,7 @@ namespace MWGui
|
||||
processor->enableTechnique(technique);
|
||||
else
|
||||
processor->disableTechnique(technique);
|
||||
saveChain();
|
||||
processor->saveChain();
|
||||
}
|
||||
}
|
||||
|
||||
@ -178,7 +156,7 @@ namespace MWGui
|
||||
return;
|
||||
|
||||
if (processor->enableTechnique(technique, index) != MWRender::PostProcessor::Status_Error)
|
||||
saveChain();
|
||||
processor->saveChain();
|
||||
}
|
||||
}
|
||||
|
||||
@ -377,7 +355,7 @@ namespace MWGui
|
||||
{
|
||||
Gui::AutoSizedTextBox* divider = mConfigArea->createWidget<Gui::AutoSizedTextBox>("MW_UniformGroup", {0,0,0,34}, MyGUI::Align::Default);
|
||||
divider->setNeedMouseFocus(false);
|
||||
divider->setCaption(uniform->mHeader);
|
||||
divider->setCaptionWithReplacing(uniform->mHeader);
|
||||
}
|
||||
|
||||
fx::Widgets::UniformBase* uwidget = mConfigArea->createWidget<fx::Widgets::UniformBase>("MW_UniformEdit", {0,0,0,22}, MyGUI::Align::Default);
|
||||
|
@ -56,6 +56,8 @@ namespace MWGui
|
||||
setText("AppearanceT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sRaceMenu1", "Appearance"));
|
||||
getWidget(mPreviewImage, "PreviewImage");
|
||||
|
||||
mPreviewImage->eventMouseWheel += MyGUI::newDelegate(this, &RaceDialog::onPreviewScroll);
|
||||
|
||||
getWidget(mHeadRotate, "HeadRotate");
|
||||
|
||||
mHeadRotate->setScrollRange(1000);
|
||||
@ -211,6 +213,19 @@ namespace MWGui
|
||||
eventBack();
|
||||
}
|
||||
|
||||
void RaceDialog::onPreviewScroll(MyGUI::Widget*, int _delta)
|
||||
{
|
||||
size_t oldPos = mHeadRotate->getScrollPosition();
|
||||
size_t maxPos = mHeadRotate->getScrollRange() - 1;
|
||||
size_t scrollPage = mHeadRotate->getScrollWheelPage();
|
||||
if (_delta < 0)
|
||||
mHeadRotate->setScrollPosition(oldPos + std::min(maxPos - oldPos, scrollPage));
|
||||
else
|
||||
mHeadRotate->setScrollPosition(oldPos - std::min(oldPos, scrollPage));
|
||||
|
||||
onHeadRotate(mHeadRotate, mHeadRotate->getScrollPosition());
|
||||
}
|
||||
|
||||
void RaceDialog::onHeadRotate(MyGUI::ScrollBar* scroll, size_t _position)
|
||||
{
|
||||
float angle = (float(_position) / (scroll->getScrollRange()-1) - 0.5f) * osg::PI * 2;
|
||||
|
@ -66,6 +66,7 @@ namespace MWGui
|
||||
EventHandle_WindowBase eventDone;
|
||||
|
||||
protected:
|
||||
void onPreviewScroll(MyGUI::Widget* _sender, int _delta);
|
||||
void onHeadRotate(MyGUI::ScrollBar* _sender, size_t _position);
|
||||
|
||||
void onSelectPreviousGender(MyGUI::Widget* _sender);
|
||||
|
@ -56,7 +56,7 @@ namespace MWGui
|
||||
if (effectId != -1)
|
||||
{
|
||||
const ESM::MagicEffect *magicEffect =
|
||||
store.get<ESM::MagicEffect>().search(effectId);
|
||||
store.get<ESM::MagicEffect>().find(effectId);
|
||||
std::string effectIDStr = ESM::MagicEffect::effectIdToString(effectId);
|
||||
std::string fullEffectName = wm->getGameSettingString(effectIDStr, "");
|
||||
|
||||
|
@ -670,25 +670,26 @@ namespace MWMechanics
|
||||
}
|
||||
}
|
||||
|
||||
short unsigned AiWander::getRandomIdle()
|
||||
int AiWander::getRandomIdle() const
|
||||
{
|
||||
unsigned short idleRoll = 0;
|
||||
short unsigned selectedAnimation = 0;
|
||||
MWBase::World* world = MWBase::Environment::get().getWorld();
|
||||
static const float fIdleChanceMultiplier = world->getStore().get<ESM::GameSetting>().find("fIdleChanceMultiplier")->mValue.getFloat();
|
||||
if (Misc::Rng::rollClosedProbability(world->getPrng()) > fIdleChanceMultiplier)
|
||||
return 0;
|
||||
|
||||
for(unsigned int counter = 0; counter < mIdle.size(); counter++)
|
||||
int newIdle = 0;
|
||||
float maxRoll = 0.f;
|
||||
for (size_t i = 0; i < mIdle.size(); i++)
|
||||
{
|
||||
MWBase::World* world = MWBase::Environment::get().getWorld();
|
||||
static float fIdleChanceMultiplier = world->getStore().get<ESM::GameSetting>().find("fIdleChanceMultiplier")->mValue.getFloat();
|
||||
|
||||
unsigned short idleChance = static_cast<unsigned short>(fIdleChanceMultiplier * mIdle[counter]);
|
||||
unsigned short randSelect = (int)(Misc::Rng::rollProbability(world->getPrng()) * int(100 / fIdleChanceMultiplier));
|
||||
if(randSelect < idleChance && randSelect > idleRoll)
|
||||
float roll = Misc::Rng::rollClosedProbability(world->getPrng()) * 100.f;
|
||||
if (roll <= mIdle[i] && roll > maxRoll)
|
||||
{
|
||||
selectedAnimation = counter + GroupIndex_MinIdle;
|
||||
idleRoll = randSelect;
|
||||
newIdle = GroupIndex_MinIdle + i;
|
||||
maxRoll = roll;
|
||||
}
|
||||
}
|
||||
return selectedAnimation;
|
||||
|
||||
return newIdle;
|
||||
}
|
||||
|
||||
void AiWander::fastForward(const MWWorld::Ptr& actor, AiState &state)
|
||||
|
@ -112,7 +112,7 @@ namespace MWMechanics
|
||||
/// @return Success or error
|
||||
bool playIdle(const MWWorld::Ptr& actor, unsigned short idleSelect);
|
||||
bool checkIdle(const MWWorld::Ptr& actor, unsigned short idleSelect);
|
||||
short unsigned getRandomIdle();
|
||||
int getRandomIdle() const;
|
||||
void setPathToAnAllowedNode(const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos);
|
||||
void evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage);
|
||||
void doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage);
|
||||
|
@ -307,7 +307,7 @@ void CharacterController::resetCurrentHitState()
|
||||
void CharacterController::resetCurrentWeaponState()
|
||||
{
|
||||
clearStateAnimation(mCurrentWeapon);
|
||||
mUpperBodyState = UpperCharState_Nothing;
|
||||
mUpperBodyState = UpperBodyState::None;
|
||||
}
|
||||
|
||||
void CharacterController::resetCurrentDeathState()
|
||||
@ -401,15 +401,15 @@ void CharacterController::refreshHitRecoilAnims()
|
||||
{
|
||||
if (!mCurrentWeapon.empty())
|
||||
mAnimation->disable(mCurrentWeapon);
|
||||
if (mUpperBodyState > UpperCharState_WeapEquiped)
|
||||
if (mUpperBodyState > UpperBodyState::WeaponEquipped)
|
||||
{
|
||||
mUpperBodyState = UpperCharState_WeapEquiped;
|
||||
mUpperBodyState = UpperBodyState::WeaponEquipped;
|
||||
if (mWeaponType > ESM::Weapon::None)
|
||||
mAnimation->showWeapons(true);
|
||||
}
|
||||
else if (mUpperBodyState < UpperCharState_WeapEquiped)
|
||||
else if (mUpperBodyState < UpperBodyState::WeaponEquipped)
|
||||
{
|
||||
mUpperBodyState = UpperCharState_Nothing;
|
||||
mUpperBodyState = UpperBodyState::None;
|
||||
}
|
||||
}
|
||||
|
||||
@ -680,7 +680,7 @@ void CharacterController::refreshIdleAnims(CharacterState idle, bool force)
|
||||
{
|
||||
// FIXME: if one of the below states is close to their last animation frame (i.e. will be disabled in the coming update),
|
||||
// the idle animation should be displayed
|
||||
if (((mUpperBodyState != UpperCharState_Nothing && mUpperBodyState != UpperCharState_WeapEquiped)
|
||||
if (((mUpperBodyState != UpperBodyState::None && mUpperBodyState != UpperBodyState::WeaponEquipped)
|
||||
|| mMovementState != CharState_None || mHitState != CharState_None) && !mPtr.getClass().isBipedal(mPtr))
|
||||
{
|
||||
resetCurrentIdleState();
|
||||
@ -855,7 +855,7 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim
|
||||
getActiveWeapon(mPtr, &mWeaponType);
|
||||
if (mWeaponType != ESM::Weapon::None)
|
||||
{
|
||||
mUpperBodyState = UpperCharState_WeapEquiped;
|
||||
mUpperBodyState = UpperBodyState::WeaponEquipped;
|
||||
mCurrentWeapon = getWeaponAnimation(mWeaponType);
|
||||
}
|
||||
|
||||
@ -1071,7 +1071,7 @@ void CharacterController::updatePtr(const MWWorld::Ptr &ptr)
|
||||
|
||||
void CharacterController::updateIdleStormState(bool inwater) const
|
||||
{
|
||||
if (!mAnimation->hasAnimation("idlestorm") || mUpperBodyState != UpperCharState_Nothing || inwater)
|
||||
if (!mAnimation->hasAnimation("idlestorm") || mUpperBodyState != UpperBodyState::None || inwater)
|
||||
{
|
||||
mAnimation->disable("idlestorm");
|
||||
return;
|
||||
@ -1113,7 +1113,7 @@ bool CharacterController::updateCarriedLeftVisible(const int weaptype) const
|
||||
return mAnimation->updateCarriedLeftVisible(weaptype);
|
||||
}
|
||||
|
||||
bool CharacterController::updateWeaponState(CharacterState idle)
|
||||
bool CharacterController::updateWeaponState()
|
||||
{
|
||||
const auto world = MWBase::Environment::get().getWorld();
|
||||
auto& prng = world->getPrng();
|
||||
@ -1166,20 +1166,18 @@ bool CharacterController::updateWeaponState(CharacterState idle)
|
||||
bool forcestateupdate = false;
|
||||
|
||||
// We should not play equipping animation and sound during weapon->weapon transition
|
||||
const bool isStillWeapon = weaptype != ESM::Weapon::HandToHand && weaptype != ESM::Weapon::Spell && weaptype != ESM::Weapon::None &&
|
||||
mWeaponType != ESM::Weapon::HandToHand && mWeaponType != ESM::Weapon::Spell && mWeaponType != ESM::Weapon::None;
|
||||
const bool isStillWeapon = isRealWeapon(mWeaponType) && isRealWeapon(weaptype);
|
||||
|
||||
// If the current weapon type was changed in the middle of attack (e.g. by Equip console command or when bound spell expires),
|
||||
// we should force actor to the "weapon equipped" state, interrupt attack and update animations.
|
||||
if (isStillWeapon && mWeaponType != weaptype && mUpperBodyState > UpperCharState_WeapEquiped)
|
||||
if (isStillWeapon && mWeaponType != weaptype && mUpperBodyState > UpperBodyState::WeaponEquipped)
|
||||
{
|
||||
forcestateupdate = true;
|
||||
if (!mCurrentWeapon.empty())
|
||||
mAnimation->disable(mCurrentWeapon);
|
||||
mUpperBodyState = UpperCharState_WeapEquiped;
|
||||
mUpperBodyState = UpperBodyState::WeaponEquipped;
|
||||
setAttackingOrSpell(false);
|
||||
mAnimation->showWeapons(true);
|
||||
stats.setAttackingOrSpell(false);
|
||||
}
|
||||
|
||||
if(!isKnockedOut() && !isKnockedDown() && !isRecovery())
|
||||
@ -1187,7 +1185,7 @@ bool CharacterController::updateWeaponState(CharacterState idle)
|
||||
std::string weapgroup;
|
||||
if ((!isWerewolf || mWeaponType != ESM::Weapon::Spell)
|
||||
&& weaptype != mWeaponType
|
||||
&& mUpperBodyState != UpperCharState_UnEquipingWeap
|
||||
&& mUpperBodyState != UpperBodyState::Unequipping
|
||||
&& !isStillWeapon)
|
||||
{
|
||||
// We can not play un-equip animation if weapon changed since last update
|
||||
@ -1209,7 +1207,7 @@ bool CharacterController::updateWeaponState(CharacterState idle)
|
||||
|
||||
mAnimation->play(weapgroup, priorityWeapon, unequipMask, false,
|
||||
1.0f, "unequip start", "unequip stop", 0.0f, 0);
|
||||
mUpperBodyState = UpperCharState_UnEquipingWeap;
|
||||
mUpperBodyState = UpperBodyState::Unequipping;
|
||||
|
||||
mAnimation->detachArrow();
|
||||
|
||||
@ -1247,7 +1245,8 @@ bool CharacterController::updateWeaponState(CharacterState idle)
|
||||
|
||||
if (!isStillWeapon)
|
||||
{
|
||||
clearStateAnimation(mCurrentWeapon);
|
||||
if (animPlaying)
|
||||
mAnimation->disable(mCurrentWeapon);
|
||||
if (weaptype != ESM::Weapon::None)
|
||||
{
|
||||
mAnimation->showWeapons(false);
|
||||
@ -1262,15 +1261,18 @@ bool CharacterController::updateWeaponState(CharacterState idle)
|
||||
|
||||
mAnimation->play(weapgroup, priorityWeapon, equipMask, true,
|
||||
1.0f, "equip start", "equip stop", 0.0f, 0);
|
||||
mUpperBodyState = UpperCharState_EquipingWeap;
|
||||
mUpperBodyState = UpperBodyState::Equipping;
|
||||
|
||||
// If we do not have the "equip attach" key, show weapon manually.
|
||||
if (weaptype != ESM::Weapon::Spell)
|
||||
if (weaptype != ESM::Weapon::Spell && mAnimation->getTextKeyTime(weapgroup+": equip attach") < 0)
|
||||
{
|
||||
if (mAnimation->getTextKeyTime(weapgroup+": equip attach") < 0)
|
||||
mAnimation->showWeapons(true);
|
||||
mAnimation->showWeapons(true);
|
||||
}
|
||||
}
|
||||
if (!upSoundId.empty())
|
||||
{
|
||||
sndMgr->playSound3D(mPtr, upSoundId, 1.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
if(isWerewolf)
|
||||
@ -1286,18 +1288,13 @@ bool CharacterController::updateWeaponState(CharacterState idle)
|
||||
mWeaponType = weaptype;
|
||||
mCurrentWeapon = weapgroup;
|
||||
|
||||
if(!upSoundId.empty() && !isStillWeapon)
|
||||
{
|
||||
sndMgr->playSound3D(mPtr, upSoundId, 1.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure that we disabled unequipping animation
|
||||
if (mUpperBodyState == UpperCharState_UnEquipingWeap)
|
||||
if (mUpperBodyState == UpperBodyState::Unequipping)
|
||||
{
|
||||
resetCurrentWeaponState();
|
||||
mWeaponType = ESM::Weapon::None;
|
||||
mCurrentWeapon = getWeaponAnimation(mWeaponType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1317,29 +1314,43 @@ bool CharacterController::updateWeaponState(CharacterState idle)
|
||||
sndMgr->stopSound3D(mPtr, "WolfRun");
|
||||
}
|
||||
|
||||
// Cancel attack if we no longer have ammunition
|
||||
bool ammunition = true;
|
||||
bool isWeapon = false;
|
||||
float weapSpeed = 1.f;
|
||||
if (cls.hasInventoryStore(mPtr))
|
||||
{
|
||||
MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr);
|
||||
MWWorld::ConstContainerStoreIterator weapon = getActiveWeapon(mPtr, &weaptype);
|
||||
isWeapon = (weapon != inv.end() && weapon->getType() == ESM::Weapon::sRecordId);
|
||||
if (isWeapon)
|
||||
if (stats.getDrawState() == DrawState::Weapon && !mWeapon.isEmpty() && mWeapon.getType() == ESM::Weapon::sRecordId)
|
||||
{
|
||||
weapSpeed = weapon->get<ESM::Weapon>()->mBase->mData.mSpeed;
|
||||
weapSpeed = mWeapon.get<ESM::Weapon>()->mBase->mData.mSpeed;
|
||||
MWWorld::ConstContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition);
|
||||
int ammotype = getWeaponType(weapon->get<ESM::Weapon>()->mBase->mData.mType)->mAmmoType;
|
||||
if (ammotype != ESM::Weapon::None && (ammo == inv.end() || ammo->get<ESM::Weapon>()->mBase->mData.mType != ammotype))
|
||||
ammunition = false;
|
||||
int ammotype = getWeaponType(mWeapon.get<ESM::Weapon>()->mBase->mData.mType)->mAmmoType;
|
||||
if (ammotype != ESM::Weapon::None)
|
||||
ammunition = ammo != inv.end() && ammo->get<ESM::Weapon>()->mBase->mData.mType == ammotype;
|
||||
}
|
||||
|
||||
if (!ammunition && mUpperBodyState > UpperCharState_WeapEquiped)
|
||||
// Cancel attack if we no longer have ammunition
|
||||
if (!ammunition)
|
||||
{
|
||||
if (!mCurrentWeapon.empty())
|
||||
if (mUpperBodyState == UpperBodyState::AttackPreWindUp || mUpperBodyState == UpperBodyState::AttackWindUp)
|
||||
{
|
||||
mAnimation->disable(mCurrentWeapon);
|
||||
mUpperBodyState = UpperCharState_WeapEquiped;
|
||||
mUpperBodyState = UpperBodyState::WeaponEquipped;
|
||||
}
|
||||
setAttackingOrSpell(false);
|
||||
}
|
||||
|
||||
MWWorld::ConstContainerStoreIterator torch = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
|
||||
if (torch != inv.end() && torch->getType() == ESM::Light::sRecordId && updateCarriedLeftVisible(mWeaponType))
|
||||
{
|
||||
if (mAnimation->isPlaying("shield"))
|
||||
mAnimation->disable("shield");
|
||||
|
||||
mAnimation->play("torch", Priority_Torch, MWRender::Animation::BlendMask_LeftArm,
|
||||
false, 1.0f, "start", "stop", 0.0f, std::numeric_limits<size_t>::max(), true);
|
||||
}
|
||||
else if (mAnimation->isPlaying("torch"))
|
||||
{
|
||||
mAnimation->disable("torch");
|
||||
}
|
||||
}
|
||||
|
||||
@ -1352,15 +1363,13 @@ bool CharacterController::updateWeaponState(CharacterState idle)
|
||||
ESM::WeaponType::Class weapclass = getWeaponType(mWeaponType)->mWeaponClass;
|
||||
if(getAttackingOrSpell())
|
||||
{
|
||||
bool resetIdle = ammunition;
|
||||
if(mUpperBodyState == UpperCharState_WeapEquiped && (mHitState == CharState_None || mHitState == CharState_Block))
|
||||
bool resetIdle = true;
|
||||
if (mUpperBodyState == UpperBodyState::WeaponEquipped && (mHitState == CharState_None || mHitState == CharState_Block))
|
||||
{
|
||||
mAttackStrength = 0;
|
||||
|
||||
// Randomize attacks for non-bipedal creatures
|
||||
if (cls.getType() == ESM::Creature::sRecordId &&
|
||||
!cls.isBipedal(mPtr) &&
|
||||
(!mAnimation->hasAnimation(mCurrentWeapon) || isRandomAttackAnimation(mCurrentWeapon)))
|
||||
if (!cls.isBipedal(mPtr) && (!mAnimation->hasAnimation(mCurrentWeapon) || isRandomAttackAnimation(mCurrentWeapon)))
|
||||
{
|
||||
mCurrentWeapon = chooseRandomAttackAnimation();
|
||||
}
|
||||
@ -1411,7 +1420,7 @@ bool CharacterController::updateWeaponState(CharacterState idle)
|
||||
resetIdle = false;
|
||||
// Spellcasting animation needs to "play" for at least one frame to reset the aiming factor
|
||||
animPlaying = true;
|
||||
mUpperBodyState = UpperCharState_CastingSpell;
|
||||
mUpperBodyState = UpperBodyState::Casting;
|
||||
}
|
||||
// Play the spellcasting animation/VFX if the spellcasting was successful or failed due to insufficient magicka.
|
||||
// Used up powers are exempt from this from some reason.
|
||||
@ -1485,65 +1494,53 @@ bool CharacterController::updateWeaponState(CharacterState idle)
|
||||
MWRender::Animation::BlendMask_All, true,
|
||||
1, startKey, stopKey,
|
||||
0.0f, 0);
|
||||
mUpperBodyState = UpperCharState_CastingSpell;
|
||||
mUpperBodyState = UpperBodyState::Casting;
|
||||
}
|
||||
else
|
||||
{
|
||||
resetIdle = false;
|
||||
}
|
||||
}
|
||||
else if(mWeaponType == ESM::Weapon::PickProbe)
|
||||
else
|
||||
{
|
||||
world->breakInvisibility(mPtr);
|
||||
MWWorld::ContainerStoreIterator weapon = cls.getInventoryStore(mPtr).getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
|
||||
MWWorld::Ptr item = *weapon;
|
||||
// TODO: this will only work for the player, and needs to be fixed if NPCs should ever use lockpicks/probes.
|
||||
MWWorld::Ptr target = world->getFacedObject();
|
||||
std::string resultMessage, resultSound;
|
||||
std::string startKey = "start";
|
||||
std::string stopKey = "stop";
|
||||
bool autodisable = false;
|
||||
|
||||
if(!target.isEmpty())
|
||||
if (mWeaponType == ESM::Weapon::PickProbe)
|
||||
{
|
||||
if(item.getType() == ESM::Lockpick::sRecordId)
|
||||
Security(mPtr).pickLock(target, item, resultMessage, resultSound);
|
||||
else if(item.getType() == ESM::Probe::sRecordId)
|
||||
Security(mPtr).probeTrap(target, item, resultMessage, resultSound);
|
||||
}
|
||||
mAnimation->play(mCurrentWeapon, priorityWeapon,
|
||||
MWRender::Animation::BlendMask_All, true,
|
||||
1.0f, "start", "stop", 0.0, 0);
|
||||
mUpperBodyState = UpperCharState_FollowStartToFollowStop;
|
||||
autodisable = true;
|
||||
mUpperBodyState = UpperBodyState::AttackEnd;
|
||||
|
||||
if(!resultMessage.empty())
|
||||
MWBase::Environment::get().getWindowManager()->messageBox(resultMessage);
|
||||
if(!resultSound.empty())
|
||||
sndMgr->playSound3D(target, resultSound, 1.0f, 1.0f);
|
||||
}
|
||||
else if (ammunition)
|
||||
{
|
||||
std::string startKey;
|
||||
std::string stopKey;
|
||||
world->breakInvisibility(mPtr);
|
||||
// TODO: this will only work for the player, and needs to be fixed if NPCs should ever use lockpicks/probes.
|
||||
MWWorld::Ptr target = world->getFacedObject();
|
||||
std::string resultMessage, resultSound;
|
||||
|
||||
if(weapclass == ESM::WeaponType::Ranged || weapclass == ESM::WeaponType::Thrown)
|
||||
{
|
||||
mAttackType = "shoot";
|
||||
startKey = mAttackType+" start";
|
||||
stopKey = mAttackType+" min attack";
|
||||
if(!target.isEmpty())
|
||||
{
|
||||
if (mWeapon.getType() == ESM::Lockpick::sRecordId)
|
||||
Security(mPtr).pickLock(target, mWeapon, resultMessage, resultSound);
|
||||
else if (mWeapon.getType() == ESM::Probe::sRecordId)
|
||||
Security(mPtr).probeTrap(target, mWeapon, resultMessage, resultSound);
|
||||
}
|
||||
|
||||
if (!resultMessage.empty())
|
||||
MWBase::Environment::get().getWindowManager()->messageBox(resultMessage);
|
||||
if (!resultSound.empty())
|
||||
sndMgr->playSound3D(target, resultSound, 1.0f, 1.0f);
|
||||
}
|
||||
else if (isRandomAttackAnimation(mCurrentWeapon))
|
||||
else if (!isRandomAttackAnimation(mCurrentWeapon))
|
||||
{
|
||||
startKey = "start";
|
||||
stopKey = "stop";
|
||||
}
|
||||
else
|
||||
{
|
||||
if(mPtr == getPlayer())
|
||||
if (weapclass == ESM::WeaponType::Ranged || weapclass == ESM::WeaponType::Thrown)
|
||||
mAttackType = "shoot";
|
||||
else if (mPtr == getPlayer())
|
||||
{
|
||||
if (Settings::Manager::getBool("best attack", "Game"))
|
||||
{
|
||||
if (isWeapon)
|
||||
if (!mWeapon.isEmpty() && mWeapon.getType() == ESM::Weapon::sRecordId)
|
||||
{
|
||||
MWWorld::ConstContainerStoreIterator weapon = cls.getInventoryStore(mPtr).getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
|
||||
mAttackType = getBestAttack(weapon->get<ESM::Weapon>()->mBase);
|
||||
mAttackType = getBestAttack(mWeapon.get<ESM::Weapon>()->mBase);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -1562,12 +1559,12 @@ bool CharacterController::updateWeaponState(CharacterState idle)
|
||||
}
|
||||
|
||||
mAnimation->play(mCurrentWeapon, priorityWeapon,
|
||||
MWRender::Animation::BlendMask_All, false,
|
||||
MWRender::Animation::BlendMask_All, autodisable,
|
||||
weapSpeed, startKey, stopKey,
|
||||
0.0f, 0);
|
||||
if(mAnimation->getCurrentTime(mCurrentWeapon) != -1.f)
|
||||
if (mWeaponType != ESM::Weapon::PickProbe && mAnimation->getCurrentTime(mCurrentWeapon) != -1.f)
|
||||
{
|
||||
mUpperBodyState = UpperCharState_StartToMinAttack;
|
||||
mUpperBodyState = UpperBodyState::AttackPreWindUp;
|
||||
if (isRandomAttackAnimation(mCurrentWeapon))
|
||||
{
|
||||
mAttackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability(prng));
|
||||
@ -1578,25 +1575,34 @@ bool CharacterController::updateWeaponState(CharacterState idle)
|
||||
}
|
||||
|
||||
// We should not break swim and sneak animations
|
||||
if (resetIdle &&
|
||||
idle != CharState_IdleSneak && idle != CharState_IdleSwim &&
|
||||
mIdleState != CharState_IdleSneak && mIdleState != CharState_IdleSwim)
|
||||
if (resetIdle && mIdleState != CharState_IdleSneak && mIdleState != CharState_IdleSwim)
|
||||
{
|
||||
resetCurrentIdleState();
|
||||
}
|
||||
|
||||
if (!animPlaying)
|
||||
animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete);
|
||||
if(mUpperBodyState == UpperCharState_MinAttackToMaxAttack && !isKnockedDown())
|
||||
mAttackStrength = complete;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
if (!animPlaying)
|
||||
animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete);
|
||||
if(mUpperBodyState == UpperCharState_MinAttackToMaxAttack && !isKnockedDown())
|
||||
|
||||
if (isKnockedDown())
|
||||
{
|
||||
if (mUpperBodyState > UpperBodyState::WeaponEquipped)
|
||||
{
|
||||
mUpperBodyState = UpperBodyState::WeaponEquipped;
|
||||
if (mWeaponType > ESM::Weapon::None)
|
||||
mAnimation->showWeapons(true);
|
||||
}
|
||||
if (!mCurrentWeapon.empty())
|
||||
mAnimation->disable(mCurrentWeapon);
|
||||
}
|
||||
|
||||
if (mUpperBodyState == UpperBodyState::AttackWindUp)
|
||||
{
|
||||
mAttackStrength = complete;
|
||||
|
||||
if (!getAttackingOrSpell())
|
||||
{
|
||||
world->breakInvisibility(mPtr);
|
||||
float attackStrength = complete;
|
||||
float minAttackTime = mAnimation->getTextKeyTime(mCurrentWeapon+": "+mAttackType+" "+"min attack");
|
||||
float maxAttackTime = mAnimation->getTextKeyTime(mCurrentWeapon+": "+mAttackType+" "+"max attack");
|
||||
if (minAttackTime == maxAttackTime)
|
||||
@ -1604,44 +1610,20 @@ bool CharacterController::updateWeaponState(CharacterState idle)
|
||||
// most creatures don't actually have an attack wind-up animation, so use a uniform random value
|
||||
// (even some creatures that can use weapons don't have a wind-up animation either, e.g. Rieklings)
|
||||
// Note: vanilla MW uses a random value for *all* non-player actors, but we probably don't need to go that far.
|
||||
attackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability(prng));
|
||||
mAttackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability(prng));
|
||||
}
|
||||
|
||||
if(weapclass != ESM::WeaponType::Ranged && weapclass != ESM::WeaponType::Thrown)
|
||||
{
|
||||
if(isWerewolf)
|
||||
{
|
||||
const MWWorld::ESMStore &store = world->getStore();
|
||||
const ESM::Sound *sound = store.get<ESM::Sound>().searchRandom("WolfSwing", prng);
|
||||
if(sound)
|
||||
sndMgr->playSound3D(mPtr, sound->mId, 1.0f, 1.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
playSwishSound(attackStrength);
|
||||
}
|
||||
}
|
||||
mAttackStrength = attackStrength;
|
||||
playSwishSound(mAttackStrength);
|
||||
|
||||
mAnimation->disable(mCurrentWeapon);
|
||||
if (animPlaying)
|
||||
mAnimation->disable(mCurrentWeapon);
|
||||
mAnimation->play(mCurrentWeapon, priorityWeapon,
|
||||
MWRender::Animation::BlendMask_All, false,
|
||||
weapSpeed, mAttackType+" max attack", mAttackType+" min hit",
|
||||
1.0f-complete, 0);
|
||||
|
||||
complete = 0.f;
|
||||
mUpperBodyState = UpperCharState_MaxAttackToMinHit;
|
||||
}
|
||||
else if (isKnockedDown())
|
||||
{
|
||||
if (mUpperBodyState > UpperCharState_WeapEquiped)
|
||||
{
|
||||
mUpperBodyState = UpperCharState_WeapEquiped;
|
||||
if (mWeaponType > ESM::Weapon::None)
|
||||
mAnimation->showWeapons(true);
|
||||
}
|
||||
if (!mCurrentWeapon.empty())
|
||||
mAnimation->disable(mCurrentWeapon);
|
||||
mUpperBodyState = UpperBodyState::AttackRelease;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1650,15 +1632,15 @@ bool CharacterController::updateWeaponState(CharacterState idle)
|
||||
{
|
||||
switch (mUpperBodyState)
|
||||
{
|
||||
case UpperCharState_StartToMinAttack:
|
||||
case UpperBodyState::AttackPreWindUp:
|
||||
mAnimation->setPitchFactor(complete);
|
||||
break;
|
||||
case UpperCharState_MinAttackToMaxAttack:
|
||||
case UpperCharState_MaxAttackToMinHit:
|
||||
case UpperCharState_MinHitToHit:
|
||||
case UpperBodyState::AttackWindUp:
|
||||
case UpperBodyState::AttackRelease:
|
||||
case UpperBodyState::AttackHit:
|
||||
mAnimation->setPitchFactor(1.f);
|
||||
break;
|
||||
case UpperCharState_FollowStartToFollowStop:
|
||||
case UpperBodyState::AttackEnd:
|
||||
if (animPlaying)
|
||||
{
|
||||
// technically we do not need a pitch for crossbow reload animation,
|
||||
@ -1676,39 +1658,39 @@ bool CharacterController::updateWeaponState(CharacterState idle)
|
||||
|
||||
if(!animPlaying)
|
||||
{
|
||||
if(mUpperBodyState == UpperCharState_EquipingWeap ||
|
||||
mUpperBodyState == UpperCharState_FollowStartToFollowStop ||
|
||||
mUpperBodyState == UpperCharState_CastingSpell)
|
||||
if (mUpperBodyState == UpperBodyState::Equipping ||
|
||||
mUpperBodyState == UpperBodyState::AttackEnd ||
|
||||
mUpperBodyState == UpperBodyState::Casting)
|
||||
{
|
||||
if (ammunition && mWeaponType == ESM::Weapon::MarksmanCrossbow)
|
||||
mAnimation->attachArrow();
|
||||
|
||||
// Cancel stagger animation at the end of an attack to avoid abrupt transitions
|
||||
// in favor of a different abrupt transition, like Morrowind
|
||||
if (mUpperBodyState != UpperCharState_EquipingWeap && isRecovery())
|
||||
if (mUpperBodyState != UpperBodyState::Equipping && isRecovery())
|
||||
mAnimation->disable(mCurrentHit);
|
||||
|
||||
mUpperBodyState = UpperCharState_WeapEquiped;
|
||||
mUpperBodyState = UpperBodyState::WeaponEquipped;
|
||||
}
|
||||
else if(mUpperBodyState == UpperCharState_UnEquipingWeap)
|
||||
mUpperBodyState = UpperCharState_Nothing;
|
||||
else if (mUpperBodyState == UpperBodyState::Unequipping)
|
||||
mUpperBodyState = UpperBodyState::None;
|
||||
}
|
||||
else if(complete >= 1.0f && !isRandomAttackAnimation(mCurrentWeapon))
|
||||
{
|
||||
std::string start, stop;
|
||||
switch(mUpperBodyState)
|
||||
{
|
||||
case UpperCharState_MinAttackToMaxAttack:
|
||||
case UpperBodyState::AttackWindUp:
|
||||
//hack to avoid body pos desync when jumping/sneaking in 'max attack' state
|
||||
if(!mAnimation->isPlaying(mCurrentWeapon))
|
||||
mAnimation->play(mCurrentWeapon, priorityWeapon,
|
||||
MWRender::Animation::BlendMask_All, false,
|
||||
0, mAttackType+" min attack", mAttackType+" max attack", 0.999f, 0);
|
||||
break;
|
||||
case UpperCharState_StartToMinAttack:
|
||||
case UpperCharState_MaxAttackToMinHit:
|
||||
case UpperBodyState::AttackPreWindUp:
|
||||
case UpperBodyState::AttackRelease:
|
||||
{
|
||||
if (mUpperBodyState == UpperCharState_StartToMinAttack)
|
||||
if (mUpperBodyState == UpperBodyState::AttackPreWindUp)
|
||||
{
|
||||
// If actor is already stopped preparing attack, do not play the "min attack -> max attack" part.
|
||||
// Happens if the player did not hold the attack button.
|
||||
@ -1719,13 +1701,12 @@ bool CharacterController::updateWeaponState(CharacterState idle)
|
||||
{
|
||||
start = mAttackType+" min attack";
|
||||
stop = mAttackType+" max attack";
|
||||
mUpperBodyState = UpperCharState_MinAttackToMaxAttack;
|
||||
mUpperBodyState = UpperBodyState::AttackWindUp;
|
||||
break;
|
||||
}
|
||||
|
||||
world->breakInvisibility(mPtr);
|
||||
if(weapclass != ESM::WeaponType::Ranged && weapclass != ESM::WeaponType::Thrown)
|
||||
playSwishSound(0.0f);
|
||||
playSwishSound(0.0f);
|
||||
}
|
||||
|
||||
if(mAttackType == "shoot")
|
||||
@ -1738,10 +1719,10 @@ bool CharacterController::updateWeaponState(CharacterState idle)
|
||||
start = mAttackType+" min hit";
|
||||
stop = mAttackType+" hit";
|
||||
}
|
||||
mUpperBodyState = UpperCharState_MinHitToHit;
|
||||
mUpperBodyState = UpperBodyState::AttackHit;
|
||||
break;
|
||||
}
|
||||
case UpperCharState_MinHitToHit:
|
||||
case UpperBodyState::AttackHit:
|
||||
if(mAttackType == "shoot")
|
||||
{
|
||||
start = mAttackType+" follow start";
|
||||
@ -1757,57 +1738,26 @@ bool CharacterController::updateWeaponState(CharacterState idle)
|
||||
: (str < 1.0f) ? " medium follow stop"
|
||||
: " large follow stop");
|
||||
}
|
||||
mUpperBodyState = UpperCharState_FollowStartToFollowStop;
|
||||
mUpperBodyState = UpperBodyState::AttackEnd;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Note: apply crossbow reload animation only for upper body
|
||||
// since blending with movement animations can give weird result.
|
||||
if(!start.empty())
|
||||
{
|
||||
int mask = MWRender::Animation::BlendMask_All;
|
||||
if (mWeaponType == ESM::Weapon::MarksmanCrossbow)
|
||||
mask = MWRender::Animation::BlendMask_UpperBody;
|
||||
|
||||
bool autodisable = mUpperBodyState == UpperBodyState::AttackEnd;
|
||||
mAnimation->disable(mCurrentWeapon);
|
||||
if (mUpperBodyState == UpperCharState_FollowStartToFollowStop)
|
||||
mAnimation->play(mCurrentWeapon, priorityWeapon,
|
||||
mask, true,
|
||||
weapSpeed, start, stop, 0.0f, 0);
|
||||
else
|
||||
mAnimation->play(mCurrentWeapon, priorityWeapon,
|
||||
mask, false,
|
||||
weapSpeed, start, stop, 0.0f, 0);
|
||||
mAnimation->play(mCurrentWeapon, priorityWeapon, MWRender::Animation::BlendMask_All, autodisable, weapSpeed, start, stop, 0.0f, 0);
|
||||
}
|
||||
}
|
||||
else if(complete >= 1.0f && isRandomAttackAnimation(mCurrentWeapon))
|
||||
{
|
||||
clearStateAnimation(mCurrentWeapon);
|
||||
mUpperBodyState = UpperCharState_WeapEquiped;
|
||||
mUpperBodyState = UpperBodyState::WeaponEquipped;
|
||||
}
|
||||
|
||||
if (cls.hasInventoryStore(mPtr))
|
||||
{
|
||||
const MWWorld::InventoryStore& inv = cls.getInventoryStore(mPtr);
|
||||
MWWorld::ConstContainerStoreIterator torch = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
|
||||
if(torch != inv.end() && torch->getType() == ESM::Light::sRecordId
|
||||
&& updateCarriedLeftVisible(mWeaponType))
|
||||
{
|
||||
if (mAnimation->isPlaying("shield"))
|
||||
mAnimation->disable("shield");
|
||||
|
||||
mAnimation->play("torch", Priority_Torch, MWRender::Animation::BlendMask_LeftArm,
|
||||
false, 1.0f, "start", "stop", 0.0f, (~(size_t)0), true);
|
||||
}
|
||||
else if (mAnimation->isPlaying("torch"))
|
||||
{
|
||||
mAnimation->disable("torch");
|
||||
}
|
||||
}
|
||||
|
||||
mAnimation->setAccurateAiming(mUpperBodyState > UpperCharState_WeapEquiped);
|
||||
mAnimation->setAccurateAiming(mUpperBodyState > UpperBodyState::WeaponEquipped);
|
||||
|
||||
return forcestateupdate;
|
||||
}
|
||||
@ -2270,7 +2220,7 @@ void CharacterController::update(float duration)
|
||||
|
||||
if (!mSkipAnim)
|
||||
{
|
||||
refreshCurrentAnims(idlestate, movestate, jumpstate, updateWeaponState(idlestate));
|
||||
refreshCurrentAnims(idlestate, movestate, jumpstate, updateWeaponState());
|
||||
updateIdleStormState(inwater);
|
||||
}
|
||||
|
||||
@ -2567,8 +2517,8 @@ void CharacterController::forceStateUpdate()
|
||||
mCanCast = false;
|
||||
mCastingManualSpell = false;
|
||||
setAttackingOrSpell(false);
|
||||
if (mUpperBodyState != UpperCharState_Nothing)
|
||||
mUpperBodyState = UpperCharState_WeapEquiped;
|
||||
if (mUpperBodyState != UpperBodyState::None)
|
||||
mUpperBodyState = UpperBodyState::WeaponEquipped;
|
||||
|
||||
refreshCurrentAnims(mIdleState, mMovementState, mJumpState, true);
|
||||
|
||||
@ -2687,13 +2637,13 @@ bool CharacterController::isRandomAttackAnimation(std::string_view group)
|
||||
|
||||
bool CharacterController::isAttackPreparing() const
|
||||
{
|
||||
return mUpperBodyState == UpperCharState_StartToMinAttack ||
|
||||
mUpperBodyState == UpperCharState_MinAttackToMaxAttack;
|
||||
return mUpperBodyState == UpperBodyState::AttackPreWindUp ||
|
||||
mUpperBodyState == UpperBodyState::AttackWindUp;
|
||||
}
|
||||
|
||||
bool CharacterController::isCastingSpell() const
|
||||
{
|
||||
return mCastingManualSpell || mUpperBodyState == UpperCharState_CastingSpell;
|
||||
return mCastingManualSpell || mUpperBodyState == UpperBodyState::Casting;
|
||||
}
|
||||
|
||||
bool CharacterController::isReadyToBlock() const
|
||||
@ -2729,8 +2679,7 @@ bool CharacterController::isRecovery() const
|
||||
|
||||
bool CharacterController::isAttackingOrSpell() const
|
||||
{
|
||||
return mUpperBodyState != UpperCharState_Nothing &&
|
||||
mUpperBodyState != UpperCharState_WeapEquiped;
|
||||
return mUpperBodyState != UpperBodyState::None && mUpperBodyState != UpperBodyState::WeaponEquipped;
|
||||
}
|
||||
|
||||
bool CharacterController::isSneaking() const
|
||||
@ -2786,7 +2735,7 @@ std::string_view CharacterController::getRandomAttackType()
|
||||
bool CharacterController::readyToPrepareAttack() const
|
||||
{
|
||||
return (mHitState == CharState_None || mHitState == CharState_Block)
|
||||
&& mUpperBodyState <= UpperCharState_WeapEquiped;
|
||||
&& mUpperBodyState <= UpperBodyState::WeaponEquipped;
|
||||
}
|
||||
|
||||
bool CharacterController::readyToStartAttack() const
|
||||
@ -2794,7 +2743,7 @@ bool CharacterController::readyToStartAttack() const
|
||||
if (mHitState != CharState_None && mHitState != CharState_Block)
|
||||
return false;
|
||||
|
||||
return mUpperBodyState == UpperCharState_WeapEquiped;
|
||||
return mUpperBodyState == UpperBodyState::WeaponEquipped;
|
||||
}
|
||||
|
||||
float CharacterController::getAttackStrength() const
|
||||
@ -2819,15 +2768,33 @@ void CharacterController::setHeadTrackTarget(const MWWorld::ConstPtr &target)
|
||||
|
||||
void CharacterController::playSwishSound(float attackStrength) const
|
||||
{
|
||||
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
|
||||
ESM::WeaponType::Class weapclass = getWeaponType(mWeaponType)->mWeaponClass;
|
||||
if (weapclass == ESM::WeaponType::Ranged || weapclass == ESM::WeaponType::Thrown)
|
||||
return;
|
||||
|
||||
std::string sound = "Weapon Swish";
|
||||
if(attackStrength < 0.5f)
|
||||
sndMgr->playSound3D(mPtr, sound, 1.0f, 0.8f); //Weak attack
|
||||
else if(attackStrength < 1.0f)
|
||||
sndMgr->playSound3D(mPtr, sound, 1.0f, 1.0f); //Medium attack
|
||||
std::string soundId;
|
||||
float pitch = 1.f;
|
||||
|
||||
const MWWorld::Class &cls = mPtr.getClass();
|
||||
if (cls.isNpc() && cls.getNpcStats(mPtr).isWerewolf())
|
||||
{
|
||||
MWBase::World* world = MWBase::Environment::get().getWorld();
|
||||
const MWWorld::ESMStore &store = world->getStore();
|
||||
const ESM::Sound *sound = store.get<ESM::Sound>().searchRandom("WolfSwing", world->getPrng());
|
||||
if (sound)
|
||||
soundId = sound->mId;
|
||||
}
|
||||
else
|
||||
sndMgr->playSound3D(mPtr, sound, 1.0f, 1.2f); //Strong attack
|
||||
{
|
||||
soundId = "Weapon Swish";
|
||||
if (attackStrength < 0.5f)
|
||||
pitch = 0.8f; // Weak attack
|
||||
else if (attackStrength >= 1.f)
|
||||
pitch = 1.2f; // Strong attack
|
||||
}
|
||||
|
||||
if (!soundId.empty())
|
||||
MWBase::Environment::get().getSoundManager()->playSound3D(mPtr, soundId, 1.0f, pitch);
|
||||
}
|
||||
|
||||
void CharacterController::updateHeadTracking(float duration)
|
||||
|
@ -103,17 +103,18 @@ enum CharacterState {
|
||||
CharState_Block
|
||||
};
|
||||
|
||||
enum UpperBodyCharacterState {
|
||||
UpperCharState_Nothing,
|
||||
UpperCharState_EquipingWeap,
|
||||
UpperCharState_UnEquipingWeap,
|
||||
UpperCharState_WeapEquiped,
|
||||
UpperCharState_StartToMinAttack,
|
||||
UpperCharState_MinAttackToMaxAttack,
|
||||
UpperCharState_MaxAttackToMinHit,
|
||||
UpperCharState_MinHitToHit,
|
||||
UpperCharState_FollowStartToFollowStop,
|
||||
UpperCharState_CastingSpell
|
||||
enum class UpperBodyState
|
||||
{
|
||||
None,
|
||||
Equipping,
|
||||
Unequipping,
|
||||
WeaponEquipped,
|
||||
AttackPreWindUp,
|
||||
AttackWindUp,
|
||||
AttackRelease,
|
||||
AttackHit,
|
||||
AttackEnd,
|
||||
Casting
|
||||
};
|
||||
|
||||
enum JumpingState {
|
||||
@ -156,7 +157,7 @@ class CharacterController : public MWRender::Animation::TextKeyListener
|
||||
CharacterState mHitState{CharState_None};
|
||||
std::string mCurrentHit;
|
||||
|
||||
UpperBodyCharacterState mUpperBodyState{UpperCharState_Nothing};
|
||||
UpperBodyState mUpperBodyState{UpperBodyState::None};
|
||||
|
||||
JumpingState mJumpState{JumpState_None};
|
||||
std::string mCurrentJump;
|
||||
@ -203,7 +204,7 @@ class CharacterController : public MWRender::Animation::TextKeyListener
|
||||
|
||||
void clearAnimQueue(bool clearPersistAnims = false);
|
||||
|
||||
bool updateWeaponState(CharacterState idle);
|
||||
bool updateWeaponState();
|
||||
void updateIdleStormState(bool inwater) const;
|
||||
|
||||
std::string chooseRandomAttackAnimation() const;
|
||||
|
@ -158,33 +158,16 @@ bool ActorAnimation::updateCarriedLeftVisible(const int weaptype) const
|
||||
{
|
||||
const MWWorld::Class &cls = mPtr.getClass();
|
||||
MWMechanics::CreatureStats &stats = cls.getCreatureStats(mPtr);
|
||||
if (cls.hasInventoryStore(mPtr) && weaptype != ESM::Weapon::Spell)
|
||||
if (cls.hasInventoryStore(mPtr) && stats.getDrawState() == MWMechanics::DrawState::Nothing)
|
||||
{
|
||||
SceneUtil::FindByNameVisitor findVisitor ("Bip01 AttachShield");
|
||||
mObjectRoot->accept(findVisitor);
|
||||
if (findVisitor.mFoundNode || (mPtr == MWMechanics::getPlayer() && mPtr.isInCell() && MWBase::Environment::get().getWorld()->isFirstPerson()))
|
||||
if (findVisitor.mFoundNode)
|
||||
{
|
||||
const MWWorld::InventoryStore& inv = cls.getInventoryStore(mPtr);
|
||||
const MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
|
||||
const MWWorld::ConstContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
|
||||
if (shield != inv.end() && shield->getType() == ESM::Armor::sRecordId && !getSheathedShieldMesh(*shield).empty())
|
||||
{
|
||||
if(stats.getDrawState() != MWMechanics::DrawState::Weapon)
|
||||
return false;
|
||||
|
||||
if (weapon != inv.end())
|
||||
{
|
||||
auto type = weapon->getType();
|
||||
if(type == ESM::Weapon::sRecordId)
|
||||
{
|
||||
const MWWorld::LiveCellRef<ESM::Weapon> *ref = weapon->get<ESM::Weapon>();
|
||||
ESM::Weapon::Type weaponType = (ESM::Weapon::Type)ref->mBase->mData.mType;
|
||||
return !(MWMechanics::getWeaponType(weaponType)->mFlags & ESM::WeaponType::TwoHanded);
|
||||
}
|
||||
else if (type == ESM::Lockpick::sRecordId || type == ESM::Probe::sRecordId)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -295,13 +295,23 @@ void NpcAnimation::setViewMode(NpcAnimation::ViewMode viewMode)
|
||||
assert(viewMode != VM_HeadOnly);
|
||||
if(mViewMode == viewMode)
|
||||
return;
|
||||
|
||||
// FIXME: sheathing state must be consistent if the third person skeleton doesn't have the necessary node, but
|
||||
// third person skeleton is unavailable in first person view. This is a hack to avoid cosmetic issues.
|
||||
bool viewChange = mViewMode == VM_FirstPerson || viewMode == VM_FirstPerson;
|
||||
mViewMode = viewMode;
|
||||
MWBase::Environment::get().getWorld()->scaleObject(mPtr, mPtr.getCellRef().getScale(), true); // apply race height after view change
|
||||
|
||||
mAmmunition.reset();
|
||||
rebuild();
|
||||
setRenderBin();
|
||||
|
||||
static const bool shieldSheathing = Settings::Manager::getBool("shield sheathing", "Game");
|
||||
if (viewChange && shieldSheathing)
|
||||
{
|
||||
int weaptype = ESM::Weapon::None;
|
||||
MWMechanics::getActiveWeapon(mPtr, &weaptype);
|
||||
showCarriedLeft(updateCarriedLeftVisible(weaptype));
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief A RenderBin callback to clear the depth buffer before rendering.
|
||||
@ -983,6 +993,30 @@ void NpcAnimation::showWeapons(bool showWeapon)
|
||||
updateQuiver();
|
||||
}
|
||||
|
||||
bool NpcAnimation::updateCarriedLeftVisible(const int weaptype) const
|
||||
{
|
||||
static const bool shieldSheathing = Settings::Manager::getBool("shield sheathing", "Game");
|
||||
if (shieldSheathing)
|
||||
{
|
||||
const MWWorld::Class &cls = mPtr.getClass();
|
||||
MWMechanics::CreatureStats &stats = cls.getCreatureStats(mPtr);
|
||||
if (stats.getDrawState() == MWMechanics::DrawState::Nothing)
|
||||
{
|
||||
SceneUtil::FindByNameVisitor findVisitor ("Bip01 AttachShield");
|
||||
mObjectRoot->accept(findVisitor);
|
||||
if (findVisitor.mFoundNode || mViewMode == VM_FirstPerson)
|
||||
{
|
||||
const MWWorld::InventoryStore& inv = cls.getInventoryStore(mPtr);
|
||||
const MWWorld::ConstContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
|
||||
if (shield != inv.end() && shield->getType() == ESM::Armor::sRecordId && !getSheathedShieldMesh(*shield).empty())
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return !(MWMechanics::getWeaponType(weaptype)->mFlags & ESM::WeaponType::TwoHanded);
|
||||
}
|
||||
|
||||
void NpcAnimation::showCarriedLeft(bool show)
|
||||
{
|
||||
mShowCarriedLeft = show;
|
||||
|
@ -137,6 +137,7 @@ public:
|
||||
|
||||
void showWeapons(bool showWeapon) override;
|
||||
|
||||
bool updateCarriedLeftVisible(const int weaptype) const override;
|
||||
bool getCarriedLeftShown() const override { return mShowCarriedLeft; }
|
||||
void showCarriedLeft(bool show) override;
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include "objectpaging.hpp"
|
||||
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include <osg/LOD>
|
||||
#include <osg/Switch>
|
||||
@ -276,7 +277,7 @@ namespace MWRender
|
||||
RefnumSet(){}
|
||||
RefnumSet(const RefnumSet& copy, const osg::CopyOp&) : mRefnums(copy.mRefnums) {}
|
||||
META_Object(MWRender, RefnumSet)
|
||||
std::set<ESM::RefNum> mRefnums;
|
||||
std::vector<ESM::RefNum> mRefnums;
|
||||
};
|
||||
|
||||
class AnalyzeVisitor : public osg::NodeVisitor
|
||||
@ -554,7 +555,7 @@ namespace MWRender
|
||||
if (cnode->getNumChildrenRequiringUpdateTraversal() > 0 || SceneUtil::hasUserDescription(cnode, Constants::NightDayLabel) || SceneUtil::hasUserDescription(cnode, Constants::HerbalismLabel))
|
||||
continue;
|
||||
else
|
||||
refnumSet->mRefnums.insert(pair.first);
|
||||
refnumSet->mRefnums.push_back(pair.first);
|
||||
}
|
||||
|
||||
{
|
||||
@ -727,6 +728,8 @@ namespace MWRender
|
||||
osg::UserDataContainer* udc = group->getOrCreateUserDataContainer();
|
||||
if (activeGrid)
|
||||
{
|
||||
std::sort(refnumSet->mRefnums.begin(), refnumSet->mRefnums.end());
|
||||
refnumSet->mRefnums.erase(std::unique(refnumSet->mRefnums.begin(), refnumSet->mRefnums.end()), refnumSet->mRefnums.end());
|
||||
udc->addUserObject(refnumSet);
|
||||
group->addCullCallback(new SceneUtil::LightListCallback);
|
||||
}
|
||||
@ -837,7 +840,7 @@ namespace MWRender
|
||||
|
||||
struct GetRefnumsFunctor
|
||||
{
|
||||
GetRefnumsFunctor(std::set<ESM::RefNum>& output) : mOutput(output) {}
|
||||
GetRefnumsFunctor(std::vector<ESM::RefNum>& output) : mOutput(output) {}
|
||||
void operator()(MWRender::ChunkId chunkId, osg::Object* obj)
|
||||
{
|
||||
if (!std::get<2>(chunkId)) return;
|
||||
@ -850,18 +853,20 @@ namespace MWRender
|
||||
{
|
||||
RefnumSet* refnums = dynamic_cast<RefnumSet*>(udc->getUserObject(0));
|
||||
if (!refnums) return;
|
||||
mOutput.insert(refnums->mRefnums.begin(), refnums->mRefnums.end());
|
||||
mOutput.insert(mOutput.end(), refnums->mRefnums.begin(), refnums->mRefnums.end());
|
||||
}
|
||||
}
|
||||
osg::Vec4i mActiveGrid;
|
||||
std::set<ESM::RefNum>& mOutput;
|
||||
std::vector<ESM::RefNum>& mOutput;
|
||||
};
|
||||
|
||||
void ObjectPaging::getPagedRefnums(const osg::Vec4i &activeGrid, std::set<ESM::RefNum> &out)
|
||||
void ObjectPaging::getPagedRefnums(const osg::Vec4i &activeGrid, std::vector<ESM::RefNum>& out)
|
||||
{
|
||||
GetRefnumsFunctor grf(out);
|
||||
grf.mActiveGrid = activeGrid;
|
||||
mCache->call(grf);
|
||||
std::sort(out.begin(), out.end());
|
||||
out.erase(std::unique(out.begin(), out.end()), out.end());
|
||||
}
|
||||
|
||||
void ObjectPaging::reportStats(unsigned int frameNumber, osg::Stats *stats) const
|
||||
|
@ -47,7 +47,7 @@ namespace MWRender
|
||||
|
||||
void reportStats(unsigned int frameNumber, osg::Stats* stats) const override;
|
||||
|
||||
void getPagedRefnums(const osg::Vec4i &activeGrid, std::set<ESM::RefNum> &out);
|
||||
void getPagedRefnums(const osg::Vec4i &activeGrid, std::vector<ESM::RefNum>& out);
|
||||
|
||||
private:
|
||||
Resource::SceneManager* mSceneManager;
|
||||
|
@ -100,21 +100,10 @@ namespace MWRender
|
||||
|
||||
if (filtered.empty() || !bufferData.postprocessing)
|
||||
{
|
||||
if (bufferData.postprocessing)
|
||||
{
|
||||
if (!mLoggedLastError)
|
||||
{
|
||||
Log(Debug::Error) << "Critical error, postprocess shaders failed to compile. Using default shader.";
|
||||
mLoggedLastError = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
mLoggedLastError = false;
|
||||
|
||||
state.pushStateSet(mFallbackStateSet);
|
||||
state.apply();
|
||||
|
||||
if (Stereo::getMultiview() && mMultiviewResolveProgram)
|
||||
if (Stereo::getMultiview())
|
||||
{
|
||||
state.pushStateSet(mMultiviewResolveStateSet);
|
||||
state.apply();
|
||||
@ -126,7 +115,7 @@ namespace MWRender
|
||||
drawGeometry(renderInfo);
|
||||
state.popStateSet();
|
||||
|
||||
if (Stereo::getMultiview() && mMultiviewResolveProgram)
|
||||
if (Stereo::getMultiview())
|
||||
{
|
||||
state.popStateSet();
|
||||
}
|
||||
@ -298,7 +287,7 @@ namespace MWRender
|
||||
state.popStateSet();
|
||||
}
|
||||
|
||||
if (Stereo::getMultiview() && mMultiviewResolveProgram)
|
||||
if (Stereo::getMultiview())
|
||||
{
|
||||
ext->glBindFramebuffer(GL_DRAW_FRAMEBUFFER_EXT, 0);
|
||||
lastApplied = 0;
|
||||
|
@ -83,8 +83,6 @@ namespace MWRender
|
||||
mutable std::array<BufferData, 2> mBufferData;
|
||||
mutable std::array<osg::ref_ptr<osg::FrameBufferObject>, 3> mFbos;
|
||||
mutable osg::ref_ptr<osg::Viewport> mRenderViewport;
|
||||
|
||||
mutable bool mLoggedLastError = false;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -126,7 +126,6 @@ namespace MWRender
|
||||
, mNormalsSupported(false)
|
||||
, mPassLights(false)
|
||||
, mPrevPassLights(false)
|
||||
, mMainTemplate(new osg::Texture2D)
|
||||
{
|
||||
mSoftParticles = Settings::Manager::getBool("soft particles", "Shaders");
|
||||
mUsePostProcessing = Settings::Manager::getBool("enabled", "Post Processing");
|
||||
@ -249,14 +248,6 @@ namespace MWRender
|
||||
populateTechniqueFiles();
|
||||
}
|
||||
|
||||
mMainTemplate->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);
|
||||
mMainTemplate->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
|
||||
mMainTemplate->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE);
|
||||
mMainTemplate->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE);
|
||||
mMainTemplate->setInternalFormat(GL_RGBA);
|
||||
mMainTemplate->setSourceType(GL_UNSIGNED_BYTE);
|
||||
mMainTemplate->setSourceFormat(GL_RGBA);
|
||||
|
||||
createTexturesAndCamera(frame() % 2);
|
||||
|
||||
removeChild(mHUDCamera);
|
||||
@ -402,13 +393,8 @@ namespace MWRender
|
||||
|
||||
mReload = false;
|
||||
|
||||
if (!mTechniques.empty())
|
||||
reloadMainPass(*mTechniques[0]);
|
||||
|
||||
reloadTechniques();
|
||||
|
||||
if (!mUsePostProcessing)
|
||||
resize();
|
||||
loadChain();
|
||||
resize();
|
||||
}
|
||||
|
||||
void PostProcessor::update(size_t frameId)
|
||||
@ -486,9 +472,6 @@ namespace MWRender
|
||||
auto fpDepthRb = createFrameBufferAttachmentFromTemplate(Usage::RENDER_BUFFER, width, height, textures[Tex_Depth], mSamples);
|
||||
fbos[FBO_FirstPerson]->setAttachment(osg::FrameBufferObject::BufferComponent::PACKED_DEPTH_STENCIL_BUFFER, osg::FrameBufferAttachment(fpDepthRb));
|
||||
|
||||
// When MSAA is enabled we must first render to a render buffer, then
|
||||
// blit the result to the FBO which is either passed to the main frame
|
||||
// buffer for display or used as the entry point for a post process chain.
|
||||
if (mSamples > 1)
|
||||
{
|
||||
fbos[FBO_Multisample] = new osg::FrameBufferObject;
|
||||
@ -497,7 +480,6 @@ namespace MWRender
|
||||
{
|
||||
auto normalRB = createFrameBufferAttachmentFromTemplate(Usage::RENDER_BUFFER, width, height, textures[Tex_Normal], mSamples);
|
||||
fbos[FBO_Multisample]->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER1, normalRB);
|
||||
fbos[FBO_FirstPerson]->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER1, normalRB);
|
||||
}
|
||||
auto depthRB = createFrameBufferAttachmentFromTemplate(Usage::RENDER_BUFFER, width, height, textures[Tex_Depth], mSamples);
|
||||
fbos[FBO_Multisample]->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, colorRB);
|
||||
@ -670,7 +652,7 @@ namespace MWRender
|
||||
return Status_Error;
|
||||
}
|
||||
|
||||
if (!technique || technique->getLocked() || (location.has_value() && location.value() <= 0))
|
||||
if (!technique || technique->getLocked() || (location.has_value() && location.value() < 0))
|
||||
return Status_Error;
|
||||
|
||||
disableTechnique(technique, false);
|
||||
@ -737,17 +719,6 @@ namespace MWRender
|
||||
textures[Tex_Normal]->setSourceFormat(GL_RGB);
|
||||
textures[Tex_Normal]->setInternalFormat(GL_RGB);
|
||||
|
||||
if (mMainTemplate)
|
||||
{
|
||||
textures[Tex_Scene]->setSourceFormat(mMainTemplate->getSourceFormat());
|
||||
textures[Tex_Scene]->setSourceType(mMainTemplate->getSourceType());
|
||||
textures[Tex_Scene]->setInternalFormat(mMainTemplate->getInternalFormat());
|
||||
textures[Tex_Scene]->setFilter(osg::Texture2D::MIN_FILTER, mMainTemplate->getFilter(osg::Texture2D::MIN_FILTER));
|
||||
textures[Tex_Scene]->setFilter(osg::Texture2D::MAG_FILTER, mMainTemplate->getFilter(osg::Texture2D::MAG_FILTER));
|
||||
textures[Tex_Scene]->setWrap(osg::Texture::WRAP_S, mMainTemplate->getWrap(osg::Texture2D::WRAP_S));
|
||||
textures[Tex_Scene]->setWrap(osg::Texture::WRAP_T, mMainTemplate->getWrap(osg::Texture2D::WRAP_T));
|
||||
}
|
||||
|
||||
auto setupDepth = [] (osg::Texture* tex) {
|
||||
tex->setSourceFormat(GL_DEPTH_STENCIL_EXT);
|
||||
tex->setSourceType(SceneUtil::AutoDepth::depthSourceType());
|
||||
@ -819,14 +790,12 @@ namespace MWRender
|
||||
return technique;
|
||||
}
|
||||
|
||||
reloadMainPass(*technique);
|
||||
|
||||
mTemplates.push_back(std::move(technique));
|
||||
|
||||
return mTemplates.back();
|
||||
}
|
||||
|
||||
void PostProcessor::reloadTechniques()
|
||||
void PostProcessor::loadChain()
|
||||
{
|
||||
if (!isEnabled())
|
||||
return;
|
||||
@ -835,18 +804,9 @@ namespace MWRender
|
||||
|
||||
std::vector<std::string> techniqueStrings = Settings::Manager::getStringArray("chain", "Post Processing");
|
||||
|
||||
const std::string mainIdentifier = "main";
|
||||
|
||||
auto main = loadTechnique(mainIdentifier);
|
||||
|
||||
if (main)
|
||||
main->setLocked(true);
|
||||
|
||||
mTechniques.push_back(std::move(main));
|
||||
|
||||
for (auto& techniqueName : techniqueStrings)
|
||||
{
|
||||
if (techniqueName.empty() || Misc::StringUtils::ciEqual(techniqueName, mainIdentifier))
|
||||
if (techniqueName.empty())
|
||||
continue;
|
||||
|
||||
mTechniques.push_back(loadTechnique(techniqueName));
|
||||
@ -855,14 +815,17 @@ namespace MWRender
|
||||
dirtyTechniques();
|
||||
}
|
||||
|
||||
void PostProcessor::reloadMainPass(fx::Technique& technique)
|
||||
void PostProcessor::saveChain()
|
||||
{
|
||||
if (!technique.getMainTemplate())
|
||||
return;
|
||||
std::vector<std::string> chain;
|
||||
|
||||
mMainTemplate = technique.getMainTemplate();
|
||||
for (const auto& technique : mTechniques) {
|
||||
if (!technique || technique->getDynamic())
|
||||
continue;
|
||||
chain.push_back(technique->getName());
|
||||
}
|
||||
|
||||
resize();
|
||||
Settings::Manager::setStringArray("chain", "Post Processing", chain);
|
||||
}
|
||||
|
||||
void PostProcessor::toggleMode()
|
||||
|
@ -86,6 +86,13 @@ namespace MWRender
|
||||
Unit_NextFree
|
||||
};
|
||||
|
||||
enum Status
|
||||
{
|
||||
Status_Error,
|
||||
Status_Toggled,
|
||||
Status_Unchanged
|
||||
};
|
||||
|
||||
PostProcessor(RenderingManager& rendering, osgViewer::Viewer* viewer, osg::Group* rootNode, const VFS::Manager* vfs);
|
||||
|
||||
~PostProcessor();
|
||||
@ -110,13 +117,6 @@ namespace MWRender
|
||||
|
||||
void resize();
|
||||
|
||||
enum Status
|
||||
{
|
||||
Status_Error,
|
||||
Status_Toggled,
|
||||
Status_Unchanged
|
||||
};
|
||||
|
||||
Status enableTechnique(std::shared_ptr<fx::Technique> technique, std::optional<int> location = std::nullopt);
|
||||
|
||||
Status disableTechnique(std::shared_ptr<fx::Technique> technique, bool dirty = true);
|
||||
@ -184,6 +184,10 @@ namespace MWRender
|
||||
|
||||
bool mEnableLiveReload;
|
||||
|
||||
void loadChain();
|
||||
void saveChain();
|
||||
|
||||
|
||||
private:
|
||||
|
||||
void populateTechniqueFiles();
|
||||
@ -194,8 +198,6 @@ namespace MWRender
|
||||
|
||||
void createTexturesAndCamera(size_t frameId);
|
||||
|
||||
void reloadTechniques();
|
||||
|
||||
void reloadMainPass(fx::Technique& technique);
|
||||
|
||||
void dirtyTechniques();
|
||||
@ -250,8 +252,6 @@ namespace MWRender
|
||||
bool mUBO;
|
||||
int mGLSLVersion;
|
||||
|
||||
osg::ref_ptr<osg::Texture> mMainTemplate;
|
||||
|
||||
osg::ref_ptr<fx::StateUpdater> mStateUpdater;
|
||||
osg::ref_ptr<PingPongCull> mPingPongCull;
|
||||
osg::ref_ptr<PingPongCanvas> mPingPongCanvas;
|
||||
|
@ -637,7 +637,6 @@ namespace MWRender
|
||||
updateProjectionMatrix();
|
||||
|
||||
mViewer->getCamera()->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
|
||||
mViewer->getUpdateVisitor()->setTraversalMode(osg::NodeVisitor::TRAVERSE_ACTIVE_CHILDREN);
|
||||
}
|
||||
|
||||
RenderingManager::~RenderingManager()
|
||||
@ -1616,7 +1615,7 @@ namespace MWRender
|
||||
}
|
||||
return false;
|
||||
}
|
||||
void RenderingManager::getPagedRefnums(const osg::Vec4i &activeGrid, std::set<ESM::RefNum> &out)
|
||||
void RenderingManager::getPagedRefnums(const osg::Vec4i &activeGrid, std::vector<ESM::RefNum>& out)
|
||||
{
|
||||
if (mObjectPaging)
|
||||
mObjectPaging->getPagedRefnums(activeGrid, out);
|
||||
|
@ -250,7 +250,7 @@ namespace MWRender
|
||||
bool pagingEnableObject(int type, const MWWorld::ConstPtr& ptr, bool enabled);
|
||||
void pagingBlacklistObject(int type, const MWWorld::ConstPtr &ptr);
|
||||
bool pagingUnlockCache();
|
||||
void getPagedRefnums(const osg::Vec4i &activeGrid, std::set<ESM::RefNum> &out);
|
||||
void getPagedRefnums(const osg::Vec4i &activeGrid, std::vector<ESM::RefNum>& out);
|
||||
|
||||
void updateProjectionMatrix();
|
||||
|
||||
|
@ -109,9 +109,6 @@ namespace MWRender
|
||||
if (rl->_drawable->getNodeMask() == Mask_ParticleSystem || rl->_drawable->getNodeMask() == Mask_Effect)
|
||||
continue;
|
||||
|
||||
if (ss->getAttribute(osg::StateAttribute::ALPHAFUNC))
|
||||
continue;
|
||||
|
||||
if (ss->getAttribute(osg::StateAttribute::MATERIAL)) {
|
||||
const osg::Material* mat = static_cast<const osg::Material*>(ss->getAttribute(osg::StateAttribute::MATERIAL));
|
||||
if (mat->getDiffuse(osg::Material::FRONT).a() < 0.5)
|
||||
|
@ -99,8 +99,8 @@ namespace
|
||||
return model;
|
||||
}
|
||||
|
||||
void addObject(const MWWorld::Ptr& ptr, const MWWorld::World& world, MWPhysics::PhysicsSystem& physics,
|
||||
MWRender::RenderingManager& rendering, std::set<ESM::RefNum>& pagedRefs)
|
||||
void addObject(const MWWorld::Ptr& ptr, const MWWorld::World& world, const std::vector<ESM::RefNum>& pagedRefs,
|
||||
MWPhysics::PhysicsSystem& physics, MWRender::RenderingManager& rendering)
|
||||
{
|
||||
if (ptr.getRefData().getBaseNode() || physics.getActor(ptr))
|
||||
{
|
||||
@ -112,7 +112,7 @@ namespace
|
||||
const auto rotation = makeDirectNodeRotation(ptr);
|
||||
|
||||
const ESM::RefNum& refnum = ptr.getCellRef().getRefNum();
|
||||
if (!refnum.hasContentFile() || pagedRefs.find(refnum) == pagedRefs.end())
|
||||
if (!refnum.hasContentFile() || !std::binary_search(pagedRefs.begin(), pagedRefs.end(), refnum))
|
||||
ptr.getClass().insertObjectRendering(ptr, model, rendering);
|
||||
else
|
||||
ptr.getRefData().setBaseNode(new SceneUtil::PositionAttitudeTransform); // FIXME remove this when physics code is fixed not to depend on basenode
|
||||
@ -255,6 +255,15 @@ namespace
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool removeFromSorted(const ESM::RefNum& refNum, std::vector<ESM::RefNum>& pagedRefs)
|
||||
{
|
||||
const auto it = std::lower_bound(pagedRefs.begin(), pagedRefs.end(), refNum);
|
||||
if (it == pagedRefs.end() || *it != refNum)
|
||||
return false;
|
||||
pagedRefs.erase(it);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -264,7 +273,7 @@ namespace MWWorld
|
||||
void Scene::removeFromPagedRefs(const Ptr &ptr)
|
||||
{
|
||||
const ESM::RefNum& refnum = ptr.getCellRef().getRefNum();
|
||||
if (refnum.hasContentFile() && mPagedRefs.erase(refnum))
|
||||
if (refnum.hasContentFile() && removeFromSorted(refnum, mPagedRefs))
|
||||
{
|
||||
if (!ptr.getRefData().getBaseNode()) return;
|
||||
ptr.getClass().insertObjectRendering(ptr, getModel(ptr, mRendering.getResourceSystem()->getVFS()), mRendering);
|
||||
@ -900,7 +909,7 @@ namespace MWWorld
|
||||
{
|
||||
InsertVisitor insertVisitor(cell, loadingListener);
|
||||
cell.forEach (insertVisitor);
|
||||
insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, mWorld, *mPhysics, mRendering, mPagedRefs); });
|
||||
insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, mWorld, mPagedRefs, *mPhysics, mRendering); });
|
||||
insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, mWorld, *mPhysics, mNavigator); });
|
||||
}
|
||||
|
||||
@ -908,7 +917,7 @@ namespace MWWorld
|
||||
{
|
||||
try
|
||||
{
|
||||
addObject(ptr, mWorld, *mPhysics, mRendering, mPagedRefs);
|
||||
addObject(ptr, mWorld, mPagedRefs, *mPhysics, mRendering);
|
||||
addObject(ptr, mWorld, *mPhysics, mNavigator);
|
||||
mWorld.scaleObject(ptr, ptr.getCellRef().getScale());
|
||||
if (mCurrentCell != nullptr)
|
||||
|
@ -104,7 +104,7 @@ namespace MWWorld
|
||||
|
||||
osg::Vec3f mLastPlayerPos;
|
||||
|
||||
std::set<ESM::RefNum> mPagedRefs;
|
||||
std::vector<ESM::RefNum> mPagedRefs;
|
||||
|
||||
std::vector<osg::ref_ptr<SceneUtil::WorkItem>> mWorkItems;
|
||||
|
||||
|
@ -1,130 +1,130 @@
|
||||
find_package(GTest 1.10 REQUIRED)
|
||||
find_package(GMock 1.10 REQUIRED)
|
||||
|
||||
if (GTEST_FOUND AND GMOCK_FOUND)
|
||||
include_directories(SYSTEM ${GTEST_INCLUDE_DIRS})
|
||||
include_directories(SYSTEM ${GMOCK_INCLUDE_DIRS})
|
||||
|
||||
file(GLOB UNITTEST_SRC_FILES
|
||||
testing_util.hpp
|
||||
|
||||
../openmw/mwworld/store.cpp
|
||||
../openmw/mwworld/esmstore.cpp
|
||||
mwworld/test_store.cpp
|
||||
|
||||
mwdialogue/test_keywordsearch.cpp
|
||||
|
||||
mwscript/test_scripts.cpp
|
||||
|
||||
esm/test_fixed_string.cpp
|
||||
esm/variant.cpp
|
||||
|
||||
lua/test_lua.cpp
|
||||
lua/test_scriptscontainer.cpp
|
||||
lua/test_utilpackage.cpp
|
||||
lua/test_serialization.cpp
|
||||
lua/test_configuration.cpp
|
||||
lua/test_l10n.cpp
|
||||
lua/test_storage.cpp
|
||||
|
||||
lua/test_ui_content.cpp
|
||||
|
||||
misc/test_stringops.cpp
|
||||
misc/test_endianness.cpp
|
||||
misc/test_resourcehelpers.cpp
|
||||
misc/progressreporter.cpp
|
||||
misc/compression.cpp
|
||||
|
||||
nifloader/testbulletnifloader.cpp
|
||||
|
||||
detournavigator/navigator.cpp
|
||||
detournavigator/settingsutils.cpp
|
||||
detournavigator/recastmeshbuilder.cpp
|
||||
detournavigator/gettilespositions.cpp
|
||||
detournavigator/recastmeshobject.cpp
|
||||
detournavigator/navmeshtilescache.cpp
|
||||
detournavigator/tilecachedrecastmeshmanager.cpp
|
||||
detournavigator/navmeshdb.cpp
|
||||
detournavigator/serialization.cpp
|
||||
detournavigator/asyncnavmeshupdater.cpp
|
||||
|
||||
serialization/binaryreader.cpp
|
||||
serialization/binarywriter.cpp
|
||||
serialization/sizeaccumulator.cpp
|
||||
serialization/integration.cpp
|
||||
|
||||
settings/parser.cpp
|
||||
settings/shadermanager.cpp
|
||||
|
||||
shader/parsedefines.cpp
|
||||
shader/parsefors.cpp
|
||||
shader/parselinks.cpp
|
||||
shader/shadermanager.cpp
|
||||
|
||||
../openmw/options.cpp
|
||||
openmw/options.cpp
|
||||
|
||||
sqlite3/db.cpp
|
||||
sqlite3/request.cpp
|
||||
sqlite3/statement.cpp
|
||||
sqlite3/transaction.cpp
|
||||
|
||||
esmloader/load.cpp
|
||||
esmloader/esmdata.cpp
|
||||
esmloader/record.cpp
|
||||
|
||||
files/hash.cpp
|
||||
|
||||
toutf8/toutf8.cpp
|
||||
|
||||
esm4/includes.cpp
|
||||
|
||||
fx/lexer.cpp
|
||||
fx/technique.cpp
|
||||
|
||||
esm3/readerscache.cpp
|
||||
)
|
||||
|
||||
source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES})
|
||||
|
||||
openmw_add_executable(openmw_test_suite openmw_test_suite.cpp ${UNITTEST_SRC_FILES})
|
||||
|
||||
target_link_libraries(openmw_test_suite ${GMOCK_LIBRARIES} components)
|
||||
# Fix for not visible pthreads functions for linker with glibc 2.15
|
||||
if (UNIX AND NOT APPLE)
|
||||
target_link_libraries(openmw_test_suite ${CMAKE_THREAD_LIBS_INIT})
|
||||
endif()
|
||||
|
||||
if (BUILD_WITH_CODE_COVERAGE)
|
||||
add_definitions(--coverage)
|
||||
target_link_libraries(openmw_test_suite gcov)
|
||||
endif()
|
||||
|
||||
file(DOWNLOAD
|
||||
https://gitlab.com/OpenMW/example-suite/-/raw/8966dab24692555eec720c854fb0f73d108070cd/data/template.omwgame
|
||||
${CMAKE_CURRENT_BINARY_DIR}/data/template.omwgame
|
||||
EXPECTED_HASH SHA512=6e38642bcf013c5f496a9cb0bf3ec7c9553b6e86b836e7844824c5a05f556c9391167214469b6318401684b702d7569896bf743c85aee4198612b3315ba778d6
|
||||
)
|
||||
|
||||
target_compile_definitions(openmw_test_suite
|
||||
PRIVATE OPENMW_DATA_DIR="${CMAKE_CURRENT_BINARY_DIR}/data"
|
||||
OPENMW_TEST_SUITE_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
|
||||
if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.16 AND MSVC)
|
||||
target_precompile_headers(openmw_test_suite PRIVATE
|
||||
<boost/program_options/options_description.hpp>
|
||||
|
||||
<gtest/gtest.h>
|
||||
|
||||
<sol/sol.hpp>
|
||||
|
||||
<algorithm>
|
||||
<filesystem>
|
||||
<fstream>
|
||||
<functional>
|
||||
<memory>
|
||||
<string>
|
||||
<vector>
|
||||
)
|
||||
endif()
|
||||
if (OPENMW_USE_SYSTEM_GOOGLETEST)
|
||||
find_package(GTest 1.10 REQUIRED)
|
||||
find_package(GMock 1.10 REQUIRED)
|
||||
endif()
|
||||
|
||||
include_directories(SYSTEM ${GTEST_INCLUDE_DIRS})
|
||||
include_directories(SYSTEM ${GMOCK_INCLUDE_DIRS})
|
||||
|
||||
file(GLOB UNITTEST_SRC_FILES
|
||||
testing_util.hpp
|
||||
|
||||
../openmw/mwworld/store.cpp
|
||||
../openmw/mwworld/esmstore.cpp
|
||||
mwworld/test_store.cpp
|
||||
|
||||
mwdialogue/test_keywordsearch.cpp
|
||||
|
||||
mwscript/test_scripts.cpp
|
||||
|
||||
esm/test_fixed_string.cpp
|
||||
esm/variant.cpp
|
||||
|
||||
lua/test_lua.cpp
|
||||
lua/test_scriptscontainer.cpp
|
||||
lua/test_utilpackage.cpp
|
||||
lua/test_serialization.cpp
|
||||
lua/test_configuration.cpp
|
||||
lua/test_l10n.cpp
|
||||
lua/test_storage.cpp
|
||||
|
||||
lua/test_ui_content.cpp
|
||||
|
||||
misc/test_stringops.cpp
|
||||
misc/test_endianness.cpp
|
||||
misc/test_resourcehelpers.cpp
|
||||
misc/progressreporter.cpp
|
||||
misc/compression.cpp
|
||||
|
||||
nifloader/testbulletnifloader.cpp
|
||||
|
||||
detournavigator/navigator.cpp
|
||||
detournavigator/settingsutils.cpp
|
||||
detournavigator/recastmeshbuilder.cpp
|
||||
detournavigator/gettilespositions.cpp
|
||||
detournavigator/recastmeshobject.cpp
|
||||
detournavigator/navmeshtilescache.cpp
|
||||
detournavigator/tilecachedrecastmeshmanager.cpp
|
||||
detournavigator/navmeshdb.cpp
|
||||
detournavigator/serialization.cpp
|
||||
detournavigator/asyncnavmeshupdater.cpp
|
||||
|
||||
serialization/binaryreader.cpp
|
||||
serialization/binarywriter.cpp
|
||||
serialization/sizeaccumulator.cpp
|
||||
serialization/integration.cpp
|
||||
|
||||
settings/parser.cpp
|
||||
settings/shadermanager.cpp
|
||||
|
||||
shader/parsedefines.cpp
|
||||
shader/parsefors.cpp
|
||||
shader/parselinks.cpp
|
||||
shader/shadermanager.cpp
|
||||
|
||||
../openmw/options.cpp
|
||||
openmw/options.cpp
|
||||
|
||||
sqlite3/db.cpp
|
||||
sqlite3/request.cpp
|
||||
sqlite3/statement.cpp
|
||||
sqlite3/transaction.cpp
|
||||
|
||||
esmloader/load.cpp
|
||||
esmloader/esmdata.cpp
|
||||
esmloader/record.cpp
|
||||
|
||||
files/hash.cpp
|
||||
|
||||
toutf8/toutf8.cpp
|
||||
|
||||
esm4/includes.cpp
|
||||
|
||||
fx/lexer.cpp
|
||||
fx/technique.cpp
|
||||
|
||||
esm3/readerscache.cpp
|
||||
)
|
||||
|
||||
source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES})
|
||||
|
||||
openmw_add_executable(openmw_test_suite openmw_test_suite.cpp ${UNITTEST_SRC_FILES})
|
||||
|
||||
target_link_libraries(openmw_test_suite GTest::GTest GMock::GMock components)
|
||||
# Fix for not visible pthreads functions for linker with glibc 2.15
|
||||
if (UNIX AND NOT APPLE)
|
||||
target_link_libraries(openmw_test_suite ${CMAKE_THREAD_LIBS_INIT})
|
||||
endif()
|
||||
|
||||
if (BUILD_WITH_CODE_COVERAGE)
|
||||
add_definitions(--coverage)
|
||||
target_link_libraries(openmw_test_suite gcov)
|
||||
endif()
|
||||
|
||||
file(DOWNLOAD
|
||||
https://gitlab.com/OpenMW/example-suite/-/raw/8966dab24692555eec720c854fb0f73d108070cd/data/template.omwgame
|
||||
${CMAKE_CURRENT_BINARY_DIR}/data/template.omwgame
|
||||
EXPECTED_HASH SHA512=6e38642bcf013c5f496a9cb0bf3ec7c9553b6e86b836e7844824c5a05f556c9391167214469b6318401684b702d7569896bf743c85aee4198612b3315ba778d6
|
||||
)
|
||||
|
||||
target_compile_definitions(openmw_test_suite
|
||||
PRIVATE OPENMW_DATA_DIR="${CMAKE_CURRENT_BINARY_DIR}/data"
|
||||
OPENMW_TEST_SUITE_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
|
||||
if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.16 AND MSVC)
|
||||
target_precompile_headers(openmw_test_suite PRIVATE
|
||||
<boost/program_options/options_description.hpp>
|
||||
|
||||
<gtest/gtest.h>
|
||||
|
||||
<sol/sol.hpp>
|
||||
|
||||
<algorithm>
|
||||
<filesystem>
|
||||
<fstream>
|
||||
<functional>
|
||||
<memory>
|
||||
<string>
|
||||
<vector>
|
||||
)
|
||||
endif()
|
||||
|
@ -87,20 +87,6 @@ TEST(EsmFixedString, empty_strings)
|
||||
}
|
||||
}
|
||||
|
||||
TEST(EsmFixedString, struct_size)
|
||||
{
|
||||
ASSERT_EQ(4, sizeof(ESM::NAME));
|
||||
ASSERT_EQ(32, sizeof(ESM::NAME32));
|
||||
ASSERT_EQ(64, sizeof(ESM::NAME64));
|
||||
}
|
||||
|
||||
TEST(EsmFixedString, is_pod)
|
||||
{
|
||||
ASSERT_TRUE(std::is_pod<ESM::NAME>::value);
|
||||
ASSERT_TRUE(std::is_pod<ESM::NAME32>::value);
|
||||
ASSERT_TRUE(std::is_pod<ESM::NAME64>::value);
|
||||
}
|
||||
|
||||
TEST(EsmFixedString, assign_should_zero_untouched_bytes_for_4)
|
||||
{
|
||||
ESM::NAME value;
|
||||
|
@ -23,7 +23,6 @@ namespace
|
||||
|
||||
TEST_F(LexerSingleTokenTest, single_token_shared) { test<Shared>(); }
|
||||
TEST_F(LexerSingleTokenTest, single_token_technique) { test<Technique>(); }
|
||||
TEST_F(LexerSingleTokenTest, single_token_main_pass) { test<Main_Pass>(); }
|
||||
TEST_F(LexerSingleTokenTest, single_token_render_target) { test<Render_Target>(); }
|
||||
TEST_F(LexerSingleTokenTest, single_token_vertex) { test<Vertex>(); }
|
||||
TEST_F(LexerSingleTokenTest, single_token_fragment) { test<Fragment>(); }
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include <cstdint>
|
||||
#include <cassert>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
@ -171,6 +172,14 @@ using NAME = FixedString<4>;
|
||||
using NAME32 = FixedString<32>;
|
||||
using NAME64 = FixedString<64>;
|
||||
|
||||
static_assert(std::is_standard_layout_v<NAME> && std::is_trivial_v<NAME>);
|
||||
static_assert(std::is_standard_layout_v<NAME32> && std::is_trivial_v<NAME32>);
|
||||
static_assert(std::is_standard_layout_v<NAME64> && std::is_trivial_v<NAME64>);
|
||||
|
||||
static_assert(sizeof(NAME) == 4);
|
||||
static_assert(sizeof(NAME32) == 32);
|
||||
static_assert(sizeof(NAME64) == 64);
|
||||
|
||||
/* This struct defines a file 'context' which can be saved and later
|
||||
restored by an ESMReader instance. It will save the position within
|
||||
a file, and when restored will let you read from that position as
|
||||
|
@ -230,7 +230,6 @@ namespace fx
|
||||
|
||||
if (value == "shared") return Shared{};
|
||||
if (value == "technique") return Technique{};
|
||||
if (value == "main_pass") return Main_Pass{};
|
||||
if (value == "render_target") return Render_Target{};
|
||||
if (value == "vertex") return Vertex{};
|
||||
if (value == "fragment") return Fragment{};
|
||||
|
@ -18,7 +18,6 @@ namespace fx
|
||||
struct Fragment { inline static constexpr std::string_view repr = "fragment"; };
|
||||
struct Compute { inline static constexpr std::string_view repr = "compute"; };
|
||||
struct Technique { inline static constexpr std::string_view repr = "technique"; };
|
||||
struct Main_Pass { inline static constexpr std::string_view repr = "main_pass"; };
|
||||
struct Render_Target { inline static constexpr std::string_view repr = "render_target"; };
|
||||
struct Sampler_1D { inline static constexpr std::string_view repr = "sampler_1d"; };
|
||||
struct Sampler_2D { inline static constexpr std::string_view repr = "sampler_2d"; };
|
||||
@ -49,7 +48,7 @@ namespace fx
|
||||
using Token = std::variant<Float, Integer, Boolean, String, Literal, Equal, Open_bracket, Close_bracket, Open_Parenthesis,
|
||||
Close_Parenthesis, Quote, SemiColon, Comma, VBar, Colon, Shared, Technique, Render_Target, Vertex, Fragment,
|
||||
Compute, Sampler_1D, Sampler_2D, Sampler_3D, Uniform_Bool, Uniform_Float, Uniform_Int, Uniform_Vec2, Uniform_Vec3, Uniform_Vec4,
|
||||
True, False, Vec2, Vec3, Vec4, Main_Pass, Eof>;
|
||||
True, False, Vec2, Vec3, Vec4, Eof>;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -65,7 +65,6 @@ namespace fx
|
||||
mPassKeys.clear();
|
||||
mDefinedUniforms.clear();
|
||||
mRenderTargets.clear();
|
||||
mMainTemplate = nullptr;
|
||||
mLastAppliedType = Pass::Type::None;
|
||||
mFlags = 0;
|
||||
mShared.clear();
|
||||
@ -262,47 +261,6 @@ namespace fx
|
||||
error("pass list in 'technique' block cannot be empty.");
|
||||
}
|
||||
|
||||
template<>
|
||||
void Technique::parseBlockImp<Lexer::Main_Pass>()
|
||||
{
|
||||
if (mMainTemplate)
|
||||
error("duplicate 'main_pass' block");
|
||||
|
||||
if (mName != "main")
|
||||
error("'main_pass' block can only be defined in the 'main.omwfx' technique file");
|
||||
|
||||
mMainTemplate = new osg::Texture2D;
|
||||
|
||||
mMainTemplate->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);
|
||||
mMainTemplate->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
|
||||
|
||||
while (!isNext<Lexer::Close_bracket>() && !isNext<Lexer::Eof>())
|
||||
{
|
||||
expect<Lexer::Literal>();
|
||||
|
||||
auto key = std::get<Lexer::Literal>(mToken).value;
|
||||
|
||||
expect<Lexer::Equal>();
|
||||
|
||||
if (key == "wrap_s")
|
||||
mMainTemplate->setWrap(osg::Texture::WRAP_S, parseWrapMode());
|
||||
else if (key == "wrap_t")
|
||||
mMainTemplate->setWrap(osg::Texture::WRAP_T, parseWrapMode());
|
||||
// Skip depth attachments for main scene, as some engine settings rely on specific depth formats.
|
||||
// Allowing this to be overriden will cause confusion.
|
||||
else if (key == "internal_format")
|
||||
mMainTemplate->setInternalFormat(parseInternalFormat());
|
||||
else if (key == "source_type")
|
||||
mMainTemplate->setSourceType(parseSourceType());
|
||||
else if (key == "source_format")
|
||||
mMainTemplate->setSourceFormat(parseSourceFormat());
|
||||
else
|
||||
error(Misc::StringUtils::format("unexpected key '%s'", std::string(key)));
|
||||
|
||||
expect<Lexer::SemiColon>();
|
||||
}
|
||||
}
|
||||
|
||||
template<>
|
||||
void Technique::parseBlockImp<Lexer::Render_Target>()
|
||||
{
|
||||
@ -738,8 +696,6 @@ namespace fx
|
||||
parseBlock<Lexer::Shared>(false);
|
||||
else if constexpr (std::is_same_v<Lexer::Technique, T>)
|
||||
parseBlock<Lexer::Technique>(false);
|
||||
else if constexpr (std::is_same_v<Lexer::Main_Pass, T>)
|
||||
parseBlock<Lexer::Main_Pass>(false);
|
||||
else if constexpr (std::is_same_v<Lexer::Render_Target, T>)
|
||||
parseBlock<Lexer::Render_Target>();
|
||||
else if constexpr (std::is_same_v<Lexer::Vertex, T>)
|
||||
|
@ -159,8 +159,6 @@ namespace fx
|
||||
|
||||
const std::unordered_set<std::string>& getGLSLExtensions() const { return mGLSLExtensions; }
|
||||
|
||||
osg::ref_ptr<osg::Texture2D> getMainTemplate() const { return mMainTemplate; }
|
||||
|
||||
FlagsType getFlags() const { return mFlags; }
|
||||
|
||||
bool getHidden() const { return mFlags & Flag_Hidden; }
|
||||
@ -273,7 +271,6 @@ namespace fx
|
||||
int mWidth;
|
||||
int mHeight;
|
||||
|
||||
osg::ref_ptr<osg::Texture2D> mMainTemplate;
|
||||
RenderTargetMap mRenderTargets;
|
||||
|
||||
TexList mTextures;
|
||||
@ -301,7 +298,6 @@ namespace fx
|
||||
|
||||
template<> void Technique::parseBlockImp<Lexer::Shared>();
|
||||
template<> void Technique::parseBlockImp<Lexer::Technique>();
|
||||
template<> void Technique::parseBlockImp<Lexer::Main_Pass>();
|
||||
template<> void Technique::parseBlockImp<Lexer::Render_Target>();
|
||||
template<> void Technique::parseBlockImp<Lexer::Vertex>();
|
||||
template<> void Technique::parseBlockImp<Lexer::Fragment>();
|
||||
|
@ -40,7 +40,7 @@ namespace Nif
|
||||
horizontalDir = nif->getFloat();
|
||||
horizontalAngle = nif->getFloat();
|
||||
/*normal?*/ nif->getVector3();
|
||||
/*color?*/ nif->getVector4();
|
||||
color = nif->getVector4();
|
||||
size = nif->getFloat();
|
||||
startTime = nif->getFloat();
|
||||
stopTime = nif->getFloat();
|
||||
|
@ -52,6 +52,7 @@ struct NiParticleSystemController : public Controller
|
||||
float horizontalDir;
|
||||
float horizontalAngle;
|
||||
|
||||
osg::Vec4f color;
|
||||
float size;
|
||||
float startTime;
|
||||
float stopTime;
|
||||
|
@ -1050,9 +1050,8 @@ namespace NifOsg
|
||||
const osg::Vec3f& position = particledata->vertices[particle.vertex];
|
||||
created->setPosition(position);
|
||||
|
||||
osg::Vec4f partcolor (1.f,1.f,1.f,1.f);
|
||||
if (particle.vertex < particledata->colors.size())
|
||||
partcolor = particledata->colors[particle.vertex];
|
||||
created->setColorRange(osgParticle::rangev4(partctrl->color, partctrl->color));
|
||||
created->setAlphaRange(osgParticle::rangef(1.f, 1.f));
|
||||
|
||||
float size = partctrl->size;
|
||||
if (particle.vertex < particledata->sizes.size())
|
||||
@ -1171,7 +1170,7 @@ namespace NifOsg
|
||||
handleParticleInitialState(nifNode, partsys, partctrl);
|
||||
|
||||
partsys->getDefaultParticleTemplate().setSizeRange(osgParticle::rangef(partctrl->size, partctrl->size));
|
||||
partsys->getDefaultParticleTemplate().setColorRange(osgParticle::rangev4(osg::Vec4f(1.f,1.f,1.f,1.f), osg::Vec4f(1.f,1.f,1.f,1.f)));
|
||||
partsys->getDefaultParticleTemplate().setColorRange(osgParticle::rangev4(partctrl->color, partctrl->color));
|
||||
partsys->getDefaultParticleTemplate().setAlphaRange(osgParticle::rangef(1.f, 1.f));
|
||||
|
||||
if (!partctrl->emitter.empty())
|
||||
|
@ -476,16 +476,14 @@ namespace SceneUtil
|
||||
{
|
||||
osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet;
|
||||
|
||||
osg::ref_ptr<osg::IntArray> indices = new osg::IntArray(mLightManager->getMaxLights());
|
||||
osg::ref_ptr<osg::Uniform> indicesUni = new osg::Uniform(osg::Uniform::Type::INT, "PointLightIndex", indices->size());
|
||||
osg::ref_ptr<osg::Uniform> indicesUni = new osg::Uniform(osg::Uniform::Type::INT, "PointLightIndex", mLightManager->getMaxLights());
|
||||
int pointCount = 0;
|
||||
|
||||
for (size_t i = 0; i < lightList.size(); ++i)
|
||||
{
|
||||
int bufIndex = mLightManager->getLightIndexMap(frameNum)[lightList[i]->mLightSource->getId()];
|
||||
indices->at(pointCount++) = bufIndex;
|
||||
indicesUni->setElement(pointCount++, bufIndex);
|
||||
}
|
||||
indicesUni->setArray(indices);
|
||||
stateset->addUniform(indicesUni);
|
||||
stateset->addUniform(new osg::Uniform("PointLightCount", pointCount));
|
||||
|
||||
@ -608,20 +606,32 @@ namespace SceneUtil
|
||||
class LightManagerCullCallback : public SceneUtil::NodeCallback<LightManagerCullCallback, LightManager*, osgUtil::CullVisitor*>
|
||||
{
|
||||
public:
|
||||
void operator()(LightManager* node, osgUtil::CullVisitor* cv)
|
||||
LightManagerCullCallback(LightManager* lightManager)
|
||||
{
|
||||
osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet;
|
||||
if (!lightManager->getUBOManager())
|
||||
return;
|
||||
|
||||
if (node->getLightingMethod() == LightingMethod::SingleUBO)
|
||||
for (size_t i = 0; i < mStateSet.size(); ++i)
|
||||
{
|
||||
auto buffer = node->getUBOManager()->getLightBuffer(cv->getTraversalNumber());
|
||||
|
||||
auto& buffer = lightManager->getUBOManager()->getLightBuffer(i);
|
||||
#if OSG_VERSION_GREATER_OR_EQUAL(3,5,7)
|
||||
osg::ref_ptr<osg::UniformBufferBinding> ubb = new osg::UniformBufferBinding(static_cast<int>(Resource::SceneManager::UBOBinding::LightBuffer), buffer->getData(), 0, buffer->getData()->getTotalDataSize());
|
||||
#else
|
||||
osg::ref_ptr<osg::UniformBufferBinding> ubb = new osg::UniformBufferBinding(static_cast<int>(Resource::SceneManager::UBOBinding::LightBuffer), buffer->getData()->getBufferObject(), 0, buffer->getData()->getTotalDataSize());
|
||||
#endif
|
||||
stateset->setAttributeAndModes(ubb, osg::StateAttribute::ON);
|
||||
mStateSet[i]->setAttributeAndModes(ubb, osg::StateAttribute::ON);
|
||||
}
|
||||
}
|
||||
|
||||
void operator()(LightManager* node, osgUtil::CullVisitor* cv)
|
||||
{
|
||||
const size_t frameId = cv->getTraversalNumber() % 2;
|
||||
|
||||
auto& stateset = mStateSet[frameId];
|
||||
|
||||
if (node->getLightingMethod() == LightingMethod::SingleUBO)
|
||||
{
|
||||
auto& buffer = node->getUBOManager()->getLightBuffer(cv->getTraversalNumber());
|
||||
|
||||
if (auto sun = node->getSunlight())
|
||||
{
|
||||
@ -652,6 +662,8 @@ namespace SceneUtil
|
||||
if (node->getPPLightsBuffer() && cv->getCurrentCamera()->getName() == Constants::SceneCamera)
|
||||
node->getPPLightsBuffer()->updateCount(cv->getTraversalNumber());
|
||||
}
|
||||
|
||||
std::array<osg::ref_ptr<osg::StateSet>, 2> mStateSet = { new osg::StateSet, new osg::StateSet };
|
||||
};
|
||||
|
||||
UBOManager::UBOManager(int lightCount)
|
||||
@ -838,7 +850,7 @@ namespace SceneUtil
|
||||
|
||||
getOrCreateStateSet()->addUniform(new osg::Uniform("PointLightCount", 0));
|
||||
|
||||
addCullCallback(new LightManagerCullCallback);
|
||||
addCullCallback(new LightManagerCullCallback(this));
|
||||
}
|
||||
|
||||
LightManager::LightManager(const LightManager ©, const osg::CopyOp ©op)
|
||||
|
18
extern/CMakeLists.txt
vendored
18
extern/CMakeLists.txt
vendored
@ -301,3 +301,21 @@ if (NOT OPENMW_USE_SYSTEM_ICU)
|
||||
endforeach()
|
||||
set(ICU_LIBRARIES ICU::i18n ICU::uc ICU::data PARENT_SCOPE)
|
||||
endif()
|
||||
|
||||
if (BUILD_UNITTESTS AND NOT OPENMW_USE_SYSTEM_GOOGLETEST)
|
||||
cmake_minimum_required(VERSION 3.11)
|
||||
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(googletest
|
||||
URL https://github.com/google/googletest/archive/refs/tags/release-1.12.1.zip
|
||||
URL_HASH SHA512=1479ea2f3172c622c0ca305f5b2bc45a42941221ec0ac7865e6d6d020ec4d008d952fc64e01a4c5138d7bed4148cf75596f25bb9e9044a98bbbf5662053ea11c
|
||||
SOURCE_DIR fetched/googletest
|
||||
)
|
||||
if (MSVC)
|
||||
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
|
||||
endif()
|
||||
FetchContent_MakeAvailableExcludeFromAll(googletest)
|
||||
|
||||
add_library(GTest::GTest ALIAS gtest)
|
||||
add_library(GMock::GMock ALIAS gmock)
|
||||
endif()
|
||||
|
@ -87,8 +87,8 @@ set(BUILTIN_DATA_FILES
|
||||
scripts/omw/mwui/space.lua
|
||||
scripts/omw/mwui/init.lua
|
||||
|
||||
shaders/main.omwfx
|
||||
shaders/displaydepth.omwfx
|
||||
shaders/adjustments.omwfx
|
||||
shaders/debug.omwfx
|
||||
|
||||
mygui/core.skin
|
||||
mygui/core.xml
|
||||
|
@ -1,3 +1,7 @@
|
||||
DisplayDepthDescription: "Visualisiert den Tiefenpuffer."
|
||||
DisplayDepthName: "Visualisiert den Tiefenpuffer."
|
||||
DisplayDepthFactorDescription: "Bestimmt die Korrelation zwischen dem Pixeltiefenwert und seiner Ausgabefarbe. Hohe Werte führen zu einem helleren Bild."
|
||||
DisplayDepthFactorName: "Farbfaktor"
|
||||
ContrastLevelDescription: "Kontraststufe"
|
||||
ContrastLevelName: "Kontrast"
|
||||
GammaLevelDescription: "Gamma-Level"
|
||||
GammaLevelName: "Gamma"
|
@ -1,3 +1,12 @@
|
||||
DisplayDepthDescription: "Visualizes the depth buffer."
|
||||
DisplayDepthFactorDescription: "Determines correlation between pixel depth value and its output color. High values lead to brighter image."
|
||||
DisplayDepthFactorName: "Color factor"
|
||||
AdjustmentsDescription: "Colour adjustments."
|
||||
DebugDescription: "Debug shader."
|
||||
DebugHeaderDepth: "Depth Buffer"
|
||||
DebugHeaderNormals: "Normals"
|
||||
DisplayDepthFactorName: "Depth colour factor"
|
||||
DisplayDepthFactorDescription: "Determines correlation between pixel depth value and its output colour. High values lead to brighter image."
|
||||
DisplayDepthName: "Visualize depth buffer"
|
||||
DisplayNormalsName: "Visualize pass normals"
|
||||
ContrastLevelDescription: "Constrast level"
|
||||
ContrastLevelName: "Constrast"
|
||||
GammaLevelDescription: "Gamma level"
|
||||
GammaLevelName: "Gamma"
|
@ -1,3 +1,12 @@
|
||||
DisplayDepthDescription: "Визуализирует буфер глубины."
|
||||
AdjustmentsDescription: "Коррекция цвета."
|
||||
DebugDescription: "Отладочный шейдер."
|
||||
DebugHeaderDepth: "Буфер глубины"
|
||||
DebugHeaderNormals: "Нормали"
|
||||
DisplayDepthName: "Визуализация буфера глубины"
|
||||
DisplayDepthFactorDescription: "Определяет соотношение между значением глубины пикселя и его цветом. Чем выше значение, тем ярче будет изображение."
|
||||
DisplayDepthFactorName: "Соотношение цвета"
|
||||
DisplayNormalsName: "Визуализация нормалей"
|
||||
ContrastLevelDescription: "Контрастность изображения"
|
||||
ContrastLevelName: "Контрастность"
|
||||
GammaLevelDescription: "Яркость изображения"
|
||||
GammaLevelName: "Яркость"
|
@ -1,3 +1,7 @@
|
||||
DisplayDepthDescription: "Visualiserar djupbufferten."
|
||||
DisplayDepthName: "Visualiserar djupbufferten."
|
||||
DisplayDepthFactorDescription: "Avgör korrelation mellan djupvärdet på pixeln och dess producerade färg. Högre värden ger ljusare bild."
|
||||
DisplayDepthFactorName: "Färgfaktor"
|
||||
ContrastLevelDescription: "Kontrastnivå"
|
||||
ContrastLevelName: "Kontrast"
|
||||
GammaLevelDescription: "Gammanivå"
|
||||
# GammaLevelName: "Gamma" samma som en
|
||||
|
@ -1,11 +1,7 @@
|
||||
Abovewater: "Überwasser"
|
||||
Author: "Autor"
|
||||
Configuration: "Konfiguration"
|
||||
ContrastLevelDescription: "Kontraststufe"
|
||||
ContrastLevelName: "Kontrast"
|
||||
Description: "Beschreibung"
|
||||
GammaLevelDescription: "Gamma-Level"
|
||||
GammaLevelName: "Gamma"
|
||||
InExteriors: "Außenbereich"
|
||||
InInteriors: "Innenbereich"
|
||||
KeyboardControls: |
|
||||
|
@ -1,11 +1,7 @@
|
||||
Abovewater: "Abovewater"
|
||||
Author: "Author"
|
||||
Configuration: "Configuration"
|
||||
ContrastLevelDescription: "Constrast level"
|
||||
ContrastLevelName: "Constrast"
|
||||
Description: "Description"
|
||||
GammaLevelDescription: "Gamma level"
|
||||
GammaLevelName: "Gamma"
|
||||
InExteriors: "Exteriors"
|
||||
InInteriors: "Interiors"
|
||||
KeyboardControls: |
|
||||
@ -15,7 +11,6 @@ KeyboardControls: |
|
||||
Shift+Left-Arrow > Deactive shader
|
||||
Shift+Up-Arrow > Move shader up
|
||||
Shift+Down-Arrow > Move shader down
|
||||
MainPassDescription: "Passes scene data to post processing shaders. Can not be toggled or moved."
|
||||
PostProcessHUD: "Postprocess HUD"
|
||||
ResetShader: "Reset shader to default state"
|
||||
ShaderLocked: "Locked"
|
||||
|
@ -1,11 +1,7 @@
|
||||
Abovewater: "Над водой"
|
||||
Author: "Автор"
|
||||
Configuration: "Настройки"
|
||||
ContrastLevelDescription: "Контрастность изображения"
|
||||
ContrastLevelName: "Контрастность"
|
||||
Description: "Описание"
|
||||
GammaLevelDescription: "Яркость изображения"
|
||||
GammaLevelName: "Яркость"
|
||||
InExteriors: "Вне помещений"
|
||||
InInteriors: "В помещениях"
|
||||
KeyboardControls: |
|
||||
@ -15,7 +11,6 @@ KeyboardControls: |
|
||||
Shift+Left-Arrow > Выключить шейдер
|
||||
Shift+Up-Arrow > Передвинуть шейдер выше
|
||||
Shift+Down-Arrow > Передвинуть шейдер ниже
|
||||
MainPassDescription: "Передает данные сцены в шейдеры постобработки. Не может быть выключен или передвинут."
|
||||
PostProcessHUD: "Настройки постобработки"
|
||||
ResetShader: "Обнулить настройки этого шейдера"
|
||||
ShaderLocked: "Заблокирован"
|
||||
|
@ -1,11 +1,7 @@
|
||||
Abovewater: "Ovan vatten"
|
||||
Author: "Skapare" # Author = Författare, but författare sounds very book-author-ish. Skapare, meaning "creator", sounds better in Swedish in this case. Ok?
|
||||
Configuration: "Konfiguration"
|
||||
ContrastLevelDescription: "Kontrastnivå"
|
||||
ContrastLevelName: "Kontrast"
|
||||
Description: "Beskrivning"
|
||||
GammaLevelDescription: "Gammanivå"
|
||||
# GammaLevelName: "Gamma" samma som en
|
||||
InExteriors: "Exteriörer"
|
||||
InInteriors: "Interiörer"
|
||||
KeyboardControls: |
|
||||
@ -15,7 +11,6 @@ KeyboardControls: |
|
||||
Shift+Vänsterpil > Avaktivera shader
|
||||
Shift+Pil upp > Flytta shader upp
|
||||
Shift+Pil ner > Flytta shader ner
|
||||
MainPassDescription: "Flyttar scendata till postprocess-shaders. Kan ej slås på/av eller flyttas."
|
||||
PostProcessHUD: "Postprocess HUD"
|
||||
ResetShader: "Återställ shader to ursprungligt läge"
|
||||
ShaderLocked: "Låst"
|
||||
|
@ -1,18 +1,10 @@
|
||||
main_pass {
|
||||
wrap_s = clamp_to_edge;
|
||||
wrap_t = clamp_to_edge;
|
||||
internal_format = rgba;
|
||||
source_type = unsigned_int;
|
||||
source_format = rgba;
|
||||
}
|
||||
|
||||
uniform_float uGamma {
|
||||
default = 1.0;
|
||||
step = 0.01;
|
||||
min = 0.0;
|
||||
max = 5.0;
|
||||
display_name = "#{PostProcessing:GammaLevelName}";
|
||||
description = "#{PostProcessing:GammaLevelDescription}";
|
||||
display_name = "#{BuiltInShaders:GammaLevelName}";
|
||||
description = "#{BuiltInShaders:GammaLevelDescription}";
|
||||
}
|
||||
|
||||
uniform_float uContrast {
|
||||
@ -20,8 +12,8 @@ uniform_float uContrast {
|
||||
step = 0.01;
|
||||
min = 0.0;
|
||||
max = 5.0;
|
||||
display_name = "#{PostProcessing:ContrastLevelName}";
|
||||
description = "#{PostProcessing:ContrastLevelDescription}";
|
||||
display_name = "#{BuiltInShaders:ContrastLevelName}";
|
||||
description = "#{BuiltInShaders:ContrastLevelDescription}";
|
||||
}
|
||||
|
||||
fragment main {
|
||||
@ -39,9 +31,8 @@ fragment main {
|
||||
}
|
||||
|
||||
technique {
|
||||
description = "#{PostProcessing:MainPassDescription}";
|
||||
description = "#{BuiltInShaders:AdjustmentsDescription}";
|
||||
version = "1.0";
|
||||
author = "OpenMW";
|
||||
passes = main;
|
||||
hdr = false;
|
||||
}
|
45
files/data/shaders/debug.omwfx
Normal file
45
files/data/shaders/debug.omwfx
Normal file
@ -0,0 +1,45 @@
|
||||
uniform_bool uDisplayDepth {
|
||||
header = "#{BuiltInShaders:DebugHeaderDepth}";
|
||||
default = true;
|
||||
display_name = "#{BuiltInShaders:DisplayDepthName}";
|
||||
}
|
||||
|
||||
uniform_float uDepthFactor {
|
||||
step = 0.1;
|
||||
min = 0.01;
|
||||
max = 20.0;
|
||||
default = 1.0;
|
||||
display_name = "#{BuiltInShaders:DisplayDepthFactorName}";
|
||||
description = "#{BuiltInShaders:DisplayDepthFactorDescription}";
|
||||
}
|
||||
|
||||
uniform_bool uDisplayNormals {
|
||||
header = "#{BuiltInShaders:DebugHeaderNormals}";
|
||||
default = true;
|
||||
display_name = "#{BuiltInShaders:DisplayNormalsName}";
|
||||
}
|
||||
|
||||
fragment main {
|
||||
|
||||
omw_In vec2 omw_TexCoord;
|
||||
|
||||
void main()
|
||||
{
|
||||
omw_FragColor = omw_GetLastShader(omw_TexCoord);
|
||||
|
||||
if (uDisplayDepth)
|
||||
omw_FragColor = vec4(vec3(omw_GetLinearDepth(omw_TexCoord) / omw.far * uDepthFactor), 1.0);
|
||||
#if OMW_NORMALS
|
||||
if (uDisplayNormals && (!uDisplayDepth || omw_TexCoord.x < 0.5))
|
||||
omw_FragColor.rgb = omw_GetNormals(omw_TexCoord);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
technique {
|
||||
passes = main;
|
||||
description = "#{BuiltInShaders:DebugDescription}";
|
||||
author = "OpenMW";
|
||||
version = "1.0";
|
||||
pass_normals = true;
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
uniform_float uFactor {
|
||||
step = 0.1;
|
||||
min = 0.01;
|
||||
max = 20.0;
|
||||
default = 1.0;
|
||||
display_name = "#{BuiltInShaders:DisplayDepthFactorName}";
|
||||
description = "#{BuiltInShaders:DisplayDepthFactorDescription}";
|
||||
}
|
||||
|
||||
fragment main {
|
||||
|
||||
omw_In vec2 omw_TexCoord;
|
||||
|
||||
void main()
|
||||
{
|
||||
omw_FragColor = vec4(vec3(omw_GetLinearDepth(omw_TexCoord) / omw.far * uFactor), 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
technique {
|
||||
passes = main;
|
||||
description = "#{BuiltInShaders:DisplayDepthDescription}";
|
||||
author = "OpenMW";
|
||||
version = "1.0";
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user